]> granicus.if.org Git - apache/blobdiff - server/util.c
fr doc rebuild.
[apache] / server / util.c
index 42b0d2dad7c84d5ced17ccab25728a7f21bf3f21..3693bfbff1535448e1820d221afb0ce2e1cc60fd 100644 (file)
@@ -48,6 +48,7 @@
 
 #include "ap_config.h"
 #include "apr_base64.h"
+#include "apr_fnmatch.h"
 #include "httpd.h"
 #include "http_main.h"
 #include "http_log.h"
@@ -80,7 +81,7 @@
  * char in here and get it to work, because if char is signed then it
  * will first be sign extended.
  */
-#define TEST_CHAR(c, f)        (test_char_table[(unsigned)(c)] & (f))
+#define TEST_CHAR(c, f)        (test_char_table[(unsigned char)(c)] & (f))
 
 /* Win32/NetWare/OS2 need to check for both forward and back slashes
  * in ap_getparents() and ap_escape_url.
 #undef APLOG_MODULE_INDEX
 #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX
 
+/* maximum nesting level for config directories */
+#ifndef AP_MAX_FNMATCH_DIR_DEPTH
+#define AP_MAX_FNMATCH_DIR_DEPTH (128)
+#endif
 
 /*
  * Examine a field value (such as a media-/content-type) string and return
@@ -191,8 +196,6 @@ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected)
     int x, y;
 
     for (x = 0, y = 0; expected[y]; ++y, ++x) {
-        if ((!str[x]) && (expected[y] != '*'))
-            return -1;
         if (expected[y] == '*') {
             while (expected[++y] == '*');
             if (!expected[y])
@@ -204,6 +207,8 @@ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected)
             }
             return -1;
         }
+        else if (!str[x])
+            return -1;
         else if ((expected[y] != '?') && (str[x] != expected[y]))
             return 1;
     }
@@ -599,8 +604,8 @@ AP_DECLARE(void) ap_no2slash(char *name)
  * MODIFIED FOR HAVE_DRIVE_LETTERS and NETWARE environments,
  * so that if n == 0, "/" is returned in d with n == 1
  * and s == "e:/test.html", "e:/" is returned in d
- * *** See also directory_walk in modules/http/http_request.c
-
+ * *** See also ap_directory_walk in server/request.c
+ *
  * examples:
  *    /a/b, 0  ==> /  (true for all platforms)
  *    /a/b, 1  ==> /
@@ -822,6 +827,60 @@ AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line)
     return res;
 }
 
+AP_DECLARE(char *) ap_getword_conf2_nc(apr_pool_t *p, char **line)
+{
+    return ap_getword_conf2(p, (const char **) line);
+}
+
+AP_DECLARE(char *) ap_getword_conf2(apr_pool_t *p, const char **line)
+{
+    const char *str = *line, *strend;
+    char *res;
+    char quote;
+    int count = 1;
+
+    while (apr_isspace(*str))
+        ++str;
+
+    if (!*str) {
+        *line = str;
+        return "";
+    }
+
+    if ((quote = *str) == '"' || quote == '\'')
+        return ap_getword_conf(p, line);
+
+    if (quote == '{') {
+        strend = str + 1;
+        while (*strend) {
+            if (*strend == '}' && !--count)
+                break;
+            if (*strend == '{')
+                ++count;
+            if (*strend == '\\' && strend[1] == '\\') {
+                ++strend;
+            }
+            ++strend;
+        }
+        res = substring_conf(p, str + 1, strend - str - 1, 0);
+
+        if (*strend == '}')
+            ++strend;
+    }
+    else {
+        strend = str;
+        while (*strend && !apr_isspace(*strend))
+            ++strend;
+
+        res = substring_conf(p, str, strend - str, 0);
+    }
+
+    while (apr_isspace(*strend))
+        ++strend;
+    *line = strend;
+    return res;
+}
+
 AP_DECLARE(int) ap_cfg_closefile(ap_configfile_t *cfp)
 {
 #ifdef DEBUG
@@ -882,7 +941,7 @@ AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg,
 
     if (finfo.filetype != APR_REG &&
 #if defined(WIN32) || defined(OS2) || defined(NETWARE)
-        strcasecmp(apr_filepath_name_get(name), "nul") != 0) {
+        ap_cstr_casecmp(apr_filepath_name_get(name), "nul") != 0) {
 #else
         strcmp(name, "/dev/null") != 0) {
 #endif /* WIN32 || OS2 */
@@ -971,20 +1030,20 @@ AP_DECLARE(const char *) ap_pcfg_strerror(apr_pool_t *p, ap_configfile_t *cfp,
 /* Read one line from open ap_configfile_t, strip LF, increase line number */
 /* If custom handler does not define a getstr() function, read char by char */
 static apr_status_t ap_cfg_getline_core(char *buf, apr_size_t bufsize,
-                                        ap_configfile_t *cfp)
+                                        apr_size_t offset, ap_configfile_t *cfp)
 {
     apr_status_t rc;
     /* If a "get string" function is defined, use it */
     if (cfp->getstr != NULL) {
         char *cp;
-        char *cbuf = buf;
-        apr_size_t cbufsize = bufsize;
+        char *cbuf = buf + offset;
+        apr_size_t cbufsize = bufsize - offset;
 
         while (1) {
             ++cfp->line_number;
             rc = cfp->getstr(cbuf, cbufsize, cfp->param);
             if (rc == APR_EOF) {
-                if (cbuf != buf) {
+                if (cbuf != buf + offset) {
                     *cbuf = '\0';
                     break;
                 }
@@ -1002,11 +1061,11 @@ static apr_status_t ap_cfg_getline_core(char *buf, apr_size_t bufsize,
              */
             cp = cbuf;
             cp += strlen(cp);
-            if (cp > cbuf && cp[-1] == LF) {
+            if (cp > buf && cp[-1] == LF) {
                 cp--;
-                if (cp > cbuf && cp[-1] == CR)
+                if (cp > buf && cp[-1] == CR)
                     cp--;
-                if (cp > cbuf && cp[-1] == '\\') {
+                if (cp > buf && cp[-1] == '\\') {
                     cp--;
                     /*
                      * line continuation requested -
@@ -1024,19 +1083,19 @@ static apr_status_t ap_cfg_getline_core(char *buf, apr_size_t bufsize,
         }
     } else {
         /* No "get string" function defined; read character by character */
-        apr_size_t i = 0;
+        apr_size_t i = offset;
 
         if (bufsize < 2) {
             /* too small, assume caller is crazy */
             return APR_EINVAL;
         }
-        buf[0] = '\0';
+        buf[offset] = '\0';
 
         while (1) {
             char c;
             rc = cfp->getch(&c, cfp->param);
             if (rc == APR_EOF) {
-                if (i > 0)
+                if (i > offset)
                     break;
                 else
                     return APR_EOF;
@@ -1054,11 +1113,11 @@ static apr_status_t ap_cfg_getline_core(char *buf, apr_size_t bufsize,
                     break;
                 }
             }
-            else if (i >= bufsize - 2) {
-                return APR_ENOSPC;
-            }
             buf[i] = c;
             ++i;
+            if (i >= bufsize - 1) {
+                return APR_ENOSPC;
+            }
         }
         buf[i] = '\0';
     }
@@ -1092,7 +1151,7 @@ static int cfg_trim_line(char *buf)
 AP_DECLARE(apr_status_t) ap_cfg_getline(char *buf, apr_size_t bufsize,
                                         ap_configfile_t *cfp)
 {
-    apr_status_t rc = ap_cfg_getline_core(buf, bufsize, cfp);
+    apr_status_t rc = ap_cfg_getline_core(buf, bufsize, 0, cfp);
     if (rc == APR_SUCCESS)
         cfg_trim_line(buf);
     return rc;
@@ -1119,7 +1178,7 @@ AP_DECLARE(apr_status_t) ap_varbuf_cfg_getline(struct ap_varbuf *vb,
     }
 
     for (;;) {
-        rc = ap_cfg_getline_core(vb->buf + vb->strlen, vb->avail - vb->strlen, cfp);
+        rc = ap_cfg_getline_core(vb->buf, vb->avail, vb->strlen, cfp);
         if (rc == APR_ENOSPC || rc == APR_SUCCESS)
             vb->strlen += strlen(vb->buf + vb->strlen);
         if (rc != APR_ENOSPC)
@@ -1452,6 +1511,126 @@ AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line,
     return find_list_item(p, line, tok, AP_ETAG_WEAK);
 }
 
+/* Grab a list of tokens of the format 1#token (from RFC7230) */
+AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p,
+                                                const char *str_in,
+                                                apr_array_header_t **tokens,
+                                                int skip_invalid)
+{
+    int in_leading_space = 1;
+    int in_trailing_space = 0;
+    int string_end = 0;
+    const char *tok_begin;
+    const char *cur;
+
+    if (!str_in) {
+        return NULL;
+    }
+
+    tok_begin = cur = str_in;
+
+    while (!string_end) {
+        const unsigned char c = (unsigned char)*cur;
+
+        if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP)) {
+            /* Non-separator character; we are finished with leading
+             * whitespace. We must never have encountered any trailing
+             * whitespace before the delimiter (comma) */
+            in_leading_space = 0;
+            if (in_trailing_space) {
+                return "Encountered illegal whitespace in token";
+            }
+        }
+        else if (c == ' ' || c == '\t') {
+            /* "Linear whitespace" only includes ASCII CRLF, space, and tab;
+             * we can't get a CRLF since headers are split on them already,
+             * so only look for a space or a tab */
+            if (in_leading_space) {
+                /* We're still in leading whitespace */
+                ++tok_begin;
+            }
+            else {
+                /* We must be in trailing whitespace */
+                ++in_trailing_space;
+            }
+        }
+        else if (c == ',' || c == '\0') {
+            if (!in_leading_space) {
+                /* If we're out of the leading space, we know we've read some
+                 * characters of a token */
+                if (*tokens == NULL) {
+                    *tokens = apr_array_make(p, 4, sizeof(char *));
+                }
+                APR_ARRAY_PUSH(*tokens, char *) =
+                    apr_pstrmemdup((*tokens)->pool, tok_begin,
+                                   (cur - tok_begin) - in_trailing_space);
+            }
+            /* We're allowed to have null elements, just don't add them to the
+             * array */
+
+            tok_begin = cur + 1;
+            in_leading_space = 1;
+            in_trailing_space = 0;
+            string_end = (c == '\0');
+        }
+        else {
+            /* Encountered illegal separator char */
+            if (skip_invalid) {
+                /* Skip to the next separator */
+                const char *temp;
+                temp = ap_strchr_c(cur, ',');
+                if(!temp) {
+                    temp = ap_strchr_c(cur, '\0');
+                }
+
+                /* Act like we haven't seen a token so we reset */
+                cur = temp - 1;
+                in_leading_space = 1;
+                in_trailing_space = 0;
+            }
+            else {
+                return apr_psprintf(p, "Encountered illegal separator "
+                                    "'\\x%.2x'", (unsigned int)c);
+            }
+        }
+
+        ++cur;
+    }
+
+    return NULL;
+}
+
+/* Scan a string for HTTP VCHAR/obs-text characters including HT and SP
+ * (as used in header values, for example, in RFC 7230 section 3.2)
+ * returning the pointer to the first non-HT ASCII ctrl character.
+ */
+AP_DECLARE(const char *) ap_scan_http_field_content(const char *ptr)
+{
+    for ( ; !TEST_CHAR(*ptr, T_HTTP_CTRLS); ++ptr) ;
+
+    return ptr;
+}
+
+/* Scan a string for HTTP token characters, returning the pointer to
+ * the first non-token character.
+ */
+AP_DECLARE(const char *) ap_scan_http_token(const char *ptr)
+{
+    for ( ; !TEST_CHAR(*ptr, T_HTTP_TOKEN_STOP); ++ptr) ;
+
+    return ptr;
+}
+
+/* Scan a string for visible ASCII (0x21-0x7E) or obstext (0x80+)
+ * and return a pointer to the first ctrl/space character encountered.
+ */
+AP_DECLARE(const char *) ap_scan_vchar_obstext(const char *ptr)
+{
+    for ( ; TEST_CHAR(*ptr, T_VCHAR_OBSTEXT); ++ptr) ;
+
+    return ptr;
+}
+
 /* Retrieve a token, spacing over it and returning a pointer to
  * the first non-white byte afterwards.  Note that these tokens
  * are delimited by semis and commas; and can also be delimited
@@ -1507,10 +1686,8 @@ AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok)
 
     s = (const unsigned char *)line;
     for (;;) {
-        /* find start of token, skip all stop characters, note NUL
-         * isn't a token stop, so we don't need to test for it
-         */
-        while (TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
+        /* find start of token, skip all stop characters */
+        while (*s && TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
             ++s;
         }
         if (!*s) {
@@ -1521,7 +1698,7 @@ AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok)
         while (*s && !TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) {
             ++s;
         }
-        if (!strncasecmp((const char *)start_token, (const char *)tok,
+        if (!ap_cstr_casecmpn((const char *)start_token, (const char *)tok,
                          s - start_token)) {
             return 1;
         }
@@ -1548,7 +1725,7 @@ AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line,
         (lidx > 0 && !(apr_isspace(line[lidx - 1]) || line[lidx - 1] == ',')))
         return 0;
 
-    return (strncasecmp(&line[lidx], tok, tlen) == 0);
+    return (ap_cstr_casecmpn(&line[lidx], tok, tlen) == 0);
 }
 
 AP_DECLARE(char *) ap_escape_shell_cmd(apr_pool_t *p, const char *str)
@@ -1623,6 +1800,10 @@ static int unescape_url(char *url, const char *forbid, const char *reserved)
 
     badesc = 0;
     badpath = 0;
+
+    if (url == NULL) {
+        return OK;
+    }
     /* Initial scan for first '%'. Don't bother writing values before
      * seeing a '%' */
     y = strchr(url, '%');
@@ -2052,16 +2233,6 @@ AP_DECLARE(void) ap_bin2hex(const void *src, apr_size_t srclen, char *dest)
     *dest = '\0';
 }
 
-AP_DECLARE(int) ap_has_cntrl(const char *str)
-{
-    while (*str) {
-        if (apr_iscntrl(*str))
-            return 1;
-        str++;
-    }
-    return 0;
-}
-
 AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path)
 {
     apr_finfo_t finfo;
@@ -2225,6 +2396,76 @@ AP_DECLARE(char *) ap_pbase64decode(apr_pool_t *p, const char *bufcoded)
     return decoded;
 }
 
+/* a stringent version of ap_pbase64decode() */
+AP_DECLARE(apr_status_t) ap_pbase64decode_strict(apr_pool_t *p,
+                                                 const char *encoded,
+                                                 char **decoded,
+                                                 apr_size_t *len)
+{
+    apr_size_t end_index;
+    int last_group_len;
+    const char *end;
+
+    /* Sanity check.
+     * TODO: this would be a lot more efficient if we had access to the lookup
+     * table used by APR. If that gets pulled in at any point, make use of it.
+     */
+    end_index = strspn(encoded, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                                "abcdefghijklmnopqrstuvwxyz"
+                                "0123456789+/");
+
+    last_group_len = end_index % 4;
+    end = encoded + end_index;
+
+    /* The only non-alphabet character allowed is the padding character '=' at
+     * the end of the string. There are two allowable padding cases for the last
+     * group of four: "xY==" or "xyZ=". We require the final (non-padding)
+     * character to have been zero-padded during encoding, which limits the
+     * character choices.
+     */
+    if (last_group_len == 1) {
+        /* This isn't ever valid. */
+        return APR_EINVAL;
+    }
+    else if (last_group_len == 2) {
+        /* ...xY== */
+        if (*end != '=' || end[1] != '=') {
+            return APR_EINVAL;
+        }
+        else if (!ap_strchr("AQgw", end[-1])) {
+            /* Correctly zero-padded input data will result in a final character
+             * that is one of the four above. */
+            return APR_EINVAL;
+        }
+
+        end += 2;
+    }
+    else if (last_group_len == 3) {
+        /* ...xyZ= */
+        if (*end != '=') {
+            return APR_EINVAL;
+        }
+        else if (!ap_strchr("AEIMQUYcgkosw048", end[-1])) {
+            /* Correctly zero-padded input data will result in a final character
+             * that is one of the sixteen above. */
+            return APR_EINVAL;
+        }
+
+        end++;
+    }
+
+    /* At this point, if the encoded buffer is correct, we should be at the end
+     * of the string. */
+    if (*end) {
+        return APR_EINVAL;
+    }
+
+    *decoded = apr_palloc(p, apr_base64_decode_len(encoded));
+    *len = apr_base64_decode(*decoded, encoded);
+
+    return APR_SUCCESS;
+}
+
 AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string)
 {
     char *encoded;
@@ -2328,7 +2569,7 @@ AP_DECLARE(char *) ap_append_pid(apr_pool_t *p, const char *string,
 /**
  * Parse a given timeout parameter string into an apr_interval_time_t value.
  * The unit of the time interval is given as postfix string to the numeric
- * string. Currently the following units are understood:
+ * string. Currently the following units are understood (case insensitive):
  *
  * ms    : milliseconds
  * s     : seconds
@@ -2366,20 +2607,25 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse(
     switch (*time_str) {
         /* Time is in seconds */
     case 's':
+    case 'S':
         *timeout = (apr_interval_time_t) apr_time_from_sec(tout);
         break;
     case 'h':
+    case 'H':
         /* Time is in hours */
         *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 3600);
         break;
     case 'm':
+    case 'M':
         switch (*(++time_str)) {
         /* Time is in milliseconds */
         case 's':
+        case 'S':
             *timeout = (apr_interval_time_t) tout * 1000;
             break;
         /* Time is in minutes */
         case 'i':
+        case 'I':
             *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 60);
             break;
         default:
@@ -2417,6 +2663,21 @@ AP_DECLARE(int) ap_request_has_body(request_rec *r)
     return has_body;
 }
 
+/**
+ * Check whether a request is tainted by exposure to something
+ * potentially untrusted.  
+ *
+ */
+AP_DECLARE(int) ap_request_tainted(request_rec *r, int flags)
+{
+    /** Potential future: a hook or callback here could serve modules
+     *  like mod_security and ironbee with more complex needs.
+     */
+    return r && ((r->taint&flags)
+                 || ap_request_tainted(r->main, flags)
+                 || ap_request_tainted(r->prev, flags));
+}
+
 AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_)
 {
     void **ptr = (void **)data_;
@@ -2507,15 +2768,13 @@ AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f,
     ap_form_type_t state = FORM_NAME, percent = FORM_NORMAL;
     ap_form_pair_t *pair = NULL;
     apr_array_header_t *pairs = apr_array_make(r->pool, 4, sizeof(ap_form_pair_t));
-
-    char hi = 0;
-    char low = 0;
+    char escaped_char[2] = { 0 };
 
     *ptr = pairs;
 
     /* sanity check - we only support forms for now */
     ct = apr_table_get(r->headers_in, "Content-Type");
-    if (!ct || strncasecmp("application/x-www-form-urlencoded", ct, 33)) {
+    if (!ct || ap_cstr_casecmpn("application/x-www-form-urlencoded", ct, 33)) {
         return ap_discard_request_body(r);
     }
 
@@ -2576,30 +2835,13 @@ AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f,
                     continue;
                 }
                 if (FORM_PERCENTA == percent) {
-                    if (c >= 'a') {
-                        hi = c - 'a' + 10;
-                    }
-                    else if (c >= 'A') {
-                        hi = c - 'A' + 10;
-                    }
-                    else if (c >= '0') {
-                        hi = c - '0';
-                    }
-                    hi = hi << 4;
+                    escaped_char[0] = c;
                     percent = FORM_PERCENTB;
                     continue;
                 }
                 if (FORM_PERCENTB == percent) {
-                    if (c >= 'a') {
-                        low = c - 'a' + 10;
-                    }
-                    else if (c >= 'A') {
-                        low = c - 'A' + 10;
-                    }
-                    else if (c >= '0') {
-                        low = c - '0';
-                    }
-                    c = low | hi;
+                    escaped_char[1] = c;
+                    c = x2c(escaped_char);
                     percent = FORM_NORMAL;
                 }
                 switch (state) {
@@ -2617,12 +2859,11 @@ AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f,
                     case FORM_NAME:
                         if (offset < HUGE_STRING_LEN) {
                             if ('=' == c) {
-                                buffer[offset] = 0;
-                                offset = 0;
                                 pair = (ap_form_pair_t *) apr_array_push(pairs);
-                                pair->name = apr_pstrdup(r->pool, buffer);
+                                pair->name = apr_pstrmemdup(r->pool, buffer, offset);
                                 pair->value = apr_brigade_create(r->pool, r->connection->bucket_alloc);
                                 state = FORM_VALUE;
+                                offset = 0;
                             }
                             else {
                                 buffer[offset++] = c;
@@ -3059,3 +3300,356 @@ AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p,
 
     return apr_pstrndup(p, buf, k);
 }
+
+AP_DECLARE(int) ap_array_str_index(const apr_array_header_t *array, 
+                                   const char *s,
+                                   int start)
+{
+    if (start >= 0) {
+        int i;
+        
+        for (i = start; i < array->nelts; i++) {
+            const char *p = APR_ARRAY_IDX(array, i, const char *);
+            if (!strcmp(p, s)) {
+                return i;
+            }
+        }
+    }
+    
+    return -1;
+}
+
+AP_DECLARE(int) ap_array_str_contains(const apr_array_header_t *array, 
+                                      const char *s)
+{
+    return (ap_array_str_index(array, s, 0) >= 0);
+}
+
+#if !APR_CHARSET_EBCDIC
+/*
+ * Our own known-fast translation table for casecmp by character.
+ * Only ASCII alpha characters 41-5A are folded to 61-7A, other
+ * octets (such as extended latin alphabetics) are never case-folded.
+ * NOTE: Other than Alpha A-Z/a-z, each code point is unique!
+ */
+static const short ucharmap[] = {
+    0x0,  0x1,  0x2,  0x3,  0x4,  0x5,  0x6,  0x7,
+    0x8,  0x9,  0xa,  0xb,  0xc,  0xd,  0xe,  0xf,
+    0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+    0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+    0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+    0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+    0x40,  'a',  'b',  'c',  'd',  'e',  'f',  'g',
+     'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
+     'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
+     'x',  'y',  'z', 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
+    0x60,  'a',  'b',  'c',  'd',  'e',  'f',  'g',
+     'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
+     'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
+     'x',  'y',  'z', 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+    0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
+    0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+    0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
+    0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+    0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
+    0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
+    0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
+    0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
+    0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
+    0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
+    0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
+    0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+    0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
+    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+    0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff
+};
+#else /* APR_CHARSET_EBCDIC */
+/*
+ * Derived from apr-iconv/ccs/cp037.c for EBCDIC case comparison,
+ * provides unique identity of every char value (strict ISO-646
+ * conformance, arbitrary election of an ISO-8859-1 ordering, and
+ * very arbitrary control code assignments into C1 to achieve
+ * identity and a reversible mapping of code points),
+ * then folding the equivalences of ASCII 41-5A into 61-7A, 
+ * presenting comparison results in a somewhat ISO/IEC 10646
+ * (ASCII-like) order, depending on the EBCDIC code page in use.
+ *
+ * NOTE: Other than Alpha A-Z/a-z, each code point is unique!
+ */
+static const short ucharmap[] = {
+    0x00, 0x01, 0x02, 0x03, 0x9C, 0x09, 0x86, 0x7F,
+    0x97, 0x8D, 0x8E, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
+    0x10, 0x11, 0x12, 0x13, 0x9D, 0x85, 0x08, 0x87,
+    0x18, 0x19, 0x92, 0x8F, 0x1C, 0x1D, 0x1E, 0x1F,
+    0x80, 0x81, 0x82, 0x83, 0x84, 0x0A, 0x17, 0x1B,
+    0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x05, 0x06, 0x07,
+    0x90, 0x91, 0x16, 0x93, 0x94, 0x95, 0x96, 0x04,
+    0x98, 0x99, 0x9A, 0x9B, 0x14, 0x15, 0x9E, 0x1A,
+    0x20, 0xA0, 0xE2, 0xE4, 0xE0, 0xE1, 0xE3, 0xE5,
+    0xE7, 0xF1, 0xA2, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
+    0x26, 0xE9, 0xEA, 0xEB, 0xE8, 0xED, 0xEE, 0xEF,
+    0xEC, 0xDF, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAC,
+    0x2D, 0x2F, 0xC2, 0xC4, 0xC0, 0xC1, 0xC3, 0xC5,
+    0xC7, 0xD1, 0xA6, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
+    0xF8, 0xC9, 0xCA, 0xCB, 0xC8, 0xCD, 0xCE, 0xCF,
+    0xCC, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
+    0xD8, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0xAB, 0xBB, 0xF0, 0xFD, 0xFE, 0xB1,
+    0xB0, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
+    0x71, 0x72, 0xAA, 0xBA, 0xE6, 0xB8, 0xC6, 0xA4,
+    0xB5, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+    0x79, 0x7A, 0xA1, 0xBF, 0xD0, 0xDD, 0xDE, 0xAE,
+    0x5E, 0xA3, 0xA5, 0xB7, 0xA9, 0xA7, 0xB6, 0xBC,
+    0xBD, 0xBE, 0x5B, 0x5D, 0xAF, 0xA8, 0xB4, 0xD7,
+    0x7B, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
+    0x68, 0x69, 0xAD, 0xF4, 0xF6, 0xF2, 0xF3, 0xF5,
+    0x7D, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
+    0x71, 0x72, 0xB9, 0xFB, 0xFC, 0xF9, 0xFA, 0xFF,
+    0x5C, 0xF7, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+    0x79, 0x7A, 0xB2, 0xD4, 0xD6, 0xD2, 0xD3, 0xD5,
+    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+    0x38, 0x39, 0xB3, 0xDB, 0xDC, 0xD9, 0xDA, 0x9F
+};
+#endif
+
+AP_DECLARE(int) ap_cstr_casecmp(const char *s1, const char *s2)
+{
+    const unsigned char *str1 = (const unsigned char *)s1;
+    const unsigned char *str2 = (const unsigned char *)s2;
+    for (;;)
+    {
+        const int c1 = (int)(*str1);
+        const int c2 = (int)(*str2);
+        const int cmp = ucharmap[c1] - ucharmap[c2];
+        /* Not necessary to test for !c2, this is caught by cmp */
+        if (cmp || !c1)
+            return cmp;
+        str1++;
+        str2++;
+    }
+}
+
+AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n)
+{
+    const unsigned char *str1 = (const unsigned char *)s1;
+    const unsigned char *str2 = (const unsigned char *)s2;
+    while (n--)
+    {
+        const int c1 = (int)(*str1);
+        const int c2 = (int)(*str2);
+        const int cmp = ucharmap[c1] - ucharmap[c2];
+        /* Not necessary to test for !c2, this is caught by cmp */
+        if (cmp || !c1)
+            return cmp;
+        str1++;
+        str2++;
+    }
+    return 0;
+}
+
+typedef struct {
+    const char *fname;
+} fnames;
+
+static int fname_alphasort(const void *fn1, const void *fn2)
+{
+    const fnames *f1 = fn1;
+    const fnames *f2 = fn2;
+
+    return strcmp(f1->fname, f2->fname);
+}
+
+AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags,
+        const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx)
+{
+    ap_dir_match_t *w = apr_palloc(cmd->temp_pool, sizeof(*w));
+
+    w->prefix = apr_pstrcat(cmd->pool, cmd->cmd->name, ": ", NULL);
+    w->p = cmd->pool;
+    w->ptemp = cmd->temp_pool;
+    w->flags = flags;
+    w->cb = cb;
+    w->ctx = ctx;
+    w->depth = 0;
+
+    return w;
+}
+
+AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname)
+{
+    const char *error;
+    apr_status_t rv;
+
+    if ((w->flags & AP_DIR_FLAG_RECURSIVE) && ap_is_directory(w->ptemp, fname)) {
+        apr_dir_t *dirp;
+        apr_finfo_t dirent;
+        int current;
+        apr_array_header_t *candidates = NULL;
+        fnames *fnew;
+        char *path = apr_pstrdup(w->ptemp, fname);
+
+        if (++w->depth > AP_MAX_FNMATCH_DIR_DEPTH) {
+            return apr_psprintf(w->p, "%sDirectory '%s' exceeds the maximum include "
+                    "directory nesting level of %u. You have "
+                    "probably a recursion somewhere.", w->prefix ? w->prefix : "", path,
+                    AP_MAX_FNMATCH_DIR_DEPTH);
+        }
+
+        /*
+         * first course of business is to grok all the directory
+         * entries here and store 'em away. Recall we need full pathnames
+         * for this.
+         */
+        rv = apr_dir_open(&dirp, path, w->ptemp);
+        if (rv != APR_SUCCESS) {
+            return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
+                    w->prefix ? w->prefix : "", path, &rv);
+        }
+
+        candidates = apr_array_make(w->ptemp, 1, sizeof(fnames));
+        while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) {
+            /* strip out '.' and '..' */
+            if (strcmp(dirent.name, ".")
+                && strcmp(dirent.name, "..")) {
+                fnew = (fnames *) apr_array_push(candidates);
+                fnew->fname = ap_make_full_path(w->ptemp, path, dirent.name);
+            }
+        }
+
+        apr_dir_close(dirp);
+        if (candidates->nelts != 0) {
+            qsort((void *) candidates->elts, candidates->nelts,
+                  sizeof(fnames), fname_alphasort);
+
+            /*
+             * Now recurse these... we handle errors and subdirectories
+             * via the recursion, which is nice
+             */
+            for (current = 0; current < candidates->nelts; ++current) {
+                fnew = &((fnames *) candidates->elts)[current];
+                error = ap_dir_nofnmatch(w, fnew->fname);
+                if (error) {
+                    return error;
+                }
+            }
+        }
+
+        w->depth--;
+
+        return NULL;
+    }
+    else if (w->flags & AP_DIR_FLAG_OPTIONAL) {
+        /* If the optional flag is set (like for IncludeOptional) we can
+         * tolerate that no file or directory is present and bail out.
+         */
+        apr_finfo_t finfo;
+        if (apr_stat(&finfo, fname, APR_FINFO_TYPE, w->ptemp) != APR_SUCCESS
+            || finfo.filetype == APR_NOFILE)
+            return NULL;
+    }
+
+    return w->cb(w, fname);
+}
+
+AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path,
+        const char *fname)
+{
+    const char *rest;
+    apr_status_t rv;
+    apr_dir_t *dirp;
+    apr_finfo_t dirent;
+    apr_array_header_t *candidates = NULL;
+    fnames *fnew;
+    int current;
+
+    /* find the first part of the filename */
+    rest = ap_strchr_c(fname, '/');
+    if (rest) {
+        fname = apr_pstrmemdup(w->ptemp, fname, rest - fname);
+        rest++;
+    }
+
+    /* optimisation - if the filename isn't a wildcard, process it directly */
+    if (!apr_fnmatch_test(fname)) {
+        path = path ? ap_make_full_path(w->ptemp, path, fname) : fname;
+        if (!rest) {
+            return ap_dir_nofnmatch(w, path);
+        }
+        else {
+            return ap_dir_fnmatch(w, path, rest);
+        }
+    }
+
+    /*
+     * first course of business is to grok all the directory
+     * entries here and store 'em away. Recall we need full pathnames
+     * for this.
+     */
+    rv = apr_dir_open(&dirp, path, w->ptemp);
+    if (rv != APR_SUCCESS) {
+        /* If the directory doesn't exist and the optional flag is set
+         * there is no need to return an error.
+         */
+        if (rv == APR_ENOENT && (w->flags & AP_DIR_FLAG_OPTIONAL)) {
+            return NULL;
+        }
+        return apr_psprintf(w->p, "%sCould not open directory %s: %pm",
+                w->prefix ? w->prefix : "", path, &rv);
+    }
+
+    candidates = apr_array_make(w->ptemp, 1, sizeof(fnames));
+    while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) {
+        /* strip out '.' and '..' */
+        if (strcmp(dirent.name, ".")
+            && strcmp(dirent.name, "..")
+            && (apr_fnmatch(fname, dirent.name,
+                            APR_FNM_PERIOD) == APR_SUCCESS)) {
+            const char *full_path = ap_make_full_path(w->ptemp, path, dirent.name);
+            /* If matching internal to path, and we happen to match something
+             * other than a directory, skip it
+             */
+            if (rest && (dirent.filetype != APR_DIR)) {
+                continue;
+            }
+            fnew = (fnames *) apr_array_push(candidates);
+            fnew->fname = full_path;
+        }
+    }
+
+    apr_dir_close(dirp);
+    if (candidates->nelts != 0) {
+        const char *error;
+
+        qsort((void *) candidates->elts, candidates->nelts,
+              sizeof(fnames), fname_alphasort);
+
+        /*
+         * Now recurse these... we handle errors and subdirectories
+         * via the recursion, which is nice
+         */
+        for (current = 0; current < candidates->nelts; ++current) {
+            fnew = &((fnames *) candidates->elts)[current];
+            if (!rest) {
+                error = ap_dir_nofnmatch(w, fnew->fname);
+            }
+            else {
+                error = ap_dir_fnmatch(w, fnew->fname, rest);
+            }
+            if (error) {
+                return error;
+            }
+        }
+    }
+    else {
+
+        if (!(w->flags & AP_DIR_FLAG_OPTIONAL)) {
+            return apr_psprintf(w->p, "%sNo matches for the wildcard '%s' in '%s', failing",
+                    w->prefix ? w->prefix : "", fname, path);
+        }
+    }
+
+    return NULL;
+}