From 4aef479d090ec16a254a02c7c274f8929af7df68 Mon Sep 17 00:00:00 2001
From: zhanghua1831
Date: Thu, 18 Mar 2021 16:09:03 +0800
Subject: [PATCH] fix CVE-2020-27223
(cherry picked from commit 0cb5b0df31e3ed5aac61addecc7ff2727ae3493e)
---
CVE-2020-27223-pre-1.patch | 545 +++++++++++++++++
CVE-2020-27223-pre-2.patch | 692 +++++++++++++++++++++
CVE-2020-27223-pre-3.patch | 518 ++++++++++++++++
CVE-2020-27223-pre-4.patch | 1167 ++++++++++++++++++++++++++++++++++++
CVE-2020-27223.patch | 228 +++++++
jetty.spec | 10 +-
6 files changed, 3159 insertions(+), 1 deletion(-)
create mode 100644 CVE-2020-27223-pre-1.patch
create mode 100644 CVE-2020-27223-pre-2.patch
create mode 100644 CVE-2020-27223-pre-3.patch
create mode 100644 CVE-2020-27223-pre-4.patch
create mode 100644 CVE-2020-27223.patch
diff --git a/CVE-2020-27223-pre-1.patch b/CVE-2020-27223-pre-1.patch
new file mode 100644
index 0000000..597b053
--- /dev/null
+++ b/CVE-2020-27223-pre-1.patch
@@ -0,0 +1,545 @@
+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
new file mode 100644
index 0000000..989fd75
--- /dev/null
+++ b/CVE-2020-27223-pre-2.patch
@@ -0,0 +1,692 @@
+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
new file mode 100644
index 0000000..356a049
--- /dev/null
+++ b/CVE-2020-27223-pre-3.patch
@@ -0,0 +1,518 @@
+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
new file mode 100644
index 0000000..b77d180
--- /dev/null
+++ b/CVE-2020-27223-pre-4.patch
@@ -0,0 +1,1167 @@
+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
new file mode 100644
index 0000000..ab24788
--- /dev/null
+++ b/CVE-2020-27223.patch
@@ -0,0 +1,228 @@
+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
+
+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(-)
+
+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
+--- 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;
+ import java.util.ArrayList;
+ import java.util.Iterator;
+ import java.util.List;
++import java.util.Objects;
+ import java.util.function.ToIntFunction;
++import java.util.stream.Collectors;
+
+ import org.eclipse.jetty.util.log.Log;
+
+-import static java.lang.Integer.MIN_VALUE;
+-
+ /* ------------------------------------------------------------ */
+
+ /**
+@@ -57,7 +57,8 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable
+ return 3;
+ };
+
+- private final List _quality = new ArrayList<>();
++ private final List _qualities = new ArrayList<>();
++ private QualityValue _lastQuality;
+ private boolean _sorted = false;
+ private final ToIntFunction _secondaryOrdering;
+
+@@ -68,7 +69,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable
+ */
+ public QuotedQualityCSV()
+ {
+- this((ToIntFunction)null);
++ this((ToIntFunction)null);
+ }
+
+ /* ------------------------------------------------------------ */
+@@ -89,7 +90,7 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable
+ if ("*".equals(s))
+ return preferredOrder.length;
+
+- return MIN_VALUE;
++ return 0;
+ });
+ }
+
+@@ -98,27 +99,43 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable
+ /**
+ * Orders values with equal quality with the given function.
+ *
+- * @param secondaryOrdering Function to apply an ordering other than specified by quality
++ * @param secondaryOrdering Function to apply an ordering other than specified by quality, highest values are sorted first.
+ */
+ public QuotedQualityCSV(ToIntFunction secondaryOrdering)
+ {
+ 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);
++ }
++
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void parsedValue(StringBuffer buffer)
+ {
+ super.parsedValue(buffer);
+
++ _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);
+ }
+
+ /* ------------------------------------------------------------ */
+ @Override
+ protected void parsedParam(StringBuffer buffer, int valueLength, int paramName, int paramValue)
+ {
++ _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) == '=')
+ {
+- Double q;
++ double q;
+ try
+ {
+ q = (_keepQuotes && buffer.charAt(paramValue) == '"')
+@@ -143,8 +160,10 @@ public class QuotedQualityCSV extends QuotedCSV implements Iterable
+ buffer.setLength(Math.max(0, paramName - 1));
+
+ if (q != 1.0D)
+- // replace assumed quality
+- _quality.set(_quality.size() - 1, q);
++ {
++ _lastQuality = new QualityValue(q, buffer.toString(), _lastQuality._index);
++ _qualities.set(_lastQuality._index, _lastQuality);
++ }
+ }
+ }
+
+@@ -166,38 +185,74 @@ 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));
+ _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;
+
+- for (int i = _values.size(); i-- > 0; )
++ private QualityValue(double quality, String value, int index)
+ {
+- 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;
++ }
+
+- 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)
+ {
+- _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;
+- 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);
+ }
+
+- last = q;
+- lastSecondaryOrder = _secondaryOrdering.applyAsInt(v);
++ return compare;
+ }
+
+- int last_element = _quality.size();
+- while (last_element > 0 && _quality.get(--last_element).equals(0.0D))
++ @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);
+ }
+ }
+ }
+--
+2.23.0
+
diff --git a/jetty.spec b/jetty.spec
index fdb4982..e4cba33 100644
--- a/jetty.spec
+++ b/jetty.spec
@@ -12,7 +12,7 @@
%bcond_with jp_minimal
Name: jetty
Version: 9.4.15
-Release: 5
+Release: 6
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,6 +22,11 @@ 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
BuildRequires: maven-local mvn(javax.servlet:javax.servlet-api)
BuildRequires: mvn(org.apache.felix:maven-bundle-plugin)
BuildRequires: mvn(org.apache.maven.plugins:maven-shade-plugin)
@@ -780,6 +785,9 @@ exit 0
%license LICENSE NOTICE.txt LICENSE-MIT
%changelog
+* Wed Mar 17 2021 zhanghua - 9.4.15-6
+- fix CVE-2020-27223
+
* Mon Feb 8 2021 zhanghua - 9.4.15-5
- fix CVE-2020-27216
--
Gitee