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