Index: include/ap_mmn.h
===================================================================
--- include/ap_mmn.h	(revision 1828879)
+++ include/ap_mmn.h	(working copy)
@@ -570,6 +570,7 @@
  *                         ap_regcomp_default_cflag_by_name
  * 20171014.7 (2.5.1-dev)  Add AP_CORE_DEFAULT macro
  * 20171014.8 (2.5.1-dev)  Add CONN_STATE_NUM to enum conn_state_e
+ * 20171014.9 (2.5.1-dev)  Add ap_normalize_hostname() to http_vhost.h
  */
 
 #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */
Index: include/http_vhost.h
===================================================================
--- include/http_vhost.h	(revision 1828879)
+++ include/http_vhost.h	(working copy)
@@ -100,6 +100,14 @@
 AP_DECLARE(void) ap_update_vhost_from_headers(request_rec *r);
 
 /**
+ * ap_normalize_hostname checks the given hostname and, if successful, replaces
+ * it with a normalized version without port number.
+ * @param c The connection to work with and allocate from
+ * @param phostname the hostname to normalize, on success always a copy in the connection pool 
+ */
+AP_DECLARE(apr_status_t) ap_normalize_hostname(conn_rec *c, const char **phostname);
+
+/**
  * Match the host in the header with the hostname of the server for this
  * request.
  * @param r The current request
Index: server/vhost.c
===================================================================
--- server/vhost.c	(revision 1828879)
+++ server/vhost.c	(working copy)
@@ -704,7 +704,7 @@
  * run-time vhost matching functions
  */
 
-static apr_status_t fix_hostname_v6_literal(request_rec *r, char *host)
+static apr_status_t fix_hostname_v6_literal(char *host)
 {
     char *dst;
     int double_colon = 0;
@@ -737,7 +737,7 @@
     return APR_SUCCESS;
 }
 
-static apr_status_t fix_hostname_non_v6(request_rec *r, char *host)
+static apr_status_t fix_hostname_non_v6(char *host)
 {
     char *dst;
 
@@ -768,7 +768,7 @@
  * If strict mode ever becomes the default, this should be folded into
  * fix_hostname_non_v6()
  */
-static apr_status_t strict_hostname_check(request_rec *r, char *host)
+static apr_status_t strict_hostname_check(conn_rec *c, char *host)
 {
     char *ch;
     int is_dotted_decimal = 1, leading_zeroes = 0, dots = 0;
@@ -805,7 +805,7 @@
     return APR_SUCCESS;
 
 bad:
-    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02415)
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(02415)
                   "[strict] Invalid host name '%s'%s%.6s",
                   host, *ch ? ", problem near: " : "", ch);
     return APR_EINVAL;
@@ -824,56 +824,65 @@
  * Instead we just check for filesystem metacharacters: directory
  * separators / and \ and sequences of more than one dot.
  */
-static int fix_hostname(request_rec *r, const char *host_header,
-                        unsigned http_conformance)
+ 
+typedef struct {
+    const char *authority;
+    unsigned http_conformance;
+    int is_parsed;
+    apr_pool_t *pool;
+    char *hostname;
+    apr_port_t port;
+    int is_v6literal;
+    const char *err;
+} hostname_rec;
+
+/* Given input in h->authority, h->http_conformance, h->is_parsed
+ * and h->pool, check the authority for a valid, normalized hostname
+ * without port (can be an ip4/6 address). 
+ * The other hostname_rec fields are always set on return. On APR_SUCCESS,
+ * h->hostname will hold a normalized copy allocated in h->pool.
+ */
+static apr_status_t fix_hostname(hostname_rec *h, conn_rec *c)
 {
     const char *src;
-    char *host, *scope_id;
-    apr_port_t port;
+    char *scope_id;
     apr_status_t rv;
-    const char *c;
-    int is_v6literal = 0;
-    int strict = (http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
+    const char *ch;
+    int strict = (h->http_conformance != AP_HTTP_CONFORMANCE_UNSAFE);
 
-    src = host_header ? host_header : r->hostname;
-
+    src = h->authority;
+    h->hostname = "";
+    h->port = 0;
+    h->is_v6literal = 0;
+    h->err = NULL;
+    
     /* According to RFC 2616, Host header field CAN be blank */
     if (!*src) {
-        return is_v6literal;
+        return APR_SUCCESS;
     }
 
     /* apr_parse_addr_port will interpret a bare integer as a port
      * which is incorrect in this context.  So treat it separately.
      */
-    for (c = src; apr_isdigit(*c); ++c);
-    if (!*c) {
+    for (ch = src; apr_isdigit(*ch); ++ch);
+    if (!*ch) {
         /* pure integer */
         if (strict) {
             /* RFC 3986 7.4 */
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02416)
-                         "[strict] purely numeric host names not allowed: %s",
-                         src);
-            goto bad_nolog;
+            h->err = "[strict] purely numeric host names not allowed";
+            return APR_EINVAL;
         }
-        r->hostname = src;
-        return is_v6literal;
+        return APR_SUCCESS;
     }
 
-    if (host_header) {
-        rv = apr_parse_addr_port(&host, &scope_id, &port, src, r->pool);
-        if (rv != APR_SUCCESS || scope_id)
-            goto bad;
-        if (port) {
-            /* Don't throw the Host: header's port number away:
-               save it in parsed_uri -- ap_get_server_port() needs it! */
-            /* @@@ XXX there should be a better way to pass the port.
-             *         Like r->hostname, there should be a r->portno
-             */
-            r->parsed_uri.port = port;
-            r->parsed_uri.port_str = apr_itoa(r->pool, (int)port);
-        }
-        if (host_header[0] == '[')
-            is_v6literal = 1;
+    if (!h->is_parsed) {
+        rv = apr_parse_addr_port(&h->hostname, &scope_id, &h->port, src, h->pool);
+        if (rv != APR_SUCCESS)
+            return rv;
+        else if (scope_id)
+            return APR_EINVAL;
+        if (src[0] == '[')
+            h->is_v6literal = 1;
     }
     else {
         /*
@@ -880,32 +889,76 @@
          * Already parsed, surrounding [ ] (if IPv6 literal) and :port have
          * already been removed.
          */
-        host = apr_pstrdup(r->pool, r->hostname);
-        if (ap_strchr(host, ':') != NULL)
-            is_v6literal = 1;
+        h->hostname = apr_pstrdup(h->pool, h->authority);
+        if (ap_strchr(h->hostname, ':') != NULL)
+            h->is_v6literal = 1;
     }
 
-    if (is_v6literal) {
-        rv = fix_hostname_v6_literal(r, host);
+    if (h->is_v6literal) {
+        rv = fix_hostname_v6_literal(h->hostname);
     }
     else {
-        rv = fix_hostname_non_v6(r, host);
+        rv = fix_hostname_non_v6(h->hostname);
         if (strict && rv == APR_SUCCESS)
-            rv = strict_hostname_check(r, host);
+            rv = strict_hostname_check(c, h->hostname);
     }
-    if (rv != APR_SUCCESS)
-        goto bad;
+    return rv;
+}
 
-    r->hostname = host;
-    return is_v6literal;
+static int fix_rhostname(request_rec *r, const char *host_header,
+                         unsigned http_conformance)
+{
+    apr_status_t rv;
+    hostname_rec h;
+    
+    h.authority = host_header? host_header : r->hostname;
+    h.http_conformance = http_conformance;
+    h.is_parsed = (host_header == NULL);
+    h.pool = r->pool;
+    
+    rv = fix_hostname(&h, r->connection);
+    if (APR_SUCCESS != rv) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550)
+                      "%s: %s", h.err? h.err : "Client sent malformed Host header", 
+                      h.authority);
+        r->status = HTTP_BAD_REQUEST;
+    }
+    else {
+        r->hostname = h.hostname;
+        if (h.port > 0) {
+            /* Don't throw the Host: header's port number away:
+             save it in parsed_uri -- ap_get_server_port() needs it! */
+            /* @@@ XXX there should be a better way to pass the port.
+             *         Like r->hostname, there should be a r->portno
+             */
+            r->parsed_uri.port = h.port;
+            r->parsed_uri.port_str = apr_itoa(r->pool, (int)h.port);
+        }
+    }
+    return h.is_v6literal;
+}
 
-bad:
-    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00550)
-                  "Client sent malformed Host header: %s",
-                  src);
-bad_nolog:
-    r->status = HTTP_BAD_REQUEST;
-    return is_v6literal;
+apr_status_t ap_normalize_hostname(conn_rec *c, const char **phostname)
+{
+    core_server_config *conf = ap_get_core_module_config(c->base_server->module_config);
+    apr_status_t rv;
+    hostname_rec h;
+    
+    h.authority = *phostname;
+    h.http_conformance = conf->http_conformance;
+    h.is_parsed = 0;
+    h.pool = c->pool;
+    
+    rv = fix_hostname(&h, c);
+    if (APR_SUCCESS != rv) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO()
+                      "%s: %s", h.err? h.err : "Client sent malformed Host header", 
+                      h.authority);
+    }
+    else {
+        *phostname = h.hostname;
+    }
+    return rv;
 }
 
 /* return 1 if host matches ServerName or ServerAliases */
@@ -1163,10 +1216,10 @@
          * header.
          */
         have_hostname_from_url = 1;
-        is_v6literal = fix_hostname(r, NULL, conf->http_conformance);
+        is_v6literal = fix_rhostname(r, NULL, conf->http_conformance);
     }
     else if (host_header != NULL) {
-        is_v6literal = fix_hostname(r, host_header, conf->http_conformance);
+        is_v6literal = fix_rhostname(r, host_header, conf->http_conformance);
     }
     if (r->status != HTTP_OK)
         return;
