1 /*
2  * Copyright (c) 1995, 2010, 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 
MessageHeader()55     public MessageHeader () {
56         grow();
57     }
58 
MessageHeader(InputStream is)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      */
reset()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      */
findValue(String k)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
getKey(String k)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 
getKey(int n)101     public synchronized String getKey(int n) {
102         if (n < 0 || n >= nkeys) return null;
103         return keys[n];
104     }
105 
getValue(int n)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      */
findNextValue(String k, String v)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     /**
142      * Removes bare Negotiate and Kerberos headers when an "NTLM ..."
143      * appears. All Performed on headers with key being k.
144      * @return true if there is a change
145      */
filterNTLMResponses(String k)146     public boolean filterNTLMResponses(String k) {
147         boolean found = false;
148         for (int i=0; i<nkeys; i++) {
149             if (k.equalsIgnoreCase(keys[i])
150                     && values[i] != null && values[i].length() > 5
151                     && values[i].regionMatches(true, 0, "NTLM ", 0, 5)) {
152                 found = true;
153                 break;
154             }
155         }
156         if (found) {
157             int j = 0;
158             for (int i=0; i<nkeys; i++) {
159                 if (k.equalsIgnoreCase(keys[i]) && (
160                         "Negotiate".equalsIgnoreCase(values[i]) ||
161                         "Kerberos".equalsIgnoreCase(values[i]))) {
162                     continue;
163                 }
164                 if (i != j) {
165                     keys[j] = keys[i];
166                     values[j] = values[i];
167                 }
168                 j++;
169             }
170             if (j != nkeys) {
171                 nkeys = j;
172                 return true;
173             }
174         }
175         return false;
176     }
177 
178     class HeaderIterator implements Iterator<String> {
179         int index = 0;
180         int next = -1;
181         String key;
182         boolean haveNext = false;
183         Object lock;
184 
HeaderIterator(String k, Object lock)185         public HeaderIterator (String k, Object lock) {
186             key = k;
187             this.lock = lock;
188         }
hasNext()189         public boolean hasNext () {
190             synchronized (lock) {
191                 if (haveNext) {
192                     return true;
193                 }
194                 while (index < nkeys) {
195                     if (key.equalsIgnoreCase (keys[index])) {
196                         haveNext = true;
197                         next = index++;
198                         return true;
199                     }
200                     index ++;
201                 }
202                 return false;
203             }
204         }
next()205         public String next() {
206             synchronized (lock) {
207                 if (haveNext) {
208                     haveNext = false;
209                     return values [next];
210                 }
211                 if (hasNext()) {
212                     return next();
213                 } else {
214                     throw new NoSuchElementException ("No more elements");
215                 }
216             }
217         }
remove()218         public void remove () {
219             throw new UnsupportedOperationException ("remove not allowed");
220         }
221     }
222 
223     /**
224      * return an Iterator that returns all values of a particular
225      * key in sequence
226      */
multiValueIterator(String k)227     public Iterator<String> multiValueIterator (String k) {
228         return new HeaderIterator (k, this);
229     }
230 
getHeaders()231     public synchronized Map<String, List<String>> getHeaders() {
232         return getHeaders(null);
233     }
234 
getHeaders(String[] excludeList)235     public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
236         return filterAndAddHeaders(excludeList, null);
237     }
238 
filterAndAddHeaders(String[] excludeList, Map<String, List<String>> include)239     public synchronized Map<String, List<String>> filterAndAddHeaders(String[] excludeList, Map<String, List<String>>  include) {
240         boolean skipIt = false;
241         Map<String, List<String>> m = new HashMap<String, List<String>>();
242         for (int i = nkeys; --i >= 0;) {
243             if (excludeList != null) {
244                 // check if the key is in the excludeList.
245                 // if so, don't include it in the Map.
246                 for (int j = 0; j < excludeList.length; j++) {
247                     if ((excludeList[j] != null) &&
248                         (excludeList[j].equalsIgnoreCase(keys[i]))) {
249                         skipIt = true;
250                         break;
251                     }
252                 }
253             }
254             if (!skipIt) {
255                 List<String> l = m.get(keys[i]);
256                 if (l == null) {
257                     l = new ArrayList<String>();
258                     m.put(keys[i], l);
259                 }
260                 l.add(values[i]);
261             } else {
262                 // reset the flag
263                 skipIt = false;
264             }
265         }
266 
267         if (include != null) {
268             Iterator entries = include.entrySet().iterator();
269             while (entries.hasNext()) {
270                 Map.Entry entry = (Map.Entry)entries.next();
271                 List l = (List)m.get(entry.getKey());
272                 if (l == null) {
273                     l = new ArrayList();
274                     m.put((String)entry.getKey(), l);
275                 }
276                 l.add(entry.getValue());
277             }
278         }
279 
280         for (String key : m.keySet()) {
281             m.put(key, Collections.unmodifiableList(m.get(key)));
282         }
283 
284         return Collections.unmodifiableMap(m);
285     }
286 
287     /** Prints the key-value pairs represented by this
288         header.  Also prints the RFC required blank line
289         at the end. Omits pairs with a null key. */
print(PrintStream p)290     public synchronized void print(PrintStream p) {
291         for (int i = 0; i < nkeys; i++)
292             if (keys[i] != null) {
293                 p.print(keys[i] +
294                     (values[i] != null ? ": "+values[i]: "") + "\r\n");
295             }
296         p.print("\r\n");
297         p.flush();
298     }
299 
300     /** Adds a key value pair to the end of the
301         header.  Duplicates are allowed */
add(String k, String v)302     public synchronized void add(String k, String v) {
303         grow();
304         keys[nkeys] = k;
305         values[nkeys] = v;
306         nkeys++;
307     }
308 
309     /** Prepends a key value pair to the beginning of the
310         header.  Duplicates are allowed */
prepend(String k, String v)311     public synchronized void prepend(String k, String v) {
312         grow();
313         for (int i = nkeys; i > 0; i--) {
314             keys[i] = keys[i-1];
315             values[i] = values[i-1];
316         }
317         keys[0] = k;
318         values[0] = v;
319         nkeys++;
320     }
321 
322     /** Overwrite the previous key/val pair at location 'i'
323      * with the new k/v.  If the index didn't exist before
324      * the key/val is simply tacked onto the end.
325      */
326 
set(int i, String k, String v)327     public synchronized void set(int i, String k, String v) {
328         grow();
329         if (i < 0) {
330             return;
331         } else if (i >= nkeys) {
332             add(k, v);
333         } else {
334             keys[i] = k;
335             values[i] = v;
336         }
337     }
338 
339 
340     /** grow the key/value arrays as needed */
341 
grow()342     private void grow() {
343         if (keys == null || nkeys >= keys.length) {
344             String[] nk = new String[nkeys + 4];
345             String[] nv = new String[nkeys + 4];
346             if (keys != null)
347                 System.arraycopy(keys, 0, nk, 0, nkeys);
348             if (values != null)
349                 System.arraycopy(values, 0, nv, 0, nkeys);
350             keys = nk;
351             values = nv;
352         }
353     }
354 
355     /**
356      * Remove the key from the header. If there are multiple values under
357      * the same key, they are all removed.
358      * Nothing is done if the key doesn't exist.
359      * After a remove, the other pairs' order are not changed.
360      * @param k the key to remove
361      */
remove(String k)362     public synchronized void remove(String k) {
363         if(k == null) {
364             for (int i = 0; i < nkeys; i++) {
365                 while (keys[i] == null && i < nkeys) {
366                     for(int j=i; j<nkeys-1; j++) {
367                         keys[j] = keys[j+1];
368                         values[j] = values[j+1];
369                     }
370                     nkeys--;
371                 }
372             }
373         } else {
374             for (int i = 0; i < nkeys; i++) {
375                 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
376                     for(int j=i; j<nkeys-1; j++) {
377                         keys[j] = keys[j+1];
378                         values[j] = values[j+1];
379                     }
380                     nkeys--;
381                 }
382             }
383         }
384     }
385 
386     /** Sets the value of a key.  If the key already
387         exists in the header, it's value will be
388         changed.  Otherwise a new key/value pair will
389         be added to the end of the header. */
set(String k, String v)390     public synchronized void set(String k, String v) {
391         for (int i = nkeys; --i >= 0;)
392             if (k.equalsIgnoreCase(keys[i])) {
393                 values[i] = v;
394                 return;
395             }
396         add(k, v);
397     }
398 
399     /** Set's the value of a key only if there is no
400      *  key with that value already.
401      */
402 
setIfNotSet(String k, String v)403     public synchronized void setIfNotSet(String k, String v) {
404         if (findValue(k) == null) {
405             add(k, v);
406         }
407     }
408 
409     /** Convert a message-id string to canonical form (strips off
410         leading and trailing <>s) */
canonicalID(String id)411     public static String canonicalID(String id) {
412         if (id == null)
413             return "";
414         int st = 0;
415         int len = id.length();
416         boolean substr = false;
417         int c;
418         while (st < len && ((c = id.charAt(st)) == '<' ||
419                             c <= ' ')) {
420             st++;
421             substr = true;
422         }
423         while (st < len && ((c = id.charAt(len - 1)) == '>' ||
424                             c <= ' ')) {
425             len--;
426             substr = true;
427         }
428         return substr ? id.substring(st, len) : id;
429     }
430 
431     /** Parse a MIME header from an input stream. */
parseHeader(InputStream is)432     public void parseHeader(InputStream is) throws java.io.IOException {
433         synchronized (this) {
434             nkeys = 0;
435         }
436         mergeHeader(is);
437     }
438 
439     /** Parse and merge a MIME header from an input stream. */
mergeHeader(InputStream is)440     public void mergeHeader(InputStream is) throws java.io.IOException {
441         if (is == null)
442             return;
443         char s[] = new char[10];
444         int firstc = is.read();
445         while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
446             int len = 0;
447             int keyend = -1;
448             int c;
449             boolean inKey = firstc > ' ';
450             s[len++] = (char) firstc;
451     parseloop:{
452                 while ((c = is.read()) >= 0) {
453                     switch (c) {
454                       case ':':
455                         if (inKey && len > 0)
456                             keyend = len;
457                         inKey = false;
458                         break;
459                       case '\t':
460                         c = ' ';
461                       case ' ':
462                         inKey = false;
463                         break;
464                       case '\r':
465                       case '\n':
466                         firstc = is.read();
467                         if (c == '\r' && firstc == '\n') {
468                             firstc = is.read();
469                             if (firstc == '\r')
470                                 firstc = is.read();
471                         }
472                         if (firstc == '\n' || firstc == '\r' || firstc > ' ')
473                             break parseloop;
474                         /* continuation */
475                         c = ' ';
476                         break;
477                     }
478                     if (len >= s.length) {
479                         char ns[] = new char[s.length * 2];
480                         System.arraycopy(s, 0, ns, 0, len);
481                         s = ns;
482                     }
483                     s[len++] = (char) c;
484                 }
485                 firstc = -1;
486             }
487             while (len > 0 && s[len - 1] <= ' ')
488                 len--;
489             String k;
490             if (keyend <= 0) {
491                 k = null;
492                 keyend = 0;
493             } else {
494                 k = String.copyValueOf(s, 0, keyend);
495                 if (keyend < len && s[keyend] == ':')
496                     keyend++;
497                 while (keyend < len && s[keyend] <= ' ')
498                     keyend++;
499             }
500             String v;
501             if (keyend >= len)
502                 v = new String();
503             else
504                 v = String.copyValueOf(s, keyend, len - keyend);
505             add(k, v);
506         }
507     }
508 
toString()509     public synchronized String toString() {
510         String result = super.toString() + nkeys + " pairs: ";
511         for (int i = 0; i < keys.length && i < nkeys; i++) {
512             result += "{"+keys[i]+": "+values[i]+"}";
513         }
514         return result;
515     }
516 }
517