diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java new file mode 100644 index 00000000000..b1cac669469 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/DateGenerator.java @@ -0,0 +1,160 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.eclipse.jetty.util.StringUtil; + +public class DateGenerator +{ + private static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); + static + { + __GMT.setID("GMT"); + } + + static final String[] DAYS = + { "Sat", "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static final String[] MONTHS = + { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", "Jan"}; + + + private static final ThreadLocal __dateGenerator =new ThreadLocal() + { + @Override + protected DateGenerator initialValue() + { + return new DateGenerator(); + } + }; + + + public final static String __01Jan1970=DateGenerator.formatDate(0); + + /** + * Format HTTP date "EEE, dd MMM yyyy HH:mm:ss 'GMT'" + */ + public static String formatDate(long date) + { + return __dateGenerator.get().doFormatDate(date); + } + + /** + * Format "EEE, dd-MMM-yyyy HH:mm:ss 'GMT'" for cookies + */ + public static void formatCookieDate(StringBuilder buf, long date) + { + __dateGenerator.get().doFormatCookieDate(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 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 doFormatDate(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 doFormatCookieDate(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"); + } +} \ No newline at end of file diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java new file mode 100644 index 00000000000..103c562bb10 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java @@ -0,0 +1,105 @@ +// +// ======================================================================== +// Copyright (c) 1995-2013 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.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +class DateParser +{ + private static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); + static + { + __GMT.setID("GMT"); + } + + 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", + }; + + public static long parseDate(String date) + { + return __dateParser.get().parse(date); + } + + private static final ThreadLocal __dateParser =new ThreadLocal() + { + @Override + protected DateParser initialValue() + { + return new DateParser(); + } + }; + + final SimpleDateFormat _dateReceive[]= new SimpleDateFormat[__dateReceiveFmt.length]; + + private 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 (SimpleDateFormat element : _dateReceive) + { + try + { + Date date = (Date) element.parseObject(val); + return date.getTime(); + } + catch (java.lang.Exception e) + { + // LOG.ignore(e); + } + } + } + return -1; + } +} \ No newline at end of file diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java index 9e261756522..182848f89d9 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java @@ -18,92 +18,13 @@ package org.eclipse.jetty.http; -import java.nio.ByteBuffer; -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Pattern; - -import org.eclipse.jetty.util.ArrayTrie; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.StringUtil; -import org.eclipse.jetty.util.Trie; - /* ------------------------------------------------------------ */ /** A HTTP Field */ public class HttpField { - /** - * Cache of common {@link HttpField}s including:
    - *
  • Common static combinations such as:
      - *
    • Connection: close - *
    • Accept-Encoding: gzip - *
    • Content-Length: 0 - *
    - *
  • Combinations of Content-Type header for common mime types by common charsets - *
  • Most common headers with null values so that a lookup will at least - * determine the header name even if the name:value combination is not cached - *
- */ - public final static Trie CACHE = new ArrayTrie<>(2048); - public final static Trie CONTENT_TYPE = new ArrayTrie<>(512); - static - { - CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE)); - CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE)); - CACHE.put(new CachedHttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE)); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"*/*")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5")); - CACHE.put(new CachedHttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); - CACHE.put(new CachedHttpField(HttpHeader.PRAGMA,"no-cache")); - CACHE.put(new CachedHttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate")); - CACHE.put(new CachedHttpField(HttpHeader.CACHE_CONTROL,"no-cache")); - CACHE.put(new CachedHttpField(HttpHeader.CONTENT_LENGTH,"0")); - CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"gzip")); - CACHE.put(new CachedHttpField(HttpHeader.CONTENT_ENCODING,"deflate")); - CACHE.put(new CachedHttpField(HttpHeader.TRANSFER_ENCODING,"chunked")); - CACHE.put(new CachedHttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT")); - - // Content types - for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/x-www-form-urlencoded"}) - { - HttpField field=new CachedHttpField(HttpHeader.CONTENT_TYPE,type); - CACHE.put(field); - CONTENT_TYPE.put(type,field); - - for (String charset : new String[]{"UTF-8","ISO-8859-1"}) - { - String type_charset=type+"; charset="+charset; - field=new CachedHttpField(HttpHeader.CONTENT_TYPE,type_charset); - CACHE.put(field); - CACHE.put(new CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset)); - CONTENT_TYPE.put(type_charset,field); - CONTENT_TYPE.put(type+";charset="+charset,field); - } - } - - // Add headers with null values so HttpParser can avoid looking up name again for unknown values - for (HttpHeader h:HttpHeader.values()) - if (!CACHE.put(new HttpField(h,(String)null))) - throw new IllegalStateException("CACHE FULL"); - // Add some more common headers - CACHE.put(new HttpField(HttpHeader.REFERER,(String)null)); - CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null)); - CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null)); - CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null)); - CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null)); - } - - private final static Pattern __splitter = Pattern.compile("\\s*,\\s*"); - private final static byte[] __colon_space = new byte[] {':',' '}; private final HttpHeader _header; private final String _name; @@ -146,89 +67,7 @@ public class HttpField { return _value; } - - public boolean contains(String value) - { - if (_value==null) - return false; - - if (value.equalsIgnoreCase(_value)) - return true; - - String[] split = __splitter.split(_value); - for (int i = 0; split!=null && i < split.length; i++) - { - if (value.equalsIgnoreCase(split[i])) - return true; - } - - return false; - } - - - public int getIntValue() - { - return StringUtil.toInt(_value); - } - - public long getLongValue() - { - return StringUtil.toLong(_value); - } - private static byte[] toSanitisedName(String s) - { - byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); - for (int i=bytes.length;i-->0;) - { - switch(bytes[i]) - { - case '\r': - case '\n': - case ':' : - bytes[i]=(byte)'?'; - } - } - return bytes; - } - - private static byte[] toSanitisedValue(String s) - { - byte[] bytes = s.getBytes(StringUtil.__ISO_8859_1_CHARSET); - for (int i=bytes.length;i-->0;) - { - switch(bytes[i]) - { - case '\r': - case '\n': - bytes[i]=(byte)'?'; - } - } - return bytes; - } - - public void putTo(ByteBuffer bufferInFillMode) - { - if (_header!=null) - { - bufferInFillMode.put(_header.getBytesColonSpace()); - bufferInFillMode.put(toSanitisedValue(_value)); - } - else - { - bufferInFillMode.put(toSanitisedName(_name)); - bufferInFillMode.put(__colon_space); - bufferInFillMode.put(toSanitisedValue(_value)); - } - - BufferUtil.putCRLF(bufferInFillMode); - } - - public void putValueTo(ByteBuffer buffer) - { - buffer.put(toSanitisedValue(_value)); - } - @Override public String toString() { @@ -250,31 +89,4 @@ public class HttpField } - /* ------------------------------------------------------------ */ - /** A HTTP Field optimised to be reused. - */ - public static class CachedHttpField extends HttpField - { - final byte[] _bytes; - public CachedHttpField(HttpHeader header, String value) - { - super(header,value); - _bytes=new byte[header.asString().length()+2+value.length()+2]; - System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,header.asString().length()+2); - System.arraycopy(toSanitisedValue(value),0,_bytes,header.asString().length()+2,value.length()); - _bytes[_bytes.length-2]='\r'; - _bytes[_bytes.length-1]='\n'; - } - - CachedHttpField(HttpHeader header, HttpHeaderValue value) - { - this(header,value.asString()); - } - - @Override - public void putTo(ByteBuffer bufferInFillMode) - { - bufferInFillMode.put(_bytes); - } - } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index e990ea58987..2d0da1968c9 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -18,29 +18,21 @@ package org.eclipse.jetty.http; -import java.nio.ByteBuffer; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Calendar; import java.util.Collection; import java.util.Collections; -import java.util.Date; import java.util.Enumeration; -import java.util.GregorianCalendar; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.StringTokenizer; -import java.util.TimeZone; +import java.util.regex.Pattern; import org.eclipse.jetty.util.ArrayTernaryTrie; -import org.eclipse.jetty.util.BufferUtil; -import org.eclipse.jetty.util.DateCache; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; @@ -48,13 +40,11 @@ import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted; - /** * HTTP Fields. A collection of HTTP header and or Trailer fields. * - *

This class is not synchronised as it is expected that modifications will only be performed by a + *

This class is not synchronized as it is expected that modifications will only be performed by a * single thread. * *

The cookie handling provided by this class is guided by the Servlet specification and RFC6265. @@ -63,222 +53,9 @@ import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted; public class HttpFields implements Iterable { private static final Logger LOG = Log.getLogger(HttpFields.class); - public static final TimeZone __GMT = TimeZone.getTimeZone("GMT"); - public static final DateCache __dateCache = new DateCache("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US); - - public static final String __COOKIE_DELIM="\",;\\ \t"; - - static - { - __GMT.setID("GMT"); - __dateCache.setTimeZone(__GMT); - } - + private final static Pattern __splitter = Pattern.compile("\\s*,\\s*"); 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"}; - - public 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 =new ThreadLocal() - { - @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 (SimpleDateFormat element : _dateReceive) - { - try - { - Date date = (Date) element.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 =new ThreadLocal() - { - @Override - protected DateParser initialValue() - { - return new DateParser(); - } - }; - - public final static String __01Jan1970=formatDate(0); - public final static ByteBuffer __01Jan1970_BUFFER=BufferUtil.toBuffer(__01Jan1970); - public final static String __01Jan1970_COOKIE = formatCookieDate(0).trim(); private final ArrayList _fields = new ArrayList<>(20); /** @@ -355,12 +132,14 @@ public class HttpFields implements Iterable return null; } + + public boolean contains(HttpHeader header, String value) { for (int i=0;i<_fields.size();i++) { HttpField f=_fields.get(i); - if (f.getHeader()==header && f.contains(value)) + if (f.getHeader()==header && contains(f,value)) return true; } return false; @@ -371,12 +150,32 @@ public class HttpFields implements Iterable for (int i=0;i<_fields.size();i++) { HttpField f=_fields.get(i); - if (f.getName().equalsIgnoreCase(name) && f.contains(value)) + if (f.getName().equalsIgnoreCase(name) && contains(f,value)) return true; } return false; } + + private boolean contains(HttpField field,String value) + { + String v = field.getValue(); + if (v==null) + return false; + + if (value.equalsIgnoreCase(v)) + return true; + + String[] split = __splitter.split(v); + for (int i = 0; split!=null && i < split.length; i++) + { + if (value.equals(split[i])) + return true; + } + + return false; + } + public boolean containsKey(String name) { for (int i=0;i<_fields.size();i++) @@ -683,7 +482,7 @@ public class HttpFields implements Iterable public long getLongField(String name) throws NumberFormatException { HttpField field = getField(name); - return field==null?-1L:field.getLongValue(); + return field==null?-1L:StringUtil.toLong(field.getValue()); } /** @@ -702,7 +501,7 @@ public class HttpFields implements Iterable if (val == null) return -1; - final long date = __dateParser.get().parse(val); + final long date = DateParser.parseDate(val); if (date==-1) throw new IllegalArgumentException("Cannot convert date: " + val); return date; @@ -742,7 +541,7 @@ public class HttpFields implements Iterable */ public void putDateField(HttpHeader name, long date) { - String d=formatDate(date); + String d=DateGenerator.formatDate(date); put(name, d); } @@ -754,7 +553,7 @@ public class HttpFields implements Iterable */ public void putDateField(String name, long date) { - String d=formatDate(date); + String d=DateGenerator.formatDate(date); put(name, d); } @@ -766,170 +565,10 @@ public class HttpFields implements Iterable */ public void addDateField(String name, long date) { - String d=formatDate(date); + String d=DateGenerator.formatDate(date); add(name,d); } - /** - * 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) - { - // Check arguments - if (name == null || name.length() == 0) - throw new IllegalArgumentException("Bad cookie name"); - - // Format value and params - StringBuilder buf = new StringBuilder(128); - - // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting - boolean quote_name=isQuoteNeededForCookie(name); - quoteOnlyOrAppend(buf,name,quote_name); - - buf.append('='); - - // Remember name= part to look for other matching set-cookie - String name_equals=buf.toString(); - - // Append the value - boolean quote_value=isQuoteNeededForCookie(value); - quoteOnlyOrAppend(buf,value,quote_value); - - // Look for domain and path fields and check if they need to be quoted - boolean has_domain = domain!=null && domain.length()>0; - boolean quote_domain = has_domain && isQuoteNeededForCookie(domain); - boolean has_path = path!=null && path.length()>0; - boolean quote_path = has_path && isQuoteNeededForCookie(path); - - // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted - if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain))) - version=1; - - // Append version - if (version==1) - buf.append (";Version=1"); - else if (version>1) - buf.append (";Version=").append(version); - - // Append path - if (has_path) - { - buf.append(";Path="); - quoteOnlyOrAppend(buf,path,quote_path); - } - - // Append domain - if (has_domain) - { - buf.append(";Domain="); - quoteOnlyOrAppend(buf,domain,quote_domain); - } - - // Handle max-age and/or expires - if (maxAge >= 0) - { - // Always use expires - // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies - buf.append(";Expires="); - if (maxAge == 0) - buf.append(__01Jan1970_COOKIE); - else - formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); - - // for v1 cookies, also send max-age - if (version>=1) - { - buf.append(";Max-Age="); - buf.append(maxAge); - } - } - - // add the other fields - if (isSecure) - buf.append(";Secure"); - if (isHttpOnly) - buf.append(";HttpOnly"); - if (comment != null) - { - buf.append(";Comment="); - quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment)); - } - - // remove any existing set-cookie fields of same name - Iterator i=_fields.iterator(); - while (i.hasNext()) - { - HttpField field=i.next(); - if (field.getHeader()==HttpHeader.SET_COOKIE) - { - String val = field.getValue(); - if (val!=null && val.startsWith(name_equals)) - { - //existing cookie has same name, does it also match domain and path? - if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) && - ((!has_path && !val.contains("Path")) || (has_path && val.contains(path)))) - { - i.remove(); - } - } - } - } - - // add the set cookie - add(HttpHeader.SET_COOKIE.toString(), buf.toString()); - - // Expire responses with set-cookie headers so they do not get cached. - put(HttpHeader.EXPIRES.toString(), __01Jan1970); - } - - public void putTo(ByteBuffer bufferInFillMode) - { - for (HttpField field : _fields) - { - if (field != null) - field.putTo(bufferInFillMode); - } - BufferUtil.putCRLF(bufferInFillMode); - } - @Override public String toString() @@ -1148,39 +787,4 @@ public class HttpFields implements Iterable - /* ------------------------------------------------------------ */ - /** Does a cookie value need to be quoted? - * @param s value string - * @return true if quoted; - * @throws IllegalArgumentException If there a control characters in the string - */ - public static boolean isQuoteNeededForCookie(String s) - { - if (s==null || s.length()==0) - return true; - - if (QuotedStringTokenizer.isQuoted(s)) - return false; - - for (int i=0;i=0) - return true; - - if (c<0x20 || c>=0x7f) - throw new IllegalArgumentException("Illegal character in cookie value"); - } - - return false; - } - - - private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote) - { - if (quote) - QuotedStringTokenizer.quoteOnly(buf,s); - else - buf.append(s); - } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java index af09866863a..9cbeedddaf3 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java @@ -37,6 +37,7 @@ public class HttpGenerator { private static final Logger LOG = Log.getLogger(HttpGenerator.class); + private final static byte[] __colon_space = new byte[] {':',' '}; public static final ResponseInfo CONTINUE_100_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,100,null,false); public static final ResponseInfo PROGRESS_102_INFO = new ResponseInfo(HttpVersion.HTTP_1_1,null,-1,102,null,false); public final static ResponseInfo RESPONSE_500_INFO = @@ -495,6 +496,9 @@ public class HttpGenerator case HTTP_1_1: header.put((byte)' '); header.put(request.getHttpVersion().toBytes()); + break; + default: + throw new IllegalStateException(); } header.put(HttpTokens.CRLF); } @@ -585,7 +589,7 @@ public class HttpGenerator // write the field to the header content_type=true; - field.putTo(header); + putTo(field,header); break; } @@ -600,7 +604,7 @@ public class HttpGenerator case CONNECTION: { if (request!=null) - field.putTo(header); + putTo(field,header); // Lookup and/or split connection value field HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())}; @@ -672,12 +676,12 @@ public class HttpGenerator case SERVER: { send=send&~SEND_SERVER; - field.putTo(header); + putTo(field,header); break; } default: - field.putTo(header); + putTo(field,header); } } } @@ -776,7 +780,7 @@ public class HttpGenerator { String c = transfer_encoding.getValue(); if (c.endsWith(HttpHeaderValue.CHUNKED.toString())) - transfer_encoding.putTo(header); + putTo(transfer_encoding,header); else throw new IllegalArgumentException("BAD TE"); } @@ -1007,5 +1011,88 @@ public class HttpGenerator { return String.format("ResponseInfo{%s %s %s,%d,%b}",_httpVersion,_status,_reason,_contentLength,_head); } + } + + private static void putSanitisedName(String s,ByteBuffer buffer) + { + int l=s.length(); + for (int i=0;i0xff || c=='\r' || c=='\n'|| c==':') + buffer.put((byte)'?'); + else + buffer.put((byte)(0xff&c)); + } + } + + private static void putSanitisedValue(String s,ByteBuffer buffer) + { + int l=s.length(); + for (int i=0;i0xff || c=='\r' || c=='\n') + buffer.put((byte)'?'); + else + buffer.put((byte)(0xff&c)); + } + } + + public static void putTo(HttpField field, ByteBuffer bufferInFillMode) + { + if (field instanceof CachedHttpField) + { + ((CachedHttpField)field).putTo(bufferInFillMode); + } + else + { + HttpHeader header=field.getHeader(); + if (header!=null) + { + bufferInFillMode.put(header.getBytesColonSpace()); + putSanitisedValue(field.getValue(),bufferInFillMode); + } + else + { + putSanitisedName(field.getName(),bufferInFillMode); + bufferInFillMode.put(__colon_space); + putSanitisedValue(field.getValue(),bufferInFillMode); + } + + BufferUtil.putCRLF(bufferInFillMode); + } + } + + public static void putTo(HttpFields fields, ByteBuffer bufferInFillMode) + { + for (HttpField field : fields) + { + if (field != null) + putTo(field,bufferInFillMode); + } + BufferUtil.putCRLF(bufferInFillMode); + } + + public static class CachedHttpField extends HttpField + { + private final byte[] _bytes; + public CachedHttpField(HttpHeader header,String value) + { + super(header,value); + int cbl=header.getBytesColonSpace().length; + _bytes=new byte[cbl+value.length()+2]; + System.arraycopy(header.getBytesColonSpace(),0,_bytes,0,cbl); + System.arraycopy(value.getBytes(StringUtil.__ISO_8859_1_CHARSET),0,_bytes,cbl,value.length()); + _bytes[_bytes.length-2]=(byte)'\r'; + _bytes[_bytes.length-1]=(byte)'\n'; + } + + public void putTo(ByteBuffer bufferInFillMode) + { + bufferInFillMode.put(_bytes); + } } } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java index 7cef1dcf538..9e73ee5d69a 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java @@ -22,6 +22,7 @@ import java.nio.ByteBuffer; import org.eclipse.jetty.http.HttpTokens.EndOfContent; import org.eclipse.jetty.util.ArrayTernaryTrie; +import org.eclipse.jetty.util.ArrayTrie; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.Trie; @@ -76,6 +77,21 @@ public class HttpParser public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpParser.STRICT"); public final static int INITIAL_URI_LENGTH=256; + /** + * Cache of common {@link HttpField}s including:

    + *
  • Common static combinations such as:
      + *
    • Connection: close + *
    • Accept-Encoding: gzip + *
    • Content-Length: 0 + *
    + *
  • Combinations of Content-Type header for common mime types by common charsets + *
  • Most common headers with null values so that a lookup will at least + * determine the header name even if the name:value combination is not cached + *
+ */ + public final static Trie CACHE = new ArrayTrie<>(2048); + public final static Trie CONTENT_TYPE = new ArrayTrie<>(512); + // States public enum State { @@ -138,6 +154,59 @@ public class HttpParser private int _length; private final StringBuilder _string=new StringBuilder(); + static + { + CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.CLOSE)); + CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.KEEP_ALIVE)); + CACHE.put(new HttpField(HttpHeader.CONNECTION,HttpHeaderValue.UPGRADE)); + CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip")); + CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip, deflate")); + CACHE.put(new HttpField(HttpHeader.ACCEPT_ENCODING,"gzip,deflate,sdch")); + CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-US,en;q=0.5")); + CACHE.put(new HttpField(HttpHeader.ACCEPT_LANGUAGE,"en-GB,en-US;q=0.8,en;q=0.6")); + CACHE.put(new HttpField(HttpHeader.ACCEPT_CHARSET,"ISO-8859-1,utf-8;q=0.7,*;q=0.3")); + CACHE.put(new HttpField(HttpHeader.ACCEPT,"*/*")); + CACHE.put(new HttpField(HttpHeader.ACCEPT,"image/png,image/*;q=0.8,*/*;q=0.5")); + CACHE.put(new HttpField(HttpHeader.ACCEPT,"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")); + CACHE.put(new HttpField(HttpHeader.PRAGMA,"no-cache")); + CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"private, no-cache, no-cache=Set-Cookie, proxy-revalidate")); + CACHE.put(new HttpField(HttpHeader.CACHE_CONTROL,"no-cache")); + CACHE.put(new HttpField(HttpHeader.CONTENT_LENGTH,"0")); + CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"gzip")); + CACHE.put(new HttpField(HttpHeader.CONTENT_ENCODING,"deflate")); + CACHE.put(new HttpField(HttpHeader.TRANSFER_ENCODING,"chunked")); + CACHE.put(new HttpField(HttpHeader.EXPIRES,"Fri, 01 Jan 1990 00:00:00 GMT")); + + // Content types + for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/x-www-form-urlencoded"}) + { + HttpField field=new HttpField(HttpHeader.CONTENT_TYPE,type); + CACHE.put(field); + CONTENT_TYPE.put(type,field); + + for (String charset : new String[]{"UTF-8","ISO-8859-1"}) + { + String type_charset=type+"; charset="+charset; + field=new HttpField(HttpHeader.CONTENT_TYPE,type_charset); + CACHE.put(field); + CACHE.put(new HttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset)); + CONTENT_TYPE.put(type_charset,field); + CONTENT_TYPE.put(type+";charset="+charset,field); + } + } + + // Add headers with null values so HttpParser can avoid looking up name again for unknown values + for (HttpHeader h:HttpHeader.values()) + if (!CACHE.put(new HttpField(h,(String)null))) + throw new IllegalStateException("CACHE FULL"); + // Add some more common headers + CACHE.put(new HttpField(HttpHeader.REFERER,(String)null)); + CACHE.put(new HttpField(HttpHeader.IF_MODIFIED_SINCE,(String)null)); + CACHE.put(new HttpField(HttpHeader.IF_NONE_MATCH,(String)null)); + CACHE.put(new HttpField(HttpHeader.AUTHORIZATION,(String)null)); + CACHE.put(new HttpField(HttpHeader.COOKIE,(String)null)); + } + /* ------------------------------------------------------------------------------- */ public HttpParser(RequestHandler handler) { @@ -801,7 +870,7 @@ public class HttpParser if (add_to_connection_trie && !_connectionFields.isFull() && _header!=null && _valueString!=null) { - _field=new HttpField.CachedHttpField(_header,_valueString); + _field=new HttpField(_header,_valueString); _connectionFields.put(_field); } @@ -944,7 +1013,7 @@ public class HttpParser // Try a look ahead for the known header name and value. HttpField field=_connectionFields==null?null:_connectionFields.getBest(buffer,-1,buffer.remaining()); if (field==null) - field=HttpField.CACHE.getBest(buffer,-1,buffer.remaining()); + field=CACHE.getBest(buffer,-1,buffer.remaining()); if (field!=null) { @@ -1547,5 +1616,4 @@ public class HttpParser { return _connectionFields; } - } diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java index 311a68827ef..35fcb3230c0 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -136,10 +136,10 @@ public class MimeTypes try { ResourceBundle mime = ResourceBundle.getBundle("org/eclipse/jetty/http/mime"); - Enumeration i = mime.getKeys(); + Enumeration i = mime.getKeys(); while(i.hasMoreElements()) { - String ext = (String)i.nextElement(); + String ext = i.nextElement(); String m = mime.getString(ext); __dftMimeMap.put(StringUtil.asciiToLowerCase(ext),normalizeMimeType(m)); } diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 13dbd77cd20..4333cfc2ced 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -83,7 +83,7 @@ public class HttpFieldsTest ByteBuffer buffer=BufferUtil.allocate(1024); BufferUtil.flipToFill(buffer); - header.putTo(buffer); + HttpGenerator.putTo(header,buffer); BufferUtil.flipToFlush(buffer,0); String result=BufferUtil.toString(buffer); @@ -117,7 +117,7 @@ public class HttpFieldsTest ByteBuffer buffer = BufferUtil.allocate(1024); BufferUtil.flipToFill(buffer); - header.putTo(buffer); + HttpGenerator.putTo(header,buffer); BufferUtil.flipToFlush(buffer,0); String out = BufferUtil.toString(buffer); assertThat(out,containsString("name0: value??0")); @@ -136,7 +136,7 @@ public class HttpFieldsTest ByteBuffer buffer = BufferUtil.allocate(1024); BufferUtil.flipToFill(buffer); - header.putTo(buffer); + HttpGenerator.putTo(header,buffer); BufferUtil.flipToFlush(buffer,0); String out = BufferUtil.toString(buffer).toLowerCase(); @@ -268,123 +268,6 @@ public class HttpFieldsTest - @Test - public void testSetCookie() throws Exception - { - HttpFields fields = new HttpFields(); - - fields.addSetCookie("null",null,null,null,-1,null,false,false,-1); - assertEquals("null=",fields.getStringField("Set-Cookie")); - - fields.clear(); - - fields.addSetCookie("minimal","value",null,null,-1,null,false,false,-1); - assertEquals("minimal=value",fields.getStringField("Set-Cookie")); - - fields.clear(); - //test cookies with same name, domain and path, only 1 allowed - fields.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0); - fields.addSetCookie("everything","value","domain","path",0,"comment",true,true,0); - assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie")); - Enumeration e =fields.getValues("Set-Cookie"); - assertTrue(e.hasMoreElements()); - assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); - assertFalse(e.hasMoreElements()); - assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires")); - assertFalse(e.hasMoreElements()); - - //test cookies with same name, different domain - fields.clear(); - fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0); - fields.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0); - e =fields.getValues("Set-Cookie"); - assertTrue(e.hasMoreElements()); - assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); - assertTrue(e.hasMoreElements()); - assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); - assertFalse(e.hasMoreElements()); - - //test cookies with same name, same path, one with domain, one without - fields.clear(); - fields.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0); - fields.addSetCookie("everything","value","","path",0,"comment",true,true,0); - e =fields.getValues("Set-Cookie"); - assertTrue(e.hasMoreElements()); - assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); - assertTrue(e.hasMoreElements()); - assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); - assertFalse(e.hasMoreElements()); - - - //test cookies with same name, different path - fields.clear(); - fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0); - fields.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0); - e =fields.getValues("Set-Cookie"); - assertTrue(e.hasMoreElements()); - assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); - assertTrue(e.hasMoreElements()); - assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); - assertFalse(e.hasMoreElements()); - - //test cookies with same name, same domain, one with path, one without - fields.clear(); - fields.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0); - fields.addSetCookie("everything","value","domain1","",0,"comment",true,true,0); - e =fields.getValues("Set-Cookie"); - assertTrue(e.hasMoreElements()); - assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); - assertTrue(e.hasMoreElements()); - assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); - assertFalse(e.hasMoreElements()); - - //test cookies same name only, no path, no domain - fields.clear(); - fields.addSetCookie("everything","other","","",0,"blah",true,true,0); - fields.addSetCookie("everything","value","","",0,"comment",true,true,0); - e =fields.getValues("Set-Cookie"); - assertTrue(e.hasMoreElements()); - assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); - assertFalse(e.hasMoreElements()); - - fields.clear(); - fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1); - String setCookie=fields.getStringField("Set-Cookie"); - assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires=")); - assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\"")); - - fields.clear(); - fields.addSetCookie("name","value",null,null,-1,null,false,false,0); - setCookie=fields.getStringField("Set-Cookie"); - assertEquals(-1,setCookie.indexOf("Version=")); - fields.clear(); - fields.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0); - setCookie=fields.getStringField("Set-Cookie"); - - fields.clear(); - fields.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1); - assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie")); - - fields.clear(); - fields.addSetCookie("name","value","domain",null,-1,null,false,false,-1); - fields.addSetCookie("name","other","domain",null,-1,null,false,false,-1); - assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie")); - fields.addSetCookie("name","more","domain",null,-1,null,false,false,-1); - assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie")); - fields.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1); - fields.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1); - assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie")); - - e=fields.getValues("Set-Cookie"); - assertEquals("name=more;Domain=domain",e.nextElement()); - assertEquals("foo=bob;Domain=domain",e.nextElement()); - - fields=new HttpFields(); - fields.addSetCookie("name","value%=",null,null,-1,null,false,false,0); - setCookie=fields.getStringField("Set-Cookie"); - assertEquals("name=value%=",setCookie); - - } private Set enum2set(Enumeration e) { diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java index 026fd2c3cba..74122714dc3 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerTest.java @@ -372,7 +372,7 @@ public class HttpGeneratorServerTest assertEquals(HttpGenerator.State.START, gen.getState()); ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false); - info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970); + info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, null, true); assertEquals(HttpGenerator.Result.NEED_HEADER, result); @@ -441,7 +441,7 @@ public class HttpGeneratorServerTest assertEquals(HttpGenerator.State.START, gen.getState()); ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), -1, 200, null, false); - info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970); + info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -503,7 +503,7 @@ public class HttpGeneratorServerTest assertEquals(HttpGenerator.State.START, gen.getState()); ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false); - info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970); + info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); @@ -570,7 +570,7 @@ public class HttpGeneratorServerTest assertEquals(HttpGenerator.State.START, gen.getState()); ResponseInfo info = new ResponseInfo(HttpVersion.HTTP_1_1, new HttpFields(), 59, 200, null, false); - info.getHttpFields().add("Last-Modified", HttpFields.__01Jan1970); + info.getHttpFields().add("Last-Modified", DateGenerator.__01Jan1970); result = gen.generateResponse(info, null, null, content0, false); assertEquals(HttpGenerator.Result.NEED_HEADER, result); assertEquals(HttpGenerator.State.START, gen.getState()); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java index f711ce6e4f3..07bb5a42670 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java @@ -59,13 +59,13 @@ import org.eclipse.jetty.util.thread.Scheduler; public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable { protected static final Logger LOG = Log.getLogger(SelectorManager.class); - + public static final String SUBMIT_KEY_UPDATES="org.eclipse.jetty.io.SelectorManager.submitKeyUpdates"; /** * The default connect timeout, in milliseconds */ public static final int DEFAULT_CONNECT_TIMEOUT = 15000; - private final static boolean __submitKeyUpdates=Boolean.valueOf(System.getProperty("org.eclipse.jetty.io.SelectorManager.SubmitKeyUpdates","FALSE")); + private final static boolean __submitKeyUpdates=Boolean.valueOf(System.getProperty(SUBMIT_KEY_UPDATES,"FALSE")); private final Executor executor; private final Scheduler scheduler; @@ -360,9 +360,8 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa } /** - * Submit a task to update a selector key. If the System property - * "org.eclipse.jetty.io.SelectorManager.SubmitKeyUpdates" is set true (default is false), the - * task is passed to {@link #submit(Runnable)}. Otherwise it is run immediately and the selector + * Submit a task to update a selector key. If the System property {@link SelectorManager#SUBMIT_KEY_UPDATES} + * is set true (default is false), the task is passed to {@link #submit(Runnable)}. Otherwise it is run immediately and the selector * woken up if need be. * @param update the update to a key */ @@ -374,22 +373,9 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa { update.run(); - out: while (true) - { - switch (_state.get()) - { - case SELECT: - // Avoid multiple wakeup() calls if we the CAS fails - if (!_state.compareAndSet(State.SELECT, State.WAKEUP)) - continue; - wakeup(); - break out; - default: - break out; - } - } + if (_state.compareAndSet(State.SELECT, State.WAKEUP)) + wakeup(); } - } /** diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java index 7e1dd935c7d..8d709d87cc0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java @@ -17,11 +17,12 @@ // package org.eclipse.jetty.server; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import javax.servlet.http.Cookie; -import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -41,10 +42,9 @@ public class CookieCutter { private static final Logger LOG = Log.getLogger(CookieCutter.class); - private Cookie[] _cookies; private Cookie[] _lastCookies; - Object _lazyFields; + private final List _fieldList = new ArrayList<>(); int _fields; public CookieCutter() @@ -56,9 +56,7 @@ public class CookieCutter if (_cookies!=null) return _cookies; - if (_lastCookies!=null && - _lazyFields!=null && - _fields==LazyList.size(_lazyFields)) + if (_lastCookies!=null && _fields==_fieldList.size()) _cookies=_lastCookies; else parseFields(); @@ -70,7 +68,7 @@ public class CookieCutter { _cookies=cookies; _lastCookies=null; - _lazyFields=null; + _fieldList.clear(); _fields=0; } @@ -88,20 +86,20 @@ public class CookieCutter if (f.length()==0) return; - if (LazyList.size(_lazyFields)>_fields) + if (_fieldList.size()>_fields) { - if (f.equals(LazyList.get(_lazyFields,_fields))) + if (f.equals(_fieldList.get(_fields))) { _fields++; return; } - while (LazyList.size(_lazyFields)>_fields) - _lazyFields=LazyList.remove(_lazyFields,_fields); + while (_fieldList.size()>_fields) + _fieldList.remove(_fields); } _cookies=null; _lastCookies=null; - _lazyFields=LazyList.add(_lazyFields,_fields++,f); + _fieldList.add(_fields++,f); } @@ -110,19 +108,17 @@ public class CookieCutter _lastCookies=null; _cookies=null; - Object cookies = null; + List cookies = new ArrayList<>(); int version = 0; // delete excess fields - while (LazyList.size(_lazyFields)>_fields) - _lazyFields=LazyList.remove(_lazyFields,_fields); + while (_fieldList.size()>_fields) + _fieldList.remove(_fields); // For each cookie field - for (int f=0;f<_fields;f++) + for (String hdr : _fieldList) { - String hdr = LazyList.get(_lazyFields,f); - // Parse the header String name = null; String value = null; @@ -311,7 +307,7 @@ public class CookieCutter cookie = new Cookie(name, value); if (version > 0) cookie.setVersion(version); - cookies = LazyList.add(cookies, cookie); + cookies.add(cookie); } } catch (Exception e) @@ -325,7 +321,7 @@ public class CookieCutter } } - _cookies = (Cookie[]) LazyList.toArray(cookies,Cookie.class); + _cookies = (Cookie[]) cookies.toArray(new Cookie[cookies.size()]); _lastCookies=_cookies; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java index 0d67712f3ed..5507d1e9dbc 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceCache.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpContent; import org.eclipse.jetty.http.HttpContent.ResourceAsHttpContent; import org.eclipse.jetty.http.HttpFields; @@ -345,7 +346,7 @@ public class ResourceCache _contentType=(mimeType==null?null:BufferUtil.toBuffer(mimeType)); boolean exists=resource.exists(); _lastModified=exists?resource.lastModified():-1; - _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(HttpFields.formatDate(_lastModified)); + _lastModifiedBytes=_lastModified<0?null:BufferUtil.toBuffer(DateGenerator.formatDate(_lastModified)); _length=exists?(int)resource.length():0; _cachedSize.addAndGet(_length); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index d2b5675739a..bd698f7b133 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -18,6 +18,8 @@ package org.eclipse.jetty.server; +import static org.eclipse.jetty.util.QuotedStringTokenizer.isQuoted; + import java.io.IOException; import java.io.PrintWriter; import java.nio.channels.IllegalSelectorException; @@ -26,6 +28,7 @@ import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.Enumeration; +import java.util.Iterator; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; @@ -42,8 +45,10 @@ import org.eclipse.jetty.http.HttpField; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpGenerator.ResponseInfo; +import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpHeaderValue; +import org.eclipse.jetty.http.HttpParser; import org.eclipse.jetty.http.HttpScheme; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpURI; @@ -53,6 +58,7 @@ import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.util.ByteArrayISO8859Writer; +import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; @@ -63,7 +69,10 @@ import org.eclipse.jetty.util.log.Logger; */ public class Response implements HttpServletResponse { - private static final Logger LOG = Log.getLogger(Response.class); + private static final Logger LOG = Log.getLogger(Response.class); + private static final String __COOKIE_DELIM="\",;\\ \t"; + private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim(); + /* ------------------------------------------------------------ */ public static Response getResponse(HttpServletResponse response) @@ -186,7 +195,16 @@ public class Response implements HttpServletResponse public void addCookie(HttpCookie cookie) { - _fields.addSetCookie(cookie); + addSetCookie( + cookie.getName(), + cookie.getValue(), + cookie.getDomain(), + cookie.getPath(), + cookie.getMaxAge(), + cookie.getComment(), + cookie.isSecure(), + cookie.isHttpOnly(), + cookie.getVersion());; } @Override @@ -206,7 +224,7 @@ public class Response implements HttpServletResponse comment = null; } } - _fields.addSetCookie(cookie.getName(), + addSetCookie(cookie.getName(), cookie.getValue(), cookie.getDomain(), cookie.getPath(), @@ -217,6 +235,175 @@ public class Response implements HttpServletResponse 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) + { + // Check arguments + if (name == null || name.length() == 0) + throw new IllegalArgumentException("Bad cookie name"); + + // Format value and params + StringBuilder buf = new StringBuilder(128); + + // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting + boolean quote_name=isQuoteNeededForCookie(name); + quoteOnlyOrAppend(buf,name,quote_name); + + buf.append('='); + + // Remember name= part to look for other matching set-cookie + String name_equals=buf.toString(); + + // Append the value + boolean quote_value=isQuoteNeededForCookie(value); + quoteOnlyOrAppend(buf,value,quote_value); + + // Look for domain and path fields and check if they need to be quoted + boolean has_domain = domain!=null && domain.length()>0; + boolean quote_domain = has_domain && isQuoteNeededForCookie(domain); + boolean has_path = path!=null && path.length()>0; + boolean quote_path = has_path && isQuoteNeededForCookie(path); + + // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted + if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || isQuoted(name) || isQuoted(value) || isQuoted(path) || isQuoted(domain))) + version=1; + + // Append version + if (version==1) + buf.append (";Version=1"); + else if (version>1) + buf.append (";Version=").append(version); + + // Append path + if (has_path) + { + buf.append(";Path="); + quoteOnlyOrAppend(buf,path,quote_path); + } + + // Append domain + if (has_domain) + { + buf.append(";Domain="); + quoteOnlyOrAppend(buf,domain,quote_domain); + } + + // Handle max-age and/or expires + if (maxAge >= 0) + { + // Always use expires + // This is required as some browser (M$ this means you!) don't handle max-age even with v1 cookies + buf.append(";Expires="); + if (maxAge == 0) + buf.append(__01Jan1970_COOKIE); + else + DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); + + // for v1 cookies, also send max-age + if (version>=1) + { + buf.append(";Max-Age="); + buf.append(maxAge); + } + } + + // add the other fields + if (isSecure) + buf.append(";Secure"); + if (isHttpOnly) + buf.append(";HttpOnly"); + if (comment != null) + { + buf.append(";Comment="); + quoteOnlyOrAppend(buf,comment,isQuoteNeededForCookie(comment)); + } + + // remove any existing set-cookie fields of same name + Iterator i=_fields.iterator(); + while (i.hasNext()) + { + HttpField field=i.next(); + if (field.getHeader()==HttpHeader.SET_COOKIE) + { + String val = field.getValue(); + if (val!=null && val.startsWith(name_equals)) + { + //existing cookie has same name, does it also match domain and path? + if (((!has_domain && !val.contains("Domain")) || (has_domain && val.contains(domain))) && + ((!has_path && !val.contains("Path")) || (has_path && val.contains(path)))) + { + i.remove(); + } + } + } + } + + // add the set cookie + _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString()); + + // Expire responses with set-cookie headers so they do not get cached. + _fields.put(HttpHeader.EXPIRES.toString(), DateGenerator.__01Jan1970); + } + + + /* ------------------------------------------------------------ */ + /** Does a cookie value need to be quoted? + * @param s value string + * @return true if quoted; + * @throws IllegalArgumentException If there a control characters in the string + */ + private static boolean isQuoteNeededForCookie(String s) + { + if (s==null || s.length()==0) + return true; + + if (QuotedStringTokenizer.isQuoted(s)) + return false; + + for (int i=0;i=0) + return true; + + if (c<0x20 || c>=0x7f) + throw new IllegalArgumentException("Illegal character in cookie value"); + } + + return false; + } + + + private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote) + { + if (quote) + QuotedStringTokenizer.quoteOnly(buf,s); + else + buf.append(s); + } + @Override public boolean containsHeader(String name) { @@ -877,7 +1064,7 @@ public class Response implements HttpServletResponse if (_contentType != null) { _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType); - HttpField field = HttpField.CONTENT_TYPE.get(_contentType); + HttpField field = HttpParser.CONTENT_TYPE.get(_contentType); if (field!=null) _fields.put(field); else @@ -893,7 +1080,7 @@ public class Response implements HttpServletResponse if (_contentType != null) { _contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + ";charset=" + _characterEncoding; - HttpField field = HttpField.CONTENT_TYPE.get(_contentType); + HttpField field = HttpParser.CONTENT_TYPE.get(_contentType); if (field!=null) _fields.put(field); else @@ -952,7 +1139,7 @@ public class Response implements HttpServletResponse _explicitEncoding = true; } - HttpField field = HttpField.CONTENT_TYPE.get(_contentType); + HttpField field = HttpParser.CONTENT_TYPE.get(_contentType); if (field!=null) _fields.put(field); else diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index 31a67de487d..63fefb9c25c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -32,13 +32,14 @@ import java.util.TimerTask; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; + import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; @@ -314,17 +315,17 @@ public class Server extends HandlerWrapper implements Attributes // use DateCache timer for Date field reformat - final HttpFields.DateGenerator date = new HttpFields.DateGenerator(); + final DateGenerator date = new DateGenerator(); long now=System.currentTimeMillis(); long tick=1000*((now/1000)+1)-now; - _dateField=new HttpField.CachedHttpField(HttpHeader.DATE,date.formatDate(now)); + _dateField=new HttpGenerator.CachedHttpField(HttpHeader.DATE,date.formatDate(now)); DateCache.getTimer().scheduleAtFixedRate(new TimerTask() { @Override public void run() { long now=System.currentTimeMillis(); - _dateField=new HttpField.CachedHttpField(HttpHeader.DATE,date.formatDate(now)); + _dateField=new HttpGenerator.CachedHttpField(HttpHeader.DATE,date.formatDate(now)); if (!isRunning()) this.cancel(); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java index 4778f1a5bca..e9dfab14427 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java @@ -39,6 +39,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpGenerator.ResponseInfo; +import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.io.AbstractEndPoint; @@ -61,6 +62,7 @@ import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -645,6 +647,126 @@ public class ResponseTest output.flush(); } + + @Test + public void testSetCookie() throws Exception + { + Response response = _channel.getResponse(); + HttpFields fields = response.getHttpFields(); + + response.addSetCookie("null",null,null,null,-1,null,false,false,-1); + assertEquals("null=",fields.getStringField("Set-Cookie")); + + fields.clear(); + + response.addSetCookie("minimal","value",null,null,-1,null,false,false,-1); + assertEquals("minimal=value",fields.getStringField("Set-Cookie")); + + fields.clear(); + //test cookies with same name, domain and path, only 1 allowed + response.addSetCookie("everything","wrong","domain","path",0,"to be replaced",true,true,0); + response.addSetCookie("everything","value","domain","path",0,"comment",true,true,0); + assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",fields.getStringField("Set-Cookie")); + Enumeration e =fields.getValues("Set-Cookie"); + assertTrue(e.hasMoreElements()); + assertEquals("everything=value;Version=1;Path=path;Domain=domain;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); + assertFalse(e.hasMoreElements()); + assertEquals("Thu, 01 Jan 1970 00:00:00 GMT",fields.getStringField("Expires")); + assertFalse(e.hasMoreElements()); + + //test cookies with same name, different domain + fields.clear(); + response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0); + response.addSetCookie("everything","value","domain2","path",0,"comment",true,true,0); + e =fields.getValues("Set-Cookie"); + assertTrue(e.hasMoreElements()); + assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); + assertTrue(e.hasMoreElements()); + assertEquals("everything=value;Version=1;Path=path;Domain=domain2;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); + assertFalse(e.hasMoreElements()); + + //test cookies with same name, same path, one with domain, one without + fields.clear(); + response.addSetCookie("everything","other","domain1","path",0,"blah",true,true,0); + response.addSetCookie("everything","value","","path",0,"comment",true,true,0); + e =fields.getValues("Set-Cookie"); + assertTrue(e.hasMoreElements()); + assertEquals("everything=other;Version=1;Path=path;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); + assertTrue(e.hasMoreElements()); + assertEquals("everything=value;Version=1;Path=path;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); + assertFalse(e.hasMoreElements()); + + + //test cookies with same name, different path + fields.clear(); + response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0); + response.addSetCookie("everything","value","domain1","path2",0,"comment",true,true,0); + e =fields.getValues("Set-Cookie"); + assertTrue(e.hasMoreElements()); + assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); + assertTrue(e.hasMoreElements()); + assertEquals("everything=value;Version=1;Path=path2;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); + assertFalse(e.hasMoreElements()); + + //test cookies with same name, same domain, one with path, one without + fields.clear(); + response.addSetCookie("everything","other","domain1","path1",0,"blah",true,true,0); + response.addSetCookie("everything","value","domain1","",0,"comment",true,true,0); + e =fields.getValues("Set-Cookie"); + assertTrue(e.hasMoreElements()); + assertEquals("everything=other;Version=1;Path=path1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=blah",e.nextElement()); + assertTrue(e.hasMoreElements()); + assertEquals("everything=value;Version=1;Domain=domain1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); + assertFalse(e.hasMoreElements()); + + //test cookies same name only, no path, no domain + fields.clear(); + response.addSetCookie("everything","other","","",0,"blah",true,true,0); + response.addSetCookie("everything","value","","",0,"comment",true,true,0); + e =fields.getValues("Set-Cookie"); + assertTrue(e.hasMoreElements()); + assertEquals("everything=value;Version=1;Expires=Thu, 01-Jan-1970 00:00:00 GMT;Max-Age=0;Secure;HttpOnly;Comment=comment",e.nextElement()); + assertFalse(e.hasMoreElements()); + + fields.clear(); + response.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,1); + String setCookie=fields.getStringField("Set-Cookie"); + assertThat(setCookie,Matchers.startsWith("\"ev erything\"=\"va lue\";Version=1;Path=\"pa th\";Domain=\"do main\";Expires=")); + assertThat(setCookie,Matchers.endsWith(" GMT;Max-Age=1;Secure;HttpOnly;Comment=\"co mment\"")); + + fields.clear(); + response.addSetCookie("name","value",null,null,-1,null,false,false,0); + setCookie=fields.getStringField("Set-Cookie"); + assertEquals(-1,setCookie.indexOf("Version=")); + fields.clear(); + response.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0); + setCookie=fields.getStringField("Set-Cookie"); + + fields.clear(); + response.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1); + assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie")); + + fields.clear(); + response.addSetCookie("name","value","domain",null,-1,null,false,false,-1); + response.addSetCookie("name","other","domain",null,-1,null,false,false,-1); + assertEquals("name=other;Domain=domain",fields.getStringField("Set-Cookie")); + response.addSetCookie("name","more","domain",null,-1,null,false,false,-1); + assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie")); + response.addSetCookie("foo","bar","domain",null,-1,null,false,false,-1); + response.addSetCookie("foo","bob","domain",null,-1,null,false,false,-1); + assertEquals("name=more;Domain=domain",fields.getStringField("Set-Cookie")); + + e=fields.getValues("Set-Cookie"); + assertEquals("name=more;Domain=domain",e.nextElement()); + assertEquals("foo=bob;Domain=domain",e.nextElement()); + + fields.clear(); + response.addSetCookie("name","value%=",null,null,-1,null,false,false,0); + setCookie=fields.getStringField("Set-Cookie"); + assertEquals("name=value%=",setCookie); + + } + private Response newResponse() { _channel.reset(); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java index 4a84ad96cea..9ccbe9b3874 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/DefaultServletTest.java @@ -35,6 +35,7 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; +import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.LocalConnector; @@ -724,16 +725,16 @@ public class DefaultServletTest response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+last_modified+"\r\n\r\n"); assertResponseContains("304", response); - response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n"); + response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n"); assertResponseContains("200", response); - response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n"); + response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Modified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n"); assertResponseContains("304", response); - response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n"); + response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()+10000)+"\r\n\r\n"); assertResponseContains("200", response); - response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+HttpFields.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n"); + response = connector.getResponses("GET /context/file.txt HTTP/1.1\r\nHost:test\r\nConnection:close\r\nIf-Unmodified-Since: "+DateGenerator.formatDate(System.currentTimeMillis()-10000)+"\r\n\r\n"); assertResponseContains("412", response); } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java index 1cf6f100680..c4a8619f461 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipTester.java @@ -42,6 +42,7 @@ import javax.servlet.DispatcherType; import javax.servlet.Servlet; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.DateGenerator; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpTester; @@ -98,7 +99,7 @@ public class GzipTester request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",compressionType); if (ifmodifiedsince>0) - request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),HttpFields.formatDate(ifmodifiedsince)); + request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince)); if (this.userAgent != null) request.setHeader("User-Agent", this.userAgent); request.setURI("/context/" + requestedFilename); @@ -176,7 +177,7 @@ public class GzipTester request.setHeader("Host","tester"); request.setHeader("Accept-Encoding",compressionType); if (ifmodifiedsince>0) - request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),HttpFields.formatDate(ifmodifiedsince)); + request.setHeader(HttpHeader.IF_MODIFIED_SINCE.asString(),DateGenerator.formatDate(ifmodifiedsince)); if (this.userAgent != null) request.setHeader("User-Agent", this.userAgent); request.setURI("/context/" + requestedFilename);