From 3527c6a71b7247edc8ba92352b706679d958efd7 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Fri, 19 Jun 2015 16:48:53 +1000 Subject: StringUtil.csvSplit(String) Conflicts: jetty-maven-plugin/src/main/java/org/eclipse/jetty/maven/plugin/AbstractJettyMojo.java jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java jetty-servlets/src/main/java/org/eclipse/jetty/servlets/PushCacheFilter.java jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java --- .../java/org/eclipse/jetty/util/StringUtil.java | 183 ++++++++++++++++++++- .../org/eclipse/jetty/util/StringUtilTest.java | 33 ++++ .../jetty/util/ssl/SslContextFactoryTest.java | 6 + 3 files changed, 221 insertions(+), 1 deletion(-) (limited to 'jetty-util/src') diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index e82b8c9387..9c3315f7cc 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -21,6 +21,8 @@ package org.eclipse.jetty.util; import java.io.UnsupportedEncodingException; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -721,6 +723,11 @@ public class StringUtil return str.substring(0,maxSize); } + /** + * Parse the string representation of a list using {@link #csvSplit(List,String,int,int)} + * @param s The string to parse, expected to be enclosed as '[...]' + * @return An array of parsed values. + */ public static String[] arrayFromString(String s) { if (s==null) @@ -731,9 +738,183 @@ public class StringUtil if (s.length()==2) return new String[]{}; - return s.substring(1,s.length()-1).split(" *, *"); + return csvSplit(s,1,s.length()-2); + } + + /** + * Parse a CSV string using {@link #csvSplit(List,String, int, int)} + * @param s The string to parse + * @return An array of parsed values. + */ + public static String[] csvSplit(String s) + { + if (s==null) + return null; + return csvSplit(s,0,s.length()); } + /** + * Parse a CSV string using {@link #csvSplit(List,String, int, int)} + * @param s The string to parse + * @param off The offset into the string to start parsing + * @param len The len in characters to parse + * @return An array of parsed values. + */ + public static String[] csvSplit(String s, int off,int len) + { + if (s==null) + return null; + if (off<0 || len<0 || off>s.length()) + throw new IllegalArgumentException(); + + List list = new ArrayList<>(); + csvSplit(list,s,off,len); + return list.toArray(new String[list.size()]); + } + + enum CsvSplitState { PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA }; + + /** Split a quoted comma separated string to a list + *

Handle rfc4180-like + * CSV strings, with the exceptions:

+ * @param list The Collection to split to (or null to get a new list) + * @param s The string to parse + * @param off The offset into the string to start parsing + * @param len The len in characters to parse + * @return list containing the parsed list values + */ + public static List csvSplit(List list,String s, int off,int len) + { + if (list==null) + list=new ArrayList<>(); + CsvSplitState state = CsvSplitState.PRE_DATA; + StringBuilder out = new StringBuilder(); + int last=-1; + while (len>0) + { + char ch = s.charAt(off++); + len--; + + switch(state) + { + case PRE_DATA: + if (Character.isWhitespace(ch)) + continue; + + if ('"'==ch) + { + state=CsvSplitState.QUOTE; + continue; + } + + if (','==ch) + { + list.add(""); + continue; + } + + state=CsvSplitState.DATA; + out.append(ch); + continue; + + case DATA: + if (Character.isWhitespace(ch)) + { + last=out.length(); + out.append(ch); + state=CsvSplitState.WHITE; + continue; + } + + if (','==ch) + { + list.add(out.toString()); + out.setLength(0); + state=CsvSplitState.PRE_DATA; + continue; + } + + out.append(ch); + continue; + + case WHITE: + if (Character.isWhitespace(ch)) + { + out.append(ch); + continue; + } + + if (','==ch) + { + out.setLength(last); + list.add(out.toString()); + out.setLength(0); + state=CsvSplitState.PRE_DATA; + continue; + } + + state=CsvSplitState.DATA; + out.append(ch); + last=-1; + continue; + + case QUOTE: + if ('\\'==ch) + { + state=CsvSplitState.SLOSH; + continue; + } + if ('"'==ch) + { + list.add(out.toString()); + out.setLength(0); + state=CsvSplitState.POST_DATA; + continue; + } + out.append(ch); + continue; + + case SLOSH: + out.append(ch); + state=CsvSplitState.QUOTE; + continue; + + case POST_DATA: + if (','==ch) + { + state=CsvSplitState.PRE_DATA; + continue; + } + continue; + } + } + + switch(state) + { + case PRE_DATA: + case POST_DATA: + break; + + case DATA: + case QUOTE: + case SLOSH: + list.add(out.toString()); + break; + + case WHITE: + out.setLength(last); + list.add(out.toString()); + break; + } + + return list; + } + public static String sanitizeXmlString(String html) { if (html==null) diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java index 8c4b05f8ee..394d1f3fe7 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java @@ -18,12 +18,17 @@ package org.eclipse.jetty.util; +import static org.hamcrest.Matchers.arrayContaining; +import static org.hamcrest.Matchers.emptyArray; +import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.nio.charset.StandardCharsets; +import org.hamcrest.Matchers; import org.junit.Assert; import org.junit.Test; @@ -241,4 +246,32 @@ public class StringUtilTest assertEquals("Hello <Cruel> World",StringUtil.sanitizeXmlString("Hello World")); assertEquals("Hello ? World",StringUtil.sanitizeXmlString("Hello \u0000 World")); } + + @Test + public void testSplit() + { + assertThat(StringUtil.csvSplit(null),nullValue()); + assertThat(StringUtil.csvSplit(null),nullValue()); + + assertThat(StringUtil.csvSplit(""),emptyArray()); + assertThat(StringUtil.csvSplit(" \t\n"),emptyArray()); + + assertThat(StringUtil.csvSplit("aaa"),arrayContaining("aaa")); + assertThat(StringUtil.csvSplit(" \taaa\n"),arrayContaining("aaa")); + assertThat(StringUtil.csvSplit(" \ta\n"),arrayContaining("a")); + assertThat(StringUtil.csvSplit(" \t\u1234\n"),arrayContaining("\u1234")); + + assertThat(StringUtil.csvSplit("aaa,bbb,ccc"),arrayContaining("aaa","bbb","ccc")); + assertThat(StringUtil.csvSplit("aaa,,ccc"),arrayContaining("aaa","","ccc")); + assertThat(StringUtil.csvSplit(",b b,"),arrayContaining("","b b")); + assertThat(StringUtil.csvSplit(",,bbb,,"),arrayContaining("","","bbb","")); + + assertThat(StringUtil.csvSplit(" aaa, bbb, ccc"),arrayContaining("aaa","bbb","ccc")); + assertThat(StringUtil.csvSplit("aaa,\t,ccc"),arrayContaining("aaa","","ccc")); + assertThat(StringUtil.csvSplit(" , b b , "),arrayContaining("","b b")); + assertThat(StringUtil.csvSplit(" ,\n,bbb, , "),arrayContaining("","","bbb","")); + + assertThat(StringUtil.csvSplit("\"aaa\", \" b,\\\"\",\"\""),arrayContaining("aaa"," b,\"","")); + } + } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java index a59a4b33cd..e14fa54f9a 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java @@ -31,11 +31,14 @@ import java.security.KeyStore; import javax.net.ssl.SSLEngine; +import org.eclipse.jetty.toolchain.test.JDK; +import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StdErrLog; import org.eclipse.jetty.util.resource.Resource; import org.junit.Assert; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -208,6 +211,9 @@ public class SslContextFactoryTest @Test public void testSetIncludeCipherSuitesRegex() throws Exception { + // Test does not work on JDK 8+ (RC4 is disabled) + Assume.assumeFalse(JDK.IS_8); + cf.setIncludeCipherSuites(".*RC4.*"); cf.start(); SSLEngine sslEngine = cf.newSSLEngine(); -- cgit v1.2.3 From 5e3fbbccd0df05049f248e5ac3a9207feaeb3b29 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 8 Dec 2015 15:51:25 -0700 Subject: Backporting GzipHandler's IncludeExclude configs --- .../main/java/org/eclipse/jetty/http/PathMap.java | 50 +++ .../org/eclipse/jetty/servlets/GzipFilter.java | 9 +- .../servlets/gzip/CompressedResponseWrapper.java | 10 +- .../eclipse/jetty/servlets/gzip/GzipHandler.java | 406 +++++++++++++++------ .../org/eclipse/jetty/util/IncludeExclude.java | 136 +++++++ .../main/java/org/eclipse/jetty/util/RegexSet.java | 103 ++++++ .../org/eclipse/jetty/util/IncludeExcludeTest.java | 153 ++++++++ .../java/org/eclipse/jetty/util/RegexSetTest.java | 86 +++++ 8 files changed, 843 insertions(+), 110 deletions(-) create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java create mode 100644 jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java (limited to 'jetty-util/src') diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 2c68dcea7f..482c92b9da 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -18,14 +18,17 @@ package org.eclipse.jetty.http; +import java.util.AbstractSet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.jetty.util.ArrayTernaryTrie; +import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.URIUtil; @@ -569,4 +572,51 @@ public class PathMap extends HashMap this.mapped = mapped; } } + + public static class PathSet extends AbstractSet implements IncludeExclude.MatchSet + { + private final PathMap _map = new PathMap<>(); + + @Override + public Iterator iterator() + { + return _map.keySet().iterator(); + } + + @Override + public int size() + { + return _map.size(); + } + + @Override + public boolean add(String item) + { + return _map.put(item,Boolean.TRUE)==null; + } + + @Override + public boolean remove(Object item) + { + return _map.remove(item)!=null; + } + + @Override + public boolean contains(Object o) + { + return _map.containsKey(o); + } + + + public boolean containsMatch(String s) + { + return _map.containsMatch(s); + } + + @Override + public boolean matches(String item) + { + return _map.containsMatch(item); + } + } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java index 24738a1989..8f559ad9b1 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/GzipFilter.java @@ -45,6 +45,7 @@ import org.eclipse.jetty.servlets.gzip.AbstractCompressedStream; import org.eclipse.jetty.servlets.gzip.CompressedResponseWrapper; import org.eclipse.jetty.servlets.gzip.DeflatedOutputStream; import org.eclipse.jetty.servlets.gzip.GzipOutputStream; +import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -539,7 +540,13 @@ public class GzipFilter extends UserAgentFilter protected void configureWrappedResponse(CompressedResponseWrapper wrappedResponse) { - wrappedResponse.setMimeTypes(_mimeTypes,_excludeMimeTypes); + IncludeExclude mimeTypeExclusions = new IncludeExclude<>(); + if(_excludeMimeTypes) + mimeTypeExclusions.getExcluded().addAll(_mimeTypes); + else + mimeTypeExclusions.getIncluded().addAll(_mimeTypes); + + wrappedResponse.setMimeTypes(mimeTypeExclusions); wrappedResponse.setBufferSize(_bufferSize); wrappedResponse.setMinCompressSize(_minGzipSize); } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java index b9ad084495..fdd7fb60ea 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/CompressedResponseWrapper.java @@ -23,13 +23,13 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; -import java.util.Set; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; +import org.eclipse.jetty.util.IncludeExclude; import org.eclipse.jetty.util.StringUtil; /*------------------------------------------------------------ */ @@ -41,8 +41,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp public static final int DEFAULT_BUFFER_SIZE = 8192; public static final int DEFAULT_MIN_COMPRESS_SIZE = 256; - private Set _mimeTypes; - private boolean _excludeMimeTypes; + private IncludeExclude _mimeTypes; private int _bufferSize=DEFAULT_BUFFER_SIZE; private int _minCompressSize=DEFAULT_MIN_COMPRESS_SIZE; protected HttpServletRequest _request; @@ -95,9 +94,8 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp /* ------------------------------------------------------------ */ /** */ - public void setMimeTypes(Set mimeTypes,boolean excludeMimeTypes) + public void setMimeTypes(IncludeExclude mimeTypes) { - _excludeMimeTypes=excludeMimeTypes; _mimeTypes = mimeTypes; } @@ -138,7 +136,7 @@ public abstract class CompressedResponseWrapper extends HttpServletResponseWrapp if (colon>0) ct=ct.substring(0,colon); - if (_mimeTypes.contains(StringUtil.asciiToLowerCase(ct))==_excludeMimeTypes) + if (!_mimeTypes.matches(StringUtil.asciiToLowerCase(ct))) noCompression(); } } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java index c9976e4795..30a8d784fc 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java @@ -23,22 +23,26 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; -import java.util.HashSet; import java.util.Set; -import java.util.StringTokenizer; import java.util.zip.DeflaterOutputStream; import java.util.zip.GZIPOutputStream; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; +import org.eclipse.jetty.util.IncludeExclude; +import org.eclipse.jetty.util.RegexSet; +import org.eclipse.jetty.util.StringUtil; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -63,12 +67,17 @@ public class GzipHandler extends HandlerWrapper { private static final Logger LOG = Log.getLogger(GzipHandler.class); - final protected Set _mimeTypes=new HashSet<>(); - protected boolean _excludeMimeTypes=false; - protected Set _excludedUA; + // final protected Set _mimeTypes=new HashSet<>(); + // protected boolean _excludeMimeTypes=false; + // protected Set _excludedUA; protected int _bufferSize = 8192; protected int _minGzipSize = 256; protected String _vary = "Accept-Encoding, User-Agent"; + + private final IncludeExclude _agentPatterns=new IncludeExclude<>(RegexSet.class); + private final IncludeExclude _methods = new IncludeExclude<>(); + private final IncludeExclude _paths = new IncludeExclude<>(PathMap.PathSet.class); + private final IncludeExclude _mimeTypes = new IncludeExclude<>(); /* ------------------------------------------------------------ */ /** @@ -76,6 +85,174 @@ public class GzipHandler extends HandlerWrapper */ public GzipHandler() { + _methods.include(HttpMethod.GET.asString()); + for (String type:MimeTypes.getKnownMimeTypes()) + { + if ("image/svg+xml".equals(type)) + _paths.exclude("*.svgz"); + else if (type.startsWith("image/")|| + type.startsWith("audio/")|| + type.startsWith("video/")) + _mimeTypes.exclude(type); + } + _mimeTypes.exclude("application/compress"); + _mimeTypes.exclude("application/zip"); + _mimeTypes.exclude("application/gzip"); + _mimeTypes.exclude("application/bzip2"); + _mimeTypes.exclude("application/x-rar-compressed"); + LOG.debug("{} mime types {}",this,_mimeTypes); + + _agentPatterns.exclude(".*MSIE 6.0.*"); + } + + /* ------------------------------------------------------------ */ + /** + * @param patterns Regular expressions matching user agents to exclude + */ + public void addExcludedAgentPatterns(String... patterns) + { + _agentPatterns.exclude(patterns); + } + + /* ------------------------------------------------------------ */ + /** + * @param methods The methods to exclude in compression + */ + public void addExcludedMethods(String... methods) + { + for (String m : methods) + _methods.exclude(m); + } + + /* ------------------------------------------------------------ */ + /** + * Set the mime types. + * @param types The mime types to exclude (without charset or other parameters). + * For backward compatibility the mimetypes may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addExcludedMimeTypes(String... types) + { + for (String t : types) + _mimeTypes.exclude(StringUtil.csvSplit(t)); + } + + /* ------------------------------------------------------------ */ + /** + * @param pathspecs Path specs (as per servlet spec) to exclude. If a + * ServletContext is available, the paths are relative to the context path, + * otherwise they are absolute. + * For backward compatibility the pathspecs may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addExcludedPaths(String... pathspecs) + { + for (String p : pathspecs) + _paths.exclude(StringUtil.csvSplit(p)); + } + + /* ------------------------------------------------------------ */ + /** + * @param patterns Regular expressions matching user agents to exclude + */ + public void addIncludedAgentPatterns(String... patterns) + { + _agentPatterns.include(patterns); + } + + /* ------------------------------------------------------------ */ + /** + * @param methods The methods to include in compression + */ + public void addIncludedMethods(String... methods) + { + for (String m : methods) + _methods.include(m); + } + + /* ------------------------------------------------------------ */ + /** + * Add included mime types. Inclusion takes precedence over + * exclusion. + * @param types The mime types to include (without charset or other parameters) + * For backward compatibility the mimetypes may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addIncludedMimeTypes(String... types) + { + for (String t : types) + _mimeTypes.include(StringUtil.csvSplit(t)); + } + + /* ------------------------------------------------------------ */ + /** + * Add path specs to include. Inclusion takes precedence over exclusion. + * @param pathspecs Path specs (as per servlet spec) to include. If a + * ServletContext is available, the paths are relative to the context path, + * otherwise they are absolute + * For backward compatibility the pathspecs may be comma separated strings, but this + * will not be supported in future versions. + */ + public void addIncludedPaths(String... pathspecs) + { + for (String p : pathspecs) + _paths.include(StringUtil.csvSplit(p)); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedAgentPatterns() + { + Set excluded=_agentPatterns.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedMethods() + { + Set excluded=_methods.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedMimeTypes() + { + Set excluded=_mimeTypes.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getExcludedPaths() + { + Set excluded=_paths.getExcluded(); + return excluded.toArray(new String[excluded.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedAgentPatterns() + { + Set includes=_agentPatterns.getIncluded(); + return includes.toArray(new String[includes.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedMethods() + { + Set includes=_methods.getIncluded(); + return includes.toArray(new String[includes.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedMimeTypes() + { + Set includes=_mimeTypes.getIncluded(); + return includes.toArray(new String[includes.size()]); + } + + /* ------------------------------------------------------------ */ + public String[] getIncludedPaths() + { + Set includes=_paths.getIncluded(); + return includes.toArray(new String[includes.size()]); } /* ------------------------------------------------------------ */ @@ -84,9 +261,10 @@ public class GzipHandler extends HandlerWrapper * * @return mime types to set */ + @Deprecated public Set getMimeTypes() { - return _mimeTypes; + throw new UnsupportedOperationException("Use getIncludedMimeTypes or getExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -96,11 +274,10 @@ public class GzipHandler extends HandlerWrapper * @param mimeTypes * the mime types to set */ + @Deprecated public void setMimeTypes(Set mimeTypes) { - _excludeMimeTypes=false; - _mimeTypes.clear(); - _mimeTypes.addAll(mimeTypes); + throw new UnsupportedOperationException("Use setIncludedMimeTypes or setExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -110,18 +287,10 @@ public class GzipHandler extends HandlerWrapper * @param mimeTypes * the mime types to set */ + @Deprecated public void setMimeTypes(String mimeTypes) { - if (mimeTypes != null) - { - _excludeMimeTypes=false; - _mimeTypes.clear(); - StringTokenizer tok = new StringTokenizer(mimeTypes,",",false); - while (tok.hasMoreTokens()) - { - _mimeTypes.add(tok.nextToken()); - } - } + throw new UnsupportedOperationException("Use setIncludedMimeTypes or setExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -130,7 +299,7 @@ public class GzipHandler extends HandlerWrapper */ public void setExcludeMimeTypes(boolean exclude) { - _excludeMimeTypes=exclude; + throw new UnsupportedOperationException("Use setExcludedMimeTypes instead"); } /* ------------------------------------------------------------ */ @@ -141,7 +310,7 @@ public class GzipHandler extends HandlerWrapper */ public Set getExcluded() { - return _excludedUA; + return _agentPatterns.getExcluded(); } /* ------------------------------------------------------------ */ @@ -153,7 +322,8 @@ public class GzipHandler extends HandlerWrapper */ public void setExcluded(Set excluded) { - _excludedUA = excluded; + _agentPatterns.getExcluded().clear(); + _agentPatterns.getExcluded().addAll(excluded); } /* ------------------------------------------------------------ */ @@ -165,12 +335,11 @@ public class GzipHandler extends HandlerWrapper */ public void setExcluded(String excluded) { + _agentPatterns.getExcluded().clear(); + if (excluded != null) { - _excludedUA = new HashSet(); - StringTokenizer tok = new StringTokenizer(excluded,",",false); - while (tok.hasMoreTokens()) - _excludedUA.add(tok.nextToken()); + _agentPatterns.exclude(StringUtil.csvSplit(excluded)); } } @@ -249,22 +418,6 @@ public class GzipHandler extends HandlerWrapper @Override protected void doStart() throws Exception { - if (_mimeTypes.size()==0 && _excludeMimeTypes) - { - _excludeMimeTypes = true; - for (String type:MimeTypes.getKnownMimeTypes()) - { - if (type.equals("image/svg+xml")) //always compressable (unless .svgz file) - continue; - if (type.startsWith("image/")|| - type.startsWith("audio/")|| - type.startsWith("video/")) - _mimeTypes.add(type); - } - _mimeTypes.add("application/compress"); - _mimeTypes.add("application/zip"); - _mimeTypes.add("application/gzip"); - } super.doStart(); } @@ -275,80 +428,127 @@ public class GzipHandler extends HandlerWrapper @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { - if (_handler!=null && isStarted()) + if(_handler == null || !isStarted()) { - String ae = request.getHeader("accept-encoding"); - if (ae != null && ae.indexOf("gzip")>=0 && !response.containsHeader("Content-Encoding") - && !HttpMethod.HEAD.is(request.getMethod())) - { - if (_excludedUA!=null) - { - String ua = request.getHeader("User-Agent"); - if (_excludedUA.contains(ua)) - { - _handler.handle(target,baseRequest, request, response); - return; - } - } - - final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response); + // do nothing + return; + } + + if(isGzippable(baseRequest, request, response)) + { + final CompressedResponseWrapper wrappedResponse = newGzipResponseWrapper(request,response); - boolean exceptional=true; - try - { - _handler.handle(target, baseRequest, request, wrappedResponse); - exceptional=false; - } - finally + boolean exceptional=true; + try + { + _handler.handle(target, baseRequest, request, wrappedResponse); + exceptional=false; + } + finally + { + if (request.isAsyncStarted()) { - if (request.isAsyncStarted()) + request.getAsyncContext().addListener(new AsyncListener() { - request.getAsyncContext().addListener(new AsyncListener() + + @Override + public void onTimeout(AsyncEvent event) throws IOException { - - @Override - public void onTimeout(AsyncEvent event) throws IOException - { - } - - @Override - public void onStartAsync(AsyncEvent event) throws IOException - { - } - - @Override - public void onError(AsyncEvent event) throws IOException + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException + { + } + + @Override + public void onError(AsyncEvent event) throws IOException + { + } + + @Override + public void onComplete(AsyncEvent event) throws IOException + { + try { + wrappedResponse.finish(); } - - @Override - public void onComplete(AsyncEvent event) throws IOException + catch(IOException e) { - try - { - wrappedResponse.finish(); - } - catch(IOException e) - { - LOG.warn(e); - } + LOG.warn(e); } - }); - } - else if (exceptional && !response.isCommitted()) - { - wrappedResponse.resetBuffer(); - wrappedResponse.noCompression(); - } - else - wrappedResponse.finish(); + } + }); + } + else if (exceptional && !response.isCommitted()) + { + wrappedResponse.resetBuffer(); + wrappedResponse.noCompression(); } + else + wrappedResponse.finish(); } - else + } + else + { + _handler.handle(target,baseRequest, request, response); + } + } + + private boolean isGzippable(Request baseRequest, HttpServletRequest request, HttpServletResponse response) + { + String ae = request.getHeader("accept-encoding"); + if (ae == null || !ae.contains("gzip")) + { + // Request not indicated for Gzip + return false; + } + + if(response.containsHeader("Content-Encoding")) + { + // Response is already declared, can't gzip + LOG.debug("{} excluded as Content-Encoding already declared {}",this,request); + return false; + } + + if(HttpMethod.HEAD.is(request.getMethod())) + { + // HEAD is never Gzip'd + LOG.debug("{} excluded by method {}",this,request); + return false; + } + + // Exclude based on Request Method + if (!_methods.matches(baseRequest.getMethod())) + { + LOG.debug("{} excluded by method {}",this,request); + return false; + } + + // Exclude based on Request Path + ServletContext context = baseRequest.getServletContext(); + String path = context==null?baseRequest.getRequestURI():URIUtil.addPaths(baseRequest.getServletPath(),baseRequest.getPathInfo()); + + if(path != null && !_paths.matches(path)) + { + LOG.debug("{} excluded by path {}",this,request); + return false; + } + + + // 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); + if (mimeType!=null) + { + mimeType = MimeTypes.getContentTypeWithoutCharset(mimeType); + if (!_mimeTypes.matches(mimeType)) { - _handler.handle(target,baseRequest, request, response); + LOG.debug("{} excluded by path suffix mime type {}",this,request); + return false; } } + + return true; } /** @@ -363,7 +563,7 @@ public class GzipHandler extends HandlerWrapper return new CompressedResponseWrapper(request,response) { { - super.setMimeTypes(GzipHandler.this._mimeTypes,GzipHandler.this._excludeMimeTypes); + super.setMimeTypes(GzipHandler.this._mimeTypes); super.setBufferSize(GzipHandler.this._bufferSize); super.setMinCompressSize(GzipHandler.this._minGzipSize); } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java new file mode 100644 index 0000000000..b5af37356e --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java @@ -0,0 +1,136 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import java.util.HashSet; +import java.util.Set; + + +/** Utility class to maintain a set of inclusions and exclusions. + *

Maintains a set of included and excluded elements. The method {@link #matches(Object)} + * will return true IFF the passed object is not in the excluded set AND ( either the + * included set is empty OR the object is in the included set) + *

The type of the underlying {@link Set} used may be passed into the + * constructor, so special sets like Servlet PathMap may be used. + *

+ * @param The type of element + */ +public class IncludeExclude +{ + public interface MatchSet extends Set + { + public boolean matches(ITEM item); + } + + @SuppressWarnings("serial") + protected static class ContainsMatchSet extends HashSet implements MatchSet + { + @Override + public boolean matches(ITEM item) + { + return contains(item); + } + } + + private final MatchSet _includes; + private final MatchSet _excludes; + + /** + * Default constructor over {@link HashSet} + */ + public IncludeExclude() + { + _includes = new ContainsMatchSet(); + _excludes = new ContainsMatchSet(); + } + + /** + * Construct an IncludeExclude + * @param setClass The type of {@link Set} to using internally + * @param matcher A function to test if a passed ITEM is matched by the passed SET, or null to use {@link Set#contains(Object)} + */ + public IncludeExclude(Class> setClass) + { + try + { + _includes = setClass.newInstance(); + _excludes = setClass.newInstance(); + } + catch (InstantiationException | IllegalAccessException e) + { + throw new RuntimeException(e); + } + } + + public void include(ITEM element) + { + _includes.add(element); + } + + public void include(ITEM... element) + { + for (ITEM e: element) + _includes.add(e); + } + + public void exclude(ITEM element) + { + _excludes.add(element); + } + + public void exclude(ITEM... element) + { + for (ITEM e: element) + _excludes.add(e); + } + + public boolean matches(ITEM e) + { + if (_includes.size()>0 && !_includes.matches(e)) + return false; + return !_excludes.matches(e); + } + + public int size() + { + return _includes.size()+_excludes.size(); + } + + public Set getIncluded() + { + return _includes; + } + + public Set getExcluded() + { + return _excludes; + } + + public void clear() + { + _includes.clear(); + _excludes.clear(); + } + + @Override + public String toString() + { + return String.format("%s@%x{i=%s,e=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_excludes); + } +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java new file mode 100644 index 0000000000..288e5154f2 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java @@ -0,0 +1,103 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import java.util.AbstractSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.regex.Pattern; + +/** + * A Set of Regular expressions strings. + *

+ * Provides the efficient {@link #matches(String)} method to check for a match against all the combined Regex's + */ +public class RegexSet extends AbstractSet implements IncludeExclude.MatchSet +{ + private final Set _patterns=new HashSet(); + private final Set _unmodifiable=Collections.unmodifiableSet(_patterns); + private Pattern _pattern; + + @Override + public Iterator iterator() + { + return _unmodifiable.iterator(); + } + + @Override + public int size() + { + return _patterns.size(); + } + + @Override + public boolean add(String pattern) + { + boolean added = _patterns.add(pattern); + if (added) + updatePattern(); + return added; + } + + @Override + public boolean remove(Object pattern) + { + boolean removed = _patterns.remove(pattern); + + if (removed) + updatePattern(); + return removed; + } + + @Override + public boolean isEmpty() + { + return _patterns.isEmpty(); + } + + @Override + public void clear() + { + _patterns.clear(); + _pattern=null; + } + + private void updatePattern() + { + StringBuilder builder = new StringBuilder(); + builder.append("^("); + for (String pattern: _patterns) + { + if (builder.length()>2) + builder.append('|'); + builder.append('('); + builder.append(pattern); + builder.append(')'); + } + builder.append(")$"); + _pattern = Pattern.compile(builder.toString()); + } + + public boolean matches(String s) + { + return _pattern!=null && _pattern.matcher(s).matches(); + } +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java new file mode 100644 index 0000000000..c2b4d61559 --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java @@ -0,0 +1,153 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IncludeExcludeTest +{ + @Test + public void testEmpty() + { + IncludeExclude ie = new IncludeExclude<>(); + + assertEquals(0,ie.size()); + assertEquals(true,ie.matches("foo")); + } + + @Test + public void testIncludeOnly() + { + IncludeExclude ie = new IncludeExclude<>(); + ie.include("foo"); + ie.include("bar"); + + assertEquals(2,ie.size()); + assertEquals(false,ie.matches("")); + assertEquals(true,ie.matches("foo")); + assertEquals(true,ie.matches("bar")); + assertEquals(false,ie.matches("foobar")); + } + + @Test + public void testExcludeOnly() + { + IncludeExclude ie = new IncludeExclude<>(); + ie.exclude("foo"); + ie.exclude("bar"); + + assertEquals(2,ie.size()); + + assertEquals(false,ie.matches("foo")); + assertEquals(false,ie.matches("bar")); + assertEquals(true,ie.matches("")); + assertEquals(true,ie.matches("foobar")); + assertEquals(true,ie.matches("wibble")); + } + + @Test + public void testIncludeExclude() + { + IncludeExclude ie = new IncludeExclude<>(); + ie.include("foo"); + ie.include("bar"); + ie.exclude("bar"); + ie.exclude("xxx"); + + assertEquals(4,ie.size()); + + assertEquals(true,ie.matches("foo")); + assertEquals(false,ie.matches("bar")); + assertEquals(false,ie.matches("")); + assertEquals(false,ie.matches("foobar")); + assertEquals(false,ie.matches("xxx")); + } + + + + @Test + public void testEmptyRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + + assertEquals(0,ie.size()); + assertEquals(true,ie.matches("foo")); + } + + @Test + public void testIncludeRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + ie.include("f.."); + ie.include("b((ar)|(oo))"); + + assertEquals(2,ie.size()); + assertEquals(false,ie.matches("")); + assertEquals(true,ie.matches("foo")); + assertEquals(true,ie.matches("far")); + assertEquals(true,ie.matches("bar")); + assertEquals(true,ie.matches("boo")); + assertEquals(false,ie.matches("foobar")); + assertEquals(false,ie.matches("xxx")); + } + + @Test + public void testExcludeRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + ie.exclude("f.."); + ie.exclude("b((ar)|(oo))"); + + assertEquals(2,ie.size()); + + assertEquals(false,ie.matches("foo")); + assertEquals(false,ie.matches("far")); + assertEquals(false,ie.matches("bar")); + assertEquals(false,ie.matches("boo")); + assertEquals(true,ie.matches("")); + assertEquals(true,ie.matches("foobar")); + assertEquals(true,ie.matches("xxx")); + } + + @Test + public void testIncludeExcludeRegex() + { + IncludeExclude ie = new IncludeExclude<>(RegexSet.class); + ie.include(".*[aeiou].*"); + ie.include("[AEIOU].*"); + ie.exclude("f.."); + ie.exclude("b((ar)|(oo))"); + + assertEquals(4,ie.size()); + assertEquals(false,ie.matches("foo")); + assertEquals(false,ie.matches("far")); + assertEquals(false,ie.matches("bar")); + assertEquals(false,ie.matches("boo")); + assertEquals(false,ie.matches("")); + assertEquals(false,ie.matches("xxx")); + + assertEquals(true,ie.matches("foobar")); + assertEquals(true,ie.matches("Ant")); + + } + + +} diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java new file mode 100644 index 0000000000..f80c2eda5c --- /dev/null +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/RegexSetTest.java @@ -0,0 +1,86 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +import org.junit.Test; + +import org.junit.Assert; + +public class RegexSetTest +{ + + @Test + public void testEmpty() + { + RegexSet set = new RegexSet(); + + Assert.assertEquals(false,set.contains("foo")); + Assert.assertEquals(false,set.matches("foo")); + Assert.assertEquals(false,set.matches("")); + + } + + @Test + public void testSimple() + { + RegexSet set = new RegexSet(); + set.add("foo.*"); + + Assert.assertEquals(true,set.contains("foo.*")); + Assert.assertEquals(true,set.matches("foo")); + Assert.assertEquals(true,set.matches("foobar")); + Assert.assertEquals(false,set.matches("bar")); + Assert.assertEquals(false,set.matches("")); + + } + + @Test + public void testSimpleTerminated() + { + RegexSet set = new RegexSet(); + set.add("^foo.*$"); + + Assert.assertEquals(true,set.contains("^foo.*$")); + Assert.assertEquals(true,set.matches("foo")); + Assert.assertEquals(true,set.matches("foobar")); + Assert.assertEquals(false,set.matches("bar")); + Assert.assertEquals(false,set.matches("")); + } + + @Test + public void testCombined() + { + RegexSet set = new RegexSet(); + set.add("^foo.*$"); + set.add("bar"); + set.add("[a-z][0-9][a-z][0-9]"); + + Assert.assertEquals(true,set.contains("^foo.*$")); + Assert.assertEquals(true,set.matches("foo")); + Assert.assertEquals(true,set.matches("foobar")); + Assert.assertEquals(true,set.matches("bar")); + Assert.assertEquals(true,set.matches("c3p0")); + Assert.assertEquals(true,set.matches("r2d2")); + + Assert.assertEquals(false,set.matches("wibble")); + Assert.assertEquals(false,set.matches("barfoo")); + Assert.assertEquals(false,set.matches("2b!b")); + Assert.assertEquals(false,set.matches("")); + } +} -- cgit v1.2.3 From 6e0ad429d9f8bbf2124ee0581ce9a2394222bf96 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Mon, 14 Dec 2015 14:58:12 -0700 Subject: 484350 - Allow GzipHandler path include/exclude to use regex + Overhauled IncludeExclude to use java 8 predicate + Introduced PathSpecSet to standardize path IncludeExclude + GzipHandler now uses PathSpecSet for paths Conflicts: jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java --- .../main/java/org/eclipse/jetty/http/PathMap.java | 10 +- .../eclipse/jetty/http/pathmap/PathSpecSet.java | 223 +++++++++++++++++++++ .../eclipse/jetty/servlets/gzip/GzipHandler.java | 45 ++++- .../jetty/servlets/gzip/GzipHandlerTest.java | 16 ++ .../org/eclipse/jetty/util/IncludeExclude.java | 70 +++++-- .../java/org/eclipse/jetty/util/Predicate.java | 31 +++ .../main/java/org/eclipse/jetty/util/RegexSet.java | 8 +- .../org/eclipse/jetty/util/IncludeExcludeTest.java | 11 +- 8 files changed, 378 insertions(+), 36 deletions(-) create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java create mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java (limited to 'jetty-util/src') diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 482c92b9da..807d5966e1 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -29,6 +29,7 @@ import java.util.StringTokenizer; import org.eclipse.jetty.util.ArrayTernaryTrie; import org.eclipse.jetty.util.IncludeExclude; +import org.eclipse.jetty.util.Predicate; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.URIUtil; @@ -573,7 +574,7 @@ public class PathMap extends HashMap } } - public static class PathSet extends AbstractSet implements IncludeExclude.MatchSet + public static class PathSet extends AbstractSet implements Predicate { private final PathMap _map = new PathMap<>(); @@ -607,13 +608,16 @@ public class PathMap extends HashMap return _map.containsKey(o); } + @Override + public boolean test(String s) + { + return _map.containsMatch(s); + } public boolean containsMatch(String s) { return _map.containsMatch(s); } - - @Override public boolean matches(String item) { return _map.containsMatch(item); diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java new file mode 100644 index 0000000000..330b55ab25 --- /dev/null +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java @@ -0,0 +1,223 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.pathmap; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +import org.eclipse.jetty.util.Predicate; + +/** + * A Set of PathSpec strings. + *

+ * Used by {@link org.eclipse.jetty.util.IncludeExclude} logic + */ +public class PathSpecSet implements Set, Predicate +{ + private final Set specs = new TreeSet<>(); + + @Override + public boolean test(String s) + { + for (PathSpec spec : specs) + { + if (spec.matches(s)) + { + return true; + } + } + return false; + } + + @Override + public boolean isEmpty() + { + return specs.isEmpty(); + } + + @Override + public Iterator iterator() + { + return new Iterator() + { + private Iterator iter = specs.iterator(); + + @Override + public boolean hasNext() + { + return iter.hasNext(); + } + + @Override + public String next() + { + PathSpec spec = iter.next(); + if (spec == null) + { + return null; + } + return spec.getDeclaration(); + } + + @Override + public void remove() + { + throw new UnsupportedOperationException("Remove not supported by this Iterator"); + } + }; + } + + @Override + public int size() + { + return specs.size(); + } + + @Override + public boolean contains(Object o) + { + if (o instanceof PathSpec) + { + return specs.contains(o); + } + if (o instanceof String) + { + return specs.contains(toPathSpec((String)o)); + } + return false; + } + + private PathSpec asPathSpec(Object o) + { + if (o == null) + { + return null; + } + if (o instanceof PathSpec) + { + return (PathSpec)o; + } + if (o instanceof String) + { + return toPathSpec((String)o); + } + return toPathSpec(o.toString()); + } + + private PathSpec toPathSpec(String rawSpec) + { + if ((rawSpec == null) || (rawSpec.length() < 1)) + { + throw new RuntimeException("Path Spec String must start with '^', '/', or '*.': got [" + rawSpec + "]"); + } + if (rawSpec.charAt(0) == '^') + { + return new RegexPathSpec(rawSpec); + } + else + { + return new ServletPathSpec(rawSpec); + } + } + + @Override + public Object[] toArray() + { + return toArray(new String[specs.size()]); + } + + @Override + public T[] toArray(T[] a) + { + int i = 0; + for (PathSpec spec : specs) + { + a[i++] = (T)spec.getDeclaration(); + } + return a; + } + + @Override + public boolean add(String e) + { + return specs.add(toPathSpec(e)); + } + + @Override + public boolean remove(Object o) + { + return specs.remove(asPathSpec(o)); + } + + @Override + public boolean containsAll(Collection coll) + { + for (Object o : coll) + { + if (!specs.contains(asPathSpec(o))) + return false; + } + return true; + } + + @Override + public boolean addAll(Collection coll) + { + boolean ret = false; + + for (String s : coll) + { + ret |= add(s); + } + + return ret; + } + + @Override + public boolean retainAll(Collection coll) + { + List collSpecs = new ArrayList<>(); + for (Object o : coll) + { + collSpecs.add(asPathSpec(o)); + } + return specs.retainAll(collSpecs); + } + + @Override + public boolean removeAll(Collection coll) + { + List collSpecs = new ArrayList<>(); + for (Object o : coll) + { + collSpecs.add(asPathSpec(o)); + } + return specs.removeAll(collSpecs); + } + + @Override + public void clear() + { + specs.clear(); + } +} diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java index 7a843a5879..f1924e0d6c 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/gzip/GzipHandler.java @@ -36,7 +36,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.MimeTypes; -import org.eclipse.jetty.http.PathMap; +import org.eclipse.jetty.http.pathmap.PathSpecSet; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.util.IncludeExclude; @@ -73,7 +73,7 @@ public class GzipHandler extends HandlerWrapper private final IncludeExclude _agentPatterns=new IncludeExclude<>(RegexSet.class); private final IncludeExclude _methods = new IncludeExclude<>(); - private final IncludeExclude _paths = new IncludeExclude<>(PathMap.PathSet.class); + private final IncludeExclude _paths = new IncludeExclude(PathSpecSet.class); private final IncludeExclude _mimeTypes = new IncludeExclude<>(); /* ------------------------------------------------------------ */ @@ -136,9 +136,27 @@ public class GzipHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** + * Add path to excluded paths list. + *

+ * There are 2 syntaxes supported, Servlet url-pattern based, and + * Regex based. This means that the initial characters on the path spec + * line are very strict, and determine the behavior of the path matching. + *

    + *
  • If the spec starts with '^' the spec is assumed to be + * a regex based path spec and will match with normal Java regex rules.
  • + *
  • If the spec starts with '/' then spec is assumed to be + * a Servlet url-pattern rules path spec for either an exact match + * or prefix based match.
  • + *
  • If the spec starts with '*.' then spec is assumed to be + * a Servlet url-pattern rules path spec for a suffix based match.
  • + *
  • All other syntaxes are unsupported
  • + *
+ *

+ * Note: inclusion takes precedence over exclude. + * * @param pathspecs Path specs (as per servlet spec) to exclude. If a * ServletContext is available, the paths are relative to the context path, - * otherwise they are absolute. + * otherwise they are absolute.
* For backward compatibility the pathspecs may be comma separated strings, but this * will not be supported in future versions. */ @@ -183,12 +201,27 @@ public class GzipHandler extends HandlerWrapper /* ------------------------------------------------------------ */ /** - * Add path specs to include. Inclusion takes precedence over exclusion. + * Add path specs to include. + *

+ * There are 2 syntaxes supported, Servlet url-pattern based, and + * Regex based. This means that the initial characters on the path spec + * line are very strict, and determine the behavior of the path matching. + *

    + *
  • If the spec starts with '^' the spec is assumed to be + * a regex based path spec and will match with normal Java regex rules.
  • + *
  • If the spec starts with '/' then spec is assumed to be + * a Servlet url-pattern rules path spec for either an exact match + * or prefix based match.
  • + *
  • If the spec starts with '*.' then spec is assumed to be + * a Servlet url-pattern rules path spec for a suffix based match.
  • + *
  • All other syntaxes are unsupported
  • + *
+ *

+ * Note: inclusion takes precedence over exclude. + * * @param pathspecs Path specs (as per servlet spec) to include. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute - * For backward compatibility the pathspecs may be comma separated strings, but this - * will not be supported in future versions. */ public void addIncludedPaths(String... pathspecs) { diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java index 1982a1c278..2d7183c742 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/gzip/GzipHandlerTest.java @@ -18,7 +18,10 @@ package org.eclipse.jetty.servlets.gzip; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import java.io.ByteArrayInputStream; @@ -26,6 +29,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.util.Arrays; import java.util.zip.GZIPInputStream; import javax.servlet.ServletException; @@ -204,4 +208,16 @@ public class GzipHandlerTest assertEquals(__icontent, testOut.toString("UTF8")); } + + @Test + public void testAddGetPaths() + { + GzipHandler gzip = new GzipHandler(); + gzip.addIncludedPaths("/foo"); + gzip.addIncludedPaths("^/bar.*$"); + + String[] includedPaths = gzip.getIncludedPaths(); + assertThat("Included Paths.size", includedPaths.length, is(2)); + assertThat("Included Paths", Arrays.asList(includedPaths), contains("/foo","^/bar.*$")); + } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java index b5af37356e..96a8791382 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java @@ -33,51 +33,81 @@ import java.util.Set; */ public class IncludeExclude { - public interface MatchSet extends Set - { - public boolean matches(ITEM item); - } + private final Set _includes; + private final Predicate _includePredicate; + private final Set _excludes; + private final Predicate _excludePredicate; - @SuppressWarnings("serial") - protected static class ContainsMatchSet extends HashSet implements MatchSet + private static class SetContainsPredicate implements Predicate { + private final Set set; + + public SetContainsPredicate(Set set) + { + this.set = set; + } + @Override - public boolean matches(ITEM item) + public boolean test(ITEM item) { - return contains(item); + return set.contains(item); } } - private final MatchSet _includes; - private final MatchSet _excludes; - /** * Default constructor over {@link HashSet} */ public IncludeExclude() { - _includes = new ContainsMatchSet(); - _excludes = new ContainsMatchSet(); + this(HashSet.class); } /** * Construct an IncludeExclude * @param setClass The type of {@link Set} to using internally - * @param matcher A function to test if a passed ITEM is matched by the passed SET, or null to use {@link Set#contains(Object)} + * @param predicate A predicate function to test if a passed ITEM is matched by the passed SET} */ - public IncludeExclude(Class> setClass) + public > IncludeExclude(Class setClass) { try { _includes = setClass.newInstance(); _excludes = setClass.newInstance(); + + if(_includes instanceof Predicate) { + _includePredicate = (Predicate)_includes; + } else { + _includePredicate = new SetContainsPredicate<>(_includes); + } + + if(_excludes instanceof Predicate) { + _excludePredicate = (Predicate)_excludes; + } else { + _excludePredicate = new SetContainsPredicate<>(_excludes); + } } catch (InstantiationException | IllegalAccessException e) { throw new RuntimeException(e); } } - + + /** + * Construct an IncludeExclude + * + * @param includeSet the Set of items that represent the included space + * @param includePredicate the Predicate for included item testing (null for simple {@link Set#contains(Object)} test) + * @param excludeSet the Set of items that represent the excluded space + * @param excludePredicate the Predicate for excluded item testing (null for simple {@link Set#contains(Object)} test) + */ + public > IncludeExclude(Set includeSet, Predicate includePredicate, Set excludeSet, Predicate excludePredicate) + { + _includes = includeSet; + _includePredicate = includePredicate; + _excludes = excludeSet; + _excludePredicate = excludePredicate; + } + public void include(ITEM element) { _includes.add(element); @@ -102,11 +132,11 @@ public class IncludeExclude public boolean matches(ITEM e) { - if (_includes.size()>0 && !_includes.matches(e)) + if (!_includes.isEmpty() && !_includePredicate.test(e)) return false; - return !_excludes.matches(e); + return !_excludePredicate.test(e); } - + public int size() { return _includes.size()+_excludes.size(); @@ -131,6 +161,6 @@ public class IncludeExclude @Override public String toString() { - return String.format("%s@%x{i=%s,e=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_excludes); + return String.format("%s@%x{i=%s,ip=%s,e=%s,ep=%s}",this.getClass().getSimpleName(),hashCode(),_includes,_includePredicate,_excludes,_excludePredicate); } } diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java new file mode 100644 index 0000000000..33a8eb41a8 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java @@ -0,0 +1,31 @@ +// +// ======================================================================== +// Copyright (c) 1995-2015 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.util; + +/** + * Temporary implementation of Java 8's java.util.function.Predicate + *

+ * To be removed for Java 8 only versions of Jetty. + * + * @param the item to test + */ +public interface Predicate +{ + boolean test(ITEM item); +} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java index 288e5154f2..3f738d226c 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java @@ -30,7 +30,7 @@ import java.util.regex.Pattern; *

* Provides the efficient {@link #matches(String)} method to check for a match against all the combined Regex's */ -public class RegexSet extends AbstractSet implements IncludeExclude.MatchSet +public class RegexSet extends AbstractSet implements Predicate { private final Set _patterns=new HashSet(); private final Set _unmodifiable=Collections.unmodifiableSet(_patterns); @@ -95,6 +95,12 @@ public class RegexSet extends AbstractSet implements IncludeExclude.Matc builder.append(")$"); _pattern = Pattern.compile(builder.toString()); } + + @Override + public boolean test(String s) + { + return _pattern!=null && _pattern.matcher(s).matches(); + } public boolean matches(String s) { diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java index c2b4d61559..4b7dde734e 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/IncludeExcludeTest.java @@ -20,7 +20,9 @@ package org.eclipse.jetty.util; import org.junit.Test; +import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; public class IncludeExcludeTest { @@ -29,8 +31,8 @@ public class IncludeExcludeTest { IncludeExclude ie = new IncludeExclude<>(); - assertEquals(0,ie.size()); - assertEquals(true,ie.matches("foo")); + assertThat("Empty IncludeExclude", ie.size(), is(0)); + assertThat("Matches 'foo'",ie.matches("foo"),is(true)); } @Test @@ -40,7 +42,7 @@ public class IncludeExcludeTest ie.include("foo"); ie.include("bar"); - assertEquals(2,ie.size()); + assertThat("IncludeExclude.size", ie.size(), is(2)); assertEquals(false,ie.matches("")); assertEquals(true,ie.matches("foo")); assertEquals(true,ie.matches("bar")); @@ -146,8 +148,5 @@ public class IncludeExcludeTest assertEquals(true,ie.matches("foobar")); assertEquals(true,ie.matches("Ant")); - } - - } -- cgit v1.2.3 From 7c5bec1b48db95854965c13c203a0e23373c88d6 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Thu, 17 Dec 2015 10:15:57 -0700 Subject: Using Java 8 Predicate Function + Removing jetty-util (java 7) holdover Predicate.java + Converting use to (java 8) java.util.function.Predicate --- .../main/java/org/eclipse/jetty/http/PathMap.java | 4 +-- .../eclipse/jetty/http/pathmap/PathSpecSet.java | 3 +-- .../org/eclipse/jetty/util/IncludeExclude.java | 1 + .../java/org/eclipse/jetty/util/Predicate.java | 31 ---------------------- .../main/java/org/eclipse/jetty/util/RegexSet.java | 1 + 5 files changed, 4 insertions(+), 36 deletions(-) delete mode 100644 jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java (limited to 'jetty-util/src') diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java index 41cc11f1d8..457679434c 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/PathMap.java @@ -26,11 +26,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; -import java.util.function.BiFunction; +import java.util.function.Predicate; import org.eclipse.jetty.util.ArrayTernaryTrie; -import org.eclipse.jetty.util.IncludeExclude; -import org.eclipse.jetty.util.Predicate; import org.eclipse.jetty.util.Trie; import org.eclipse.jetty.util.URIUtil; diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java index 330b55ab25..75fc421403 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java @@ -24,8 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; - -import org.eclipse.jetty.util.Predicate; +import java.util.function.Predicate; /** * A Set of PathSpec strings. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java index 96a8791382..39d80e2291 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExclude.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.util; import java.util.HashSet; import java.util.Set; +import java.util.function.Predicate; /** Utility class to maintain a set of inclusions and exclusions. diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java deleted file mode 100644 index 33a8eb41a8..0000000000 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Predicate.java +++ /dev/null @@ -1,31 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2015 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.util; - -/** - * Temporary implementation of Java 8's java.util.function.Predicate - *

- * To be removed for Java 8 only versions of Jetty. - * - * @param the item to test - */ -public interface Predicate -{ - boolean test(ITEM item); -} diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java index 3f738d226c..ca5a0305d8 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java @@ -23,6 +23,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import java.util.function.Predicate; import java.util.regex.Pattern; /** -- cgit v1.2.3