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