// ========================================================================
// $Id: HttpFields.java,v 1.77 2006/11/22 20:02:15 gregwilkins Exp $
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.openqa.jetty.http;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
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 javax.servlet.http.Cookie;
import org.apache.commons.logging.Log;
import org.openqa.jetty.log.LogFactory;
import org.openqa.jetty.util.DateCache;
import org.openqa.jetty.util.LazyList;
import org.openqa.jetty.util.LineInput;
import org.openqa.jetty.util.LogSupport;
import org.openqa.jetty.util.QuotedStringTokenizer;
import org.openqa.jetty.util.StringMap;
import org.openqa.jetty.util.StringUtil;
import org.openqa.jetty.util.URI;
/* ------------------------------------------------------------ */
/** HTTP Fields.
* A collection of HTTP header and or Trailer fields.
* This class is not synchronized and needs to be protected from
* concurrent access.
*
* This class is not synchronized as it is expected that modifications
* will only be performed by a single thread.
*
* @version $Id: HttpFields.java,v 1.77 2006/11/22 20:02:15 gregwilkins Exp $
* @author Greg Wilkins (gregw)
*/
public class HttpFields
{
private static Log log = LogFactory.getLog(HttpFields.class);
/* ------------------------------------------------------------ */
/** General Fields.
*/
public final static String
__CacheControl = "Cache-Control",
__Connection = "Connection",
__Date = "Date",
__Pragma = "Pragma",
__ProxyConnection = "Proxy-Connection",
__Trailer = "Trailer",
__TransferEncoding = "Transfer-Encoding",
__Upgrade = "Upgrade",
__Via = "Via",
__Warning = "Warning";
/* ------------------------------------------------------------ */
/** Entity Fields.
*/
public final static String
__Allow = "Allow",
__ContentEncoding = "Content-Encoding",
__ContentLanguage = "Content-Language",
__ContentLength = "Content-Length",
__ContentLocation = "Content-Location",
__ContentMD5 = "Content-MD5",
__ContentRange = "Content-Range",
__ContentType = "Content-Type",
__Expires = "Expires",
__LastModified = "Last-Modified";
/* ------------------------------------------------------------ */
/** Request Fields.
*/
public final static String
__Accept = "Accept",
__AcceptCharset = "Accept-Charset",
__AcceptEncoding = "Accept-Encoding",
__AcceptLanguage = "Accept-Language",
__Authorization = "Authorization",
__Expect = "Expect",
__Forwarded = "Forwarded",
__From = "From",
__Host = "Host",
__IfMatch = "If-Match",
__IfModifiedSince = "If-Modified-Since",
__IfNoneMatch = "If-None-Match",
__IfRange = "If-Range",
__IfUnmodifiedSince = "If-Unmodified-Since",
__KeepAlive = "keep-alive",
__MaxForwards = "Max-Forwards",
__ProxyAuthorization = "Proxy-Authorization",
__Range = "Range",
__RequestRange = "Request-Range",
__Referer = "Referer",
__TE = "TE",
__UserAgent = "User-Agent",
__XForwardedFor = "X-Forwarded-For";
/* ------------------------------------------------------------ */
/** Response Fields.
*/
public final static String
__AcceptRanges = "Accept-Ranges",
__Age = "Age",
__ETag = "ETag",
__Location = "Location",
__ProxyAuthenticate = "Proxy-Authenticate",
__RetryAfter = "Retry-After",
__Server = "Server",
__ServletEngine = "Servlet-Engine",
__Vary = "Vary",
__WwwAuthenticate = "WWW-Authenticate";
/* ------------------------------------------------------------ */
/** Other Fields.
*/
public final static String
__Cookie = "Cookie",
__SetCookie = "Set-Cookie",
__SetCookie2 = "Set-Cookie2",
__MimeVersion ="MIME-Version",
__Identity ="identity",
__SoapAction ="SOAPAction";
/* ------------------------------------------------------------ */
/** Private class to hold Field name info
*/
private static final class FieldInfo
{
String _name;
String _lname;
boolean _inlineValues;
int _hashCode;
static int __hashCode;
FieldInfo(String name, boolean inline)
{
synchronized(FieldInfo.class)
{
_name=name;
_lname=StringUtil.asciiToLowerCase(name);
_inlineValues=inline;
_hashCode=__hashCode++;
if (__hashCode < __maxCacheSize)
{
FieldInfo oldInfo = (FieldInfo)__info.get(name);
if (oldInfo == null)
{
__info.put(name, this);
if (!name.equals(_lname))
__info.put(_lname, this);
}
else
_hashCode = oldInfo._hashCode;
}
}
}
public String toString()
{
return "["+_name+","+_hashCode+","+_inlineValues+"]";
}
public int hashCode()
{
return _hashCode;
}
public boolean equals(Object o)
{
if (o==null || !(o instanceof FieldInfo))
return false;
FieldInfo fi = (FieldInfo)o;
return
fi==this ||
fi._hashCode==_hashCode ||
fi._name.equals(_name);
}
}
/* ------------------------------------------------------------ */
private static final StringMap __info = new StringMap(true);
private static final StringMap __values = new StringMap(true);
private static final int __maxCacheSize=128;
/* ------------------------------------------------------------ */
static
{
// Initialize FieldInfo's with special values.
// In order of most frequently used.
new FieldInfo(__Host,false);
new FieldInfo(__KeepAlive,false);
new FieldInfo(__Connection,false);
new FieldInfo(__Cookie,false);
new FieldInfo(__Accept,false);
new FieldInfo(__AcceptLanguage,false);
new FieldInfo(__AcceptEncoding,false);
new FieldInfo(__AcceptCharset,false);
new FieldInfo(__CacheControl,false);
new FieldInfo(__SetCookie,false);
new FieldInfo(__SetCookie2,false);
new FieldInfo(__Date,false);
new FieldInfo(__TransferEncoding,true);
new FieldInfo(__ContentEncoding,true);
new FieldInfo(__ContentLength,false);
new FieldInfo(__Expires,false);
new FieldInfo(__Expect,false);
new FieldInfo(__Referer,false);
new FieldInfo(__TE,false);
new FieldInfo(__UserAgent,false);
new FieldInfo(__IfModifiedSince,false);
new FieldInfo(__IfRange,false);
new FieldInfo(__IfUnmodifiedSince,false);
new FieldInfo(__Location,false);
new FieldInfo(__Server,false);
new FieldInfo(__ServletEngine,false);
new FieldInfo(__AcceptRanges,false);
new FieldInfo(__Range,false);
new FieldInfo(__RequestRange,false);
new FieldInfo(__SoapAction,false);
new FieldInfo(__ContentLocation,false);
new FieldInfo(__ContentMD5,false);
new FieldInfo(__ContentRange,false);
new FieldInfo(__ContentType,false);
new FieldInfo(__LastModified,false);
new FieldInfo(__Authorization,false);
new FieldInfo(__From,false);
new FieldInfo(__MaxForwards,false);
new FieldInfo(__ProxyAuthenticate,false);
new FieldInfo(__Age,false);
new FieldInfo(__ETag,false);
new FieldInfo(__RetryAfter,false);
}
/* ------------------------------------------------------------ */
private static FieldInfo getFieldInfo(String name)
{
FieldInfo info = (FieldInfo)__info.get(name);
if (info==null)
info = new FieldInfo(name,false);
return info;
}
/* ------------------------------------------------------------ */
private static FieldInfo getFieldInfo(char[] name,int offset,int length)
{
Map.Entry entry = __info.getEntry(name,offset,length);
if (entry==null)
return new FieldInfo(new String(name,offset,length),false);
return (FieldInfo) entry.getValue();
}
/* ------------------------------------------------------------ */
/** Fields Values.
*/
public final static String __Chunked = "chunked";
public final static String __Close = "close";
public final static String __TextHtml = "text/html";
public final static String __MessageHttp = "message/http";
public final static String __WwwFormUrlEncode =
"application/x-www-form-urlencoded";
public static final String __ExpectContinue="100-continue";
static
{
__values.put(__KeepAlive,__KeepAlive);
__values.put(__Chunked,__Chunked);
__values.put(__Close,__Close);
__values.put(__TextHtml,__TextHtml);
__values.put(__MessageHttp,__MessageHttp);
__values.put(__WwwFormUrlEncode,__WwwFormUrlEncode);
__values.put(__ExpectContinue,__ExpectContinue);
__values.put("max-age=0","max-age=0");
__values.put("no-cache","no-cache");
__values.put("300","300");
__values.put("ISO-8859-1, utf-8;q=0.66, *;q=0.66","ISO-8859-1, utf-8;q=0.66, *;q=0.66");
}
/* ------------------------------------------------------------ */
public final static String __separators = ", \t";
/* ------------------------------------------------------------ */
public final static char[] __CRLF = {'\015','\012'};
public final static char[] __COLON = {':',' '};
/* ------------------------------------------------------------ */
private static String[] DAYS= { "Sat","Sun","Mon","Tue","Wed","Thu","Fri","Sat" };
private static String[] MONTHS= { "Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec","Jan" };
/* ------------------------------------------------------------ */
/** Format HTTP date
* "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or
* "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
*/
public static String formatDate(long date, boolean cookie)
{
StringBuffer buf = new StringBuffer(32);
HttpCal gc = new HttpCal();
gc.setTimeInMillis(date);
formatDate(buf,gc,cookie);
return buf.toString();
}
/* ------------------------------------------------------------ */
/** Format HTTP date
* "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or
* "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
*/
public static String formatDate(Calendar calendar, boolean cookie)
{
StringBuffer buf = new StringBuffer(32);
formatDate(buf,calendar,cookie);
return buf.toString();
}
/* ------------------------------------------------------------ */
/** Format HTTP date
* "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or
* "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
*/
public static String formatDate(StringBuffer buf, long date, boolean cookie)
{
HttpCal gc = new HttpCal();
gc.setTimeInMillis(date);
formatDate(buf,gc,cookie);
return buf.toString();
}
/* ------------------------------------------------------------ */
/** Format HTTP date
* "EEE, dd MMM yyyy HH:mm:ss 'GMT'" or
* "EEE, dd-MMM-yy HH:mm:ss 'GMT'"for cookies
*/
public static void formatDate(StringBuffer buf,Calendar calendar, boolean cookie)
{
// "EEE, dd MMM yyyy HH:mm:ss 'GMT'"
// "EEE, dd-MMM-yy HH:mm:ss 'GMT'", cookie
int day_of_week = calendar.get(Calendar.DAY_OF_WEEK);
int day_of_month = calendar.get(Calendar.DAY_OF_MONTH);
int month = calendar.get(Calendar.MONTH);
int year = calendar.get(Calendar.YEAR);
int century = year/100;
year=year%100;
long tm = (calendar instanceof HttpCal)?(((HttpCal)calendar).getTimeInMillis()):calendar.getTime().getTime();
int epoch=(int)((tm/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);
if (cookie)
{
buf.append('-');
buf.append(MONTHS[month]);
buf.append('-');
StringUtil.append2digits(buf,year);
}
else
{
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");
}
/* -------------------------------------------------------------- */
private static TimeZone __GMT = TimeZone.getTimeZone("GMT");
public final static DateCache __dateCache =
new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'",
Locale.US);
/* ------------------------------------------------------------ */
private final static String __dateReceiveFmt[] =
{
"EEE, dd MMM yyyy HH:mm:ss zzz",
"EEE, dd-MMM-yy HH:mm:ss zzz",
"EEE MMM dd HH:mm:ss yyyy",
"EEE, dd MMM yyyy HH:mm:ss zzz",
"EEE, dd-MMM-yy HH:mm:ss zzz",
"dd MMM yyyy HH:mm:ss",
"dd-MMM-yy HH:mm:ss",
};
public static SimpleDateFormat __dateReceiveSource[];
public static final ThreadLocal __dateReceiveCache=new ThreadLocal();
static
{
__GMT.setID("GMT");
__dateCache.setTimeZone(__GMT);
__dateReceiveSource = new SimpleDateFormat[__dateReceiveFmt.length];
for(int i=0;i<__dateReceiveSource.length;i++)
{
__dateReceiveSource[i] =
new SimpleDateFormat(__dateReceiveFmt[i],Locale.US);
__dateReceiveSource[i].setTimeZone(__GMT);
}
}
public final static String __01Jan1970=HttpFields.formatDate(0,false);
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private static final class Field
{
FieldInfo _info;
String _value;
Field _next;
Field _prev;
int _version;
/* ------------------------------------------------------------ */
Field(FieldInfo info, String value, int version)
{
_info=info;
_value=value;
_next=null;
_prev=null;
_version=version;
}
/* ------------------------------------------------------------ */
Field(FieldInfo info, char[] buf, int offset, int length, int version)
{
Map.Entry valueEntry=__values.getEntry(buf,offset,length);
String value=null;
if (valueEntry!=null)
value=(String)valueEntry.getKey();
else
value=new String(buf,offset,length);
_info=info;
_value=value;
_next=null;
_prev=null;
_version=version;
}
/* ------------------------------------------------------------ */
public boolean equals(Object o)
{
return (o instanceof Field) &&
o==this &&
_version==((Field)o)._version;
}
/* ------------------------------------------------------------ */
public int hashCode()
{
return _info.hashCode()*_version;
}
/* ------------------------------------------------------------ */
void clear()
{
_version=-1;
}
/* ------------------------------------------------------------ */
void destroy()
{
_info=null;
_value=null;
_next=null;
_prev=null;
_version=-1;
}
/* ------------------------------------------------------------ */
void reset(String value,int version)
{
_value=value;
_version=version;
}
/* ------------------------------------------------------------ */
/** Reassign a value to this field.
* Checks if the value is the same as that in the char array, if so
* then just reuse existing value.
*/
void reset(char[] buf, int offset, int length, int version)
{
_version=version;
if (!StringUtil.equals(_value,buf,offset,length))
{
Map.Entry valueEntry=__values.getEntry(buf,offset,length);
String value=null;
if (valueEntry!=null)
value=(String)valueEntry.getKey();
else
value=new String(buf,offset,length);
_value=value;
}
}
/* ------------------------------------------------------------ */
void write(Writer writer, int version)
throws IOException
{
if (_info==null || _version!=version)
return;
if (_info._inlineValues)
{
if (_prev!=null)
return;
writer.write(_info._name);
writer.write(__COLON);
Field f=this;
while (true)
{
writer.write(QuotedStringTokenizer.quote(f._value,", \t"));
f=f._next;
if (f==null)
break;
writer.write(",");
}
writer.write(__CRLF);
}
else
{
writer.write(_info._name);
writer.write(__COLON);
writer.write(_value);
writer.write(__CRLF);
}
}
/* ------------------------------------------------------------ */
String getDisplayName()
{
return _info._name;
}
/* ------------------------------------------------------------ */
public String toString()
{
return ("["+
(_prev==null?"":"<-")+
getDisplayName()+"="+_value+
(_next==null?"":"->")+
"]");
}
}
/* ------------------------------------------------------------ */
private static Float __one = new Float("1.0");
private static Float __zero = new Float("0.0");
private static 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);
}
/* -------------------------------------------------------------- */
private ArrayList _fields=new ArrayList(15);
private int[] _index=new int[__maxCacheSize];
private int _version;
private SimpleDateFormat _dateReceive[];
private StringBuffer _dateBuffer;
private HttpCal _calendar;
/* ------------------------------------------------------------ */
/** Constructor.
*/
public HttpFields()
{
Arrays.fill(_index,-1);
}
/* ------------------------------------------------------------ */
public int size()
{
return _fields.size();
}
/* -------------------------------------------------------------- */
/** Get enumeration of header _names.
* Returns an enumeration of strings representing the header _names
* for this request.
*/
public Enumeration getFieldNames()
{
return new Enumeration()
{
int i=0;
Field field=null;
public boolean hasMoreElements()
{
if (field!=null)
return true;
while (i<_fields.size())
{
Field f=(Field)_fields.get(i++);
if (f!=null && f._version==_version && f._prev==null)
{
field=f;
return true;
}
}
return false;
}
public Object nextElement()
throws NoSuchElementException
{
if (field!=null || hasMoreElements())
{
String n=field._info._name;
field=null;
return n;
}
throw new NoSuchElementException();
}
};
}
/* ------------------------------------------------------------ */
Field getField(String name)
{
FieldInfo info=getFieldInfo(name);
return getField(info,true);
}
/* ------------------------------------------------------------ */
Field getField(FieldInfo info, boolean getValid)
{
int hi=info.hashCode();
if (hi<_index.length)
{
if (_index[hi]>=0)
{
Field field=(Field)(_fields.get(_index[hi]));
return (field!=null && (!getValid||field._version==_version))?field:null;
}
}
else
{
for (int i=0;i<_fields.size();i++)
{
Field field=(Field)_fields.get(i);
if (info.equals(field._info) && (!getValid||field._version==_version))
return field;
}
}
return null;
}
/* ------------------------------------------------------------ */
public boolean containsKey(String name)
{
FieldInfo info=getFieldInfo(name);
return getField(info,true)!=null;
}
/* -------------------------------------------------------------- */
/**
* @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 get(String name)
{
FieldInfo info=getFieldInfo(name);
Field field=getField(info,true);
if (field!=null)
return field._value;
return null;
}
/* -------------------------------------------------------------- */
/** Get multi headers
* @return Enumeration of the values, or null if no such header.
* @param name the case-insensitive field name
*/
public Enumeration getValues(String name)
{
FieldInfo info=getFieldInfo(name);
final Field field=getField(info,true);
if (field!=null)
{
return new Enumeration()
{
Field f=field;
public boolean hasMoreElements()
{
while (f!=null && f._version!=_version)
f=f._next;
return f!=null;
}
public Object nextElement()
throws NoSuchElementException
{
if (f==null)
throw new NoSuchElementException();
Field n=f;
do f=f._next; while (f!=null && f._version!=_version);
return n._value;
}
};
}
return null;
}
/* -------------------------------------------------------------- */
/** 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 getValues(String name,final String separators)
{
final Enumeration e = getValues(name);
if (e==null)
return null;
return new Enumeration()
{
QuotedStringTokenizer tok=null;
public boolean hasMoreElements()
{
if (tok!=null && tok.hasMoreElements())
return true;
while (e.hasMoreElements())
{
String value=(String)e.nextElement();
tok=new QuotedStringTokenizer(value,separators,false,false);
if (tok.hasMoreElements())
return true;
}
tok=null;
return false;
}
public Object 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 String put(String name,String value)
{
if (value==null)
return remove(name);
FieldInfo info=getFieldInfo(name);
Field field=getField(info,false);
// Look for value to replace.
if (field!=null)
{
String old=(field._version==_version)?field._value:null;
field.reset(value,_version);
field=field._next;
while(field!=null)
{
field.clear();
field=field._next;
}
return old;
}
else
{
// new value;
field=new Field(info,value,_version);
int hi=info.hashCode();
if (hi<_index.length)
_index[hi]=_fields.size();
_fields.add(field);
return null;
}
}
/* -------------------------------------------------------------- */
/** 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;
}
Object v=list.get(0);
if (v!=null)
put(name,v.toString());
else
remove(name);
if (list.size()>1)
{
java.util.Iterator iter = list.iterator();
iter.next();
while(iter.hasNext())
{
v=iter.next();
if (v!=null)
add(name,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)
throw new IllegalArgumentException("null value");
FieldInfo info=getFieldInfo(name);
Field field=getField(info,false);
Field last=null;
if (field!=null)
{
while(field!=null && field._version==_version)
{
last=field;
field=field._next;
}
}
if (field!=null)
field.reset(value,_version);
else
{
// create the field
field=new Field(info,value,_version);
// look for chain to add too
if(last!=null)
{
field._prev=last;
last._next=field;
}
else if (info.hashCode()<_index.length)
_index[info.hashCode()]=_fields.size();
_fields.add(field);
}
}
/* ------------------------------------------------------------ */
/** Remove a field.
* @param name
*/
public String remove(String name)
{
String old=null;
FieldInfo info=getFieldInfo(name);
Field field=getField(info,true);
if (field!=null)
{
old=field._value;
while(field!=null)
{
field.clear();
field=field._next;
}
}
return old;
}
/* -------------------------------------------------------------- */
/** Get a header as an integer 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 integer found
*/
public int getIntField(String name)
throws NumberFormatException
{
String val = valueParameters(get(name),null);
if (val!=null)
return Integer.parseInt(val);
return -1;
}
/* -------------------------------------------------------------- */
/** 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)
{
String val = valueParameters(get(name),null);
if (val==null)
return -1;
if (_dateReceive==null)
{
_dateReceive=(SimpleDateFormat[])__dateReceiveCache.get();
if (_dateReceive==null)
{
_dateReceive=(SimpleDateFormat[]) new SimpleDateFormat[__dateReceiveSource.length];
__dateReceiveCache.set(_dateReceive);
}
}
for (int i=0;i<_dateReceive.length;i++)
{
// clone formatter for thread safety
if (_dateReceive[i]==null)
_dateReceive[i]=(SimpleDateFormat)__dateReceiveSource[i].clone();
try{
Date date=(Date)_dateReceive[i].parseObject(val);
return date.getTime();
}
catch(java.lang.Exception e)
{
LogSupport.ignore(log,e);
}
}
if (val.endsWith(" GMT"))
{
val=val.substring(0,val.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)
{
LogSupport.ignore(log,e);
}
}
}
throw new IllegalArgumentException(val);
}
/* -------------------------------------------------------------- */
/**
* Sets the value of an integer field.
* @param name the field name
* @param value the field integer value
*/
public void putIntField(String name, int value)
{
put(name, Integer.toString(value));
}
/* -------------------------------------------------------------- */
/**
* Sets the value of a date field.
* @param name the field name
* @param date the field date value
*/
public void putDateField(String name, Date date)
{
putDateField(name,date.getTime());
}
/* -------------------------------------------------------------- */
/**
* Adds the value of a date field.
* @param name the field name
* @param date the field date value
*/
public void addDateField(String name, Date date)
{
addDateField(name,date.getTime());
}
/* -------------------------------------------------------------- */
/**
* Adds the value of a date field.
* @param name the field name
* @param date the field date value
*/
public void addDateField(String name, long date)
{
if (_dateBuffer==null)
{
_dateBuffer=new StringBuffer(32);
_calendar=new HttpCal();
}
_dateBuffer.setLength(0);
_calendar.setTimeInMillis(date);
formatDate(_dateBuffer, _calendar, false);
add(name, _dateBuffer.toString());
}
/* -------------------------------------------------------------- */
/**
* 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)
{
if (_dateBuffer==null)
{
_dateBuffer=new StringBuffer(32);
_calendar=new HttpCal();
}
_dateBuffer.setLength(0);
_calendar.setTimeInMillis(date);
formatDate(_dateBuffer, _calendar, false);
put(name, _dateBuffer.toString());
}
/* -------------------------------------------------------------- */
/** Read HttpHeaders from inputStream.
*/
public void read(LineInput in)
throws IOException
{
Field last=null;
char[] buf=null;
int size=0;
org.openqa.jetty.util.LineInput.LineBuffer line_buffer;
synchronized(in)
{
line:
while ((line_buffer=in.readLineBuffer())!=null)
{
// check space in the lowercase buffer
buf=line_buffer.buffer;
size=line_buffer.size;
if (size==0)
break;
// setup loop state machine
int i1=-1;
int i2=-1;
int name_l=0;
int i=0;
char c=buf[0];
// Check for continuity line
if (c!=' ' && c!='\t')
{
i2=0;
// reading name upto :
for (i=1;i<size;i++)
{
c=buf[i];
if (c==':')
{
name_l=i2+1;
break;
}
if (c!=' '&&c!='\t')
i2=i;
}
}
// skip whitespace after : or start of continuity line
for (i++;i<size;i++)
{
c=buf[i];
if (c!=' ' && c!='\t')
{
i1=i;
i2=i-1;
break;
}
}
// Reverse Parse the "name : value" to last char of value
for (i=size;i-->i1 && i>=0;)
{
c=buf[i];
if (c!=' ' && c!='\t')
{
i2=i;
break;
}
}
// If no name, it is a continuation line
if (name_l<=0)
{
if (i1>0 && last!=null)
last._value=last._value+' '+new String(buf,i1,i2-i1+1);
continue;
}
// create the field.
FieldInfo info = getFieldInfo(buf,0,name_l);
Field field=getField(info,false);
last=null;
if (field!=null)
{
while(field!=null && field._version==_version)
{
last=field;
field=field._next;
}
}
if (field!=null)
{
if (i1>=0)
field.reset(buf,i1,i2-i1+1,_version);
else
field.reset("",_version);
}
else
{
// create the field
if (i1>=0)
field=new Field(info,buf,i1,i2-i1+1,_version);
else
field=new Field(info,"",_version);
// look for chain to add too
if(last!=null)
{
field._prev=last;
last._next=field;
}
else if (info.hashCode()<_index.length)
_index[info.hashCode()]=_fields.size();
_fields.add(field);
}
last=field;
}
}
}
/* -------------------------------------------------------------- */
/* Write Extra HTTP headers.
*/
public void write(Writer writer)
throws IOException
{
synchronized(writer)
{
for (int i=0;i<_fields.size();i++)
{
Field field=(Field)_fields.get(i);
if (field!=null)
field.write(writer,_version);
}
writer.write(__CRLF);
}
}
/* -------------------------------------------------------------- */
public String toString()
{
try
{
StringWriter writer = new StringWriter();
write(writer);
return writer.toString();
}
catch(Exception e)
{}
return null;
}
/* ------------------------------------------------------------ */
/** Clear the header.
*/
public void clear()
{
_version++;
if (_version>1000)
{
_version=0;
for (int i=_fields.size();i-->0;)
{
Field field=(Field)_fields.get(i);
if (field!=null)
field.clear();
}
}
}
/* ------------------------------------------------------------ */
/** Destroy the header.
* Help the garbage collector by null everything that we can.
*/
public void destroy()
{
for (int i=_fields.size();i-->0;)
{
Field field=(Field)_fields.get(i);
if (field!=null)
field.destroy();
}
_fields=null;
_index=null;
_dateBuffer=null;
_calendar=null;
_dateReceive=null;
}
/* ------------------------------------------------------------ */
/** 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 paramters. 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 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();
}
/* ------------------------------------------------------------ */
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 enm Enumeration of values with quality parameters
* @return values in quality order.
*/
public static List qualityList(Enumeration enm)
{
if(enm==null || !enm.hasMoreElements())
return Collections.EMPTY_LIST;
Object list=null;
Object qual=null;
// Assume list will be well ordered and just add nonzero
while(enm.hasMoreElements())
{
String v=enm.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;
}
/* ------------------------------------------------------------ */
/** Format a set cookie value
* @param cookie The cookie.
*/
public void addSetCookie(Cookie cookie)
{
String name=cookie.getName();
String value=cookie.getValue();
int version=cookie.getVersion();
// Check arguments
if (name==null || name.length()==0)
throw new IllegalArgumentException("Bad cookie name");
// Format value and params
StringBuffer buf = new StringBuffer(128);
String name_value_params=null;
synchronized(buf)
{
buf.append(name);
buf.append('=');
if (value!=null && value.length()>0)
{
if (version==0)
URI.encodeString(buf,value,"\";, '");
else
buf.append(QuotedStringTokenizer.quote(value,"\";, '"));
}
if (version>0)
{
buf.append(";Version=");
buf.append(version);
String comment=cookie.getComment();
if (comment!=null && comment.length()>0)
{
buf.append(";Comment=");
QuotedStringTokenizer.quote(buf,comment);
}
}
String path=cookie.getPath();
if (path!=null && path.length()>0)
{
buf.append(";Path=");
buf.append(path);
}
String domain=cookie.getDomain();
if (domain!=null && domain.length()>0)
{
buf.append(";Domain=");
buf.append(domain.toLowerCase());// lowercase for IE
}
long maxAge = cookie.getMaxAge();
if (maxAge>=0)
{
if (version==0)
{
buf.append(";Expires=");
if (maxAge==0)
buf.append(__01Jan1970);
else
formatDate(buf,System.currentTimeMillis()+1000L*maxAge,true);
}
else
{
buf.append (";Max-Age=");
buf.append (cookie.getMaxAge());
}
}
else if (version>0)
{
buf.append (";Discard");
}
if (cookie.getSecure())
{
buf.append(";Secure");
}
if (cookie instanceof HttpOnlyCookie)
buf.append(";HttpOnly");
name_value_params = buf.toString();
}
put(__Expires,__01Jan1970);
add(__SetCookie,name_value_params);
}
/* ------------------------------------------------------------ */
/** 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 enm = fields.getFieldNames();
while( enm.hasMoreElements() )
{
String name = (String)enm.nextElement();
Enumeration values = fields.getValues(name);
while(values.hasMoreElements())
add(name,(String)values.nextElement());
}
}
/* ------------------------------------------------------------ */
/**
* return an iterator for field name:value pairs
* @return an HttpFields.Iterator
*/
public Iterator iterator() {return new EntryIterator();}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
public class Entry
{
protected int _i;
Entry(int i) {_i=i;}
public String getKey() {return ((Field)_fields.get(_i)).getDisplayName();}
public String getValue() {return ((Field)_fields.get(_i))._value;}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class EntryIterator implements Iterator
{
protected int _i=0;
public boolean hasNext() {return (_i<_fields.size());}
public Object next() throws NoSuchElementException {return new Entry(_i++);}
public void remove() { throw new UnsupportedOperationException();}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/* handle 1.3 protected methods */
private static class HttpCal extends GregorianCalendar
{
HttpCal()
{
super(__GMT);
}
/* ------------------------------------------------------------------------------- */
/**
* @see java.util.Calendar#setTimeInMillis(long)
*/
public void setTimeInMillis(long arg0)
{
super.setTimeInMillis(arg0);
}
/* ------------------------------------------------------------------------------- */
/**
* @see java.util.Calendar#getTimeInMillis()
*/
public long getTimeInMillis()
{
return super.getTimeInMillis();
}
}
}