Merge remote-tracking branch 'origin/jetty-9.3.x' into jetty-9.4.x

This commit is contained in:
Greg Wilkins 2017-01-18 14:39:20 +11:00
commit c6e910cf12
6 changed files with 190 additions and 23 deletions

View File

@ -41,11 +41,18 @@ import org.eclipse.jetty.util.log.Logger;
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /** MIME Type enum and utilities
* *
*/ */
public class MimeTypes public class MimeTypes
{ {
/* ------------------------------------------------------------ */
private static final Logger LOG = Log.getLogger(MimeTypes.class);
private static final Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
private static final Map<String,String> __dftMimeMap = new HashMap<String,String>();
private static final Map<String,String> __inferredEncodings = new HashMap<String,String>();
private static final Map<String,String> __assumedEncodings = new HashMap<String,String>();
public enum Type public enum Type
{ {
FORM_ENCODED("application/x-www-form-urlencoded"), FORM_ENCODED("application/x-www-form-urlencoded"),
@ -70,8 +77,8 @@ public class MimeTypes
TEXT_JSON_8859_1("text/json;charset=iso-8859-1",TEXT_JSON), TEXT_JSON_8859_1("text/json;charset=iso-8859-1",TEXT_JSON),
TEXT_JSON_UTF_8("text/json;charset=utf-8",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_8859_1("application/json;charset=iso-8859-1",APPLICATION_JSON),
APPLICATION_JSON_UTF_8("text/json;charset=utf-8",APPLICATION_JSON); APPLICATION_JSON_UTF_8("application/json;charset=utf-8",APPLICATION_JSON);
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
@ -177,12 +184,7 @@ public class MimeTypes
} }
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
private static final Logger LOG = Log.getLogger(MimeTypes.class); public static final Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
public final static Trie<MimeTypes.Type> CACHE= new ArrayTrie<>(512);
private final static Trie<ByteBuffer> TYPES= new ArrayTrie<ByteBuffer>(512);
private final static Map<String,String> __dftMimeMap = new HashMap<String,String>();
private final static Map<String,String> __encodings = new HashMap<String,String>();
static static
{ {
for (MimeTypes.Type type : MimeTypes.Type.values()) for (MimeTypes.Type type : MimeTypes.Type.values())
@ -197,6 +199,9 @@ public class MimeTypes
CACHE.put(alt,type); CACHE.put(alt,type);
TYPES.put(alt,type.asBuffer()); TYPES.put(alt,type.asBuffer());
} }
if (type.isCharsetAssumed())
__assumedEncodings.put(type.asString(),type.getCharsetString());
} }
String resourceName = "org/eclipse/jetty/http/mime.properties"; String resourceName = "org/eclipse/jetty/http/mime.properties";
@ -240,7 +245,6 @@ public class MimeTypes
LOG.debug(e); LOG.debug(e);
} }
resourceName = "org/eclipse/jetty/http/encoding.properties"; resourceName = "org/eclipse/jetty/http/encoding.properties";
try (InputStream stream = MimeTypes.class.getClassLoader().getResourceAsStream(resourceName)) try (InputStream stream = MimeTypes.class.getClassLoader().getResourceAsStream(resourceName))
{ {
@ -254,13 +258,20 @@ public class MimeTypes
props.load(reader); props.load(reader);
props.stringPropertyNames().stream() props.stringPropertyNames().stream()
.filter(t->t!=null) .filter(t->t!=null)
.forEach(t->__encodings.put(t, props.getProperty(t))); .forEach(t->
{
String charset = props.getProperty(t);
if (charset.startsWith("-"))
__assumedEncodings.put(t, charset.substring(1));
else
__inferredEncodings.put(t, props.getProperty(t));
});
if (__encodings.size()==0) if (__inferredEncodings.size()==0)
{ {
LOG.warn("Empty encodings at {}", resourceName); LOG.warn("Empty encodings at {}", resourceName);
} }
else if (__encodings.size()<props.keySet().size()) else if ((__inferredEncodings.size()+__assumedEncodings.size())<props.keySet().size())
{ {
LOG.warn("Null or duplicate encodings in resource: {}", resourceName); LOG.warn("Null or duplicate encodings in resource: {}", resourceName);
} }
@ -312,6 +323,43 @@ public class MimeTypes
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** Get the MIME type by filename extension. /** Get the MIME type by filename extension.
* Lookup only the static default mime map.
* @param filename A file name
* @return MIME type matching the longest dot extension of the
* file name.
*/
public static String getDefaultMimeByExtension(String filename)
{
String type=null;
if (filename!=null)
{
int i=-1;
while(type==null)
{
i=filename.indexOf(".",i+1);
if (i<0 || i>=filename.length())
break;
String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
if (type==null)
type=__dftMimeMap.get(ext);
}
}
if (type==null)
{
if (type==null)
type=__dftMimeMap.get("*");
}
return type;
}
/* ------------------------------------------------------------ */
/** Get the MIME type by filename extension.
* Lookup the content and static default mime maps.
* @param filename A file name * @param filename A file name
* @return MIME type matching the longest dot extension of the * @return MIME type matching the longest dot extension of the
* file name. * file name.
@ -449,11 +497,44 @@ public class MimeTypes
return null; return null;
} }
public static String inferCharsetFromContentType(String value) /**
* Access a mutable map of mime type to the charset inferred from that content type.
* An inferred encoding is used by when encoding/decoding a stream and is
* explicitly set in any metadata (eg Content-Type).
* @return Map of mime type to charset
*/
public static Map<String,String> getInferredEncodings()
{ {
return __encodings.get(value); return __inferredEncodings;
}
/**
* Access a mutable map of mime type to the charset assumed for that content type.
* An assumed encoding is used by when encoding/decoding a stream, but is not
* explicitly set in any metadata (eg Content-Type).
* @return Map of mime type to charset
*/
public static Map<String,String> getAssumedEncodings()
{
return __inferredEncodings;
} }
@Deprecated
public static String inferCharsetFromContentType(String contentType)
{
return getCharsetAssumedFromContentType(contentType);
}
public static String getCharsetInferredFromContentType(String contentType)
{
return __inferredEncodings.get(contentType);
}
public static String getCharsetAssumedFromContentType(String contentType)
{
return __assumedEncodings.get(contentType);
}
public static String getContentTypeWithoutCharset(String value) public static String getContentTypeWithoutCharset(String value)
{ {
int end=value.length(); int end=value.length();
@ -545,4 +626,5 @@ public class MimeTypes
return builder.toString(); return builder.toString();
} }
} }

View File

@ -1,5 +1,11 @@
# Mapping of mime type to inferred or assumed charset
# inferred charsets are used for encoding/decoding and explicitly set in associated metadata
# assumed charsets are used for encoding/decoding, but are not set in associated metadata
# In this file, assumed charsets are indicatd with a leading '-'
text/html=utf-8 text/html=utf-8
text/plain=iso-8859-1 text/plain=iso-8859-1
text/xml=utf-8 text/xml=utf-8
text/json=utf-8 application/xhtml+xml=utf-8
application/xhtml+xml=utf-8 text/json=-utf-8
application/vnd.api+json=-utf-8

View File

@ -797,7 +797,12 @@ public class Response implements HttpServletResponse
public String getCharacterEncoding() public String getCharacterEncoding()
{ {
if (_characterEncoding == null) if (_characterEncoding == null)
{
String encoding = MimeTypes.getCharsetAssumedFromContentType(_contentType);
if (encoding!=null)
return encoding;
_characterEncoding = StringUtil.__ISO_8859_1; _characterEncoding = StringUtil.__ISO_8859_1;
}
return _characterEncoding; return _characterEncoding;
} }
@ -837,10 +842,14 @@ public class Response implements HttpServletResponse
encoding=_mimeType.getCharsetString(); encoding=_mimeType.getCharsetString();
else else
{ {
encoding = MimeTypes.inferCharsetFromContentType(_contentType); encoding = MimeTypes.getCharsetAssumedFromContentType(_contentType);
if (encoding == null) if (encoding == null)
encoding = StringUtil.__ISO_8859_1; {
setCharacterEncoding(encoding,EncodingFrom.INFERRED); encoding = MimeTypes.getCharsetInferredFromContentType(_contentType);
if (encoding == null)
encoding = StringUtil.__ISO_8859_1;
setCharacterEncoding(encoding,EncodingFrom.INFERRED);
}
} }
} }

View File

@ -150,7 +150,7 @@ public class BufferedResponseHandler extends HandlerWrapper
} }
// If the mime type is known from the path, then apply mime type filtering // If the mime type is known from the path, then apply mime type filtering
String mimeType = context==null?null:context.getMimeType(path); String mimeType = context==null?MimeTypes.getDefaultMimeByExtension(path):context.getMimeType(path);
if (mimeType!=null) if (mimeType!=null)
{ {
mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType); mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);

View File

@ -20,9 +20,12 @@ package org.eclipse.jetty.server.handler.gzip;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set; import java.util.Set;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import javax.servlet.DispatcherType;
import javax.servlet.ServletContext; import javax.servlet.ServletContext;
import javax.servlet.ServletException; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -66,6 +69,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
private boolean _checkGzExists = true; private boolean _checkGzExists = true;
private boolean _syncFlush = false; private boolean _syncFlush = false;
private int _inflateBufferSize = -1; private int _inflateBufferSize = -1;
private EnumSet<DispatcherType> _dispatchers = EnumSet.of(DispatcherType.REQUEST);
// non-static, as other GzipHandler instances may have different configurations // non-static, as other GzipHandler instances may have different configurations
private final ThreadLocal<Deflater> _deflater = new ThreadLocal<>(); private final ThreadLocal<Deflater> _deflater = new ThreadLocal<>();
@ -130,6 +134,25 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
_methods.exclude(m); _methods.exclude(m);
} }
/* ------------------------------------------------------------ */
public EnumSet<DispatcherType> getDispatcherTypes()
{
return _dispatchers;
}
/* ------------------------------------------------------------ */
public void setDispatcherTypes(EnumSet<DispatcherType> dispatchers)
{
_dispatchers = dispatchers;
}
/* ------------------------------------------------------------ */
public void setDispatcherTypes(DispatcherType... dispatchers)
{
_dispatchers = EnumSet.copyOf(Arrays.asList(dispatchers));
}
/* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */
/** /**
* Set the mime types. * Set the mime types.
@ -395,6 +418,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
return _minGzipSize; return _minGzipSize;
} }
/* ------------------------------------------------------------ */
protected HttpField getVaryField() protected HttpField getVaryField()
{ {
return _vary; return _vary;
@ -429,6 +453,13 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo()); String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo());
LOG.debug("{} handle {} in {}",this,baseRequest,context); LOG.debug("{} handle {} in {}",this,baseRequest,context);
if (!_dispatchers.contains(baseRequest.getDispatcherType()))
{
LOG.debug("{} excluded by dispatcherType {}",this,baseRequest.getDispatcherType());
_handler.handle(target,baseRequest, request, response);
return;
}
// Handle request inflation // Handle request inflation
if (_inflateBufferSize>0) if (_inflateBufferSize>0)
{ {
@ -442,8 +473,8 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
} }
} }
HttpOutput out = baseRequest.getResponse().getHttpOutput();
// Are we already being gzipped? // Are we already being gzipped?
HttpOutput out = baseRequest.getResponse().getHttpOutput();
HttpOutput.Interceptor interceptor = out.getInterceptor(); HttpOutput.Interceptor interceptor = out.getInterceptor();
while (interceptor!=null) while (interceptor!=null)
{ {
@ -474,7 +505,7 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory
} }
// Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded // Exclude non compressible mime-types known from URI extension. - no Vary because no matter what client, this URI is always excluded
String mimeType = context==null?null:context.getMimeType(path); String mimeType = context==null?MimeTypes.getDefaultMimeByExtension(path):context.getMimeType(path);
if (mimeType!=null) if (mimeType!=null)
{ {
mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType); mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType);

View File

@ -278,6 +278,45 @@ public class ResponseTest
response.getWriter(); response.getWriter();
assertEquals("application/json",response.getContentType()); assertEquals("application/json",response.getContentType());
} }
@Test
public void testInferredCharset() throws Exception
{
// Inferred from encoding.properties
Response response = getResponse();
assertEquals(null, response.getContentType());
response.setHeader("Content-Type", "application/xhtml+xml");
assertEquals("application/xhtml+xml", response.getContentType());
response.getWriter();
assertEquals("application/xhtml+xml;charset=utf-8", response.getContentType());
assertEquals("utf-8", response.getCharacterEncoding());
}
@Test
public void testAssumedCharset() throws Exception
{
Response response = getResponse();
// Assumed from known types
assertEquals(null, response.getContentType());
response.setHeader("Content-Type", "text/json");
assertEquals("text/json", response.getContentType());
response.getWriter();
assertEquals("text/json", response.getContentType());
assertEquals("utf-8", response.getCharacterEncoding());
response.recycle();
// Assumed from encoding.properties
assertEquals(null, response.getContentType());
response.setHeader("Content-Type", "application/vnd.api+json");
assertEquals("application/vnd.api+json", response.getContentType());
response.getWriter();
assertEquals("application/vnd.api+json", response.getContentType());
assertEquals("utf-8", response.getCharacterEncoding());
}
@Test @Test
public void testStrangeContentType() throws Exception public void testStrangeContentType() throws Exception