summaryrefslogtreecommitdiffstats
path: root/server/util.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-25 04:41:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-25 04:41:27 +0000
commitc54018b07a9085c0a3aedbc2bd01a85a3b3e20cf (patch)
treef6e1d6fcf9f6db3794c418b2f89ecf9e08ff41c8 /server/util.c
parentAdding debian version 2.4.38-3+deb10u10. (diff)
downloadapache2-c54018b07a9085c0a3aedbc2bd01a85a3b3e20cf.tar.xz
apache2-c54018b07a9085c0a3aedbc2bd01a85a3b3e20cf.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.c737
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;
+}