diff --git a/CVE-2020-27216.patch b/CVE-2020-27216.patch old mode 100644 new mode 100755 index d58e913f7fcb5c0fdf1ba2492e33591ca933396d..6b8051ab615f29ede5f197fa434ccca0d8af871b --- a/CVE-2020-27216.patch +++ b/CVE-2020-27216.patch @@ -1,22 +1,17 @@ -From 53e0e0e9b25a6309bf24ee3b10984f4145701edb Mon Sep 17 00:00:00 2001 -From: Joakim Erdfelt -Date: Thu, 15 Oct 2020 17:39:30 -0500 -Subject: [PATCH] Merge pull request from GHSA-g3wg-6mcf-8jj6 +From: Markus Koschany +Date: Sat, 3 Jul 2021 22:05:57 +0200 +Subject: CVE-2020-27216 -* Issue #5451 - Improving temp directory creation. - -+ Using new Files.createTempDirectory() instead - of nonsense around File.createTempFile() - -Signed-off-by: Joakim Erdfelt - -* Fixes #5451 - Restoring File.deleteOnExit +Origin: https://github.com/eclipse/jetty.project/commit/53e0e0e9b25a6309bf24ee3b10984f4145701edb +Origin: https://github.com/eclipse/jetty.project/commit/9ad6beb80543b392c91653f6bfce233fc75b9d5f --- - .../jetty/webapp/WebInfConfiguration.java | 20 +++++++------------ - 1 file changed, 7 insertions(+), 13 deletions(-) + .../eclipse/jetty/webapp/WebInfConfiguration.java | 20 +-- + .../server/session/InfinispanTestSupport.java | 173 +++++++++++++-------- + .../test/java/org/eclipse/jetty/TestServer.java | 54 +++---- + 3 files changed, 135 insertions(+), 112 deletions(-) diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java -index b94f788..f39432d 100644 +index b94f788..19663bc 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java @@ -24,6 +24,8 @@ import java.net.URI; @@ -41,9 +36,9 @@ index b94f788..f39432d 100644 - tmpDir.deleteOnExit(); - context.setTempDirectory(tmpDir); + Path tmpDir = Files.createTempDirectory(template.getTempDirectory().getParentFile().toPath(), WebInfConfiguration.getCanonicalNameForWebAppTmpDir(context)); -+ File tmpDirAsFile = tmpDir.toFile(); -+ tmpDirAsFile.deleteOnExit(); -+ context.setTempDirectory(tmpDirAsFile); ++ File tmpDirAsFile = tmpDir.toFile(); ++ tmpDirAsFile.deleteOnExit(); ++ context.setTempDirectory(tmpDirAsFile); } @@ -60,6 +55,423 @@ index b94f788..f39432d 100644 } configureTempDirectory(tmpDir, context); --- -2.23.0 - +diff --git a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSupport.java b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSupport.java +index 57ecb1f..663e8dd 100644 +--- a/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSupport.java ++++ b/tests/test-sessions/test-infinispan-sessions/src/test/java/org/eclipse/jetty/server/session/InfinispanTestSupport.java +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -16,38 +16,44 @@ + // ======================================================================== + // + +- + package org.eclipse.jetty.server.session; + +-import static org.junit.jupiter.api.Assertions.assertEquals; +-import static org.junit.jupiter.api.Assertions.assertTrue; +- + import java.io.File; ++import java.lang.annotation.ElementType; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.Properties; + ++import org.eclipse.jetty.toolchain.test.FS; + import org.eclipse.jetty.util.IO; ++import org.hibernate.search.cfg.Environment; ++import org.hibernate.search.cfg.SearchMapping; + import org.infinispan.Cache; +-import org.infinispan.configuration.cache.Configuration; + import org.infinispan.configuration.cache.ConfigurationBuilder; ++import org.infinispan.configuration.cache.ConfigurationChildBuilder; ++import org.infinispan.configuration.cache.Index; + import org.infinispan.configuration.global.GlobalConfigurationBuilder; + import org.infinispan.manager.DefaultCacheManager; + import org.infinispan.manager.EmbeddedCacheManager; + ++import static org.junit.jupiter.api.Assertions.assertEquals; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ + /** + * InfinispanTestSupport +- * +- * + */ + public class InfinispanTestSupport + { +- public static final String DEFAULT_CACHE_NAME = "session_test_cache"; +- public Cache _cache; +- ++ public static final String DEFAULT_CACHE_NAME = "session_test_cache"; ++ public Cache _cache; ++ + public ConfigurationBuilder _builder; +- private File _tmpdir; ++ private File _tmpdir; + private boolean _useFileStore; ++ private boolean _serializeSessionData; + private String _name; +- public static EmbeddedCacheManager _manager; +- ++ public static EmbeddedCacheManager _manager; ++ + static + { + try +@@ -59,53 +65,84 @@ public class InfinispanTestSupport + e.printStackTrace(); + } + } +- +- +- +- +- public InfinispanTestSupport () ++ ++ public InfinispanTestSupport() + { +- this (null); ++ this(null); + } +- ++ + public InfinispanTestSupport(String cacheName) +- { ++ { + if (cacheName == null) +- cacheName = DEFAULT_CACHE_NAME+System.currentTimeMillis(); +- ++ cacheName = DEFAULT_CACHE_NAME + System.currentTimeMillis(); ++ + _name = cacheName; + _builder = new ConfigurationBuilder(); + } +- +- public void setUseFileStore (boolean useFileStore) ++ ++ public void setUseFileStore(boolean useFileStore) + { + _useFileStore = useFileStore; + } +- +- public Cache getCache () ++ ++ public void setSerializeSessionData(boolean serializeSessionData) + { +- return _cache; ++ _serializeSessionData = serializeSessionData; + } + +- public void setup () throws Exception ++ public Cache getCache() + { +- if (_useFileStore) +- { +- _tmpdir = File.createTempFile("infini", "span"); +- _tmpdir.delete(); +- _tmpdir.mkdir(); +- Configuration config = _builder.persistence().addSingleFileStore().location(_tmpdir.getAbsolutePath()).storeAsBinary().build(); +- _manager.defineConfiguration(_name, config); +- } +- else +- { +- _manager.defineConfiguration(_name, _builder.build()); +- } +- _cache = _manager.getCache(_name); ++ return _cache; + } + ++ public void setup(Path root) throws Exception ++ { ++ Path indexesDir = root.resolve("indexes"); ++ FS.ensureDirExists(indexesDir); ++ ++ SearchMapping mapping = new SearchMapping(); ++ mapping.entity(SessionData.class).indexed().providedId().property("expiry", ElementType.FIELD).field(); ++ Properties properties = new Properties(); ++ properties.put(Environment.MODEL_MAPPING, mapping); ++ properties.put("hibernate.search.default.indexBase", indexesDir.toString()); ++ ++ if (_useFileStore) ++ { ++ Path tmpDir = Files.createTempDirectory("infinispan"); ++ _tmpdir = tmpDir.toFile(); ++ ++ ConfigurationChildBuilder b = _builder.indexing() ++ .index(Index.ALL) ++ .addIndexedEntity(SessionData.class) ++ .withProperties(properties) ++ .persistence() ++ .addSingleFileStore() ++ .location(_tmpdir.getAbsolutePath()); ++ if (_serializeSessionData) ++ { ++ b = b.storeAsBinary().enable(); ++ } ++ ++ _manager.defineConfiguration(_name, b.build()); ++ } ++ else ++ { ++ ConfigurationChildBuilder b = _builder.indexing() ++ .withProperties(properties) ++ .index(Index.ALL) ++ .addIndexedEntity(SessionData.class); ++ ++ if (_serializeSessionData) ++ { ++ b = b.storeAsBinary().enable(); ++ } ++ ++ _manager.defineConfiguration(_name, b.build()); ++ } ++ _cache = _manager.getCache(_name); ++ } + +- public void teardown () throws Exception ++ public void teardown() throws Exception + { + _cache.clear(); + _manager.removeCache(_name); +@@ -117,39 +154,41 @@ public class InfinispanTestSupport + } + } + } +- +- ++ + @SuppressWarnings("unchecked") +- public void createSession (SessionData data) +- throws Exception ++ public void createSession(SessionData data) ++ throws Exception + { +- _cache.put(data.getContextPath()+"_"+data.getVhost()+"_"+data.getId(), data); ++ _cache.put(data.getContextPath() + "_" + data.getVhost() + "_" + data.getId(), data); + } + +- +- public void createUnreadableSession (SessionData data) ++ public void createUnreadableSession(SessionData data) + { +- ++ + } +- +- +- public boolean checkSessionExists (SessionData data) +- throws Exception ++ ++ public boolean checkSessionExists(SessionData data) ++ throws Exception + { +- return (_cache.get(data.getContextPath()+"_"+data.getVhost()+"_"+data.getId()) != null); ++ return (_cache.get(data.getContextPath() + "_" + data.getVhost() + "_" + data.getId()) != null); + } +- +- +- public boolean checkSessionPersisted (SessionData data) +- throws Exception ++ ++ public boolean checkSessionPersisted(SessionData data) ++ throws Exception + { +- Object obj = _cache.get(data.getContextPath()+"_"+data.getVhost()+"_"+data.getId()); ++ ++ //evicts the object from memory. Forces the cache to fetch the data from file ++ if (_useFileStore) ++ { ++ _cache.evict(data.getContextPath() + "_" + data.getVhost() + "_" + data.getId()); ++ } ++ ++ Object obj = _cache.get(data.getContextPath() + "_" + data.getVhost() + "_" + data.getId()); + if (obj == null) + return false; +- ++ + SessionData saved = (SessionData)obj; +- +- ++ + //turn an Entity into a Session + assertEquals(data.getId(), saved.getId()); + assertEquals(data.getContextPath(), saved.getContextPath()); +@@ -168,11 +207,11 @@ public class InfinispanTestSupport + //same keys + assertTrue(data.getKeys().equals(saved.getKeys())); + //same values +- for (String name:data.getKeys()) ++ for (String name : data.getKeys()) + { + assertTrue(data.getAttribute(name).equals(saved.getAttribute(name))); + } +- ++ + return true; + } + } +diff --git a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java +index a7af064..c1c5dc5 100644 +--- a/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java ++++ b/tests/test-webapps/test-jetty-webapp/src/test/java/org/eclipse/jetty/TestServer.java +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -18,13 +18,11 @@ + + package org.eclipse.jetty; + +-import java.io.File; + import java.io.IOException; + import java.lang.management.ManagementFactory; + import java.nio.file.FileSystems; + import java.nio.file.Files; + import java.nio.file.Path; +- + import javax.servlet.ServletException; + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; +@@ -65,11 +63,11 @@ public class TestServer + ((StdErrLog)Log.getLog()).setSource(false); + + // TODO don't depend on this file structure +- Path jetty_root = FileSystems.getDefault().getPath(".").toAbsolutePath().normalize(); +- if (!Files.exists(jetty_root.resolve("VERSION.txt"))) +- jetty_root = FileSystems.getDefault().getPath("../../..").toAbsolutePath().normalize(); +- if (!Files.exists(jetty_root.resolve("VERSION.txt"))) +- throw new IllegalArgumentException(jetty_root.toString()); ++ Path jettyRoot = FileSystems.getDefault().getPath(".").toAbsolutePath().normalize(); ++ if (!Files.exists(jettyRoot.resolve("VERSION.txt"))) ++ jettyRoot = FileSystems.getDefault().getPath("../../..").toAbsolutePath().normalize(); ++ if (!Files.exists(jettyRoot.resolve("VERSION.txt"))) ++ throw new IllegalArgumentException(jettyRoot.toString()); + + // Setup Threadpool + QueuedThreadPool threadPool = new QueuedThreadPool(); +@@ -80,10 +78,9 @@ public class TestServer + server.manage(threadPool); + + // Setup JMX +- MBeanContainer mbContainer=new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); ++ MBeanContainer mbContainer = new MBeanContainer(ManagementFactory.getPlatformMBeanServer()); + server.addBean(mbContainer); + server.addBean(Log.getLog()); +- + + // Common HTTP configuration + HttpConfiguration config = new HttpConfiguration(); +@@ -92,21 +89,19 @@ public class TestServer + config.addCustomizer(new SecureRequestCustomizer()); + config.setSendDateHeader(true); + config.setSendServerVersion(true); +- +- ++ + // Http Connector + HttpConnectionFactory http = new HttpConnectionFactory(config); +- ServerConnector httpConnector = new ServerConnector(server,http); ++ ServerConnector httpConnector = new ServerConnector(server, http); + httpConnector.setPort(8080); + httpConnector.setIdleTimeout(30000); + server.addConnector(httpConnector); + +- + // Handlers + HandlerCollection handlers = new HandlerCollection(); + ContextHandlerCollection contexts = new ContextHandlerCollection(); + handlers.setHandlers(new Handler[] +- { contexts, new DefaultHandler() }); ++ {contexts, new DefaultHandler()}); + + // Add restart handler to test the ability to save sessions and restart + RestartHandler restart = new RestartHandler(); +@@ -114,15 +109,14 @@ public class TestServer + + server.setHandler(restart); + +- + // Setup context + HashLoginService login = new HashLoginService(); + login.setName("Test Realm"); +- login.setConfig(jetty_root.resolve("tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties").toString()); ++ login.setConfig(jettyRoot.resolve("tests/test-webapps/test-jetty-webapp/src/main/config/demo-base/etc/realm.properties").toString()); + server.addBean(login); + +- File log=File.createTempFile("jetty-yyyy_mm_dd", "log"); +- CustomRequestLog requestLog = new CustomRequestLog(log.toString()); ++ Path logPath = Files.createTempFile("jetty-yyyy_mm_dd", "log"); ++ CustomRequestLog requestLog = new CustomRequestLog(logPath.toString()); + server.setRequestLog(requestLog); + + server.setStopAtShutdown(true); +@@ -130,23 +124,19 @@ public class TestServer + WebAppContext webapp = new WebAppContext(); + webapp.setContextPath("/test"); + webapp.setParentLoaderPriority(true); +- webapp.setResourceBase(jetty_root.resolve("tests/test-webapps/test-jetty-webapp/src/main/webapp").toString()); +- webapp.setAttribute("testAttribute","testValue"); +- File sessiondir=File.createTempFile("sessions",null); +- if (sessiondir.exists()) +- sessiondir.delete(); +- sessiondir.mkdir(); +- sessiondir.deleteOnExit(); ++ webapp.setResourceBase(jettyRoot.resolve("tests/test-webapps/test-jetty-webapp/src/main/webapp").toString()); ++ webapp.setAttribute("testAttribute", "testValue"); ++ Path sessionDir = Files.createTempDirectory("sessions"); + DefaultSessionCache ss = new DefaultSessionCache(webapp.getSessionHandler()); + FileSessionDataStore sds = new FileSessionDataStore(); + ss.setSessionDataStore(sds); +- sds.setStoreDir(sessiondir); ++ sds.setStoreDir(sessionDir.toFile()); + webapp.getSessionHandler().setSessionCache(ss); + + contexts.addHandler(webapp); + + ContextHandler srcroot = new ContextHandler(); +- srcroot.setResourceBase(jetty_root.resolve("tests/test-webapps/test-jetty-webapp/src").toString()); ++ srcroot.setResourceBase(jettyRoot.resolve("tests/test-webapps/test-jetty-webapp/src").toString()); + srcroot.setHandler(new ResourceHandler()); + srcroot.setContextPath("/src"); + contexts.addHandler(srcroot); +@@ -158,17 +148,17 @@ public class TestServer + + private static class RestartHandler extends HandlerWrapper + { +- /* ------------------------------------------------------------ */ ++ + /** + * @see org.eclipse.jetty.server.handler.HandlerWrapper#handle(java.lang.String, org.eclipse.jetty.server.Request, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) + */ + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { +- super.handle(target,baseRequest,request,response); ++ super.handle(target, baseRequest, request, response); + if (Boolean.valueOf(request.getParameter("restart"))) + { +- final Server server=getServer(); ++ final Server server = getServer(); + + new Thread() + { +@@ -182,7 +172,7 @@ public class TestServer + Thread.sleep(100); + server.start(); + } +- catch(Exception e) ++ catch (Exception e) + { + LOG.warn(e); + } diff --git a/CVE-2020-27223-pre-1.patch b/CVE-2020-27223-pre-1.patch deleted file mode 100644 index 597b0535292f941411ae3aa52b5597066ff4f7a1..0000000000000000000000000000000000000000 --- a/CVE-2020-27223-pre-1.patch +++ /dev/null @@ -1,545 +0,0 @@ -From 8011c48685e78850a88e7cc06650f678a375037d Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Thu, 28 Feb 2019 18:37:02 +1100 -Subject: [PATCH 1/4] Issue #3404 Updated QCSV Double usage - -Signed-off-by: Greg Wilkins ---- - .../eclipse/jetty/http/QuotedQualityCSV.java | 58 ++++++++++--------- - 1 file changed, 30 insertions(+), 28 deletions(-) - -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -index 61cee88bfd5..c7047c57a4b 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -@@ -25,6 +25,8 @@ - import java.util.List; - import java.util.function.Function; - -+import org.eclipse.jetty.util.log.Log; -+ - /* ------------------------------------------------------------ */ - /** - * Implements a quoted comma separated list of quality values -@@ -37,21 +39,16 @@ - */ - public class QuotedQualityCSV extends QuotedCSV implements Iterable - { -- private final static Double ZERO=new Double(0.0); -- private final static Double ONE=new Double(1.0); -- -+ private final static Double ZERO = 0.0D; -+ private final static Double ONE = 1.0D; - - /** -- * Function to apply a most specific MIME encoding secondary ordering -+ * Lambda to apply a most specific MIME encoding secondary ordering - */ -- public static Function MOST_SPECIFIC = new Function() -+ public static Function MOST_SPECIFIC = s -> - { -- @Override -- public Integer apply(String s) -- { -- String[] elements = s.split("/"); -- return 1000000*elements.length+1000*elements[0].length()+elements[elements.length-1].length(); -- } -+ String[] elements = s.split("/"); -+ return 1000000*elements.length+1000*elements[0].length()+elements[elements.length-1].length(); - }; - - private final List _quality = new ArrayList<>(); -@@ -74,7 +71,8 @@ public QuotedQualityCSV() - */ - public QuotedQualityCSV(String[] preferredOrder) - { -- this((s) -> { -+ this((s) -> -+ { - for (int i=0;i secondaryOrdering) - protected void parsedValue(StringBuffer buffer) - { - super.parsedValue(buffer); -+ -+ // Assume a quality of ONE - _quality.add(ONE); - } - -@@ -108,30 +108,32 @@ protected void parsedValue(StringBuffer buffer) - @Override - protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) - { -- if (paramName<0) -+ if (paramName < 0) - { -- if (buffer.charAt(buffer.length()-1)==';') -- buffer.setLength(buffer.length()-1); -+ if (buffer.charAt(buffer.length() - 1) == ';') -+ buffer.setLength(buffer.length() - 1); - } -- else if (paramValue>=0 && -- buffer.charAt(paramName)=='q' && paramValue>paramName && -- buffer.length()>=paramName && buffer.charAt(paramName+1)=='=') -+ else if (paramValue >= 0 && -+ buffer.charAt(paramName) == 'q' && paramValue > paramName && -+ buffer.length() >= paramName && buffer.charAt(paramName + 1) == '=') - { - Double q; - try - { -- q=(_keepQuotes && buffer.charAt(paramValue)=='"') -- ?new Double(buffer.substring(paramValue+1,buffer.length()-1)) -- :new Double(buffer.substring(paramValue)); -+ q = (_keepQuotes && buffer.charAt(paramValue) == '"') -+ ? Double.valueOf(buffer.substring(paramValue + 1, buffer.length() - 1)) -+ : Double.valueOf(buffer.substring(paramValue)); - } -- catch(Exception e) -+ catch (Exception e) - { -- q=ZERO; -- } -- buffer.setLength(Math.max(0,paramName-1)); -- -- if (!ONE.equals(q)) -- _quality.set(_quality.size()-1,q); -+ Log.getLogger(QuotedQualityCSV.class).ignore(e); -+ q = ZERO; -+ } -+ buffer.setLength(Math.max(0, paramName - 1)); -+ -+ if (!ONE.equals(q)) -+ // replace assumed quality -+ _quality.set(_quality.size() - 1, q); - } - } - - -From ca8a10a9d5a50e7d814c65134144f776bff1c07a Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Fri, 1 Mar 2019 10:17:02 +1100 -Subject: [PATCH 2/4] Issue #3404 Cleanup QCSV mime ordering - -Signed-off-by: Greg Wilkins ---- - .../org/eclipse/jetty/http/HttpFields.java | 16 ++++++++- - .../eclipse/jetty/http/QuotedQualityCSV.java | 35 ++++++++++--------- - .../jetty/http/QuotedQualityCSVTest.java | 2 +- - .../jetty/server/handler/ErrorHandler.java | 3 +- - 4 files changed, 37 insertions(+), 19 deletions(-) - -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java -index 14c4335eb5a..5a8b53013a7 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java -@@ -31,6 +31,7 @@ - import java.util.NoSuchElementException; - import java.util.Set; - import java.util.StringTokenizer; -+import java.util.function.Function; - import java.util.stream.Stream; - import java.util.stream.StreamSupport; - -@@ -437,6 +438,19 @@ protected String addCSV(QuotedCSV existing,String... values) - * @param header The header - */ - public List getQualityCSV(HttpHeader header) -+ { -+ return getQualityCSV(header,null); -+ } -+ -+ /** -+ * Get multiple field values of the same name, split and -+ * sorted as a {@link QuotedQualityCSV} -+ * -+ * @param header The header -+ * @param secondaryOrdering Function to apply an ordering other than specified by quality -+ * @return List the values in quality order with the q param and OWS stripped -+ */ -+ public List getQualityCSV(HttpHeader header, Function secondaryOrdering) - { - QuotedQualityCSV values = null; - for (HttpField f : this) -@@ -444,7 +458,7 @@ protected String addCSV(QuotedCSV existing,String... values) - if (f.getHeader()==header) - { - if (values==null) -- values = new QuotedQualityCSV(); -+ values = new QuotedQualityCSV(secondaryOrdering); - values.addValue(f.getValue()); - } - } -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -index c7047c57a4b..498d4e01491 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -@@ -38,17 +38,20 @@ - * @see "https://tools.ietf.org/html/rfc7231#section-5.3.1" - */ - public class QuotedQualityCSV extends QuotedCSV implements Iterable --{ -- private final static Double ZERO = 0.0D; -- private final static Double ONE = 1.0D; -- -+{ - /** -- * Lambda to apply a most specific MIME encoding secondary ordering -+ * Lambda to apply a most specific MIME encoding secondary ordering. -+ * @see "https://tools.ietf.org/html/rfc7231#section-5.3.2" - */ -- public static Function MOST_SPECIFIC = s -> -+ public static Function MOST_SPECIFIC_MIME_ORDERING = s -> - { -- String[] elements = s.split("/"); -- return 1000000*elements.length+1000*elements[0].length()+elements[elements.length-1].length(); -+ if ("*/*".equals(s)) -+ return 0; -+ if (s.endsWith("/*")) -+ return 1; -+ if (s.indexOf(';')<0) -+ return 2; -+ return 3; - }; - - private final List _quality = new ArrayList<>(); -@@ -61,7 +64,7 @@ - */ - public QuotedQualityCSV() - { -- this((s) -> 0); -+ this((Function)null); - } - - /* ------------------------------------------------------------ */ -@@ -91,7 +94,7 @@ public QuotedQualityCSV(String[] preferredOrder) - */ - public QuotedQualityCSV(Function secondaryOrdering) - { -- this._secondaryOrdering = secondaryOrdering; -+ this._secondaryOrdering = secondaryOrdering == null ? s->0 : secondaryOrdering; - } - - /* ------------------------------------------------------------ */ -@@ -101,7 +104,7 @@ protected void parsedValue(StringBuffer buffer) - super.parsedValue(buffer); - - // Assume a quality of ONE -- _quality.add(ONE); -+ _quality.add(1.0D); - } - - /* ------------------------------------------------------------ */ -@@ -127,11 +130,11 @@ else if (paramValue >= 0 && - catch (Exception e) - { - Log.getLogger(QuotedQualityCSV.class).ignore(e); -- q = ZERO; -+ q = 0.0D; - } - buffer.setLength(Math.max(0, paramName - 1)); - -- if (!ONE.equals(q)) -+ if (!((Double)1.0D).equals(q)) - // replace assumed quality - _quality.set(_quality.size() - 1, q); - } -@@ -157,7 +160,7 @@ protected void sort() - { - _sorted=true; - -- Double last = ZERO; -+ Double last = 0.0D; - int lastSecondaryOrder = Integer.MIN_VALUE; - - for (int i = _values.size(); i-- > 0;) -@@ -172,7 +175,7 @@ protected void sort() - _values.set(i + 1, v); - _quality.set(i, _quality.get(i + 1)); - _quality.set(i + 1, q); -- last = ZERO; -+ last = 0.0D; - lastSecondaryOrder=0; - i = _values.size(); - continue; -@@ -183,7 +186,7 @@ protected void sort() - } - - int last_element=_quality.size(); -- while(last_element>0 && _quality.get(--last_element).equals(ZERO)) -+ while(last_element>0 && _quality.get(--last_element).equals(0.0D)) - { - _quality.remove(last_element); - _values.remove(last_element); -diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java -index ba0db4a8972..f03657ba3e5 100644 ---- a/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java -+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java -@@ -61,7 +61,7 @@ public void test7231_5_3_2_example3() - @Test - public void test7231_5_3_2_example3_most_specific() - { -- QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC); -+ QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING); - values.addValue("text/*, text/plain, text/plain;format=flowed, */*"); - - assertThat(values,Matchers.contains("text/plain;format=flowed","text/plain","text/*","*/*")); -diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java -index 84088146cd3..2820dca3a0c 100644 ---- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java -+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java -@@ -38,6 +38,7 @@ - import org.eclipse.jetty.http.HttpMethod; - import org.eclipse.jetty.http.HttpStatus; - import org.eclipse.jetty.http.MimeTypes; -+import org.eclipse.jetty.http.QuotedQualityCSV; - import org.eclipse.jetty.server.Dispatcher; - import org.eclipse.jetty.server.Request; - import org.eclipse.jetty.server.Server; -@@ -159,7 +160,7 @@ else if (old_error_page!=null && old_error_page.equals(error_page)) - protected void generateAcceptableResponse(Request baseRequest, HttpServletRequest request, HttpServletResponse response, int code, String message) - throws IOException - { -- List acceptable=baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT); -+ List acceptable=baseRequest.getHttpFields().getQualityCSV(HttpHeader.ACCEPT, QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING); - - if (acceptable.isEmpty() && !baseRequest.getHttpFields().contains(HttpHeader.ACCEPT)) - { - -From b925380ede948ab7d8757e95a0ce384b2441625b Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Fri, 1 Mar 2019 10:20:10 +1100 -Subject: [PATCH 3/4] Issue #3404 Updated QCSV Double usage - -Signed-off-by: Greg Wilkins ---- - .../src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -index 498d4e01491..a7407d34199 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -@@ -134,7 +134,7 @@ else if (paramValue >= 0 && - } - buffer.setLength(Math.max(0, paramName - 1)); - -- if (!((Double)1.0D).equals(q)) -+ if (q!=1.0D) - // replace assumed quality - _quality.set(_quality.size() - 1, q); - } - -From 69f6b3b6164228485744918a28ab6a0d4c3facae Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Tue, 5 Mar 2019 08:59:55 +1100 -Subject: [PATCH 4/4] Issue #3404 - -updates after review: - + use ToIntFunction - + reformat - -Signed-off-by: Greg Wilkins ---- - .../org/eclipse/jetty/http/HttpFields.java | 4 +- - .../eclipse/jetty/http/QuotedQualityCSV.java | 62 +++++++++++-------- - 2 files changed, 37 insertions(+), 29 deletions(-) - -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java -index 5a8b53013a7..395b87bde35 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java -@@ -31,7 +31,7 @@ - import java.util.NoSuchElementException; - import java.util.Set; - import java.util.StringTokenizer; --import java.util.function.Function; -+import java.util.function.ToIntFunction; - import java.util.stream.Stream; - import java.util.stream.StreamSupport; - -@@ -450,7 +450,7 @@ protected String addCSV(QuotedCSV existing,String... values) - * @param secondaryOrdering Function to apply an ordering other than specified by quality - * @return List the values in quality order with the q param and OWS stripped - */ -- public List getQualityCSV(HttpHeader header, Function secondaryOrdering) -+ public List getQualityCSV(HttpHeader header, ToIntFunction secondaryOrdering) - { - QuotedQualityCSV values = null; - for (HttpField f : this) -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -index a7407d34199..d148d9e65e1 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -@@ -18,21 +18,23 @@ - - package org.eclipse.jetty.http; - --import static java.lang.Integer.MIN_VALUE; -- - import java.util.ArrayList; - import java.util.Iterator; - import java.util.List; --import java.util.function.Function; -+import java.util.function.ToIntFunction; - - import org.eclipse.jetty.util.log.Log; - -+import static java.lang.Integer.MIN_VALUE; -+ - /* ------------------------------------------------------------ */ -+ - /** - * Implements a quoted comma separated list of quality values - * in accordance with RFC7230 and RFC7231. -- * Values are returned sorted in quality order, with OWS and the -+ * Values are returned sorted in quality order, with OWS and the - * quality parameters removed. -+ * - * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" - * @see "https://tools.ietf.org/html/rfc7230#section-7" - * @see "https://tools.ietf.org/html/rfc7231#section-5.3.1" -@@ -41,44 +43,48 @@ - { - /** - * Lambda to apply a most specific MIME encoding secondary ordering. -+ * - * @see "https://tools.ietf.org/html/rfc7231#section-5.3.2" - */ -- public static Function MOST_SPECIFIC_MIME_ORDERING = s -> -+ public static ToIntFunction MOST_SPECIFIC_MIME_ORDERING = s -> - { - if ("*/*".equals(s)) - return 0; - if (s.endsWith("/*")) - return 1; -- if (s.indexOf(';')<0) -+ if (s.indexOf(';') < 0) - return 2; - return 3; - }; -- -+ - private final List _quality = new ArrayList<>(); - private boolean _sorted = false; -- private final Function _secondaryOrdering; -- -+ private final ToIntFunction _secondaryOrdering; -+ - /* ------------------------------------------------------------ */ -+ - /** - * Sorts values with equal quality according to the length of the value String. - */ - public QuotedQualityCSV() - { -- this((Function)null); -+ this((ToIntFunction)null); - } - - /* ------------------------------------------------------------ */ -+ - /** - * Sorts values with equal quality according to given order. -+ * - * @param preferredOrder Array indicating the preferred order of known values - */ - public QuotedQualityCSV(String[] preferredOrder) - { - this((s) -> - { -- for (int i=0;i secondaryOrdering) -+ public QuotedQualityCSV(ToIntFunction secondaryOrdering) - { -- this._secondaryOrdering = secondaryOrdering == null ? s->0 : secondaryOrdering; -+ this._secondaryOrdering = secondaryOrdering == null ? s -> 0 : secondaryOrdering; - } -- -+ - /* ------------------------------------------------------------ */ - @Override - protected void parsedValue(StringBuffer buffer) -@@ -134,7 +142,7 @@ else if (paramValue >= 0 && - } - buffer.setLength(Math.max(0, paramName - 1)); - -- if (q!=1.0D) -+ if (q != 1.0D) - // replace assumed quality - _quality.set(_quality.size() - 1, q); - } -@@ -147,7 +155,7 @@ else if (paramValue >= 0 && - sort(); - return _values; - } -- -+ - @Override - public Iterator iterator() - { -@@ -158,35 +166,35 @@ else if (paramValue >= 0 && - - protected void sort() - { -- _sorted=true; -+ _sorted = true; - - Double last = 0.0D; - int lastSecondaryOrder = Integer.MIN_VALUE; - -- for (int i = _values.size(); i-- > 0;) -+ for (int i = _values.size(); i-- > 0; ) - { - String v = _values.get(i); - Double q = _quality.get(i); - -- int compare=last.compareTo(q); -- if (compare>0 || (compare==0 && _secondaryOrdering.apply(v) 0 || (compare == 0 && _secondaryOrdering.applyAsInt(v) < lastSecondaryOrder)) - { - _values.set(i, _values.get(i + 1)); - _values.set(i + 1, v); - _quality.set(i, _quality.get(i + 1)); - _quality.set(i + 1, q); - last = 0.0D; -- lastSecondaryOrder=0; -+ lastSecondaryOrder = 0; - i = _values.size(); - continue; - } - -- last=q; -- lastSecondaryOrder=_secondaryOrdering.apply(v); -+ last = q; -+ lastSecondaryOrder = _secondaryOrdering.applyAsInt(v); - } -- -- int last_element=_quality.size(); -- while(last_element>0 && _quality.get(--last_element).equals(0.0D)) -+ -+ int last_element = _quality.size(); -+ while (last_element > 0 && _quality.get(--last_element).equals(0.0D)) - { - _quality.remove(last_element); - _values.remove(last_element); diff --git a/CVE-2020-27223-pre-2.patch b/CVE-2020-27223-pre-2.patch deleted file mode 100644 index 989fd75fb6662a4d1410eb45e8727188c2607c32..0000000000000000000000000000000000000000 --- a/CVE-2020-27223-pre-2.patch +++ /dev/null @@ -1,692 +0,0 @@ -From 05072b34dce43064e87ad0d59065f14666c1f34e Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Wed, 8 May 2019 13:28:16 +0200 -Subject: [PATCH] Issue #3630 Forwarded-Port - -Added support for the X-Forwarded-Port header. -Reimplemented header scanning using more efficient Trie and MethodHandles - -Signed-off-by: Greg Wilkins ---- - .../eclipse/jetty/http/HostPortHttpField.java | 13 + - .../org/eclipse/jetty/http/HttpHeader.java | 1 + - .../main/config/etc/jetty-http-forwarded.xml | 1 + - .../main/config/modules/http-forwarded.mod | 1 + - .../server/ForwardedRequestCustomizer.java | 274 +++++++++++++----- - .../ForwardedRequestCustomizerTest.java | 27 +- - .../java/org/eclipse/jetty/util/HostPort.java | 30 +- - .../org/eclipse/jetty/util/HostPortTest.java | 13 +- - 8 files changed, 269 insertions(+), 91 deletions(-) - -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java -index 215c353b4b9..dc386665339 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java -@@ -49,6 +49,19 @@ protected HostPortHttpField(HttpHeader header, String name, String authority) - } - } - -+ /* ------------------------------------------------------------ */ -+ public HostPortHttpField(String host, int port) -+ { -+ this(new HostPort(host, port)); -+ } -+ -+ /* ------------------------------------------------------------ */ -+ protected HostPortHttpField(HostPort hostport) -+ { -+ super(HttpHeader.HOST,HttpHeader.HOST.asString(),hostport.toString()); -+ _hostPort = hostport; -+ } -+ - /* ------------------------------------------------------------ */ - /** Get the host. - * @return the host -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java -index f39c0f7df98..ac6bf2be8da 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeader.java -@@ -82,6 +82,7 @@ - TE("TE"), - USER_AGENT("User-Agent"), - X_FORWARDED_FOR("X-Forwarded-For"), -+ X_FORWARDED_PORT("X-Forwarded-Port"), - X_FORWARDED_PROTO("X-Forwarded-Proto"), - X_FORWARDED_SERVER("X-Forwarded-Server"), - X_FORWARDED_HOST("X-Forwarded-Host"), -diff --git a/jetty-server/src/main/config/etc/jetty-http-forwarded.xml b/jetty-server/src/main/config/etc/jetty-http-forwarded.xml -index 50b80976a2a..648d6c6a94f 100644 ---- a/jetty-server/src/main/config/etc/jetty-http-forwarded.xml -+++ b/jetty-server/src/main/config/etc/jetty-http-forwarded.xml -@@ -11,6 +11,7 @@ - - - -+ - - - -diff --git a/jetty-server/src/main/config/modules/http-forwarded.mod b/jetty-server/src/main/config/modules/http-forwarded.mod -index 34e25642b2c..f67822065a4 100644 ---- a/jetty-server/src/main/config/modules/http-forwarded.mod -+++ b/jetty-server/src/main/config/modules/http-forwarded.mod -@@ -23,6 +23,7 @@ etc/jetty-http-forwarded.xml - # jetty.httpConfig.forwardedServerHeader=X-Forwarded-Server - # jetty.httpConfig.forwardedProtoHeader=X-Forwarded-Proto - # jetty.httpConfig.forwardedForHeader=X-Forwarded-For -+# jetty.httpConfig.forwardedPortHeader=X-Forwarded-Port - # jetty.httpConfig.forwardedHttpsHeader=X-Proxied-Https - # jetty.httpConfig.forwardedSslSessionIdHeader=Proxy-ssl-id - # jetty.httpConfig.forwardedCipherSuiteHeader=Proxy-auth-cert -diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -index f2ffe24fb85..1e64892cbe9 100644 ---- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -@@ -18,6 +18,9 @@ - - package org.eclipse.jetty.server; - -+import java.lang.invoke.MethodHandle; -+import java.lang.invoke.MethodHandles; -+import java.lang.invoke.MethodType; - import java.net.InetSocketAddress; - - import javax.servlet.ServletRequest; -@@ -29,11 +32,15 @@ - import org.eclipse.jetty.http.HttpScheme; - import org.eclipse.jetty.http.QuotedCSV; - import org.eclipse.jetty.server.HttpConfiguration.Customizer; -+import org.eclipse.jetty.util.ArrayTrie; - import org.eclipse.jetty.util.HostPort; - import org.eclipse.jetty.util.StringUtil; -+import org.eclipse.jetty.util.Trie; - import org.eclipse.jetty.util.log.Log; - import org.eclipse.jetty.util.log.Logger; - -+import static java.lang.invoke.MethodType.methodType; -+ - - /* ------------------------------------------------------------ */ - /** Customize Requests for Proxy Forwarding. -@@ -63,14 +70,21 @@ - private String _forwardedHeader = HttpHeader.FORWARDED.toString(); - private String _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); - private String _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); -- private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); - private String _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); -+ private String _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); -+ private String _forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString(); - private String _forwardedHttpsHeader = "X-Proxied-Https"; - private String _forwardedCipherSuiteHeader = "Proxy-auth-cert"; - private String _forwardedSslSessionIdHeader = "Proxy-ssl-id"; - private boolean _proxyAsAuthority=false; - private boolean _sslIsSecure=true; -- -+ private Trie _handles; -+ -+ public ForwardedRequestCustomizer() -+ { -+ updateHandles(); -+ } -+ - /** - * @return true if the proxy address obtained via - * {@code X-Forwarded-Server} or RFC7239 "by" is used as -@@ -103,9 +117,9 @@ public void setForwardedOnly(boolean rfc7239only) - if (_forwardedHeader==null) - _forwardedHeader=HttpHeader.FORWARDED.toString(); - _forwardedHostHeader=null; -- _forwardedHostHeader=null; - _forwardedServerHeader=null; - _forwardedForHeader=null; -+ _forwardedPortHeader=null; - _forwardedProtoHeader=null; - _forwardedHttpsHeader=null; - } -@@ -117,11 +131,15 @@ public void setForwardedOnly(boolean rfc7239only) - _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); - if (_forwardedForHeader==null) - _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); -+ if (_forwardedPortHeader==null) -+ _forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString(); - if (_forwardedProtoHeader==null) - _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); - if (_forwardedHttpsHeader==null) - _forwardedHttpsHeader = "X-Proxied-Https"; - } -+ -+ updateHandles(); - } - - public String getForcedHost() -@@ -138,6 +156,7 @@ public String getForcedHost() - public void setForcedHost(String hostAndPort) - { - _forcedHost = new HostPortHttpField(hostAndPort); -+ updateHandles(); - } - - /** -@@ -155,6 +174,7 @@ public String getForwardedHeader() - public void setForwardedHeader(String forwardedHeader) - { - _forwardedHeader = forwardedHeader; -+ updateHandles(); - } - - public String getForwardedHostHeader() -@@ -169,6 +189,7 @@ public String getForwardedHostHeader() - public void setForwardedHostHeader(String forwardedHostHeader) - { - _forwardedHostHeader = forwardedHostHeader; -+ updateHandles(); - } - - /** -@@ -186,6 +207,7 @@ public String getForwardedServerHeader() - public void setForwardedServerHeader(String forwardedServerHeader) - { - _forwardedServerHeader = forwardedServerHeader; -+ updateHandles(); - } - - /** -@@ -203,6 +225,22 @@ public String getForwardedForHeader() - public void setForwardedForHeader(String forwardedRemoteAddressHeader) - { - _forwardedForHeader = forwardedRemoteAddressHeader; -+ updateHandles(); -+ } -+ -+ public String getForwardedPortHeader() -+ { -+ return _forwardedHostHeader; -+ } -+ -+ /** -+ * @param forwardedPortHeader -+ * The header name for forwarded hosts (default {@code X-Forwarded-Port}) -+ */ -+ public void setForwardedPortHeader(String forwardedPortHeader) -+ { -+ _forwardedHostHeader = forwardedPortHeader; -+ updateHandles(); - } - - /** -@@ -224,6 +262,7 @@ public String getForwardedProtoHeader() - public void setForwardedProtoHeader(String forwardedProtoHeader) - { - _forwardedProtoHeader = forwardedProtoHeader; -+ updateHandles(); - } - - /** -@@ -241,6 +280,7 @@ public String getForwardedCipherSuiteHeader() - public void setForwardedCipherSuiteHeader(String forwardedCipherSuite) - { - _forwardedCipherSuiteHeader = forwardedCipherSuite; -+ updateHandles(); - } - - /** -@@ -258,6 +298,7 @@ public String getForwardedSslSessionIdHeader() - public void setForwardedSslSessionIdHeader(String forwardedSslSessionId) - { - _forwardedSslSessionIdHeader = forwardedSslSessionId; -+ updateHandles(); - } - - /** -@@ -274,6 +315,7 @@ public String getForwardedHttpsHeader() - public void setForwardedHttpsHeader(String forwardedHttpsHeader) - { - _forwardedHttpsHeader = forwardedHttpsHeader; -+ updateHandles(); - } - - /** -@@ -299,118 +341,84 @@ public void customize(Connector connector, HttpConfiguration config, Request req - { - HttpFields httpFields = request.getHttpFields(); - -- RFC7239 rfc7239 = null; -- String forwardedHost = null; -- String forwardedServer = null; -- HostPort forwardedFor = null; -- String forwardedProto = null; -- String forwardedHttps = null; -- - // Do a single pass through the header fields as it is a more efficient single iteration. -- for (HttpField field : httpFields) -+ Forwarded forwarded = new Forwarded(request, config); -+ try - { -- String name = field.getName(); -- -- if (getForwardedCipherSuiteHeader()!=null && getForwardedCipherSuiteHeader().equalsIgnoreCase(name)) -+ for (HttpField field : httpFields) - { -- request.setAttribute("javax.servlet.request.cipher_suite",field.getValue()); -- if (isSslIsSecure()) -- { -- request.setSecure(true); -- request.setScheme(config.getSecureScheme()); -- } -- } -- -- if (getForwardedSslSessionIdHeader()!=null && getForwardedSslSessionIdHeader().equalsIgnoreCase(name)) -- { -- request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue()); -- if (isSslIsSecure()) -- { -- request.setSecure(true); -- request.setScheme(config.getSecureScheme()); -- } -- } -- -- if (forwardedHost==null && _forwardedHostHeader!=null && _forwardedHostHeader.equalsIgnoreCase(name)) -- forwardedHost = getLeftMost(field.getValue()); -- -- if (forwardedServer==null && _forwardedServerHeader!=null && _forwardedServerHeader.equalsIgnoreCase(name)) -- forwardedServer = getLeftMost(field.getValue()); -- -- if (forwardedFor==null && _forwardedForHeader!=null && _forwardedForHeader.equalsIgnoreCase(name)) -- forwardedFor = getRemoteAddr(field.getValue()); -- -- if (forwardedProto==null && _forwardedProtoHeader!=null && _forwardedProtoHeader.equalsIgnoreCase(name)) -- forwardedProto = getLeftMost(field.getValue()); -- -- if (forwardedHttps==null && _forwardedHttpsHeader!=null && _forwardedHttpsHeader.equalsIgnoreCase(name)) -- forwardedHttps = getLeftMost(field.getValue()); -- -- if (_forwardedHeader!=null && _forwardedHeader.equalsIgnoreCase(name)) -- { -- if (rfc7239==null) -- rfc7239= new RFC7239(); -- rfc7239.addValue(field.getValue()); -+ MethodHandle handle = _handles.get(field.getName()); -+ if (handle != null) -+ handle.invoke(forwarded, field); - } - } -- -- // Handle host header if if not available any RFC7230.by or X-ForwardedServer header -+ catch (Throwable e) -+ { -+ throw new RuntimeException(e); -+ } -+ -+ // Determine host - if (_forcedHost != null) - { - // Update host header - httpFields.put(_forcedHost); - request.setAuthority(_forcedHost.getHost(),_forcedHost.getPort()); - } -- else if (rfc7239!=null && rfc7239._host!=null) -+ else if (forwarded._rfc7239!=null && forwarded._rfc7239._host!=null) - { -- HostPortHttpField auth = rfc7239._host; -+ HostPortHttpField auth = forwarded._rfc7239._host; - httpFields.put(auth); - request.setAuthority(auth.getHost(),auth.getPort()); - } -- else if (forwardedHost != null) -+ else if (forwarded._forwardedHost != null) - { -- HostPortHttpField auth = new HostPortHttpField(forwardedHost); -+ HostPortHttpField auth = new HostPortHttpField(forwarded._forwardedHost); - httpFields.put(auth); -- request.setAuthority(auth.getHost(),auth.getPort()); -+ request.setAuthority(auth.getHost(), auth.getPort()); - } - else if (_proxyAsAuthority) - { -- if (rfc7239!=null && rfc7239._by!=null) -+ if (forwarded._rfc7239!=null && forwarded._rfc7239._by!=null) - { -- HostPortHttpField auth = rfc7239._by; -+ HostPortHttpField auth = forwarded._rfc7239._by; - httpFields.put(auth); - request.setAuthority(auth.getHost(),auth.getPort()); - } -- else if (forwardedServer != null) -+ else if (forwarded._forwardedServer != null) - { -- request.setAuthority(forwardedServer,request.getServerPort()); -+ request.setAuthority(forwarded._forwardedServer,request.getServerPort()); - } - } - - // handle remote end identifier -- if (rfc7239!=null && rfc7239._for!=null) -+ if (forwarded._rfc7239!=null && forwarded._rfc7239._for!=null) - { -- request.setRemoteAddr(InetSocketAddress.createUnresolved(rfc7239._for.getHost(),rfc7239._for.getPort())); -+ request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._rfc7239._for.getHost(),forwarded._rfc7239._for.getPort())); - } -- else if (forwardedFor != null) -+ else if (forwarded._forwardedFor != null) - { -- request.setRemoteAddr(InetSocketAddress.createUnresolved(forwardedFor.getHost(), (forwardedFor.getPort() > 0) ? forwardedFor.getPort() : request.getRemotePort())); -+ int port = (forwarded._forwardedPort>0) -+ ? forwarded._forwardedPort -+ : (forwarded._forwardedFor.getPort() > 0) -+ ? forwarded._forwardedFor.getPort() -+ : request.getRemotePort(); -+ request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._forwardedFor.getHost(), port)); - } - - // handle protocol identifier -- if (rfc7239!=null && rfc7239._proto!=null) -+ if (forwarded._rfc7239!=null && forwarded._rfc7239._proto!=null) - { -- request.setScheme(rfc7239._proto); -- if (rfc7239._proto.equals(config.getSecureScheme())) -+ request.setScheme(forwarded._rfc7239._proto); -+ if (forwarded._rfc7239._proto.equals(config.getSecureScheme())) - request.setSecure(true); - } -- else if (forwardedProto != null) -+ else if (forwarded._forwardedProto != null) - { -- request.setScheme(forwardedProto); -- if (forwardedProto.equals(config.getSecureScheme())) -+ request.setScheme(forwarded._forwardedProto); -+ if (forwarded._forwardedProto.equals(config.getSecureScheme())) - request.setSecure(true); - } -- else if (forwardedHttps !=null && ("on".equalsIgnoreCase(forwardedHttps)||"true".equalsIgnoreCase(forwardedHttps))) -+ else if (forwarded._forwardedHttps !=null && ("on".equalsIgnoreCase(forwarded._forwardedHttps)||"true".equalsIgnoreCase(forwarded._forwardedHttps))) - { - request.setScheme(HttpScheme.HTTPS.asString()); - if (HttpScheme.HTTPS.asString().equals(config.getSecureScheme())) -@@ -521,4 +529,122 @@ protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, - } - } - } -+ -+ private void updateHandles() -+ { -+ int size = 0; -+ MethodHandles.Lookup lookup = MethodHandles.lookup(); -+ MethodType type = methodType(Void.TYPE, HttpField.class); -+ -+ while(true) -+ { -+ try -+ { -+ size += 128; -+ _handles = new ArrayTrie<>(size); -+ -+ if (_forwardedCipherSuiteHeader != null && !_handles.put(_forwardedCipherSuiteHeader, lookup.findVirtual(Forwarded.class, "handleCipherSuite", type))) -+ continue; -+ if (_forwardedSslSessionIdHeader != null && !_handles.put(_forwardedSslSessionIdHeader, lookup.findVirtual(Forwarded.class, "handleSslSessionId", type))) -+ continue; -+ if (_forwardedHeader != null && !_handles.put(_forwardedHeader, lookup.findVirtual(Forwarded.class, "handleRFC7239", type))) -+ continue; -+ if (_forwardedForHeader != null && !_handles.put(_forwardedForHeader, lookup.findVirtual(Forwarded.class, "handleFor", type))) -+ continue; -+ if (_forwardedPortHeader != null && !_handles.put(_forwardedPortHeader, lookup.findVirtual(Forwarded.class, "handlePort", type))) -+ continue; -+ if (_forwardedHostHeader != null && !_handles.put(_forwardedHostHeader, lookup.findVirtual(Forwarded.class, "handleHost", type))) -+ continue; -+ if (_forwardedProtoHeader != null && !_handles.put(_forwardedProtoHeader, lookup.findVirtual(Forwarded.class, "handleProto", type))) -+ continue; -+ if (_forwardedHttpsHeader != null && !_handles.put(_forwardedHttpsHeader, lookup.findVirtual(Forwarded.class, "handleHttps", type))) -+ continue; -+ if (_forwardedServerHeader != null && !_handles.put(_forwardedServerHeader, lookup.findVirtual(Forwarded.class, "handleServer", type))) -+ continue; -+ break; -+ } -+ catch (NoSuchMethodException|IllegalAccessException e) -+ { -+ throw new IllegalStateException(e); -+ } -+ } -+ } -+ -+ private class Forwarded -+ { -+ HttpConfiguration _config; -+ Request _request; -+ -+ RFC7239 _rfc7239 = null; -+ String _forwardedHost = null; -+ String _forwardedServer = null; -+ String _forwardedProto = null; -+ HostPort _forwardedFor = null; -+ int _forwardedPort = -1; -+ String _forwardedHttps = null; -+ -+ public Forwarded(Request request, HttpConfiguration config) -+ { -+ _request = request; -+ _config = config; -+ } -+ -+ public void handleCipherSuite(HttpField field) -+ { -+ _request.setAttribute("javax.servlet.request.cipher_suite",field.getValue()); -+ if (isSslIsSecure()) -+ { -+ _request.setSecure(true); -+ _request.setScheme(_config.getSecureScheme()); -+ } -+ } -+ -+ public void handleSslSessionId(HttpField field) -+ { -+ _request.setAttribute("javax.servlet.request.ssl_session_id", field.getValue()); -+ if (isSslIsSecure()) -+ { -+ _request.setSecure(true); -+ _request.setScheme(_config.getSecureScheme()); -+ } -+ } -+ -+ public void handleHost(HttpField field) -+ { -+ _forwardedHost = getLeftMost(field.getValue()); -+ } -+ -+ public void handleServer(HttpField field) -+ { -+ _forwardedServer = getLeftMost(field.getValue()); -+ } -+ -+ public void handleProto(HttpField field) -+ { -+ _forwardedProto = getLeftMost(field.getValue()); -+ } -+ -+ public void handleFor(HttpField field) -+ { -+ _forwardedFor = getRemoteAddr(field.getValue()); -+ } -+ -+ public void handlePort(HttpField field) -+ { -+ _forwardedPort = field.getIntValue(); -+ } -+ public void handleHttps(HttpField field) -+ { -+ _forwardedHttps = getLeftMost(field.getValue()); -+ } -+ -+ public void handleRFC7239(HttpField field) -+ { -+ if (_rfc7239 ==null) -+ _rfc7239 = new RFC7239(); -+ _rfc7239.addValue(field.getValue()); -+ } -+ -+ -+ } - } -diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java -index 5d5467f73bd..0a1cecec0c2 100644 ---- a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java -+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java -@@ -18,11 +18,6 @@ - - package org.eclipse.jetty.server; - --import static org.junit.jupiter.api.Assertions.assertEquals; --import static org.junit.jupiter.api.Assertions.assertFalse; --import static org.hamcrest.MatcherAssert.assertThat; --import static org.junit.jupiter.api.Assertions.assertTrue; -- - import java.io.IOException; - import java.util.ArrayDeque; - import java.util.Deque; -@@ -41,6 +36,11 @@ - import org.junit.jupiter.api.BeforeEach; - import org.junit.jupiter.api.Test; - -+import static org.hamcrest.MatcherAssert.assertThat; -+import static org.junit.jupiter.api.Assertions.assertEquals; -+import static org.junit.jupiter.api.Assertions.assertFalse; -+import static org.junit.jupiter.api.Assertions.assertTrue; -+ - public class ForwardedRequestCustomizerTest - { - private Server _server; -@@ -249,6 +249,23 @@ public void testForIpv6WithPort() throws Exception - assertEquals("1111",_results.poll()); - } - -+ @Test -+ public void testForIpv6AndPort() throws Exception -+ { -+ String response=_connector.getResponse( -+ "GET / HTTP/1.1\n"+ -+ "Host: myhost\n"+ -+ "X-Forwarded-For: 1:2:3:4:5:6:7:8\n"+ -+ "X-Forwarded-Port: 2222\n"+ -+ "\n"); -+ assertThat(response, Matchers.containsString("200 OK")); -+ assertEquals("http",_results.poll()); -+ assertEquals("myhost",_results.poll()); -+ assertEquals("80",_results.poll()); -+ assertEquals("[1:2:3:4:5:6:7:8]",_results.poll()); -+ assertEquals("2222",_results.poll()); -+ } -+ - @Test - public void testLegacyProto() throws Exception - { -diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/HostPort.java b/jetty-util/src/main/java/org/eclipse/jetty/util/HostPort.java -index 1824dd799d1..8fde487ae19 100644 ---- a/jetty-util/src/main/java/org/eclipse/jetty/util/HostPort.java -+++ b/jetty-util/src/main/java/org/eclipse/jetty/util/HostPort.java -@@ -32,6 +32,12 @@ - private final String _host; - private final int _port; - -+ public HostPort(String host, int port) throws IllegalArgumentException -+ { -+ _host = host; -+ _port = port; -+ } -+ - public HostPort(String authority) throws IllegalArgumentException - { - if (authority==null) -@@ -66,8 +72,16 @@ else if (authority.charAt(0)=='[') - int c = authority.lastIndexOf(':'); - if (c>=0) - { -- _host=authority.substring(0,c); -- _port=StringUtil.toInt(authority,c+1); -+ if (c!=authority.indexOf(':')) -+ { -+ _host="[" + authority + "]"; -+ _port=0; -+ } -+ else -+ { -+ _host = authority.substring(0, c); -+ _port = StringUtil.toInt(authority, c + 1); -+ } - } - else - { -@@ -93,7 +107,6 @@ else if (authority.charAt(0)=='[') - throw new IllegalArgumentException("Bad port"); - } - -- /* ------------------------------------------------------------ */ - /** Get the host. - * @return the host - */ -@@ -102,7 +115,6 @@ public String getHost() - return _host; - } - -- /* ------------------------------------------------------------ */ - /** Get the port. - * @return the port - */ -@@ -111,7 +123,6 @@ public int getPort() - return _port; - } - -- /* ------------------------------------------------------------ */ - /** Get the port. - * @param defaultPort, the default port to return if a port is not specified - * @return the port -@@ -121,7 +132,14 @@ public int getPort(int defaultPort) - return _port>0?_port:defaultPort; - } - -- /* ------------------------------------------------------------ */ -+ @Override -+ public String toString() -+ { -+ if (_port>0) -+ return normalizeHost(_host) + ":" + _port; -+ return _host; -+ } -+ - /** Normalize IPv6 address as per https://www.ietf.org/rfc/rfc2732.txt - * @param host A host name - * @return Host name surrounded by '[' and ']' as needed. -diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/HostPortTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/HostPortTest.java -index 705bbbf6fa6..a700262cb90 100644 ---- a/jetty-util/src/test/java/org/eclipse/jetty/util/HostPortTest.java -+++ b/jetty-util/src/test/java/org/eclipse/jetty/util/HostPortTest.java -@@ -18,17 +18,17 @@ - - package org.eclipse.jetty.util; - --import static org.hamcrest.MatcherAssert.assertThat; --import static org.hamcrest.Matchers.is; --import static org.junit.jupiter.api.Assertions.assertNull; --import static org.junit.jupiter.api.Assertions.assertThrows; -- - import java.util.stream.Stream; - - import org.junit.jupiter.params.ParameterizedTest; - import org.junit.jupiter.params.provider.Arguments; - import org.junit.jupiter.params.provider.MethodSource; - -+import static org.hamcrest.MatcherAssert.assertThat; -+import static org.hamcrest.Matchers.is; -+import static org.junit.jupiter.api.Assertions.assertNull; -+import static org.junit.jupiter.api.Assertions.assertThrows; -+ - public class HostPortTest - { - private static Stream validAuthorityProvider() -@@ -41,7 +41,8 @@ - Arguments.of("10.10.10.1", "10.10.10.1", null), - Arguments.of("10.10.10.1:80", "10.10.10.1", "80"), - Arguments.of("[0::0::0::1]", "[0::0::0::1]", null), -- Arguments.of("[0::0::0::1]:80", "[0::0::0::1]", "80") -+ Arguments.of("[0::0::0::1]:80", "[0::0::0::1]", "80"), -+ Arguments.of("0:1:2:3:4:5:6","[0:1:2:3:4:5:6]",null) - ); - } - diff --git a/CVE-2020-27223-pre-3.patch b/CVE-2020-27223-pre-3.patch deleted file mode 100644 index 356a049f8c942dc16c315428276caee080c9b940..0000000000000000000000000000000000000000 --- a/CVE-2020-27223-pre-3.patch +++ /dev/null @@ -1,518 +0,0 @@ -From 9f3b0223ab7e3159c4794e102f6b5e06dfc8710d Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Wed, 8 May 2019 14:00:58 +0200 -Subject: [PATCH] Issue #3630 Forwarded-Port - -reformatted code -Avoid updating handles unless configuration is changed. - -Signed-off-by: Greg Wilkins ---- - .../server/ForwardedRequestCustomizer.java | 216 ++++++++++-------- - 1 file changed, 117 insertions(+), 99 deletions(-) - -diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -index 1e64892cbe9..be3990794ed 100644 ---- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -@@ -43,7 +43,9 @@ - - - /* ------------------------------------------------------------ */ --/** Customize Requests for Proxy Forwarding. -+ -+/** -+ * Customize Requests for Proxy Forwarding. - *

- * This customizer looks at at HTTP request for headers that indicate - * it has been forwarded by one or more proxies. Specifically handled are -@@ -59,7 +61,8 @@ - * so that the proxy is not seen as the other end point of the connection on which - * the request came

- *

Headers can also be defined so that forwarded SSL Session IDs and Cipher -- * suites may be customised

-+ * suites may be customised

-+ * - * @see Wikipedia: X-Forwarded-For - */ - public class ForwardedRequestCustomizer implements Customizer -@@ -76,9 +79,9 @@ - private String _forwardedHttpsHeader = "X-Proxied-Https"; - private String _forwardedCipherSuiteHeader = "Proxy-auth-cert"; - private String _forwardedSslSessionIdHeader = "Proxy-ssl-id"; -- private boolean _proxyAsAuthority=false; -- private boolean _sslIsSecure=true; -- private Trie _handles; -+ private boolean _proxyAsAuthority = false; -+ private boolean _sslIsSecure = true; -+ private Trie _handles; - - public ForwardedRequestCustomizer() - { -@@ -97,7 +100,7 @@ public boolean getProxyAsAuthority() - - /** - * @param proxyAsAuthority if true, use the proxy address obtained via -- * {@code X-Forwarded-Server} or RFC7239 "by" as the request authority. -+ * {@code X-Forwarded-Server} or RFC7239 "by" as the request authority. - */ - public void setProxyAsAuthority(boolean proxyAsAuthority) - { -@@ -114,49 +117,47 @@ public void setForwardedOnly(boolean rfc7239only) - { - if (rfc7239only) - { -- if (_forwardedHeader==null) -- _forwardedHeader=HttpHeader.FORWARDED.toString(); -- _forwardedHostHeader=null; -- _forwardedServerHeader=null; -- _forwardedForHeader=null; -- _forwardedPortHeader=null; -- _forwardedProtoHeader=null; -- _forwardedHttpsHeader=null; -+ if (_forwardedHeader == null) -+ _forwardedHeader = HttpHeader.FORWARDED.toString(); -+ _forwardedHostHeader = null; -+ _forwardedServerHeader = null; -+ _forwardedForHeader = null; -+ _forwardedPortHeader = null; -+ _forwardedProtoHeader = null; -+ _forwardedHttpsHeader = null; - } - else - { -- if (_forwardedHostHeader==null) -+ if (_forwardedHostHeader == null) - _forwardedHostHeader = HttpHeader.X_FORWARDED_HOST.toString(); -- if (_forwardedServerHeader==null) -+ if (_forwardedServerHeader == null) - _forwardedServerHeader = HttpHeader.X_FORWARDED_SERVER.toString(); -- if (_forwardedForHeader==null) -+ if (_forwardedForHeader == null) - _forwardedForHeader = HttpHeader.X_FORWARDED_FOR.toString(); -- if (_forwardedPortHeader==null) -+ if (_forwardedPortHeader == null) - _forwardedPortHeader = HttpHeader.X_FORWARDED_PORT.toString(); -- if (_forwardedProtoHeader==null) -+ if (_forwardedProtoHeader == null) - _forwardedProtoHeader = HttpHeader.X_FORWARDED_PROTO.toString(); -- if (_forwardedHttpsHeader==null) -+ if (_forwardedHttpsHeader == null) - _forwardedHttpsHeader = "X-Proxied-Https"; - } - - updateHandles(); - } -- -+ - public String getForcedHost() - { - return _forcedHost.getValue(); - } -- -+ - /** - * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. - * -- * @param hostAndPort -- * The value of the host header to force. -+ * @param hostAndPort The value of the host header to force. - */ - public void setForcedHost(String hostAndPort) - { - _forcedHost = new HostPortHttpField(hostAndPort); -- updateHandles(); - } - - /** -@@ -168,13 +169,15 @@ public String getForwardedHeader() - } - - /** -- * @param forwardedHeader -- * The header name for RFC forwarded (default Forwarded) -+ * @param forwardedHeader The header name for RFC forwarded (default Forwarded) - */ - public void setForwardedHeader(String forwardedHeader) - { -- _forwardedHeader = forwardedHeader; -- updateHandles(); -+ if (_forwardedHeader == null || !_forwardedHeader.equals(forwardedHeader)) -+ { -+ _forwardedHeader = forwardedHeader; -+ updateHandles(); -+ } - } - - public String getForwardedHostHeader() -@@ -183,13 +186,15 @@ public String getForwardedHostHeader() - } - - /** -- * @param forwardedHostHeader -- * The header name for forwarded hosts (default {@code X-Forwarded-Host}) -+ * @param forwardedHostHeader The header name for forwarded hosts (default {@code X-Forwarded-Host}) - */ - public void setForwardedHostHeader(String forwardedHostHeader) - { -- _forwardedHostHeader = forwardedHostHeader; -- updateHandles(); -+ if (_forwardedHostHeader == null || !_forwardedHostHeader.equalsIgnoreCase(forwardedHostHeader)) -+ { -+ _forwardedHostHeader = forwardedHostHeader; -+ updateHandles(); -+ } - } - - /** -@@ -201,13 +206,15 @@ public String getForwardedServerHeader() - } - - /** -- * @param forwardedServerHeader -- * The header name for forwarded server (default {@code X-Forwarded-Server}) -+ * @param forwardedServerHeader The header name for forwarded server (default {@code X-Forwarded-Server}) - */ - public void setForwardedServerHeader(String forwardedServerHeader) - { -- _forwardedServerHeader = forwardedServerHeader; -- updateHandles(); -+ if (_forwardedServerHeader == null || !_forwardedServerHeader.equalsIgnoreCase(forwardedServerHeader)) -+ { -+ _forwardedServerHeader = forwardedServerHeader; -+ updateHandles(); -+ } - } - - /** -@@ -219,13 +226,15 @@ public String getForwardedForHeader() - } - - /** -- * @param forwardedRemoteAddressHeader -- * The header name for forwarded for (default {@code X-Forwarded-For}) -+ * @param forwardedRemoteAddressHeader The header name for forwarded for (default {@code X-Forwarded-For}) - */ - public void setForwardedForHeader(String forwardedRemoteAddressHeader) - { -- _forwardedForHeader = forwardedRemoteAddressHeader; -- updateHandles(); -+ if (_forwardedForHeader == null || !_forwardedForHeader.equalsIgnoreCase(forwardedRemoteAddressHeader)) -+ { -+ _forwardedForHeader = forwardedRemoteAddressHeader; -+ updateHandles(); -+ } - } - - public String getForwardedPortHeader() -@@ -234,13 +243,15 @@ public String getForwardedPortHeader() - } - - /** -- * @param forwardedPortHeader -- * The header name for forwarded hosts (default {@code X-Forwarded-Port}) -+ * @param forwardedPortHeader The header name for forwarded hosts (default {@code X-Forwarded-Port}) - */ - public void setForwardedPortHeader(String forwardedPortHeader) - { -- _forwardedHostHeader = forwardedPortHeader; -- updateHandles(); -+ if (_forwardedHostHeader == null || !_forwardedHostHeader.equalsIgnoreCase(forwardedPortHeader)) -+ { -+ _forwardedHostHeader = forwardedPortHeader; -+ updateHandles(); -+ } - } - - /** -@@ -256,13 +267,15 @@ public String getForwardedProtoHeader() - /** - * Set the forwardedProtoHeader. - * -- * @param forwardedProtoHeader -- * the forwardedProtoHeader to set (default {@code X-Forwarded-Proto}) -+ * @param forwardedProtoHeader the forwardedProtoHeader to set (default {@code X-Forwarded-Proto}) - */ - public void setForwardedProtoHeader(String forwardedProtoHeader) - { -- _forwardedProtoHeader = forwardedProtoHeader; -- updateHandles(); -+ if (_forwardedProtoHeader == null || !_forwardedProtoHeader.equalsIgnoreCase(forwardedProtoHeader)) -+ { -+ _forwardedProtoHeader = forwardedProtoHeader; -+ updateHandles(); -+ } - } - - /** -@@ -274,13 +287,15 @@ public String getForwardedCipherSuiteHeader() - } - - /** -- * @param forwardedCipherSuite -- * The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert}) -+ * @param forwardedCipherSuiteHeader The header name holding a forwarded cipher suite (default {@code Proxy-auth-cert}) - */ -- public void setForwardedCipherSuiteHeader(String forwardedCipherSuite) -+ public void setForwardedCipherSuiteHeader(String forwardedCipherSuiteHeader) - { -- _forwardedCipherSuiteHeader = forwardedCipherSuite; -- updateHandles(); -+ if (_forwardedCipherSuiteHeader == null || !_forwardedCipherSuiteHeader.equalsIgnoreCase(forwardedCipherSuiteHeader)) -+ { -+ _forwardedCipherSuiteHeader = forwardedCipherSuiteHeader; -+ updateHandles(); -+ } - } - - /** -@@ -292,13 +307,15 @@ public String getForwardedSslSessionIdHeader() - } - - /** -- * @param forwardedSslSessionId -- * The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id}) -+ * @param forwardedSslSessionIdHeader The header name holding a forwarded SSL Session ID (default {@code Proxy-ssl-id}) - */ -- public void setForwardedSslSessionIdHeader(String forwardedSslSessionId) -+ public void setForwardedSslSessionIdHeader(String forwardedSslSessionIdHeader) - { -- _forwardedSslSessionIdHeader = forwardedSslSessionId; -- updateHandles(); -+ if (_forwardedSslSessionIdHeader == null || !_forwardedSslSessionIdHeader.equalsIgnoreCase(forwardedSslSessionIdHeader)) -+ { -+ _forwardedSslSessionIdHeader = forwardedSslSessionIdHeader; -+ updateHandles(); -+ } - } - - /** -@@ -314,10 +331,13 @@ public String getForwardedHttpsHeader() - */ - public void setForwardedHttpsHeader(String forwardedHttpsHeader) - { -- _forwardedHttpsHeader = forwardedHttpsHeader; -- updateHandles(); -+ if (_forwardedHttpsHeader == null || !_forwardedHttpsHeader.equalsIgnoreCase(forwardedHttpsHeader)) -+ { -+ _forwardedHttpsHeader = forwardedHttpsHeader; -+ updateHandles(); -+ } - } -- -+ - /** - * @return true if the presence of a SSL session or certificate header is sufficient - * to indicate a secure request (default is true) -@@ -329,7 +349,7 @@ public boolean isSslIsSecure() - - /** - * @param sslIsSecure true if the presence of a SSL session or certificate header is sufficient -- * to indicate a secure request (default is true) -+ * to indicate a secure request (default is true) - */ - public void setSslIsSecure(boolean sslIsSecure) - { -@@ -362,13 +382,13 @@ public void customize(Connector connector, HttpConfiguration config, Request req - { - // Update host header - httpFields.put(_forcedHost); -- request.setAuthority(_forcedHost.getHost(),_forcedHost.getPort()); -+ request.setAuthority(_forcedHost.getHost(), _forcedHost.getPort()); - } -- else if (forwarded._rfc7239!=null && forwarded._rfc7239._host!=null) -+ else if (forwarded._rfc7239 != null && forwarded._rfc7239._host != null) - { - HostPortHttpField auth = forwarded._rfc7239._host; - httpFields.put(auth); -- request.setAuthority(auth.getHost(),auth.getPort()); -+ request.setAuthority(auth.getHost(), auth.getPort()); - } - else if (forwarded._forwardedHost != null) - { -@@ -378,26 +398,26 @@ else if (forwarded._forwardedHost != null) - } - else if (_proxyAsAuthority) - { -- if (forwarded._rfc7239!=null && forwarded._rfc7239._by!=null) -+ if (forwarded._rfc7239 != null && forwarded._rfc7239._by != null) - { - HostPortHttpField auth = forwarded._rfc7239._by; - httpFields.put(auth); -- request.setAuthority(auth.getHost(),auth.getPort()); -+ request.setAuthority(auth.getHost(), auth.getPort()); - } - else if (forwarded._forwardedServer != null) - { -- request.setAuthority(forwarded._forwardedServer,request.getServerPort()); -+ request.setAuthority(forwarded._forwardedServer, request.getServerPort()); - } - } - - // handle remote end identifier -- if (forwarded._rfc7239!=null && forwarded._rfc7239._for!=null) -+ if (forwarded._rfc7239 != null && forwarded._rfc7239._for != null) - { -- request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._rfc7239._for.getHost(),forwarded._rfc7239._for.getPort())); -+ request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._rfc7239._for.getHost(), forwarded._rfc7239._for.getPort())); - } - else if (forwarded._forwardedFor != null) - { -- int port = (forwarded._forwardedPort>0) -+ int port = (forwarded._forwardedPort > 0) - ? forwarded._forwardedPort - : (forwarded._forwardedFor.getPort() > 0) - ? forwarded._forwardedFor.getPort() -@@ -406,7 +426,7 @@ else if (forwarded._forwardedFor != null) - } - - // handle protocol identifier -- if (forwarded._rfc7239!=null && forwarded._rfc7239._proto!=null) -+ if (forwarded._rfc7239 != null && forwarded._rfc7239._proto != null) - { - request.setScheme(forwarded._rfc7239._proto); - if (forwarded._rfc7239._proto.equals(config.getSecureScheme())) -@@ -418,7 +438,7 @@ else if (forwarded._forwardedProto != null) - if (forwarded._forwardedProto.equals(config.getSecureScheme())) - request.setSecure(true); - } -- else if (forwarded._forwardedHttps !=null && ("on".equalsIgnoreCase(forwarded._forwardedHttps)||"true".equalsIgnoreCase(forwarded._forwardedHttps))) -+ else if (forwarded._forwardedHttps != null && ("on".equalsIgnoreCase(forwarded._forwardedHttps) || "true".equalsIgnoreCase(forwarded._forwardedHttps))) - { - request.setScheme(HttpScheme.HTTPS.asString()); - if (HttpScheme.HTTPS.asString().equals(config.getSecureScheme())) -@@ -441,7 +461,7 @@ protected String getLeftMost(String headerValue) - } - - // The left-most value is the farthest downstream client -- return headerValue.substring(0,commaIndex).trim(); -+ return headerValue.substring(0, commaIndex).trim(); - } - - protected HostPort getRemoteAddr(String headerValue) -@@ -463,11 +483,11 @@ protected HostPort getRemoteAddr(String headerValue) - return null; - } - } -- -+ - @Override - public String toString() - { -- return String.format("%s@%x",this.getClass().getSimpleName(),hashCode()); -+ return String.format("%s@%x", this.getClass().getSimpleName(), hashCode()); - } - - @Deprecated -@@ -475,12 +495,11 @@ public String getHostHeader() - { - return _forcedHost.getValue(); - } -- -+ - /** - * Set a forced valued for the host header to control what is returned by {@link ServletRequest#getServerName()} and {@link ServletRequest#getServerPort()}. - * -- * @param hostHeader -- * The value of the host header to force. -+ * @param hostHeader The value of the host header to force. - */ - @Deprecated - public void setHostHeader(String hostHeader) -@@ -494,7 +513,7 @@ public void setHostHeader(String hostHeader) - HostPortHttpField _for; - HostPortHttpField _host; - String _proto; -- -+ - private RFC7239() - { - super(false); -@@ -503,27 +522,27 @@ private RFC7239() - @Override - protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) - { -- if (valueLength==0 && paramValue>paramName) -+ if (valueLength == 0 && paramValue > paramName) - { -- String name=StringUtil.asciiToLowerCase(buffer.substring(paramName,paramValue-1)); -- String value=buffer.substring(paramValue); -- switch(name) -+ String name = StringUtil.asciiToLowerCase(buffer.substring(paramName, paramValue - 1)); -+ String value = buffer.substring(paramValue); -+ switch (name) - { - case "by": -- if (_by==null && !value.startsWith("_") && !"unknown".equals(value)) -- _by=new HostPortHttpField(value); -+ if (_by == null && !value.startsWith("_") && !"unknown".equals(value)) -+ _by = new HostPortHttpField(value); - break; - case "for": -- if (_for==null && !value.startsWith("_") && !"unknown".equals(value)) -- _for=new HostPortHttpField(value); -+ if (_for == null && !value.startsWith("_") && !"unknown".equals(value)) -+ _for = new HostPortHttpField(value); - break; - case "host": -- if (_host==null) -- _host=new HostPortHttpField(value); -+ if (_host == null) -+ _host = new HostPortHttpField(value); - break; - case "proto": -- if (_proto==null) -- _proto=value; -+ if (_proto == null) -+ _proto = value; - break; - } - } -@@ -536,7 +555,7 @@ private void updateHandles() - MethodHandles.Lookup lookup = MethodHandles.lookup(); - MethodType type = methodType(Void.TYPE, HttpField.class); - -- while(true) -+ while (true) - { - try - { -@@ -563,7 +582,7 @@ private void updateHandles() - continue; - break; - } -- catch (NoSuchMethodException|IllegalAccessException e) -+ catch (NoSuchMethodException | IllegalAccessException e) - { - throw new IllegalStateException(e); - } -@@ -591,7 +610,7 @@ public Forwarded(Request request, HttpConfiguration config) - - public void handleCipherSuite(HttpField field) - { -- _request.setAttribute("javax.servlet.request.cipher_suite",field.getValue()); -+ _request.setAttribute("javax.servlet.request.cipher_suite", field.getValue()); - if (isSslIsSecure()) - { - _request.setSecure(true); -@@ -633,6 +652,7 @@ public void handlePort(HttpField field) - { - _forwardedPort = field.getIntValue(); - } -+ - public void handleHttps(HttpField field) - { - _forwardedHttps = getLeftMost(field.getValue()); -@@ -640,11 +660,9 @@ public void handleHttps(HttpField field) - - public void handleRFC7239(HttpField field) - { -- if (_rfc7239 ==null) -+ if (_rfc7239 == null) - _rfc7239 = new RFC7239(); - _rfc7239.addValue(field.getValue()); - } -- -- - } - } diff --git a/CVE-2020-27223-pre-4.patch b/CVE-2020-27223-pre-4.patch deleted file mode 100644 index b77d1802f73ffbaee37fb75ba8b5003e72efcfdc..0000000000000000000000000000000000000000 --- a/CVE-2020-27223-pre-4.patch +++ /dev/null @@ -1,1167 +0,0 @@ -From cec50b3d2cea7bdf6b2f00d45c8554139aebbf87 Mon Sep 17 00:00:00 2001 -From: Greg Wilkins -Date: Wed, 8 May 2019 21:07:22 +0200 -Subject: [PATCH] Issue #3630 Optimized ForwardedRequestCustomizer - -Signed-off-by: Greg Wilkins ---- - .../eclipse/jetty/http/HostPortHttpField.java | 17 +- - .../org/eclipse/jetty/http/QuotedCSV.java | 264 +--------------- - .../eclipse/jetty/http/QuotedCSVParser.java | 288 ++++++++++++++++++ - .../server/ForwardedRequestCustomizer.java | 249 +++++++-------- - .../ForwardedRequestCustomizerTest.java | 99 +++++- - 5 files changed, 507 insertions(+), 410 deletions(-) - create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java - -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java -index dc386665339..6c9f3b921e8 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HostPortHttpField.java -@@ -22,9 +22,9 @@ - import org.eclipse.jetty.util.HostPort; - - -- --/* ------------------------------------------------------------ */ - /** -+ * A HttpField holding a preparsed Host and port number -+ * @see HostPort - */ - public class HostPortHttpField extends HttpField - { -@@ -35,7 +35,6 @@ public HostPortHttpField(String authority) - this(HttpHeader.HOST,HttpHeader.HOST.asString(),authority); - } - -- /* ------------------------------------------------------------ */ - protected HostPortHttpField(HttpHeader header, String name, String authority) - { - super(header,name,authority); -@@ -49,20 +48,17 @@ protected HostPortHttpField(HttpHeader header, String name, String authority) - } - } - -- /* ------------------------------------------------------------ */ - public HostPortHttpField(String host, int port) - { - this(new HostPort(host, port)); - } - -- /* ------------------------------------------------------------ */ -- protected HostPortHttpField(HostPort hostport) -+ public HostPortHttpField(HostPort hostport) - { - super(HttpHeader.HOST,HttpHeader.HOST.asString(),hostport.toString()); - _hostPort = hostport; - } - -- /* ------------------------------------------------------------ */ - /** Get the host. - * @return the host - */ -@@ -71,7 +67,6 @@ public String getHost() - return _hostPort.getHost(); - } - -- /* ------------------------------------------------------------ */ - /** Get the port. - * @return the port - */ -@@ -80,7 +75,6 @@ public int getPort() - return _hostPort.getPort(); - } - -- /* ------------------------------------------------------------ */ - /** Get the port. - * @param defaultPort The default port to return if no port set - * @return the port -@@ -89,4 +83,9 @@ public int getPort(int defaultPort) - { - return _hostPort.getPort(defaultPort); - } -+ -+ public HostPort getHostPort() -+ { -+ return _hostPort; -+ } - } -diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java -index 9ca7dbeec2c..8ebc2c36307 100644 ---- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java -+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java -@@ -22,8 +22,6 @@ - import java.util.Iterator; - import java.util.List; - --import org.eclipse.jetty.util.QuotedStringTokenizer; -- - /* ------------------------------------------------------------ */ - /** - * Implements a quoted comma separated list of values -@@ -32,226 +30,26 @@ - * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" - * @see "https://tools.ietf.org/html/rfc7230#section-7" - */ --public class QuotedCSV implements Iterable --{ -- private enum State { VALUE, PARAM_NAME, PARAM_VALUE}; -- -+public class QuotedCSV extends QuotedCSVParser implements Iterable -+{ - protected final List _values = new ArrayList<>(); -- protected final boolean _keepQuotes; -- -- /* ------------------------------------------------------------ */ -+ - public QuotedCSV(String... values) - { - this(true,values); - } - -- /* ------------------------------------------------------------ */ - public QuotedCSV(boolean keepQuotes,String... values) - { -- _keepQuotes=keepQuotes; -+ super(keepQuotes); - for (String v:values) - addValue(v); - } -- -- /* ------------------------------------------------------------ */ -- /** Add and parse a value string(s) -- * @param value A value that may contain one or more Quoted CSV items. -- */ -- public void addValue(String value) -- { -- if (value == null) -- return; -- -- StringBuffer buffer = new StringBuffer(); -- -- int l=value.length(); -- State state=State.VALUE; -- boolean quoted=false; -- boolean sloshed=false; -- int nws_length=0; -- int last_length=0; -- int value_length=-1; -- int param_name=-1; -- int param_value=-1; -- -- for (int i=0;i<=l;i++) -- { -- char c=i==l?0:value.charAt(i); -- -- // Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6 -- if (quoted && c!=0) -- { -- if (sloshed) -- sloshed=false; -- else -- { -- switch(c) -- { -- case '\\': -- sloshed=true; -- if (!_keepQuotes) -- continue; -- break; -- case '"': -- quoted=false; -- if (!_keepQuotes) -- continue; -- break; -- } -- } -- -- buffer.append(c); -- nws_length=buffer.length(); -- continue; -- } -- -- // Handle common cases -- switch(c) -- { -- case ' ': -- case '\t': -- if (buffer.length()>last_length) // not leading OWS -- buffer.append(c); -- continue; -- -- case '"': -- quoted=true; -- if (_keepQuotes) -- { -- if (state==State.PARAM_VALUE && param_value<0) -- param_value=nws_length; -- buffer.append(c); -- } -- else if (state==State.PARAM_VALUE && param_value<0) -- param_value=nws_length; -- nws_length=buffer.length(); -- continue; -- -- case ';': -- buffer.setLength(nws_length); // trim following OWS -- if (state==State.VALUE) -- { -- parsedValue(buffer); -- value_length=buffer.length(); -- } -- else -- parsedParam(buffer,value_length,param_name,param_value); -- nws_length=buffer.length(); -- param_name=param_value=-1; -- buffer.append(c); -- last_length=++nws_length; -- state=State.PARAM_NAME; -- continue; -- -- case ',': -- case 0: -- if (nws_length>0) -- { -- buffer.setLength(nws_length); // trim following OWS -- switch(state) -- { -- case VALUE: -- parsedValue(buffer); -- value_length=buffer.length(); -- break; -- case PARAM_NAME: -- case PARAM_VALUE: -- parsedParam(buffer,value_length,param_name,param_value); -- break; -- } -- _values.add(buffer.toString()); -- } -- buffer.setLength(0); -- last_length=0; -- nws_length=0; -- value_length=param_name=param_value=-1; -- state=State.VALUE; -- continue; -- -- case '=': -- switch (state) -- { -- case VALUE: -- // It wasn't really a value, it was a param name -- value_length=param_name=0; -- buffer.setLength(nws_length); // trim following OWS -- String param = buffer.toString(); -- buffer.setLength(0); -- parsedValue(buffer); -- value_length=buffer.length(); -- buffer.append(param); -- buffer.append(c); -- last_length=++nws_length; -- state=State.PARAM_VALUE; -- continue; -- -- case PARAM_NAME: -- buffer.setLength(nws_length); // trim following OWS -- buffer.append(c); -- last_length=++nws_length; -- state=State.PARAM_VALUE; -- continue; -- -- case PARAM_VALUE: -- if (param_value<0) -- param_value=nws_length; -- buffer.append(c); -- nws_length=buffer.length(); -- continue; -- } -- continue; -- -- default: -- { -- switch (state) -- { -- case VALUE: -- { -- buffer.append(c); -- nws_length=buffer.length(); -- continue; -- } -- -- case PARAM_NAME: -- { -- if (param_name<0) -- param_name=nws_length; -- buffer.append(c); -- nws_length=buffer.length(); -- continue; -- } -- -- case PARAM_VALUE: -- { -- if (param_value<0) -- param_value=nws_length; -- buffer.append(c); -- nws_length=buffer.length(); -- continue; -- } -- } -- } -- } -- } -- } -- -- /** -- * Called when a value has been parsed -- * @param buffer Containing the trimmed value, which may be mutated -- */ -- protected void parsedValue(StringBuffer buffer) -- { -- } - -- /** -- * Called when a parameter has been parsed -- * @param buffer Containing the trimmed value and all parameters, which may be mutated -- * @param valueLength The length of the value -- * @param paramName The index of the start of the parameter just parsed -- * @param paramValue The index of the start of the parameter value just parsed, or -1 -- */ -- protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) -+ @Override -+ protected void parsedValueAndParams(StringBuffer buffer) - { -+ _values.add(buffer.toString()); - } - - public int size() -@@ -274,55 +72,7 @@ public boolean isEmpty() - { - return _values.iterator(); - } -- -- public static String unquote(String s) -- { -- // handle trivial cases -- int l=s.length(); -- if (s==null || l==0) -- return s; -- -- // Look for any quotes -- int i=0; -- for (;ilast_length) // not leading OWS -+ buffer.append(c); -+ continue; -+ -+ case '"': -+ quoted=true; -+ if (_keepQuotes) -+ { -+ if (state==State.PARAM_VALUE && param_value<0) -+ param_value=nws_length; -+ buffer.append(c); -+ } -+ else if (state==State.PARAM_VALUE && param_value<0) -+ param_value=nws_length; -+ nws_length=buffer.length(); -+ continue; -+ -+ case ';': -+ buffer.setLength(nws_length); // trim following OWS -+ if (state==State.VALUE) -+ { -+ parsedValue(buffer); -+ value_length=buffer.length(); -+ } -+ else -+ parsedParam(buffer,value_length,param_name,param_value); -+ nws_length=buffer.length(); -+ param_name=param_value=-1; -+ buffer.append(c); -+ last_length=++nws_length; -+ state=State.PARAM_NAME; -+ continue; -+ -+ case ',': -+ case 0: -+ if (nws_length>0) -+ { -+ buffer.setLength(nws_length); // trim following OWS -+ switch(state) -+ { -+ case VALUE: -+ parsedValue(buffer); -+ value_length=buffer.length(); -+ break; -+ case PARAM_NAME: -+ case PARAM_VALUE: -+ parsedParam(buffer,value_length,param_name,param_value); -+ break; -+ } -+ parsedValueAndParams(buffer); -+ } -+ buffer.setLength(0); -+ last_length=0; -+ nws_length=0; -+ value_length=param_name=param_value=-1; -+ state=State.VALUE; -+ continue; -+ -+ case '=': -+ switch (state) -+ { -+ case VALUE: -+ // It wasn't really a value, it was a param name -+ value_length=param_name=0; -+ buffer.setLength(nws_length); // trim following OWS -+ String param = buffer.toString(); -+ buffer.setLength(0); -+ parsedValue(buffer); -+ value_length=buffer.length(); -+ buffer.append(param); -+ buffer.append(c); -+ last_length=++nws_length; -+ state=State.PARAM_VALUE; -+ continue; -+ -+ case PARAM_NAME: -+ buffer.setLength(nws_length); // trim following OWS -+ buffer.append(c); -+ last_length=++nws_length; -+ state=State.PARAM_VALUE; -+ continue; -+ -+ case PARAM_VALUE: -+ if (param_value<0) -+ param_value=nws_length; -+ buffer.append(c); -+ nws_length=buffer.length(); -+ continue; -+ } -+ continue; -+ -+ default: -+ { -+ switch (state) -+ { -+ case VALUE: -+ { -+ buffer.append(c); -+ nws_length=buffer.length(); -+ continue; -+ } -+ -+ case PARAM_NAME: -+ { -+ if (param_name<0) -+ param_name=nws_length; -+ buffer.append(c); -+ nws_length=buffer.length(); -+ continue; -+ } -+ -+ case PARAM_VALUE: -+ { -+ if (param_value<0) -+ param_value=nws_length; -+ buffer.append(c); -+ nws_length=buffer.length(); -+ continue; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ /** -+ * Called when a value and it's parameters has been parsed -+ * @param buffer Containing the trimmed value and parameters -+ */ -+ protected void parsedValueAndParams(StringBuffer buffer) -+ { -+ } -+ -+ /** -+ * Called when a value has been parsed (prior to any parameters) -+ * @param buffer Containing the trimmed value, which may be mutated -+ */ -+ protected void parsedValue(StringBuffer buffer) -+ { -+ } -+ -+ /** -+ * Called when a parameter has been parsed -+ * @param buffer Containing the trimmed value and all parameters, which may be mutated -+ * @param valueLength The length of the value -+ * @param paramName The index of the start of the parameter just parsed -+ * @param paramValue The index of the start of the parameter value just parsed, or -1 -+ */ -+ protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) -+ { -+ } -+ -+} -diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -index be3990794ed..e098a68e635 100644 ---- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -+++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java -@@ -30,7 +30,7 @@ - import org.eclipse.jetty.http.HttpFields; - import org.eclipse.jetty.http.HttpHeader; - import org.eclipse.jetty.http.HttpScheme; --import org.eclipse.jetty.http.QuotedCSV; -+import org.eclipse.jetty.http.QuotedCSVParser; - import org.eclipse.jetty.server.HttpConfiguration.Customizer; - import org.eclipse.jetty.util.ArrayTrie; - import org.eclipse.jetty.util.HostPort; -@@ -42,8 +42,6 @@ - import static java.lang.invoke.MethodType.methodType; - - --/* ------------------------------------------------------------ */ -- - /** - * Customize Requests for Proxy Forwarding. - *

-@@ -157,7 +155,7 @@ public String getForcedHost() - */ - public void setForcedHost(String hostAndPort) - { -- _forcedHost = new HostPortHttpField(hostAndPort); -+ _forcedHost = new HostPortHttpField(new ForcedHostPort(hostAndPort)); - } - - /** -@@ -377,76 +375,26 @@ public void customize(Connector connector, HttpConfiguration config, Request req - throw new RuntimeException(e); - } - -- // Determine host -- if (_forcedHost != null) -- { -- // Update host header -- httpFields.put(_forcedHost); -- request.setAuthority(_forcedHost.getHost(), _forcedHost.getPort()); -- } -- else if (forwarded._rfc7239 != null && forwarded._rfc7239._host != null) -- { -- HostPortHttpField auth = forwarded._rfc7239._host; -- httpFields.put(auth); -- request.setAuthority(auth.getHost(), auth.getPort()); -- } -- else if (forwarded._forwardedHost != null) -- { -- HostPortHttpField auth = new HostPortHttpField(forwarded._forwardedHost); -- httpFields.put(auth); -- request.setAuthority(auth.getHost(), auth.getPort()); -- } -- else if (_proxyAsAuthority) -+ if (forwarded._proto!=null) - { -- if (forwarded._rfc7239 != null && forwarded._rfc7239._by != null) -- { -- HostPortHttpField auth = forwarded._rfc7239._by; -- httpFields.put(auth); -- request.setAuthority(auth.getHost(), auth.getPort()); -- } -- else if (forwarded._forwardedServer != null) -- { -- request.setAuthority(forwarded._forwardedServer, request.getServerPort()); -- } -+ request.setScheme(forwarded._proto); -+ if (forwarded._proto.equalsIgnoreCase(config.getSecureScheme())) -+ request.setSecure(true); - } - -- // handle remote end identifier -- if (forwarded._rfc7239 != null && forwarded._rfc7239._for != null) -- { -- request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._rfc7239._for.getHost(), forwarded._rfc7239._for.getPort())); -- } -- else if (forwarded._forwardedFor != null) -+ if (forwarded._host!=null) - { -- int port = (forwarded._forwardedPort > 0) -- ? forwarded._forwardedPort -- : (forwarded._forwardedFor.getPort() > 0) -- ? forwarded._forwardedFor.getPort() -- : request.getRemotePort(); -- request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._forwardedFor.getHost(), port)); -+ httpFields.put(new HostPortHttpField(forwarded._host)); -+ request.setAuthority(forwarded._host.getHost(), forwarded._host.getPort()); - } - -- // handle protocol identifier -- if (forwarded._rfc7239 != null && forwarded._rfc7239._proto != null) -+ if (forwarded._for!=null) - { -- request.setScheme(forwarded._rfc7239._proto); -- if (forwarded._rfc7239._proto.equals(config.getSecureScheme())) -- request.setSecure(true); -- } -- else if (forwarded._forwardedProto != null) -- { -- request.setScheme(forwarded._forwardedProto); -- if (forwarded._forwardedProto.equals(config.getSecureScheme())) -- request.setSecure(true); -- } -- else if (forwarded._forwardedHttps != null && ("on".equalsIgnoreCase(forwarded._forwardedHttps) || "true".equalsIgnoreCase(forwarded._forwardedHttps))) -- { -- request.setScheme(HttpScheme.HTTPS.asString()); -- if (HttpScheme.HTTPS.asString().equals(config.getSecureScheme())) -- request.setSecure(true); -+ int port = forwarded._for.getPort()>0 ? forwarded._for.getPort() : request.getRemotePort(); -+ request.setRemoteAddr(InetSocketAddress.createUnresolved(forwarded._for.getHost(),port)); - } - } - -- /* ------------------------------------------------------------ */ - protected String getLeftMost(String headerValue) - { - if (headerValue == null) -@@ -464,26 +412,6 @@ protected String getLeftMost(String headerValue) - return headerValue.substring(0, commaIndex).trim(); - } - -- protected HostPort getRemoteAddr(String headerValue) -- { -- String leftMost = getLeftMost(headerValue); -- if (leftMost == null) -- { -- return null; -- } -- -- try -- { -- return new HostPort(leftMost); -- } -- catch (Exception e) -- { -- // failed to parse in host[:port] format -- LOG.ignore(e); -- return null; -- } -- } -- - @Override - public String toString() - { -@@ -507,48 +435,6 @@ public void setHostHeader(String hostHeader) - _forcedHost = new HostPortHttpField(hostHeader); - } - -- private final class RFC7239 extends QuotedCSV -- { -- HostPortHttpField _by; -- HostPortHttpField _for; -- HostPortHttpField _host; -- String _proto; -- -- private RFC7239() -- { -- super(false); -- } -- -- @Override -- protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) -- { -- if (valueLength == 0 && paramValue > paramName) -- { -- String name = StringUtil.asciiToLowerCase(buffer.substring(paramName, paramValue - 1)); -- String value = buffer.substring(paramValue); -- switch (name) -- { -- case "by": -- if (_by == null && !value.startsWith("_") && !"unknown".equals(value)) -- _by = new HostPortHttpField(value); -- break; -- case "for": -- if (_for == null && !value.startsWith("_") && !"unknown".equals(value)) -- _for = new HostPortHttpField(value); -- break; -- case "host": -- if (_host == null) -- _host = new HostPortHttpField(value); -- break; -- case "proto": -- if (_proto == null) -- _proto = value; -- break; -- } -- } -- } -- } -- - private void updateHandles() - { - int size = 0; -@@ -589,23 +475,52 @@ private void updateHandles() - } - } - -- private class Forwarded -+ private static class ForcedHostPort extends HostPort -+ { -+ ForcedHostPort(String authority) -+ { -+ super(authority); -+ } -+ } -+ -+ private static class XHostPort extends HostPort -+ { -+ XHostPort(String authority) -+ { -+ super(authority); -+ } -+ -+ XHostPort(String host, int port) -+ { -+ super(host, port); -+ } -+ } -+ -+ private static class Rfc7239HostPort extends HostPort -+ { -+ Rfc7239HostPort(String authority) -+ { -+ super(authority); -+ } -+ } -+ -+ private class Forwarded extends QuotedCSVParser - { - HttpConfiguration _config; - Request _request; - -- RFC7239 _rfc7239 = null; -- String _forwardedHost = null; -- String _forwardedServer = null; -- String _forwardedProto = null; -- HostPort _forwardedFor = null; -- int _forwardedPort = -1; -- String _forwardedHttps = null; -+ boolean _protoRfc7239; -+ String _proto; -+ HostPort _for; -+ HostPort _host; - - public Forwarded(Request request, HttpConfiguration config) - { -+ super(false); - _request = request; - _config = config; -+ if (_forcedHost!=null) -+ _host = _forcedHost.getHostPort(); - } - - public void handleCipherSuite(HttpField field) -@@ -630,39 +545,87 @@ public void handleSslSessionId(HttpField field) - - public void handleHost(HttpField field) - { -- _forwardedHost = getLeftMost(field.getValue()); -+ if (_host==null) -+ _host = new XHostPort(getLeftMost(field.getValue())); - } - - public void handleServer(HttpField field) - { -- _forwardedServer = getLeftMost(field.getValue()); -+ if (_proxyAsAuthority && _host==null) -+ _host = new XHostPort(getLeftMost(field.getValue())); - } - - public void handleProto(HttpField field) - { -- _forwardedProto = getLeftMost(field.getValue()); -+ if (_proto==null) -+ _proto = getLeftMost(field.getValue()); - } - - public void handleFor(HttpField field) - { -- _forwardedFor = getRemoteAddr(field.getValue()); -+ if (_for==null) -+ _for = new XHostPort(getLeftMost(field.getValue())); -+ else if (_for instanceof XHostPort && "unknown".equals(_for.getHost())) -+ _for = new XHostPort(HostPort.normalizeHost(getLeftMost(field.getValue())),_for.getPort()); - } - - public void handlePort(HttpField field) - { -- _forwardedPort = field.getIntValue(); -+ if (_for == null) -+ _for = new XHostPort("unknown", field.getIntValue()); -+ else if (_for instanceof XHostPort && _for.getPort()<=0) -+ _for = new XHostPort(HostPort.normalizeHost(_for.getHost()), field.getIntValue()); - } - - public void handleHttps(HttpField field) - { -- _forwardedHttps = getLeftMost(field.getValue()); -+ if (_proto==null && ("on".equalsIgnoreCase(field.getValue()) || "true".equalsIgnoreCase(field.getValue()))) -+ _proto = HttpScheme.HTTPS.asString(); - } - - public void handleRFC7239(HttpField field) - { -- if (_rfc7239 == null) -- _rfc7239 = new RFC7239(); -- _rfc7239.addValue(field.getValue()); -+ addValue(field.getValue()); -+ } -+ -+ @Override -+ protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) -+ { -+ if (valueLength == 0 && paramValue > paramName) -+ { -+ String name = StringUtil.asciiToLowerCase(buffer.substring(paramName, paramValue - 1)); -+ String value = buffer.substring(paramValue); -+ switch (name) -+ { -+ case "by": -+ if (!_proxyAsAuthority) -+ break; -+ if (value.startsWith("_") || "unknown".equals(value)) -+ break; -+ if (_host == null || _host instanceof XHostPort) -+ _host = new Rfc7239HostPort(value); -+ break; -+ case "for": -+ if (value.startsWith("_") || "unknown".equals(value)) -+ break; -+ if (_for == null || _for instanceof XHostPort) -+ _for = new Rfc7239HostPort(value); -+ break; -+ case "host": -+ if (value.startsWith("_") || "unknown".equals(value)) -+ break; -+ if (_host == null || _host instanceof XHostPort) -+ _host = new Rfc7239HostPort(value); -+ break; -+ case "proto": -+ if (_proto == null || !_protoRfc7239) -+ { -+ _protoRfc7239 = true; -+ _proto = value; -+ } -+ break; -+ } -+ } - } - } - } -diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java -index 0a1cecec0c2..db8a9ad64e0 100644 ---- a/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java -+++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java -@@ -95,6 +95,68 @@ public void destroy() throws Exception - _server.join(); - } - -+ @Test -+ public void testHostIpv4() throws Exception -+ { -+ String response=_connector.getResponse( -+ "GET / HTTP/1.1\n"+ -+ "Host: 1.2.3.4:2222\n"+ -+ "\n"); -+ assertThat(response, Matchers.containsString("200 OK")); -+ assertEquals("http",_results.poll()); -+ assertEquals("1.2.3.4",_results.poll()); -+ assertEquals("2222",_results.poll()); -+ assertEquals("0.0.0.0",_results.poll()); -+ assertEquals("0",_results.poll()); -+ } -+ -+ @Test -+ public void testHostIpv6() throws Exception -+ { -+ String response=_connector.getResponse( -+ "GET / HTTP/1.1\n"+ -+ "Host: [::1]:2222\n"+ -+ "\n"); -+ assertThat(response, Matchers.containsString("200 OK")); -+ assertEquals("http",_results.poll()); -+ assertEquals("[::1]",_results.poll()); -+ assertEquals("2222",_results.poll()); -+ assertEquals("0.0.0.0",_results.poll()); -+ assertEquals("0",_results.poll()); -+ } -+ -+ -+ -+ @Test -+ public void testURIIpv4() throws Exception -+ { -+ String response=_connector.getResponse( -+ "GET http://1.2.3.4:2222/ HTTP/1.1\n"+ -+ "Host: wrong\n"+ -+ "\n"); -+ assertThat(response, Matchers.containsString("200 OK")); -+ assertEquals("http",_results.poll()); -+ assertEquals("1.2.3.4",_results.poll()); -+ assertEquals("2222",_results.poll()); -+ assertEquals("0.0.0.0",_results.poll()); -+ assertEquals("0",_results.poll()); -+ } -+ -+ @Test -+ public void testURIIpv6() throws Exception -+ { -+ String response=_connector.getResponse( -+ "GET http://[::1]:2222/ HTTP/1.1\n"+ -+ "Host: wrong\n"+ -+ "\n"); -+ assertThat(response, Matchers.containsString("200 OK")); -+ assertEquals("http",_results.poll()); -+ assertEquals("[::1]",_results.poll()); -+ assertEquals("2222",_results.poll()); -+ assertEquals("0.0.0.0",_results.poll()); -+ assertEquals("0",_results.poll()); -+ } -+ - - @Test - public void testRFC7239_Examples_4() throws Exception -@@ -208,6 +270,7 @@ public void testFor() throws Exception - "GET / HTTP/1.1\n"+ - "Host: myhost\n"+ - "X-Forwarded-For: 10.9.8.7,6.5.4.3\n"+ -+ "X-Forwarded-For: 8.9.8.7,7.5.4.3\n"+ - "\n"); - assertThat(response, Matchers.containsString("200 OK")); - assertEquals("http",_results.poll()); -@@ -264,6 +327,21 @@ public void testForIpv6AndPort() throws Exception - assertEquals("80",_results.poll()); - assertEquals("[1:2:3:4:5:6:7:8]",_results.poll()); - assertEquals("2222",_results.poll()); -+ -+ response=_connector.getResponse( -+ "GET / HTTP/1.1\n"+ -+ "Host: myhost\n"+ -+ "X-Forwarded-Port: 2222\n"+ -+ "X-Forwarded-For: 1:2:3:4:5:6:7:8\n"+ -+ "X-Forwarded-For: 7:7:7:7:7:7:7:7\n"+ -+ "X-Forwarded-Port: 3333\n"+ -+ "\n"); -+ assertThat(response, Matchers.containsString("200 OK")); -+ assertEquals("http",_results.poll()); -+ assertEquals("myhost",_results.poll()); -+ assertEquals("80",_results.poll()); -+ assertEquals("[1:2:3:4:5:6:7:8]",_results.poll()); -+ assertEquals("2222",_results.poll()); - } - - @Test -@@ -355,7 +433,26 @@ public void testSslCertificate() throws Exception - assertTrue(_wasSecure.get()); - assertEquals("0123456789abcdef",_sslCertificate.get()); - } -- -+ -+ -+ @Test -+ public void testMixed() throws Exception -+ { -+ String response = _connector.getResponse( -+ "GET / HTTP/1.1\n" + -+ "Host: myhost\n" + -+ "X-Forwarded-For: 11.9.8.7:1111,8.5.4.3:2222\n" + -+ "X-Forwarded-Port: 3333\n" + -+ "Forwarded: for=192.0.2.43,for=198.51.100.17;by=203.0.113.60;proto=http;host=example.com\n"+ -+ "X-Forwarded-For: 11.9.8.7:1111,8.5.4.3:2222\n" + -+ "\n"); -+ -+ assertEquals("http",_results.poll()); -+ assertEquals("example.com",_results.poll()); -+ assertEquals("80",_results.poll()); -+ assertEquals("192.0.2.43",_results.poll()); -+ assertEquals("0",_results.poll()); -+ } - - - interface RequestTester diff --git a/CVE-2020-27223.patch b/CVE-2020-27223.patch old mode 100644 new mode 100755 index ab24788fbcb43b79cd67494788a086ec0fd94b28..7d31ce49eb56789ae332820325ac68e8f54aac83 --- a/CVE-2020-27223.patch +++ b/CVE-2020-27223.patch @@ -1,22 +1,670 @@ -From 10e531756b972162eed402c44d0244f7f6b85131 Mon Sep 17 00:00:00 2001 -From: Joakim Erdfelt -Date: Thu, 18 Feb 2021 07:14:38 -0600 -Subject: [PATCH] Merge pull request from GHSA-m394-8rww-3jr7 +From: Markus Koschany +Date: Sat, 31 Jul 2021 17:21:57 +0200 +Subject: CVE-2020-27223 -Use comparator based sort -Signed-off-by: Joakim Erdfelt -Signed-off-by: gregw - -Co-authored-by: gregw --- - .../eclipse/jetty/http/QuotedQualityCSV.java | 117 +++++++++++++----- - 1 file changed, 86 insertions(+), 31 deletions(-) + .../java/org/eclipse/jetty/http/QuotedCSV.java | 280 ++----------------- + .../org/eclipse/jetty/http/QuotedCSVParser.java | 303 +++++++++++++++++++++ + .../org/eclipse/jetty/http/QuotedQualityCSV.java | 140 ++++++---- + .../eclipse/jetty/http/QuotedQualityCSVTest.java | 143 +++++----- + 4 files changed, 479 insertions(+), 387 deletions(-) + create mode 100644 jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java +diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java +index 9ca7dbe..a356213 100644 +--- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java ++++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSV.java +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -22,236 +22,36 @@ import java.util.ArrayList; + import java.util.Iterator; + import java.util.List; + +-import org.eclipse.jetty.util.QuotedStringTokenizer; +- +-/* ------------------------------------------------------------ */ + /** + * Implements a quoted comma separated list of values + * in accordance with RFC7230. + * OWS is removed and quoted characters ignored for parsing. ++ * + * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" + * @see "https://tools.ietf.org/html/rfc7230#section-7" + */ +-public class QuotedCSV implements Iterable +-{ +- private enum State { VALUE, PARAM_NAME, PARAM_VALUE}; +- ++public class QuotedCSV extends QuotedCSVParser implements Iterable ++{ + protected final List _values = new ArrayList<>(); +- protected final boolean _keepQuotes; +- +- /* ------------------------------------------------------------ */ ++ + public QuotedCSV(String... values) + { +- this(true,values); ++ this(true, values); + } +- +- /* ------------------------------------------------------------ */ +- public QuotedCSV(boolean keepQuotes,String... values) +- { +- _keepQuotes=keepQuotes; +- for (String v:values) +- addValue(v); +- } +- +- /* ------------------------------------------------------------ */ +- /** Add and parse a value string(s) +- * @param value A value that may contain one or more Quoted CSV items. +- */ +- public void addValue(String value) ++ ++ public QuotedCSV(boolean keepQuotes, String... values) + { +- if (value == null) +- return; +- +- StringBuffer buffer = new StringBuffer(); +- +- int l=value.length(); +- State state=State.VALUE; +- boolean quoted=false; +- boolean sloshed=false; +- int nws_length=0; +- int last_length=0; +- int value_length=-1; +- int param_name=-1; +- int param_value=-1; +- +- for (int i=0;i<=l;i++) ++ super(keepQuotes); ++ for (String v : values) + { +- char c=i==l?0:value.charAt(i); +- +- // Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6 +- if (quoted && c!=0) +- { +- if (sloshed) +- sloshed=false; +- else +- { +- switch(c) +- { +- case '\\': +- sloshed=true; +- if (!_keepQuotes) +- continue; +- break; +- case '"': +- quoted=false; +- if (!_keepQuotes) +- continue; +- break; +- } +- } +- +- buffer.append(c); +- nws_length=buffer.length(); +- continue; +- } +- +- // Handle common cases +- switch(c) +- { +- case ' ': +- case '\t': +- if (buffer.length()>last_length) // not leading OWS +- buffer.append(c); +- continue; +- +- case '"': +- quoted=true; +- if (_keepQuotes) +- { +- if (state==State.PARAM_VALUE && param_value<0) +- param_value=nws_length; +- buffer.append(c); +- } +- else if (state==State.PARAM_VALUE && param_value<0) +- param_value=nws_length; +- nws_length=buffer.length(); +- continue; +- +- case ';': +- buffer.setLength(nws_length); // trim following OWS +- if (state==State.VALUE) +- { +- parsedValue(buffer); +- value_length=buffer.length(); +- } +- else +- parsedParam(buffer,value_length,param_name,param_value); +- nws_length=buffer.length(); +- param_name=param_value=-1; +- buffer.append(c); +- last_length=++nws_length; +- state=State.PARAM_NAME; +- continue; +- +- case ',': +- case 0: +- if (nws_length>0) +- { +- buffer.setLength(nws_length); // trim following OWS +- switch(state) +- { +- case VALUE: +- parsedValue(buffer); +- value_length=buffer.length(); +- break; +- case PARAM_NAME: +- case PARAM_VALUE: +- parsedParam(buffer,value_length,param_name,param_value); +- break; +- } +- _values.add(buffer.toString()); +- } +- buffer.setLength(0); +- last_length=0; +- nws_length=0; +- value_length=param_name=param_value=-1; +- state=State.VALUE; +- continue; +- +- case '=': +- switch (state) +- { +- case VALUE: +- // It wasn't really a value, it was a param name +- value_length=param_name=0; +- buffer.setLength(nws_length); // trim following OWS +- String param = buffer.toString(); +- buffer.setLength(0); +- parsedValue(buffer); +- value_length=buffer.length(); +- buffer.append(param); +- buffer.append(c); +- last_length=++nws_length; +- state=State.PARAM_VALUE; +- continue; +- +- case PARAM_NAME: +- buffer.setLength(nws_length); // trim following OWS +- buffer.append(c); +- last_length=++nws_length; +- state=State.PARAM_VALUE; +- continue; +- +- case PARAM_VALUE: +- if (param_value<0) +- param_value=nws_length; +- buffer.append(c); +- nws_length=buffer.length(); +- continue; +- } +- continue; +- +- default: +- { +- switch (state) +- { +- case VALUE: +- { +- buffer.append(c); +- nws_length=buffer.length(); +- continue; +- } +- +- case PARAM_NAME: +- { +- if (param_name<0) +- param_name=nws_length; +- buffer.append(c); +- nws_length=buffer.length(); +- continue; +- } +- +- case PARAM_VALUE: +- { +- if (param_value<0) +- param_value=nws_length; +- buffer.append(c); +- nws_length=buffer.length(); +- continue; +- } +- } +- } +- } ++ addValue(v); + } + } + +- /** +- * Called when a value has been parsed +- * @param buffer Containing the trimmed value, which may be mutated +- */ +- protected void parsedValue(StringBuffer buffer) +- { +- } +- +- /** +- * Called when a parameter has been parsed +- * @param buffer Containing the trimmed value and all parameters, which may be mutated +- * @param valueLength The length of the value +- * @param paramName The index of the start of the parameter just parsed +- * @param paramValue The index of the start of the parameter value just parsed, or -1 +- */ +- protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) ++ @Override ++ protected void parsedValueAndParams(StringBuffer buffer) + { ++ _values.add(buffer.toString()); + } + + public int size() +@@ -268,67 +68,21 @@ public class QuotedCSV implements Iterable + { + return _values; + } +- ++ + @Override + public Iterator iterator() + { + return _values.iterator(); + } +- +- public static String unquote(String s) +- { +- // handle trivial cases +- int l=s.length(); +- if (s==null || l==0) +- return s; +- +- // Look for any quotes +- int i=0; +- for (;i list = new ArrayList<>(); + for (String s : this) ++ { + list.add(s); ++ } + return list.toString(); + } + } +diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java +new file mode 100644 +index 0000000..7aefcf7 +--- /dev/null ++++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java +@@ -0,0 +1,303 @@ ++// ++// ======================================================================== ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. ++// ------------------------------------------------------------------------ ++// 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; ++ ++/** ++ * Implements a quoted comma separated list parser ++ * in accordance with RFC7230. ++ * OWS is removed and quoted characters ignored for parsing. ++ * ++ * @see "https://tools.ietf.org/html/rfc7230#section-3.2.6" ++ * @see "https://tools.ietf.org/html/rfc7230#section-7" ++ */ ++public abstract class QuotedCSVParser ++{ ++ private enum State ++ { ++ VALUE, PARAM_NAME, PARAM_VALUE ++ } ++ ++ protected final boolean _keepQuotes; ++ ++ public QuotedCSVParser(boolean keepQuotes) ++ { ++ _keepQuotes = keepQuotes; ++ } ++ ++ public static String unquote(String s) ++ { ++ // handle trivial cases ++ int l = s.length(); ++ if (s == null || l == 0) ++ return s; ++ ++ // Look for any quotes ++ int i = 0; ++ for (; i < l; i++) ++ { ++ char c = s.charAt(i); ++ if (c == '"') ++ break; ++ } ++ if (i == l) ++ return s; ++ ++ boolean quoted = true; ++ boolean sloshed = false; ++ StringBuffer buffer = new StringBuffer(); ++ buffer.append(s, 0, i); ++ i++; ++ for (; i < l; i++) ++ { ++ char c = s.charAt(i); ++ if (quoted) ++ { ++ if (sloshed) ++ { ++ buffer.append(c); ++ sloshed = false; ++ } ++ else if (c == '"') ++ quoted = false; ++ else if (c == '\\') ++ sloshed = true; ++ else ++ buffer.append(c); ++ } ++ else if (c == '"') ++ quoted = true; ++ else ++ buffer.append(c); ++ } ++ return buffer.toString(); ++ } ++ ++ /** ++ * Add and parse a value string(s) ++ * ++ * @param value A value that may contain one or more Quoted CSV items. ++ */ ++ public void addValue(String value) ++ { ++ if (value == null) ++ return; ++ ++ StringBuffer buffer = new StringBuffer(); ++ ++ int l = value.length(); ++ State state = State.VALUE; ++ boolean quoted = false; ++ boolean sloshed = false; ++ int nwsLength = 0; ++ int lastLength = 0; ++ int valueLength = -1; ++ int paramName = -1; ++ int paramValue = -1; ++ ++ for (int i = 0; i <= l; i++) ++ { ++ char c = i == l ? 0 : value.charAt(i); ++ ++ // Handle quoting https://tools.ietf.org/html/rfc7230#section-3.2.6 ++ if (quoted && c != 0) ++ { ++ if (sloshed) ++ sloshed = false; ++ else ++ { ++ switch (c) ++ { ++ case '\\': ++ sloshed = true; ++ if (!_keepQuotes) ++ continue; ++ break; ++ case '"': ++ quoted = false; ++ if (!_keepQuotes) ++ continue; ++ break; ++ } ++ } ++ ++ buffer.append(c); ++ nwsLength = buffer.length(); ++ continue; ++ } ++ ++ // Handle common cases ++ switch (c) ++ { ++ case ' ': ++ case '\t': ++ if (buffer.length() > lastLength) // not leading OWS ++ buffer.append(c); ++ continue; ++ ++ case '"': ++ quoted = true; ++ if (_keepQuotes) ++ { ++ if (state == State.PARAM_VALUE && paramValue < 0) ++ paramValue = nwsLength; ++ buffer.append(c); ++ } ++ else if (state == State.PARAM_VALUE && paramValue < 0) ++ paramValue = nwsLength; ++ nwsLength = buffer.length(); ++ continue; ++ ++ case ';': ++ buffer.setLength(nwsLength); // trim following OWS ++ if (state == State.VALUE) ++ { ++ parsedValue(buffer); ++ valueLength = buffer.length(); ++ } ++ else ++ parsedParam(buffer, valueLength, paramName, paramValue); ++ nwsLength = buffer.length(); ++ paramName = paramValue = -1; ++ buffer.append(c); ++ lastLength = ++nwsLength; ++ state = State.PARAM_NAME; ++ continue; ++ ++ case ',': ++ case 0: ++ if (nwsLength > 0) ++ { ++ buffer.setLength(nwsLength); // trim following OWS ++ switch (state) ++ { ++ case VALUE: ++ parsedValue(buffer); ++ valueLength = buffer.length(); ++ break; ++ case PARAM_NAME: ++ case PARAM_VALUE: ++ parsedParam(buffer, valueLength, paramName, paramValue); ++ break; ++ } ++ parsedValueAndParams(buffer); ++ } ++ buffer.setLength(0); ++ lastLength = 0; ++ nwsLength = 0; ++ valueLength = paramName = paramValue = -1; ++ state = State.VALUE; ++ continue; ++ ++ case '=': ++ switch (state) ++ { ++ case VALUE: ++ // It wasn't really a value, it was a param name ++ valueLength = paramName = 0; ++ buffer.setLength(nwsLength); // trim following OWS ++ String param = buffer.toString(); ++ buffer.setLength(0); ++ parsedValue(buffer); ++ valueLength = buffer.length(); ++ buffer.append(param); ++ buffer.append(c); ++ lastLength = ++nwsLength; ++ state = State.PARAM_VALUE; ++ continue; ++ ++ case PARAM_NAME: ++ buffer.setLength(nwsLength); // trim following OWS ++ buffer.append(c); ++ lastLength = ++nwsLength; ++ state = State.PARAM_VALUE; ++ continue; ++ ++ case PARAM_VALUE: ++ if (paramValue < 0) ++ paramValue = nwsLength; ++ buffer.append(c); ++ nwsLength = buffer.length(); ++ continue; ++ } ++ continue; ++ ++ default: ++ { ++ switch (state) ++ { ++ case VALUE: ++ { ++ buffer.append(c); ++ nwsLength = buffer.length(); ++ continue; ++ } ++ ++ case PARAM_NAME: ++ { ++ if (paramName < 0) ++ paramName = nwsLength; ++ buffer.append(c); ++ nwsLength = buffer.length(); ++ continue; ++ } ++ ++ case PARAM_VALUE: ++ { ++ if (paramValue < 0) ++ paramValue = nwsLength; ++ buffer.append(c); ++ nwsLength = buffer.length(); ++ continue; ++ } ++ } ++ } ++ } ++ } ++ } ++ ++ /** ++ * Called when a value and it's parameters has been parsed ++ * ++ * @param buffer Containing the trimmed value and parameters ++ */ ++ protected void parsedValueAndParams(StringBuffer buffer) ++ { ++ } ++ ++ /** ++ * Called when a value has been parsed (prior to any parameters) ++ * ++ * @param buffer Containing the trimmed value, which may be mutated ++ */ ++ protected void parsedValue(StringBuffer buffer) ++ { ++ } ++ ++ /** ++ * Called when a parameter has been parsed ++ * ++ * @param buffer Containing the trimmed value and all parameters, which may be mutated ++ * @param valueLength The length of the value ++ * @param paramName The index of the start of the parameter just parsed ++ * @param paramValue The index of the start of the parameter value just parsed, or -1 ++ */ ++ protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) ++ { ++ } ++} diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -index d148d9e..67f9981 100644 +index d148d9e..5bc9985 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java -@@ -21,12 +21,12 @@ package org.eclipse.jetty.http; +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -21,14 +21,12 @@ package org.eclipse.jetty.http; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -28,10 +676,12 @@ index d148d9e..67f9981 100644 -import static java.lang.Integer.MIN_VALUE; - - /* ------------------------------------------------------------ */ - +-/* ------------------------------------------------------------ */ +- /** -@@ -57,7 +57,8 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable + * Implements a quoted comma separated list of quality values + * in accordance with RFC7230 and RFC7231. +@@ -57,22 +55,19 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable return 3; }; @@ -41,7 +691,10 @@ index d148d9e..67f9981 100644 private boolean _sorted = false; private final ToIntFunction _secondaryOrdering; -@@ -68,7 +69,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable +- /* ------------------------------------------------------------ */ +- + /** + * Sorts values with equal quality according to the length of the value String. */ public QuotedQualityCSV() { @@ -49,8 +702,20 @@ index d148d9e..67f9981 100644 + this((ToIntFunction)null); } - /* ------------------------------------------------------------ */ -@@ -89,7 +90,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable +- /* ------------------------------------------------------------ */ +- + /** + * Sorts values with equal quality according to given order. + * +@@ -83,57 +78,71 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable + this((s) -> + { + for (int i = 0; i < preferredOrder.length; ++i) ++ { + if (preferredOrder[i].equals(s)) + return preferredOrder.length - i; ++ } + if ("*".equals(s)) return preferredOrder.length; @@ -59,7 +724,8 @@ index d148d9e..67f9981 100644 }); } -@@ -98,27 +99,43 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable +- /* ------------------------------------------------------------ */ +- /** * Orders values with equal quality with the given function. * @@ -71,50 +737,61 @@ index d148d9e..67f9981 100644 this._secondaryOrdering = secondaryOrdering == null ? s -> 0 : secondaryOrdering; } +- /* ------------------------------------------------------------ */ + @Override + protected void parsedValueAndParams(StringBuffer buffer) + { + super.parsedValueAndParams(buffer); + -+ // Collect full value with parameters -+ _lastQuality = new QualityValue(_lastQuality._quality, buffer.toString(), _lastQuality._index); -+ _qualities.set(_lastQuality._index, _lastQuality); ++ // Collect full value with parameters ++ _lastQuality = new QualityValue(_lastQuality._quality, buffer.toString(), _lastQuality._index); ++ _qualities.set(_lastQuality._index, _lastQuality); + } + - /* ------------------------------------------------------------ */ @Override protected void parsedValue(StringBuffer buffer) { super.parsedValue(buffer); -+ _sorted = false; ++ _sorted = false; + + // This is the just the value, without parameters. // Assume a quality of ONE - _quality.add(1.0D); -+ _lastQuality = new QualityValue(1.0D, buffer.toString(), _qualities.size()); -+ _qualities.add(_lastQuality); ++ _lastQuality = new QualityValue(1.0D, buffer.toString(), _qualities.size()); ++ _qualities.add(_lastQuality); } - /* ------------------------------------------------------------ */ +- /* ------------------------------------------------------------ */ @Override protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue) { -+ _sorted = false; ++ _sorted = false; + if (paramName < 0) { if (buffer.charAt(buffer.length() - 1) == ';') -@@ -128,7 +145,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable - buffer.charAt(paramName) == 'q' && paramValue > paramName && - buffer.length() >= paramName && buffer.charAt(paramName + 1) == '=') + buffer.setLength(buffer.length() - 1); + } + else if (paramValue >= 0 && +- buffer.charAt(paramName) == 'q' && paramValue > paramName && +- buffer.length() >= paramName && buffer.charAt(paramName + 1) == '=') ++ buffer.charAt(paramName) == 'q' && paramValue > paramName && ++ buffer.length() >= paramName && buffer.charAt(paramName + 1) == '=') { - Double q; + double q; try { q = (_keepQuotes && buffer.charAt(paramValue) == '"') -@@ -143,8 +160,10 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable +- ? Double.valueOf(buffer.substring(paramValue + 1, buffer.length() - 1)) +- : Double.valueOf(buffer.substring(paramValue)); ++ ? Double.valueOf(buffer.substring(paramValue + 1, buffer.length() - 1)) ++ : Double.valueOf(buffer.substring(paramValue)); + } + catch (Exception e) + { +@@ -143,8 +152,10 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable buffer.setLength(Math.max(0, paramName - 1)); if (q != 1.0D) @@ -127,65 +804,65 @@ index d148d9e..67f9981 100644 } } -@@ -166,38 +185,74 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable +@@ -166,38 +177,73 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable protected void sort() { -+ _values.clear(); -+ _qualities.stream() -+ .filter((qv) -> qv._quality != 0.0D) -+ .sorted() -+ .map(QualityValue::getValue) -+ .collect(Collectors.toCollection(() -> _values)); ++ _values.clear(); ++ _qualities.stream() ++ .filter((qv) -> qv._quality != 0.0D) ++ .sorted() ++ .map(QualityValue::getValue) ++ .collect(Collectors.toCollection(() -> _values)); _sorted = true; + } - -- Double last = 0.0D; -- int lastSecondaryOrder = Integer.MIN_VALUE; ++ + private class QualityValue implements Comparable + { + private final double _quality; -+ private final String _value; -+ private final int _index; ++ private final String _value; ++ private final int _index; + +- Double last = 0.0D; +- int lastSecondaryOrder = Integer.MIN_VALUE; ++ private QualityValue(double quality, String value, int index) ++ { ++ _quality = quality; ++ _value = value; ++ _index = index; ++ } - for (int i = _values.size(); i-- > 0; ) -+ private QualityValue(double quality, String value, int index) ++ @Override ++ public int hashCode() { - String v = _values.get(i); - Double q = _quality.get(i); -+ _quality = quality; -+ _value = value; -+ _index = index; -+ } -+ -+ @Override -+ public int hashCode() -+ { -+ return Double.hashCode(_quality) ^ Objects.hash(_value, _index); -+ } -+ -+ @Override -+ public boolean equals(Object obj) -+ { -+ if (!(obj instanceof QualityValue)) -+ return false; -+ QualityValue qv = (QualityValue)obj; -+ return _quality == qv._quality && Objects.equals(_value, qv._value) && Objects.equals(_index, qv._index); -+ } -+ -+ private String getValue() -+ { -+ return _value; -+ } ++ return Double.hashCode(_quality) ^ Objects.hash(_value, _index); ++ } - int compare = last.compareTo(q); - if (compare > 0 || (compare == 0 && _secondaryOrdering.applyAsInt(v) < lastSecondaryOrder)) + @Override -+ public int compareTo(QualityValue o) -+ { -+ // sort highest quality first -+ int compare = Double.compare(o._quality, _quality); -+ if (compare == 0) ++ public boolean equals(Object obj) ++ { ++ if (!(obj instanceof QualityValue)) ++ return false; ++ QualityValue qv = (QualityValue)obj; ++ return _quality == qv._quality && Objects.equals(_value, qv._value) && Objects.equals(_index, qv._index); ++ } ++ ++ private String getValue() ++ { ++ return _value; ++ } ++ ++ @Override ++ public int compareTo(QualityValue o) ++ { ++ // sort highest quality first ++ int compare = Double.compare(o._quality, _quality); ++ if (compare == 0) { - _values.set(i, _values.get(i + 1)); - _values.set(i + 1, v); @@ -195,34 +872,325 @@ index d148d9e..67f9981 100644 - lastSecondaryOrder = 0; - i = _values.size(); - continue; -+ // then sort secondary order highest first -+ compare = Integer.compare(_secondaryOrdering.applyAsInt(o._value), _secondaryOrdering.applyAsInt(_value)); -+ if (compare == 0) -+ // then sort index lowest first -+ compare = -Integer.compare(o._index, _index); ++ // then sort secondary order highest first ++ compare = Integer.compare(_secondaryOrdering.applyAsInt(o._value), _secondaryOrdering.applyAsInt(_value)); ++ if (compare == 0) ++ // then sort index lowest first ++ compare = -Integer.compare(o._index, _index); } - +- - last = q; - lastSecondaryOrder = _secondaryOrdering.applyAsInt(v); -+ return compare; ++ return compare; } - int last_element = _quality.size(); - while (last_element > 0 && _quality.get(--last_element).equals(0.0D)) -+ @Override -+ public String toString() ++ @Override ++ public String toString() { - _quality.remove(last_element); - _values.remove(last_element); + return String.format("%s@%x[%s,q=%f,i=%d]", + getClass().getSimpleName(), -+ hashCode(), -+ _value, -+ _quality, -+ _index); ++ hashCode(), ++ _value, ++ _quality, ++ _index); } } } --- -2.23.0 - +diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java +index f03657b..b941e95 100644 +--- a/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java ++++ b/jetty-http/src/test/java/org/eclipse/jetty/http/QuotedQualityCSVTest.java +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -18,13 +18,12 @@ + + package org.eclipse.jetty.http; + ++import java.util.ArrayList; ++import java.util.List; + + import org.hamcrest.Matchers; + import org.junit.jupiter.api.Test; + +-import java.util.ArrayList; +-import java.util.List; +- + import static org.hamcrest.MatcherAssert.assertThat; + import static org.hamcrest.Matchers.contains; + +@@ -32,58 +31,58 @@ public class QuotedQualityCSVTest + { + + @Test +- public void test7231_5_3_2_example1() ++ public void test7231Sec532Example1() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue(" audio/*; q=0.2, audio/basic"); +- assertThat(values,Matchers.contains("audio/basic","audio/*")); ++ assertThat(values, Matchers.contains("audio/basic", "audio/*")); + } + + @Test +- public void test7231_5_3_2_example2() ++ public void test7231Sec532Example2() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("text/plain; q=0.5, text/html,"); + values.addValue("text/x-dvi; q=0.8, text/x-c"); +- assertThat(values,Matchers.contains("text/html","text/x-c","text/x-dvi","text/plain")); ++ assertThat(values, Matchers.contains("text/html", "text/x-c", "text/x-dvi", "text/plain")); + } +- ++ + @Test +- public void test7231_5_3_2_example3() ++ public void test7231Sec532Example3() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("text/*, text/plain, text/plain;format=flowed, */*"); +- ++ + // Note this sort is only on quality and not the most specific type as per 5.3.2 +- assertThat(values,Matchers.contains("text/*","text/plain","text/plain;format=flowed","*/*")); ++ assertThat(values, Matchers.contains("text/*", "text/plain", "text/plain;format=flowed", "*/*")); + } +- ++ + @Test +- public void test7231_5_3_2_example3_most_specific() ++ public void test7231532Example3MostSpecific() + { + QuotedQualityCSV values = new QuotedQualityCSV(QuotedQualityCSV.MOST_SPECIFIC_MIME_ORDERING); + values.addValue("text/*, text/plain, text/plain;format=flowed, */*"); +- +- assertThat(values,Matchers.contains("text/plain;format=flowed","text/plain","text/*","*/*")); ++ ++ assertThat(values, Matchers.contains("text/plain;format=flowed", "text/plain", "text/*", "*/*")); + } +- ++ + @Test +- public void test7231_5_3_2_example4() ++ public void test7231Sec532Example4() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("text/*;q=0.3, text/html;q=0.7, text/html;level=1,"); + values.addValue("text/html;level=2;q=0.4, */*;q=0.5"); +- assertThat(values,Matchers.contains( +- "text/html;level=1", +- "text/html", +- "*/*", +- "text/html;level=2", +- "text/*" +- )); ++ assertThat(values, Matchers.contains( ++ "text/html;level=1", ++ "text/html", ++ "*/*", ++ "text/html;level=2", ++ "text/*" ++ )); + } +- ++ + @Test +- public void test7231_5_3_4_example1() ++ public void test7231Sec534Example1() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("compress, gzip"); +@@ -91,16 +90,16 @@ public class QuotedQualityCSVTest + values.addValue("*"); + values.addValue("compress;q=0.5, gzip;q=1.0"); + values.addValue("gzip;q=1.0, identity; q=0.5, *;q=0"); +- +- assertThat(values,Matchers.contains( +- "compress", +- "gzip", +- "*", +- "gzip", +- "gzip", +- "compress", +- "identity" +- )); ++ ++ assertThat(values, Matchers.contains( ++ "compress", ++ "gzip", ++ "*", ++ "gzip", ++ "gzip", ++ "compress", ++ "identity" ++ )); + } + + @Test +@@ -108,66 +107,65 @@ public class QuotedQualityCSVTest + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue(" value 0.5 ; p = v ; q =0.5 , value 1.0 "); +- assertThat(values,Matchers.contains( +- "value 1.0", +- "value 0.5;p=v")); ++ assertThat(values, Matchers.contains( ++ "value 1.0", ++ "value 0.5;p=v")); + } +- ++ + @Test + public void testEmpty() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue(",aaaa, , bbbb ,,cccc,"); +- assertThat(values,Matchers.contains( +- "aaaa", +- "bbbb", +- "cccc")); ++ assertThat(values, Matchers.contains( ++ "aaaa", ++ "bbbb", ++ "cccc")); + } +- ++ + @Test + public void testQuoted() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue(" value 0.5 ; p = \"v ; q = \\\"0.5\\\" , value 1.0 \" "); +- assertThat(values,Matchers.contains( +- "value 0.5;p=\"v ; q = \\\"0.5\\\" , value 1.0 \"")); ++ assertThat(values, Matchers.contains( ++ "value 0.5;p=\"v ; q = \\\"0.5\\\" , value 1.0 \"")); + } +- ++ + @Test + public void testOpenQuote() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("value;p=\"v"); +- assertThat(values,Matchers.contains( +- "value;p=\"v")); ++ assertThat(values, Matchers.contains( ++ "value;p=\"v")); + } +- ++ + @Test + public void testQuotedQuality() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue(" value 0.5 ; p = v ; q = \"0.5\" , value 1.0 "); +- assertThat(values,Matchers.contains( +- "value 1.0", +- "value 0.5;p=v")); ++ assertThat(values, Matchers.contains( ++ "value 1.0", ++ "value 0.5;p=v")); + } +- ++ + @Test + public void testBadQuality() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("value0.5;p=v;q=0.5,value1.0,valueBad;q=X"); +- assertThat(values,Matchers.contains( +- "value1.0", +- "value0.5;p=v")); ++ assertThat(values, Matchers.contains( ++ "value1.0", ++ "value0.5;p=v")); + } +- ++ + @Test + public void testBad() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + +- + // None of these should throw exceptions + values.addValue(null); + values.addValue(""); +@@ -223,13 +221,10 @@ public class QuotedQualityCSVTest + values.addValue("q="); + values.addValue("q=,"); + values.addValue("q=;"); +- + } + +- /* ------------------------------------------------------------ */ +- +- private static final String[] preferBrotli = {"br","gzip"}; +- private static final String[] preferGzip = {"gzip","br"}; ++ private static final String[] preferBrotli = {"br", "gzip"}; ++ private static final String[] preferGzip = {"gzip", "br"}; + private static final String[] noFormats = {}; + + @Test +@@ -295,14 +290,13 @@ public class QuotedQualityCSVTest + values.addValue("gzip, *"); + assertThat(values, contains("*", "gzip")); + } +- + + @Test + public void testSameQuality() + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("one;q=0.5,two;q=0.5,three;q=0.5"); +- assertThat(values.getValues(),Matchers.contains("one","two","three")); ++ assertThat(values.getValues(), Matchers.contains("one", "two", "three")); + } + + @Test +@@ -310,10 +304,9 @@ public class QuotedQualityCSVTest + { + QuotedQualityCSV values = new QuotedQualityCSV(); + values.addValue("one,two;,three;x=y"); +- assertThat(values.getValues(),Matchers.contains("one","two","three;x=y")); ++ assertThat(values.getValues(), Matchers.contains("one", "two", "three;x=y")); + } + +- + @Test + public void testQuality() + { +@@ -339,19 +332,15 @@ public class QuotedQualityCSVTest + } + }; + +- + // The provided string is not legal according to some RFCs ( not a token because of = and not a parameter because not preceded by ; ) + // The string is legal according to RFC7239 which allows for just parameters (called forwarded-pairs) + values.addValue("p=0.5,q=0.5"); + +- + // The QuotedCSV implementation is lenient and adopts the later interpretation and thus sees q=0.5 and p=0.5 both as parameters +- assertThat(results,contains("parsedValue: ", "parsedParam: p=0.5", +- "parsedValue: ", "parsedParam: q=0.5")); +- ++ assertThat(results, contains("parsedValue: ", "parsedParam: p=0.5", ++ "parsedValue: ", "parsedParam: q=0.5")); + + // However the QuotedQualityCSV only handles the q parameter and that is consumed from the parameter string. +- assertThat(values,contains("p=0.5", "")); +- ++ assertThat(values, contains("p=0.5", "")); + } + } diff --git a/CVE-2021-28165-1.patch b/CVE-2021-28165-1.patch deleted file mode 100644 index 585049cb644dd70092b45039833768177aa29848..0000000000000000000000000000000000000000 --- a/CVE-2021-28165-1.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 00d379c94ba865dced2025c2d1bc3e2e0e41e880 Mon Sep 17 00:00:00 2001 -From: Joakim Erdfelt -Date: Thu, 18 Mar 2021 08:08:55 -0500 -Subject: [PATCH] Fixes #6072 - jetty server high CPU when client send data - length > 17408. - -Avoid spinning if the input buffer is full. - -Signed-off-by: Simone Bordet -Co-authored-by: Joakim Erdfelt ---- - .../main/java/org/eclipse/jetty/io/ssl/SslConnection.java | 8 +++++++- - 1 file changed, 7 insertions(+), 1 deletion(-) - -diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java -index bc2431d..b2482e7 100644 ---- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java -+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java -@@ -603,7 +603,13 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr - - case BUFFER_UNDERFLOW: - if (net_filled > 0) -- continue; // try filling some more -+ { -+ if (BufferUtil.space(_encryptedInput) > 0) -+ continue; // try filling some more -+ BufferUtil.clear(_encryptedInput); -+ throw new SSLHandshakeException("Encrypted buffer max length exceeded"); -+ } -+ - _underflown = true; - if (net_filled < 0 && _sslEngine.getUseClientMode()) - { --- -2.23.0 - diff --git a/CVE-2021-28165-2.patch b/CVE-2021-28165-2.patch deleted file mode 100644 index 30634102bacb1fde3965f761333148ef784d774a..0000000000000000000000000000000000000000 --- a/CVE-2021-28165-2.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 294b2ba02b667548617a94cd99592110ac230add Mon Sep 17 00:00:00 2001 -From: Simone Bordet -Date: Mon, 22 Mar 2021 10:39:36 +0100 -Subject: [PATCH] Fixes #6072 - jetty server high CPU when client send data - length > 17408. - -Updates after review. - -Signed-off-by: Simone Bordet ---- - .../main/java/org/eclipse/jetty/io/ssl/SslConnection.java | 7 ++++--- - 1 file changed, 4 insertions(+), 3 deletions(-) - -diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java -index b2482e7..44c7f10 100644 ---- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java -+++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java -@@ -602,14 +602,15 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr - return filled = -1; - - case BUFFER_UNDERFLOW: -- if (net_filled > 0) -+ if (BufferUtil.space(_encryptedInput) == 0) - { -- if (BufferUtil.space(_encryptedInput) > 0) -- continue; // try filling some more - BufferUtil.clear(_encryptedInput); - throw new SSLHandshakeException("Encrypted buffer max length exceeded"); - } - -+ if (net_filled > 0) -+ continue; // try filling some more -+ - _underflown = true; - if (net_filled < 0 && _sslEngine.getUseClientMode()) - { --- -2.23.0 - diff --git a/CVE-2021-28165.patch b/CVE-2021-28165.patch new file mode 100755 index 0000000000000000000000000000000000000000..5d1f6a9cdaa8aa31af0c621a062c0093b5e2d502 --- /dev/null +++ b/CVE-2021-28165.patch @@ -0,0 +1,533 @@ +From: Markus Koschany +Date: Sat, 31 Jul 2021 17:24:07 +0200 +Subject: CVE-2021-28165 + +--- + .../org/eclipse/jetty/io/ssl/SslConnection.java | 12 + + .../eclipse/jetty/server/ssl/SSLEngineTest.java | 267 +++++++++++++-------- + 2 files changed, 183 insertions(+), 96 deletions(-) + +diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +index a2c1fdc..c385f27 100644 +--- a/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java ++++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java +@@ -330,6 +330,11 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr + _decryptedEndPoint.onFillableFail(cause == null ? new IOException() : cause); + } + ++ protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException ++ { ++ return sslEngine.unwrap(input, output); ++ } ++ + @Override + public String toConnectionString() + { +@@ -602,8 +607,15 @@ public class SslConnection extends AbstractConnection implements Connection.Upgr + return filled = -1; + + case BUFFER_UNDERFLOW: ++ if (BufferUtil.space(_encryptedInput) == 0) ++ { ++ BufferUtil.clear(_encryptedInput); ++ throw new SSLHandshakeException("Encrypted buffer max length exceeded"); ++ } ++ + if (net_filled > 0) + continue; // try filling some more ++ + _underflown = true; + if (net_filled < 0 && _sslEngine.getUseClientMode()) + { +diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java +index ae6a5b6..aa1b9c9 100644 +--- a/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java ++++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLEngineTest.java +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -34,20 +34,31 @@ import java.net.Socket; + import java.net.SocketException; + import java.net.SocketTimeoutException; + import java.net.URL; +- ++import java.nio.ByteBuffer; ++import java.util.Arrays; ++import java.util.concurrent.atomic.AtomicLong; ++import javax.net.SocketFactory; + import javax.net.ssl.HostnameVerifier; + import javax.net.ssl.HttpsURLConnection; + import javax.net.ssl.SSLContext; ++import javax.net.ssl.SSLEngine; ++import javax.net.ssl.SSLEngineResult; ++import javax.net.ssl.SSLException; + import javax.net.ssl.SSLSession; + import javax.servlet.ServletException; + import javax.servlet.ServletOutputStream; + import javax.servlet.http.HttpServletRequest; + import javax.servlet.http.HttpServletResponse; + ++import org.eclipse.jetty.io.EndPoint; ++import org.eclipse.jetty.io.ssl.SslConnection; ++import org.eclipse.jetty.server.ConnectionFactory; ++import org.eclipse.jetty.server.Connector; + import org.eclipse.jetty.server.HttpConnectionFactory; + import org.eclipse.jetty.server.Request; + import org.eclipse.jetty.server.Server; + import org.eclipse.jetty.server.ServerConnector; ++import org.eclipse.jetty.server.SslConnectionFactory; + import org.eclipse.jetty.server.handler.AbstractHandler; + import org.eclipse.jetty.toolchain.test.MavenTestingUtils; + import org.eclipse.jetty.util.IO; +@@ -60,6 +71,7 @@ import org.junit.jupiter.api.Test; + import static org.hamcrest.MatcherAssert.assertThat; + import static org.hamcrest.Matchers.greaterThan; + import static org.hamcrest.Matchers.is; ++import static org.hamcrest.Matchers.lessThan; + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertNotNull; + +@@ -69,41 +81,45 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; + public class SSLEngineTest + { + // Useful constants +- private static final String HELLO_WORLD="Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n"; +- private static final String JETTY_VERSION= Server.getVersion(); +- private static final String PROTOCOL_VERSION="2.0"; +- +- /** The request. */ +- private static final String REQUEST0_HEADER="POST /r0 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Content-Length: "; +- private static final String REQUEST1_HEADER="POST /r1 HTTP/1.1\n"+"Host: localhost\n"+"Content-Type: text/xml\n"+"Connection: close\n"+"Content-Length: "; +- private static final String REQUEST_CONTENT= +- "\n"+ +- "\n"+ +- ""; +- +- private static final String REQUEST0=REQUEST0_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT; +- private static final String REQUEST1=REQUEST1_HEADER+REQUEST_CONTENT.getBytes().length+"\n\n"+REQUEST_CONTENT; +- +- /** The expected response. */ +- private static final String RESPONSE0="HTTP/1.1 200 OK\n"+ +- "Content-Length: "+HELLO_WORLD.length()+"\n"+ +- "Server: Jetty("+JETTY_VERSION+")\n"+ +- '\n'+ ++ private static final String HELLO_WORLD = "Hello world. The quick brown fox jumped over the lazy dog. How now brown cow. The rain in spain falls mainly on the plain.\n"; ++ private static final String JETTY_VERSION = Server.getVersion(); ++ private static final String PROTOCOL_VERSION = "2.0"; ++ ++ /** ++ * The request. ++ */ ++ private static final String REQUEST0_HEADER = "POST /r0 HTTP/1.1\n" + "Host: localhost\n" + "Content-Type: text/xml\n" + "Content-Length: "; ++ private static final String REQUEST1_HEADER = "POST /r1 HTTP/1.1\n" + "Host: localhost\n" + "Content-Type: text/xml\n" + "Connection: close\n" + "Content-Length: "; ++ private static final String REQUEST_CONTENT = ++ "\n" + ++ "\n" + ++ ""; ++ ++ private static final String REQUEST0 = REQUEST0_HEADER + REQUEST_CONTENT.getBytes().length + "\n\n" + REQUEST_CONTENT; ++ private static final String REQUEST1 = REQUEST1_HEADER + REQUEST_CONTENT.getBytes().length + "\n\n" + REQUEST_CONTENT; ++ ++ /** ++ * The expected response. ++ */ ++ private static final String RESPONSE0 = "HTTP/1.1 200 OK\n" + ++ "Content-Length: " + HELLO_WORLD.length() + "\n" + ++ "Server: Jetty(" + JETTY_VERSION + ")\n" + ++ '\n' + + HELLO_WORLD; +- +- private static final String RESPONSE1="HTTP/1.1 200 OK\n"+ +- "Connection: close\n"+ +- "Content-Length: "+HELLO_WORLD.length()+"\n"+ +- "Server: Jetty("+JETTY_VERSION+")\n"+ +- '\n'+ ++ ++ private static final String RESPONSE1 = "HTTP/1.1 200 OK\n" + ++ "Connection: close\n" + ++ "Content-Length: " + HELLO_WORLD.length() + "\n" + ++ "Server: Jetty(" + JETTY_VERSION + ")\n" + ++ '\n' + + HELLO_WORLD; + +- private static final int BODY_SIZE=300; ++ private static final int BODY_SIZE = 300; + + private Server server; + private ServerConnector connector; +- ++ private SslContextFactory sslContextFactory; + + @BeforeEach + public void startServer() throws Exception +@@ -114,11 +130,11 @@ public class SSLEngineTest + sslContextFactory.setKeyStorePassword("storepwd"); + sslContextFactory.setKeyManagerPassword("keypwd"); + +- server=new Server(); ++ server = new Server(); + HttpConnectionFactory http = new HttpConnectionFactory(); + http.setInputBufferSize(512); + http.getHttpConfiguration().setRequestHeaderSize(512); +- connector=new ServerConnector(server, sslContextFactory, http); ++ connector = new ServerConnector(server, sslContextFactory, http); + connector.setPort(0); + connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setSendDateHeader(false); + +@@ -138,19 +154,19 @@ public class SSLEngineTest + server.setHandler(new HelloWorldHandler()); + server.start(); + +- SSLContext ctx=SSLContext.getInstance("TLS"); +- ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); ++ SSLContext ctx = SSLContext.getInstance("TLS"); ++ ctx.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom()); + +- int port=connector.getLocalPort(); ++ int port = connector.getLocalPort(); + +- Socket client=ctx.getSocketFactory().createSocket("localhost",port); +- OutputStream os=client.getOutputStream(); ++ Socket client = ctx.getSocketFactory().createSocket("localhost", port); ++ OutputStream os = client.getOutputStream(); + + String request = +- "GET / HTTP/1.1\r\n"+ +- "Host: localhost:"+port+"\r\n"+ +- "Connection: close\r\n"+ +- "\r\n"; ++ "GET / HTTP/1.1\r\n" + ++ "Host: localhost:" + port + "\r\n" + ++ "Connection: close\r\n" + ++ "\r\n"; + + os.write(request.getBytes()); + os.flush(); +@@ -158,7 +174,7 @@ public class SSLEngineTest + String response = IO.toString(client.getInputStream()); + + assertThat(response, Matchers.containsString("200 OK")); +- assertThat(response,Matchers.containsString(HELLO_WORLD)); ++ assertThat(response, Matchers.containsString(HELLO_WORLD)); + } + + @Test +@@ -167,26 +183,81 @@ public class SSLEngineTest + server.setHandler(new HelloWorldHandler()); + server.start(); + +- SSLContext ctx=SSLContext.getInstance("TLS"); +- ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); ++ SSLContext ctx = SSLContext.getInstance("TLS"); ++ ctx.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom()); + +- int port=connector.getLocalPort(); ++ int port = connector.getLocalPort(); + +- Socket client=ctx.getSocketFactory().createSocket("localhost",port); +- OutputStream os=client.getOutputStream(); ++ Socket client = ctx.getSocketFactory().createSocket("localhost", port); ++ OutputStream os = client.getOutputStream(); + + String request = +- "GET /?dump=102400 HTTP/1.1\r\n"+ +- "Host: localhost:"+port+"\r\n"+ +- "Connection: close\r\n"+ +- "\r\n"; ++ "GET /?dump=102400 HTTP/1.1\r\n" + ++ "Host: localhost:" + port + "\r\n" + ++ "Connection: close\r\n" + ++ "\r\n"; + + os.write(request.getBytes()); + os.flush(); + + String response = IO.toString(client.getInputStream()); + +- assertThat(response.length(),greaterThan(102400)); ++ assertThat(response.length(), greaterThan(102400)); ++ } ++ ++ @Test ++ public void testInvalidLargeTLSFrame() throws Exception ++ { ++ AtomicLong unwraps = new AtomicLong(); ++ ConnectionFactory http = connector.getConnectionFactory(HttpConnectionFactory.class); ++ ConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, http.getProtocol()) ++ { ++ @Override ++ protected SslConnection newSslConnection(Connector connector, EndPoint endPoint, SSLEngine engine) ++ { ++ return new SslConnection(connector.getByteBufferPool(), connector.getExecutor(), endPoint, engine, isDirectBuffersForEncryption(), isDirectBuffersForDecryption()) ++ { ++ @Override ++ protected SSLEngineResult unwrap(SSLEngine sslEngine, ByteBuffer input, ByteBuffer output) throws SSLException ++ { ++ unwraps.incrementAndGet(); ++ return super.unwrap(sslEngine, input, output); ++ } ++ }; ++ } ++ }; ++ ServerConnector tlsConnector = new ServerConnector(server, 1, 1, ssl, http); ++ server.addConnector(tlsConnector); ++ server.setHandler(new HelloWorldHandler()); ++ server.start(); ++ ++ // Create raw TLS record. ++ byte[] bytes = new byte[20005]; ++ Arrays.fill(bytes, (byte)1); ++ ++ bytes[0] = 22; // record type ++ bytes[1] = 3; // major version ++ bytes[2] = 3; // minor version ++ bytes[3] = 78; // record length 2 bytes / 0x4E20 / decimal 20,000 ++ bytes[4] = 32; // record length ++ bytes[5] = 1; // message type ++ bytes[6] = 0; // message length 3 bytes / 0x004E17 / decimal 19,991 ++ bytes[7] = 78; ++ bytes[8] = 23; ++ ++ SocketFactory socketFactory = SocketFactory.getDefault(); ++ try (Socket client = socketFactory.createSocket("localhost", tlsConnector.getLocalPort())) ++ { ++ client.getOutputStream().write(bytes); ++ ++ // Sleep to see if the server spins. ++ Thread.sleep(1000); ++ assertThat(unwraps.get(), lessThan(128L)); ++ ++ // Read until -1 or read timeout. ++ client.setSoTimeout(1000); ++ IO.readBytes(client.getInputStream()); ++ } + } + + @Test +@@ -195,63 +266,64 @@ public class SSLEngineTest + server.setHandler(new HelloWorldHandler()); + server.start(); + +- final int loops=10; +- final int numConns=20; ++ final int loops = 10; ++ final int numConns = 20; + +- Socket[] client=new Socket[numConns]; ++ Socket[] client = new Socket[numConns]; + +- SSLContext ctx=SSLContext.getInstance("TLSv1.2"); +- ctx.init(null,SslContextFactory.TRUST_ALL_CERTS,new java.security.SecureRandom()); ++ SSLContext ctx = SSLContext.getInstance("TLSv1.2"); ++ ctx.init(null, SslContextFactory.TRUST_ALL_CERTS, new java.security.SecureRandom()); + +- int port=connector.getLocalPort(); ++ int port = connector.getLocalPort(); + + try + { +- for (int l=0;l -1) +- bytes+=len; ++ { ++ bytes += len; ++ } + is.close(); + +- assertEquals(BODY_SIZE,handler.bytes); +- assertEquals(BODY_SIZE,bytes); ++ assertEquals(BODY_SIZE, handler.bytes); ++ assertEquals(BODY_SIZE, bytes); + } + + /** +@@ -327,30 +401,30 @@ public class SSLEngineTest + */ + private static String readResponse(Socket client) throws IOException + { +- BufferedReader br=null; +- StringBuilder sb=new StringBuilder(1000); ++ BufferedReader br = null; ++ StringBuilder sb = new StringBuilder(1000); + + try + { + client.setSoTimeout(5000); +- br=new BufferedReader(new InputStreamReader(client.getInputStream())); ++ br = new BufferedReader(new InputStreamReader(client.getInputStream())); + + String line; + +- while ((line=br.readLine())!=null) ++ while ((line = br.readLine()) != null) + { + sb.append(line); + sb.append('\n'); + } + } +- catch(SocketTimeoutException e) ++ catch (SocketTimeoutException e) + { +- System.err.println("Test timedout: "+e.toString()); ++ System.err.println("Test timedout: " + e.toString()); + e.printStackTrace(); // added to see if we can get more info from failures on CI + } + finally + { +- if (br!=null) ++ if (br != null) + { + br.close(); + } +@@ -364,22 +438,24 @@ public class SSLEngineTest + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + // System.err.println("HANDLE "+request.getRequestURI()); +- String ssl_id = (String)request.getAttribute("javax.servlet.request.ssl_session_id"); +- assertNotNull(ssl_id); +- +- if (request.getParameter("dump")!=null) ++ String sslId = (String)request.getAttribute("javax.servlet.request.ssl_session_id"); ++ assertNotNull(sslId); ++ ++ if (request.getParameter("dump") != null) + { +- ServletOutputStream out=response.getOutputStream(); ++ ServletOutputStream out = response.getOutputStream(); + byte[] buf = new byte[Integer.parseInt(request.getParameter("dump"))]; + // System.err.println("DUMP "+buf.length); +- for (int i=0;i -1) + { +- bytes+=len; ++ bytes += len; + } + + OutputStream os = response.getOutputStream(); +@@ -412,5 +488,4 @@ public class SSLEngineTest + response.flushBuffer(); + } + } +- + } diff --git a/CVE-2021-28169.patch b/CVE-2021-28169.patch old mode 100644 new mode 100755 index 1c0fec9802ecb8c153be0ab7500b8c9bec108402..8f39175d4b3f7c525c68a13a71f5cf3d1a2370e7 --- a/CVE-2021-28169.patch +++ b/CVE-2021-28169.patch @@ -1,30 +1,24 @@ -From aec4092cc718b61998c1de221c9c728f377cd430 Mon Sep 17 00:00:00 2001 -From: Lachlan -Date: Thu, 13 May 2021 01:13:30 +1000 -Subject: [PATCH] Fixes #6263 - Review URI encoding in ConcatServlet & -WelcomeFilter. +From: Markus Koschany +Date: Sat, 3 Jul 2021 20:47:31 +0200 +Subject: CVE-2021-28169 -Review URI encoding in ConcatServlet & WelcomeFilter and improve testing. - -Signed-off-by: Lachlan Roberts -Signed-off-by: Simone Bordet -Co-authored-by: Simone Bordet +Origin: https://github.com/eclipse/jetty.project/commit/1c05b0bcb181c759e98b060bded0b9376976b055 --- - .../eclipse/jetty/server/ResourceService.java | 4 +- - .../eclipse/jetty/servlets/ConcatServlet.java | 4 +- - .../eclipse/jetty/servlets/WelcomeFilter.java | 8 +- - .../jetty/servlets/ConcatServletTest.java | 83 ++++++---- - .../jetty/servlets/WelcomeFilterTest.java | 143 ++++++++++++++++++ - .../webapp/WebAppDefaultServletTest.java | 142 +++++++++++++++++ - 6 files changed, 353 insertions(+), 31 deletions(-) + .../org/eclipse/jetty/server/ResourceService.java | 4 +- + .../org/eclipse/jetty/servlets/ConcatServlet.java | 4 +- + .../org/eclipse/jetty/servlets/WelcomeFilter.java | 8 +- + .../eclipse/jetty/servlets/ConcatServletTest.java | 34 +++-- + .../eclipse/jetty/servlets/WelcomeFilterTest.java | 143 +++++++++++++++++++++ + .../jetty/webapp/WebAppDefaultServletTest.java | 142 ++++++++++++++++++++ + 6 files changed, 313 insertions(+), 22 deletions(-) create mode 100644 jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java create mode 100644 jetty-webapp/src/test/java/org/eclipse/jetty/webapp/WebAppDefaultServletTest.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java -index 3d3f05c..5ce24b4 100644 +index 048bd71..737f461 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java -@@ -236,7 +236,7 @@ public class ResourceService +@@ -234,7 +234,7 @@ public class ResourceService // Find the content content=_contentFactory.getContent(pathInContext,response.getBufferSize()); if (LOG.isDebugEnabled()) @@ -33,12 +27,12 @@ index 3d3f05c..5ce24b4 100644 // Not found? if (content==null || !content.getResource().exists()) -@@ -422,7 +422,7 @@ public class ResourceService +@@ -420,7 +420,7 @@ public class ResourceService return; } - RequestDispatcher dispatcher=context.getRequestDispatcher(welcome); -+ RequestDispatcher dispatcher = context.getRequestDispatcher(URIUtil.encodePath(welcome)); ++ RequestDispatcher dispatcher = context.getRequestDispatcher(URIUtil.encodePath(welcome)); if (dispatcher!=null) { // Forward to the index @@ -65,7 +59,7 @@ index a4b7df0..f1d8e57 100644 dispatchers.add(dispatcher); } diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java -index e67a067..492a8ca 100644 +index e67a067..22ea603 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/WelcomeFilter.java @@ -28,6 +28,8 @@ import javax.servlet.ServletRequest; @@ -92,140 +86,114 @@ index e67a067..492a8ca 100644 - request.getRequestDispatcher(path+welcome).forward(request,response); + { + String uriInContext = URIUtil.encodePath(URIUtil.addPaths(path, welcome)); -+ request.getRequestDispatcher(uriInContext).forward(request, response); -+ } ++ request.getRequestDispatcher(uriInContext).forward(request, response); ++ } else chain.doFilter(request, response); } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java -index 3fcb9af..b815b35 100644 +index 3fcb9af..f8ea087 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ConcatServletTest.java -@@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets; +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -18,11 +18,6 @@ + + package org.eclipse.jetty.servlets; + +-import static org.junit.jupiter.api.Assertions.assertEquals; +-import static org.junit.jupiter.api.Assertions.assertNotNull; +-import static org.junit.jupiter.api.Assertions.assertNull; +-import static org.junit.jupiter.api.Assertions.assertTrue; +- + import java.io.BufferedReader; + import java.io.File; + import java.io.IOException; +@@ -31,7 +26,6 @@ import java.io.StringReader; + import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; - -+import java.util.stream.Stream; +- import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; -@@ -48,7 +49,12 @@ import org.junit.jupiter.api.AfterEach; - +@@ -45,10 +39,14 @@ import org.eclipse.jetty.servlet.ServletHolder; + import org.eclipse.jetty.toolchain.test.MavenTestingUtils; + import org.eclipse.jetty.webapp.WebAppContext; + import org.junit.jupiter.api.AfterEach; +- import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -+import org.junit.jupiter.params.ParameterizedTest; -+import org.junit.jupiter.params.provider.Arguments; -+import org.junit.jupiter.params.provider.MethodSource; -+import static org.hamcrest.MatcherAssert.assertThat; -+import static org.hamcrest.Matchers.startsWith; ++import static org.junit.jupiter.api.Assertions.assertEquals; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; ++ public class ConcatServletTest { private Server server; -@@ -114,7 +120,7 @@ public class ConcatServletTest - } - - @Test -- public void testWEBINFResourceIsNotServed() throws Exception -+ public void testDirectoryNotAccessible() throws Exception - { - File directoryFile = MavenTestingUtils.getTargetTestingDir(); - Path directoryPath = directoryFile.toPath(); -@@ -136,9 +142,8 @@ public class ConcatServletTest - // Verify that I can get the file programmatically, as required by the spec. - assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js")); - -- // Having a path segment and then ".." triggers a special case -- // that the ConcatServlet must detect and avoid. -- String uri = contextPath + concatPath + "?/trick/../WEB-INF/one.js"; -+ // Make sure ConcatServlet cannot see file system files. -+ String uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName(); - String request = "" + - "GET " + uri + " HTTP/1.1\r\n" + +@@ -92,8 +90,8 @@ public class ConcatServletTest + String resource1 = "/resource/one.js"; + String resource2 = "/resource/two.js"; + String uri = contextPath + concatPath + "?" + resource1 + "&" + resource2; +- String request = "" + +- "GET " + uri + " HTTP/1.1\r\n" + ++ String request = ++ "GET " + uri + " HTTP/1.1\r\n" + "Host: localhost\r\n" + -@@ -146,35 +151,59 @@ public class ConcatServletTest + "Connection: close\r\n" + "\r\n"; - String response = connector.getResponse(request); - assertTrue(response.startsWith("HTTP/1.1 404 ")); -+ } +@@ -139,8 +137,8 @@ public class ConcatServletTest + // Having a path segment and then ".." triggers a special case + // that the ConcatServlet must detect and avoid. + String uri = contextPath + concatPath + "?/trick/../WEB-INF/one.js"; +- String request = "" + +- "GET " + uri + " HTTP/1.1\r\n" + ++ String request = ++ "GET " + uri + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "\r\n"; +@@ -149,8 +147,8 @@ public class ConcatServletTest -- // Make sure ConcatServlet behaves well if it's case insensitive. -- uri = contextPath + concatPath + "?/trick/../web-inf/one.js"; + // Make sure ConcatServlet behaves well if it's case insensitive. + uri = contextPath + concatPath + "?/trick/../web-inf/one.js"; - request = "" + - "GET " + uri + " HTTP/1.1\r\n" + -- "Host: localhost\r\n" + -- "Connection: close\r\n" + -- "\r\n"; -- response = connector.getResponse(request); -- assertTrue(response.startsWith("HTTP/1.1 404 ")); -+ public static Stream webInfTestExamples() -+ { -+ return Stream.of( -+ // Cannot access WEB-INF. -+ Arguments.of("?/WEB-INF/", "HTTP/1.1 404 "), -+ Arguments.of("?/WEB-INF/one.js", "HTTP/1.1 404 "), -+ -+ // Having a path segment and then ".." triggers a special case that the ConcatServlet must detect and avoid. -+ Arguments.of("?/trick/../WEB-INF/one.js", "HTTP/1.1 404 "), -+ -+ // Make sure ConcatServlet behaves well if it's case insensitive. -+ Arguments.of("?/trick/../web-inf/one.js", "HTTP/1.1 404 "), -+ -+ // Make sure ConcatServlet behaves well if encoded. -+ Arguments.of("?/trick/..%2FWEB-INF%2Fone.js", "HTTP/1.1 404 "), -+ Arguments.of("?/%2557EB-INF/one.js", "HTTP/1.1 500 "), -+ Arguments.of("?/js/%252e%252e/WEB-INF/one.js", "HTTP/1.1 500 ") -+ ); -+ } ++ request = ++ "GET " + uri + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "\r\n"; +@@ -159,8 +157,8 @@ public class ConcatServletTest -- // Make sure ConcatServlet behaves well if encoded. -- uri = contextPath + concatPath + "?/trick/..%2FWEB-INF%2Fone.js"; + // Make sure ConcatServlet behaves well if encoded. + uri = contextPath + concatPath + "?/trick/..%2FWEB-INF%2Fone.js"; - request = "" + - "GET " + uri + " HTTP/1.1\r\n" + -- "Host: localhost\r\n" + -- "Connection: close\r\n" + -- "\r\n"; -- response = connector.getResponse(request); -- assertTrue(response.startsWith("HTTP/1.1 404 ")); -+ @ParameterizedTest -+ @MethodSource("webInfTestExamples") -+ public void testWEBINFResourceIsNotServed(String querystring, String expectedStatus) throws Exception -+ { -+ File directoryFile = MavenTestingUtils.getTargetTestingDir(); -+ Path directoryPath = directoryFile.toPath(); -+ Path hiddenDirectory = directoryPath.resolve("WEB-INF"); -+ Files.createDirectories(hiddenDirectory); -+ Path hiddenResource = hiddenDirectory.resolve("one.js"); -+ try (OutputStream output = Files.newOutputStream(hiddenResource)) -+ { -+ output.write("function() {}".getBytes(StandardCharsets.UTF_8)); -+ } -+ -+ String contextPath = ""; -+ WebAppContext context = new WebAppContext(server, directoryPath.toString(), contextPath); -+ server.setHandler(context); -+ String concatPath = "/concat"; -+ context.addServlet(ConcatServlet.class, concatPath); -+ server.start(); ++ request = ++ "GET " + uri + " HTTP/1.1\r\n" + + "Host: localhost\r\n" + + "Connection: close\r\n" + + "\r\n"; +@@ -169,8 +167,8 @@ public class ConcatServletTest -- // Make sure ConcatServlet cannot see file system files. -- uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName(); + // Make sure ConcatServlet cannot see file system files. + uri = contextPath + concatPath + "?/trick/../../" + directoryFile.getName(); - request = "" + -+ // Verify that I can get the file programmatically, as required by the spec. -+ assertNotNull(context.getServletContext().getResource("/WEB-INF/one.js")); -+ -+ String uri = contextPath + concatPath + querystring; -+ String request = - "GET " + uri + " HTTP/1.1\r\n" + +- "GET " + uri + " HTTP/1.1\r\n" + ++ request = ++ "GET " + uri + " HTTP/1.1\r\n" + "Host: localhost\r\n" + "Connection: close\r\n" + "\r\n"; -- response = connector.getResponse(request); -- assertTrue(response.startsWith("HTTP/1.1 404 ")); -+ String response = connector.getResponse(request); -+ assertThat(response, startsWith(expectedStatus)); - } - } diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/WelcomeFilterTest.java new file mode 100644 index 0000000..65e6503 @@ -523,6 +491,3 @@ index 0000000..933bb7a + } + } +} --- -2.23.0 - diff --git a/CVE-2021-34428.patch b/CVE-2021-34428.patch old mode 100644 new mode 100755 index da93065b4d52477e34e6c19bd789d60a8ed386f2..5090be080a75b3620112126284585a7d28e1325d --- a/CVE-2021-34428.patch +++ b/CVE-2021-34428.patch @@ -1,21 +1,16 @@ -From 91d9850d64076cad97611a3379775e01acddf986 Mon Sep 17 00:00:00 2001 -From: Jan Bartel -Date: Sun, 16 May 2021 09:45:50 +1000 -Subject: [PATCH] Issue #6277 Better handling of exceptions thrown in - sessionDestroyed (#6278) - -* Issue #6277 Better handling of exceptions thrown in sessionDestroyed - -Signed-off-by: Jan Bartel +From: Markus Koschany +Date: Sat, 3 Jul 2021 20:28:06 +0200 +Subject: CVE-2021-34428 +Origin: https://github.com/eclipse/jetty.project/commit/cd6462a6252d083b3c9ea2684aab0b4c9669ed19 --- - .../eclipse/jetty/server/session/Session.java | 9 +- - .../session/TestHttpSessionListener.java | 26 ++++-- - .../server/session/SessionListenerTest.java | 82 +++++++++++++++++-- - 3 files changed, 102 insertions(+), 15 deletions(-) + .../org/eclipse/jetty/server/session/Session.java | 9 +- + .../server/session/TestHttpSessionListener.java | 24 +- + .../jetty/server/session/SessionListenerTest.java | 367 +++++++++++++++------ + 3 files changed, 291 insertions(+), 109 deletions(-) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java -index a34bc0f..ecaf7c7 100644 +index a34bc0f..d667560 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java @@ -506,6 +506,7 @@ public class Session implements SessionHandler.SessionIf @@ -30,17 +25,17 @@ index a34bc0f..ecaf7c7 100644 // do the invalidation _handler.callSessionDestroyedListeners(this); } -+ catch (Exception e) -+ { -+ LOG.warn("Error during Session destroy listener", e); -+ } ++ catch (Exception e) ++ { ++ LOG.warn("Error during Session destroy listener", e); ++ } finally { // call the attribute removed listeners and finally mark it // as invalid finishInvalidate(); -+ // tell id mgr to remove sessions with same id from all contexts -+ _handler.getSessionIdManager().invalidateAll(_sessionData.getId()); ++ // tell id mgr to remove sessions with same id from all contexts ++ _handler.getSessionIdManager().invalidateAll(_sessionData.getId()); } - // tell id mgr to remove sessions with same id from all contexts - _handler.getSessionIdManager().invalidateAll(_sessionData.getId()); @@ -48,16 +43,14 @@ index a34bc0f..ecaf7c7 100644 } catch (Exception e) diff --git a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java -index 770627b..b736fdf 100644 +index 770627b..dd8982f 100644 --- a/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java +++ b/tests/test-sessions/test-sessions-common/src/main/java/org/eclipse/jetty/server/session/TestHttpSessionListener.java -@@ -34,17 +34,19 @@ public class TestHttpSessionListener implements HttpSessionListener - { +@@ -35,16 +35,18 @@ public class TestHttpSessionListener implements HttpSessionListener public List createdSessions = new ArrayList<>(); public List destroyedSessions = new ArrayList<>(); -- public boolean accessAttribute = false; + public boolean accessAttribute = false; - public Exception ex = null; -+ public boolean accessAttribute = false; + public boolean lastAccessTime = false; + public Exception attributeException = null; + public Exception accessTimeException = null; @@ -67,7 +60,7 @@ index 770627b..b736fdf 100644 { - accessAttribute = access; + this.accessAttribute = accessAttribute; -+ this.lastAccessTime = lastAccessTime; ++ this.lastAccessTime = lastAccessTime; } public TestHttpSessionListener() @@ -76,55 +69,182 @@ index 770627b..b736fdf 100644 } public void sessionDestroyed(HttpSessionEvent se) -@@ -58,9 +60,21 @@ public class TestHttpSessionListener implements HttpSessionListener +@@ -58,7 +60,19 @@ public class TestHttpSessionListener implements HttpSessionListener } catch (Exception e) { - ex = e; + attributeException = e; - } - } -+ -+ if (lastAccessTime) -+ { -+ try ++ } ++ } ++ ++ if (lastAccessTime) ++ { ++ try + { -+ se.getSession().getLastAccessedTime(); -+ } -+ catch (Exception e) ++ se.getSession().getLastAccessedTime(); ++ } ++ catch (Exception e) + { -+ accessTimeException = e; -+ } -+ } ++ accessTimeException = e; + } + } } - - public void sessionCreated(HttpSessionEvent se) diff --git a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java -index ba83986..24ac045 100644 +index ba83986..363d1e3 100644 --- a/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java +++ b/tests/test-sessions/test-sessions-common/src/test/java/org/eclipse/jetty/server/session/SessionListenerTest.java -@@ -21,6 +21,7 @@ package org.eclipse.jetty.server.session; - import static org.hamcrest.MatcherAssert.assertThat; - import static org.hamcrest.Matchers.isIn; - import static org.junit.jupiter.api.Assertions.assertEquals; +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. + // ------------------------------------------------------------------------ + // All rights reserved. This program and the accompanying materials + // are made available under the terms of the Eclipse Public License v1.0 +@@ -18,19 +18,17 @@ + + package org.eclipse.jetty.server.session; + +-import static org.hamcrest.MatcherAssert.assertThat; +-import static org.hamcrest.Matchers.isIn; +-import static org.junit.jupiter.api.Assertions.assertEquals; +-import static org.junit.jupiter.api.Assertions.assertNotEquals; +-import static org.junit.jupiter.api.Assertions.assertNotNull; +-import static org.junit.jupiter.api.Assertions.assertNull; +-import static org.junit.jupiter.api.Assertions.assertTrue; +- + import java.io.IOException; ++import java.io.InputStream; ++import java.io.OutputStream; + import java.io.Serializable; + import java.net.HttpCookie; ++import java.net.URL; ++import java.net.URLClassLoader; ++import java.nio.file.Files; ++import java.nio.file.Path; ++import java.util.Collection; + import java.util.concurrent.TimeUnit; +- + import javax.servlet.ServletException; + import javax.servlet.http.HttpServlet; + import javax.servlet.http.HttpServletRequest; +@@ -38,53 +36,140 @@ import javax.servlet.http.HttpServletResponse; + import javax.servlet.http.HttpSession; + import javax.servlet.http.HttpSessionBindingEvent; + import javax.servlet.http.HttpSessionBindingListener; ++import javax.servlet.http.HttpSessionEvent; ++import javax.servlet.http.HttpSessionListener; + + import org.eclipse.jetty.client.HttpClient; + import org.eclipse.jetty.client.api.ContentResponse; + import org.eclipse.jetty.client.api.Request; ++import org.eclipse.jetty.server.Server; + import org.eclipse.jetty.servlet.ServletContextHandler; + import org.eclipse.jetty.servlet.ServletHolder; ++import org.eclipse.jetty.toolchain.test.IO; ++import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; ++import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; ++import org.eclipse.jetty.util.component.LifeCycle; ++import org.eclipse.jetty.webapp.WebAppContext; + import org.junit.jupiter.api.Test; ++import org.junit.jupiter.api.extension.ExtendWith; ++import org.junit.jupiter.api.Disabled; ++ ++import static org.hamcrest.MatcherAssert.assertThat; ++import static org.hamcrest.Matchers.greaterThan; ++//import static org.hamcrest.Matchers.in; ++import static org.hamcrest.Matchers.is; ++import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; - import static org.junit.jupiter.api.Assertions.assertNotEquals; - import static org.junit.jupiter.api.Assertions.assertNotNull; - import static org.junit.jupiter.api.Assertions.assertNull; -@@ -74,7 +75,7 @@ public class SessionListenerTest - TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, - cacheFactory, storeFactory); ++import static org.junit.jupiter.api.Assertions.assertNotEquals; ++import static org.junit.jupiter.api.Assertions.assertNotNull; ++import static org.junit.jupiter.api.Assertions.assertNull; ++import static org.junit.jupiter.api.Assertions.assertTrue; + + /** + * SessionListenerTest + * + * Test that session listeners are called. + */ ++@ExtendWith(WorkDirExtension.class) + public class SessionListenerTest + { ++ public WorkDir workDir; ++ + /** + * Test that listeners are called when a session is deliberately invalidated. +- * +- * @throws Exception + */ ++ @Disabled + @Test + public void testListenerWithInvalidation() throws Exception + { + String contextPath = ""; + String servletMapping = "/server"; + int inactivePeriod = 6; +- int scavengePeriod = -1; ++ int scavengePeriod = -1; + + DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); + cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); +- SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); +- ((AbstractSessionDataStoreFactory)storeFactory).setGracePeriodSec(scavengePeriod); ++ TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); ++ storeFactory.setGracePeriodSec(scavengePeriod); + +- TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, +- cacheFactory, storeFactory); ++ TestServer server = new TestServer(0, inactivePeriod, scavengePeriod, ++ cacheFactory, storeFactory); ServletContextHandler context = server.addContext(contextPath); - TestHttpSessionListener listener = new TestHttpSessionListener(true); -+ TestHttpSessionListener listener = new TestHttpSessionListener(true,true); ++ TestHttpSessionListener listener = new TestHttpSessionListener(true, true); context.getSessionHandler().addEventListener(listener); TestServlet servlet = new TestServlet(); ServletHolder holder = new ServletHolder(servlet); -@@ -120,6 +121,73 @@ public class SessionListenerTest - } - + context.addServlet(holder, servletMapping); ++ ++ try ++ { ++ server.start(); ++ int port1 = server.getPort(); ++ ++ HttpClient client = new HttpClient(); ++ client.start(); ++ try ++ { ++ String url = "http://localhost:" + port1 + contextPath + servletMapping; ++ // Create the session ++ ContentResponse response1 = client.GET(url + "?action=init"); ++ assertEquals(HttpServletResponse.SC_OK, response1.getStatus()); ++ String sessionCookie = response1.getHeaders().get("Set-Cookie"); ++ assertNotNull(sessionCookie); ++ assertTrue(TestServlet.bindingListener.bound); ++ ++ String sessionId = TestServer.extractSessionId(sessionCookie); ++ //assertThat(sessionId, is(in(listener.createdSessions))); ++ ++ // Make a request which will invalidate the existing session ++ Request request2 = client.newRequest(url + "?action=test"); ++ ContentResponse response2 = request2.send(); ++ assertEquals(HttpServletResponse.SC_OK, response2.getStatus()); ++ ++ assertTrue(TestServlet.bindingListener.unbound); ++ assertTrue(listener.destroyedSessions.contains(sessionId)); ++ } ++ finally ++ { ++ LifeCycle.stop(client); ++ } ++ } ++ finally ++ { ++ LifeCycle.stop(server); ++ } ++ } -+ + /** + * Test that if a session listener throws an exception during sessionDestroyed the session is still invalidated + */ @@ -150,97 +270,388 @@ index ba83986..24ac045 100644 + ServletHolder holder = new ServletHolder(servlet); + context.addServlet(holder, servletMapping); + -+ try -+ { -+ server.start(); -+ int port1 = server.getPort(); + try + { + server.start(); + int port1 = server.getPort(); +- + -+ HttpClient client = new HttpClient(); -+ client.start(); -+ try -+ { -+ String url = "http://localhost:" + port1 + contextPath + servletMapping; -+ // Create the session -+ ContentResponse response1 = client.GET(url + "?action=init"); + HttpClient client = new HttpClient(); + client.start(); + try +@@ -92,42 +177,59 @@ public class SessionListenerTest + String url = "http://localhost:" + port1 + contextPath + servletMapping; + // Create the session + ContentResponse response1 = client.GET(url + "?action=init"); +- assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); + assertEquals(HttpServletResponse.SC_OK, response1.getStatus()); -+ String sessionCookie = response1.getHeaders().get("Set-Cookie"); + String sessionCookie = response1.getHeaders().get("Set-Cookie"); +- assertTrue(sessionCookie != null); +- assertTrue (TestServlet.bindingListener.bound); +- + assertNotNull(sessionCookie); + assertTrue(TestServlet.bindingListener.bound); + -+ String sessionId = TestServer.extractSessionId(sessionCookie); + String sessionId = TestServer.extractSessionId(sessionCookie); +- assertThat(sessionId, isIn(listener.createdSessions)); +- + -+ // Make a request which will invalidate the existing session -+ Request request2 = client.newRequest(url + "?action=test"); -+ ContentResponse response2 = request2.send(); + // Make a request which will invalidate the existing session + Request request2 = client.newRequest(url + "?action=test"); + ContentResponse response2 = request2.send(); +- assertEquals(HttpServletResponse.SC_OK,response2.getStatus()); + assertEquals(HttpServletResponse.SC_OK, response2.getStatus()); -+ + +- assertTrue (TestServlet.bindingListener.unbound); +- assertTrue (listener.destroyedSessions.contains(sessionId)); + assertTrue(TestServlet.bindingListener.unbound); + + //check session no longer exists + assertFalse(context.getSessionHandler().getSessionCache().contains(sessionId)); + assertFalse(context.getSessionHandler().getSessionCache().getSessionDataStore().exists(sessionId)); -+ } -+ finally -+ { + } + finally + { +- client.stop(); + LifeCycle.stop(client); -+ } -+ } -+ finally -+ { + } + } + finally + { +- server.stop(); + LifeCycle.stop(server); + } + } + +- + /** +- * Test that listeners are called when a session expires. +- * +- * @throws Exception ++ * Test that listeners are called when a session expires ++ * and that the listener is able to access webapp classes. + */ ++ @Disabled + @Test + public void testSessionExpiresWithListener() throws Exception + { ++ Path foodir = workDir.getEmptyPathDir(); ++ Path fooClass = foodir.resolve("Foo.class"); ++ ++ //Use a class that would only be known to the webapp classloader ++ try (InputStream foostream = Thread.currentThread().getContextClassLoader().getResourceAsStream("Foo.clazz"); ++ OutputStream out = Files.newOutputStream(fooClass)) ++ { ++ IO.copy(foostream, out); + } -+ } + - /** - * Test that listeners are called when a session expires. - * -@@ -144,7 +212,7 @@ public class SessionListenerTest ++ assertTrue(Files.exists(fooClass)); ++ assertThat(Files.size(fooClass), greaterThan(0L)); ++ ++ URL[] foodirUrls = new URL[]{foodir.toUri().toURL()}; ++ URLClassLoader contextClassLoader = new URLClassLoader(foodirUrls, Thread.currentThread().getContextClassLoader()); ++ + String contextPath = "/"; + String servletMapping = "/server"; + int inactivePeriod = 3; +@@ -135,58 +237,66 @@ public class SessionListenerTest + + DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); + cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); +- SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); +- ((AbstractSessionDataStoreFactory)storeFactory).setGracePeriodSec(scavengePeriod); ++ TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); ++ storeFactory.setGracePeriodSec(scavengePeriod); + + TestServer server1 = new TestServer(0, inactivePeriod, scavengePeriod, +- cacheFactory, storeFactory); ++ cacheFactory, storeFactory); + TestServlet servlet = new TestServlet(); ServletHolder holder = new ServletHolder(servlet); ServletContextHandler context = server1.addContext(contextPath); ++ context.setClassLoader(contextClassLoader); context.addServlet(holder, servletMapping); - TestHttpSessionListener listener = new TestHttpSessionListener(true); -+ TestHttpSessionListener listener = new TestHttpSessionListener(true.true); ++ //TestHttpSessionListener listener = new TestHttpSessionListenerWithWebappClasses(true, true); ++ TestHttpSessionListener listener = null; context.getSessionHandler().addEventListener(listener); - - server1.start(); -@@ -171,7 +239,8 @@ public class SessionListenerTest +- +- server1.start(); +- int port1 = server1.getPort(); - assertThat(sessionId, isIn(listener.destroyedSessions)); + try + { ++ server1.start(); ++ int port1 = server1.getPort(); ++ + HttpClient client = new HttpClient(); +- client.start(); +- String url = "http://localhost:" + port1 + contextPath + servletMapping.substring(1); ++ try ++ { ++ client.start(); ++ String url = "http://localhost:" + port1 + contextPath + servletMapping.substring(1); + +- //make a request to set up a session on the server +- ContentResponse response1 = client.GET(url + "?action=init"); +- assertEquals(HttpServletResponse.SC_OK,response1.getStatus()); +- String sessionCookie = response1.getHeaders().get("Set-Cookie"); +- assertTrue(sessionCookie != null); +- +- String sessionId = TestServer.extractSessionId(sessionCookie); ++ //make a request to set up a session on the server ++ ContentResponse response1 = client.GET(url + "?action=init"); ++ assertEquals(HttpServletResponse.SC_OK, response1.getStatus()); ++ String sessionCookie = response1.getHeaders().get("Set-Cookie"); ++ assertNotNull(sessionCookie); ++ ++ String sessionId = TestServer.extractSessionId(sessionCookie); ++ ++ //assertThat(sessionId, is(in(listener.createdSessions))); + +- assertThat(sessionId, isIn(listener.createdSessions)); +- +- //and wait until the session should have expired +- Thread.currentThread().sleep(TimeUnit.SECONDS.toMillis(inactivePeriod+(scavengePeriod))); ++ //and wait until the session should have expired ++ Thread.sleep(TimeUnit.SECONDS.toMillis(inactivePeriod + (2 * scavengePeriod))); + +- assertThat(sessionId, isIn(listener.destroyedSessions)); ++ //assertThat(sessionId, is(in(listener.destroyedSessions))); - assertNull(listener.ex); -+ assertNull(listener.attributeException); -+ assertNull(listener.accessTimeException); ++ assertNull(listener.attributeException); ++ assertNull(listener.accessTimeException); ++ } ++ finally ++ { ++ LifeCycle.stop(client); ++ } } finally { -@@ -203,7 +272,7 @@ public class SessionListenerTest + server1.stop(); +- } ++ } + } +- ++ + /** + * Check that a session that is expired cannot be reused, and expiry listeners are called for it +- * +- * @throws Exception + */ + @Test + public void testExpiredSession() throws Exception +- { ++ { + String contextPath = "/"; + String servletMapping = "/server"; + int inactivePeriod = 4; +@@ -194,65 +304,122 @@ public class SessionListenerTest + + DefaultSessionCacheFactory cacheFactory = new DefaultSessionCacheFactory(); + cacheFactory.setEvictionPolicy(SessionCache.NEVER_EVICT); +- SessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); +- ((AbstractSessionDataStoreFactory)storeFactory).setGracePeriodSec(scavengePeriod); ++ TestSessionDataStoreFactory storeFactory = new TestSessionDataStoreFactory(); ++ storeFactory.setGracePeriodSec(scavengePeriod); + + TestServer server1 = new TestServer(0, inactivePeriod, scavengePeriod, +- cacheFactory, storeFactory); ++ cacheFactory, storeFactory); + SimpleTestServlet servlet = new SimpleTestServlet(); ServletHolder holder = new ServletHolder(servlet); ServletContextHandler context = server1.addContext(contextPath); context.addServlet(holder, servletMapping); - TestHttpSessionListener listener = new TestHttpSessionListener(); -+ TestHttpSessionListener listener = new TestHttpSessionListener(true.true); - +- ++ TestHttpSessionListener listener = new TestHttpSessionListener(true, true); ++ context.getSessionHandler().addEventListener(listener); - -@@ -236,7 +305,8 @@ public class SessionListenerTest - - assertTrue (listener.destroyedSessions.contains("1234")); +- +- server1.start(); +- int port1 = server1.getPort(); + + try +- { ++ { ++ server1.start(); ++ int port1 = server1.getPort(); ++ + //save a session that has already expired + long now = System.currentTimeMillis(); +- SessionData data = context.getSessionHandler().getSessionCache().getSessionDataStore().newSessionData("1234", now-10, now-5, now-10, 30000); ++ SessionData data = context.getSessionHandler().getSessionCache().getSessionDataStore().newSessionData("1234", now - 10, now - 5, now - 10, 30000); + data.setExpiry(100); //make it expired a long time ago + context.getSessionHandler().getSessionCache().getSessionDataStore().store("1234", data); +- ++ + HttpClient client = new HttpClient(); +- client.start(); ++ try ++ { ++ client.start(); ++ ++ port1 = server1.getPort(); ++ String url = "http://localhost:" + port1 + contextPath + servletMapping.substring(1); ++ ++ //make another request using the id of the expired session ++ Request request = client.newRequest(url + "?action=test"); ++ request.cookie(new HttpCookie("JSESSIONID", "1234")); ++ ContentResponse response = request.send(); ++ assertEquals(HttpServletResponse.SC_OK, response.getStatus()); ++ ++ //should be a new session id ++ String cookie2 = response.getHeaders().get("Set-Cookie"); ++ assertNotEquals("1234", TestServer.extractSessionId(cookie2)); +- port1 = server1.getPort(); +- String url = "http://localhost:" + port1 + contextPath + servletMapping.substring(1); +- +- //make another request using the id of the expired session +- Request request = client.newRequest(url + "?action=test"); +- request.cookie(new HttpCookie("JSESSIONID", "1234")); +- ContentResponse response = request.send(); +- assertEquals(HttpServletResponse.SC_OK,response.getStatus()); +- +- //should be a new session id +- String cookie2 = response.getHeaders().get("Set-Cookie"); +- assertNotEquals("1234", TestServer.extractSessionId(cookie2)); +- +- assertTrue (listener.destroyedSessions.contains("1234")); +- - assertNull(listener.ex); -+ assertNull(listener.attributeException); -+ assertNull(listener.accessTimeException); ++ assertTrue(listener.destroyedSessions.contains("1234")); ++ assertNull(listener.attributeException); ++ assertNull(listener.accessTimeException); ++ } ++ finally ++ { ++ LifeCycle.stop(client); ++ } } finally -@@ -245,8 +315,6 @@ public class SessionListenerTest - } + { + server1.stop(); +- } ++ } ++ } ++ ++ public static class MyHttpSessionListener implements HttpSessionListener ++ { ++ @Override ++ public void sessionCreated(HttpSessionEvent se) ++ { ++ } ++ ++ @Override ++ public void sessionDestroyed(HttpSessionEvent se) ++ { ++ } } - - ++ public static class ThrowingSessionListener implements HttpSessionListener ++ { ++ ++ @Override ++ public void sessionCreated(HttpSessionEvent se) ++ { ++ } ++ ++ @Override ++ public void sessionDestroyed(HttpSessionEvent se) ++ { ++ throw new IllegalStateException("Exception during sessionDestroyed"); ++ } ++ ++ } ++ ++ @Test ++ public void testSessionListeners() ++ { ++ Server server = new Server(); ++ try ++ { ++ WebAppContext wac = new WebAppContext(); ++ ++ wac.setServer(server); ++ server.setHandler(wac); ++ wac.addEventListener(new MyHttpSessionListener()); ++ ++ Collection listeners = wac.getSessionHandler().getBeans(MyHttpSessionListener.class); ++ assertNotNull(listeners); ++ ++ assertEquals(1, listeners.size()); ++ } ++ finally ++ { ++ LifeCycle.stop(server); ++ } ++ } ++ public static class MySessionBindingListener implements HttpSessionBindingListener, Serializable { private static final long serialVersionUID = 1L; --- -2.23.0 - + boolean unbound = false; + boolean bound = false; +- ++ + public void valueUnbound(HttpSessionBindingEvent event) + { + unbound = true; +@@ -263,38 +430,34 @@ public class SessionListenerTest + bound = true; + } + } +- +- +- ++ + public static class TestServlet extends HttpServlet + { + private static final long serialVersionUID = 1L; + public static final MySessionBindingListener bindingListener = new MySessionBindingListener(); +- + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse httpServletResponse) throws ServletException, IOException + { + String action = request.getParameter("action"); +- ++ + if ("init".equals(action)) + { + HttpSession session = request.getSession(true); + session.setAttribute("foo", bindingListener); + assertNotNull(session); +- + } + else if ("test".equals(action)) + { + HttpSession session = request.getSession(false); + assertNotNull(session); +- ++ + //invalidate existing session + session.invalidate(); + } + } + } +- ++ + public static class SimpleTestServlet extends HttpServlet + { + private static final long serialVersionUID = 1L; +@@ -306,7 +469,7 @@ public class SessionListenerTest + if ("test".equals(action)) + { + HttpSession session = request.getSession(true); +- assertTrue(session != null); ++ assertNotNull(session); + } + } + } diff --git a/jetty-9.4.15.v20190215.tar.gz b/jetty-9.4.16.v20190411.tar.gz similarity index 69% rename from jetty-9.4.15.v20190215.tar.gz rename to jetty-9.4.16.v20190411.tar.gz index 995fce98136669269e234a39806185736e642b66..0e76c17d4f0a6952b3fa1360e1483f3f8fee9925 100644 Binary files a/jetty-9.4.15.v20190215.tar.gz and b/jetty-9.4.16.v20190411.tar.gz differ diff --git a/jetty.spec b/jetty.spec index 427cd6cc7d353f374353145fb7792b739d13f82d..b49c5eb0de5c9e7ecde59ff55549b7b950e7f11d 100644 --- a/jetty.spec +++ b/jetty.spec @@ -8,11 +8,11 @@ %global rundir %{_localstatedir}/run/%{name} %global jettylibdir %{_localstatedir}/lib/%{name} %global appdir %{jettylibdir}/webapps -%global addver .v20190215 +%global addver .v20190411 %bcond_with jp_minimal Name: jetty -Version: 9.4.15 -Release: 9 +Version: 9.4.16 +Release: 1 Summary: Java Webserver and Servlet Container License: ASL 2.0 or EPL-1.0 or EPL-2.0 URL: http://www.eclipse.org/jetty/ @@ -22,15 +22,10 @@ Source3: jetty.logrotate Source5: %{name}.service Source6: LICENSE-MIT Patch0: CVE-2020-27216.patch -Patch1: CVE-2020-27223-pre-1.patch -Patch2: CVE-2020-27223-pre-2.patch -Patch3: CVE-2020-27223-pre-3.patch -Patch4: CVE-2020-27223-pre-4.patch -Patch5: CVE-2020-27223.patch -Patch6: CVE-2021-28165-1.patch -Patch7: CVE-2021-28165-2.patch -Patch8: CVE-2021-28169.patch -Patch9: CVE-2021-34428.patch +Patch1: CVE-2020-27223.patch +Patch2: CVE-2021-28165.patch +Patch3: CVE-2021-28169.patch +Patch4: CVE-2021-34428.patch BuildRequires: maven-local mvn(javax.servlet:javax.servlet-api) BuildRequires: mvn(org.apache.felix:maven-bundle-plugin) @@ -790,6 +785,9 @@ exit 0 %license LICENSE NOTICE.txt LICENSE-MIT %changelog +* Tue Sep 13 2022 liangqifeng - 9.4.16-1 +- update to 9.4.14 to Fix CVE-2019-10241 + * Thu Jul 1 2021 wutao - 9.4.15-9 - Fix CVE-2021-34428