]> granicus.if.org Git - apache/blobdiff - server/util.c
fr doc rebuild.
[apache] / server / util.c
index a80c0ecfaba1d7569497f1b159dcbc24b0e26727..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
  * it sans any parameters; e.g., strip off any ';charset=foo' and the like.
@@ -190,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])
@@ -203,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;
     }
@@ -598,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  ==> /
@@ -851,7 +857,7 @@ AP_DECLARE(char *) ap_getword_conf2(apr_pool_t *p, const char **line)
                 break;
             if (*strend == '{')
                 ++count;
-            if (*strend == '\\' && strend[1] && strend[1] == '\\') {
+            if (*strend == '\\' && strend[1] == '\\') {
                 ++strend;
             }
             ++strend;
@@ -1526,7 +1532,7 @@ AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p,
     while (!string_end) {
         const unsigned char c = (unsigned char)*cur;
 
-        if (!TEST_CHAR(c, T_HTTP_TOKEN_STOP) && c != '\0') {
+        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) */
@@ -1594,6 +1600,37 @@ AP_DECLARE(const char *) ap_parse_token_list_strict(apr_pool_t *p,
     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
@@ -1649,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) {
@@ -1765,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, '%');
@@ -2194,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;
@@ -2367,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;
@@ -2470,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
@@ -2508,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:
@@ -2559,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_;
@@ -2649,9 +2768,7 @@ 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;
 
@@ -2718,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) {
@@ -2759,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;
@@ -3316,7 +3415,7 @@ static const short ucharmap[] = {
 };
 #endif
 
-AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2)
+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;
@@ -3351,3 +3450,206 @@ AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n)
     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;
+}