From cec50b3d2cea7bdf6b2f00d45c8554139aebbf87 Mon Sep 17 00:00:00 2001
From: Greg Wilkins <gregw@webtide.com>
Date: Wed, 8 May 2019 21:07:22 +0200
Subject: [PATCH] Issue #3630 Optimized ForwardedRequestCustomizer

Signed-off-by: Greg Wilkins <gregw@webtide.com>
---
 .../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<String>
-{    
-    private enum State { VALUE, PARAM_NAME, PARAM_VALUE};
-    
+public class QuotedCSV extends QuotedCSVParser implements Iterable<String>
+{
     protected final List<String> _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 (;i<l;i++)
-        {
-            char c=s.charAt(i);
-            if (c=='"')
-                break;
-        }
-        if (i==l)
-            return s;
 
-        boolean quoted=true;
-        boolean sloshed=false;
-        StringBuffer buffer = new StringBuffer();
-        buffer.append(s,0,i);
-        i++;
-        for (;i<l;i++)
-        {
-            char c=s.charAt(i);
-            if (quoted)
-            {
-                if (sloshed)
-                {
-                    buffer.append(c);
-                    sloshed=false;
-                }
-                else if (c=='"')
-                    quoted=false;
-                else if (c=='\\')
-                    sloshed=true;
-                else
-                    buffer.append(c);
-            }
-            else if (c=='"')
-                quoted=true;
-            else
-                buffer.append(c);
-        }
-        return buffer.toString();
-    }
-    
     @Override
     public String toString()
     {
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java
new file mode 100644
index 00000000000..8f521db7ffb
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/QuotedCSVParser.java
@@ -0,0 +1,288 @@
+//
+//  ========================================================================
+//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
+//  ------------------------------------------------------------------------
+//  All rights reserved. This program and the accompanying materials
+//  are made available under the terms of the Eclipse Public License v1.0
+//  and Apache License v2.0 which accompanies this distribution.
+//
+//      The Eclipse Public License is available at
+//      http://www.eclipse.org/legal/epl-v10.html
+//
+//      The Apache License v2.0 is available at
+//      http://www.opensource.org/licenses/apache2.0.php
+//
+//  You may elect to redistribute this code under either of these licenses.
+//  ========================================================================
+//
+
+package org.eclipse.jetty.http;
+
+public abstract class QuotedCSVParser
+{
+    private enum State { VALUE, PARAM_NAME, PARAM_VALUE}
+
+    protected final boolean _keepQuotes;
+
+    public QuotedCSVParser(boolean keepQuotes)
+    {
+        _keepQuotes=keepQuotes;
+    }
+
+    public static String unquote(String s)
+    {
+        // handle trivial cases
+        int l=s.length();
+        if (s==null || l==0)
+            return s;
+
+        // Look for any quotes
+        int i=0;
+        for (;i<l;i++)
+        {
+            char c=s.charAt(i);
+            if (c=='"')
+                break;
+        }
+        if (i==l)
+            return s;
+
+        boolean quoted=true;
+        boolean sloshed=false;
+        StringBuffer buffer = new StringBuffer();
+        buffer.append(s,0,i);
+        i++;
+        for (;i<l;i++)
+        {
+            char c=s.charAt(i);
+            if (quoted)
+            {
+                if (sloshed)
+                {
+                    buffer.append(c);
+                    sloshed=false;
+                }
+                else if (c=='"')
+                    quoted=false;
+                else if (c=='\\')
+                    sloshed=true;
+                else
+                    buffer.append(c);
+            }
+            else if (c=='"')
+                quoted=true;
+            else
+                buffer.append(c);
+        }
+        return buffer.toString();
+    }
+
+    /** Add and parse a value string(s)
+     * @param value A value that may contain one or more Quoted CSV items.
+     */
+    public void addValue(String value)
+    {
+        if (value == null)
+            return;
+
+        StringBuffer buffer = new StringBuffer();
+
+        int l=value.length();
+        State state=State.VALUE;
+        boolean quoted=false;
+        boolean sloshed=false;
+        int 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;
+                        }
+                        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.
  * <p>
@@ -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
