1 /*
   2  * Copyright (c) 1995, 2011, 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.Iterator;
  39 import java.util.NoSuchElementException;
  40 
  41 /** An RFC 844 or MIME message header.  Includes methods
  42     for parsing headers from incoming streams, fetching
  43     values, setting values, and printing headers.
  44     Key values of null are legal: they indicate lines in
  45     the header that don't have a valid key, but do have
  46     a value (this isn't legal according to the standard,
  47     but lines like this are everywhere). */
  48 public
  49 class MessageHeader {
  50     private String keys[];
  51     private String values[];
  52     private int nkeys;
  53 
  54     public MessageHeader () {
  55         grow();
  56     }
  57 
  58     public MessageHeader (InputStream is) throws java.io.IOException {
  59         parseHeader(is);
  60     }
  61 
  62     /**
  63      * Reset a message header (all key/values removed)
  64      */
  65     public synchronized void reset() {
  66         keys = null;
  67         values = null;
  68         nkeys = 0;
  69         grow();
  70     }
  71 
  72     /**
  73      * Find the value that corresponds to this key.
  74      * It finds only the first occurrence of the key.
  75      * @param k the key to find.
  76      * @return null if not found.
  77      */
  78     public synchronized String findValue(String k) {
  79         if (k == null) {
  80             for (int i = nkeys; --i >= 0;)
  81                 if (keys[i] == null)
  82                     return values[i];
  83         } else
  84             for (int i = nkeys; --i >= 0;) {
  85                 if (k.equalsIgnoreCase(keys[i]))
  86                     return values[i];
  87             }
  88         return null;
  89     }
  90 
  91     // return the location of the key
  92     public synchronized int getKey(String k) {
  93         for (int i = nkeys; --i >= 0;)
  94             if ((keys[i] == k) ||
  95                 (k != null && k.equalsIgnoreCase(keys[i])))
  96                 return i;
  97         return -1;
  98     }
  99 
 100     public synchronized String getKey(int n) {
 101         if (n < 0 || n >= nkeys) return null;
 102         return keys[n];
 103     }
 104 
 105     public synchronized String getValue(int n) {
 106         if (n < 0 || n >= nkeys) return null;
 107         return values[n];
 108     }
 109 
 110     /** Deprecated: Use multiValueIterator() instead.
 111      *
 112      *  Find the next value that corresponds to this key.
 113      *  It finds the first value that follows v. To iterate
 114      *  over all the values of a key use:
 115      *  <pre>
 116      *          for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
 117      *              ...
 118      *          }
 119      *  </pre>
 120      */
 121     public synchronized String findNextValue(String k, String v) {
 122         boolean foundV = false;
 123         if (k == null) {
 124             for (int i = nkeys; --i >= 0;)
 125                 if (keys[i] == null)
 126                     if (foundV)
 127                         return values[i];
 128                     else if (values[i] == v)
 129                         foundV = true;
 130         } else
 131             for (int i = nkeys; --i >= 0;)
 132                 if (k.equalsIgnoreCase(keys[i]))
 133                     if (foundV)
 134                         return values[i];
 135                     else if (values[i] == v)
 136                         foundV = true;
 137         return null;
 138     }
 139 
 140     class HeaderIterator implements Iterator<String> {
 141         int index = 0;
 142         int next = -1;
 143         String key;
 144         boolean haveNext = false;
 145         Object lock;
 146 
 147         public HeaderIterator (String k, Object lock) {
 148             key = k;
 149             this.lock = lock;
 150         }
 151         public boolean hasNext () {
 152             synchronized (lock) {
 153                 if (haveNext) {
 154                     return true;
 155                 }
 156                 while (index < nkeys) {
 157                     if (key.equalsIgnoreCase (keys[index])) {
 158                         haveNext = true;
 159                         next = index++;
 160                         return true;
 161                     }
 162                     index ++;
 163                 }
 164                 return false;
 165             }
 166         }
 167         public String next() {
 168             synchronized (lock) {
 169                 if (haveNext) {
 170                     haveNext = false;
 171                     return values [next];
 172                 }
 173                 if (hasNext()) {
 174                     return next();
 175                 } else {
 176                     throw new NoSuchElementException ("No more elements");
 177                 }
 178             }
 179         }
 180         public void remove () {
 181             throw new UnsupportedOperationException ("remove not allowed");
 182         }
 183     }
 184 
 185     /**
 186      * return an Iterator that returns all values of a particular
 187      * key in sequence
 188      */
 189     public Iterator<String> multiValueIterator (String k) {
 190         return new HeaderIterator (k, this);
 191     }
 192 
 193     public synchronized Map<String, List<String>> getHeaders() {
 194         return getHeaders(null);
 195     }
 196 
 197     public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
 198         return filterAndAddHeaders(excludeList, null);
 199     }
 200 
 201     public synchronized Map<String, List<String>> filterAndAddHeaders(
 202             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                 for (Map.Entry<String,List<String>> entry: include.entrySet()) {
 232                 List<String> l = m.get(entry.getKey());
 233                 if (l == null) {
 234                     l = new ArrayList<String>();
 235                     m.put(entry.getKey(), l);
 236                 }
 237                 l.addAll(entry.getValue());
 238             }
 239         }
 240 
 241         for (String key : m.keySet()) {
 242             m.put(key, Collections.unmodifiableList(m.get(key)));
 243         }
 244 
 245         return Collections.unmodifiableMap(m);
 246     }
 247 
 248     /** Prints the key-value pairs represented by this
 249         header.  Also prints the RFC required blank line
 250         at the end. Omits pairs with a null key. */
 251     public synchronized void print(PrintStream p) {
 252         for (int i = 0; i < nkeys; i++)
 253             if (keys[i] != null) {
 254                 p.print(keys[i] +
 255                     (values[i] != null ? ": "+values[i]: "") + "\r\n");
 256             }
 257         p.print("\r\n");
 258         p.flush();
 259     }
 260 
 261     /** Adds a key value pair to the end of the
 262         header.  Duplicates are allowed */
 263     public synchronized void add(String k, String v) {
 264         grow();
 265         keys[nkeys] = k;
 266         values[nkeys] = v;
 267         nkeys++;
 268     }
 269 
 270     /** Prepends a key value pair to the beginning of the
 271         header.  Duplicates are allowed */
 272     public synchronized void prepend(String k, String v) {
 273         grow();
 274         for (int i = nkeys; i > 0; i--) {
 275             keys[i] = keys[i-1];
 276             values[i] = values[i-1];
 277         }
 278         keys[0] = k;
 279         values[0] = v;
 280         nkeys++;
 281     }
 282 
 283     /** Overwrite the previous key/val pair at location 'i'
 284      * with the new k/v.  If the index didn't exist before
 285      * the key/val is simply tacked onto the end.
 286      */
 287 
 288     public synchronized void set(int i, String k, String v) {
 289         grow();
 290         if (i < 0) {
 291             return;
 292         } else if (i >= nkeys) {
 293             add(k, v);
 294         } else {
 295             keys[i] = k;
 296             values[i] = v;
 297         }
 298     }
 299 
 300 
 301     /** grow the key/value arrays as needed */
 302 
 303     private void grow() {
 304         if (keys == null || nkeys >= keys.length) {
 305             String[] nk = new String[nkeys + 4];
 306             String[] nv = new String[nkeys + 4];
 307             if (keys != null)
 308                 System.arraycopy(keys, 0, nk, 0, nkeys);
 309             if (values != null)
 310                 System.arraycopy(values, 0, nv, 0, nkeys);
 311             keys = nk;
 312             values = nv;
 313         }
 314     }
 315 
 316     /**
 317      * Remove the key from the header. If there are multiple values under
 318      * the same key, they are all removed.
 319      * Nothing is done if the key doesn't exist.
 320      * After a remove, the other pairs' order are not changed.
 321      * @param k the key to remove
 322      */
 323     public synchronized void remove(String k) {
 324         if(k == null) {
 325             for (int i = 0; i < nkeys; i++) {
 326                 while (keys[i] == null && i < nkeys) {
 327                     for(int j=i; j<nkeys-1; j++) {
 328                         keys[j] = keys[j+1];
 329                         values[j] = values[j+1];
 330                     }
 331                     nkeys--;
 332                 }
 333             }
 334         } else {
 335             for (int i = 0; i < nkeys; i++) {
 336                 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
 337                     for(int j=i; j<nkeys-1; j++) {
 338                         keys[j] = keys[j+1];
 339                         values[j] = values[j+1];
 340                     }
 341                     nkeys--;
 342                 }
 343             }
 344         }
 345     }
 346 
 347     /** Sets the value of a key.  If the key already
 348         exists in the header, it's value will be
 349         changed.  Otherwise a new key/value pair will
 350         be added to the end of the header. */
 351     public synchronized void set(String k, String v) {
 352         for (int i = nkeys; --i >= 0;)
 353             if (k.equalsIgnoreCase(keys[i])) {
 354                 values[i] = v;
 355                 return;
 356             }
 357         add(k, v);
 358     }
 359 
 360     /** Set's the value of a key only if there is no
 361      *  key with that value already.
 362      */
 363 
 364     public synchronized void setIfNotSet(String k, String v) {
 365         if (findValue(k) == null) {
 366             add(k, v);
 367         }
 368     }
 369 
 370     /** Convert a message-id string to canonical form (strips off
 371         leading and trailing <>s) */
 372     public static String canonicalID(String id) {
 373         if (id == null)
 374             return "";
 375         int st = 0;
 376         int len = id.length();
 377         boolean substr = false;
 378         int c;
 379         while (st < len && ((c = id.charAt(st)) == '<' ||
 380                             c <= ' ')) {
 381             st++;
 382             substr = true;
 383         }
 384         while (st < len && ((c = id.charAt(len - 1)) == '>' ||
 385                             c <= ' ')) {
 386             len--;
 387             substr = true;
 388         }
 389         return substr ? id.substring(st, len) : id;
 390     }
 391 
 392     /** Parse a MIME header from an input stream. */
 393     public void parseHeader(InputStream is) throws java.io.IOException {
 394         synchronized (this) {
 395             nkeys = 0;
 396         }
 397         mergeHeader(is);
 398     }
 399 
 400     /** Parse and merge a MIME header from an input stream. */
 401     @SuppressWarnings("fallthrough")
 402     public void mergeHeader(InputStream is) throws java.io.IOException {
 403         if (is == null)
 404             return;
 405         char s[] = new char[10];
 406         int firstc = is.read();
 407         while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
 408             int len = 0;
 409             int keyend = -1;
 410             int c;
 411             boolean inKey = firstc > ' ';
 412             s[len++] = (char) firstc;
 413     parseloop:{
 414                 while ((c = is.read()) >= 0) {
 415                     switch (c) {
 416                       case ':':
 417                         if (inKey && len > 0)
 418                             keyend = len;
 419                         inKey = false;
 420                         break;
 421                       case '\t':
 422                         c = ' ';
 423                       /*fall through*/
 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 }