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