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 
MessageHeader()49     public MessageHeader () {
50         grow();
51     }
52 
MessageHeader(InputStream is)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      */
getHeaderNamesInList()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      */
reset()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      */
findValue(String k)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
getKey(String k)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 
getKey(int n)106     public synchronized String getKey(int n) {
107         if (n < 0 || n >= nkeys) return null;
108         return keys[n];
109     }
110 
getValue(int n)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      */
findNextValue(String k, String v)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      */
filterNTLMResponses(String k)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 
HeaderIterator(String k, Object lock)190         public HeaderIterator (String k, Object lock) {
191             key = k;
192             this.lock = lock;
193         }
hasNext()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         }
next()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         }
remove()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      */
multiValueIterator(String k)232     public Iterator<String> multiValueIterator (String k) {
233         return new HeaderIterator (k, this);
234     }
235 
getHeaders()236     public synchronized Map<String, List<String>> getHeaders() {
237         return getHeaders(null);
238     }
239 
getHeaders(String[] excludeList)240     public synchronized Map<String, List<String>> getHeaders(String[] excludeList) {
241         return filterAndAddHeaders(excludeList, null);
242     }
243 
filterAndAddHeaders( String[] excludeList, Map<String, List<String>> include)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<>();
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<>();
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<>();
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     /** Check if a line of message header looks like a request line.
292      * This method does not perform a full validation but simply
293      * returns false if the line does not end with 'HTTP/[1-9].[0-9]'
294      * @param line the line to check.
295      * @return true if the line might be a request line.
296      */
isRequestline(String line)297     private boolean isRequestline(String line) {
298         String k = line.trim();
299         int i = k.lastIndexOf(' ');
300         if (i <= 0) return false;
301         int len = k.length();
302         if (len - i < 9) return false;
303 
304         char c1 = k.charAt(len-3);
305         char c2 = k.charAt(len-2);
306         char c3 = k.charAt(len-1);
307         if (c1 < '1' || c1 > '9') return false;
308         if (c2 != '.') return false;
309         if (c3 < '0' || c3 > '9') return false;
310 
311         return (k.substring(i+1, len-3).equalsIgnoreCase("HTTP/"));
312     }
313 
314 
315     /** Prints the key-value pairs represented by this
316         header. Also prints the RFC required blank line
317         at the end. Omits pairs with a null key. Omits
318         colon if key-value pair is the requestline. */
print(PrintStream p)319     public synchronized void print(PrintStream p) {
320         for (int i = 0; i < nkeys; i++)
321             if (keys[i] != null) {
322                 StringBuilder sb = new StringBuilder(keys[i]);
323                 if (values[i] != null) {
324                     sb.append(": " + values[i]);
325                 } else if (i != 0 || !isRequestline(keys[i])) {
326                     sb.append(":");
327                 }
328                 p.print(sb.append("\r\n"));
329             }
330         p.print("\r\n");
331         p.flush();
332     }
333 
334     /** Adds a key value pair to the end of the
335         header.  Duplicates are allowed */
add(String k, String v)336     public synchronized void add(String k, String v) {
337         grow();
338         keys[nkeys] = k;
339         values[nkeys] = v;
340         nkeys++;
341     }
342 
343     /** Prepends a key value pair to the beginning of the
344         header.  Duplicates are allowed */
prepend(String k, String v)345     public synchronized void prepend(String k, String v) {
346         grow();
347         for (int i = nkeys; i > 0; i--) {
348             keys[i] = keys[i-1];
349             values[i] = values[i-1];
350         }
351         keys[0] = k;
352         values[0] = v;
353         nkeys++;
354     }
355 
356     /** Overwrite the previous key/val pair at location 'i'
357      * with the new k/v.  If the index didn't exist before
358      * the key/val is simply tacked onto the end.
359      */
360 
set(int i, String k, String v)361     public synchronized void set(int i, String k, String v) {
362         grow();
363         if (i < 0) {
364             return;
365         } else if (i >= nkeys) {
366             add(k, v);
367         } else {
368             keys[i] = k;
369             values[i] = v;
370         }
371     }
372 
373 
374     /** grow the key/value arrays as needed */
375 
grow()376     private void grow() {
377         if (keys == null || nkeys >= keys.length) {
378             String[] nk = new String[nkeys + 4];
379             String[] nv = new String[nkeys + 4];
380             if (keys != null)
381                 System.arraycopy(keys, 0, nk, 0, nkeys);
382             if (values != null)
383                 System.arraycopy(values, 0, nv, 0, nkeys);
384             keys = nk;
385             values = nv;
386         }
387     }
388 
389     /**
390      * Remove the key from the header. If there are multiple values under
391      * the same key, they are all removed.
392      * Nothing is done if the key doesn't exist.
393      * After a remove, the other pairs' order are not changed.
394      * @param k the key to remove
395      */
remove(String k)396     public synchronized void remove(String k) {
397         if(k == null) {
398             for (int i = 0; i < nkeys; i++) {
399                 while (keys[i] == null && i < nkeys) {
400                     for(int j=i; j<nkeys-1; j++) {
401                         keys[j] = keys[j+1];
402                         values[j] = values[j+1];
403                     }
404                     nkeys--;
405                 }
406             }
407         } else {
408             for (int i = 0; i < nkeys; i++) {
409                 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) {
410                     for(int j=i; j<nkeys-1; j++) {
411                         keys[j] = keys[j+1];
412                         values[j] = values[j+1];
413                     }
414                     nkeys--;
415                 }
416             }
417         }
418     }
419 
420     /** Sets the value of a key.  If the key already
421         exists in the header, it's value will be
422         changed.  Otherwise a new key/value pair will
423         be added to the end of the header. */
set(String k, String v)424     public synchronized void set(String k, String v) {
425         for (int i = nkeys; --i >= 0;)
426             if (k.equalsIgnoreCase(keys[i])) {
427                 values[i] = v;
428                 return;
429             }
430         add(k, v);
431     }
432 
433     /** Set's the value of a key only if there is no
434      *  key with that value already.
435      */
436 
setIfNotSet(String k, String v)437     public synchronized void setIfNotSet(String k, String v) {
438         if (findValue(k) == null) {
439             add(k, v);
440         }
441     }
442 
443     /** Convert a message-id string to canonical form (strips off
444         leading and trailing {@literal <>s}) */
canonicalID(String id)445     public static String canonicalID(String id) {
446         if (id == null)
447             return "";
448         int st = 0;
449         int len = id.length();
450         boolean substr = false;
451         int c;
452         while (st < len && ((c = id.charAt(st)) == '<' ||
453                             c <= ' ')) {
454             st++;
455             substr = true;
456         }
457         while (st < len && ((c = id.charAt(len - 1)) == '>' ||
458                             c <= ' ')) {
459             len--;
460             substr = true;
461         }
462         return substr ? id.substring(st, len) : id;
463     }
464 
465     /** Parse a MIME header from an input stream. */
parseHeader(InputStream is)466     public void parseHeader(InputStream is) throws java.io.IOException {
467         synchronized (this) {
468             nkeys = 0;
469         }
470         mergeHeader(is);
471     }
472 
473     /** Parse and merge a MIME header from an input stream. */
474     @SuppressWarnings("fallthrough")
mergeHeader(InputStream is)475     public void mergeHeader(InputStream is) throws java.io.IOException {
476         if (is == null)
477             return;
478         char s[] = new char[10];
479         int firstc = is.read();
480         while (firstc != '\n' && firstc != '\r' && firstc >= 0) {
481             int len = 0;
482             int keyend = -1;
483             int c;
484             boolean inKey = firstc > ' ';
485             s[len++] = (char) firstc;
486     parseloop:{
487                 while ((c = is.read()) >= 0) {
488                     switch (c) {
489                       case ':':
490                         if (inKey && len > 0)
491                             keyend = len;
492                         inKey = false;
493                         break;
494                       case '\t':
495                         c = ' ';
496                       /*fall through*/
497                       case ' ':
498                         inKey = false;
499                         break;
500                       case '\r':
501                       case '\n':
502                         firstc = is.read();
503                         if (c == '\r' && firstc == '\n') {
504                             firstc = is.read();
505                             if (firstc == '\r')
506                                 firstc = is.read();
507                         }
508                         if (firstc == '\n' || firstc == '\r' || firstc > ' ')
509                             break parseloop;
510                         /* continuation */
511                         c = ' ';
512                         break;
513                     }
514                     if (len >= s.length) {
515                         char ns[] = new char[s.length * 2];
516                         System.arraycopy(s, 0, ns, 0, len);
517                         s = ns;
518                     }
519                     s[len++] = (char) c;
520                 }
521                 firstc = -1;
522             }
523             while (len > 0 && s[len - 1] <= ' ')
524                 len--;
525             String k;
526             if (keyend <= 0) {
527                 k = null;
528                 keyend = 0;
529             } else {
530                 k = String.copyValueOf(s, 0, keyend);
531                 if (keyend < len && s[keyend] == ':')
532                     keyend++;
533                 while (keyend < len && s[keyend] <= ' ')
534                     keyend++;
535             }
536             String v;
537             if (keyend >= len)
538                 v = new String();
539             else
540                 v = String.copyValueOf(s, keyend, len - keyend);
541             add(k, v);
542         }
543     }
544 
toString()545     public synchronized String toString() {
546         String result = super.toString() + nkeys + " pairs: ";
547         for (int i = 0; i < keys.length && i < nkeys; i++) {
548             result += "{"+keys[i]+": "+values[i]+"}";
549         }
550         return result;
551     }
552 }
553