| // |
| // ======================================================================== |
| // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. |
| // ------------------------------------------------------------------------ |
| // All rights reserved. This program and the accompanying materials |
| // are made available under the terms of the Eclipse Public License v1.0 |
| // and Apache License v2.0 which accompanies this distribution. |
| // |
| // The Eclipse Public License is available at |
| // http://www.eclipse.org/legal/epl-v10.html |
| // |
| // The Apache License v2.0 is available at |
| // http://www.opensource.org/licenses/apache2.0.php |
| // |
| // You may elect to redistribute this code under either of these licenses. |
| // ======================================================================== |
| // |
| |
| package org.eclipse.jetty.http; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.text.SimpleDateFormat; |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| import java.util.Collections; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.GregorianCalendar; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.StringTokenizer; |
| import java.util.TimeZone; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| |
| import org.eclipse.jetty.io.Buffer; |
| import org.eclipse.jetty.io.BufferCache; |
| import org.eclipse.jetty.io.BufferCache.CachedBuffer; |
| import org.eclipse.jetty.io.BufferDateCache; |
| import org.eclipse.jetty.io.BufferUtil; |
| import org.eclipse.jetty.io.ByteArrayBuffer; |
| import org.eclipse.jetty.util.LazyList; |
| import org.eclipse.jetty.util.QuotedStringTokenizer; |
| import org.eclipse.jetty.util.StringMap; |
| import org.eclipse.jetty.util.StringUtil; |
| import org.eclipse.jetty.util.log.Log; |
| import org.eclipse.jetty.util.log.Logger; |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * HTTP Fields. A collection of HTTP header and or Trailer fields. |
| * |
| * <p>This class is not synchronized as it is expected that modifications will only be performed by a |
| * single thread. |
| * |
| * |
| */ |
| public class HttpFields |
| { |
| private static final Logger LOG = Log.getLogger(HttpFields.class); |
| |
| /* ------------------------------------------------------------ */ |
| public static final String __COOKIE_DELIM="\"\\\n\r\t\f\b%+ ;="; |
| public static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); |
| public static final BufferDateCache __dateCache = new BufferDateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); |
| |
| /* -------------------------------------------------------------- */ |
| static |
| { |
| __GMT.setID("GMT"); |
| __dateCache.setTimeZone(__GMT); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public final static String __separators = ", \t"; |
| |
| /* ------------------------------------------------------------ */ |
| private static final String[] DAYS = |
| { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; |
| private static final String[] MONTHS = |
| { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"}; |
| |
| |
| /* ------------------------------------------------------------ */ |
| private static class DateGenerator |
| { |
| private final StringBuilder buf = new StringBuilder(32); |
| private final GregorianCalendar gc = new GregorianCalendar(__GMT); |
| |
| /** |
| * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" |
| */ |
| public String formatDate(long date) |
| { |
| buf.setLength(0); |
| gc.setTimeInMillis(date); |
| |
| int day_of_week = gc.get(Calendar.DAY_OF_WEEK); |
| int day_of_month = gc.get(Calendar.DAY_OF_MONTH); |
| int month = gc.get(Calendar.MONTH); |
| int year = gc.get(Calendar.YEAR); |
| int century = year / 100; |
| year = year % 100; |
| |
| int hours = gc.get(Calendar.HOUR_OF_DAY); |
| int minutes = gc.get(Calendar.MINUTE); |
| int seconds = gc.get(Calendar.SECOND); |
| |
| buf.append(DAYS[day_of_week]); |
| buf.append(','); |
| buf.append(' '); |
| StringUtil.append2digits(buf, day_of_month); |
| |
| buf.append(' '); |
| buf.append(MONTHS[month]); |
| buf.append(' '); |
| StringUtil.append2digits(buf, century); |
| StringUtil.append2digits(buf, year); |
| |
| buf.append(' '); |
| StringUtil.append2digits(buf, hours); |
| buf.append(':'); |
| StringUtil.append2digits(buf, minutes); |
| buf.append(':'); |
| StringUtil.append2digits(buf, seconds); |
| buf.append(" GMT"); |
| return buf.toString(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Format "EEE, dd-MMM-yy HH:mm:ss 'GMT'" for cookies |
| */ |
| public void formatCookieDate(StringBuilder buf, long date) |
| { |
| gc.setTimeInMillis(date); |
| |
| int day_of_week = gc.get(Calendar.DAY_OF_WEEK); |
| int day_of_month = gc.get(Calendar.DAY_OF_MONTH); |
| int month = gc.get(Calendar.MONTH); |
| int year = gc.get(Calendar.YEAR); |
| year = year % 10000; |
| |
| int epoch = (int) ((date / 1000) % (60 * 60 * 24)); |
| int seconds = epoch % 60; |
| epoch = epoch / 60; |
| int minutes = epoch % 60; |
| int hours = epoch / 60; |
| |
| buf.append(DAYS[day_of_week]); |
| buf.append(','); |
| buf.append(' '); |
| StringUtil.append2digits(buf, day_of_month); |
| |
| buf.append('-'); |
| buf.append(MONTHS[month]); |
| buf.append('-'); |
| StringUtil.append2digits(buf, year/100); |
| StringUtil.append2digits(buf, year%100); |
| |
| buf.append(' '); |
| StringUtil.append2digits(buf, hours); |
| buf.append(':'); |
| StringUtil.append2digits(buf, minutes); |
| buf.append(':'); |
| StringUtil.append2digits(buf, seconds); |
| buf.append(" GMT"); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private static final ThreadLocal<DateGenerator> __dateGenerator =new ThreadLocal<DateGenerator>() |
| { |
| @Override |
| protected DateGenerator initialValue() |
| { |
| return new DateGenerator(); |
| } |
| }; |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" |
| */ |
| public static String formatDate(long date) |
| { |
| return __dateGenerator.get().formatDate(date); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies |
| */ |
| public static void formatCookieDate(StringBuilder buf, long date) |
| { |
| __dateGenerator.get().formatCookieDate(buf,date); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies |
| */ |
| public static String formatCookieDate(long date) |
| { |
| StringBuilder buf = new StringBuilder(28); |
| formatCookieDate(buf, date); |
| return buf.toString(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private final static String __dateReceiveFmt[] = |
| { |
| "EEE, dd MMM yyyy HH:mm:ss zzz", |
| "EEE, dd-MMM-yy HH:mm:ss", |
| "EEE MMM dd HH:mm:ss yyyy", |
| |
| "EEE, dd MMM yyyy HH:mm:ss", "EEE dd MMM yyyy HH:mm:ss zzz", |
| "EEE dd MMM yyyy HH:mm:ss", "EEE MMM dd yyyy HH:mm:ss zzz", "EEE MMM dd yyyy HH:mm:ss", |
| "EEE MMM-dd-yyyy HH:mm:ss zzz", "EEE MMM-dd-yyyy HH:mm:ss", "dd MMM yyyy HH:mm:ss zzz", |
| "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", |
| "MMM dd HH:mm:ss yyyy", "EEE MMM dd HH:mm:ss yyyy zzz", |
| "EEE, MMM dd HH:mm:ss yyyy zzz", "EEE, MMM dd HH:mm:ss yyyy", "EEE, dd-MMM-yy HH:mm:ss zzz", |
| "EEE dd-MMM-yy HH:mm:ss zzz", "EEE dd-MMM-yy HH:mm:ss", |
| }; |
| |
| /* ------------------------------------------------------------ */ |
| private static class DateParser |
| { |
| final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length]; |
| |
| long parse(final String dateVal) |
| { |
| for (int i = 0; i < _dateReceive.length; i++) |
| { |
| if (_dateReceive[i] == null) |
| { |
| _dateReceive[i] = new SimpleDateFormat(__dateReceiveFmt[i], Locale.US); |
| _dateReceive[i].setTimeZone(__GMT); |
| } |
| |
| try |
| { |
| Date date = (Date) _dateReceive[i].parseObject(dateVal); |
| return date.getTime(); |
| } |
| catch (java.lang.Exception e) |
| { |
| // LOG.ignore(e); |
| } |
| } |
| |
| if (dateVal.endsWith(" GMT")) |
| { |
| final String val = dateVal.substring(0, dateVal.length() - 4); |
| |
| for (int i = 0; i < _dateReceive.length; i++) |
| { |
| try |
| { |
| Date date = (Date) _dateReceive[i].parseObject(val); |
| return date.getTime(); |
| } |
| catch (java.lang.Exception e) |
| { |
| // LOG.ignore(e); |
| } |
| } |
| } |
| return -1; |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public static long parseDate(String date) |
| { |
| return __dateParser.get().parse(date); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private static final ThreadLocal<DateParser> __dateParser =new ThreadLocal<DateParser>() |
| { |
| @Override |
| protected DateParser initialValue() |
| { |
| return new DateParser(); |
| } |
| }; |
| |
| /* -------------------------------------------------------------- */ |
| public final static String __01Jan1970=formatDate(0); |
| public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970); |
| public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim(); |
| |
| /* -------------------------------------------------------------- */ |
| private final ArrayList<Field> _fields = new ArrayList<Field>(20); |
| private final HashMap<Buffer,Field> _names = new HashMap<Buffer,Field>(32); |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Constructor. |
| */ |
| public HttpFields() |
| { |
| } |
| |
| // TODO externalize this cache so it can be configurable |
| private static ConcurrentMap<String, Buffer> __cache = new ConcurrentHashMap<String, Buffer>(); |
| private static int __cacheSize = Integer.getInteger("org.eclipse.jetty.http.HttpFields.CACHE",2000); |
| |
| /* -------------------------------------------------------------- */ |
| private Buffer convertValue(String value) |
| { |
| Buffer buffer = __cache.get(value); |
| if (buffer!=null) |
| return buffer; |
| |
| try |
| { |
| buffer = new ByteArrayBuffer(value,StringUtil.__ISO_8859_1); |
| |
| if (__cacheSize>0) |
| { |
| if (__cache.size()>__cacheSize) |
| __cache.clear(); |
| Buffer b=__cache.putIfAbsent(value,buffer); |
| if (b!=null) |
| buffer=b; |
| } |
| |
| return buffer; |
| } |
| catch (UnsupportedEncodingException e) |
| { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get Collection of header names. |
| */ |
| public Collection<String> getFieldNamesCollection() |
| { |
| final List<String> list = new ArrayList<String>(_fields.size()); |
| |
| for (Field f : _fields) |
| { |
| if (f!=null) |
| list.add(BufferUtil.to8859_1_String(f._name)); |
| } |
| return list; |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get enumeration of header _names. Returns an enumeration of strings representing the header |
| * _names for this request. |
| */ |
| public Enumeration<String> getFieldNames() |
| { |
| final Enumeration<?> buffers = Collections.enumeration(_names.keySet()); |
| return new Enumeration<String>() |
| { |
| public String nextElement() |
| { |
| return buffers.nextElement().toString(); |
| } |
| |
| public boolean hasMoreElements() |
| { |
| return buffers.hasMoreElements(); |
| } |
| }; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public int size() |
| { |
| return _fields.size(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Get a Field by index. |
| * @return A Field value or null if the Field value has not been set |
| * |
| */ |
| public Field getField(int i) |
| { |
| return _fields.get(i); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private Field getField(String name) |
| { |
| return _names.get(HttpHeaders.CACHE.lookup(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private Field getField(Buffer name) |
| { |
| return _names.get(HttpHeaders.CACHE.lookup(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public boolean containsKey(Buffer name) |
| { |
| return _names.containsKey(HttpHeaders.CACHE.lookup(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public boolean containsKey(String name) |
| { |
| return _names.containsKey(HttpHeaders.CACHE.lookup(name)); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * @return the value of a field, or null if not found. For multiple fields of the same name, |
| * only the first is returned. |
| * @param name the case-insensitive field name |
| */ |
| public String getStringField(String name) |
| { |
| Field field = getField(name); |
| return field==null?null:field.getValue(); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * @return the value of a field, or null if not found. For multiple fields of the same name, |
| * only the first is returned. |
| * @param name the case-insensitive field name |
| */ |
| public String getStringField(Buffer name) |
| { |
| Field field = getField(name); |
| return field==null?null:field.getValue(); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * @return the value of a field, or null if not found. For multiple fields of the same name, |
| * only the first is returned. |
| * @param name the case-insensitive field name |
| */ |
| public Buffer get(Buffer name) |
| { |
| Field field = getField(name); |
| return field==null?null:field._value; |
| } |
| |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get multi headers |
| * |
| * @return Enumeration of the values, or null if no such header. |
| * @param name the case-insensitive field name |
| */ |
| public Collection<String> getValuesCollection(String name) |
| { |
| Field field = getField(name); |
| if (field==null) |
| return null; |
| |
| final List<String> list = new ArrayList<String>(); |
| |
| while(field!=null) |
| { |
| list.add(field.getValue()); |
| field=field._next; |
| } |
| return list; |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get multi headers |
| * |
| * @return Enumeration of the values |
| * @param name the case-insensitive field name |
| */ |
| public Enumeration<String> getValues(String name) |
| { |
| final Field field = getField(name); |
| if (field == null) |
| { |
| List<String> empty=Collections.emptyList(); |
| return Collections.enumeration(empty); |
| } |
| |
| return new Enumeration<String>() |
| { |
| Field f = field; |
| |
| public boolean hasMoreElements() |
| { |
| return f != null; |
| } |
| |
| public String nextElement() throws NoSuchElementException |
| { |
| if (f == null) throw new NoSuchElementException(); |
| Field n = f; |
| f = f._next; |
| return n.getValue(); |
| } |
| }; |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get multi headers |
| * |
| * @return Enumeration of the value Strings |
| * @param name the case-insensitive field name |
| */ |
| public Enumeration<String> getValues(Buffer name) |
| { |
| final Field field = getField(name); |
| if (field == null) |
| { |
| List<String> empty=Collections.emptyList(); |
| return Collections.enumeration(empty); |
| } |
| |
| return new Enumeration<String>() |
| { |
| Field f = field; |
| |
| public boolean hasMoreElements() |
| { |
| return f != null; |
| } |
| |
| public String nextElement() throws NoSuchElementException |
| { |
| if (f == null) throw new NoSuchElementException(); |
| Field n = f; |
| f = f._next; |
| return n.getValue(); |
| } |
| }; |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get multi field values with separator. The multiple values can be represented as separate |
| * headers of the same name, or by a single header using the separator(s), or a combination of |
| * both. Separators may be quoted. |
| * |
| * @param name the case-insensitive field name |
| * @param separators String of separators. |
| * @return Enumeration of the values, or null if no such header. |
| */ |
| public Enumeration<String> getValues(String name, final String separators) |
| { |
| final Enumeration<String> e = getValues(name); |
| if (e == null) |
| return null; |
| return new Enumeration<String>() |
| { |
| QuotedStringTokenizer tok = null; |
| |
| public boolean hasMoreElements() |
| { |
| if (tok != null && tok.hasMoreElements()) return true; |
| while (e.hasMoreElements()) |
| { |
| String value = e.nextElement(); |
| tok = new QuotedStringTokenizer(value, separators, false, false); |
| if (tok.hasMoreElements()) return true; |
| } |
| tok = null; |
| return false; |
| } |
| |
| public String nextElement() throws NoSuchElementException |
| { |
| if (!hasMoreElements()) throw new NoSuchElementException(); |
| String next = (String) tok.nextElement(); |
| if (next != null) next = next.trim(); |
| return next; |
| } |
| }; |
| } |
| |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Set a field. |
| * |
| * @param name the name of the field |
| * @param value the value of the field. If null the field is cleared. |
| */ |
| public void put(String name, String value) |
| { |
| if (value==null) |
| remove(name); |
| else |
| { |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| Buffer v = convertValue(value); |
| put(n, v); |
| } |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Set a field. |
| * |
| * @param name the name of the field |
| * @param value the value of the field. If null the field is cleared. |
| */ |
| public void put(Buffer name, String value) |
| { |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| Buffer v = convertValue(value); |
| put(n, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Set a field. |
| * |
| * @param name the name of the field |
| * @param value the value of the field. If null the field is cleared. |
| */ |
| public void put(Buffer name, Buffer value) |
| { |
| remove(name); |
| if (value == null) |
| return; |
| |
| if (!(name instanceof BufferCache.CachedBuffer)) |
| name = HttpHeaders.CACHE.lookup(name); |
| if (!(value instanceof CachedBuffer)) |
| value= HttpHeaderValues.CACHE.lookup(value).asImmutableBuffer(); |
| |
| // new value; |
| Field field = new Field(name, value); |
| _fields.add(field); |
| _names.put(name, field); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Set a field. |
| * |
| * @param name the name of the field |
| * @param list the List value of the field. If null the field is cleared. |
| */ |
| public void put(String name, List<?> list) |
| { |
| if (list == null || list.size() == 0) |
| { |
| remove(name); |
| return; |
| } |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| |
| Object v = list.get(0); |
| if (v != null) |
| put(n, HttpHeaderValues.CACHE.lookup(v.toString())); |
| else |
| remove(n); |
| |
| if (list.size() > 1) |
| { |
| java.util.Iterator<?> iter = list.iterator(); |
| iter.next(); |
| while (iter.hasNext()) |
| { |
| v = iter.next(); |
| if (v != null) put(n, HttpHeaderValues.CACHE.lookup(v.toString())); |
| } |
| } |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Add to or set a field. If the field is allowed to have multiple values, add will add multiple |
| * headers of the same name. |
| * |
| * @param name the name of the field |
| * @param value the value of the field. |
| * @exception IllegalArgumentException If the name is a single valued field and already has a |
| * value. |
| */ |
| public void add(String name, String value) throws IllegalArgumentException |
| { |
| if (value==null) |
| return; |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| Buffer v = convertValue(value); |
| add(n, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Add to or set a field. If the field is allowed to have multiple values, add will add multiple |
| * headers of the same name. |
| * |
| * @param name the name of the field |
| * @param value the value of the field. |
| * @exception IllegalArgumentException If the name is a single valued field and already has a |
| * value. |
| */ |
| public void add(Buffer name, Buffer value) throws IllegalArgumentException |
| { |
| if (value == null) throw new IllegalArgumentException("null value"); |
| |
| if (!(name instanceof CachedBuffer)) |
| name = HttpHeaders.CACHE.lookup(name); |
| name=name.asImmutableBuffer(); |
| |
| if (!(value instanceof CachedBuffer) && HttpHeaderValues.hasKnownValues(HttpHeaders.CACHE.getOrdinal(name))) |
| value= HttpHeaderValues.CACHE.lookup(value); |
| value=value.asImmutableBuffer(); |
| |
| Field field = _names.get(name); |
| Field last = null; |
| while (field != null) |
| { |
| last = field; |
| field = field._next; |
| } |
| |
| // create the field |
| field = new Field(name, value); |
| _fields.add(field); |
| |
| // look for chain to add too |
| if (last != null) |
| last._next = field; |
| else |
| _names.put(name, field); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Remove a field. |
| * |
| * @param name |
| */ |
| public void remove(String name) |
| { |
| remove(HttpHeaders.CACHE.lookup(name)); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Remove a field. |
| * |
| * @param name |
| */ |
| public void remove(Buffer name) |
| { |
| if (!(name instanceof BufferCache.CachedBuffer)) |
| name = HttpHeaders.CACHE.lookup(name); |
| Field field = _names.remove(name); |
| while (field != null) |
| { |
| _fields.remove(field); |
| field = field._next; |
| } |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get a header as an long value. Returns the value of an integer field or -1 if not found. The |
| * case of the field name is ignored. |
| * |
| * @param name the case-insensitive field name |
| * @exception NumberFormatException If bad long found |
| */ |
| public long getLongField(String name) throws NumberFormatException |
| { |
| Field field = getField(name); |
| return field==null?-1L:field.getLongValue(); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get a header as an long value. Returns the value of an integer field or -1 if not found. The |
| * case of the field name is ignored. |
| * |
| * @param name the case-insensitive field name |
| * @exception NumberFormatException If bad long found |
| */ |
| public long getLongField(Buffer name) throws NumberFormatException |
| { |
| Field field = getField(name); |
| return field==null?-1L:field.getLongValue(); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Get a header as a date value. Returns the value of a date field, or -1 if not found. The case |
| * of the field name is ignored. |
| * |
| * @param name the case-insensitive field name |
| */ |
| public long getDateField(String name) |
| { |
| Field field = getField(name); |
| if (field == null) |
| return -1; |
| |
| String val = valueParameters(BufferUtil.to8859_1_String(field._value), null); |
| if (val == null) |
| return -1; |
| |
| final long date = __dateParser.get().parse(val); |
| if (date==-1) |
| throw new IllegalArgumentException("Cannot convert date: " + val); |
| return date; |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of an long field. |
| * |
| * @param name the field name |
| * @param value the field long value |
| */ |
| public void putLongField(Buffer name, long value) |
| { |
| Buffer v = BufferUtil.toBuffer(value); |
| put(name, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of an long field. |
| * |
| * @param name the field name |
| * @param value the field long value |
| */ |
| public void putLongField(String name, long value) |
| { |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| Buffer v = BufferUtil.toBuffer(value); |
| put(n, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of an long field. |
| * |
| * @param name the field name |
| * @param value the field long value |
| */ |
| public void addLongField(String name, long value) |
| { |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| Buffer v = BufferUtil.toBuffer(value); |
| add(n, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of an long field. |
| * |
| * @param name the field name |
| * @param value the field long value |
| */ |
| public void addLongField(Buffer name, long value) |
| { |
| Buffer v = BufferUtil.toBuffer(value); |
| add(name, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of a date field. |
| * |
| * @param name the field name |
| * @param date the field date value |
| */ |
| public void putDateField(Buffer name, long date) |
| { |
| String d=formatDate(date); |
| Buffer v = new ByteArrayBuffer(d); |
| put(name, v); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of a date field. |
| * |
| * @param name the field name |
| * @param date the field date value |
| */ |
| public void putDateField(String name, long date) |
| { |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| putDateField(n,date); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| /** |
| * Sets the value of a date field. |
| * |
| * @param name the field name |
| * @param date the field date value |
| */ |
| public void addDateField(String name, long date) |
| { |
| String d=formatDate(date); |
| Buffer n = HttpHeaders.CACHE.lookup(name); |
| Buffer v = new ByteArrayBuffer(d); |
| add(n, v); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Format a set cookie value |
| * |
| * @param cookie The cookie. |
| */ |
| public void addSetCookie(HttpCookie cookie) |
| { |
| addSetCookie( |
| cookie.getName(), |
| cookie.getValue(), |
| cookie.getDomain(), |
| cookie.getPath(), |
| cookie.getMaxAge(), |
| cookie.getComment(), |
| cookie.isSecure(), |
| cookie.isHttpOnly(), |
| cookie.getVersion()); |
| } |
| |
| /** |
| * Format a set cookie value |
| * |
| * @param name the name |
| * @param value the value |
| * @param domain the domain |
| * @param path the path |
| * @param maxAge the maximum age |
| * @param comment the comment (only present on versions > 0) |
| * @param isSecure true if secure cookie |
| * @param isHttpOnly true if for http only |
| * @param version version of cookie logic to use (0 == default behavior) |
| */ |
| public void addSetCookie( |
| final String name, |
| final String value, |
| final String domain, |
| final String path, |
| final long maxAge, |
| final String comment, |
| final boolean isSecure, |
| final boolean isHttpOnly, |
| int version) |
| { |
| String delim=__COOKIE_DELIM; |
| |
| // Check arguments |
| if (name == null || name.length() == 0) |
| throw new IllegalArgumentException("Bad cookie name"); |
| |
| // Format value and params |
| StringBuilder buf = new StringBuilder(128); |
| String name_value_params; |
| QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); |
| buf.append('='); |
| String start=buf.toString(); |
| boolean hasDomain = false; |
| boolean hasPath = false; |
| |
| if (value != null && value.length() > 0) |
| QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); |
| |
| if (comment != null && comment.length() > 0) |
| { |
| buf.append(";Comment="); |
| QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim); |
| } |
| |
| if (path != null && path.length() > 0) |
| { |
| hasPath = true; |
| buf.append(";Path="); |
| if (path.trim().startsWith("\"")) |
| buf.append(path); |
| else |
| QuotedStringTokenizer.quoteIfNeeded(buf,path,delim); |
| } |
| if (domain != null && domain.length() > 0) |
| { |
| hasDomain = true; |
| buf.append(";Domain="); |
| QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(Locale.ENGLISH),delim); |
| } |
| |
| if (maxAge >= 0) |
| { |
| // Always add the expires param as some browsers still don't handle max-age |
| buf.append(";Expires="); |
| if (maxAge == 0) |
| buf.append(__01Jan1970_COOKIE); |
| else |
| formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); |
| |
| if (version >0) |
| { |
| buf.append(";Max-Age="); |
| buf.append(maxAge); |
| } |
| } |
| |
| if (isSecure) |
| buf.append(";Secure"); |
| if (isHttpOnly) |
| buf.append(";HttpOnly"); |
| |
| name_value_params = buf.toString(); |
| |
| // remove existing set-cookie of same name |
| Field field = getField(HttpHeaders.SET_COOKIE); |
| Field last=null; |
| while (field!=null) |
| { |
| String val = (field._value == null ? null : field._value.toString()); |
| if (val!=null && val.startsWith(start)) |
| { |
| //existing cookie has same name, does it also match domain and path? |
| if (((!hasDomain && !val.contains("Domain")) || (hasDomain && val.contains("Domain="+domain))) && |
| ((!hasPath && !val.contains("Path")) || (hasPath && val.contains("Path="+path)))) |
| { |
| _fields.remove(field); |
| if (last==null) |
| _names.put(HttpHeaders.SET_COOKIE_BUFFER,field._next); |
| else |
| last._next=field._next; |
| break; |
| } |
| } |
| last=field; |
| field=field._next; |
| } |
| |
| add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params)); |
| |
| // Expire responses with set-cookie headers so they do not get cached. |
| put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| public void putTo(Buffer buffer) throws IOException |
| { |
| for (int i = 0; i < _fields.size(); i++) |
| { |
| Field field = _fields.get(i); |
| if (field != null) |
| field.putTo(buffer); |
| } |
| BufferUtil.putCRLF(buffer); |
| } |
| |
| /* -------------------------------------------------------------- */ |
| public String toString() |
| { |
| try |
| { |
| StringBuffer buffer = new StringBuffer(); |
| for (int i = 0; i < _fields.size(); i++) |
| { |
| Field field = (Field) _fields.get(i); |
| if (field != null) |
| { |
| String tmp = field.getName(); |
| if (tmp != null) buffer.append(tmp); |
| buffer.append(": "); |
| tmp = field.getValue(); |
| if (tmp != null) buffer.append(tmp); |
| buffer.append("\r\n"); |
| } |
| } |
| buffer.append("\r\n"); |
| return buffer.toString(); |
| } |
| catch (Exception e) |
| { |
| LOG.warn(e); |
| return e.toString(); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Clear the header. |
| */ |
| public void clear() |
| { |
| _fields.clear(); |
| _names.clear(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Add fields from another HttpFields instance. Single valued fields are replaced, while all |
| * others are added. |
| * |
| * @param fields |
| */ |
| public void add(HttpFields fields) |
| { |
| if (fields == null) return; |
| |
| Enumeration e = fields.getFieldNames(); |
| while (e.hasMoreElements()) |
| { |
| String name = (String) e.nextElement(); |
| Enumeration values = fields.getValues(name); |
| while (values.hasMoreElements()) |
| add(name, (String) values.nextElement()); |
| } |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * Get field value parameters. Some field values can have parameters. This method separates the |
| * value from the parameters and optionally populates a map with the parameters. For example: |
| * |
| * <PRE> |
| * |
| * FieldName : Value ; param1=val1 ; param2=val2 |
| * |
| * </PRE> |
| * |
| * @param value The Field value, possibly with parameteres. |
| * @param parameters A map to populate with the parameters, or null |
| * @return The value. |
| */ |
| public static String valueParameters(String value, Map<String,String> parameters) |
| { |
| if (value == null) return null; |
| |
| int i = value.indexOf(';'); |
| if (i < 0) return value; |
| if (parameters == null) return value.substring(0, i).trim(); |
| |
| StringTokenizer tok1 = new QuotedStringTokenizer(value.substring(i), ";", false, true); |
| while (tok1.hasMoreTokens()) |
| { |
| String token = tok1.nextToken(); |
| StringTokenizer tok2 = new QuotedStringTokenizer(token, "= "); |
| if (tok2.hasMoreTokens()) |
| { |
| String paramName = tok2.nextToken(); |
| String paramVal = null; |
| if (tok2.hasMoreTokens()) paramVal = tok2.nextToken(); |
| parameters.put(paramName, paramVal); |
| } |
| } |
| |
| return value.substring(0, i).trim(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| private static final Float __one = new Float("1.0"); |
| private static final Float __zero = new Float("0.0"); |
| private static final StringMap __qualities = new StringMap(); |
| static |
| { |
| __qualities.put(null, __one); |
| __qualities.put("1.0", __one); |
| __qualities.put("1", __one); |
| __qualities.put("0.9", new Float("0.9")); |
| __qualities.put("0.8", new Float("0.8")); |
| __qualities.put("0.7", new Float("0.7")); |
| __qualities.put("0.66", new Float("0.66")); |
| __qualities.put("0.6", new Float("0.6")); |
| __qualities.put("0.5", new Float("0.5")); |
| __qualities.put("0.4", new Float("0.4")); |
| __qualities.put("0.33", new Float("0.33")); |
| __qualities.put("0.3", new Float("0.3")); |
| __qualities.put("0.2", new Float("0.2")); |
| __qualities.put("0.1", new Float("0.1")); |
| __qualities.put("0", __zero); |
| __qualities.put("0.0", __zero); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public static Float getQuality(String value) |
| { |
| if (value == null) return __zero; |
| |
| int qe = value.indexOf(";"); |
| if (qe++ < 0 || qe == value.length()) return __one; |
| |
| if (value.charAt(qe++) == 'q') |
| { |
| qe++; |
| Map.Entry entry = __qualities.getEntry(value, qe, value.length() - qe); |
| if (entry != null) return (Float) entry.getValue(); |
| } |
| |
| HashMap params = new HashMap(3); |
| valueParameters(value, params); |
| String qs = (String) params.get("q"); |
| Float q = (Float) __qualities.get(qs); |
| if (q == null) |
| { |
| try |
| { |
| q = new Float(qs); |
| } |
| catch (Exception e) |
| { |
| q = __one; |
| } |
| } |
| return q; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /** |
| * List values in quality order. |
| * |
| * @param e Enumeration of values with quality parameters |
| * @return values in quality order. |
| */ |
| public static List qualityList(Enumeration e) |
| { |
| if (e == null || !e.hasMoreElements()) return Collections.EMPTY_LIST; |
| |
| Object list = null; |
| Object qual = null; |
| |
| // Assume list will be well ordered and just add nonzero |
| while (e.hasMoreElements()) |
| { |
| String v = e.nextElement().toString(); |
| Float q = getQuality(v); |
| |
| if (q.floatValue() >= 0.001) |
| { |
| list = LazyList.add(list, v); |
| qual = LazyList.add(qual, q); |
| } |
| } |
| |
| List vl = LazyList.getList(list, false); |
| if (vl.size() < 2) return vl; |
| |
| List ql = LazyList.getList(qual, false); |
| |
| // sort list with swaps |
| Float last = __zero; |
| for (int i = vl.size(); i-- > 0;) |
| { |
| Float q = (Float) ql.get(i); |
| if (last.compareTo(q) > 0) |
| { |
| Object tmp = vl.get(i); |
| vl.set(i, vl.get(i + 1)); |
| vl.set(i + 1, tmp); |
| ql.set(i, ql.get(i + 1)); |
| ql.set(i + 1, q); |
| last = __zero; |
| i = vl.size(); |
| continue; |
| } |
| last = q; |
| } |
| ql.clear(); |
| return vl; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| /* ------------------------------------------------------------ */ |
| public static final class Field |
| { |
| private Buffer _name; |
| private Buffer _value; |
| private Field _next; |
| |
| /* ------------------------------------------------------------ */ |
| private Field(Buffer name, Buffer value) |
| { |
| _name = name; |
| _value = value; |
| _next = null; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public void putTo(Buffer buffer) throws IOException |
| { |
| int o=(_name instanceof CachedBuffer)?((CachedBuffer)_name).getOrdinal():-1; |
| if (o>=0) |
| buffer.put(_name); |
| else |
| { |
| int s=_name.getIndex(); |
| int e=_name.putIndex(); |
| while (s<e) |
| { |
| byte b=_name.peek(s++); |
| switch(b) |
| { |
| case '\r': |
| case '\n': |
| case ':' : |
| continue; |
| default: |
| buffer.put(b); |
| } |
| } |
| } |
| |
| buffer.put((byte) ':'); |
| buffer.put((byte) ' '); |
| |
| o=(_value instanceof CachedBuffer)?((CachedBuffer)_value).getOrdinal():-1; |
| if (o>=0) |
| buffer.put(_value); |
| else |
| { |
| int s=_value.getIndex(); |
| int e=_value.putIndex(); |
| while (s<e) |
| { |
| byte b=_value.peek(s++); |
| switch(b) |
| { |
| case '\r': |
| case '\n': |
| continue; |
| default: |
| buffer.put(b); |
| } |
| } |
| } |
| |
| BufferUtil.putCRLF(buffer); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public String getName() |
| { |
| return BufferUtil.to8859_1_String(_name); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| Buffer getNameBuffer() |
| { |
| return _name; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public int getNameOrdinal() |
| { |
| return HttpHeaders.CACHE.getOrdinal(_name); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public String getValue() |
| { |
| return BufferUtil.to8859_1_String(_value); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public Buffer getValueBuffer() |
| { |
| return _value; |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public int getValueOrdinal() |
| { |
| return HttpHeaderValues.CACHE.getOrdinal(_value); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public int getIntValue() |
| { |
| return (int) getLongValue(); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public long getLongValue() |
| { |
| return BufferUtil.toLong(_value); |
| } |
| |
| /* ------------------------------------------------------------ */ |
| public String toString() |
| { |
| return ("[" + getName() + "=" + _value + (_next == null ? "" : "->") + "]"); |
| } |
| } |
| } |