1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4 // ------------------------------------------------------------------------ 5 // All rights reserved. This program and the accompanying materials 6 // are made available under the terms of the Eclipse Public License v1.0 7 // and Apache License v2.0 which accompanies this distribution. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.http; 20 21 import java.io.IOException; 22 import java.io.UnsupportedEncodingException; 23 import java.text.SimpleDateFormat; 24 import java.util.ArrayList; 25 import java.util.Calendar; 26 import java.util.Collections; 27 import java.util.Collection; 28 import java.util.Date; 29 import java.util.Enumeration; 30 import java.util.GregorianCalendar; 31 import java.util.HashMap; 32 import java.util.Iterator; 33 import java.util.List; 34 import java.util.Locale; 35 import java.util.Map; 36 import java.util.NoSuchElementException; 37 import java.util.StringTokenizer; 38 import java.util.TimeZone; 39 import java.util.concurrent.ConcurrentHashMap; 40 import java.util.concurrent.ConcurrentMap; 41 42 import org.eclipse.jetty.io.Buffer; 43 import org.eclipse.jetty.io.BufferCache; 44 import org.eclipse.jetty.io.BufferCache.CachedBuffer; 45 import org.eclipse.jetty.io.BufferDateCache; 46 import org.eclipse.jetty.io.BufferUtil; 47 import org.eclipse.jetty.io.ByteArrayBuffer; 48 import org.eclipse.jetty.util.LazyList; 49 import org.eclipse.jetty.util.QuotedStringTokenizer; 50 import org.eclipse.jetty.util.StringMap; 51 import org.eclipse.jetty.util.StringUtil; 52 import org.eclipse.jetty.util.log.Log; 53 import org.eclipse.jetty.util.log.Logger; 54 55 /* ------------------------------------------------------------ */ 56 /** 57 * HTTP Fields. A collection of HTTP header and or Trailer fields. 58 * 59 * <p>This class is not synchronized as it is expected that modifications will only be performed by a 60 * single thread. 61 * 62 * 63 */ 64 public class HttpFields 65 { 66 private static final Logger LOG = Log.getLogger(HttpFields.class); 67 68 /* ------------------------------------------------------------ */ 69 public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;="; 70 public static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); 71 public static final BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); 72 73 /* -------------------------------------------------------------- */ 74 static 75 { 76 __GMT.setID("GMT"); 77 __dateCache.setTimeZone(__GMT); 78 } 79 80 /* ------------------------------------------------------------ */ 81 public final static String __separators = ", \t"; 82 83 /* ------------------------------------------------------------ */ 84 private static final String[] DAYS = 85 { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 86 private static final String[] MONTHS = 87 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"}; 88 89 90 /* ------------------------------------------------------------ */ 91 private static class DateGenerator 92 { 93 private final StringBuilder buf = new StringBuilder(32); 94 private final GregorianCalendar gc = new GregorianCalendar(__GMT); 95 96 /** 97 * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 98 */ formatDate(long date)99 public String formatDate(long date) 100 { 101 buf.setLength(0); 102 gc.setTimeInMillis(date); 103 104 int day_of_week = gc.get(Calendar.DAY_OF_WEEK); 105 int day_of_month = gc.get(Calendar.DAY_OF_MONTH); 106 int month = gc.get(Calendar.MONTH); 107 int year = gc.get(Calendar.YEAR); 108 int century = year / 100; 109 year = year % 100; 110 111 int hours = gc.get(Calendar.HOUR_OF_DAY); 112 int minutes = gc.get(Calendar.MINUTE); 113 int seconds = gc.get(Calendar.SECOND); 114 115 buf.append(DAYS[day_of_week]); 116 buf.append(','); 117 buf.append(' '); 118 StringUtil.append2digits(buf, day_of_month); 119 120 buf.append(' '); 121 buf.append(MONTHS[month]); 122 buf.append(' '); 123 StringUtil.append2digits(buf, century); 124 StringUtil.append2digits(buf, year); 125 126 buf.append(' '); 127 StringUtil.append2digits(buf, hours); 128 buf.append(':'); 129 StringUtil.append2digits(buf, minutes); 130 buf.append(':'); 131 StringUtil.append2digits(buf, seconds); 132 buf.append(" GMT"); 133 return buf.toString(); 134 } 135 136 /* ------------------------------------------------------------ */ 137 /** 138 * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies 139 */ formatCookieDate(StringBuilder buf, long date)140 public void formatCookieDate(StringBuilder buf, long date) 141 { 142 gc.setTimeInMillis(date); 143 144 int day_of_week = gc.get(Calendar.DAY_OF_WEEK); 145 int day_of_month = gc.get(Calendar.DAY_OF_MONTH); 146 int month = gc.get(Calendar.MONTH); 147 int year = gc.get(Calendar.YEAR); 148 year = year % 10000; 149 150 int epoch = (int) ((date / 1000) % (60 * 60 * 24)); 151 int seconds = epoch % 60; 152 epoch = epoch / 60; 153 int minutes = epoch % 60; 154 int hours = epoch / 60; 155 156 buf.append(DAYS[day_of_week]); 157 buf.append(','); 158 buf.append(' '); 159 StringUtil.append2digits(buf, day_of_month); 160 161 buf.append('-'); 162 buf.append(MONTHS[month]); 163 buf.append('-'); 164 StringUtil.append2digits(buf, year/100); 165 StringUtil.append2digits(buf, year%100); 166 167 buf.append(' '); 168 StringUtil.append2digits(buf, hours); 169 buf.append(':'); 170 StringUtil.append2digits(buf, minutes); 171 buf.append(':'); 172 StringUtil.append2digits(buf, seconds); 173 buf.append(" GMT"); 174 } 175 } 176 177 /* ------------------------------------------------------------ */ 178 private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>() 179 { 180 @Override 181 protected DateGenerator initialValue() 182 { 183 return new DateGenerator(); 184 } 185 }; 186 187 /* ------------------------------------------------------------ */ 188 /** 189 * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" 190 */ formatDate(long date)191 public static String formatDate(long date) 192 { 193 return __dateGenerator.get().formatDate(date); 194 } 195 196 /* ------------------------------------------------------------ */ 197 /** 198 * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies 199 */ formatCookieDate(StringBuilder buf, long date)200 public static void formatCookieDate(StringBuilder buf, long date) 201 { 202 __dateGenerator.get().formatCookieDate(buf,date); 203 } 204 205 /* ------------------------------------------------------------ */ 206 /** 207 * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies 208 */ formatCookieDate(long date)209 public static String formatCookieDate(long date) 210 { 211 StringBuilder buf = new StringBuilder(28); 212 formatCookieDate(buf, date); 213 return buf.toString(); 214 } 215 216 /* ------------------------------------------------------------ */ 217 private final static String __dateReceiveFmt[] = 218 { 219 "EEE, dd MMM yyyy HH:mm:ss zzz", 220 "EEE, dd-MMM-yy HH:mm:ss", 221 "EEE MMM dd HH:mm:ss yyyy", 222 223 "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", 224 "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", 225 "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", 226 "dd MMM yyyy HH:mm:ss", "dd-MMM-yy HH:mm:ss zzz", "dd-MMM-yy HH:mm:ss", "MMM dd HH:mm:ss yyyy zzz", 227 "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz", 228 "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", 229 "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss", 230 }; 231 232 /* ------------------------------------------------------------ */ 233 private static class DateParser 234 { 235 final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length]; 236 parse(final String dateVal)237 long parse(final String dateVal) 238 { 239 for (int i = 0; i < _dateReceive.length; i++) 240 { 241 if (_dateReceive[i] == null) 242 { 243 _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US); 244 _dateReceive[i].setTimeZone(__GMT); 245 } 246 247 try 248 { 249 Date date = (Date) _dateReceive[i].parseObject(dateVal); 250 return date.getTime(); 251 } 252 catch (java.lang.Exception e) 253 { 254 // LOG.ignore(e); 255 } 256 } 257 258 if (dateVal.endsWith(" GMT")) 259 { 260 final String val = dateVal.substring(0, dateVal.length() - 4); 261 262 for (int i = 0; i < _dateReceive.length; i++) 263 { 264 try 265 { 266 Date date = (Date) _dateReceive[i].parseObject(val); 267 return date.getTime(); 268 } 269 catch (java.lang.Exception e) 270 { 271 // LOG.ignore(e); 272 } 273 } 274 } 275 return -1; 276 } 277 } 278 279 /* ------------------------------------------------------------ */ parseDate(String date)280 public static long parseDate(String date) 281 { 282 return __dateParser.get().parse(date); 283 } 284 285 /* ------------------------------------------------------------ */ 286 private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>() 287 { 288 @Override 289 protected DateParser initialValue() 290 { 291 return new DateParser(); 292 } 293 }; 294 295 /* -------------------------------------------------------------- */ 296 public final static String __01Jan1970=formatDate(0); 297 public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970); 298 public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim(); 299 300 /* -------------------------------------------------------------- */ 301 private final ArrayList<Field> _fields = new ArrayList<Field>(20); 302 private final HashMap<Buffer,Field> _names = new HashMap<Buffer,Field>(32); 303 304 /* ------------------------------------------------------------ */ 305 /** 306 * Constructor. 307 */ HttpFields()308 public HttpFields() 309 { 310 } 311 312 // TODO externalize this cache so it can be configurable 313 private static ConcurrentMap<String, Buffer> __cache = new ConcurrentHashMap<String, Buffer>(); 314 private static int __cacheSize = Integer.getInteger("org.eclipse.jetty.http.HttpFields.CACHE",2000); 315 316 /* -------------------------------------------------------------- */ convertValue(String value)317 private Buffer convertValue(String value) 318 { 319 Buffer buffer = __cache.get(value); 320 if (buffer!=null) 321 return buffer; 322 323 try 324 { 325 buffer = new ByteArrayBuffer(value,StringUtil.__ISO_8859_1); 326 327 if (__cacheSize>0) 328 { 329 if (__cache.size()>__cacheSize) 330 __cache.clear(); 331 Buffer b=__cache.putIfAbsent(value,buffer); 332 if (b!=null) 333 buffer=b; 334 } 335 336 return buffer; 337 } 338 catch (UnsupportedEncodingException e) 339 { 340 throw new RuntimeException(e); 341 } 342 } 343 344 /* -------------------------------------------------------------- */ 345 /** 346 * Get Collection of header names. 347 */ getFieldNamesCollection()348 public Collection<String> getFieldNamesCollection() 349 { 350 final List<String> list = new ArrayList<String>(_fields.size()); 351 352 for (Field f : _fields) 353 { 354 if (f!=null) 355 list.add(BufferUtil.to8859_1_String(f._name)); 356 } 357 return list; 358 } 359 360 /* -------------------------------------------------------------- */ 361 /** 362 * Get enumeration of header _names. Returns an enumeration of strings representing the header 363 * _names for this request. 364 */ getFieldNames()365 public Enumeration<String> getFieldNames() 366 { 367 final Enumeration<?> buffers = Collections.enumeration(_names.keySet()); 368 return new Enumeration<String>() 369 { 370 public String nextElement() 371 { 372 return buffers.nextElement().toString(); 373 } 374 375 public boolean hasMoreElements() 376 { 377 return buffers.hasMoreElements(); 378 } 379 }; 380 } 381 382 /* ------------------------------------------------------------ */ 383 public int size() 384 { 385 return _fields.size(); 386 } 387 388 /* ------------------------------------------------------------ */ 389 /** 390 * Get a Field by index. 391 * @return A Field value or null if the Field value has not been set 392 * 393 */ 394 public Field getField(int i) 395 { 396 return _fields.get(i); 397 } 398 399 /* ------------------------------------------------------------ */ 400 private Field getField(String name) 401 { 402 return _names.get(HttpHeaders.CACHE.lookup(name)); 403 } 404 405 /* ------------------------------------------------------------ */ 406 private Field getField(Buffer name) 407 { 408 return _names.get(HttpHeaders.CACHE.lookup(name)); 409 } 410 411 /* ------------------------------------------------------------ */ 412 public boolean containsKey(Buffer name) 413 { 414 return _names.containsKey(HttpHeaders.CACHE.lookup(name)); 415 } 416 417 /* ------------------------------------------------------------ */ 418 public boolean containsKey(String name) 419 { 420 return _names.containsKey(HttpHeaders.CACHE.lookup(name)); 421 } 422 423 /* -------------------------------------------------------------- */ 424 /** 425 * @return the value of a field, or null if not found. For multiple fields of the same name, 426 * only the first is returned. 427 * @param name the case-insensitive field name 428 */ 429 public String getStringField(String name) 430 { 431 Field field = getField(name); 432 return field==null?null:field.getValue(); 433 } 434 435 /* -------------------------------------------------------------- */ 436 /** 437 * @return the value of a field, or null if not found. For multiple fields of the same name, 438 * only the first is returned. 439 * @param name the case-insensitive field name 440 */ 441 public String getStringField(Buffer name) 442 { 443 Field field = getField(name); 444 return field==null?null:field.getValue(); 445 } 446 447 /* -------------------------------------------------------------- */ 448 /** 449 * @return the value of a field, or null if not found. For multiple fields of the same name, 450 * only the first is returned. 451 * @param name the case-insensitive field name 452 */ 453 public Buffer get(Buffer name) 454 { 455 Field field = getField(name); 456 return field==null?null:field._value; 457 } 458 459 460 /* -------------------------------------------------------------- */ 461 /** 462 * Get multi headers 463 * 464 * @return Enumeration of the values, or null if no such header. 465 * @param name the case-insensitive field name 466 */ 467 public Collection<String> getValuesCollection(String name) 468 { 469 Field field = getField(name); 470 if (field==null) 471 return null; 472 473 final List<String> list = new ArrayList<String>(); 474 475 while(field!=null) 476 { 477 list.add(field.getValue()); 478 field=field._next; 479 } 480 return list; 481 } 482 483 /* -------------------------------------------------------------- */ 484 /** 485 * Get multi headers 486 * 487 * @return Enumeration of the values 488 * @param name the case-insensitive field name 489 */ 490 public Enumeration<String> getValues(String name) 491 { 492 final Field field = getField(name); 493 if (field == null) 494 { 495 List<String> empty=Collections.emptyList(); 496 return Collections.enumeration(empty); 497 } 498 499 return new Enumeration<String>() 500 { 501 Field f = field; 502 503 public boolean hasMoreElements() 504 { 505 return f != null; 506 } 507 508 public String nextElement() throws NoSuchElementException 509 { 510 if (f == null) throw new NoSuchElementException(); 511 Field n = f; 512 f = f._next; 513 return n.getValue(); 514 } 515 }; 516 } 517 518 /* -------------------------------------------------------------- */ 519 /** 520 * Get multi headers 521 * 522 * @return Enumeration of the value Strings 523 * @param name the case-insensitive field name 524 */ 525 public Enumeration<String> getValues(Buffer name) 526 { 527 final Field field = getField(name); 528 if (field == null) 529 { 530 List<String> empty=Collections.emptyList(); 531 return Collections.enumeration(empty); 532 } 533 534 return new Enumeration<String>() 535 { 536 Field f = field; 537 538 public boolean hasMoreElements() 539 { 540 return f != null; 541 } 542 543 public String nextElement() throws NoSuchElementException 544 { 545 if (f == null) throw new NoSuchElementException(); 546 Field n = f; 547 f = f._next; 548 return n.getValue(); 549 } 550 }; 551 } 552 553 /* -------------------------------------------------------------- */ 554 /** 555 * Get multi field values with separator. The multiple values can be represented as separate 556 * headers of the same name, or by a single header using the separator(s), or a combination of 557 * both. Separators may be quoted. 558 * 559 * @param name the case-insensitive field name 560 * @param separators String of separators. 561 * @return Enumeration of the values, or null if no such header. 562 */ 563 public Enumeration<String> getValues(String name, final String separators) 564 { 565 final Enumeration<String> e = getValues(name); 566 if (e == null) 567 return null; 568 return new Enumeration<String>() 569 { 570 QuotedStringTokenizer tok = null; 571 572 public boolean hasMoreElements() 573 { 574 if (tok != null && tok.hasMoreElements()) return true; 575 while (e.hasMoreElements()) 576 { 577 String value = e.nextElement(); 578 tok = new QuotedStringTokenizer(value, separators, false, false); 579 if (tok.hasMoreElements()) return true; 580 } 581 tok = null; 582 return false; 583 } 584 585 public String nextElement() throws NoSuchElementException 586 { 587 if (!hasMoreElements()) throw new NoSuchElementException(); 588 String next = (String) tok.nextElement(); 589 if (next != null) next = next.trim(); 590 return next; 591 } 592 }; 593 } 594 595 596 /* -------------------------------------------------------------- */ 597 /** 598 * Set a field. 599 * 600 * @param name the name of the field 601 * @param value the value of the field. If null the field is cleared. 602 */ 603 public void put(String name, String value) 604 { 605 if (value==null) 606 remove(name); 607 else 608 { 609 Buffer n = HttpHeaders.CACHE.lookup(name); 610 Buffer v = convertValue(value); 611 put(n, v); 612 } 613 } 614 615 /* -------------------------------------------------------------- */ 616 /** 617 * Set a field. 618 * 619 * @param name the name of the field 620 * @param value the value of the field. If null the field is cleared. 621 */ 622 public void put(Buffer name, String value) 623 { 624 Buffer n = HttpHeaders.CACHE.lookup(name); 625 Buffer v = convertValue(value); 626 put(n, v); 627 } 628 629 /* -------------------------------------------------------------- */ 630 /** 631 * Set a field. 632 * 633 * @param name the name of the field 634 * @param value the value of the field. If null the field is cleared. 635 */ 636 public void put(Buffer name, Buffer value) 637 { 638 remove(name); 639 if (value == null) 640 return; 641 642 if (!(name instanceof BufferCache.CachedBuffer)) 643 name = HttpHeaders.CACHE.lookup(name); 644 if (!(value instanceof CachedBuffer)) 645 value= HttpHeaderValues.CACHE.lookup(value).asImmutableBuffer(); 646 647 // new value; 648 Field field = new Field(name, value); 649 _fields.add(field); 650 _names.put(name, field); 651 } 652 653 /* -------------------------------------------------------------- */ 654 /** 655 * Set a field. 656 * 657 * @param name the name of the field 658 * @param list the List value of the field. If null the field is cleared. 659 */ 660 public void put(String name, List<?> list) 661 { 662 if (list == null || list.size() == 0) 663 { 664 remove(name); 665 return; 666 } 667 Buffer n = HttpHeaders.CACHE.lookup(name); 668 669 Object v = list.get(0); 670 if (v != null) 671 put(n, HttpHeaderValues.CACHE.lookup(v.toString())); 672 else 673 remove(n); 674 675 if (list.size() > 1) 676 { 677 java.util.Iterator<?> iter = list.iterator(); 678 iter.next(); 679 while (iter.hasNext()) 680 { 681 v = iter.next(); 682 if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString())); 683 } 684 } 685 } 686 687 /* -------------------------------------------------------------- */ 688 /** 689 * Add to or set a field. If the field is allowed to have multiple values, add will add multiple 690 * headers of the same name. 691 * 692 * @param name the name of the field 693 * @param value the value of the field. 694 * @exception IllegalArgumentException If the name is a single valued field and already has a 695 * value. 696 */ 697 public void add(String name, String value) throws IllegalArgumentException 698 { 699 if (value==null) 700 return; 701 Buffer n = HttpHeaders.CACHE.lookup(name); 702 Buffer v = convertValue(value); 703 add(n, v); 704 } 705 706 /* -------------------------------------------------------------- */ 707 /** 708 * Add to or set a field. If the field is allowed to have multiple values, add will add multiple 709 * headers of the same name. 710 * 711 * @param name the name of the field 712 * @param value the value of the field. 713 * @exception IllegalArgumentException If the name is a single valued field and already has a 714 * value. 715 */ 716 public void add(Buffer name, Buffer value) throws IllegalArgumentException 717 { 718 if (value == null) throw new IllegalArgumentException("null value"); 719 720 if (!(name instanceof CachedBuffer)) 721 name = HttpHeaders.CACHE.lookup(name); 722 name=name.asImmutableBuffer(); 723 724 if (!(value instanceof CachedBuffer) && HttpHeaderValues.hasKnownValues(HttpHeaders.CACHE.getOrdinal(name))) 725 value= HttpHeaderValues.CACHE.lookup(value); 726 value=value.asImmutableBuffer(); 727 728 Field field = _names.get(name); 729 Field last = null; 730 while (field != null) 731 { 732 last = field; 733 field = field._next; 734 } 735 736 // create the field 737 field = new Field(name, value); 738 _fields.add(field); 739 740 // look for chain to add too 741 if (last != null) 742 last._next = field; 743 else 744 _names.put(name, field); 745 } 746 747 /* ------------------------------------------------------------ */ 748 /** 749 * Remove a field. 750 * 751 * @param name 752 */ 753 public void remove(String name) 754 { 755 remove(HttpHeaders.CACHE.lookup(name)); 756 } 757 758 /* ------------------------------------------------------------ */ 759 /** 760 * Remove a field. 761 * 762 * @param name 763 */ 764 public void remove(Buffer name) 765 { 766 if (!(name instanceof BufferCache.CachedBuffer)) 767 name = HttpHeaders.CACHE.lookup(name); 768 Field field = _names.remove(name); 769 while (field != null) 770 { 771 _fields.remove(field); 772 field = field._next; 773 } 774 } 775 776 /* -------------------------------------------------------------- */ 777 /** 778 * Get a header as an long value. Returns the value of an integer field or -1 if not found. The 779 * case of the field name is ignored. 780 * 781 * @param name the case-insensitive field name 782 * @exception NumberFormatException If bad long found 783 */ 784 public long getLongField(String name) throws NumberFormatException 785 { 786 Field field = getField(name); 787 return field==null?-1L:field.getLongValue(); 788 } 789 790 /* -------------------------------------------------------------- */ 791 /** 792 * Get a header as an long value. Returns the value of an integer field or -1 if not found. The 793 * case of the field name is ignored. 794 * 795 * @param name the case-insensitive field name 796 * @exception NumberFormatException If bad long found 797 */ 798 public long getLongField(Buffer name) throws NumberFormatException 799 { 800 Field field = getField(name); 801 return field==null?-1L:field.getLongValue(); 802 } 803 804 /* -------------------------------------------------------------- */ 805 /** 806 * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case 807 * of the field name is ignored. 808 * 809 * @param name the case-insensitive field name 810 */ 811 public long getDateField(String name) 812 { 813 Field field = getField(name); 814 if (field == null) 815 return -1; 816 817 String val = valueParameters(BufferUtil.to8859_1_String(field._value), null); 818 if (val == null) 819 return -1; 820 821 final long date = __dateParser.get().parse(val); 822 if (date==-1) 823 throw new IllegalArgumentException("Cannot convert date: " + val); 824 return date; 825 } 826 827 /* -------------------------------------------------------------- */ 828 /** 829 * Sets the value of an long field. 830 * 831 * @param name the field name 832 * @param value the field long value 833 */ 834 public void putLongField(Buffer name, long value) 835 { 836 Buffer v = BufferUtil.toBuffer(value); 837 put(name, v); 838 } 839 840 /* -------------------------------------------------------------- */ 841 /** 842 * Sets the value of an long field. 843 * 844 * @param name the field name 845 * @param value the field long value 846 */ 847 public void putLongField(String name, long value) 848 { 849 Buffer n = HttpHeaders.CACHE.lookup(name); 850 Buffer v = BufferUtil.toBuffer(value); 851 put(n, v); 852 } 853 854 /* -------------------------------------------------------------- */ 855 /** 856 * Sets the value of an long field. 857 * 858 * @param name the field name 859 * @param value the field long value 860 */ 861 public void addLongField(String name, long value) 862 { 863 Buffer n = HttpHeaders.CACHE.lookup(name); 864 Buffer v = BufferUtil.toBuffer(value); 865 add(n, v); 866 } 867 868 /* -------------------------------------------------------------- */ 869 /** 870 * Sets the value of an long field. 871 * 872 * @param name the field name 873 * @param value the field long value 874 */ 875 public void addLongField(Buffer name, long value) 876 { 877 Buffer v = BufferUtil.toBuffer(value); 878 add(name, v); 879 } 880 881 /* -------------------------------------------------------------- */ 882 /** 883 * Sets the value of a date field. 884 * 885 * @param name the field name 886 * @param date the field date value 887 */ 888 public void putDateField(Buffer name, long date) 889 { 890 String d=formatDate(date); 891 Buffer v = new ByteArrayBuffer(d); 892 put(name, v); 893 } 894 895 /* -------------------------------------------------------------- */ 896 /** 897 * Sets the value of a date field. 898 * 899 * @param name the field name 900 * @param date the field date value 901 */ 902 public void putDateField(String name, long date) 903 { 904 Buffer n = HttpHeaders.CACHE.lookup(name); 905 putDateField(n,date); 906 } 907 908 /* -------------------------------------------------------------- */ 909 /** 910 * Sets the value of a date field. 911 * 912 * @param name the field name 913 * @param date the field date value 914 */ 915 public void addDateField(String name, long date) 916 { 917 String d=formatDate(date); 918 Buffer n = HttpHeaders.CACHE.lookup(name); 919 Buffer v = new ByteArrayBuffer(d); 920 add(n, v); 921 } 922 923 /* ------------------------------------------------------------ */ 924 /** 925 * Format a set cookie value 926 * 927 * @param cookie The cookie. 928 */ 929 public void addSetCookie(HttpCookie cookie) 930 { 931 addSetCookie( 932 cookie.getName(), 933 cookie.getValue(), 934 cookie.getDomain(), 935 cookie.getPath(), 936 cookie.getMaxAge(), 937 cookie.getComment(), 938 cookie.isSecure(), 939 cookie.isHttpOnly(), 940 cookie.getVersion()); 941 } 942 943 /** 944 * Format a set cookie value 945 * 946 * @param name the name 947 * @param value the value 948 * @param domain the domain 949 * @param path the path 950 * @param maxAge the maximum age 951 * @param comment the comment (only present on versions > 0) 952 * @param isSecure true if secure cookie 953 * @param isHttpOnly true if for http only 954 * @param version version of cookie logic to use (0 == default behavior) 955 */ 956 public void addSetCookie( 957 final String name, 958 final String value, 959 final String domain, 960 final String path, 961 final long maxAge, 962 final String comment, 963 final boolean isSecure, 964 final boolean isHttpOnly, 965 int version) 966 { 967 String delim=__COOKIE_DELIM; 968 969 // Check arguments 970 if (name == null || name.length() == 0) 971 throw new IllegalArgumentException("Bad cookie name"); 972 973 // Format value and params 974 StringBuilder buf = new StringBuilder(128); 975 String name_value_params; 976 QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); 977 buf.append('='); 978 String start=buf.toString(); 979 boolean hasDomain = false; 980 boolean hasPath = false; 981 982 if (value != null && value.length() > 0) 983 QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); 984 985 if (comment != null && comment.length() > 0) 986 { 987 buf.append(";Comment="); 988 QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim); 989 } 990 991 if (path != null && path.length() > 0) 992 { 993 hasPath = true; 994 buf.append(";Path="); 995 if (path.trim().startsWith("\"")) 996 buf.append(path); 997 else 998 QuotedStringTokenizer.quoteIfNeeded(buf,path,delim); 999 } 1000 if (domain != null && domain.length() > 0) 1001 { 1002 hasDomain = true; 1003 buf.append(";Domain="); 1004 QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim); 1005 } 1006 1007 if (maxAge >= 0) 1008 { 1009 // Always add the expires param as some browsers still don't handle max-age 1010 buf.append(";Expires="); 1011 if (maxAge == 0) 1012 buf.append(__01Jan1970_COOKIE); 1013 else 1014 formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); 1015 1016 if (version >0) 1017 { 1018 buf.append(";Max-Age="); 1019 buf.append(maxAge); 1020 } 1021 } 1022 1023 if (isSecure) 1024 buf.append(";Secure"); 1025 if (isHttpOnly) 1026 buf.append(";HttpOnly"); 1027 1028 name_value_params = buf.toString(); 1029 1030 // remove existing set-cookie of same name 1031 Field field = getField(HttpHeaders.SET_COOKIE); 1032 Field last=null; 1033 while (field!=null) 1034 { 1035 String val = (field._value == null ? null : field._value.toString()); 1036 if (val!=null && val.startsWith(start)) 1037 { 1038 //existing cookie has same name, does it also match domain and path? 1039 if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) && 1040 ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path)))) 1041 { 1042 _fields.remove(field); 1043 if (last==null) 1044 _names.put(HttpHeaders.SET_COOKIE_BUFFER,field._next); 1045 else 1046 last._next=field._next; 1047 break; 1048 } 1049 } 1050 last=field; 1051 field=field._next; 1052 } 1053 1054 add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params)); 1055 1056 // Expire responses with set-cookie headers so they do not get cached. 1057 put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER); 1058 } 1059 1060 /* -------------------------------------------------------------- */ 1061 public void putTo(Buffer buffer) throws IOException 1062 { 1063 for (int i = 0; i < _fields.size(); i++) 1064 { 1065 Field field = _fields.get(i); 1066 if (field != null) 1067 field.putTo(buffer); 1068 } 1069 BufferUtil.putCRLF(buffer); 1070 } 1071 1072 /* -------------------------------------------------------------- */ 1073 public String toString() 1074 { 1075 try 1076 { 1077 StringBuffer buffer = new StringBuffer(); 1078 for (int i = 0; i < _fields.size(); i++) 1079 { 1080 Field field = (Field) _fields.get(i); 1081 if (field != null) 1082 { 1083 String tmp = field.getName(); 1084 if (tmp != null) buffer.append(tmp); 1085 buffer.append(": "); 1086 tmp = field.getValue(); 1087 if (tmp != null) buffer.append(tmp); 1088 buffer.append("\r\n"); 1089 } 1090 } 1091 buffer.append("\r\n"); 1092 return buffer.toString(); 1093 } 1094 catch (Exception e) 1095 { 1096 LOG.warn(e); 1097 return e.toString(); 1098 } 1099 } 1100 1101 /* ------------------------------------------------------------ */ 1102 /** 1103 * Clear the header. 1104 */ 1105 public void clear() 1106 { 1107 _fields.clear(); 1108 _names.clear(); 1109 } 1110 1111 /* ------------------------------------------------------------ */ 1112 /** 1113 * Add fields from another HttpFields instance. Single valued fields are replaced, while all 1114 * others are added. 1115 * 1116 * @param fields 1117 */ 1118 public void add(HttpFields fields) 1119 { 1120 if (fields == null) return; 1121 1122 Enumeration e = fields.getFieldNames(); 1123 while (e.hasMoreElements()) 1124 { 1125 String name = (String) e.nextElement(); 1126 Enumeration values = fields.getValues(name); 1127 while (values.hasMoreElements()) 1128 add(name, (String) values.nextElement()); 1129 } 1130 } 1131 1132 /* ------------------------------------------------------------ */ 1133 /** 1134 * Get field value parameters. Some field values can have parameters. This method separates the 1135 * value from the parameters and optionally populates a map with the parameters. For example: 1136 * 1137 * <PRE> 1138 * 1139 * FieldName : Value ; param1=val1 ; param2=val2 1140 * 1141 * </PRE> 1142 * 1143 * @param value The Field value, possibly with parameteres. 1144 * @param parameters A map to populate with the parameters, or null 1145 * @return The value. 1146 */ 1147 public static String valueParameters(String value, Map<String,String> parameters) 1148 { 1149 if (value == null) return null; 1150 1151 int i = value.indexOf(';'); 1152 if (i < 0) return value; 1153 if (parameters == null) return value.substring(0, i).trim(); 1154 1155 StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true); 1156 while (tok1.hasMoreTokens()) 1157 { 1158 String token = tok1.nextToken(); 1159 StringTokenizer tok2 = new QuotedStringTokenizer(token, "= "); 1160 if (tok2.hasMoreTokens()) 1161 { 1162 String paramName = tok2.nextToken(); 1163 String paramVal = null; 1164 if (tok2.hasMoreTokens()) paramVal = tok2.nextToken(); 1165 parameters.put(paramName, paramVal); 1166 } 1167 } 1168 1169 return value.substring(0, i).trim(); 1170 } 1171 1172 /* ------------------------------------------------------------ */ 1173 private static final Float __one = new Float("1.0"); 1174 private static final Float __zero = new Float("0.0"); 1175 private static final StringMap __qualities = new StringMap(); 1176 static 1177 { 1178 __qualities.put(null, __one); 1179 __qualities.put("1.0", __one); 1180 __qualities.put("1", __one); 1181 __qualities.put("0.9", new Float("0.9")); 1182 __qualities.put("0.8", new Float("0.8")); 1183 __qualities.put("0.7", new Float("0.7")); 1184 __qualities.put("0.66", new Float("0.66")); 1185 __qualities.put("0.6", new Float("0.6")); 1186 __qualities.put("0.5", new Float("0.5")); 1187 __qualities.put("0.4", new Float("0.4")); 1188 __qualities.put("0.33", new Float("0.33")); 1189 __qualities.put("0.3", new Float("0.3")); 1190 __qualities.put("0.2", new Float("0.2")); 1191 __qualities.put("0.1", new Float("0.1")); 1192 __qualities.put("0", __zero); 1193 __qualities.put("0.0", __zero); 1194 } 1195 1196 /* ------------------------------------------------------------ */ 1197 public static Float getQuality(String value) 1198 { 1199 if (value == null) return __zero; 1200 1201 int qe = value.indexOf(";"); 1202 if (qe++ < 0 || qe == value.length()) return __one; 1203 1204 if (value.charAt(qe++) == 'q') 1205 { 1206 qe++; 1207 Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe); 1208 if (entry != null) return (Float) entry.getValue(); 1209 } 1210 1211 HashMap params = new HashMap(3); 1212 valueParameters(value, params); 1213 String qs = (String) params.get("q"); 1214 Float q = (Float) __qualities.get(qs); 1215 if (q == null) 1216 { 1217 try 1218 { 1219 q = new Float(qs); 1220 } 1221 catch (Exception e) 1222 { 1223 q = __one; 1224 } 1225 } 1226 return q; 1227 } 1228 1229 /* ------------------------------------------------------------ */ 1230 /** 1231 * List values in quality order. 1232 * 1233 * @param e Enumeration of values with quality parameters 1234 * @return values in quality order. 1235 */ 1236 public static List qualityList(Enumeration e) 1237 { 1238 if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST; 1239 1240 Object list = null; 1241 Object qual = null; 1242 1243 // Assume list will be well ordered and just add nonzero 1244 while (e.hasMoreElements()) 1245 { 1246 String v = e.nextElement().toString(); 1247 Float q = getQuality(v); 1248 1249 if (q.floatValue() >= 0.001) 1250 { 1251 list = LazyList.add(list, v); 1252 qual = LazyList.add(qual, q); 1253 } 1254 } 1255 1256 List vl = LazyList.getList(list, false); 1257 if (vl.size() < 2) return vl; 1258 1259 List ql = LazyList.getList(qual, false); 1260 1261 // sort list with swaps 1262 Float last = __zero; 1263 for (int i = vl.size(); i-- > 0;) 1264 { 1265 Float q = (Float) ql.get(i); 1266 if (last.compareTo(q) > 0) 1267 { 1268 Object tmp = vl.get(i); 1269 vl.set(i, vl.get(i + 1)); 1270 vl.set(i + 1, tmp); 1271 ql.set(i, ql.get(i + 1)); 1272 ql.set(i + 1, q); 1273 last = __zero; 1274 i = vl.size(); 1275 continue; 1276 } 1277 last = q; 1278 } 1279 ql.clear(); 1280 return vl; 1281 } 1282 1283 /* ------------------------------------------------------------ */ 1284 /* ------------------------------------------------------------ */ 1285 /* ------------------------------------------------------------ */ 1286 public static final class Field 1287 { 1288 private Buffer _name; 1289 private Buffer _value; 1290 private Field _next; 1291 1292 /* ------------------------------------------------------------ */ 1293 private Field(Buffer name, Buffer value) 1294 { 1295 _name = name; 1296 _value = value; 1297 _next = null; 1298 } 1299 1300 /* ------------------------------------------------------------ */ 1301 public void putTo(Buffer buffer) throws IOException 1302 { 1303 int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1; 1304 if (o>=0) 1305 buffer.put(_name); 1306 else 1307 { 1308 int s=_name.getIndex(); 1309 int e=_name.putIndex(); 1310 while (s<e) 1311 { 1312 byte b=_name.peek(s++); 1313 switch(b) 1314 { 1315 case '\r': 1316 case '\n': 1317 case ':' : 1318 continue; 1319 default: 1320 buffer.put(b); 1321 } 1322 } 1323 } 1324 1325 buffer.put((byte) ':'); 1326 buffer.put((byte) ' '); 1327 1328 o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1; 1329 if (o>=0) 1330 buffer.put(_value); 1331 else 1332 { 1333 int s=_value.getIndex(); 1334 int e=_value.putIndex(); 1335 while (s<e) 1336 { 1337 byte b=_value.peek(s++); 1338 switch(b) 1339 { 1340 case '\r': 1341 case '\n': 1342 continue; 1343 default: 1344 buffer.put(b); 1345 } 1346 } 1347 } 1348 1349 BufferUtil.putCRLF(buffer); 1350 } 1351 1352 /* ------------------------------------------------------------ */ 1353 public String getName() 1354 { 1355 return BufferUtil.to8859_1_String(_name); 1356 } 1357 1358 /* ------------------------------------------------------------ */ 1359 Buffer getNameBuffer() 1360 { 1361 return _name; 1362 } 1363 1364 /* ------------------------------------------------------------ */ 1365 public int getNameOrdinal() 1366 { 1367 return HttpHeaders.CACHE.getOrdinal(_name); 1368 } 1369 1370 /* ------------------------------------------------------------ */ 1371 public String getValue() 1372 { 1373 return BufferUtil.to8859_1_String(_value); 1374 } 1375 1376 /* ------------------------------------------------------------ */ 1377 public Buffer getValueBuffer() 1378 { 1379 return _value; 1380 } 1381 1382 /* ------------------------------------------------------------ */ 1383 public int getValueOrdinal() 1384 { 1385 return HttpHeaderValues.CACHE.getOrdinal(_value); 1386 } 1387 1388 /* ------------------------------------------------------------ */ 1389 public int getIntValue() 1390 { 1391 return (int) getLongValue(); 1392 } 1393 1394 /* ------------------------------------------------------------ */ 1395 public long getLongValue() 1396 { 1397 return BufferUtil.toLong(_value); 1398 } 1399 1400 /* ------------------------------------------------------------ */ 1401 public String toString() 1402 { 1403 return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]"); 1404 } 1405 } 1406 } 1407