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 Cookie[cookies.size()]);
-        _lastCookies=_cookies;
+        _cookies = cookies.toArray(new Cookie[0]);
+        _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("Cookies[0]", cookies[0], "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("Cookies[0]", cookies[0], "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("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
         assertCookie("Cookies[1]", cookies[1], "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("Cookies[0]", cookies[0], "Customer", "WILE_E_COYOTE", 1, "/acme");
         assertCookie("Cookies[1]", cookies[1], "Part_Number", "Rocket_Launcher_0001", 1, "/acme");
         assertCookie("Cookies[2]", cookies[2], "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("Cookies[0]", cookies[0], "Part_Number", "Riding_Rocket_0023", 1, "/acme/ammo");
         assertCookie("Cookies[1]", cookies[1], "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("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
         assertCookie("Cookies[1]", cookies[1], "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("Cookies[0]", cookies[0], "session_id", "1234", 1, null);
         assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 1, null);
 
-        cookies = parseCookieHeaders(CookieCompliance.RFC6265,rawCookie);
-        assertThat("Cookies.length", cookies.length, is(2));
-        assertCookie("Cookies[0]", cookies[0], "session_id", "1234\", $Version=\"1", 0, null);
-        assertCookie("Cookies[1]", cookies[1], "session_id", "1111", 0, null);
+        cookies = parseCookieHeaders(CookieCompliance.RFC6265, rawCookie);
+        assertThat("Cookies.length", cookies.length, is(1));
+        assertCookie("Cookies[0]", cookies[0], "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("Cookies[0]", cookies[0], "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("Cookies[0]", cookies[0], "SID", "31d4d96e407aad42", 0, null);
+        assertCookie("Cookies[1]", cookies[1], "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("Cookies[0]", cookies[0], "SID", "31d4d96e407aad42", 0, null);
         assertCookie("Cookies[1]", cookies[1], "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("Cookies[0]", cookies[0], "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("Cookies[0]", cookies[0], "server.id", "abcd", 0, null);
+        assertCookie("Cookies[1]", cookies[1], "server.detail", "cfg", 0, null);
+    }
+
+    @Test
+    public void testExcessiveSemicolons()
+    {
+        char[] excessive = new char[65535];
+        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("Cookies[0]", cookies[0], "foo", "bar", 0, null);
+        assertCookie("Cookies[1]", cookies[1], "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 = cookies[i];
+            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);
