1 /* 2 * The Apache Software License, Version 1.1 3 * 4 * Copyright (c) 1999 The Apache Software Foundation. All rights 5 * reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * 3. The end-user documentation included with the redistribution, if 20 * any, must include the following acknowlegement: 21 * "This product includes software developed by the 22 * Apache Software Foundation (http://www.apache.org/)." 23 * Alternately, this acknowlegement may appear in the software itself, 24 * if and wherever such third-party acknowlegements normally appear. 25 * 26 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software 27 * Foundation" must not be used to endorse or promote products derived 28 * from this software without prior written permission. For written 29 * permission, please contact apache@apache.org. 30 * 31 * 5. Products derived from this software may not be called "Apache" 32 * nor may "Apache" appear in their names without prior written 33 * permission of the Apache Group. 34 * 35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 46 * SUCH DAMAGE. 47 * ==================================================================== 48 * 49 * This software consists of voluntary contributions made by many 50 * individuals on behalf of the Apache Software Foundation. For more 51 * information on the Apache Software Foundation, please see 52 * <http://www.apache.org/>. 53 * 54 * [Additional notices, if required by prior licensing conditions] 55 * 56 */ 57 58 59 package org.apache.tomcat.util; 60 61 import javax.servlet; 62 import java.io.IOException; 63 import java.io.OutputStream; 64 import java.io.PrintStream; 65 import java.util.Enumeration; 66 import java.util.Hashtable; 67 import java.util.Vector; 68 import java.util.NoSuchElementException; 69 70 /** 71 * This class is used to contain standard internet message headers, 72 * used for SMTP (RFC822) and HTTP (RFC2068) messages as well as for 73 * MIME (RFC 2045) applications such as transferring typed data and 74 * grouping related items in multipart message bodies. 75 * 76 * <P> Message headers, as specified in RFC822, include a field name 77 * and a field body. Order has no semantic significance, and several 78 * fields with the same name may exist. However, most fields do not 79 * (and should not) exist more than once in a header. 80 * 81 * <P> Many kinds of field body must conform to a specified syntax, 82 * including the standard parenthesized comment syntax. This class 83 * supports only two simple syntaxes, for dates and integers. 84 * 85 * <P> When processing headers, care must be taken to handle the case of 86 * multiple same-name fields correctly. The values of such fields are 87 * only available as strings. They may be accessed by index (treating 88 * the header as an array of fields), or by name (returning an array 89 * of string values). 90 * 91 * @author dac@eng.sun.com 92 * @author James Todd [gonzo@eng.sun.com] 93 */ 94 95 public class MimeHeaders { 96 97 private StringManager sm = null; 98 // StringManager.getManager(Constants.Package); 99 100 private StringManager getStringManager() { 101 if (sm == null) { 102 sm = StringManager.getManager(Constants.Package); 103 } 104 return sm; 105 } 106 107 /** 108 * The header fields. 109 */ 110 111 private MimeHeaderField[] headers = new MimeHeaderField[8]; 112 113 /** 114 * The current number of header fields. 115 */ 116 117 private int count; 118 119 /** 120 * A buffer used when parsing headers. 121 */ 122 123 private byte[] buf; 124 125 /** 126 * Creates a new MimeHeaders object using the specified buffer size. 127 * @param len the buffer size initially used for parsing headers 128 */ 129 130 public MimeHeaders(int len) { 131 buf = new byte[len]; 132 } 133 134 /** 135 * Creates a new MimeHeaders object using a default buffer size. 136 */ 137 138 public MimeHeaders() { 139 this(512); 140 } 141 142 /** 143 * Clears all header fields. 144 */ 145 146 public void clear() { 147 for (int i = 0; i < count; i++) { 148 headers[i].reset(); 149 } 150 151 count = 0; 152 } 153 154 /** 155 * Returns the current number of header fields. 156 */ 157 158 public int size() { 159 return count; 160 } 161 162 /** 163 * Returns an enumeration of strings representing the header field names. 164 * Field names may appear multiple times in this enumeration, indicating 165 * that multiple fields with that name exist in this header. 166 */ 167 168 public Enumeration names() { 169 return new MimeHeadersEnumerator(this); 170 } 171 172 // NOTE: All of these put/get "Header" calls should 173 // be renamed to put/get "field" !!! This object is 174 // the header, and its components are called fields. 175 176 /** 177 * Creates a new header field whose value is the specified string. 178 * @param name the header name 179 * @param s the header field string value 180 */ 181 182 public void putHeader(String name, String s) { 183 putHeader(name).setValue(s); 184 } 185 186 public void addHeader(String name, String s) { 187 addHeader(name).setValue(s); 188 } 189 190 /** 191 * Creates a new header field whose value is the specified integer. 192 * @param name the header name 193 * @param i the header field integer value 194 */ 195 196 public void putIntHeader(String name, int i) { 197 putHeader(name).setIntValue(i); 198 } 199 200 public void addIntHeader(String name, int i) { 201 addHeader(name).setIntValue(i); 202 } 203 204 /** 205 * Creates a new header field whose value is the specified time. 206 * The encoding uses RFC 822 date format, as updated by RFC 1123. 207 * @param name the header name 208 * @param t the time in number of milliseconds since the epoch 209 */ 210 211 public void putDateHeader(String name, long t) { 212 putHeader(name).setDateValue(t); 213 } 214 215 public void addDateHeader(String name, long t) { 216 addHeader(name).setDateValue(t); 217 } 218 219 /** 220 * Creates a new header field whose value is the current date and time. 221 * @param name the header name 222 */ 223 224 public void putDateHeader(String name) { 225 putHeader(name).setDateValue(); 226 } 227 228 public void addDateHeader(String name) { 229 addHeader(name).setDateValue(); 230 } 231 232 /** 233 * Returns the string value of one of the headers with the 234 * specified name. 235 * @see getHeaders 236 * @param name the header field name 237 * @return the string value of the field, or null if none found 238 */ 239 240 public String getHeader(String name) { 241 MimeHeaderField mh = find(name); 242 243 return mh != null ? mh.getValue() : null; 244 } 245 246 /** 247 * Returns the string value of all of the headers with the 248 * specified name. 249 * @see getHeader 250 * @param name the header field name 251 * @return array values of the fields, or null if none found 252 */ 253 254 public String[] getHeaders(String name) { 255 Vector values = getHeadersVector(name); 256 257 if (values.size() > 0) { 258 String retval[] = new String[values.size()]; 259 260 for (int i = 0; i < retval.length; i++) 261 retval[i] = (String)values.elementAt(i); 262 return retval; 263 } 264 return null; 265 } 266 267 /** Same as getHeaders, return a Vector - avoid Vector-[]-Vector conversion 268 */ 269 public Vector getHeadersVector(String name) { 270 Vector values = new Vector(); 271 272 for (int i = 0; i < count; i++) { 273 if (headers[i].nameEquals(name)) 274 values.addElement(headers[i].getValue()); 275 } 276 277 return values; 278 } 279 280 /** 281 * Returns the integer value of a header with the specified name. 282 * @param name the header field name 283 * @return the integer value of the header field, or -1 if the header 284 * was not found 285 * @exception NumberFormatException if the integer format was invalid 286 */ 287 288 public int getIntHeader(String name) throws NumberFormatException { 289 MimeHeaderField mh = find(name); 290 291 return mh != null ? mh.getIntValue() : -1; 292 } 293 294 /** 295 * Returns the date value of a header with the specified name. 296 * @param name the header field name 297 * @return the date value of the header field in number of milliseconds 298 * since the epoch, or -1 if the header was not found 299 * @exception IllegalArgumentException if the date format was invalid 300 */ 301 302 public long getDateHeader(String name) throws IllegalArgumentException { 303 MimeHeaderField mh = find(name); 304 305 return mh != null ? mh.getDateValue() : -1; 306 } 307 308 /** 309 * Returns the name of the nth header field where n >= 0. Returns null 310 * if there were fewer than (n + 1) fields. This can be used to iterate 311 * through all the fields in the header. 312 */ 313 314 public String getHeaderName(int n) { 315 return n >= 0 && n < count ? headers[n].getName() : null; 316 } 317 318 /** 319 * Returns the body of the nth header field where n >= 0. Returns null 320 * if there were fewer than (n + 1) fields. This can be used along 321 * with getHeaderName to iterate through all the fields in the header. 322 */ 323 324 public String getHeader(int n) { 325 return n >= 0 && n < count ? headers[n].getValue() : null; 326 } 327 328 /** 329 * Returns the Nth header field, or null if there is no such header. 330 * This may be used to iterate through all header fields. 331 */ 332 333 public MimeHeaderField getField(int n) { 334 return n >= 0 && n < count ? headers[n] : null; 335 } 336 337 /** 338 * Returns the number of fields using a given field name. 339 */ 340 341 public int getFieldCount (String name) { 342 int retval = 0; 343 344 for (int i = 0; i < count; i++) 345 if (headers [i].nameEquals (name)) 346 retval++; 347 348 return retval; 349 } 350 351 /** 352 * Finds and returns a header field with the given name. If no such 353 * field exists, null is returned. If more than one such field is 354 * in the header, an arbitrary one is returned. 355 */ 356 357 protected MimeHeaderField find(String name) { 358 for (int i = 0; i < count; i++) { 359 if (headers[i].nameEquals(name)) { 360 return headers[i]; 361 } 362 } 363 364 return null; 365 } 366 367 /** 368 * Removes a header field with the specified name. Does nothing 369 * if such a field could not be found. 370 * @param name the name of the header field to be removed 371 */ 372 373 public void removeHeader(String name) { 374 // XXX 375 // warning: rather sticky code; heavily tuned 376 377 for (int i = 0; i < count; i++) { 378 if (headers[i].nameEquals(name)) { 379 // reset and swap with last header 380 MimeHeaderField mh = headers[i]; 381 382 mh.reset(); 383 headers[i] = headers[count - 1]; 384 headers[count - 1] = mh; 385 386 count--; 387 i--; 388 } 389 } 390 } 391 392 /** 393 * Returns true if the specified field is contained in the header, 394 * otherwise returns false. 395 * @param name the field name 396 */ 397 398 public boolean containsHeader(String name) { 399 return find(name) != null; 400 } 401 402 /** 403 * Reads header fields from the specified servlet input stream until 404 * a blank line is encountered. 405 * @param in the servlet input stream 406 * @exception IllegalArgumentException if the header format was invalid 407 * @exception IOException if an I/O error has occurred 408 */ 409 410 public void read(ServletInputStream in) throws IOException { 411 // use pre-allocated buffer if possible 412 byte[] b; 413 414 if (count == 0) { 415 b = buf; 416 } else { 417 b = new byte[buf.length]; 418 } 419 420 int off = 0; 421 422 while (true) { 423 int start = off; 424 425 while (true) { 426 int len = b.length - off; 427 428 if (len > 0) { 429 len = in.readLine(b, off, len); 430 431 if (len == -1) { 432 StringManager sm = getStringManager(); 433 String msg = sm.getString("mimeHeader.connection.ioe"); 434 throw new IOException (msg); 435 } 436 } 437 438 off += len; 439 440 if (len == 0 || b[off-1] == '\n') { 441 break; 442 } 443 444 // overflowed buffer, so temporarily expand and continue 445 byte[] tmp = new byte[b.length * 2]; 446 447 System.arraycopy(b, 0, tmp, 0, b.length); 448 b = tmp; 449 } 450 451 // strip off trailing "\r\n" 452 if (--off > start && b[off-1] == '\r') { 453 --off; 454 } 455 456 if (off == start) { 457 break; 458 } 459 460 // XXX this does not currently handle headers which 461 // are folded to take more than one line. 462 463 putHeader().parse(b, start, off - start); 464 } 465 } 466 467 /** 468 * Writes out header fields to the specified servlet output stream. 469 * @param out the servlet output stream 470 * @exception IOException if an I/O error has occurred 471 */ 472 473 public void write(ServletOutputStream out) throws IOException { 474 for (int i = 0; i < count; i++) { 475 headers[i].write(out); 476 } 477 478 out.println(); 479 } 480 481 /** 482 * Finds a header field given name. If the header doesn't exist, 483 * it will create a new one. 484 * @param name the header field name 485 * @return the new field 486 */ 487 488 protected MimeHeaderField putHeader(String name) { 489 if (containsHeader(name)) { 490 removeHeader(name); 491 } 492 493 return addHeader(name); 494 } 495 496 protected MimeHeaderField addHeader(String name) { 497 MimeHeaderField mh = putHeader(); 498 499 mh.setName(name); 500 501 return mh; 502 } 503 504 /** 505 * Creates a new header with given name, and add it to the headers. 506 * @param name the header field name 507 * @param s the header value 508 * @return the new field 509 */ 510 511 public void appendHeader(String name, String s) { 512 MimeHeaderField mh = putHeader(); 513 514 mh.setName(name); 515 mh.setValue(s); 516 } 517 518 /** 519 * Adds a partially constructed field to the header. This 520 * field has not had its name or value initialized. 521 */ 522 523 protected MimeHeaderField putHeader() { 524 MimeHeaderField mh; 525 int len = headers.length; 526 527 if (count >= len) { 528 // expand header list array 529 MimeHeaderField tmp[] = new MimeHeaderField[count * 2]; 530 531 System.arraycopy(headers, 0, tmp, 0, len); 532 headers = tmp; 533 } 534 535 if ((mh = headers[count]) == null) { 536 headers[count] = mh = new MimeHeaderField(); 537 } 538 539 count++; 540 541 return mh; 542 } 543 544 /** 545 * Get the current header fields in the byte array buf. The headers 546 * fields are placed starting at offset buf_offset. 547 * @return the number of bytes written into buf. 548 */ 549 550 public int getAll(byte buf[], int buf_offset) { 551 int start_pt = buf_offset; 552 553 for (int i = 0; i < count; i++) { 554 buf_offset += headers[i].getBytes(buf, buf_offset); 555 } 556 557 return buf_offset - start_pt; 558 } 559 560 /** 561 * Returns a lengthly string representation of the current header fields. 562 */ 563 564 public String toString() { 565 StringBuffer sb = new StringBuffer(); 566 567 sb.append("{"); 568 569 for (int i = 0; i < count; i++) { 570 sb.append("{"); 571 sb.append(headers[i].toString()); 572 sb.append("}"); 573 574 if (i < count - 1) { 575 sb.append(","); 576 } 577 } 578 579 sb.append("}"); 580 581 return sb.toString(); 582 } 583 584 /** 585 * Dumps current headers to specified PrintStream for debugging. 586 */ 587 588 public void dump(PrintStream out) { 589 for (int i = 0; i < count; i++) { 590 out.println(headers[i]); 591 } 592 } 593 } 594 595 class MimeHeadersEnumerator implements Enumeration { 596 // private StringManager sm = 597 // StringManager.getManager(Constants.Package); 598 private Hashtable hash; 599 private Enumeration delegate; 600 601 MimeHeadersEnumerator(MimeHeaders headers) { 602 // Store header names in a Hashtable to guarantee uniqueness 603 // This has the side benefit of letting us use Hashtable's enumerator 604 hash = new Hashtable(); 605 int size = headers.size(); 606 for (int i = 0; i < size; i++) { 607 hash.put(headers.getHeaderName(i), ""); 608 } 609 delegate = hash.keys(); 610 } 611 612 public boolean hasMoreElements() { 613 return delegate.hasMoreElements(); 614 } 615 616 public Object nextElement() { 617 try { 618 return delegate.nextElement(); 619 } 620 catch (NoSuchElementException e) { 621 StringManager sm = 622 StringManager.getManager(Constants.Package); 623 624 String msg = sm.getString("mimeHeaderEnumerator.next.nse"); 625 throw new NoSuchElementException(msg); 626 } 627 } 628 }