Index: httpd-2.4.23/modules/proxy/mod_proxy.c
===================================================================
--- httpd-2.4.23.orig/modules/proxy/mod_proxy.c	2022-01-05 11:12:39.653129490 +0100
+++ httpd-2.4.23/modules/proxy/mod_proxy.c	2022-01-05 11:20:19.967732087 +0100
@@ -1577,7 +1577,8 @@ static const char *
     const apr_array_header_t *arr;
     const apr_table_entry_t *elts;
     int i;
-    int use_regex = is_regex;
+    unsigned int worker_type = (is_regex) ? AP_PROXY_WORKER_IS_MATCH
+                                          : AP_PROXY_WORKER_IS_PREFIX;
     unsigned int flags = 0;
     const char *err;
 
@@ -1593,7 +1594,7 @@ static const char *
                 if (is_regex) {
                     return "ProxyPassMatch invalid syntax ('~' usage).";
                 }
-                use_regex = 1;
+                worker_type = AP_PROXY_WORKER_IS_MATCH;
                 continue;
             }
             f = word;
@@ -1644,7 +1645,7 @@ static const char *
         dconf->alias_set = 1;
         new = dconf->alias;
         if (apr_fnmatch_test(f)) {
-            use_regex = 1;
+            worker_type = AP_PROXY_WORKER_IS_MATCH;
         }
     }
     /* if per server, add to the alias array */
@@ -1655,7 +1656,7 @@ static const char *
     new->fake = apr_pstrdup(cmd->pool, f);
     new->real = apr_pstrdup(cmd->pool, ap_proxy_de_socketfy(cmd->pool, r));
     new->flags = flags;
-    if (use_regex) {
+    if (worker_type & AP_PROXY_WORKER_IS_MATCH) {
         new->regex = ap_pregcomp(cmd->pool, f, AP_REG_EXTENDED);
         if (new->regex == NULL)
             return "Regular expression could not be compiled.";
@@ -1679,7 +1680,7 @@ static const char *
          * cannot be parsed anyway with apr_uri_parse later on in
          * ap_proxy_define_balancer / ap_proxy_update_balancer
          */
-        if (use_regex) {
+        if (worker_type & AP_PROXY_WORKER_IS_MATCH) {
             fake_copy = NULL;
         }
         else {
@@ -1702,10 +1703,13 @@ static const char *
         new->balancer = balancer;
     }
     else {
-        proxy_worker *worker = ap_proxy_get_worker(cmd->temp_pool, NULL, conf, ap_proxy_de_socketfy(cmd->pool, r));
         int reuse = 0;
+        proxy_worker *worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL,
+                                                      conf, new->real,
+                                                      worker_type);
         if (!worker) {
-            const char *err = ap_proxy_define_worker(cmd->pool, &worker, NULL, conf, r, 0);
+            const char *err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL,
+                                                        conf, r, worker_type);
             if (err)
                 return apr_pstrcat(cmd->temp_pool, "ProxyPass ", err, NULL);
 
@@ -2181,7 +2185,8 @@ static const char *add_member(cmd_parms
     }
 
     /* Try to find existing worker */
-    worker = ap_proxy_get_worker(cmd->temp_pool, balancer, conf, ap_proxy_de_socketfy(cmd->temp_pool, name));
+    worker = ap_proxy_get_worker(cmd->temp_pool, balancer, conf,
+                                 ap_proxy_de_socketfy(cmd->temp_pool, name));
     if (!worker) {
         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, cmd->server, APLOGNO(01147)
                      "Defining worker '%s' for balancer '%s'",
@@ -2227,6 +2232,7 @@ static const char *
     char *word, *val;
     proxy_balancer *balancer = NULL;
     proxy_worker *worker = NULL;
+    unsigned int worker_type = 0;
     int in_proxy_section = 0;
     /* XXX: Should this be NOT_IN_DIRECTORY|NOT_IN_FILES? */
     const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
@@ -2243,6 +2249,13 @@ static const char *
         name = ap_getword_conf(cmd->temp_pool, &pargs);
         if ((word = ap_strchr(name, '>')))
             *word = '\0';
+        if (strncasecmp(cmd->directive->parent->directive + 6,
+                        "Match", 5) == 0) {
+            worker_type = AP_PROXY_WORKER_IS_MATCH;
+        }
+        else {
+            worker_type = AP_PROXY_WORKER_IS_PREFIX;
+        }
         in_proxy_section = 1;
     }
     else {
@@ -2267,11 +2280,13 @@ static const char *
         }
     }
     else {
-        worker = ap_proxy_get_worker(cmd->temp_pool, NULL, conf, ap_proxy_de_socketfy(cmd->temp_pool, name));
+        worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL, conf,
+                                        ap_proxy_de_socketfy(cmd->temp_pool, name),
+                                        worker_type);
         if (!worker) {
             if (in_proxy_section) {
-                err = ap_proxy_define_worker(cmd->pool, &worker, NULL,
-                                             conf, name, 0);
+                err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL,
+                                                conf, name, worker_type);
                 if (err)
                     return apr_pstrcat(cmd->temp_pool, "ProxySet ",
                                        err, NULL);
@@ -2326,6 +2341,7 @@ static const char *proxysection(cmd_parm
     proxy_balancer *balancer = NULL;
     proxy_worker *worker = NULL;
 
+    unsigned int worker_type = AP_PROXY_WORKER_IS_PREFIX;
     const char *err = ap_check_cmd_context(cmd, NOT_IN_DIR_LOC_FILE);
     proxy_server_conf *sconf =
     (proxy_server_conf *) ap_get_module_config(cmd->server->module_config, &proxy_module);
@@ -2363,6 +2379,8 @@ static const char *proxysection(cmd_parm
         if (!r) {
             return "Regex could not be compiled";
         }
+
+        worker_type = AP_PROXY_WORKER_IS_MATCH;
     }
 
     /* initialize our config and fetch it */
@@ -2406,11 +2424,12 @@ static const char *proxysection(cmd_parm
             }
         }
         else {
-            worker = ap_proxy_get_worker(cmd->temp_pool, NULL, sconf,
-                                         ap_proxy_de_socketfy(cmd->temp_pool, (char*)conf->p));
+            worker = ap_proxy_get_worker_ex(cmd->temp_pool, NULL, sconf,
+                                            ap_proxy_de_socketfy(cmd->temp_pool, conf->p),
+                                            worker_type);
             if (!worker) {
-                err = ap_proxy_define_worker(cmd->pool, &worker, NULL,
-                                          sconf, conf->p, 0);
+                err = ap_proxy_define_worker_ex(cmd->pool, &worker, NULL, sconf,
+                                                conf->p, worker_type);
                 if (err)
                     return apr_pstrcat(cmd->temp_pool, thiscmd->name,
                                        " ", err, NULL);
Index: httpd-2.4.23/modules/proxy/mod_proxy.h
===================================================================
--- httpd-2.4.23.orig/modules/proxy/mod_proxy.h	2022-01-05 11:12:39.549128902 +0100
+++ httpd-2.4.23/modules/proxy/mod_proxy.h	2022-01-05 11:12:39.669129580 +0100
@@ -673,8 +673,29 @@ typedef __declspec(dllimport) const char
 PROXY_DECLARE(char *) ap_proxy_worker_name(apr_pool_t *p,
                                            proxy_worker *worker);
 
+/* Bitmask for ap_proxy_{define,get}_worker_ex(). */
+#define AP_PROXY_WORKER_IS_PREFIX   (1u << 0)
+#define AP_PROXY_WORKER_IS_MATCH    (1u << 1)
+#define AP_PROXY_WORKER_IS_MALLOCED (1u << 2)
+
+/**
+ * Get the worker from proxy configuration, looking for either PREFIXED or
+ * MATCHED or both types of workers according to given mask
+ * @param p        memory pool used for finding worker
+ * @param balancer the balancer that the worker belongs to
+ * @param conf     current proxy server configuration
+ * @param url      url to find the worker from
+ * @param mask     bitmask of AP_PROXY_WORKER_IS_*
+ * @return         proxy_worker or NULL if not found
+ */
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker_ex(apr_pool_t *p,
+                                                     proxy_balancer *balancer,
+                                                     proxy_server_conf *conf,
+                                                     const char *url,
+                                                     unsigned int mask);
+
 /**
- * Get the worker from proxy configuration
+ * Get the worker from proxy configuration, both types
  * @param p        memory pool used for finding worker
  * @param balancer the balancer that the worker belongs to
  * @param conf     current proxy server configuration
@@ -685,7 +706,26 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
                                                   proxy_balancer *balancer,
                                                   proxy_server_conf *conf,
                                                   const char *url);
+
 /**
+ * Define and Allocate space for the worker to proxy configuration, of either
+ * PREFIXED or MATCHED type according to given mask
+ * @param p         memory pool to allocate worker from
+ * @param worker    the new worker
+ * @param balancer  the balancer that the worker belongs to
+ * @param conf      current proxy server configuration
+ * @param url       url containing worker name
+ * @param mask      bitmask of AP_PROXY_WORKER_IS_*
+ * @return          error message or NULL if successful (*worker is new worker)
+ */
+PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
+                                             proxy_worker **worker,
+                                             proxy_balancer *balancer,
+                                             proxy_server_conf *conf,
+                                             const char *url,
+                                             unsigned int mask);
+
+ /**
  * Define and Allocate space for the worker to proxy configuration
  * @param p         memory pool to allocate worker from
  * @param worker    the new worker
@@ -694,6 +734,7 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
  * @param url       url containing worker name
  * @param do_malloc true if shared struct should be malloced
  * @return          error message or NULL if successful (*worker is new worker)
+ * @deprecated Replaced by ap_proxy_define_worker_ex()
  */
 PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
                                              proxy_worker **worker,
Index: httpd-2.4.23/modules/proxy/proxy_util.c
===================================================================
--- httpd-2.4.23.orig/modules/proxy/proxy_util.c	2022-01-05 11:12:39.653129490 +0100
+++ httpd-2.4.23/modules/proxy/proxy_util.c	2022-01-05 11:12:39.669129580 +0100
@@ -19,6 +19,7 @@
 #include "ap_mpm.h"
 #include "scoreboard.h"
 #include "apr_version.h"
+#include "apr_strings.h"
 #include "apr_hash.h"
 #include "proxy_util.h"
 #include "ajp.h"
@@ -1503,10 +1504,11 @@ PROXY_DECLARE(char *) ap_proxy_worker_na
     return apr_pstrcat(p, "unix:", worker->s->uds_path, "|", worker->s->name, NULL);
 }
 
-PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
-                                                  proxy_balancer *balancer,
-                                                  proxy_server_conf *conf,
-                                                  const char *url)
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker_ex(apr_pool_t *p,
+                                                     proxy_balancer *balancer,
+                                                     proxy_server_conf *conf,
+                                                     const char *url,
+                                                     unsigned int mask)
 {
     proxy_worker *worker;
     proxy_worker *max_worker = NULL;
@@ -1532,6 +1534,11 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
     url_length = strlen(url);
     url_copy = apr_pstrmemdup(p, url, url_length);
 
+    /* Default to lookup for both _PREFIX and _MATCH workers */
+    if (!(mask & (AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH))) {
+        mask |= AP_PROXY_WORKER_IS_PREFIX | AP_PROXY_WORKER_IS_MATCH;
+    }
+
     /*
      * We need to find the start of the path and
      * therefore we know the length of the scheme://hostname/
@@ -1565,7 +1572,7 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
             if ( ((worker_name_length = strlen(worker->s->name)) <= url_length)
                 && (worker_name_length >= min_match)
                 && (worker_name_length > max_match)
-                && (strncmp(url_copy, worker->s->name, worker_name_length) == 0) ) {
+                && ((mask & AP_PROXY_WORKER_IS_PREFIX) && strncmp(url_copy, worker->s->name, worker_name_length) == 0) ) {
                 max_worker = worker;
                 max_match = worker_name_length;
             }
@@ -1577,7 +1584,7 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
             if ( ((worker_name_length = strlen(worker->s->name)) <= url_length)
                 && (worker_name_length >= min_match)
                 && (worker_name_length > max_match)
-                && (strncmp(url_copy, worker->s->name, worker_name_length) == 0) ) {
+                && ((mask & AP_PROXY_WORKER_IS_PREFIX) && strncmp(url_copy, worker->s->name, worker_name_length) == 0) ) {
                 max_worker = worker;
                 max_match = worker_name_length;
             }
@@ -1587,6 +1594,14 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
     return max_worker;
 }
 
+PROXY_DECLARE(proxy_worker *) ap_proxy_get_worker(apr_pool_t *p,
+                                                  proxy_balancer *balancer,
+                                                  proxy_server_conf *conf,
+                                                  const char *url)
+{
+    return ap_proxy_get_worker_ex(p, balancer, conf, url, 0);
+}
+
 /*
  * To create a worker from scratch first we define the
  * specifics of the worker; this is all local data.
@@ -1594,46 +1609,86 @@ PROXY_DECLARE(proxy_worker *) ap_proxy_g
  * shared. This allows for dynamic addition during
  * config and runtime.
  */
-PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
+PROXY_DECLARE(char *) ap_proxy_define_worker_ex(apr_pool_t *p,
                                              proxy_worker **worker,
                                              proxy_balancer *balancer,
                                              proxy_server_conf *conf,
                                              const char *url,
-                                             int do_malloc)
+                                             unsigned int mask)
 {
-    int rv;
-    apr_uri_t uri, urisock;
+    apr_status_t rv;
     proxy_worker_shared *wshared;
-    char *ptr, *sockpath = NULL;
+    const char *ptr = NULL, *sockpath = NULL, *pdollars = NULL;
+    apr_port_t port_of_scheme;
+    apr_uri_t uri;
 
     /*
      * Look to see if we are using UDS:
      * require format: unix:/path/foo/bar.sock|http://ignored/path2/
      * This results in talking http to the socket at /path/foo/bar.sock
      */
-    ptr = ap_strchr((char *)url, '|');
-    if (ptr) {
-        *ptr = '\0';
-        rv = apr_uri_parse(p, url, &urisock);
-        if (rv == APR_SUCCESS && !strcasecmp(urisock.scheme, "unix")) {
-            sockpath = ap_runtime_dir_relative(p, urisock.path);;
-            url = ptr+1;    /* so we get the scheme for the uds */
+    if (!ap_cstr_casecmpn(url, "unix:", 5)
+            && (ptr = ap_strchr_c(url + 5, '|'))) {
+        rv = apr_uri_parse(p, apr_pstrmemdup(p, url, ptr - url), &uri);
+        if (rv == APR_SUCCESS) {
+            sockpath = ap_runtime_dir_relative(p, uri.path);;
+            ptr++;    /* so we get the scheme for the uds */
         }
         else {
-            *ptr = '|';
+            ptr = url;
         }
     }
-    rv = apr_uri_parse(p, url, &uri);
+    else {
+        ptr = url;
+    }
+    if (mask & AP_PROXY_WORKER_IS_MATCH) {
+        /* apr_uri_parse() will accept the '$' sign anywhere in the URL but
+         * in the :port part, and we don't want scheme://host:port$1$2/path
+         * to fail (e.g. "ProxyPassMatch ^/(a|b)(/.*)? http://host:port$2").
+         * So we trim all the $n from the :port and prepend them in uri.path
+         * afterward for apr_uri_unparse() to restore the original URL below.
+         */
+#define IS_REF(x) (x[0] == '$' && apr_isdigit(x[1]))
+        const char *pos = ap_strstr_c(ptr, "://");
+        if (pos) {
+            pos += 3;
+            while (*pos && *pos != ':' && *pos != '/') {
+                pos++;
+            }
+            if (*pos == ':') {
+                pos++;
+                while (*pos && !IS_REF(pos) && *pos != '/') {
+                    pos++;
+                }
+                if (IS_REF(pos)) {
+                    struct iovec vec[2];
+                    const char *path = pos + 2;
+                    while (*path && *path != '/') {
+                        path++;
+                    }
+                    pdollars = apr_pstrmemdup(p, pos, path - pos);
+                    vec[0].iov_base = (void *)ptr;
+                    vec[0].iov_len = pos - ptr;
+                    vec[1].iov_base = (void *)path;
+                    vec[1].iov_len = strlen(path);
+                    ptr = apr_pstrcatv(p, vec, 2, NULL);
+                }
+            }
+         }
+#undef IS_REF
+     }
 
+    /* Normalize the url (worker name) */
+    rv = apr_uri_parse(p, ptr, &uri);
     if (rv != APR_SUCCESS) {
         return apr_pstrcat(p, "Unable to parse URL: ", url, NULL);
     }
     if (!uri.scheme) {
         return apr_pstrcat(p, "URL must be absolute!: ", url, NULL);
     }
-    /* allow for unix:/path|http: */
     if (!uri.hostname) {
         if (sockpath) {
+            /* allow for unix:/path|http: */
             uri.hostname = "localhost";
         }
         else {
@@ -1644,6 +1699,16 @@ PROXY_DECLARE(char *) ap_proxy_define_wo
         ap_str_tolower(uri.hostname);
     }
     ap_str_tolower(uri.scheme);
+    port_of_scheme = ap_proxy_port_of_scheme(uri.scheme);
+    if (uri.port && uri.port == port_of_scheme) {
+        uri.port = 0;
+    }
+    if (pdollars) {
+        /* Restore/prepend pdollars into the path. */
+        uri.path = apr_pstrcat(p, pdollars, uri.path, NULL);
+    }
+    ptr = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
+
     /*
      * Workers can be associated w/ balancers or on their
      * own; ie: the generic reverse-proxy or a worker
@@ -1667,23 +1732,17 @@ PROXY_DECLARE(char *) ap_proxy_define_wo
         /* we need to allocate space here */
         *worker = apr_palloc(p, sizeof(proxy_worker));
     }
-
     memset(*worker, 0, sizeof(proxy_worker));
+
     /* right here we just want to tuck away the worker info.
      * if called during config, we don't have shm setup yet,
      * so just note the info for later. */
-    if (do_malloc)
+    if (mask & AP_PROXY_WORKER_IS_MALLOCED)
         wshared = ap_malloc(sizeof(proxy_worker_shared));  /* will be freed ap_proxy_share_worker */
     else
         wshared = apr_palloc(p, sizeof(proxy_worker_shared));
-
     memset(wshared, 0, sizeof(proxy_worker_shared));
 
-    wshared->port = (uri.port ? uri.port : ap_proxy_port_of_scheme(uri.scheme));
-    if (uri.port && uri.port == ap_proxy_port_of_scheme(uri.scheme)) {
-        uri.port = 0;
-    }
-    ptr = apr_uri_unparse(p, &uri, APR_URI_UNP_REVEALPASSWORD);
     if (PROXY_STRNCPY(wshared->name, ptr) != APR_SUCCESS) {
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, ap_server_conf, APLOGNO(02808)
         "Alert! worker name (%s) too long; truncated to: %s", ptr, wshared->name);
@@ -1694,6 +1753,7 @@ PROXY_DECLARE(char *) ap_proxy_define_wo
     if (PROXY_STRNCPY(wshared->hostname, uri.hostname) != APR_SUCCESS) {
         return apr_psprintf(p, "worker hostname (%s) too long", uri.hostname);
     }
+    wshared->port = (uri.port) ? uri.port : port_of_scheme;
     wshared->flush_packets = flush_off;
     wshared->flush_wait = PROXY_FLUSH_WAIT;
     wshared->is_address_reusable = 1;
@@ -1704,7 +1764,7 @@ PROXY_DECLARE(char *) ap_proxy_define_wo
     wshared->smax = -1;
     wshared->hash.def = ap_proxy_hashfunc(wshared->name, PROXY_HASHFUNC_DEFAULT);
     wshared->hash.fnv = ap_proxy_hashfunc(wshared->name, PROXY_HASHFUNC_FNV);
-    wshared->was_malloced = (do_malloc != 0);
+    wshared->was_malloced = (mask & AP_PROXY_WORKER_IS_MALLOCED) != 0;
     if (sockpath) {
         if (PROXY_STRNCPY(wshared->uds_path, sockpath) != APR_SUCCESS) {
             return apr_psprintf(p, "worker uds path (%s) too long", sockpath);
@@ -1724,9 +1784,37 @@ PROXY_DECLARE(char *) ap_proxy_define_wo
     (*worker)->balancer = balancer;
     (*worker)->s = wshared;
 
+    if (mask & AP_PROXY_WORKER_IS_MATCH) {
+        if (ap_strchr_c((*worker)->s->name, '$')) {
+            /* Before AP_PROXY_WORKER_IS_MATCH (< 2.4.47), a regex worker
+             * with dollar substitution was never matched against the actual
+             * URL thus the request fell through the generic worker. To avoid
+             * dns and connection reuse compat issues, let's disable connection
+             * reuse by default, it can still be overwritten by an explicit
+             * enablereuse=on.
+             */
+            (*worker)->s->disablereuse = 1;
+        }
+    }
+
+
     return NULL;
 }
 
+PROXY_DECLARE(char *) ap_proxy_define_worker(apr_pool_t *p,
+                                              proxy_worker **worker,
+                                              proxy_balancer *balancer,
+                                              proxy_server_conf *conf,
+                                              const char *url,
+                                              int do_malloc)
+{
+ return ap_proxy_define_worker_ex(p, worker, balancer, conf, url,
+                                     AP_PROXY_WORKER_IS_PREFIX |
+                                     (do_malloc ? AP_PROXY_WORKER_IS_MALLOCED
+                                                : 0));
+}
+
+
 /*
  * Create an already defined worker and free up memory
  */
