1 /* 2 * Copyright (c) 1995, 2008, 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 55 public MessageHeader () { 56 grow(); 57 } 58 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 */ 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 */ 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 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 101 public synchronized String getKey(int n) { 102 if (n < 0 || n >= nkeys) return null; 103 return keys[n]; 104 } 105 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 */ 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 class HeaderIterator implements Iterator<String> { 142 int index = 0; 143 int next = -1; 144 String key; 145 boolean haveNext = false; 146 Object lock; 147 148 public HeaderIterator (String k, Object lock) { 149 key = k; 150 this.lock = lock; 151 } 152 public boolean hasNext () { 153 synchronized (lock) { 154 if (haveNext) { 155 return true; 156 } 157 while (index < nkeys) { 158 if (key.equalsIgnoreCase (keys[index])) { 159 haveNext = true; 160 next = index++; 161 return true; 162 } 163 index ++; 164 } 165 return false; 166 } 167 } 168 public String next() { 169 synchronized (lock) { 170 if (haveNext) { 171 haveNext = false; 172 return values [next]; 173 } 174 if (hasNext()) { 175 return next(); 176 } else { 177 throw new NoSuchElementException ("No more elements"); 178 } 179 } 180 } 181 public void remove () { 182 throw new UnsupportedOperationException ("remove not allowed"); 183 } 184 } 185 186 /** 187 * return an Iterator that returns all values of a particular 188 * key in sequence 189 */ 190 public Iterator<String> multiValueIterator (String k) { 191 return new HeaderIterator (k, this); 192 } 193 194 public synchronized Map<String, List<String>> getHeaders() { 195 return getHeaders(null); 196 } 197 198 public synchronized Map<String, List<String>> getHeaders(String[] excludeList) { 199 return filterAndAddHeaders(excludeList, null); 200 } 201 202 public synchronized Map<String, List<String>> filterAndAddHeaders(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 Iterator entries = include.entrySet().iterator(); 232 while (entries.hasNext()) { 233 Map.Entry entry = (Map.Entry)entries.next(); 234 List l = (List)m.get(entry.getKey()); 235 if (l == null) { 236 l = new ArrayList(); 237 m.put((String)entry.getKey(), l); 238 } 239 l.add(entry.getValue()); 240 } 241 } 242 243 for (String key : m.keySet()) { 244 m.put(key, Collections.unmodifiableList(m.get(key))); 245 } 246 247 return Collections.unmodifiableMap(m); 248 } 249 250 /** Prints the key-value pairs represented by this 251 header. Also prints the RFC required blank line 252 at the end. Omits pairs with a null key. */ 253 public synchronized void print(PrintStream p) { 254 for (int i = 0; i < nkeys; i++) 255 if (keys[i] != null) { 256 p.print(keys[i] + 257 (values[i] != null ? ": "+values[i]: "") + "\r\n"); 258 } 259 p.print("\r\n"); 260 p.flush(); 261 } 262 263 /** Adds a key value pair to the end of the 264 header. Duplicates are allowed */ 265 public synchronized void add(String k, String v) { 266 grow(); 267 keys[nkeys] = k; 268 values[nkeys] = v; 269 nkeys++; 270 } 271 272 /** Prepends a key value pair to the beginning of the 273 header. Duplicates are allowed */ 274 public synchronized void prepend(String k, String v) { 275 grow(); 276 for (int i = nkeys; i > 0; i--) { 277 keys[i] = keys[i-1]; 278 values[i] = values[i-1]; 279 } 280 keys[0] = k; 281 values[0] = v; 282 nkeys++; 283 } 284 285 /** Overwrite the previous key/val pair at location 'i' 286 * with the new k/v. If the index didn't exist before 287 * the key/val is simply tacked onto the end. 288 */ 289 290 public synchronized void set(int i, String k, String v) { 291 grow(); 292 if (i < 0) { 293 return; 294 } else if (i >= nkeys) { 295 add(k, v); 296 } else { 297 keys[i] = k; 298 values[i] = v; 299 } 300 } 301 302 303 /** grow the key/value arrays as needed */ 304 305 private void grow() { 306 if (keys == null || nkeys >= keys.length) { 307 String[] nk = new String[nkeys + 4]; 308 String[] nv = new String[nkeys + 4]; 309 if (keys != null) 310 System.arraycopy(keys, 0, nk, 0, nkeys); 311 if (values != null) 312 System.arraycopy(values, 0, nv, 0, nkeys); 313 keys = nk; 314 values = nv; 315 } 316 } 317 318 /** 319 * Remove the key from the header. If there are multiple values under 320 * the same key, they are all removed. 321 * Nothing is done if the key doesn't exist. 322 * After a remove, the other pairs' order are not changed. 323 * @param k the key to remove 324 */ 325 public synchronized void remove(String k) { 326 if(k == null) { 327 for (int i = 0; i < nkeys; i++) { 328 while (keys[i] == null && i < nkeys) { 329 for(int j=i; j<nkeys-1; j++) { 330 keys[j] = keys[j+1]; 331 values[j] = values[j+1]; 332 } 333 nkeys--; 334 } 335 } 336 } else { 337 for (int i = 0; i < nkeys; i++) { 338 while (k.equalsIgnoreCase(keys[i]) && i < nkeys) { 339 for(int j=i; j<nkeys-1; j++) { 340 keys[j] = keys[j+1]; 341 values[j] = values[j+1]; 342 } 343 nkeys--; 344 } 345 } 346 } 347 } 348 349 /** Sets the value of a key. If the key already 350 exists in the header, it's value will be 351 changed. Otherwise a new key/value pair will 352 be added to the end of the header. */ 353 public synchronized void set(String k, String v) { 354 for (int i = nkeys; --i >= 0;) 355 if (k.equalsIgnoreCase(keys[i])) { 356 values[i] = v; 357 return; 358 } 359 add(k, v); 360 } 361 362 /** Set's the value of a key only if there is no 363 * key with that value already. 364 */ 365 366 public synchronized void setIfNotSet(String k, String v) { 367 if (findValue(k) == null) { 368 add(k, v); 369 } 370 } 371 372 /** Convert a message-id string to canonical form (strips off 373 leading and trailing <>s) */ 374 public static String canonicalID(String id) { 375 if (id == null) 376 return ""; 377 int st = 0; 378 int len = id.length(); 379 boolean substr = false; 380 int c; 381 while (st < len && ((c = id.charAt(st)) == '<' || 382 c <= ' ')) { 383 st++; 384 substr = true; 385 } 386 while (st < len && ((c = id.charAt(len - 1)) == '>' || 387 c <= ' ')) { 388 len--; 389 substr = true; 390 } 391 return substr ? id.substring(st, len) : id; 392 } 393 394 /** Parse a MIME header from an input stream. */ 395 public void parseHeader(InputStream is) throws java.io.IOException { 396 synchronized (this) { 397 nkeys = 0; 398 } 399 mergeHeader(is); 400 } 401 402 /** Parse and merge a MIME header from an input stream. */ 403 public void mergeHeader(InputStream is) throws java.io.IOException { 404 if (is == null) 405 return; 406 char s[] = new char[10]; 407 int firstc = is.read(); 408 while (firstc != '\n' && firstc != '\r' && firstc >= 0) { 409 int len = 0; 410 int keyend = -1; 411 int c; 412 boolean inKey = firstc > ' '; 413 s[len++] = (char) firstc; 414 parseloop:{ 415 while ((c = is.read()) >= 0) { 416 switch (c) { 417 case ':': 418 if (inKey && len > 0) 419 keyend = len; 420 inKey = false; 421 break; 422 case '\t': 423 c = ' '; 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 }