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<>();
 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     /** 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 }