1 /* 2 * Copyright (c) 1995, 2011, 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.Iterator; 39 import java.util.NoSuchElementException; 40 41 /** An RFC 844 or MIME message header. Includes methods 42 for parsing headers from incoming streams, fetching 43 values, setting values, and printing headers. 44 Key values of null are legal: they indicate lines in 45 the header that don't have a valid key, but do have 46 a value (this isn't legal according to the standard, 47 but lines like this are everywhere). */ 48 public 49 class MessageHeader { 50 private String keys[]; 51 private String values[]; 52 private int nkeys; 53 54 public MessageHeader () { 55 grow(); 56 } 57 58 public MessageHeader (InputStream is) throws java.io.IOException { 59 parseHeader(is); 60 } 61 62 /** 63 * Reset a message header (all key/values removed) 64 */ 65 public synchronized void reset() { 66 keys = null; 67 values = null; 68 nkeys = 0; 69 grow(); 70 } 71 72 /** 73 * Find the value that corresponds to this key. 74 * It finds only the first occurrence of the key. 75 * @param k the key to find. 76 * @return null if not found. 77 */ 78 public synchronized String findValue(String k) { 79 if (k == null) { 80 for (int i = nkeys; --i >= 0;) 81 if (keys[i] == null) 82 return values[i]; 83 } else 84 for (int i = nkeys; --i >= 0;) { 85 if (k.equalsIgnoreCase(keys[i])) 86 return values[i]; 87 } 88 return null; 89 } 90 91 // return the location of the key 92 public synchronized int getKey(String k) { 93 for (int i = nkeys; --i >= 0;) 94 if ((keys[i] == k) || 95 (k != null && k.equalsIgnoreCase(keys[i]))) 96 return i; 97 return -1; 98 } 99 100 public synchronized String getKey(int n) { 101 if (n < 0 || n >= nkeys) return null; 102 return keys[n]; 103 } 104 105 public synchronized String getValue(int n) { 106 if (n < 0 || n >= nkeys) return null; 107 return values[n]; 108 } 109 110 /** Deprecated: Use multiValueIterator() instead. 111 * 112 * Find the next value that corresponds to this key. 113 * It finds the first value that follows v. To iterate 114 * over all the values of a key use: 115 * <pre> 116 * for(String v=h.findValue(k); v!=null; v=h.findNextValue(k, v)) { 117 * ... 118 * } 119 * </pre> 120 */ 121 public synchronized String findNextValue(String k, String v) { 122 boolean foundV = false; 123 if (k == null) { 124 for (int i = nkeys; --i >= 0;) 125 if (keys[i] == null) 126 if (foundV) 127 return values[i]; 128 else if (values[i] == v) 129 foundV = true; 130 } else 131 for (int i = nkeys; --i >= 0;) 132 if (k.equalsIgnoreCase(keys[i])) 133 if (foundV) 134 return values[i]; 135 else if (values[i] == v) 136 foundV = true; 137 return null; 138 } 139 140 class HeaderIterator implements Iterator<String> { 141 int index = 0; 142 int next = -1; 143 String key; 144 boolean haveNext = false; 145 Object lock; 146 147 public HeaderIterator (String k, Object lock) { 148 key = k; 149 this.lock = lock; 150 } 151 public boolean hasNext () { 152 synchronized (lock) { 153 if (haveNext) { 154 return true; 155 } 156 while (index < nkeys) { 157 if (key.equalsIgnoreCase (keys[index])) { 158 haveNext = true; 159 next = index++; 160 return true; 161 } 162 index ++; 163 } 164 return false; 165 } 166 } 167 public String next() { 168 synchronized (lock) { 169 if (haveNext) { 170 haveNext = false; 171 return values [next]; 172 } 173 if (hasNext()) { 174 return next(); 175 } else { 176 throw new NoSuchElementException ("No more elements"); 177 } 178 } 179 } 180 public void remove () { 181 throw new UnsupportedOperationException ("remove not allowed"); 182 } 183 } 184 185 /** 186 * return an Iterator that returns all values of a particular 187 * key in sequence 188 */ 189 public Iterator<String> multiValueIterator (String k) { 190 return new HeaderIterator (k, this); 191 } 192 193 public synchronized Map<String, List<String>> getHeaders() { 194 return getHeaders(null); 195 } 196 197 public synchronized Map<String, List<String>> getHeaders(String[] excludeList) { 198 return filterAndAddHeaders(excludeList, null); 199 } 200 201 public synchronized Map<String, List<String>> filterAndAddHeaders( 202 String[] excludeList, Map<String, List<String>> include) { 203 boolean skipIt = false; 204 Map<String, List<String>> m = new HashMap<String, List<String>>(); 205 for (int i = nkeys; --i >= 0;) { 206 if (excludeList != null) { 207 // check if the key is in the excludeList. 208 // if so, don't include it in the Map. 209 for (int j = 0; j < excludeList.length; j++) { 210 if ((excludeList[j] != null) && 211 (excludeList[j].equalsIgnoreCase(keys[i]))) { 212 skipIt = true; 213 break; 214 } 215 } 216 } 217 if (!skipIt) { 218 List<String> l = m.get(keys[i]); 219 if (l == null) { 220 l = new ArrayList<String>(); 221 m.put(keys[i], l); 222 } 223 l.add(values[i]); 224 } else { 225 // reset the flag 226 skipIt = false; 227 } 228 } 229 230 if (include != null) { 231 for (Map.Entry<String,List<String>> entry: include.entrySet()) { 232 List<String> l = m.get(entry.getKey()); 233 if (l == null) { 234 l = new ArrayList<String>(); 235 m.put(entry.getKey(), l); 236 } 237 l.addAll(entry.getValue()); 238 } 239 } 240 241 for (String key : m.keySet()) { 242 m.put(key, Collections.unmodifiableList(m.get(key))); 243 } 244 245 return Collections.unmodifiableMap(m); 246 } 247 248 /** Prints the key-value pairs represented by this 249 header. Also prints the RFC required blank line 250 at the end. Omits pairs with a null key. */ 251 public synchronized void print(PrintStream p) { 252 for (int i = 0; i < nkeys; i++) 253 if (keys[i] != null) { 254 p.print(keys[i] + 255 (values[i] != null ? ": "+values[i]: "") + "\r\n"); 256 } 257 p.print("\r\n"); 258 p.flush(); 259 } 260 261 /** Adds a key value pair to the end of the 262 header. Duplicates are allowed */ 263 public synchronized void add(String k, String v) { 264 grow(); 265 keys[nkeys] = k; 266 values[nkeys] = v; 267 nkeys++; 268 } 269 270 /** Prepends a key value pair to the beginning of the 271 header. Duplicates are allowed */ 272 public synchronized void prepend(String k, String v) { 273 grow(); 274 for (int i = nkeys; i > 0; i--) { 275 keys[i] = keys[i-1]; 276 values[i] = values[i-1]; 277 } 278 keys[0] = k; 279 values[0] = v; 280 nkeys++; 281 } 282 283 /** Overwrite the previous key/val pair at location 'i' 284 * with the new k/v. If the index didn't exist before 285 * the key/val is simply tacked onto the end. 286 */ 287 288 public synchronized void set(int i, String k, String v) { 289 grow(); 290 if (i < 0) { 291 return; 292 } else if (i >= nkeys) { 293 add(k, v); 294 } else { 295 keys[i] = k; 296 values[i] = v; 297 } 298 } 299 300 301 /** grow the key/value arrays as needed */ 302 303 private void grow() { 304 if (keys == null || nkeys >= keys.length) { 305 String[] nk = new String[nkeys + 4]; 306 String[] nv = new String[nkeys + 4]; 307 if (keys != null) 308 System.arraycopy(keys, 0, nk, 0, nkeys); 309 if (values != null) 310 System.arraycopy(values, 0, nv, 0, nkeys); 311 keys = nk; 312 values = nv; 313 } 314 } 315 316 /** 317 * Remove the key from the header. If there are multiple values under 318 * the same key, they are all removed. 319 * Nothing is done if the key doesn't exist. 320 * After a remove, the other pairs' order are not changed. 321 * @param k the key to remove 322 */ 323 public synchronized void remove(String k) { 324 if(k == null) { 325 for (int i = 0; i < nkeys; i++) { 326 while (keys[i] == null && i < nkeys) { 327 for(int j=i; j<nkeys-1; j++) { 328 keys[j] = keys[j+1]; 329 values[j] = values[j+1]; 330 } 331 nkeys--; 332 } 333 } 334 } else { 335 for (int i = 0; i < nkeys; i++) { 336 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) { 337 for(int j=i; j<nkeys-1; j++) { 338 keys[j] = keys[j+1]; 339 values[j] = values[j+1]; 340 } 341 nkeys--; 342 } 343 } 344 } 345 } 346 347 /** Sets the value of a key. If the key already 348 exists in the header, it's value will be 349 changed. Otherwise a new key/value pair will 350 be added to the end of the header. */ 351 public synchronized void set(String k, String v) { 352 for (int i = nkeys; --i >= 0;) 353 if (k.equalsIgnoreCase(keys[i])) { 354 values[i] = v; 355 return; 356 } 357 add(k, v); 358 } 359 360 /** Set's the value of a key only if there is no 361 * key with that value already. 362 */ 363 364 public synchronized void setIfNotSet(String k, String v) { 365 if (findValue(k) == null) { 366 add(k, v); 367 } 368 } 369 370 /** Convert a message-id string to canonical form (strips off 371 leading and trailing <>s) */ 372 public static String canonicalID(String id) { 373 if (id == null) 374 return ""; 375 int st = 0; 376 int len = id.length(); 377 boolean substr = false; 378 int c; 379 while (st < len && ((c = id.charAt(st)) == '<' || 380 c <= ' ')) { 381 st++; 382 substr = true; 383 } 384 while (st < len && ((c = id.charAt(len - 1)) == '>' || 385 c <= ' ')) { 386 len--; 387 substr = true; 388 } 389 return substr ? id.substring(st, len) : id; 390 } 391 392 /** Parse a MIME header from an input stream. */ 393 public void parseHeader(InputStream is) throws java.io.IOException { 394 synchronized (this) { 395 nkeys = 0; 396 } 397 mergeHeader(is); 398 } 399 400 /** Parse and merge a MIME header from an input stream. */ 401 @SuppressWarnings("fallthrough") 402 public void mergeHeader(InputStream is) throws java.io.IOException { 403 if (is == null) 404 return; 405 char s[] = new char[10]; 406 int firstc = is.read(); 407 while (firstc != '\n' && firstc != '\r' && firstc >= 0) { 408 int len = 0; 409 int keyend = -1; 410 int c; 411 boolean inKey = firstc > ' '; 412 s[len++] = (char) firstc; 413 parseloop:{ 414 while ((c = is.read()) >= 0) { 415 switch (c) { 416 case ':': 417 if (inKey && len > 0) 418 keyend = len; 419 inKey = false; 420 break; 421 case '\t': 422 c = ' '; 423 /*fall through*/ 424 case ' ': 425 inKey = false; 426 break; 427 case '\r': 428 case '\n': 429 firstc = is.read(); 430 if (c == '\r' && firstc == '\n') { 431 firstc = is.read(); 432 if (firstc == '\r') 433 firstc = is.read(); 434 } 435 if (firstc == '\n' || firstc == '\r' || firstc > ' ') 436 break parseloop; 437 /* continuation */ 438 c = ' '; 439 break; 440 } 441 if (len >= s.length) { 442 char ns[] = new char[s.length * 2]; 443 System.arraycopy(s, 0, ns, 0, len); 444 s = ns; 445 } 446 s[len++] = (char) c; 447 } 448 firstc = -1; 449 } 450 while (len > 0 && s[len - 1] <= ' ') 451 len--; 452 String k; 453 if (keyend <= 0) { 454 k = null; 455 keyend = 0; 456 } else { 457 k = String.copyValueOf(s, 0, keyend); 458 if (keyend < len && s[keyend] == ':') 459 keyend++; 460 while (keyend < len && s[keyend] <= ' ') 461 keyend++; 462 } 463 String v; 464 if (keyend >= len) 465 v = new String(); 466 else 467 v = String.copyValueOf(s, keyend, len - keyend); 468 add(k, v); 469 } 470 } 471 472 public synchronized String toString() { 473 String result = super.toString() + nkeys + " pairs: "; 474 for (int i = 0; i < keys.length && i < nkeys; i++) { 475 result += "{"+keys[i]+": "+values[i]+"}"; 476 } 477 return result; 478 } 479 }