418888 Added strict mode to HttpGenerator

This commit is contained in:
Greg Wilkins 2013-11-22 22:54:09 +11:00
parent 3499c52019
commit 8adf552c80
6 changed files with 191 additions and 95 deletions

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.http;
import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import org.eclipse.jetty.http.HttpTokens.EndOfContent;
import org.eclipse.jetty.util.BufferUtil;
@ -32,12 +33,20 @@ import org.eclipse.jetty.util.log.Logger;
/**
* HttpGenerator. Builds HTTP Messages.
*
* If the system property "org.eclipse.jetty.http.HttpGenerator.STRICT" is set to true,
* then the generator will strictly pass on the exact strings received from methods and header
* fields. Otherwise a fast case insensitive string lookup is used that may alter the
* case and white space of some methods/headers
* </p>
*/
public class HttpGenerator
{
private static final Logger LOG = Log.getLogger(HttpGenerator.class);
private final static Logger LOG = Log.getLogger(HttpGenerator.class);
public final static boolean __STRICT=Boolean.getBoolean("org.eclipse.jetty.http.HttpGenerator.STRICT");
private final static byte[] __colon_space = new byte[] {':',' '};
private final static HttpHeaderValue[] CLOSE = {HttpHeaderValue.CLOSE};
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 =
@ -607,7 +616,7 @@ public class HttpGenerator
putTo(field,header);
// Lookup and/or split connection value field
HttpHeaderValue[] values = new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
HttpHeaderValue[] values = HttpHeaderValue.CLOSE.is(field.getValue())?CLOSE:new HttpHeaderValue[]{HttpHeaderValue.CACHE.get(field.getValue())};
String[] split = null;
if (values[0]==null)
@ -1085,7 +1094,7 @@ public class HttpGenerator
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());
System.arraycopy(value.getBytes(StandardCharsets.ISO_8859_1),0,_bytes,cbl,value.length());
_bytes[_bytes.length-2]=(byte)'\r';
_bytes[_bytes.length-1]=(byte)'\n';
}

View File

@ -69,6 +69,12 @@ public enum HttpHeaderValue
return _buffer.asReadOnlyBuffer();
}
/* ------------------------------------------------------------ */
public boolean is(String s)
{
return _string.equalsIgnoreCase(s);
}
/* ------------------------------------------------------------ */
public String asString()
{

View File

@ -91,7 +91,6 @@ public class HttpParser
* </ul>
*/
public final static Trie<HttpField> CACHE = new ArrayTrie<>(2048);
public final static Trie<HttpField> CONTENT_TYPE = new ArrayTrie<>(512);
// States
public enum State
@ -178,21 +177,16 @@ public class HttpParser
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"})
// Add common Content types as fields
for (String type : new String[]{"text/plain","text/html","text/xml","text/json","application/json","application/x-www-form-urlencoded"})
{
HttpField field=new HttpField(HttpHeader.CONTENT_TYPE,type);
HttpField field=new HttpGenerator.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 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);
CACHE.put(field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+";charset="+charset));
CACHE.put(new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,type+"; charset="+charset));
}
}

View File

@ -20,10 +20,10 @@ package org.eclipse.jetty.http;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.MissingResourceException;
@ -53,30 +53,65 @@ public class MimeTypes
TEXT_HTML("text/html"),
TEXT_PLAIN("text/plain"),
TEXT_XML("text/xml"),
TEXT_JSON("text/json"),
TEXT_JSON("text/json",StandardCharsets.UTF_8),
APPLICATION_JSON("application/json",StandardCharsets.UTF_8),
TEXT_HTML_8859_1("text/html;charset=ISO-8859-1"),
TEXT_PLAIN_8859_1("text/plain;charset=ISO-8859-1"),
TEXT_XML_8859_1("text/xml;charset=ISO-8859-1"),
TEXT_HTML_UTF_8("text/html;charset=UTF-8"),
TEXT_PLAIN_UTF_8("text/plain;charset=UTF-8"),
TEXT_XML_UTF_8("text/xml;charset=UTF-8"),
TEXT_JSON_UTF_8("text/json;charset=UTF-8");
TEXT_HTML_8859_1("text/html; charset=ISO-8859-1",TEXT_HTML),
TEXT_HTML_UTF_8("text/html; charset=UTF-8",TEXT_HTML),
TEXT_PLAIN_8859_1("text/plain; charset=ISO-8859-1",TEXT_PLAIN),
TEXT_PLAIN_UTF_8("text/plain; charset=UTF-8",TEXT_PLAIN),
TEXT_XML_8859_1("text/xml; charset=ISO-8859-1",TEXT_XML),
TEXT_XML_UTF_8("text/xml; charset=UTF-8",TEXT_XML),
TEXT_JSON_8859_1("text/json; charset=ISO-8859-1",TEXT_JSON),
TEXT_JSON_UTF_8("text/json; charset=UTF-8",TEXT_JSON),
APPLICATION_JSON_8859_1("text/json; charset=ISO-8859-1",APPLICATION_JSON),
APPLICATION_JSON_UTF_8("text/json; charset=UTF-8",APPLICATION_JSON);
/* ------------------------------------------------------------ */
private final String _string;
private final Type _base;
private final ByteBuffer _buffer;
private final Charset _charset;
private final boolean _assumedCharset;
private final HttpField _field;
/* ------------------------------------------------------------ */
Type(String s)
{
_string=s;
_buffer=BufferUtil.toBuffer(s);
int i=s.toLowerCase(Locale.ENGLISH).indexOf("charset=");
_charset=(i>0)?Charset.forName(s.substring(i+8)):null;
_base=this;
_charset=null;
_assumedCharset=false;
_field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
Type(String s,Type base)
{
_string=s;
_buffer=BufferUtil.toBuffer(s);
_base=base;
int i=s.indexOf("; charset=");
_charset=Charset.forName(s.substring(i+10));
_assumedCharset=false;
_field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
Type(String s,Charset cs)
{
_string=s;
_base=this;
_buffer=BufferUtil.toBuffer(s);
_charset=cs;
_assumedCharset=true;
_field=new HttpGenerator.CachedHttpField(HttpHeader.CONTENT_TYPE,_string);
}
/* ------------------------------------------------------------ */
@ -109,6 +144,24 @@ public class MimeTypes
{
return _string;
}
/* ------------------------------------------------------------ */
public boolean isCharsetAssumed()
{
return _assumedCharset;
}
/* ------------------------------------------------------------ */
public HttpField getContentTypeField()
{
return _field;
}
/* ------------------------------------------------------------ */
public Type getBaseType()
{
return _base;
}
}
/* ------------------------------------------------------------ */

View File

@ -950,10 +950,15 @@ public class Response implements HttpServletResponse
String encoding = _characterEncoding;
if (encoding == null)
{
encoding = MimeTypes.inferCharsetFromContentType(_contentType);
if (encoding == null)
encoding = StringUtil.__ISO_8859_1;
setCharacterEncoding(encoding,false);
if (_mimeType!=null && _mimeType.isCharsetAssumed())
encoding=_mimeType.getCharset().toString();
else
{
encoding = MimeTypes.inferCharsetFromContentType(_contentType);
if (encoding == null)
encoding = StringUtil.__ISO_8859_1;
setCharacterEncoding(encoding,false);
}
}
if (_writer != null && _writer.isFor(encoding))
@ -1058,7 +1063,7 @@ public class Response implements HttpServletResponse
private void setCharacterEncoding(String encoding, boolean explicit)
{
if (isIncluding())
if (isIncluding() || isWriting())
return;
if (_outputType == OutputType.NONE && !isCommitted())
@ -1071,30 +1076,38 @@ public class Response implements HttpServletResponse
if (_characterEncoding != null)
{
_characterEncoding = null;
if (_contentType != null)
if (_mimeType!=null)
{
_mimeType=_mimeType.getBaseType();
_contentType=_mimeType.asString();
_fields.put(_mimeType.getContentTypeField());
}
else if (_contentType != null)
{
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
HttpField field = HttpParser.CONTENT_TYPE.get(_contentType);
if (field!=null)
_fields.put(field);
else
_fields.put(HttpHeader.CONTENT_TYPE, _contentType);
_fields.put(HttpHeader.CONTENT_TYPE, _contentType);
}
}
}
else
{
// No, so just add this one to the mimetype
_explicitEncoding=explicit;
_characterEncoding = StringUtil.normalizeCharset(encoding);
if (_contentType != null)
_explicitEncoding = explicit;
_characterEncoding = HttpGenerator.__STRICT?encoding:StringUtil.normalizeCharset(encoding);
if (_mimeType!=null)
{
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + ";charset=" + _characterEncoding;
HttpField field = HttpParser.CONTENT_TYPE.get(_contentType);
if (field!=null)
_fields.put(field);
else
_contentType=_mimeType.getBaseType().asString()+ "; charset=" + _characterEncoding;
_mimeType = MimeTypes.CACHE.get(_contentType);
if (_mimeType==null || HttpGenerator.__STRICT)
_fields.put(HttpHeader.CONTENT_TYPE, _contentType);
else
_fields.put(_mimeType.getContentTypeField());
}
else if (_contentType != null)
{
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType) + "; charset=" + _characterEncoding;
_fields.put(HttpHeader.CONTENT_TYPE, _contentType);
}
}
}
@ -1121,9 +1134,10 @@ public class Response implements HttpServletResponse
{
_contentType = contentType;
_mimeType = MimeTypes.CACHE.get(contentType);
String charset;
if (_mimeType != null && _mimeType.getCharset() != null)
charset = _mimeType.getCharset().toString();
if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed())
charset=_mimeType.getCharset().toString();
else
charset = MimeTypes.getCharsetFromContentType(contentType);
@ -1131,7 +1145,7 @@ public class Response implements HttpServletResponse
{
if (_characterEncoding != null)
{
_contentType = contentType + ";charset=" + _characterEncoding;
_contentType = contentType + "; charset=" + _characterEncoding;
_mimeType = null;
}
}
@ -1141,7 +1155,7 @@ public class Response implements HttpServletResponse
_mimeType = null;
_contentType = MimeTypes.getContentTypeWithoutCharset(_contentType);
if (_characterEncoding != null)
_contentType = _contentType + ";charset=" + _characterEncoding;
_contentType = _contentType + "; charset=" + _characterEncoding;
}
else
{
@ -1149,12 +1163,15 @@ public class Response implements HttpServletResponse
_explicitEncoding = true;
}
HttpField field = HttpParser.CONTENT_TYPE.get(_contentType);
if (field!=null)
_fields.put(field);
else
if (HttpGenerator.__STRICT || _mimeType==null)
_fields.put(HttpHeader.CONTENT_TYPE, _contentType);
else
{
_contentType=_mimeType.asString();
_fields.put(_mimeType.getContentTypeField());
}
}
}
@Override

View File

@ -123,9 +123,9 @@ public class ResponseTest
response.setContentType("foo/bar");
assertEquals("foo/bar", response.getContentType());
response.getWriter();
assertEquals("foo/bar;charset=ISO-8859-1", response.getContentType());
assertEquals("foo/bar; charset=ISO-8859-1", response.getContentType());
response.setContentType("foo2/bar2");
assertEquals("foo2/bar2;charset=ISO-8859-1", response.getContentType());
assertEquals("foo2/bar2; charset=ISO-8859-1", response.getContentType());
response.setHeader("name", "foo");
Iterator<String> en = response.getHeaders("name").iterator();
@ -142,35 +142,47 @@ public class ResponseTest
response.setContentType("text/html");
assertEquals("text/html", response.getContentType());
response.getWriter();
assertEquals("text/html;charset=ISO-8859-1", response.getContentType());
assertEquals("text/html; charset=ISO-8859-1", response.getContentType());
response.setContentType("foo2/bar2");
assertEquals("foo2/bar2;charset=ISO-8859-1", response.getContentType());
assertEquals("foo2/bar2; charset=ISO-8859-1", response.getContentType());
response.recycle();
response.setContentType("text/xml;charset=ISO-8859-7");
response.getWriter();
assertEquals("text/xml;charset=ISO-8859-7", response.getContentType());
response.setContentType("text/html;charset=UTF-8");
assertEquals("text/html;charset=ISO-8859-7", response.getContentType());
assertEquals("text/html; charset=ISO-8859-7", response.getContentType());
response.recycle();
response.setContentType("text/html;charset=US-ASCII");
response.getWriter();
assertEquals("text/html;charset=US-ASCII", response.getContentType());
response.recycle();
response.setContentType("text/html; charset=utf-8");
response.getWriter();
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.recycle();
response.setContentType("text/json");
response.getWriter();
assertEquals("text/json;charset=UTF-8", response.getContentType());
assertEquals("text/json", response.getContentType());
response.recycle();
response.setContentType("text/json");
response.setCharacterEncoding("UTF-8");
response.getWriter();
assertEquals("text/json; charset=UTF-8", response.getContentType());
response.recycle();
response.setCharacterEncoding("xyz");
response.setContentType("foo/bar");
assertEquals("foo/bar;charset=xyz", response.getContentType());
assertEquals("foo/bar; charset=xyz", response.getContentType());
response.recycle();
response.setContentType("foo/bar");
response.setCharacterEncoding("xyz");
assertEquals("foo/bar;charset=xyz", response.getContentType());
assertEquals("foo/bar; charset=xyz", response.getContentType());
response.recycle();
response.setCharacterEncoding("xyz");
@ -180,7 +192,7 @@ public class ResponseTest
response.recycle();
response.setContentType("foo/bar;charset=abc");
response.setCharacterEncoding("xyz");
assertEquals("foo/bar;charset=xyz", response.getContentType());
assertEquals("foo/bar; charset=xyz", response.getContentType());
response.recycle();
response.setCharacterEncoding("xyz");
@ -196,6 +208,11 @@ public class ResponseTest
response.recycle();
response.addHeader("Content-Type","text/something");
assertEquals("text/something",response.getContentType());
response.recycle();
response.addHeader("Content-Type","application/json");
response.getWriter();
assertEquals("application/json",response.getContentType());
}
@ -212,13 +229,13 @@ public class ResponseTest
response.setLocale(java.util.Locale.ITALIAN);
assertEquals(null, response.getContentType());
response.setContentType("text/plain");
assertEquals("text/plain;charset=ISO-8859-2", response.getContentType());
assertEquals("text/plain; charset=ISO-8859-2", response.getContentType());
response.recycle();
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.setLocale(java.util.Locale.ITALIAN);
assertEquals("text/plain;charset=UTF-8", response.getContentType());
assertEquals("text/plain; charset=UTF-8", response.getContentType());
assertTrue(response.toString().indexOf("charset=UTF-8") > 0);
}
@ -229,25 +246,25 @@ public class ResponseTest
response.setContentType("foo/bar");
response.setCharacterEncoding("utf-8");
assertEquals("foo/bar;charset=UTF-8", response.getContentType());
assertEquals("foo/bar; charset=UTF-8", response.getContentType());
response.getWriter();
assertEquals("foo/bar;charset=UTF-8", response.getContentType());
assertEquals("foo/bar; charset=UTF-8", response.getContentType());
response.setContentType("foo2/bar2");
assertEquals("foo2/bar2;charset=UTF-8", response.getContentType());
assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
response.setCharacterEncoding("ISO-8859-1");
assertEquals("foo2/bar2;charset=UTF-8", response.getContentType());
assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
response.recycle();
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
assertEquals("text/html;charset=UTF-8", response.getContentType());
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.getWriter();
assertEquals("text/html;charset=UTF-8", response.getContentType());
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.setContentType("text/xml");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
response.setCharacterEncoding("ISO-8859-1");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
}
@Test
@ -256,25 +273,25 @@ public class ResponseTest
Response response = newResponse();
response.setCharacterEncoding("utf-8");
response.setContentType("foo/bar");
assertEquals("foo/bar;charset=UTF-8", response.getContentType());
assertEquals("foo/bar; charset=UTF-8", response.getContentType());
response.getWriter();
assertEquals("foo/bar;charset=UTF-8", response.getContentType());
assertEquals("foo/bar; charset=UTF-8", response.getContentType());
response.setContentType("foo2/bar2");
assertEquals("foo2/bar2;charset=UTF-8", response.getContentType());
assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
response.setCharacterEncoding("ISO-8859-1");
assertEquals("foo2/bar2;charset=UTF-8", response.getContentType());
assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
response.recycle();
response.setCharacterEncoding("utf-8");
response.setContentType("text/html");
assertEquals("text/html;charset=UTF-8", response.getContentType());
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.getWriter();
assertEquals("text/html;charset=UTF-8", response.getContentType());
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.setContentType("text/xml");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
response.setCharacterEncoding("iso-8859-1");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
}
@Test
@ -288,21 +305,21 @@ public class ResponseTest
response.getWriter();
assertEquals("foo/bar; charset=utf-8", response.getContentType());
response.setContentType("foo2/bar2");
assertEquals("foo2/bar2;charset=UTF-8", response.getContentType());
assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
response.setCharacterEncoding("ISO-8859-1");
assertEquals("foo2/bar2;charset=UTF-8", response.getContentType());
assertEquals("foo2/bar2; charset=UTF-8", response.getContentType());
response.recycle();
response.setCharacterEncoding("utf16");
response.setContentType("text/html; charset=utf-8");
assertEquals("text/html; charset=utf-8", response.getContentType());
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.getWriter();
assertEquals("text/html; charset=utf-8", response.getContentType());
assertEquals("text/html; charset=UTF-8", response.getContentType());
response.setContentType("text/xml");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
response.setCharacterEncoding("iso-8859-1");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
}
@Test
@ -313,19 +330,19 @@ public class ResponseTest
response.setContentType("foo/bar; other=xyz");
assertEquals("foo/bar; other=xyz", response.getContentType());
response.getWriter();
assertEquals("foo/bar; other=xyz;charset=ISO-8859-1", response.getContentType());
assertEquals("foo/bar; other=xyz; charset=ISO-8859-1", response.getContentType());
response.setContentType("foo2/bar2");
assertEquals("foo2/bar2;charset=ISO-8859-1", response.getContentType());
assertEquals("foo2/bar2; charset=ISO-8859-1", response.getContentType());
response.recycle();
response.setCharacterEncoding("utf-8");
response.setContentType("text/html; other=xyz");
assertEquals("text/html; other=xyz;charset=UTF-8", response.getContentType());
assertEquals("text/html; other=xyz; charset=UTF-8", response.getContentType());
response.getWriter();
assertEquals("text/html; other=xyz;charset=UTF-8", response.getContentType());
assertEquals("text/html; other=xyz; charset=UTF-8", response.getContentType());
response.setContentType("text/xml");
assertEquals("text/xml;charset=UTF-8", response.getContentType());
assertEquals("text/xml; charset=UTF-8", response.getContentType());
}
@Test
@ -343,17 +360,17 @@ public class ResponseTest
response.setCharacterEncoding("utf16");
response.setContentType("text/html; other=xyz charset=utf-8");
assertEquals("text/html; other=xyz charset=utf-8;charset=UTF-16", response.getContentType());
assertEquals("text/html; other=xyz charset=utf-8; charset=UTF-16", response.getContentType());
response.getWriter();
assertEquals("text/html; other=xyz charset=utf-8;charset=UTF-16", response.getContentType());
assertEquals("text/html; other=xyz charset=utf-8; charset=UTF-16", response.getContentType());
response.recycle();
response.setCharacterEncoding("utf16");
response.setContentType("foo/bar; other=pq charset=utf-8 other=xyz");
assertEquals("foo/bar; other=pq charset=utf-8 other=xyz;charset=UTF-16", response.getContentType());
assertEquals("foo/bar; other=pq charset=utf-8 other=xyz; charset=UTF-16", response.getContentType());
response.getWriter();
assertEquals("foo/bar; other=pq charset=utf-8 other=xyz;charset=UTF-16", response.getContentType());
assertEquals("foo/bar; other=pq charset=utf-8 other=xyz; charset=UTF-16", response.getContentType());
}
@Test