Projects
openEuler:24.03:SP1:Everything:64G
jetty
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 2
View file
_service:tar_scm:jetty.spec
Changed
@@ -12,7 +12,7 @@ %bcond_with jp_minimal Name: jetty Version: 9.4.16 -Release: 6 +Release: 7 Summary: Java Webserver and Servlet Container License: Apache-2.0 OR EPL-1.0 URL: http://www.eclipse.org/jetty/ @@ -28,6 +28,10 @@ Patch4: CVE-2021-34428.patch Patch5: CVE-2022-2047.patch Patch6: CVE-2022-2048.patch +Patch7: CVE-2023-26048.patch +Patch8: CVE-2023-26049.patch +Patch9: CVE-2023-36479.patch +Patch10: CVE-2023-40167.patch BuildRequires: maven-local mvn(javax.servlet:javax.servlet-api) < 4.0.0 BuildRequires: mvn(org.apache.felix:maven-bundle-plugin) @@ -796,6 +800,9 @@ %license LICENSE NOTICE.txt LICENSE-MIT %changelog +* Tue Oct 15 2024 wangkai <13474090681@163.com> - 9.4.16-7 +- Fix CVE-2023-26048,CVE-2023-26049,CVE-2023-36479,CVE-2023-40167 + * Thu Jul 18 2024 yaoxin <yao_xin001@hoperun.com> - 9.4.16-6 - License compliance rectification
View file
_service:tar_scm:CVE-2023-26048.patch
Added
@@ -0,0 +1,537 @@ +From: Markus Koschany <apo@debian.org> +Date: Tue, 26 Sep 2023 20:37:47 +0200 +Subject: CVE-2023-26048 + +Origin: https://github.com/eclipse/jetty.project/pull/9345 +--- + .../jetty/http/MultiPartFormInputStream.java | 76 +++++++----- + .../java/org/eclipse/jetty/server/MultiParts.java | 14 ++- + .../java/org/eclipse/jetty/server/Request.java | 127 ++++++++++++--------- + .../jetty/server/handler/ContextHandler.java | 4 + + .../jetty/util/MultiPartInputStreamParser.java | 24 +++- + 5 files changed, 158 insertions(+), 87 deletions(-) + +diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java +index 928f59c..a1092f7 100644 +--- a/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java ++++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MultiPartFormInputStream.java +@@ -60,11 +60,14 @@ import org.eclipse.jetty.util.log.Logger; + public class MultiPartFormInputStream + { + private static final Logger LOG = Log.getLogger(MultiPartFormInputStream.class); ++ private static final int DEFAULT_MAX_FORM_KEYS = 1000; + private static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); ++ private final MultiMap<Part> _parts; ++ private final int _maxParts; ++ private int _numParts = 0; + private InputStream _in; + private MultipartConfigElement _config; + private String _contentType; +- private MultiMap<Part> _parts; + private Throwable _err; + private File _tmpDir; + private File _contextTmpDir; +@@ -332,26 +335,42 @@ public class MultiPartFormInputStream + * @param contextTmpDir javax.servlet.context.tempdir + */ + public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) ++ { ++ this(in, contentType, config, contextTmpDir, DEFAULT_MAX_FORM_KEYS); ++ } ++ ++ /** ++ * @param in Request input stream ++ * @param contentType Content-Type header ++ * @param config MultipartConfigElement ++ * @param contextTmpDir javax.servlet.context.tempdir ++ * @param maxParts the maximum number of parts that can be parsed from the multipart content (0 for no parts allowed, -1 for unlimited parts). ++ */ ++ public MultiPartFormInputStream(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, int maxParts) ++ + { + _contentType = contentType; + _config = config; + _contextTmpDir = contextTmpDir; ++ _maxParts = maxParts; + if (_contextTmpDir == null) + _contextTmpDir = new File(System.getProperty("java.io.tmpdir")); + + if (_config == null) + _config = new MultipartConfigElement(_contextTmpDir.getAbsolutePath()); + ++ MultiMap<Part> parts = new MultiMap<>(); + if (in instanceof ServletInputStream) + { + if (((ServletInputStream)in).isFinished()) + { +- _parts = EMPTY_MAP; ++ parts = EMPTY_MAP; + _parsed = true; +- return; + } + } +- _in = new BufferedInputStream(in); ++ if (!_parsed) ++ _in = new BufferedInputStream(in); ++ _parts = parts; + } + + /** +@@ -495,16 +514,15 @@ public class MultiPartFormInputStream + if (_parsed) + return; + _parsed = true; +- ++ ++ MultiPartParser parser = null; ++ Handler handler = new Handler(); + try + { +- // initialize +- _parts = new MultiMap<>(); +- + // if its not a multipart request, don't parse it + if (_contentType == null || !_contentType.startsWith("multipart/form-data")) + return; +- ++ + // sort out the location to which to write the files + if (_config.getLocation() == null) + _tmpDir = _contextTmpDir; +@@ -518,10 +536,10 @@ public class MultiPartFormInputStream + else + _tmpDir = new File(_contextTmpDir, _config.getLocation()); + } +- ++ + if (!_tmpDir.exists()) + _tmpDir.mkdirs(); +- ++ + String contentTypeBoundary = ""; + int bstart = _contentType.indexOf("boundary="); + if (bstart >= 0) +@@ -530,22 +548,19 @@ public class MultiPartFormInputStream + bend = (bend < 0 ? _contentType.length() : bend); + contentTypeBoundary = QuotedStringTokenizer.unquote(value(_contentType.substring(bstart, bend)).trim()); + } +- +- Handler handler = new Handler(); +- MultiPartParser parser = new MultiPartParser(handler, contentTypeBoundary); +- ++ ++ parser = new MultiPartParser(handler, contentTypeBoundary); + byte data = new byte_bufferSize; + int len; + long total = 0; +- ++ + while (true) + { +- ++ + len = _in.read(data); +- ++ + if (len > 0) + { +- + // keep running total of size of bytes read from input and throw an exception if exceeds MultipartConfigElement._maxRequestSize + total += len; + if (_config.getMaxRequestSize() > 0 && total > _config.getMaxRequestSize()) +@@ -553,30 +568,28 @@ public class MultiPartFormInputStream + _err = new IllegalStateException("Request exceeds maxRequestSize (" + _config.getMaxRequestSize() + ")"); + return; + } +- ++ + ByteBuffer buffer = BufferUtil.toBuffer(data); + buffer.limit(len); + if (parser.parse(buffer, false)) + break; +- ++ + if (buffer.hasRemaining()) + throw new IllegalStateException("Buffer did not fully consume"); +- + } + else if (len == -1) + { + parser.parse(BufferUtil.EMPTY_BUFFER, true); + break; + } +- + } +- ++ + // check for exceptions + if (_err != null) + { + return; + } +- ++ + // check we read to the end of the message + if (parser.getState() != MultiPartParser.State.END) + { +@@ -585,19 +598,23 @@ public class MultiPartFormInputStream + else + _err = new IOException("Incomplete Multipart"); + } +- ++ + if (LOG.isDebugEnabled()) + { + LOG.debug("Parsing Complete {} err={}", parser, _err); + } +- + } + catch (Throwable e) + { + _err = e; ++ ++ // Notify parser if failure occurs ++ if (parser != null) ++ parser.parse(BufferUtil.EMPTY_BUFFER, true); + } + } +- ++ ++ + class Handler implements MultiPartParser.Handler + { + private MultiPart _part = null; +@@ -735,6 +752,9 @@ public class MultiPartFormInputStream + public void startPart() + { + reset(); ++ _numParts++; ++ if (_maxParts >= 0 && _numParts > _maxParts) ++ throw new IllegalStateException(String.format("Form with too many parts %d > %d", _numParts, _maxParts)); + } + + @Override +diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java +index f28b945..1b91649 100644 +--- a/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java ++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/MultiParts.java +@@ -57,7 +57,12 @@ public interface MultiParts extends Closeable + + public MultiPartsHttpParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request) throws IOException + { +- _httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir); ++ this(in, contentType, config, contextTmpDir, request, ContextHandler.DEFAULT_MAX_FORM_KEYS); ++ } ++ ++ public MultiPartsHttpParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request, int maxParts) throws IOException ++ { ++ _httpParser = new MultiPartFormInputStream(in, contentType, config, contextTmpDir, maxParts); + _context = request.getContext(); + _httpParser.getParts(); + } +@@ -116,7 +121,12 @@ public interface MultiParts extends Closeable + + public MultiPartsUtilParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request) throws IOException + { +- _utilParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir); ++ this(in, contentType, config, contextTmpDir, request, ContextHandler.DEFAULT_MAX_FORM_KEYS); ++ } ++ ++ public MultiPartsUtilParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, Request request, int maxParts) throws IOException ++ { ++ _utilParser = new MultiPartInputStreamParser(in, contentType, config, contextTmpDir, maxParts); + _context = request.getContext(); + _utilParser.getParts(); + +diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +index 5b996bb..8a7e6f9 100644 +--- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java ++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +@@ -425,6 +425,14 @@ public class Request implements HttpServletRequest + return parameters==null?NO_PARAMS:parameters; + } + ++ private boolean isContentEncodingSupported() ++ { ++ String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING); ++ if (contentEncoding == null) ++ return true; ++ return HttpHeaderValue.IDENTITY.is(contentEncoding); ++ } ++ + /* ------------------------------------------------------------ */ + private void extractQueryParameters() + { +@@ -458,33 +466,34 @@ public class Request implements HttpServletRequest + { + String contentType = getContentType(); + if (contentType == null || contentType.isEmpty()) +- _contentParameters=NO_PARAMS; ++ _contentParameters = NO_PARAMS; + else + { +- _contentParameters=new MultiMap<>(); ++ _contentParameters = new MultiMap<>(); + int contentLength = getContentLength(); + if (contentLength != 0 && _inputState == __NONE) + { +- contentType = HttpFields.valueParameters(contentType, null); +- if (MimeTypes.Type.FORM_ENCODED.is(contentType) && ++ String baseType = HttpFields.valueParameters(contentType, null); ++ if (MimeTypes.Type.FORM_ENCODED.is(baseType) && + _channel.getHttpConfiguration().isFormEncodedMethod(getMethod())) + { +- if (_metaData!=null) ++ if (_metaData != null && !isContentEncodingSupported()) + { +- String contentEncoding = getHttpFields().get(HttpHeader.CONTENT_ENCODING); +- if (contentEncoding!=null && !HttpHeaderValue.IDENTITY.is(contentEncoding)) +- throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501, "Unsupported Content-Encoding"); ++ throw new BadMessageException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Content-Encoding"); + } ++ + extractFormParameters(_contentParameters); + } +- else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(contentType) && +- getAttribute(__MULTIPART_CONFIG_ELEMENT) != null && +- _multiParts == null) ++ else if (MimeTypes.Type.MULTIPART_FORM_DATA.is(baseType) && ++ getAttribute(__MULTIPART_CONFIG_ELEMENT) != null && ++ _multiParts == null) + { + try + { +- if (_metaData!=null && getHttpFields().contains(HttpHeader.CONTENT_ENCODING)) +- throw new BadMessageException(HttpStatus.NOT_IMPLEMENTED_501,"Unsupported Content-Encoding"); ++ if (_metaData != null && !isContentEncodingSupported()) ++ { ++ throw new BadMessageException(HttpStatus.UNSUPPORTED_MEDIA_TYPE_415, "Unsupported Content-Encoding"); ++ } + getParts(_contentParameters); + } + catch (IOException | ServletException e) +@@ -502,57 +511,30 @@ public class Request implements HttpServletRequest + { + try + { +- int maxFormContentSize = -1; +- int maxFormKeys = -1; ++ int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE; ++ int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS; + + if (_context != null) + { +- maxFormContentSize = _context.getContextHandler().getMaxFormContentSize(); +- maxFormKeys = _context.getContextHandler().getMaxFormKeys(); ++ ContextHandler contextHandler = _context.getContextHandler(); ++ maxFormContentSize = contextHandler.getMaxFormContentSize(); ++ maxFormKeys = contextHandler.getMaxFormKeys(); + } +- +- if (maxFormContentSize < 0) ++ else + { +- Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormContentSize"); +- if (obj == null) +- maxFormContentSize = 200000; +- else if (obj instanceof Number) +- { +- Number size = (Number)obj; +- maxFormContentSize = size.intValue(); +- } +- else if (obj instanceof String) +- { +- maxFormContentSize = Integer.parseInt((String)obj); +- } +- } +- +- if (maxFormKeys < 0) +- { +- Object obj = _channel.getServer().getAttribute("org.eclipse.jetty.server.Request.maxFormKeys"); +- if (obj == null) +- maxFormKeys = 1000; +- else if (obj instanceof Number) +- { +- Number keys = (Number)obj; +- maxFormKeys = keys.intValue(); +- } +- else if (obj instanceof String) +- { +- maxFormKeys = Integer.parseInt((String)obj); +- } ++ maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize); ++ maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys); + } + + int contentLength = getContentLength(); +- if (contentLength > maxFormContentSize && maxFormContentSize > 0) +- { +- throw new IllegalStateException("Form too large: " + contentLength + " > " + maxFormContentSize); +- } ++ if (maxFormContentSize >= 0 && contentLength > maxFormContentSize) ++ throw new IllegalStateException("Form is larger than max length " + maxFormContentSize); ++ + InputStream in = getInputStream(); + if (_input.isAsync()) + throw new IllegalStateException("Cannot extract parameters with async IO"); + +- UrlEncoded.decodeTo(in,params,getCharacterEncoding(),contentLength<0?maxFormContentSize:-1,maxFormKeys); ++ UrlEncoded.decodeTo(in, params, getCharacterEncoding(), maxFormContentSize, maxFormKeys); + } + catch (IOException e) + { +@@ -561,6 +543,16 @@ public class Request implements HttpServletRequest + } + } + ++ private int lookupServerAttribute(String key, int dftValue) ++ { ++ Object attribute = _channel.getServer().getAttribute(key); ++ if (attribute instanceof Number) ++ return ((Number)attribute).intValue(); ++ else if (attribute instanceof String) ++ return Integer.parseInt((String)attribute); ++ return dftValue; ++ } ++ + /* ------------------------------------------------------------ */ + @Override + public AsyncContext getAsyncContext() +@@ -2351,9 +2343,23 @@ public class Request implements HttpServletRequest + if (config == null) + throw new IllegalStateException("No multipart config for servlet"); + ++ int maxFormContentSize = ContextHandler.DEFAULT_MAX_FORM_CONTENT_SIZE; ++ int maxFormKeys = ContextHandler.DEFAULT_MAX_FORM_KEYS; ++ if (_context != null) ++ { ++ ContextHandler contextHandler = _context.getContextHandler(); ++ maxFormContentSize = contextHandler.getMaxFormContentSize(); ++ maxFormKeys = contextHandler.getMaxFormKeys(); ++ } ++ else ++ { ++ maxFormContentSize = lookupServerAttribute(ContextHandler.MAX_FORM_CONTENT_SIZE_KEY, maxFormContentSize); ++ maxFormKeys = lookupServerAttribute(ContextHandler.MAX_FORM_KEYS_KEY, maxFormKeys); ++ } ++ + _multiParts = newMultiParts(getInputStream(), + getContentType(), config, +- (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); ++ (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null),maxFormKeys); + + setAttribute(__MULTIPARTS, _multiParts); + Collection<Part> parts = _multiParts.getParts(); //causes parsing +@@ -2388,11 +2394,16 @@ public class Request implements HttpServletRequest + else + defaultCharset = StandardCharsets.UTF_8; + ++ long formContentSize = 0; + ByteArrayOutputStream os = null; + for (Part p:parts) + { + if (p.getSubmittedFileName() == null) + { ++ formContentSize = Math.addExact(formContentSize, p.getSize()); ++ if (maxFormContentSize >= 0 && formContentSize > maxFormContentSize) ++ throw new IllegalStateException("Form is larger than max length " + maxFormContentSize); ++ + // Servlet Spec 3.0 pg 23, parts without filename must be put into params. + String charset = null; + if (p.getContentType() != null) +@@ -2418,7 +2429,9 @@ public class Request implements HttpServletRequest + } + + +- private MultiParts newMultiParts(ServletInputStream inputStream, String contentType, MultipartConfigElement config, Object object) throws IOException ++ private MultiParts newMultiParts(ServletInputStream inputStream, String ++ contentType, MultipartConfigElement config, Object object, ++ int maxParts) throws IOException + { + MultiPartFormDataCompliance compliance = getHttpChannel().getHttpConfiguration().getMultipartFormDataCompliance(); + if(LOG.isDebugEnabled()) +@@ -2428,12 +2441,14 @@ public class Request implements HttpServletRequest + { + case RFC7578: + return new MultiParts.MultiPartsHttpParser(getInputStream(), getContentType(), config, +- (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this); ++ (_context != ++ null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this, maxParts); + + case LEGACY: + default: + return new MultiParts.MultiPartsUtilParser(getInputStream(), getContentType(), config, +- (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this); ++ (_context != ++ null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null), this, maxParts); + + } + } +diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +index b6ca046..c4cbebd 100644 +--- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java ++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +@@ -132,6 +132,10 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu + */ + public static final String MANAGED_ATTRIBUTES = "org.eclipse.jetty.server.context.ManagedAttributes"; + ++ public static final String MAX_FORM_KEYS_KEY = "org.eclipse.jetty.server.Request.maxFormKeys"; ++ public static final String MAX_FORM_CONTENT_SIZE_KEY = "org.eclipse.jetty.server.Request.maxFormContentSize"; ++ public static final int DEFAULT_MAX_FORM_KEYS = 1000; ++ public static final int DEFAULT_MAX_FORM_CONTENT_SIZE = 200000; + /* ------------------------------------------------------------ */ + /** + * Get the current ServletContext implementation. +diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java +index 17e7bb1..d45b8ff 100644 +--- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java ++++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStreamParser.java +@@ -65,8 +65,11 @@ import org.eclipse.jetty.util.log.Logger; + public class MultiPartInputStreamParser + { + private static final Logger LOG = Log.getLogger(MultiPartInputStreamParser.class); ++ private static final int DEFAULT_MAX_FORM_KEYS = 1000; + public static final MultipartConfigElement __DEFAULT_MULTIPART_CONFIG = new MultipartConfigElement(System.getProperty("java.io.tmpdir")); +- public static final MultiMap<Part> EMPTY_MAP = new MultiMap(Collections.emptyMap()); ++ public static final MultiMap<Part> EMPTY_MAP = new MultiMap<>(Collections.emptyMap()); ++ private final int _maxParts; ++ private int _numParts; + protected InputStream _in; + protected MultipartConfigElement _config; + protected String _contentType; +@@ -411,9 +414,23 @@ public class MultiPartInputStreamParser + */ + public MultiPartInputStreamParser (InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir) + { ++ this(in, contentType, config, contextTmpDir, DEFAULT_MAX_FORM_KEYS); ++ } ++ ++ /** ++ * @param in Request input stream ++ * @param contentType Content-Type header ++ * @param config MultipartConfigElement ++ * @param contextTmpDir javax.servlet.context.tempdir ++ * @param maxParts the maximum number of parts that can be parsed from the multipart content (0 for no parts allowed, -1 for unlimited parts). ++ */ ++ public MultiPartInputStreamParser(InputStream in, String contentType, MultipartConfigElement config, File contextTmpDir, int maxParts) ++ ++ { + _contentType = contentType; + _config = config; + _contextTmpDir = contextTmpDir; ++ _maxParts = maxParts; + if (_contextTmpDir == null) + _contextTmpDir = new File (System.getProperty("java.io.tmpdir")); + +@@ -712,6 +729,11 @@ public class MultiPartInputStreamParser + continue; + } + ++ // Check if we can create a new part. ++ _numParts++; ++ if (_maxParts >= 0 && _numParts > _maxParts) ++ throw new IllegalStateException(String.format("Form with too many parts %d > %d", _numParts, _maxParts)); ++ + //Have a new Part + MultiPart part = new MultiPart(name, filename); + part.setHeaders(headers);
View file
_service:tar_scm:CVE-2023-26049.patch
Added
@@ -0,0 +1,831 @@ +From: Markus Koschany <apo@debian.org> +Date: Tue, 26 Sep 2023 23:42:03 +0200 +Subject: CVE-2023-26049 + +Origin: https://github.com/eclipse/jetty.project/pull/9352 +--- + .../org/eclipse/jetty/http/CookieCompliance.java | 2 +- + .../org/eclipse/jetty/server/CookieCutter.java | 205 ++++++++++----- + .../org/eclipse/jetty/server/CookieCutterTest.java | 280 ++++++++++++++++----- + .../java/org/eclipse/jetty/server/RequestTest.java | 2 + + 4 files changed, 361 insertions(+), 128 deletions(-) + +diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCompliance.java b/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCompliance.java +index b2d339c..d514c15 100644 +--- a/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCompliance.java ++++ b/jetty-http/src/main/java/org/eclipse/jetty/http/CookieCompliance.java +@@ -22,4 +22,4 @@ package org.eclipse.jetty.http; + * The compliance for Cookie handling. + * + */ +-public enum CookieCompliance { RFC6265, RFC2965 } ++public enum CookieCompliance { RFC6265, RFC2965, RFC6265_LEGACY } +diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java +index 5dce1cf..e28d262 100644 +--- a/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java ++++ b/jetty-server/src/main/java/org/eclipse/jetty/server/CookieCutter.java +@@ -107,23 +107,24 @@ public class CookieCutter + _lastCookies=null; + _fieldList.add(_fields++,f); + } +- +- ++ + protected void parseFields() + { +- _lastCookies=null; +- _cookies=null; +- ++ _lastCookies = null; ++ _cookies = null; ++ + List<Cookie> cookies = new ArrayList<>(); + + int version = 0; + + // delete excess fields +- while (_fieldList.size()>_fields) ++ while (_fieldList.size() > _fields) ++ { + _fieldList.remove(_fields); +- +- StringBuilder unquoted=null; +- ++ } ++ ++ StringBuilder unquoted = null; ++ + // For each cookie field + for (String hdr : _fieldList) + { +@@ -132,25 +133,31 @@ public class CookieCutter + + Cookie cookie = null; + +- boolean invalue=false; +- boolean inQuoted=false; +- boolean quoted=false; +- boolean escaped=false; +- int tokenstart=-1; +- int tokenend=-1; ++ boolean invalue = false; ++ boolean inQuoted = false; ++ boolean quoted = false; ++ boolean escaped = false; ++ boolean reject = false; ++ int tokenstart = -1; ++ int tokenend = -1; + for (int i = 0, length = hdr.length(); i <= length; i++) + { +- char c = i==length?0:hdr.charAt(i); +- +- // System.err.printf("i=%d/%d c=%s v=%b q=%b/%b e=%b u=%s s=%d e=%d \t%s=%s%n" ,i,length,c==0?"|":(""+c),invalue,inQuoted,quoted,escaped,unquoted,tokenstart,tokenend,name,value); +- ++ char c = i == length ? 0 : hdr.charAt(i); ++ + // Handle quoted values for name or value + if (inQuoted) + { ++ boolean eol = c == 0 && i == hdr.length(); ++ if (!eol && _compliance != CookieCompliance.RFC2965 && isRFC6265RejectedCharacter(inQuoted, c)) ++ { ++ reject = true; ++ continue; ++ } ++ + if (escaped) + { +- escaped=false; +- if (c>0) ++ escaped = false; ++ if (c > 0) + unquoted.append(c); + else + { +@@ -160,7 +167,7 @@ public class CookieCutter + } + continue; + } +- ++ + switch (c) + { + case '"': +@@ -175,15 +182,24 @@ public class CookieCutter + continue; + + case 0: +- // unterminated quote, let's ignore quotes ++ // unterminated quote ++ if (_compliance == CookieCompliance.RFC6265) ++ continue; ++ // let's ignore quotes + unquoted.setLength(0); + inQuoted = false; + i--; + continue; +- ++ ++ case ';': ++ if (_compliance == CookieCompliance.RFC6265) ++ reject = true; ++ else ++ unquoted.append(c); ++ continue; ++ + default: + unquoted.append(c); +- continue; + } + } + else +@@ -191,7 +207,14 @@ public class CookieCutter + // Handle name and value state machines + if (invalue) + { +- // parse the value ++ boolean eol = c == 0 && i == hdr.length(); ++ if (!eol && _compliance == CookieCompliance.RFC6265 && isRFC6265RejectedCharacter(inQuoted, c)) ++ { ++ reject = true; ++ continue; ++ } ++ ++ // parse the cookie-value + switch (c) + { + case ' ': +@@ -199,19 +222,19 @@ public class CookieCutter + break; + + case ',': +- if (_compliance!=CookieCompliance.RFC2965) ++ if (_compliance != CookieCompliance.RFC2965) + { + if (quoted) + { + // must have been a bad internal quote. let's fix as best we can +- unquoted.append(hdr,tokenstart,i--); ++ unquoted.append(hdr, tokenstart, i--); + inQuoted = true; + quoted = false; + continue; + } +- if (tokenstart<0) ++ if (tokenstart < 0) + tokenstart = i; +- tokenend=i; ++ tokenend = i; + continue; + } + // fall through +@@ -226,8 +249,8 @@ public class CookieCutter + unquoted.setLength(0); + quoted = false; + } +- else if(tokenstart>=0) +- value = tokenend>=tokenstart?hdr.substring(tokenstart, tokenend+1):hdr.substring(tokenstart); ++ else if (tokenstart >= 0) ++ value = tokenend >= tokenstart ? hdr.substring(tokenstart, tokenend + 1) : hdr.substring(tokenstart); + else + value = ""; + +@@ -235,22 +258,22 @@ public class CookieCutter + { + if (name.startsWith("$")) + { +- if (_compliance==CookieCompliance.RFC2965) ++ if (_compliance == CookieCompliance.RFC2965) + { + String lowercaseName = name.toLowerCase(Locale.ENGLISH); +- switch(lowercaseName) ++ switch (lowercaseName) + { + case "$path": +- if (cookie!=null) ++ if (cookie != null) + cookie.setPath(value); + break; + case "$domain": +- if (cookie!=null) ++ if (cookie != null) + cookie.setDomain(value); + break; + case "$port": +- if (cookie!=null) +- cookie.setComment("$port="+value); ++ if (cookie != null) ++ cookie.setComment("$port=" + value); + break; + case "$version": + version = Integer.parseInt(value); +@@ -265,7 +288,10 @@ public class CookieCutter + cookie = new Cookie(name, value); + if (version > 0) + cookie.setVersion(version); +- cookies.add(cookie); ++ if (!reject) ++ { ++ cookies.add(cookie); ++ } + } + } + catch (Exception e) +@@ -275,46 +301,68 @@ public class CookieCutter + + name = null; + tokenstart = -1; +- invalue=false; ++ invalue = false; ++ reject = false; + + break; + } + + case '"': +- if (tokenstart<0) ++ if (tokenstart < 0) + { +- tokenstart=i; +- inQuoted=true; +- if (unquoted==null) +- unquoted=new StringBuilder(); ++ tokenstart = i; ++ inQuoted = true; ++ if (unquoted == null) ++ unquoted = new StringBuilder(); + break; + } ++ else if (_compliance == CookieCompliance.RFC6265) ++ { ++ reject = true; ++ continue; ++ } + // fall through to default case + + default: ++ if (_compliance == CookieCompliance.RFC6265 && quoted) ++ { ++ reject = true; ++ continue; ++ } ++ + if (quoted) + { + // must have been a bad internal quote. let's fix as best we can +- unquoted.append(hdr,tokenstart,i--); ++ unquoted.append(hdr, tokenstart, i--); + inQuoted = true; + quoted = false; + continue; + } +- if (tokenstart<0) ++ ++ if (_compliance == CookieCompliance.RFC6265_LEGACY && isRFC6265RejectedCharacter(inQuoted, c)) ++ reject = true; ++ ++ if (tokenstart < 0) + tokenstart = i; +- tokenend=i; +- continue; ++ tokenend = i; + } + } + else + { +- // parse the name ++ // parse the cookie-name + switch (c) + { + case ' ': + case '\t': + continue; + ++ case ';': ++ // a cookie terminated with no '=' sign. ++ tokenstart = -1; ++ invalue = false; ++ reject = false; ++ continue; ++ + case '=': + if (quoted) + { +@@ -322,8 +370,8 @@ public class CookieCutter + unquoted.setLength(0); + quoted = false; + } +- else if(tokenstart>=0) +- name = tokenend>=tokenstart?hdr.substring(tokenstart, tokenend+1):hdr.substring(tokenstart); ++ else if (tokenstart >= 0) ++ name = tokenend >= tokenstart ? hdr.substring(tokenstart, tokenend + 1) : hdr.substring(tokenstart); + + tokenstart = -1; + invalue = true; +@@ -333,14 +381,18 @@ public class CookieCutter + if (quoted) + { + // must have been a bad internal quote. let's fix as best we can +- unquoted.append(hdr,tokenstart,i--); ++ unquoted.append(hdr, tokenstart, i--); + inQuoted = true; + quoted = false; + continue; + } +- if (tokenstart<0) +- tokenstart=i; +- tokenend=i; ++ ++ if (_compliance != CookieCompliance.RFC2965 && isRFC6265RejectedCharacter(inQuoted, c)) ++ reject = true; ++ ++ if (tokenstart < 0) ++ tokenstart = i; ++ tokenend = i; + continue; + } + } +@@ -348,8 +400,45 @@ public class CookieCutter + } + } + +- _cookies = (Cookie) cookies.toArray(new Cookiecookies.size()); +- _lastCookies=_cookies; ++ _cookies = cookies.toArray(new Cookie0); ++ _lastCookies = _cookies; ++ } ++ ++ ++ protected boolean isRFC6265RejectedCharacter(boolean inQuoted, char c) ++ { ++ // LEGACY test ++ if (_compliance == CookieCompliance.RFC6265_LEGACY) ++ { ++ if (inQuoted) ++ { ++ // We only reject if a Control Character is encountered ++ if (Character.isISOControl(c)) ++ return true; ++ } ++ else ++ { ++ return Character.isISOControl(c) || // control characters ++ c > 127 || // 8-bit characters ++ c == ',' || // comma ++ c == ';'; // semicolon ++ } ++ return false; ++ } ++ ++ /* From RFC6265 - Section 4.1.1 - Syntax ++ * cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E ++ * ; US-ASCII characters excluding CTLs, ++ * ; whitespace DQUOTE, comma, semicolon, ++ * ; and backslash ++ * ++ * Note: DQUOTE and semicolon are used as separator by the parser, ++ * so we can consider them authorized. ++ */ ++ return c > 127 || // 8-bit characters ++ Character.isISOControl(c) || // control characters ++ c == ',' || // comma ++ c == '\\'; // backslash + } + + } +diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java +index ec534a1..3e84ce6 100644 +--- a/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java ++++ b/jetty-server/src/test/java/org/eclipse/jetty/server/CookieCutterTest.java +@@ -1,6 +1,6 @@ + // + // ======================================================================== +-// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. ++// Copyright (c) 1995-2022 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,18 +18,21 @@ + + package org.eclipse.jetty.server; + +-import static org.hamcrest.Matchers.is; +-import static org.hamcrest.MatcherAssert.assertThat; +- ++import java.util.Arrays; ++import java.util.List; + import javax.servlet.http.Cookie; + + import org.eclipse.jetty.http.CookieCompliance; +-import org.junit.jupiter.api.Disabled; + import org.junit.jupiter.api.Test; ++import org.junit.jupiter.params.ParameterizedTest; ++import org.junit.jupiter.params.provider.MethodSource; ++ ++import static org.hamcrest.MatcherAssert.assertThat; ++import static org.hamcrest.Matchers.is; + + public class CookieCutterTest + { +- private Cookie parseCookieHeaders(CookieCompliance compliance,String... headers) ++ private Cookie parseCookieHeaders(CookieCompliance compliance, String... headers) + { + CookieCutter cutter = new CookieCutter(compliance); + for (String header : headers) +@@ -38,7 +41,7 @@ public class CookieCutterTest + } + return cutter.getCookies(); + } +- ++ + private void assertCookie(String prefix, Cookie cookie, + String expectedName, + String expectedValue, +@@ -50,142 +53,174 @@ public class CookieCutterTest + assertThat(prefix + ".version", cookie.getVersion(), is(expectedVersion)); + assertThat(prefix + ".path", cookie.getPath(), is(expectedPath)); + } +- ++ + /** + * Example from RFC2109 and RFC2965 + */ + @Test +- public void testRFC_Single() ++ public void testRFCSingle() + { + String rawCookie = "$Version=\"1\"; Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\""; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie); +- ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(1)); + assertCookie("Cookies0", cookies0, "Customer", "WILE_E_COYOTE", 1, "/acme"); + } +- ++ ++ /** ++ * Example from RFC2109 and RFC2965. ++ * <p> ++ * Lenient parsing, input has no spaces after ';' token. ++ * </p> ++ */ ++ @Test ++ public void testRFCSingleLenientNoSpaces() ++ { ++ String rawCookie = "$Version=\"1\";Customer=\"WILE_E_COYOTE\";$Path=\"/acme\""; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); ++ ++ assertThat("Cookies.length", cookies.length, is(1)); ++ assertCookie("Cookies0", cookies0, "Customer", "WILE_E_COYOTE", 1, "/acme"); ++ } ++ + /** + * Example from RFC2109 and RFC2965 + */ + @Test +- public void testRFC_Double() ++ public void testRFCDouble() + { + String rawCookie = "$Version=\"1\"; " + +- "Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " + +- "Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie); +- ++ "Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " + ++ "Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(2)); + assertCookie("Cookies0", cookies0, "Customer", "WILE_E_COYOTE", 1, "/acme"); + assertCookie("Cookies1", cookies1, "Part_Number", "Rocket_Launcher_0001", 1, "/acme"); + } +- ++ + /** + * Example from RFC2109 and RFC2965 + */ + @Test +- public void testRFC_Triple() ++ public void testRFCTriple() + { + String rawCookie = "$Version=\"1\"; " + +- "Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " + +- "Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; " + +- "Shipping=\"FedEx\"; $Path=\"/acme\""; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie); +- ++ "Customer=\"WILE_E_COYOTE\"; $Path=\"/acme\"; " + ++ "Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\"; " + ++ "Shipping=\"FedEx\"; $Path=\"/acme\""; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(3)); + assertCookie("Cookies0", cookies0, "Customer", "WILE_E_COYOTE", 1, "/acme"); + assertCookie("Cookies1", cookies1, "Part_Number", "Rocket_Launcher_0001", 1, "/acme"); + assertCookie("Cookies2", cookies2, "Shipping", "FedEx", 1, "/acme"); + } +- ++ + /** + * Example from RFC2109 and RFC2965 + */ + @Test +- public void testRFC_PathExample() ++ public void testRFCPathExample() + { + String rawCookie = "$Version=\"1\"; " + +- "Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " + +- "Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie); +- ++ "Part_Number=\"Riding_Rocket_0023\"; $Path=\"/acme/ammo\"; " + ++ "Part_Number=\"Rocket_Launcher_0001\"; $Path=\"/acme\""; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(2)); + assertCookie("Cookies0", cookies0, "Part_Number", "Riding_Rocket_0023", 1, "/acme/ammo"); + assertCookie("Cookies1", cookies1, "Part_Number", "Rocket_Launcher_0001", 1, "/acme"); + } +- ++ + /** + * Example from RFC2109 + */ + @Test +- public void testRFC2109_CookieSpoofingExample() ++ public void testRFC2109CookieSpoofingExample() + { + String rawCookie = "$Version=\"1\"; " + +- "session_id=\"1234\"; " + +- "session_id=\"1111\"; $Domain=\".cracker.edu\""; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie); +- ++ "session_id=\"1234\"; " + ++ "session_id=\"1111\"; $Domain=\".cracker.edu\""; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(2)); + assertCookie("Cookies0", cookies0, "session_id", "1234", 1, null); + assertCookie("Cookies1", cookies1, "session_id", "1111", 1, null); + } +- ++ + /** + * Example from RFC2965 + */ + @Test +- public void testRFC2965_CookieSpoofingExample() ++ public void testRFC2965CookieSpoofingExample() + { + String rawCookie = "$Version=\"1\"; session_id=\"1234\", " + +- "$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\""; +- +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965,rawCookie); ++ "$Version=\"1\"; session_id=\"1111\"; $Domain=\".cracker.edu\""; + ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC2965, rawCookie); + assertThat("Cookies.length", cookies.length, is(2)); + assertCookie("Cookies0", cookies0, "session_id", "1234", 1, null); + assertCookie("Cookies1", cookies1, "session_id", "1111", 1, null); + +- cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie); +- assertThat("Cookies.length", cookies.length, is(2)); +- assertCookie("Cookies0", cookies0, "session_id", "1234\", $Version=\"1", 0, null); +- assertCookie("Cookies1", cookies1, "session_id", "1111", 0, null); ++ cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ assertThat("Cookies.length", cookies.length, is(1)); ++ assertCookie("Cookies0", cookies0, "session_id", "1111", 0, null); + } +- ++ + /** + * Example from RFC6265 + */ + @Test +- public void testRFC6265_SidExample() ++ public void testRFC6265SidExample() + { + String rawCookie = "SID=31d4d96e407aad42"; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie); +- ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(1)); + assertCookie("Cookies0", cookies0, "SID", "31d4d96e407aad42", 0, null); + } +- ++ + /** + * Example from RFC6265 + */ + @Test +- public void testRFC6265_SidLangExample() ++ public void testRFC6265SidLangExample() + { + String rawCookie = "SID=31d4d96e407aad42; lang=en-US"; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie); +- ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ ++ assertThat("Cookies.length", cookies.length, is(2)); ++ assertCookie("Cookies0", cookies0, "SID", "31d4d96e407aad42", 0, null); ++ assertCookie("Cookies1", cookies1, "lang", "en-US", 0, null); ++ } ++ ++ /** ++ * Example from RFC6265. ++ * <p> ++ * Lenient parsing, input has no spaces after ';' token. ++ * </p> ++ */ ++ @Test ++ public void testRFC6265SidLangExampleLenient() ++ { ++ String rawCookie = "SID=31d4d96e407aad42;lang=en-US"; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(2)); + assertCookie("Cookies0", cookies0, "SID", "31d4d96e407aad42", 0, null); + assertCookie("Cookies1", cookies1, "lang", "en-US", 0, null); + } +- ++ + /** + * Basic name=value, following RFC6265 rules + */ +@@ -193,13 +228,13 @@ public class CookieCutterTest + public void testKeyValue() + { + String rawCookie = "key=value"; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie); +- ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(1)); + assertCookie("Cookies0", cookies0, "key", "value", 0, null); + } +- ++ + /** + * Basic name=value, following RFC6265 rules + */ +@@ -207,9 +242,116 @@ public class CookieCutterTest + public void testDollarName() + { + String rawCookie = "$key=value"; +- +- Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie); +- ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ + assertThat("Cookies.length", cookies.length, is(0)); + } ++ ++ @Test ++ public void testMultipleCookies() ++ { ++ String rawCookie = "testcookie; server.id=abcd; server.detail=cfg"; ++ ++ // The first cookie "testcookie" should be ignored, per RFC6265, as it's missing the "=" sign. ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ ++ assertThat("Cookies.length", cookies.length, is(2)); ++ assertCookie("Cookies0", cookies0, "server.id", "abcd", 0, null); ++ assertCookie("Cookies1", cookies1, "server.detail", "cfg", 0, null); ++ } ++ ++ @Test ++ public void testExcessiveSemicolons() ++ { ++ char excessive = new char65535; ++ Arrays.fill(excessive, ';'); ++ String rawCookie = "foo=bar; " + new String(excessive) + "; xyz=pdq"; ++ ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie); ++ ++ assertThat("Cookies.length", cookies.length, is(2)); ++ assertCookie("Cookies0", cookies0, "foo", "bar", 0, null); ++ assertCookie("Cookies1", cookies1, "xyz", "pdq", 0, null); ++ } ++ ++ @ParameterizedTest ++ @MethodSource("rfc6265Cookies") ++ public void testRFC6265CookieParsing(Param param) ++ { ++ Cookie cookies = parseCookieHeaders(CookieCompliance.RFC6265, param.input); ++ ++ assertThat("Cookies.length (" + dump(cookies) + ")", cookies.length, is(param.expected.size())); ++ for (int i = 0; i < cookies.length; i++) ++ { ++ Cookie cookie = cookiesi; ++ assertThat("Cookies" + i + " (" + dump(cookies) + ")", cookie.getName() + "=" + cookie.getValue(), is(param.expected.get(i))); ++ } ++ } ++ ++ public static List<Param> rfc6265Cookies() ++ { ++ return Arrays.asList( ++ new Param("A=1; B=2; C=3", "A=1", "B=2", "C=3"), ++ new Param("A=\"1\"; B=2; C=3", "A=1", "B=2", "C=3"), ++ new Param("A=\"1\"; B=\"2\"; C=\"3\"", "A=1", "B=2", "C=3"), ++ new Param("A=1; B=2; C=\"3", "A=1", "B=2"), ++ new Param("A=1 ; B=2; C=3", "A=1", "B=2", "C=3"), ++ new Param("A= 1; B=2; C=3", "A=1", "B=2", "C=3"), ++ new Param("A=\"1; B=2\"; C=3", "C=3"), ++ new Param("A=\"1; B=2; C=3"), ++ new Param("A=\"1 B=2\"; C=3", "A=1 B=2", "C=3"), ++ new Param("A=\"\"1; B=2; C=3", "B=2", "C=3"), ++ new Param("A=\"\" ; B=2; C=3", "A=", "B=2", "C=3"), ++ new Param("A=\"\"; B=2; C=3", "A=", "B=2", "C=3"), ++ new Param("A=1\"\"; B=2; C=3", "B=2", "C=3"), ++ new Param("A=1\"; B=2; C=3", "B=2", "C=3"), ++ new Param("A=1\"1; B=2; C=3", "B=2", "C=3"), ++ new Param("A=\" 1\"; B=2; C=3", "A= 1", "B=2", "C=3"), ++ new Param("A=\"1 \"; B=2; C=3", "A=1 ", "B=2", "C=3"), ++ new Param("A=\" 1 \"; B=2; C=3", "A= 1 ", "B=2", "C=3"), ++ new Param("A=\" 1 1 \"; B=2; C=3", "A= 1 1 ", "B=2", "C=3"), ++ new Param("A=1,; B=2; C=3", "B=2", "C=3"), ++ new Param("A=\"1,\"; B=2; C=3", "B=2", "C=3"), ++ new Param("A=\\1; B=2; C=3", "B=2", "C=3"), ++ new Param("A=\"\\1\"; B=2; C=3", "B=2", "C=3"), ++ new Param("A=1\u0007; B=2; C=3", "B=2", "C=3"), ++ new Param("A=\"1\u0007\"; B=2; C=3", "B=2", "C=3"), ++ new Param("€"), ++ new Param("@={}"), ++ new Param("$X=Y; N=V", "N=V"), ++ new Param("N=V; $X=Y", "N=V") ++ ); ++ } ++ ++ private static String dump(Cookie cookies) ++ { ++ StringBuilder sb = new StringBuilder(); ++ for (Cookie cookie : cookies) ++ { ++ sb.append("<").append(cookie.getName()).append(">=<").append(cookie.getValue()).append("> | "); ++ } ++ if (sb.length() > 0) ++ sb.delete(sb.length() - 2, sb.length() - 1); ++ return sb.toString(); ++ } ++ ++ private static class Param ++ { ++ private final String input; ++ private final List<String> expected; ++ ++ public Param(String input, String... expected) ++ { ++ this.input = input; ++ this.expected = Arrays.asList(expected); ++ } ++ ++ @Override ++ public String toString() ++ { ++ return input + " -> " + expected.toString(); ++ } ++ } + } +diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +index 425a9ae..f119864 100644 +--- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java ++++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +@@ -61,6 +61,7 @@ import javax.servlet.http.HttpServletResponse; + import javax.servlet.http.Part; + + import org.eclipse.jetty.http.BadMessageException; ++import org.eclipse.jetty.http.CookieCompliance; + import org.eclipse.jetty.http.HttpCompliance; + import org.eclipse.jetty.http.HttpTester; + import org.eclipse.jetty.http.MimeTypes; +@@ -97,6 +98,7 @@ public class RequestTest + http.getHttpConfiguration().setRequestHeaderSize(512); + http.getHttpConfiguration().setResponseHeaderSize(512); + http.getHttpConfiguration().setOutputBufferSize(2048); ++ http.getHttpConfiguration().setRequestCookieCompliance(CookieCompliance.RFC6265_LEGACY); + http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer()); + _connector = new LocalConnector(_server,http); + _server.addConnector(_connector);
View file
_service:tar_scm:CVE-2023-36479.patch
Added
@@ -0,0 +1,50 @@ +From: Markus Koschany <apo@debian.org> +Date: Wed, 27 Sep 2023 14:25:09 +0200 +Subject: CVE-2023-36479 + +The org.eclipse.jetty.servlets.CGI Servlet should not be used anymore. +Upstream recommends to use Fast CGI instead. + +Origin: https://github.com/eclipse/jetty.project/pull/9888 +--- + .../src/main/java/org/eclipse/jetty/servlets/CGI.java | 3 +++ + .../test-jetty-webapp/src/main/webapp/WEB-INF/web.xml | 11 ----------- + 2 files changed, 3 insertions(+), 11 deletions(-) + +diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java +index 6322290..55d8f9a 100644 +--- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java ++++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CGI.java +@@ -67,7 +67,10 @@ import org.eclipse.jetty.util.log.Logger; + * <dt>ignoreExitState</dt> + * <dd>If true then do not act on a non-zero exec exit status")</dd> + * </dl> ++ * ++ * @deprecated do not use, no replacement, will be removed in a future release. + */ ++@Deprecated + public class CGI extends HttpServlet + { + private static final long serialVersionUID = -6182088932884791074L; +diff --git a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml +index 507771f..978595f 100644 +--- a/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml ++++ b/tests/test-webapps/test-jetty-webapp/src/main/webapp/WEB-INF/web.xml +@@ -121,17 +121,6 @@ + <url-pattern>/dispatch/*</url-pattern> + </servlet-mapping> + +- <servlet> +- <servlet-name>CGI</servlet-name> +- <servlet-class>org.eclipse.jetty.servlets.CGI</servlet-class> +- <load-on-startup>1</load-on-startup> +- </servlet> +- +- <servlet-mapping> +- <servlet-name>CGI</servlet-name> +- <url-pattern>/cgi-bin/*</url-pattern> +- </servlet-mapping> +- + <servlet> + <servlet-name>Chat</servlet-name> + <servlet-class>com.acme.ChatServlet</servlet-class>
View file
_service:tar_scm:CVE-2023-40167.patch
Added
@@ -0,0 +1,256 @@ +From: Markus Koschany <apo@debian.org> +Date: Tue, 26 Sep 2023 21:06:42 +0200 +Subject: CVE-2023-40167 + +Origin: https://github.com/eclipse/jetty.project/commit/e4d596eafc887bcd813ae6e28295b5ce327def47 +--- + .../java/org/eclipse/jetty/http/HttpParser.java | 47 +++++++------- + .../org/eclipse/jetty/http/HttpParserTest.java | 71 +++++----------------- + 2 files changed, 38 insertions(+), 80 deletions(-) + +diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +index 2abc4b6..c045498 100644 +--- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java ++++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java +@@ -501,7 +501,7 @@ public class HttpParser + /* Quick lookahead for the start state looking for a request method or a HTTP version, + * otherwise skip white space until something else to parse. + */ +- private boolean quickStart(ByteBuffer buffer) ++ private void quickStart(ByteBuffer buffer) + { + if (_requestHandler!=null) + { +@@ -512,7 +512,7 @@ public class HttpParser + buffer.position(buffer.position()+_methodString.length()+1); + + setState(State.SPACE1); +- return false; ++ return; + } + } + else if (_responseHandler!=null) +@@ -522,7 +522,7 @@ public class HttpParser + { + buffer.position(buffer.position()+_version.asString().length()+1); + setState(State.SPACE1); +- return false; ++ return; + } + } + +@@ -543,7 +543,7 @@ public class HttpParser + _string.setLength(0); + _string.append(t.getChar()); + setState(_requestHandler!=null?State.METHOD:State.RESPONSE_VERSION); +- return false; ++ return; + } + case OTEXT: + case SPACE: +@@ -561,7 +561,6 @@ public class HttpParser + throw new BadMessageException(HttpStatus.BAD_REQUEST_400); + } + } +- return false; + } + + /* ------------------------------------------------------------------------------- */ +@@ -979,12 +978,13 @@ public class HttpParser + switch (_header) + { + case CONTENT_LENGTH: ++ long contentLength = convertContentLength(_valueString); + if (_hasContentLength) + { + if(complianceViolation(MULTIPLE_CONTENT_LENGTHS)) + throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.description); +- if (convertContentLength(_valueString)!=_contentLength) +- throw new BadMessageException(HttpStatus.BAD_REQUEST_400,MULTIPLE_CONTENT_LENGTHS.description); ++ if (contentLength != _contentLength) ++ throw new BadMessageException(HttpStatus.BAD_REQUEST_400, MULTIPLE_CONTENT_LENGTHS.getDescription()); + } + _hasContentLength = true; + +@@ -993,11 +993,8 @@ public class HttpParser + + if (_endOfContent != EndOfContent.CHUNKED_CONTENT) + { +- _contentLength=convertContentLength(_valueString); +- if (_contentLength <= 0) +- _endOfContent=EndOfContent.NO_CONTENT; +- else +- _endOfContent=EndOfContent.CONTENT_LENGTH; ++ _contentLength = contentLength; ++ _endOfContent = EndOfContent.CONTENT_LENGTH; + } + break; + +@@ -1085,15 +1082,21 @@ public class HttpParser + + private long convertContentLength(String valueString) + { +- try +- { +- return Long.parseLong(valueString); +- } +- catch(NumberFormatException e) ++ if (valueString == null || valueString.length() == 0) ++ throw new BadMessageException("Invalid Content-Length Value", new NumberFormatException()); ++ ++ long value = 0; ++ int length = valueString.length(); ++ ++ for (int i = 0; i < length; i++) + { +- LOG.ignore(e); +- throw new BadMessageException(HttpStatus.BAD_REQUEST_400,"Invalid Content-Length Value",e); ++ char c = valueString.charAt(i); ++ if (c < '0' || c > '9') ++ throw new BadMessageException("Invalid Content-Length Value", new NumberFormatException()); ++ ++ value = Math.addExact(Math.multiplyExact(value, 10L), c - '0'); + } ++ return value; + } + + /* ------------------------------------------------------------------------------- */ +@@ -1485,12 +1488,11 @@ public class HttpParser + _methodString=null; + _endOfContent=EndOfContent.UNKNOWN_CONTENT; + _header=null; +- if (quickStart(buffer)) +- return true; ++ quickStart(buffer); + } + + // Request/response line +- if (_state.ordinal()>= State.START.ordinal() && _state.ordinal()<State.HEADER.ordinal()) ++ if (_state.ordinal() < State.HEADER.ordinal()) + { + if (parseLine(buffer)) + return true; +@@ -1994,7 +1996,6 @@ public class HttpParser + } + + /* ------------------------------------------------------------------------------- */ +- @SuppressWarnings("serial") + private static class IllegalCharacterException extends BadMessageException + { + private IllegalCharacterException(State state,HttpTokens.Token token,ByteBuffer buffer) +diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +index dc340c1..c1d59cd 100644 +--- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java ++++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpParserTest.java +@@ -23,6 +23,7 @@ import static org.hamcrest.MatcherAssert.assertThat; + import static org.hamcrest.Matchers.contains; + import static org.hamcrest.Matchers.containsString; + import static org.hamcrest.Matchers.is; ++import static org.hamcrest.Matchers.notNullValue; + import static org.hamcrest.Matchers.nullValue; + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertFalse; +@@ -1653,7 +1654,7 @@ public class HttpParserTest + } + + @Test +- public void testUnknownReponseVersion() throws Exception ++ public void testUnknownResponseVersion() + { + ByteBuffer buffer = BufferUtil.toBuffer( + "HPPT/7.7 200 OK\r\n" +@@ -1797,65 +1798,21 @@ public class HttpParserTest + assertEquals(HttpParser.State.CLOSED, parser.getState()); + } + +- @Test +- public void testBadContentLength0() throws Exception +- { +- ByteBuffer buffer = BufferUtil.toBuffer( +- "GET / HTTP/1.0\r\n" +- + "Content-Length: abc\r\n" +- + "Connection: close\r\n" +- + "\r\n"); +- +- HttpParser.RequestHandler handler = new Handler(); +- HttpParser parser = new HttpParser(handler); +- +- parser.parseNext(buffer); +- assertEquals("GET", _methodOrVersion); +- assertEquals("Invalid Content-Length Value", _bad); +- assertFalse(buffer.hasRemaining()); +- assertEquals(HttpParser.State.CLOSE, parser.getState()); +- parser.atEOF(); +- parser.parseNext(BufferUtil.EMPTY_BUFFER); +- assertEquals(HttpParser.State.CLOSED, parser.getState()); +- } +- +- @Test +- public void testBadContentLength1() throws Exception +- { +- ByteBuffer buffer = BufferUtil.toBuffer( +- "GET / HTTP/1.0\r\n" +- + "Content-Length: 9999999999999999999999999999999999999999999999\r\n" +- + "Connection: close\r\n" +- + "\r\n"); +- +- HttpParser.RequestHandler handler = new Handler(); +- HttpParser parser = new HttpParser(handler); +- +- parser.parseNext(buffer); +- assertEquals("GET", _methodOrVersion); +- assertEquals("Invalid Content-Length Value", _bad); +- assertFalse(buffer.hasRemaining()); +- assertEquals(HttpParser.State.CLOSE, parser.getState()); +- parser.atEOF(); +- parser.parseNext(BufferUtil.EMPTY_BUFFER); +- assertEquals(HttpParser.State.CLOSED, parser.getState()); +- } + +- @Test +- public void testBadContentLength2() throws Exception ++ public void testBadContentLengths(String contentLength) + { + ByteBuffer buffer = BufferUtil.toBuffer( +- "GET / HTTP/1.0\r\n" +- + "Content-Length: 1.5\r\n" +- + "Connection: close\r\n" +- + "\r\n"); ++ "GET /test HTTP/1.1\r\n" + ++ "Host: localhost\r\n" + ++ "Content-Length: " + contentLength + "\r\n" + ++ "\r\n" + ++ "1234567890\r\n"); + + HttpParser.RequestHandler handler = new Handler(); +- HttpParser parser = new HttpParser(handler); ++ HttpParser parser = new HttpParser(handler, HttpCompliance.RFC2616_LEGACY); ++ parseAll(parser, buffer); + +- parser.parseNext(buffer); +- assertEquals("GET", _methodOrVersion); +- assertEquals("Invalid Content-Length Value", _bad); ++ assertThat(_bad, notNullValue()); + assertFalse(buffer.hasRemaining()); + assertEquals(HttpParser.State.CLOSE, parser.getState()); + parser.atEOF(); +@@ -2066,7 +2023,7 @@ public class HttpParserTest + @Test + public void testBadIPv6Host() throws Exception + { +- try(StacklessLogging s = new StacklessLogging(HttpParser.class)) ++ try (StacklessLogging ignored = new StacklessLogging(HttpParser.class)) + { + ByteBuffer buffer = BufferUtil.toBuffer( + "GET / HTTP/1.1\r\n" +@@ -2254,8 +2211,8 @@ public class HttpParserTest + private String _methodOrVersion; + private String _uriOrStatus; + private String _versionOrReason; +- private List<HttpField> _fields = new ArrayList<>(); +- private List<HttpField> _trailers = new ArrayList<>(); ++ private final List<HttpField> _fields = new ArrayList<>(); ++ private final List<HttpField> _trailers = new ArrayList<>(); + private String _hdr; + private String _val; + private int _headers;
View file
_service
Changed
@@ -2,7 +2,7 @@ <service name="tar_scm"> <param name="scm">git</param> <param name="url">git@gitee.com:src-openeuler/jetty.git</param> - <param name="revision">openEuler-24.03-LTS-Next</param> + <param name="revision">openEuler-24.03-LTS-SP1</param> <param name="exclude">*</param> <param name="extract">*</param> </service>
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2