Home | History | Annotate | Download | only in www
      1 /*
      2  * Copyright (c) 1995, 2013, 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.*;
     35 
     36 /** An RFC 844 or MIME message header.  Includes methods
     37     for parsing headers from incoming streams, fetching
     38     values, setting values, and printing headers.
     39     Key values of null are legal: they indicate lines in
     40     the header that don't have a valid key, but do have
     41     a value (this isn't legal according to the standard,
     42     but lines like this are everywhere). */
     43 public
     44 class MessageHeader {
     45     private String keys[];
     46     private String values[];
     47     private int nkeys;
     48 
     49     public MessageHeader () {
     50         grow();
     51     }
     52 
     53     public MessageHeader (InputStream is) throws java.io.IOException {
     54         parseHeader(is);
     55     }
     56 
     57     /**
     58      * Returns list of header names in a comma separated list
     59      */
     60     public synchronized String getHeaderNamesInList() {
     61         StringJoiner joiner = new StringJoiner(",");
     62         for (int i=0; i<nkeys; i++) {
     63             joiner.add(keys[i]);
     64         }
     65         return joiner.toString();
     66     }
     67 
     68     /**
     69      * Reset a message header (all key/values removed)
     70      */
     71     public synchronized void reset() {
     72         keys = null;
     73         values = null;
     74         nkeys = 0;
     75         grow();
     76     }
     77 
     78     /**
     79      * Find the value that corresponds to this key.
     80      * It finds only the first occurrence of the key.
     81      * @param k the key to find.
     82      * @return null if not found.
     83      */
     84     public synchronized String findValue(String k) {
     85         if (k == null) {
     86             for (int i = nkeys; --i >= 0;)
     87                 if (keys[i] == null)
     88                     return values[i];
     89         } else
     90             for (int i = nkeys; --i >= 0;) {
     91                 if (k.equalsIgnoreCase(keys[i]))
     92                     return values[i];
     93             }
     94         return null;
     95     }
     96 
     97     // return the location of the key
     98     public synchronized int getKey(String k) {
     99         for (int i = nkeys; --i >= 0;)
    100             if ((keys[i] == k) ||
    101                 (k != null && k.equalsIgnoreCase(keys[i])))
    102                 return i;
    103         return -1;
    104     }
    105 
    106     public synchronized String getKey(int n) {
    107         if (n < 0 || n >= nkeys) return null;
    108         return keys[n];
    109     }
    110 
    111     public synchronized String getValue(int n) {
    112         if (n < 0 || n >= nkeys) return null;
    113         return values[n];
    114     }
    115 
    116     /** Deprecated: Use multiValueIterator() instead.
    117      *
    118      *  Find the next value that corresponds to this key.
    119      *  It finds the first value that follows v. To iterate
    120      *  over all the values of a key use:
    121      *  <pre>
    122      *          for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) {
    123      *              ...
    124      *          }
    125      *  </pre>
    126      */
    127     public synchronized String findNextValue(String k, String v) {
    128         boolean foundV = false;
    129         if (k == null) {
    130             for (int i = nkeys; --i >= 0;)
    131                 if (keys[i] == null)
    132                     if (foundV)
    133                         return values[i];
    134                     else if (values[i] == v)
    135                         foundV = true;
    136         } else
    137             for (int i = nkeys; --i >= 0;)
    138                 if (k.equalsIgnoreCase(keys[i]))
    139                     if (foundV)
    140                         return values[i];
    141                     else if (values[i] == v)
    142                         foundV = true;
    143         return null;
    144     }
    145 
    146     /**
    147      * Removes bare Negotiate and Kerberos headers when an "NTLM ..."
    148      * appears. All Performed on headers with key being k.
    149      * @return true if there is a change
    150      */
    151     public boolean filterNTLMResponses(String k) {
    152         boolean found = false;
    153         for (int i=0; i<nkeys; i++) {
    154             if (k.equalsIgnoreCase(keys[i])
    155                     && values[i] != null && values[i].length() > 5
    156                     && values[i].substring(0, 5).equalsIgnoreCase("NTLM ")) {
    157                 found = true;
    158                 break;
    159             }
    160         }
    161         if (found) {
    162             int j = 0;
    163             for (int i=0; i<nkeys; i++) {
    164                 if (k.equalsIgnoreCase(keys[i]) && (
    165                         "Negotiate".equalsIgnoreCase(values[i]) ||
    166                         "Kerberos".equalsIgnoreCase(values[i]))) {
    167                     continue;
    168                 }
    169                 if (i != j) {
    170                     keys[j] = keys[i];
    171                     values[j] = values[i];
    172                 }
    173                 j++;
    174             }
    175             if (j != nkeys) {
    176                 nkeys = j;
    177                 return true;
    178             }
    179         }
    180         return false;
    181     }
    182 
    183     class HeaderIterator implements Iterator<String> {
    184         int index = 0;
    185         int next = -1;
    186         String key;
    187         boolean haveNext = false;
    188         Object lock;
    189 
    190         public HeaderIterator (String k, Object lock) {
    191             key = k;
    192             this.lock = lock;
    193         }
    194         public boolean hasNext () {
    195             synchronized (lock) {
    196                 if (haveNext) {
    197                     return true;
    198                 }
    199                 while (index < nkeys) {
    200                     if (key.equalsIgnoreCase (keys[index])) {
    201                         haveNext = true;
    202                         next = index++;
    203                         return true;
    204                     }
    205                     index ++;
    206                 }
    207                 return false;
    208             }
    209         }
    210         public String next() {
    211             synchronized (lock) {
    212                 if (haveNext) {
    213                     haveNext = false;
    214                     return values [next];
    215                 }
    216                 if (hasNext()) {
    217                     return next();
    218                 } else {
    219                     throw new NoSuchElementException ("No more elements");
    220                 }
    221             }
    222         }
    223         public void remove () {
    224             throw new UnsupportedOperationException ("remove not allowed");
    225         }
    226     }
    227 
    228     /**
    229      * return an Iterator that returns all values of a particular
    230      * key in sequence
    231      */
    232     public Iterator<String> multiValueIterator (String k) {
    233         return new HeaderIterator (k, this);
    234     }
    235 
    236     public synchronized Map<String, List<String>> getHeaders() {
    237         return getHeaders(null);
    238     }
    239 
    240     public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
    241         return filterAndAddHeaders(excludeList, null);
    242     }
    243 
    244     public synchronized Map<String, List<String>> filterAndAddHeaders(
    245             String[] excludeList, Map<String, List<String>>  include) {
    246         boolean skipIt = false;
    247         Map<String, List<String>> m = new HashMap<String, List<String>>();
    248         for (int i = nkeys; --i >= 0;) {
    249             if (excludeList != null) {
    250                 // check if the key is in the excludeList.
    251                 // if so, don't include it in the Map.
    252                 for (int j = 0; j < excludeList.length; j++) {
    253                     if ((excludeList[j] != null) &&
    254                         (excludeList[j].equalsIgnoreCase(keys[i]))) {
    255                         skipIt = true;
    256                         break;
    257                     }
    258                 }
    259             }
    260             if (!skipIt) {
    261                 List<String> l = m.get(keys[i]);
    262                 if (l == null) {
    263                     l = new ArrayList<String>();
    264                     m.put(keys[i], l);
    265                 }
    266                 l.add(values[i]);
    267             } else {
    268                 // reset the flag
    269                 skipIt = false;
    270             }
    271         }
    272 
    273         if (include != null) {
    274                 for (Map.Entry<String,List<String>> entry: include.entrySet()) {
    275                 List<String> l = m.get(entry.getKey());
    276                 if (l == null) {
    277                     l = new ArrayList<String>();
    278                     m.put(entry.getKey(), l);
    279                 }
    280                 l.addAll(entry.getValue());
    281             }
    282         }
    283 
    284         for (String key : m.keySet()) {
    285             m.put(key, Collections.unmodifiableList(m.get(key)));
    286         }
    287 
    288         return Collections.unmodifiableMap(m);
    289     }
    290 
    291     /** Prints the key-value pairs represented by this
    292         header.  Also prints the RFC required blank line
    293         at the end. Omits pairs with a null key. */
    294     public synchronized void print(PrintStream p) {
    295         for (int i = 0; i < nkeys; i++)
    296             if (keys[i] != null) {
    297                 p.print(keys[i] +
    298                     (values[i] != null ? ": "+values[i]: "") + "\r\n");
    299             }
    300         p.print("\r\n");
    301         p.flush();
    302     }
    303 
    304     /** Adds a key value pair to the end of the
    305         header.  Duplicates are allowed */
    306     public synchronized void add(String k, String v) {
    307         grow();
    308         keys[nkeys] = k;
    309         values[nkeys] = v;
    310         nkeys++;
    311     }
    312 
    313     /** Prepends a key value pair to the beginning of the
    314         header.  Duplicates are allowed */
    315     public synchronized void prepend(String k, String v) {
    316         grow();
    317         for (int i = nkeys; i > 0; i--) {
    318             keys[i] = keys[i-1];
    319             values[i] = values[i-1];
    320         }
    321         keys[0] = k;
    322         values[0] = v;
    323         nkeys++;
    324     }
    325 
    326     /** Overwrite the previous key/val pair at location 'i'
    327      * with the new k/v.  If the index didn't exist before
    328      * the key/val is simply tacked onto the end.
    329      */
    330 
    331     public synchronized void set(int i, String k, String v) {
    332         grow();
    333         if (i < 0) {
    334             return;
    335         } else if (i >= nkeys) {
    336             add(k, v);
    337         } else {
    338             keys[i] = k;
    339             values[i] = v;
    340         }
    341     }
    342 
    343 
    344     /** grow the key/value arrays as needed */
    345 
    346     private void grow() {
    347         if (keys == null || nkeys >= keys.length) {
    348             String[] nk = new String[nkeys + 4];
    349             String[] nv = new String[nkeys + 4];
    350             if (keys != null)
    351                 System.arraycopy(keys, 0, nk, 0, nkeys);
    352             if (values != null)
    353                 System.arraycopy(values, 0, nv, 0, nkeys);
    354             keys = nk;
    355             values = nv;
    356         }
    357     }
    358 
    359     /**
    360      * Remove the key from the header. If there are multiple values under
    361      * the same key, they are all removed.
    362      * Nothing is done if the key doesn't exist.
    363      * After a remove, the other pairs' order are not changed.
    364      * @param k the key to remove
    365      */
    366     public synchronized void remove(String k) {
    367         if(k == null) {
    368             for (int i = 0; i < nkeys; i++) {
    369                 while (keys[i] == null && i < nkeys) {
    370                     for(int j=i; j<nkeys-1; j++) {
    371                         keys[j] = keys[j+1];
    372                         values[j] = values[j+1];
    373                     }
    374                     nkeys--;
    375                 }
    376             }
    377         } else {
    378             for (int i = 0; i < nkeys; i++) {
    379                 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
    380                     for(int j=i; j<nkeys-1; j++) {
    381                         keys[j] = keys[j+1];
    382                         values[j] = values[j+1];
    383                     }
    384                     nkeys--;
    385                 }
    386             }
    387         }
    388     }
    389 
    390     /** Sets the value of a key.  If the key already
    391         exists in the header, it's value will be
    392         changed.  Otherwise a new key/value pair will
    393         be added to the end of the header. */
    394     public synchronized void set(String k, String v) {
    395         for (int i = nkeys; --i >= 0;)
    396             if (k.equalsIgnoreCase(keys[i])) {
    397                 values[i] = v;
    398                 return;
    399             }
    400         add(k, v);
    401     }
    402 
    403     /** Set's the value of a key only if there is no
    404      *  key with that value already.
    405      */
    406 
    407     public synchronized void setIfNotSet(String k, String v) {
    408         if (findValue(k) == null) {
    409             add(k, v);
    410         }
    411     }
    412 
    413     /** Convert a message-id string to canonical form (strips off
    414         leading and trailing <>s) */
    415     public static String canonicalID(String id) {
    416         if (id == null)
    417             return "";
    418         int st = 0;
    419         int len = id.length();
    420         boolean substr = false;
    421         int c;
    422         while (st < len && ((c = id.charAt(st)) == '<' ||
    423                             c <= ' ')) {
    424             st++;
    425             substr = true;
    426         }
    427         while (st < len && ((c = id.charAt(len - 1)) == '>' ||
    428                             c <= ' ')) {
    429             len--;
    430             substr = true;
    431         }
    432         return substr ? id.substring(st, len) : id;
    433     }
    434 
    435     /** Parse a MIME header from an input stream. */
    436     public void parseHeader(InputStream is) throws java.io.IOException {
    437         synchronized (this) {
    438             nkeys = 0;
    439         }
    440         mergeHeader(is);
    441     }
    442 
    443     /** Parse and merge a MIME header from an input stream. */
    444     @SuppressWarnings("fallthrough")
    445     public void mergeHeader(InputStream is) throws java.io.IOException {
    446         if (is == null)
    447             return;
    448         char s[] = new char[10];
    449         int firstc = is.read();
    450         while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
    451             int len = 0;
    452             int keyend = -1;
    453             int c;
    454             boolean inKey = firstc > ' ';
    455             s[len++] = (char) firstc;
    456     parseloop:{
    457                 while ((c = is.read()) >= 0) {
    458                     switch (c) {
    459                       case ':':
    460                         if (inKey && len > 0)
    461                             keyend = len;
    462                         inKey = false;
    463                         break;
    464                       case '\t':
    465                         c = ' ';
    466                       /*fall through*/
    467                       case ' ':
    468                         inKey = false;
    469                         break;
    470                       case '\r':
    471                       case '\n':
    472                         firstc = is.read();
    473                         if (c == '\r' && firstc == '\n') {
    474                             firstc = is.read();
    475                             if (firstc == '\r')
    476                                 firstc = is.read();
    477                         }
    478                         if (firstc == '\n' || firstc == '\r' || firstc > ' ')
    479                             break parseloop;
    480                         /* continuation */
    481                         c = ' ';
    482                         break;
    483                     }
    484                     if (len >= s.length) {
    485                         char ns[] = new char[s.length * 2];
    486                         System.arraycopy(s, 0, ns, 0, len);
    487                         s = ns;
    488                     }
    489                     s[len++] = (char) c;
    490                 }
    491                 firstc = -1;
    492             }
    493             while (len > 0 && s[len - 1] <= ' ')
    494                 len--;
    495             String k;
    496             if (keyend <= 0) {
    497                 k = null;
    498                 keyend = 0;
    499             } else {
    500                 k = String.copyValueOf(s, 0, keyend);
    501                 if (keyend < len && s[keyend] == ':')
    502                     keyend++;
    503                 while (keyend < len && s[keyend] <= ' ')
    504                     keyend++;
    505             }
    506             String v;
    507             if (keyend >= len)
    508                 v = new String();
    509             else
    510                 v = String.copyValueOf(s, keyend, len - keyend);
    511             add(k, v);
    512         }
    513     }
    514 
    515     public synchronized String toString() {
    516         String result = super.toString() + nkeys + " pairs: ";
    517         for (int i = 0; i < keys.length && i < nkeys; i++) {
    518             result += "{"+keys[i]+": "+values[i]+"}";
    519         }
    520         return result;
    521     }
    522 }
    523