diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-25 04:41:28 +0000 |
commit | b1a1c1d95059e2fefd7b5671eb110ab690409a84 (patch) | |
tree | 97ecfcc9425e2d09d2cd669594d626a616f324a3 /server/util.c | |
parent | Releasing progress-linux version 2.4.38-3+deb10u10progress5u1. (diff) | |
download | apache2-b1a1c1d95059e2fefd7b5671eb110ab690409a84.tar.xz apache2-b1a1c1d95059e2fefd7b5671eb110ab690409a84.zip |
Merging upstream version 2.4.59.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/util.c')
-rw-r--r-- | server/util.c | 737 |
1 files changed, 609 insertions, 128 deletions
diff --git a/server/util.c b/server/util.c index fd7a0a1..45502b8 100644 --- a/server/util.c +++ b/server/util.c @@ -47,6 +47,7 @@ #include "ap_config.h" #include "apr_base64.h" +#include "apr_fnmatch.h" #include "httpd.h" #include "http_main.h" #include "http_log.h" @@ -74,15 +75,8 @@ */ #include "test_char.h" -/* we assume the folks using this ensure 0 <= c < 256... which means - * you need a cast to (unsigned char) first, you can't just plug a - * 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 char)(c)] & (f)) - /* Win32/NetWare/OS2 need to check for both forward and back slashes - * in ap_getparents() and ap_escape_url. + * in ap_normalize_path() and ap_escape_url(). */ #ifdef CASE_BLIND_FILESYSTEM #define IS_SLASH(s) ((s == '/') || (s == '\\')) @@ -96,6 +90,11 @@ #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. @@ -186,11 +185,9 @@ AP_DECLARE(char *) ap_ht_time(apr_pool_t *p, apr_time_t t, const char *fmt, */ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) { - int x, y; + apr_size_t 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]) @@ -202,6 +199,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; } @@ -210,7 +209,7 @@ AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) AP_DECLARE(int) ap_strcasecmp_match(const char *str, const char *expected) { - int x, y; + apr_size_t x, y; for (x = 0, y = 0; expected[y]; ++y, ++x) { if (!str[x] && expected[y] != '*') @@ -251,10 +250,8 @@ AP_DECLARE(int) ap_os_is_path_absolute(apr_pool_t *p, const char *dir) AP_DECLARE(int) ap_is_matchexp(const char *str) { - int x; - - for (x = 0; str[x]; x++) - if ((str[x] == '*') || (str[x] == '?')) + for (; *str; str++) + if ((*str == '*') || (*str == '?')) return 1; return 0; } @@ -492,85 +489,149 @@ AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, return rc; } +/* Forward declare */ +static char x2c(const char *what); + +#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s)) + /* - * Parse .. so we don't compromise security + * Inspired by mod_jk's jk_servlet_normalize(). */ -AP_DECLARE(void) ap_getparents(char *name) +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) { - char *next; - int l, w, first_dot; + int ret = 1; + apr_size_t l = 1, w = 1, n; + int decode_unreserved = (flags & AP_NORMALIZE_DECODE_UNRESERVED) != 0; + int merge_slashes = (flags & AP_NORMALIZE_MERGE_SLASHES) != 0; - /* Four paseses, as per RFC 1808 */ - /* a) remove ./ path segments */ - for (next = name; *next && (*next != '.'); next++) { - } + if (!IS_SLASH(path[0])) { + /* Besides "OPTIONS *", a request-target should start with '/' + * per RFC 7230 section 5.3, so anything else is invalid. + */ + if (path[0] == '*' && path[1] == '\0') { + return 1; + } + /* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass + * this restriction (e.g. for subrequest file lookups). + */ + if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') { + return 0; + } - l = w = first_dot = next - name; - while (name[l] != '\0') { - if (name[l] == '.' && IS_SLASH(name[l + 1]) - && (l == 0 || IS_SLASH(name[l - 1]))) - l += 2; - else - name[w++] = name[l++]; + l = w = 0; } - /* b) remove trailing . path, segment */ - if (w == 1 && name[0] == '.') - w--; - else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2])) - w--; - name[w] = '\0'; + while (path[l] != '\0') { + /* RFC-3986 section 2.3: + * For consistency, percent-encoded octets in the ranges of + * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), + * period (%2E), underscore (%5F), or tilde (%7E) should [...] + * be decoded to their corresponding unreserved characters by + * URI normalizers. + */ + if (decode_unreserved && path[l] == '%') { + if (apr_isxdigit(path[l + 1]) && apr_isxdigit(path[l + 2])) { + const char c = x2c(&path[l + 1]); + if (TEST_CHAR(c, T_URI_UNRESERVED)) { + /* Replace last char and fall through as the current + * read position */ + l += 2; + path[l] = c; + } + } + else { + /* Invalid encoding */ + ret = 0; + } + } - /* c) remove all xx/../ segments. (including leading ../ and /../) */ - l = first_dot; + if (w == 0 || IS_SLASH(path[w - 1])) { + /* Collapse ///// sequences to / */ + if (merge_slashes && IS_SLASH(path[l])) { + do { + l++; + } while (IS_SLASH(path[l])); + continue; + } - while (name[l] != '\0') { - if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2]) - && (l == 0 || IS_SLASH(name[l - 1]))) { - int m = l + 3, n; + if (path[l] == '.') { + /* Remove /./ segments */ + if (IS_SLASH_OR_NUL(path[l + 1])) { + l++; + if (path[l]) { + l++; + } + continue; + } - l = l - 2; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; + /* Remove /xx/../ segments (or /xx/.%2e/ when + * AP_NORMALIZE_DECODE_UNRESERVED is set since we + * decoded only the first dot above). + */ + n = l + 1; + if ((path[n] == '.' || (decode_unreserved + && path[n] == '%' + && path[++n] == '2' + && (path[++n] == 'e' + || path[n] == 'E'))) + && IS_SLASH_OR_NUL(path[n + 1])) { + /* Wind w back to remove the previous segment */ + if (w > 1) { + do { + w--; + } while (w && !IS_SLASH(path[w - 1])); + } + else { + /* Already at root, ignore and return a failure + * if asked to. + */ + if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) { + ret = 0; + } + } + + /* Move l forward to the next segment */ + l = n + 1; + if (path[l]) { + l++; + } + continue; + } } - else - l = 0; - n = l; - while ((name[n] = name[m])) - (++n, ++m); } - else - ++l; + + path[w++] = path[l++]; } + path[w] = '\0'; - /* d) remove trailing xx/.. segment. */ - if (l == 2 && name[0] == '.' && name[1] == '.') + return ret; +} + +/* + * Parse .. so we don't compromise security + */ +AP_DECLARE(void) ap_getparents(char *name) +{ + if (!ap_normalize_path(name, AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_ALLOW_RELATIVE)) { name[0] = '\0'; - else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.' - && IS_SLASH(name[l - 3])) { - l = l - 4; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; - } - else - l = 0; - name[l] = '\0'; } } -AP_DECLARE(void) ap_no2slash(char *name) +AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path) { + char *d, *s; + if (!*name) { + return; + } + s = d = name; #ifdef HAVE_UNC_PATHS /* Check for UNC names. Leave leading two slashes. */ - if (s[0] == '/' && s[1] == '/') + if (is_fs_path && s[0] == '/' && s[1] == '/') *d++ = *s++; #endif @@ -587,6 +648,10 @@ AP_DECLARE(void) ap_no2slash(char *name) *d = '\0'; } +AP_DECLARE(void) ap_no2slash(char *name) +{ + ap_no2slash_ex(name, 1); +} /* * copy at most n leading directories of s into d @@ -934,7 +999,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 */ @@ -1691,7 +1756,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; } @@ -1701,14 +1766,13 @@ AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok) } } - -AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, - const char *tok) +static const char *find_last_token(apr_pool_t *p, const char *line, + const char *tok) { int llen, tlen, lidx; if (!line) - return 0; + return NULL; llen = strlen(line); tlen = strlen(tok); @@ -1716,9 +1780,44 @@ AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, if (lidx < 0 || (lidx > 0 && !(apr_isspace(line[lidx - 1]) || line[lidx - 1] == ','))) + return NULL; + + if (ap_cstr_casecmpn(&line[lidx], tok, tlen) == 0) { + return &line[lidx]; + } + return NULL; +} + +AP_DECLARE(int) ap_find_last_token(apr_pool_t *p, const char *line, + const char *tok) +{ + return find_last_token(p, line, tok) != NULL; +} + +AP_DECLARE(int) ap_is_chunked(apr_pool_t *p, const char *line) +{ + const char *s; + + if (!line) return 0; + if (!ap_cstr_casecmp(line, "chunked")) { + return 1; + } + + s = find_last_token(p, line, "chunked"); - return (strncasecmp(&line[lidx], tok, tlen) == 0); + if (!s) return 0; + + /* eat spaces right-to-left to see what precedes "chunked" */ + while (--s > line) { + if (*s != ' ') break; + } + + /* found delim, or leading ws (input wasn't parsed by httpd as a header) */ + if (*s == ',' || *s == ' ') { + return 1; + } + return 0; } AP_DECLARE(char *) ap_escape_shell_cmd(apr_pool_t *p, const char *str) @@ -1786,8 +1885,12 @@ static char x2c(const char *what) * decoding %00 or a forbidden character returns HTTP_NOT_FOUND */ -static int unescape_url(char *url, const char *forbid, const char *reserved) +static int unescape_url(char *url, const char *forbid, const char *reserved, + unsigned int flags) { + const int keep_slashes = (flags & AP_UNESCAPE_URL_KEEP_SLASHES) != 0, + forbid_slashes = (flags & AP_UNESCAPE_URL_FORBID_SLASHES) != 0, + keep_unreserved = (flags & AP_UNESCAPE_URL_KEEP_UNRESERVED) != 0; int badesc, badpath; char *x, *y; @@ -1812,12 +1915,16 @@ static int unescape_url(char *url, const char *forbid, const char *reserved) char decoded; decoded = x2c(y + 1); if ((decoded == '\0') + || (forbid_slashes && IS_SLASH(decoded)) || (forbid && ap_strchr_c(forbid, decoded))) { badpath = 1; *x = decoded; y += 2; } - else if (reserved && ap_strchr_c(reserved, decoded)) { + else if ((keep_unreserved && TEST_CHAR(decoded, + T_URI_UNRESERVED)) + || (keep_slashes && IS_SLASH(decoded)) + || (reserved && ap_strchr_c(reserved, decoded))) { *x++ = *y++; *x++ = *y++; *x = *y; @@ -1843,19 +1950,24 @@ static int unescape_url(char *url, const char *forbid, const char *reserved) AP_DECLARE(int) ap_unescape_url(char *url) { /* Traditional */ - return unescape_url(url, SLASHES, NULL); + return unescape_url(url, SLASHES, NULL, 0); } AP_DECLARE(int) ap_unescape_url_keep2f(char *url, int decode_slashes) { /* AllowEncodedSlashes (corrected) */ if (decode_slashes) { /* no chars reserved */ - return unescape_url(url, NULL, NULL); + return unescape_url(url, NULL, NULL, 0); } else { /* reserve (do not decode) encoded slashes */ - return unescape_url(url, NULL, SLASHES); + return unescape_url(url, NULL, SLASHES, 0); } } +AP_DECLARE(int) ap_unescape_url_ex(char *url, unsigned int flags) +{ + return unescape_url(url, NULL, NULL, flags); +} + #ifdef NEW_APIS /* IFDEF these out until they've been thought through. * Just a germ of an API extension for now @@ -1865,7 +1977,7 @@ AP_DECLARE(int) ap_unescape_url_proxy(char *url) /* leave RFC1738 reserved characters intact, * so proxied URLs * don't get mangled. Where does that leave encoded '&' ? */ - return unescape_url(url, NULL, "/;?"); + return unescape_url(url, NULL, "/;?", 0); } AP_DECLARE(int) ap_unescape_url_reserved(char *url, const char *reserved) { @@ -1887,7 +1999,7 @@ AP_DECLARE(int) ap_unescape_urlencoded(char *query) } /* unescape everything else */ - return unescape_url(query, NULL, NULL); + return unescape_url(query, NULL, NULL, 0); } AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, @@ -1903,7 +2015,7 @@ AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, AP_DECLARE(int) ap_unescape_all(char *url) { - return unescape_url(url, NULL, NULL); + return unescape_url(url, NULL, NULL, 0); } /* c2x takes an unsigned, and expects the caller has guaranteed that @@ -2029,11 +2141,14 @@ AP_DECLARE(char *) ap_escape_urlencoded(apr_pool_t *p, const char *buffer) AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) { - int i, j; + apr_size_t i, j; char *x; /* first, count the number of extra characters */ - for (i = 0, j = 0; s[i] != '\0'; i++) + for (i = 0, j = 0; s[i] != '\0'; i++) { + if (i + j > APR_SIZE_MAX - 6) { + abort(); + } if (s[i] == '<' || s[i] == '>') j += 3; else if (s[i] == '&') @@ -2042,6 +2157,7 @@ AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) j += 5; else if (toasc && !apr_isascii(s[i])) j += 5; + } if (j == 0) return apr_pstrmemdup(p, s, i); @@ -2372,11 +2488,9 @@ char *ap_get_local_host(apr_pool_t *a) AP_DECLARE(char *) ap_pbase64decode(apr_pool_t *p, const char *bufcoded) { char *decoded; - int l; - decoded = (char *) apr_palloc(p, 1 + apr_base64_decode_len(bufcoded)); - l = apr_base64_decode(decoded, bufcoded); - decoded[l] = '\0'; /* make binary sequence into string */ + decoded = (char *) apr_palloc(p, apr_base64_decode_len(bufcoded)); + apr_base64_decode(decoded, bufcoded); return decoded; } @@ -2386,9 +2500,8 @@ AP_DECLARE(char *) ap_pbase64encode(apr_pool_t *p, char *string) char *encoded; int l = strlen(string); - encoded = (char *) apr_palloc(p, 1 + apr_base64_encode_len(l)); - l = apr_base64_encode(encoded, string, l); - encoded[l] = '\0'; /* make binary sequence into string */ + encoded = (char *) apr_palloc(p, apr_base64_encode_len(l)); + apr_base64_encode(encoded, string, l); return encoded; } @@ -2421,7 +2534,7 @@ AP_DECLARE(void) ap_content_type_tolower(char *str) */ AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) { - int newlen = 0; + apr_size_t size, extra = 0; const char *inchr = instring; char *outchr, *outstring; @@ -2430,21 +2543,41 @@ AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) * string up by an extra byte each time we find an unescaped ". */ while (*inchr != '\0') { - newlen++; if (*inchr == '"') { - newlen++; + extra++; } /* * If we find a slosh, and it's not the last byte in the string, * it's escaping something - advance past both bytes. */ - if ((*inchr == '\\') && (inchr[1] != '\0')) { + else if ((*inchr == '\\') && (inchr[1] != '\0')) { inchr++; - newlen++; } inchr++; } - outstring = apr_palloc(p, newlen + 1); + + if (!extra) { + return apr_pstrdup(p, instring); + } + + /* How large will the string become, once we escaped all the quotes? + * The tricky cases are + * - an `instring` that is already longer than `ptrdiff_t` + * can hold (which is an undefined case in C, as C defines ptrdiff_t as + * a signed difference between pointers into the same array and one index + * beyond). + * - an `instring` that, including the `extra` chars we want to add, becomes + * even larger than apr_size_t can handle. + * Since this function was not designed to ever return NULL for failure, we + * can only trigger a hard assertion failure. It seems more a programming + * mistake (or failure to verify the input causing this) that leads to this + * situation. + */ + ap_assert(inchr - instring > 0); + size = ((apr_size_t)(inchr - instring)) + 1; + ap_assert(size + extra > size); + + outstring = apr_palloc(p, size + extra); inchr = instring; outchr = outstring; /* @@ -2452,16 +2585,13 @@ AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) * in front of every " that doesn't already have one. */ while (*inchr != '\0') { - if ((*inchr == '\\') && (inchr[1] != '\0')) { - *outchr++ = *inchr++; - *outchr++ = *inchr++; - } if (*inchr == '"') { *outchr++ = '\\'; } - if (*inchr != '\0') { + else if ((*inchr == '\\') && (inchr[1] != '\0')) { *outchr++ = *inchr++; } + *outchr++ = *inchr++; } *outchr = '\0'; return outstring; @@ -2499,6 +2629,7 @@ AP_DECLARE(char *) ap_append_pid(apr_pool_t *p, const char *string, * in timeout_parameter. * @return Status value indicating whether the parsing was successful or not. */ +#define CHECK_OVERFLOW(a, b) if (a > b) return APR_EGENERAL AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( const char *timeout_parameter, apr_interval_time_t *timeout, @@ -2507,6 +2638,7 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( char *endp; const char *time_str; apr_int64_t tout; + apr_uint64_t check; tout = apr_strtoi64(timeout_parameter, &endp, 10); if (errno) { @@ -2519,24 +2651,32 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( time_str = endp; } + if (tout < 0) { + return APR_EGENERAL; + } + switch (*time_str) { /* Time is in seconds */ case 's': - *timeout = (apr_interval_time_t) apr_time_from_sec(tout); + CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX)); + check = apr_time_from_sec(tout); break; - case 'h': /* Time is in hours */ - *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 3600); + case 'h': + CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 3600)); + check = apr_time_from_sec(tout * 3600); break; case 'm': switch (*(++time_str)) { /* Time is in milliseconds */ case 's': - *timeout = (apr_interval_time_t) tout * 1000; + CHECK_OVERFLOW(tout, apr_time_as_msec(APR_INT64_MAX)); + check = apr_time_from_msec(tout); break; /* Time is in minutes */ case 'i': - *timeout = (apr_interval_time_t) apr_time_from_sec(tout * 60); + CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 60)); + check = apr_time_from_sec(tout * 60); break; default: return APR_EGENERAL; @@ -2545,8 +2685,20 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( default: return APR_EGENERAL; } + + *timeout = (apr_interval_time_t)check; return APR_SUCCESS; } +#undef CHECK_OVERFLOW + +AP_DECLARE(int) ap_parse_strict_length(apr_off_t *len, const char *str) +{ + char *end; + + return (apr_isdigit(*str) + && apr_strtoff(len, str, &end, 10) == APR_SUCCESS + && *end == '\0'); +} /** * Determine if a request has a request body or not. @@ -2557,20 +2709,13 @@ AP_DECLARE(apr_status_t) ap_timeout_parameter_parse( AP_DECLARE(int) ap_request_has_body(request_rec *r) { apr_off_t cl; - char *estr; const char *cls; - int has_body; - has_body = (!r->header_only - && (r->kept_body - || apr_table_get(r->headers_in, "Transfer-Encoding") - || ( (cls = apr_table_get(r->headers_in, "Content-Length")) - && (apr_strtoff(&cl, cls, &estr, 10) == APR_SUCCESS) - && (!*estr) - && (cl > 0) ) - ) - ); - return has_body; + return (!r->header_only + && (r->kept_body + || apr_table_get(r->headers_in, "Transfer-Encoding") + || ((cls = apr_table_get(r->headers_in, "Content-Length")) + && ap_parse_strict_length(&cl, cls) && cl > 0))); } AP_DECLARE_NONSTD(apr_status_t) ap_pool_cleanup_set_null(void *data_) @@ -2669,7 +2814,7 @@ AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, /* 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); } @@ -2754,12 +2899,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; @@ -2820,7 +2964,7 @@ static apr_status_t varbuf_cleanup(void *info_) return APR_SUCCESS; } -const char nul = '\0'; +static const char nul = '\0'; static char * const varbuf_empty = (char *)&nul; AP_DECLARE(void) ap_varbuf_init(apr_pool_t *p, struct ap_varbuf *vb, @@ -2889,6 +3033,11 @@ AP_DECLARE(void) ap_varbuf_grow(struct ap_varbuf *vb, apr_size_t new_len) /* The required block is rather larger. Use allocator directly so that * the memory can be freed independently from the pool. */ allocator = apr_pool_allocator_get(vb->pool); + /* Happens if APR was compiled with APR_POOL_DEBUG */ + if (allocator == NULL) { + apr_allocator_create(&allocator); + ap_assert(allocator != NULL); + } if (new_len <= VARBUF_MAX_SIZE) new_node = apr_allocator_alloc(allocator, new_len + APR_ALIGN_DEFAULT(sizeof(*new_info))); @@ -2995,7 +3144,7 @@ AP_DECLARE(apr_status_t) ap_varbuf_regsub(struct ap_varbuf *vb, static const char * const oom_message = "[crit] Memory allocation failed, " "aborting process." APR_EOL_STR; -AP_DECLARE(void) ap_abort_on_oom() +AP_DECLARE(void) ap_abort_on_oom(void) { int written, count = strlen(oom_message); const char *buf = oom_message; @@ -3035,6 +3184,135 @@ AP_DECLARE(void *) ap_realloc(void *ptr, size_t size) return p; } +#if APR_HAS_THREADS + +#if APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) + +#define ap_thread_current_create apr_thread_current_create + +#else /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */ + +#if AP_HAS_THREAD_LOCAL + +struct thread_ctx { + apr_thread_start_t func; + void *data; +}; + +static AP_THREAD_LOCAL apr_thread_t *current_thread = NULL; + +static void *APR_THREAD_FUNC thread_start(apr_thread_t *thread, void *data) +{ + struct thread_ctx *ctx = data; + + current_thread = thread; + return ctx->func(thread, ctx->data); +} + +AP_DECLARE(apr_status_t) ap_thread_create(apr_thread_t **thread, + apr_threadattr_t *attr, + apr_thread_start_t func, + void *data, apr_pool_t *pool) +{ + struct thread_ctx *ctx = apr_palloc(pool, sizeof(*ctx)); + + ctx->func = func; + ctx->data = data; + return apr_thread_create(thread, attr, thread_start, ctx, pool); +} + +#endif /* AP_HAS_THREAD_LOCAL */ + +AP_DECLARE(apr_status_t) ap_thread_current_create(apr_thread_t **current, + apr_threadattr_t *attr, + apr_pool_t *pool) +{ + apr_status_t rv; + apr_abortfunc_t abort_fn = apr_pool_abort_get(pool); + apr_allocator_t *allocator; + apr_os_thread_t osthd; + apr_pool_t *p; + + *current = ap_thread_current(); + if (*current) { + return APR_EEXIST; + } + + rv = apr_allocator_create(&allocator); + if (rv != APR_SUCCESS) { + if (abort_fn) + abort_fn(rv); + return rv; + } + rv = apr_pool_create_unmanaged_ex(&p, abort_fn, allocator); + if (rv != APR_SUCCESS) { + apr_allocator_destroy(allocator); + return rv; + } + apr_allocator_owner_set(allocator, p); + + osthd = apr_os_thread_current(); + rv = apr_os_thread_put(current, &osthd, p); + if (rv != APR_SUCCESS) { + apr_pool_destroy(p); + return rv; + } + +#if AP_HAS_THREAD_LOCAL + current_thread = *current; +#endif + return APR_SUCCESS; +} + +AP_DECLARE(void) ap_thread_current_after_fork(void) +{ +#if AP_HAS_THREAD_LOCAL + current_thread = NULL; +#endif +} + +AP_DECLARE(apr_thread_t *) ap_thread_current(void) +{ +#if AP_HAS_THREAD_LOCAL + return current_thread; +#else + return NULL; +#endif +} + +#endif /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */ + +static apr_status_t main_thread_cleanup(void *arg) +{ + apr_thread_t *thd = arg; + apr_pool_destroy(apr_thread_pool_get(thd)); + return APR_SUCCESS; +} + +AP_DECLARE(apr_status_t) ap_thread_main_create(apr_thread_t **thread, + apr_pool_t *pool) +{ + apr_status_t rv; + apr_threadattr_t *attr = NULL; + + /* Create an apr_thread_t for the main child thread to set up its Thread + * Local Storage. Since it's detached and won't apr_thread_exit(), destroy + * its pool before exiting via a cleanup of the given pool. + */ + if ((rv = apr_threadattr_create(&attr, pool)) + || (rv = apr_threadattr_detach_set(attr, 1)) + || (rv = ap_thread_current_create(thread, attr, pool))) { + *thread = NULL; + return rv; + } + + apr_pool_cleanup_register(pool, *thread, main_thread_cleanup, + apr_pool_cleanup_null); + return APR_SUCCESS; +} + +#endif /* APR_HAS_THREADS */ + AP_DECLARE(void) ap_get_sload(ap_sload_t *ld) { int i, j, server_limit, thread_limit; @@ -3307,3 +3585,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; +} |