View Javadoc
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 }