comparison src/org/eclipse/jetty/http/HttpFields.java @ 802:3428c60d7cfc

replace jetty jars with source
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 07 Sep 2016 21:15:48 -0600
parents
children 8e9db0bbf4f9
comparison
equal deleted inserted replaced
801:6a21393191c1 802:3428c60d7cfc
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 */
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 */
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 */
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 */
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 */
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
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 /* ------------------------------------------------------------ */
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 */
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 /* -------------------------------------------------------------- */
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 */
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 */
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 }