1 /*
   2  * Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /*-
  27  *      news stream opener
  28  */
  29 
  30 package sun.net.www;
  31 
  32 import java.io.*;
  33 import java.util.Collections;
  34 import java.util.Map;
  35 import java.util.HashMap;
  36 import java.util.List;
  37 import java.util.ArrayList;
  38 import java.util.Set;
  39 import java.util.Iterator;
  40 import java.util.NoSuchElementException;
  41 
  42 /** An RFC 844 or MIME message header.  Includes methods
  43     for parsing headers from incoming streams, fetching
  44     values, setting values, and printing headers.
  45     Key values of null are legal: they indicate lines in
  46     the header that don't have a valid key, but do have
  47     a value (this isn't legal according to the standard,
  48     but lines like this are everywhere). */
  49 public
  50 class MessageHeader {
  51     private String keys[];
  52     private String values[];
  53     private int nkeys;
  54 
  55     public MessageHeader () {
  56         grow();
  57     }
  58 
  59     public MessageHeader (InputStream is) throws java.io.IOException {
  60         parseHeader(is);
  61     }
  62 
  63     /**
  64      * Reset a message header (all key/values removed)
  65      */
  66     public synchronized void reset() {
  67         keys = null;
  68         values = null;
  69         nkeys = 0;
  70         grow();
  71     }
  72 
  73     /**
  74      * Find the value that corresponds to this key.
  75      * It finds only the first occurrence of the key.
  76      * @param k the key to find.
  77      * @return null if not found.
  78      */
  79     public synchronized String findValue(String k) {
  80         if (k == null) {
  81             for (int i = nkeys; --i >= 0;)
  82                 if (keys[i] == null)
  83                     return values[i];
  84         } else
  85             for (int i = nkeys; --i >= 0;) {
  86                 if (k.equalsIgnoreCase(keys[i]))
  87                     return values[i];
  88             }
  89         return null;
  90     }
  91 
  92     // return the location of the key
  93     public synchronized int getKey(String k) {
  94         for (int i = nkeys; --i >= 0;)
  95             if ((keys[i] == k) ||
  96                 (k != null && k.equalsIgnoreCase(keys[i])))
  97                 return i;
  98         return -1;
  99     }
 100 
 101     public synchronized String getKey(int n) {
 102         if (n < 0 || n >= nkeys) return null;
 103         return keys[n];
 104     }
 105 
 106     public synchronized String getValue(int n) {
 107         if (n < 0 || n >= nkeys) return null;
 108         return values[n];
 109     }
 110 
 111     /** Deprecated: Use multiValueIterator() instead.
 112      *
 113      *  Find the next value that corresponds to this key.
 114      *  It finds the first value that follows v. To iterate
 115      *  over all the values of a key use:
 116      *  <pre>
 117      *          for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
 118      *              ...
 119      *          }
 120      *  </pre>
 121      */
 122     public synchronized String findNextValue(String k, String v) {
 123         boolean foundV = false;
 124         if (k == null) {
 125             for (int i = nkeys; --i >= 0;)
 126                 if (keys[i] == null)
 127                     if (foundV)
 128                         return values[i];
 129                     else if (values[i] == v)
 130                         foundV = true;
 131         } else
 132             for (int i = nkeys; --i >= 0;)
 133                 if (k.equalsIgnoreCase(keys[i]))
 134                     if (foundV)
 135                         return values[i];
 136                     else if (values[i] == v)
 137                         foundV = true;
 138         return null;
 139     }
 140 
 141     class HeaderIterator implements Iterator<String> {
 142         int index = 0;
 143         int next = -1;
 144         String key;
 145         boolean haveNext = false;
 146         Object lock;
 147 
 148         public HeaderIterator (String k, Object lock) {
 149             key = k;
 150             this.lock = lock;
 151         }
 152         public boolean hasNext () {
 153             synchronized (lock) {
 154                 if (haveNext) {
 155                     return true;
 156                 }
 157                 while (index < nkeys) {
 158                     if (key.equalsIgnoreCase (keys[index])) {
 159                         haveNext = true;
 160                         next = index++;
 161                         return true;
 162                     }
 163                     index ++;
 164                 }
 165                 return false;
 166             }
 167         }
 168         public String next() {
 169             synchronized (lock) {
 170                 if (haveNext) {
 171                     haveNext = false;
 172                     return values [next];
 173                 }
 174                 if (hasNext()) {
 175                     return next();
 176                 } else {
 177                     throw new NoSuchElementException ("No more elements");
 178                 }
 179             }
 180         }
 181         public void remove () {
 182             throw new UnsupportedOperationException ("remove not allowed");
 183         }
 184     }
 185 
 186     /**
 187      * return an Iterator that returns all values of a particular
 188      * key in sequence
 189      */
 190     public Iterator<String> multiValueIterator (String k) {
 191         return new HeaderIterator (k, this);
 192     }
 193 
 194     public synchronized Map<String, List<String>> getHeaders() {
 195         return getHeaders(null);
 196     }
 197 
 198     public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
 199         return filterAndAddHeaders(excludeList, null);
 200     }
 201 
 202     public synchronized Map<String, List<String>> filterAndAddHeaders(String[] excludeList, Map<String, List<String>>  include) {
 203         boolean skipIt = false;
 204         Map<String, List<String>> m = new HashMap<String, List<String>>();
 205         for (int i = nkeys; --i >= 0;) {
 206             if (excludeList != null) {
 207                 // check if the key is in the excludeList.
 208                 // if so, don't include it in the Map.
 209                 for (int j = 0; j < excludeList.length; j++) {
 210                     if ((excludeList[j] != null) &&
 211                         (excludeList[j].equalsIgnoreCase(keys[i]))) {
 212                         skipIt = true;
 213                         break;
 214                     }
 215                 }
 216             }
 217             if (!skipIt) {
 218                 List<String> l = m.get(keys[i]);
 219                 if (l == null) {
 220                     l = new ArrayList<String>();
 221                     m.put(keys[i], l);
 222                 }
 223                 l.add(values[i]);
 224             } else {
 225                 // reset the flag
 226                 skipIt = false;
 227             }
 228         }
 229 
 230         if (include != null) {
 231             Iterator entries = include.entrySet().iterator();
 232             while (entries.hasNext()) {
 233                 Map.Entry entry = (Map.Entry)entries.next();
 234                 List l = (List)m.get(entry.getKey());
 235                 if (l == null) {
 236                     l = new ArrayList();
 237                     m.put((String)entry.getKey(), l);
 238                 }
 239                 l.add(entry.getValue());
 240             }
 241         }
 242 
 243         for (String key : m.keySet()) {
 244             m.put(key, Collections.unmodifiableList(m.get(key)));
 245         }
 246 
 247         return Collections.unmodifiableMap(m);
 248     }
 249 
 250     /** Prints the key-value pairs represented by this
 251         header.  Also prints the RFC required blank line
 252         at the end. Omits pairs with a null key. */
 253     public synchronized void print(PrintStream p) {
 254         for (int i = 0; i < nkeys; i++)
 255             if (keys[i] != null) {
 256                 p.print(keys[i] +
 257                     (values[i] != null ? ": "+values[i]: "") + "\r\n");
 258             }
 259         p.print("\r\n");
 260         p.flush();
 261     }
 262 
 263     /** Adds a key value pair to the end of the
 264         header.  Duplicates are allowed */
 265     public synchronized void add(String k, String v) {
 266         grow();
 267         keys[nkeys] = k;
 268         values[nkeys] = v;
 269         nkeys++;
 270     }
 271 
 272     /** Prepends a key value pair to the beginning of the
 273         header.  Duplicates are allowed */
 274     public synchronized void prepend(String k, String v) {
 275         grow();
 276         for (int i = nkeys; i > 0; i--) {
 277             keys[i] = keys[i-1];
 278             values[i] = values[i-1];
 279         }
 280         keys[0] = k;
 281         values[0] = v;
 282         nkeys++;
 283     }
 284 
 285     /** Overwrite the previous key/val pair at location 'i'
 286      * with the new k/v.  If the index didn't exist before
 287      * the key/val is simply tacked onto the end.
 288      */
 289 
 290     public synchronized void set(int i, String k, String v) {
 291         grow();
 292         if (i < 0) {
 293             return;
 294         } else if (i >= nkeys) {
 295             add(k, v);
 296         } else {
 297             keys[i] = k;
 298             values[i] = v;
 299         }
 300     }
 301 
 302 
 303     /** grow the key/value arrays as needed */
 304 
 305     private void grow() {
 306         if (keys == null || nkeys >= keys.length) {
 307             String[] nk = new String[nkeys + 4];
 308             String[] nv = new String[nkeys + 4];
 309             if (keys != null)
 310                 System.arraycopy(keys, 0, nk, 0, nkeys);
 311             if (values != null)
 312                 System.arraycopy(values, 0, nv, 0, nkeys);
 313             keys = nk;
 314             values = nv;
 315         }
 316     }
 317 
 318     /**
 319      * Remove the key from the header. If there are multiple values under
 320      * the same key, they are all removed.
 321      * Nothing is done if the key doesn't exist.
 322      * After a remove, the other pairs' order are not changed.
 323      * @param k the key to remove
 324      */
 325     public synchronized void remove(String k) {
 326         if(k == null) {
 327             for (int i = 0; i < nkeys; i++) {
 328                 while (keys[i] == null && i < nkeys) {
 329                     for(int j=i; j<nkeys-1; j++) {
 330                         keys[j] = keys[j+1];
 331                         values[j] = values[j+1];
 332                     }
 333                     nkeys--;
 334                 }
 335             }
 336         } else {
 337             for (int i = 0; i < nkeys; i++) {
 338                 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
 339                     for(int j=i; j<nkeys-1; j++) {
 340                         keys[j] = keys[j+1];
 341                         values[j] = values[j+1];
 342                     }
 343                     nkeys--;
 344                 }
 345             }
 346         }
 347     }
 348 
 349     /** Sets the value of a key.  If the key already
 350         exists in the header, it's value will be
 351         changed.  Otherwise a new key/value pair will
 352         be added to the end of the header. */
 353     public synchronized void set(String k, String v) {
 354         for (int i = nkeys; --i >= 0;)
 355             if (k.equalsIgnoreCase(keys[i])) {
 356                 values[i] = v;
 357                 return;
 358             }
 359         add(k, v);
 360     }
 361 
 362     /** Set's the value of a key only if there is no
 363      *  key with that value already.
 364      */
 365 
 366     public synchronized void setIfNotSet(String k, String v) {
 367         if (findValue(k) == null) {
 368             add(k, v);
 369         }
 370     }
 371 
 372     /** Convert a message-id string to canonical form (strips off
 373         leading and trailing <>s) */
 374     public static String canonicalID(String id) {
 375         if (id == null)
 376             return "";
 377         int st = 0;
 378         int len = id.length();
 379         boolean substr = false;
 380         int c;
 381         while (st < len && ((c = id.charAt(st)) == '<' ||
 382                             c <= ' ')) {
 383             st++;
 384             substr = true;
 385         }
 386         while (st < len && ((c = id.charAt(len - 1)) == '>' ||
 387                             c <= ' ')) {
 388             len--;
 389             substr = true;
 390         }
 391         return substr ? id.substring(st, len) : id;
 392     }
 393 
 394     /** Parse a MIME header from an input stream. */
 395     public void parseHeader(InputStream is) throws java.io.IOException {
 396         synchronized (this) {
 397             nkeys = 0;
 398         }
 399         mergeHeader(is);
 400     }
 401 
 402     /** Parse and merge a MIME header from an input stream. */
 403     public void mergeHeader(InputStream is) throws java.io.IOException {
 404         if (is == null)
 405             return;
 406         char s[] = new char[10];
 407         int firstc = is.read();
 408         while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
 409             int len = 0;
 410             int keyend = -1;
 411             int c;
 412             boolean inKey = firstc > ' ';
 413             s[len++] = (char) firstc;
 414     parseloop:{
 415                 while ((c = is.read()) >= 0) {
 416                     switch (c) {
 417                       case ':':
 418                         if (inKey && len > 0)
 419                             keyend = len;
 420                         inKey = false;
 421                         break;
 422                       case '\t':
 423                         c = ' ';
 424                       case ' ':
 425                         inKey = false;
 426                         break;
 427                       case '\r':
 428                       case '\n':
 429                         firstc = is.read();
 430                         if (c == '\r' && firstc == '\n') {
 431                             firstc = is.read();
 432                             if (firstc == '\r')
 433                                 firstc = is.read();
 434                         }
 435                         if (firstc == '\n' || firstc == '\r' || firstc > ' ')
 436                             break parseloop;
 437                         /* continuation */
 438                         c = ' ';
 439                         break;
 440                     }
 441                     if (len >= s.length) {
 442                         char ns[] = new char[s.length * 2];
 443                         System.arraycopy(s, 0, ns, 0, len);
 444                         s = ns;
 445                     }
 446                     s[len++] = (char) c;
 447                 }
 448                 firstc = -1;
 449             }
 450             while (len > 0 && s[len - 1] <= ' ')
 451                 len--;
 452             String k;
 453             if (keyend <= 0) {
 454                 k = null;
 455                 keyend = 0;
 456             } else {
 457                 k = String.copyValueOf(s, 0, keyend);
 458                 if (keyend < len && s[keyend] == ':')
 459                     keyend++;
 460                 while (keyend < len && s[keyend] <= ' ')
 461                     keyend++;
 462             }
 463             String v;
 464             if (keyend >= len)
 465                 v = new String();
 466             else
 467                 v = String.copyValueOf(s, keyend, len - keyend);
 468             add(k, v);
 469         }
 470     }
 471 
 472     public synchronized String toString() {
 473         String result = super.toString() + nkeys + " pairs: ";
 474         for (int i = 0; i < keys.length && i < nkeys; i++) {
 475             result += "{"+keys[i]+": "+values[i]+"}";
 476         }
 477         return result;
 478     }
 479 }