From 05072b34dce43064e87ad0d59065f14666c1f34e Mon Sep 17 00:00:00 2001
From: Greg Wilkins <gregw@webtide.com>
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 <gregw@webtide.com>
---
 .../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 @@
         <Set name="forwardedServerHeader"><Property name="jetty.httpConfig.forwardedServerHeader" default="X-Forwarded-Server"/></Set>
         <Set name="forwardedProtoHeader"><Property name="jetty.httpConfig.forwardedProtoHeader" default="X-Forwarded-Proto"/></Set>
         <Set name="forwardedForHeader"><Property name="jetty.httpConfig.forwardedForHeader" default="X-Forwarded-For"/></Set>
+        <Set name="forwardedPortHeader"><Property name="jetty.httpConfig.forwardedPortHeader" default="X-Forwarded-Port"/></Set>
         <Set name="forwardedHttpsHeader"><Property name="jetty.httpConfig.forwardedHttpsHeader" default="X-Proxied-Https"/></Set>
         <Set name="forwardedSslSessionIdHeader"><Property name="jetty.httpConfig.forwardedSslSessionIdHeader" default="Proxy-ssl-id" /></Set>
         <Set name="forwardedCipherSuiteHeader"><Property name="jetty.httpConfig.forwardedCipherSuiteHeader" default="Proxy-auth-cert"/></Set>
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 <MethodHandle> _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<Arguments> 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)
         );
     }
 
