Mercurial Hosting > luan
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/eclipse/jetty/http/HttpFields.java Wed Sep 07 21:15:48 2016 -0600 @@ -0,0 +1,1406 @@ +// +// ======================================================================== +// 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 ? "" : "->") + "]"); + } + } +}