| /* Licensed to the Apache Software Foundation (ASF) under one or more | |
| * contributor license agreements. See the NOTICE file distributed with | |
| * this work for additional information regarding copyright ownership. | |
| * The ASF licenses this file to You under the Apache License, Version 2.0 | |
| * (the "License"); you may not use this file except in compliance with | |
| * the License. You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| /* | |
| * util.c: string utility things | |
| * | |
| * 3/21/93 Rob McCool | |
| * 1995-96 Many changes by the Apache Software Foundation | |
| * | |
| */ | |
| /* Debugging aid: | |
| * #define DEBUG to trace all cfg_open*()/cfg_closefile() calls | |
| * #define DEBUG_CFG_LINES to trace every line read from the config files | |
| */ | |
| #include "apr.h" | |
| #include "apr_strings.h" | |
| #include "apr_lib.h" | |
| #include "apr_md5.h" /* for apr_password_validate */ | |
| #define APR_WANT_STDIO | |
| #define APR_WANT_STRFUNC | |
| #include "apr_want.h" | |
| #if APR_HAVE_UNISTD_H | |
| #include <unistd.h> | |
| #endif | |
| #if APR_HAVE_PROCESS_H | |
| #include <process.h> /* for getpid() on Win32 */ | |
| #endif | |
| #if APR_HAVE_NETDB_H | |
| #include <netdb.h> /* for gethostbyname() */ | |
| #endif | |
| #include "ap_config.h" | |
| #include "apr_base64.h" | |
| #include "apr_fnmatch.h" | |
| #include "httpd.h" | |
| #include "http_main.h" | |
| #include "http_log.h" | |
| #include "http_protocol.h" | |
| #include "http_config.h" | |
| #include "http_core.h" | |
| #include "util_ebcdic.h" | |
| #include "util_varbuf.h" | |
| #ifdef HAVE_PWD_H | |
| #include <pwd.h> | |
| #endif | |
| #ifdef HAVE_GRP_H | |
| #include <grp.h> | |
| #endif | |
| #ifdef HAVE_SYS_LOADAVG_H | |
| #include <sys/loadavg.h> | |
| #endif | |
| #include "ap_mpm.h" | |
| #include "mpm_common.h" /* for ap_max_mem_free */ | |
| /* A bunch of functions in util.c scan strings looking for certain characters. | |
| * To make that more efficient we encode a lookup table. The test_char_table | |
| * is generated automatically by gen_test_char.c. | |
| */ | |
| #include "test_char.h" | |
| /* we know core's module_index is 0 */ | |
| #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. | |
| */ | |
| AP_DECLARE(char *) ap_field_noparam(apr_pool_t *p, const char *intype) | |
| { | |
| const char *semi; | |
| if (intype == NULL) return NULL; | |
| semi = ap_strchr_c(intype, ';'); | |
| if (semi == NULL) { | |
| return apr_pstrdup(p, intype); | |
| } | |
| else { | |
| while ((semi > intype) && apr_isspace(semi[-1])) { | |
| semi--; | |
| } | |
| return apr_pstrmemdup(p, intype, semi - intype); | |
| } | |
| } | |
| AP_DECLARE(char *) ap_ht_time(apr_pool_t *p, apr_time_t t, const char *fmt, | |
| int gmt) | |
| { | |
| apr_size_t retcode; | |
| char ts[MAX_STRING_LEN]; | |
| char tf[MAX_STRING_LEN]; | |
| apr_time_exp_t xt; | |
| if (gmt) { | |
| const char *f; | |
| char *strp; | |
| apr_time_exp_gmt(&xt, t); | |
| /* Convert %Z to "GMT" and %z to "+0000"; | |
| * on hosts that do not have a time zone string in struct tm, | |
| * strftime must assume its argument is local time. | |
| */ | |
| for(strp = tf, f = fmt; strp < tf + sizeof(tf) - 6 && (*strp = *f) | |
| ; f++, strp++) { | |
| if (*f != '%') continue; | |
| switch (f[1]) { | |
| case '%': | |
| *++strp = *++f; | |
| break; | |
| case 'Z': | |
| *strp++ = 'G'; | |
| *strp++ = 'M'; | |
| *strp = 'T'; | |
| f++; | |
| break; | |
| case 'z': /* common extension */ | |
| *strp++ = '+'; | |
| *strp++ = '0'; | |
| *strp++ = '0'; | |
| *strp++ = '0'; | |
| *strp = '0'; | |
| f++; | |
| break; | |
| } | |
| } | |
| *strp = '\0'; | |
| fmt = tf; | |
| } | |
| else { | |
| apr_time_exp_lt(&xt, t); | |
| } | |
| /* check return code? */ | |
| apr_strftime(ts, &retcode, MAX_STRING_LEN, fmt, &xt); | |
| ts[MAX_STRING_LEN - 1] = '\0'; | |
| return apr_pstrdup(p, ts); | |
| } | |
| /* Roy owes Rob beer. */ | |
| /* Rob owes Roy dinner. */ | |
| /* These legacy comments would make a lot more sense if Roy hadn't | |
| * replaced the old later_than() routine with util_date.c. | |
| * | |
| * Well, okay, they still wouldn't make any sense. | |
| */ | |
| /* Match = 0, NoMatch = 1, Abort = -1 | |
| * Based loosely on sections of wildmat.c by Rich Salz | |
| * Hmmm... shouldn't this really go component by component? | |
| */ | |
| AP_DECLARE(int) ap_strcmp_match(const char *str, const char *expected) | |
| { | |
| apr_size_t x, y; | |
| for (x = 0, y = 0; expected[y]; ++y, ++x) { | |
| if (expected[y] == '*') { | |
| while (expected[++y] == '*'); | |
| if (!expected[y]) | |
| return 0; | |
| while (str[x]) { | |
| int ret; | |
| if ((ret = ap_strcmp_match(&str[x++], &expected[y])) != 1) | |
| return ret; | |
| } | |
| return -1; | |
| } | |
| else if (!str[x]) | |
| return -1; | |
| else if ((expected[y] != '?') && (str[x] != expected[y])) | |
| return 1; | |
| } | |
| return (str[x] != '\0'); | |
| } | |
| AP_DECLARE(int) ap_strcasecmp_match(const char *str, const char *expected) | |
| { | |
| 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]) | |
| return 0; | |
| while (str[x]) { | |
| int ret; | |
| if ((ret = ap_strcasecmp_match(&str[x++], &expected[y])) != 1) | |
| return ret; | |
| } | |
| return -1; | |
| } | |
| else if (expected[y] != '?' | |
| && apr_tolower(str[x]) != apr_tolower(expected[y])) | |
| return 1; | |
| } | |
| return (str[x] != '\0'); | |
| } | |
| /* We actually compare the canonical root to this root, (but we don't | |
| * waste time checking the case), since every use of this function in | |
| * httpd-2.1 tests if the path is 'proper', meaning we've already passed | |
| * it through apr_filepath_merge, or we haven't. | |
| */ | |
| AP_DECLARE(int) ap_os_is_path_absolute(apr_pool_t *p, const char *dir) | |
| { | |
| const char *newpath; | |
| const char *ourdir = dir; | |
| if (apr_filepath_root(&newpath, &dir, 0, p) != APR_SUCCESS | |
| || strncmp(newpath, ourdir, strlen(newpath)) != 0) { | |
| return 0; | |
| } | |
| return 1; | |
| } | |
| AP_DECLARE(int) ap_is_matchexp(const char *str) | |
| { | |
| for (; *str; str++) | |
| if ((*str == '*') || (*str == '?')) | |
| return 1; | |
| return 0; | |
| } | |
| /* | |
| * Here's a pool-based interface to the POSIX-esque ap_regcomp(). | |
| * Note that we return ap_regex_t instead of being passed one. | |
| * The reason is that if you use an already-used ap_regex_t structure, | |
| * the memory that you've already allocated gets forgotten, and | |
| * regfree() doesn't clear it. So we don't allow it. | |
| */ | |
| static apr_status_t regex_cleanup(void *preg) | |
| { | |
| ap_regfree((ap_regex_t *) preg); | |
| return APR_SUCCESS; | |
| } | |
| AP_DECLARE(ap_regex_t *) ap_pregcomp(apr_pool_t *p, const char *pattern, | |
| int cflags) | |
| { | |
| ap_regex_t *preg = apr_palloc(p, sizeof *preg); | |
| int err = ap_regcomp(preg, pattern, cflags); | |
| if (err) { | |
| if (err == AP_REG_ESPACE) | |
| ap_abort_on_oom(); | |
| return NULL; | |
| } | |
| apr_pool_cleanup_register(p, (void *) preg, regex_cleanup, | |
| apr_pool_cleanup_null); | |
| return preg; | |
| } | |
| AP_DECLARE(void) ap_pregfree(apr_pool_t *p, ap_regex_t *reg) | |
| { | |
| ap_regfree(reg); | |
| apr_pool_cleanup_kill(p, (void *) reg, regex_cleanup); | |
| } | |
| /* | |
| * Similar to standard strstr() but we ignore case in this version. | |
| * Based on the strstr() implementation further below. | |
| */ | |
| AP_DECLARE(char *) ap_strcasestr(const char *s1, const char *s2) | |
| { | |
| char *p1, *p2; | |
| if (*s2 == '\0') { | |
| /* an empty s2 */ | |
| return((char *)s1); | |
| } | |
| while(1) { | |
| for ( ; (*s1 != '\0') && (apr_tolower(*s1) != apr_tolower(*s2)); s1++); | |
| if (*s1 == '\0') { | |
| return(NULL); | |
| } | |
| /* found first character of s2, see if the rest matches */ | |
| p1 = (char *)s1; | |
| p2 = (char *)s2; | |
| for (++p1, ++p2; apr_tolower(*p1) == apr_tolower(*p2); ++p1, ++p2) { | |
| if (*p1 == '\0') { | |
| /* both strings ended together */ | |
| return((char *)s1); | |
| } | |
| } | |
| if (*p2 == '\0') { | |
| /* second string ended, a match */ | |
| break; | |
| } | |
| /* didn't find a match here, try starting at next character in s1 */ | |
| s1++; | |
| } | |
| return((char *)s1); | |
| } | |
| /* | |
| * Returns an offsetted pointer in bigstring immediately after | |
| * prefix. Returns bigstring if bigstring doesn't start with | |
| * prefix or if prefix is longer than bigstring while still matching. | |
| * NOTE: pointer returned is relative to bigstring, so we | |
| * can use standard pointer comparisons in the calling function | |
| * (eg: test if ap_stripprefix(a,b) == a) | |
| */ | |
| AP_DECLARE(const char *) ap_stripprefix(const char *bigstring, | |
| const char *prefix) | |
| { | |
| const char *p1; | |
| if (*prefix == '\0') | |
| return bigstring; | |
| p1 = bigstring; | |
| while (*p1 && *prefix) { | |
| if (*p1++ != *prefix++) | |
| return bigstring; | |
| } | |
| if (*prefix == '\0') | |
| return p1; | |
| /* hit the end of bigstring! */ | |
| return bigstring; | |
| } | |
| /* This function substitutes for $0-$9, filling in regular expression | |
| * submatches. Pass it the same nmatch and pmatch arguments that you | |
| * passed ap_regexec(). pmatch should not be greater than the maximum number | |
| * of subexpressions - i.e. one more than the re_nsub member of ap_regex_t. | |
| * | |
| * nmatch must be <=AP_MAX_REG_MATCH (10). | |
| * | |
| * input should be the string with the $-expressions, source should be the | |
| * string that was matched against. | |
| * | |
| * It returns the substituted string, or NULL if a vbuf is used. | |
| * On errors, returns the orig string. | |
| * | |
| * Parts of this code are based on Henry Spencer's regsub(), from his | |
| * AT&T V8 regexp package. | |
| */ | |
| static apr_status_t regsub_core(apr_pool_t *p, char **result, | |
| struct ap_varbuf *vb, const char *input, | |
| const char *source, apr_size_t nmatch, | |
| ap_regmatch_t pmatch[], apr_size_t maxlen) | |
| { | |
| const char *src = input; | |
| char *dst; | |
| char c; | |
| apr_size_t no; | |
| apr_size_t len = 0; | |
| AP_DEBUG_ASSERT((result && p && !vb) || (vb && !p && !result)); | |
| if (!source || nmatch>AP_MAX_REG_MATCH) | |
| return APR_EINVAL; | |
| if (!nmatch) { | |
| len = strlen(src); | |
| if (maxlen > 0 && len >= maxlen) | |
| return APR_ENOMEM; | |
| if (!vb) { | |
| *result = apr_pstrmemdup(p, src, len); | |
| return APR_SUCCESS; | |
| } | |
| else { | |
| ap_varbuf_strmemcat(vb, src, len); | |
| return APR_SUCCESS; | |
| } | |
| } | |
| /* First pass, find the size */ | |
| while ((c = *src++) != '\0') { | |
| if (c == '$' && apr_isdigit(*src)) | |
| no = *src++ - '0'; | |
| else | |
| no = AP_MAX_REG_MATCH; | |
| if (no >= AP_MAX_REG_MATCH) { /* Ordinary character. */ | |
| if (c == '\\' && *src) | |
| src++; | |
| len++; | |
| } | |
| else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { | |
| if (APR_SIZE_MAX - len <= pmatch[no].rm_eo - pmatch[no].rm_so) | |
| return APR_ENOMEM; | |
| len += pmatch[no].rm_eo - pmatch[no].rm_so; | |
| } | |
| } | |
| if (len >= maxlen && maxlen > 0) | |
| return APR_ENOMEM; | |
| if (!vb) { | |
| *result = dst = apr_palloc(p, len + 1); | |
| } | |
| else { | |
| if (vb->strlen == AP_VARBUF_UNKNOWN) | |
| vb->strlen = strlen(vb->buf); | |
| ap_varbuf_grow(vb, vb->strlen + len); | |
| dst = vb->buf + vb->strlen; | |
| vb->strlen += len; | |
| } | |
| /* Now actually fill in the string */ | |
| src = input; | |
| while ((c = *src++) != '\0') { | |
| if (c == '$' && apr_isdigit(*src)) | |
| no = *src++ - '0'; | |
| else | |
| no = AP_MAX_REG_MATCH; | |
| if (no >= AP_MAX_REG_MATCH) { /* Ordinary character. */ | |
| if (c == '\\' && *src) | |
| c = *src++; | |
| *dst++ = c; | |
| } | |
| else if (no < nmatch && pmatch[no].rm_so < pmatch[no].rm_eo) { | |
| len = pmatch[no].rm_eo - pmatch[no].rm_so; | |
| memcpy(dst, source + pmatch[no].rm_so, len); | |
| dst += len; | |
| } | |
| } | |
| *dst = '\0'; | |
| return APR_SUCCESS; | |
| } | |
| #ifndef AP_PREGSUB_MAXLEN | |
| #define AP_PREGSUB_MAXLEN (HUGE_STRING_LEN * 8) | |
| #endif | |
| AP_DECLARE(char *) ap_pregsub(apr_pool_t *p, const char *input, | |
| const char *source, apr_size_t nmatch, | |
| ap_regmatch_t pmatch[]) | |
| { | |
| char *result; | |
| apr_status_t rc = regsub_core(p, &result, NULL, input, source, nmatch, | |
| pmatch, AP_PREGSUB_MAXLEN); | |
| if (rc != APR_SUCCESS) | |
| result = NULL; | |
| return result; | |
| } | |
| AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, | |
| const char *input, const char *source, | |
| apr_size_t nmatch, ap_regmatch_t pmatch[], | |
| apr_size_t maxlen) | |
| { | |
| apr_status_t rc = regsub_core(p, result, NULL, input, source, nmatch, | |
| pmatch, maxlen); | |
| if (rc != APR_SUCCESS) | |
| *result = NULL; | |
| return rc; | |
| } | |
| /* Forward declare */ | |
| static char x2c(const char *what); | |
| #define IS_SLASH_OR_NUL(s) (s == '\0' || AP_IS_SLASH(s)) | |
| /* | |
| * Inspired by mod_jk's jk_servlet_normalize(). | |
| */ | |
| AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) | |
| { | |
| 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; | |
| if (!AP_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 = 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; | |
| } | |
| } | |
| if (w == 0 || AP_IS_SLASH(path[w - 1])) { | |
| /* Collapse ///// sequences to / */ | |
| if (merge_slashes && AP_IS_SLASH(path[l])) { | |
| do { | |
| l++; | |
| } while (AP_IS_SLASH(path[l])); | |
| continue; | |
| } | |
| if (path[l] == '.') { | |
| /* Remove /./ segments */ | |
| if (IS_SLASH_OR_NUL(path[l + 1])) { | |
| l++; | |
| if (path[l]) { | |
| l++; | |
| } | |
| continue; | |
| } | |
| /* 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 && !AP_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; | |
| } | |
| } | |
| } | |
| path[w++] = path[l++]; | |
| } | |
| path[w] = '\0'; | |
| 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'; | |
| } | |
| } | |
| 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 (is_fs_path && s[0] == '/' && s[1] == '/') | |
| *d++ = *s++; | |
| #endif | |
| while (*s) { | |
| if ((*d++ = *s) == '/') { | |
| do { | |
| ++s; | |
| } while (*s == '/'); | |
| } | |
| else { | |
| ++s; | |
| } | |
| } | |
| *d = '\0'; | |
| } | |
| AP_DECLARE(void) ap_no2slash(char *name) | |
| { | |
| ap_no2slash_ex(name, 1); | |
| } | |
| /* | |
| * copy at most n leading directories of s into d | |
| * d should be at least as large as s plus 1 extra byte | |
| * assumes n > 0 | |
| * the return value is the ever useful pointer to the trailing \0 of d | |
| * | |
| * 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 ap_directory_walk in server/request.c | |
| * | |
| * examples: | |
| * /a/b, 0 ==> / (true for all platforms) | |
| * /a/b, 1 ==> / | |
| * /a/b, 2 ==> /a/ | |
| * /a/b, 3 ==> /a/b/ | |
| * /a/b, 4 ==> /a/b/ | |
| * | |
| * c:/a/b 0 ==> / | |
| * c:/a/b 1 ==> c:/ | |
| * c:/a/b 2 ==> c:/a/ | |
| * c:/a/b 3 ==> c:/a/b | |
| * c:/a/b 4 ==> c:/a/b | |
| */ | |
| AP_DECLARE(char *) ap_make_dirstr_prefix(char *d, const char *s, int n) | |
| { | |
| if (n < 1) { | |
| *d = '/'; | |
| *++d = '\0'; | |
| return (d); | |
| } | |
| for (;;) { | |
| if (*s == '\0' || (*s == '/' && (--n) == 0)) { | |
| *d = '/'; | |
| break; | |
| } | |
| *d++ = *s++; | |
| } | |
| *++d = 0; | |
| return (d); | |
| } | |
| /* | |
| * return the parent directory name including trailing / of the file s | |
| */ | |
| AP_DECLARE(char *) ap_make_dirstr_parent(apr_pool_t *p, const char *s) | |
| { | |
| const char *last_slash = ap_strrchr_c(s, '/'); | |
| char *d; | |
| int l; | |
| if (last_slash == NULL) { | |
| return apr_pstrdup(p, ""); | |
| } | |
| l = (last_slash - s) + 1; | |
| d = apr_pstrmemdup(p, s, l); | |
| return (d); | |
| } | |
| AP_DECLARE(int) ap_count_dirs(const char *path) | |
| { | |
| int x, n; | |
| for (x = 0, n = 0; path[x]; x++) | |
| if (path[x] == '/') | |
| n++; | |
| return n; | |
| } | |
| AP_DECLARE(char *) ap_getword_nc(apr_pool_t *atrans, char **line, char stop) | |
| { | |
| return ap_getword(atrans, (const char **) line, stop); | |
| } | |
| AP_DECLARE(char *) ap_getword(apr_pool_t *atrans, const char **line, char stop) | |
| { | |
| const char *pos = *line; | |
| int len; | |
| char *res; | |
| while ((*pos != stop) && *pos) { | |
| ++pos; | |
| } | |
| len = pos - *line; | |
| res = apr_pstrmemdup(atrans, *line, len); | |
| if (stop) { | |
| while (*pos == stop) { | |
| ++pos; | |
| } | |
| } | |
| *line = pos; | |
| return res; | |
| } | |
| AP_DECLARE(char *) ap_getword_white_nc(apr_pool_t *atrans, char **line) | |
| { | |
| return ap_getword_white(atrans, (const char **) line); | |
| } | |
| AP_DECLARE(char *) ap_getword_white(apr_pool_t *atrans, const char **line) | |
| { | |
| const char *pos = *line; | |
| int len; | |
| char *res; | |
| while (!apr_isspace(*pos) && *pos) { | |
| ++pos; | |
| } | |
| len = pos - *line; | |
| res = apr_pstrmemdup(atrans, *line, len); | |
| while (apr_isspace(*pos)) { | |
| ++pos; | |
| } | |
| *line = pos; | |
| return res; | |
| } | |
| AP_DECLARE(char *) ap_getword_nulls_nc(apr_pool_t *atrans, char **line, | |
| char stop) | |
| { | |
| return ap_getword_nulls(atrans, (const char **) line, stop); | |
| } | |
| AP_DECLARE(char *) ap_getword_nulls(apr_pool_t *atrans, const char **line, | |
| char stop) | |
| { | |
| const char *pos = ap_strchr_c(*line, stop); | |
| char *res; | |
| if (!pos) { | |
| apr_size_t len = strlen(*line); | |
| res = apr_pstrmemdup(atrans, *line, len); | |
| *line += len; | |
| return res; | |
| } | |
| res = apr_pstrmemdup(atrans, *line, pos - *line); | |
| ++pos; | |
| *line = pos; | |
| return res; | |
| } | |
| /* Get a word, (new) config-file style --- quoted strings and backslashes | |
| * all honored | |
| */ | |
| static char *substring_conf(apr_pool_t *p, const char *start, int len, | |
| char quote) | |
| { | |
| char *result = apr_palloc(p, len + 1); | |
| char *resp = result; | |
| int i; | |
| for (i = 0; i < len; ++i) { | |
| if (start[i] == '\\' && (start[i + 1] == '\\' | |
| || (quote && start[i + 1] == quote))) | |
| *resp++ = start[++i]; | |
| else | |
| *resp++ = start[i]; | |
| } | |
| *resp++ = '\0'; | |
| #if RESOLVE_ENV_PER_TOKEN | |
| return (char *)ap_resolve_env(p,result); | |
| #else | |
| return result; | |
| #endif | |
| } | |
| AP_DECLARE(char *) ap_getword_conf_nc(apr_pool_t *p, char **line) | |
| { | |
| return ap_getword_conf(p, (const char **) line); | |
| } | |
| AP_DECLARE(char *) ap_getword_conf(apr_pool_t *p, const char **line) | |
| { | |
| const char *str = *line, *strend; | |
| char *res; | |
| char quote; | |
| while (apr_isspace(*str)) | |
| ++str; | |
| if (!*str) { | |
| *line = str; | |
| return ""; | |
| } | |
| if ((quote = *str) == '"' || quote == '\'') { | |
| strend = str + 1; | |
| while (*strend && *strend != quote) { | |
| if (*strend == '\\' && strend[1] && | |
| (strend[1] == quote || strend[1] == '\\')) { | |
| strend += 2; | |
| } | |
| else { | |
| ++strend; | |
| } | |
| } | |
| res = substring_conf(p, str + 1, strend - str - 1, quote); | |
| if (*strend == quote) | |
| ++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(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 | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00551) | |
| "Done with config file %s", cfp->name); | |
| #endif | |
| return (cfp->close == NULL) ? 0 : cfp->close(cfp->param); | |
| } | |
| /* we can't use apr_file_* directly because of linking issues on Windows */ | |
| static apr_status_t cfg_close(void *param) | |
| { | |
| return apr_file_close(param); | |
| } | |
| static apr_status_t cfg_getch(char *ch, void *param) | |
| { | |
| return apr_file_getc(ch, param); | |
| } | |
| static apr_status_t cfg_getstr(void *buf, apr_size_t bufsiz, void *param) | |
| { | |
| return apr_file_gets(buf, bufsiz, param); | |
| } | |
| /* Open a ap_configfile_t as FILE, return open ap_configfile_t struct pointer */ | |
| AP_DECLARE(apr_status_t) ap_pcfg_openfile(ap_configfile_t **ret_cfg, | |
| apr_pool_t *p, const char *name) | |
| { | |
| ap_configfile_t *new_cfg; | |
| apr_file_t *file = NULL; | |
| apr_finfo_t finfo; | |
| apr_status_t status; | |
| #ifdef DEBUG | |
| char buf[120]; | |
| #endif | |
| if (name == NULL) { | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00552) | |
| "Internal error: pcfg_openfile() called with NULL filename"); | |
| return APR_EBADF; | |
| } | |
| status = apr_file_open(&file, name, APR_READ | APR_BUFFERED, | |
| APR_OS_DEFAULT, p); | |
| #ifdef DEBUG | |
| ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, APLOGNO(00553) | |
| "Opening config file %s (%s)", | |
| name, (status != APR_SUCCESS) ? | |
| apr_strerror(status, buf, sizeof(buf)) : "successful"); | |
| #endif | |
| if (status != APR_SUCCESS) | |
| return status; | |
| status = apr_file_info_get(&finfo, APR_FINFO_TYPE, file); | |
| if (status != APR_SUCCESS) | |
| return status; | |
| if (finfo.filetype != APR_REG && | |
| #if defined(WIN32) || defined(OS2) || defined(NETWARE) | |
| ap_cstr_casecmp(apr_filepath_name_get(name), "nul") != 0) { | |
| #else | |
| strcmp(name, "/dev/null") != 0) { | |
| #endif /* WIN32 || OS2 */ | |
| ap_log_error(APLOG_MARK, APLOG_ERR, 0, NULL, APLOGNO(00554) | |
| "Access to file %s denied by server: not a regular file", | |
| name); | |
| apr_file_close(file); | |
| return APR_EBADF; | |
| } | |
| #ifdef WIN32 | |
| /* Some twisted character [no pun intended] at MS decided that a | |
| * zero width joiner as the lead wide character would be ideal for | |
| * describing Unicode text files. This was further convoluted to | |
| * another MSism that the same character mapped into utf-8, EF BB BF | |
| * would signify utf-8 text files. | |
| * | |
| * Since MS configuration files are all protecting utf-8 encoded | |
| * Unicode path, file and resource names, we already have the correct | |
| * WinNT encoding. But at least eat the stupid three bytes up front. | |
| */ | |
| { | |
| unsigned char buf[4]; | |
| apr_size_t len = 3; | |
| status = apr_file_read(file, buf, &len); | |
| if ((status != APR_SUCCESS) || (len < 3) | |
| || memcmp(buf, "\xEF\xBB\xBF", 3) != 0) { | |
| apr_off_t zero = 0; | |
| apr_file_seek(file, APR_SET, &zero); | |
| } | |
| } | |
| #endif | |
| new_cfg = apr_palloc(p, sizeof(*new_cfg)); | |
| new_cfg->param = file; | |
| new_cfg->name = apr_pstrdup(p, name); | |
| new_cfg->getch = cfg_getch; | |
| new_cfg->getstr = cfg_getstr; | |
| new_cfg->close = cfg_close; | |
| new_cfg->line_number = 0; | |
| *ret_cfg = new_cfg; | |
| return APR_SUCCESS; | |
| } | |
| /* Allocate a ap_configfile_t handle with user defined functions and params */ | |
| AP_DECLARE(ap_configfile_t *) ap_pcfg_open_custom( | |
| apr_pool_t *p, const char *descr, void *param, | |
| apr_status_t (*getc_func) (char *ch, void *param), | |
| apr_status_t (*gets_func) (void *buf, apr_size_t bufsize, void *param), | |
| apr_status_t (*close_func) (void *param)) | |
| { | |
| ap_configfile_t *new_cfg = apr_palloc(p, sizeof(*new_cfg)); | |
| new_cfg->param = param; | |
| new_cfg->name = descr; | |
| new_cfg->getch = getc_func; | |
| new_cfg->getstr = gets_func; | |
| new_cfg->close = close_func; | |
| new_cfg->line_number = 0; | |
| return new_cfg; | |
| } | |
| /* Read one character from a configfile_t */ | |
| AP_DECLARE(apr_status_t) ap_cfg_getc(char *ch, ap_configfile_t *cfp) | |
| { | |
| apr_status_t rc = cfp->getch(ch, cfp->param); | |
| if (rc == APR_SUCCESS && *ch == LF) | |
| ++cfp->line_number; | |
| return rc; | |
| } | |
| AP_DECLARE(const char *) ap_pcfg_strerror(apr_pool_t *p, ap_configfile_t *cfp, | |
| apr_status_t rc) | |
| { | |
| if (rc == APR_SUCCESS) | |
| return NULL; | |
| if (rc == APR_ENOSPC) | |
| return apr_psprintf(p, "Error reading %s at line %d: Line too long", | |
| cfp->name, cfp->line_number); | |
| return apr_psprintf(p, "Error reading %s at line %d: %pm", | |
| cfp->name, cfp->line_number, &rc); | |
| } | |
| /* 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, | |
| 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 + 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 + offset) { | |
| *cbuf = '\0'; | |
| break; | |
| } | |
| else { | |
| return APR_EOF; | |
| } | |
| } | |
| if (rc != APR_SUCCESS) { | |
| return rc; | |
| } | |
| /* | |
| * check for line continuation, | |
| * i.e. match [^\\]\\[\r]\n only | |
| */ | |
| cp = cbuf; | |
| cp += strlen(cp); | |
| if (cp > buf && cp[-1] == LF) { | |
| cp--; | |
| if (cp > buf && cp[-1] == CR) | |
| cp--; | |
| if (cp > buf && cp[-1] == '\\') { | |
| cp--; | |
| /* | |
| * line continuation requested - | |
| * then remove backslash and continue | |
| */ | |
| cbufsize -= (cp-cbuf); | |
| cbuf = cp; | |
| continue; | |
| } | |
| } | |
| else if (cp - buf >= bufsize - 1) { | |
| return APR_ENOSPC; | |
| } | |
| break; | |
| } | |
| } else { | |
| /* No "get string" function defined; read character by character */ | |
| apr_size_t i = offset; | |
| if (bufsize < 2) { | |
| /* too small, assume caller is crazy */ | |
| return APR_EINVAL; | |
| } | |
| buf[offset] = '\0'; | |
| while (1) { | |
| char c; | |
| rc = cfp->getch(&c, cfp->param); | |
| if (rc == APR_EOF) { | |
| if (i > offset) | |
| break; | |
| else | |
| return APR_EOF; | |
| } | |
| if (rc != APR_SUCCESS) | |
| return rc; | |
| if (c == LF) { | |
| ++cfp->line_number; | |
| /* check for line continuation */ | |
| if (i > 0 && buf[i-1] == '\\') { | |
| i--; | |
| continue; | |
| } | |
| else { | |
| break; | |
| } | |
| } | |
| buf[i] = c; | |
| ++i; | |
| if (i >= bufsize - 1) { | |
| return APR_ENOSPC; | |
| } | |
| } | |
| buf[i] = '\0'; | |
| } | |
| return APR_SUCCESS; | |
| } | |
| static int cfg_trim_line(char *buf) | |
| { | |
| char *start, *end; | |
| /* | |
| * Leading and trailing white space is eliminated completely | |
| */ | |
| start = buf; | |
| while (apr_isspace(*start)) | |
| ++start; | |
| /* blast trailing whitespace */ | |
| end = ap_cstr_stripws(start); | |
| /* Zap leading whitespace by shifting */ | |
| if (start != buf) | |
| memmove(buf, start, end - start + 1); | |
| #ifdef DEBUG_CFG_LINES | |
| ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, APLOGNO(00555) "Read config: '%s'", buf); | |
| #endif | |
| return end - start; | |
| } | |
| /* 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 */ | |
| 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, 0, cfp); | |
| if (rc == APR_SUCCESS) | |
| cfg_trim_line(buf); | |
| return rc; | |
| } | |
| AP_DECLARE(apr_status_t) ap_varbuf_cfg_getline(struct ap_varbuf *vb, | |
| ap_configfile_t *cfp, | |
| apr_size_t max_len) | |
| { | |
| apr_status_t rc; | |
| apr_size_t new_len; | |
| vb->strlen = 0; | |
| *vb->buf = '\0'; | |
| if (vb->strlen == AP_VARBUF_UNKNOWN) | |
| vb->strlen = strlen(vb->buf); | |
| if (vb->avail - vb->strlen < 3) { | |
| new_len = vb->avail * 2; | |
| if (new_len > max_len) | |
| new_len = max_len; | |
| else if (new_len < 3) | |
| new_len = 3; | |
| ap_varbuf_grow(vb, new_len); | |
| } | |
| for (;;) { | |
| 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) | |
| break; | |
| if (vb->avail >= max_len) | |
| return APR_ENOSPC; | |
| new_len = vb->avail * 2; | |
| if (new_len > max_len) | |
| new_len = max_len; | |
| ap_varbuf_grow(vb, new_len); | |
| --cfp->line_number; | |
| } | |
| if (vb->strlen > max_len) | |
| return APR_ENOSPC; | |
| if (rc == APR_SUCCESS) | |
| vb->strlen = cfg_trim_line(vb->buf); | |
| return rc; | |
| } | |
| /* Size an HTTP header field list item, as separated by a comma. | |
| * The return value is a pointer to the beginning of the non-empty list item | |
| * within the original string (or NULL if there is none) and the address | |
| * of field is shifted to the next non-comma, non-whitespace character. | |
| * len is the length of the item excluding any beginning whitespace. | |
| */ | |
| AP_DECLARE(const char *) ap_size_list_item(const char **field, int *len) | |
| { | |
| const unsigned char *ptr = (const unsigned char *)*field; | |
| const unsigned char *token; | |
| int in_qpair, in_qstr, in_com; | |
| /* Find first non-comma, non-whitespace byte */ | |
| while (*ptr == ',' || apr_isspace(*ptr)) | |
| ++ptr; | |
| token = ptr; | |
| /* Find the end of this item, skipping over dead bits */ | |
| for (in_qpair = in_qstr = in_com = 0; | |
| *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); | |
| ++ptr) { | |
| if (in_qpair) { | |
| in_qpair = 0; | |
| } | |
| else { | |
| switch (*ptr) { | |
| case '\\': in_qpair = 1; /* quoted-pair */ | |
| break; | |
| case '"' : if (!in_com) /* quoted string delim */ | |
| in_qstr = !in_qstr; | |
| break; | |
| case '(' : if (!in_qstr) /* comment (may nest) */ | |
| ++in_com; | |
| break; | |
| case ')' : if (in_com) /* end comment */ | |
| --in_com; | |
| break; | |
| default : break; | |
| } | |
| } | |
| } | |
| if ((*len = (ptr - token)) == 0) { | |
| *field = (const char *)ptr; | |
| return NULL; | |
| } | |
| /* Advance field pointer to the next non-comma, non-white byte */ | |
| while (*ptr == ',' || apr_isspace(*ptr)) | |
| ++ptr; | |
| *field = (const char *)ptr; | |
| return (const char *)token; | |
| } | |
| /* Retrieve an HTTP header field list item, as separated by a comma, | |
| * while stripping insignificant whitespace and lowercasing anything not in | |
| * a quoted string or comment. The return value is a new string containing | |
| * the converted list item (or NULL if none) and the address pointed to by | |
| * field is shifted to the next non-comma, non-whitespace. | |
| */ | |
| AP_DECLARE(char *) ap_get_list_item(apr_pool_t *p, const char **field) | |
| { | |
| const char *tok_start; | |
| const unsigned char *ptr; | |
| unsigned char *pos; | |
| char *token; | |
| int addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0, tok_len = 0; | |
| /* Find the beginning and maximum length of the list item so that | |
| * we can allocate a buffer for the new string and reset the field. | |
| */ | |
| if ((tok_start = ap_size_list_item(field, &tok_len)) == NULL) { | |
| return NULL; | |
| } | |
| token = apr_palloc(p, tok_len + 1); | |
| /* Scan the token again, but this time copy only the good bytes. | |
| * We skip extra whitespace and any whitespace around a '=', '/', | |
| * or ';' and lowercase normal characters not within a comment, | |
| * quoted-string or quoted-pair. | |
| */ | |
| for (ptr = (const unsigned char *)tok_start, pos = (unsigned char *)token; | |
| *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); | |
| ++ptr) { | |
| if (in_qpair) { | |
| in_qpair = 0; | |
| *pos++ = *ptr; | |
| } | |
| else { | |
| switch (*ptr) { | |
| case '\\': in_qpair = 1; | |
| if (addspace == 1) | |
| *pos++ = ' '; | |
| *pos++ = *ptr; | |
| addspace = 0; | |
| break; | |
| case '"' : if (!in_com) | |
| in_qstr = !in_qstr; | |
| if (addspace == 1) | |
| *pos++ = ' '; | |
| *pos++ = *ptr; | |
| addspace = 0; | |
| break; | |
| case '(' : if (!in_qstr) | |
| ++in_com; | |
| if (addspace == 1) | |
| *pos++ = ' '; | |
| *pos++ = *ptr; | |
| addspace = 0; | |
| break; | |
| case ')' : if (in_com) | |
| --in_com; | |
| *pos++ = *ptr; | |
| addspace = 0; | |
| break; | |
| case ' ' : | |
| case '\t': if (addspace) | |
| break; | |
| if (in_com || in_qstr) | |
| *pos++ = *ptr; | |
| else | |
| addspace = 1; | |
| break; | |
| case '=' : | |
| case '/' : | |
| case ';' : if (!(in_com || in_qstr)) | |
| addspace = -1; | |
| *pos++ = *ptr; | |
| break; | |
| default : if (addspace == 1) | |
| *pos++ = ' '; | |
| *pos++ = (in_com || in_qstr) ? *ptr | |
| : apr_tolower(*ptr); | |
| addspace = 0; | |
| break; | |
| } | |
| } | |
| } | |
| *pos = '\0'; | |
| return token; | |
| } | |
| typedef enum ap_etag_e { | |
| AP_ETAG_NONE, | |
| AP_ETAG_WEAK, | |
| AP_ETAG_STRONG | |
| } ap_etag_e; | |
| /* Find an item in canonical form (lowercase, no extra spaces) within | |
| * an HTTP field value list. Returns 1 if found, 0 if not found. | |
| * This would be much more efficient if we stored header fields as | |
| * an array of list items as they are received instead of a plain string. | |
| */ | |
| static int find_list_item(apr_pool_t *p, const char *line, | |
| const char *tok, ap_etag_e type) | |
| { | |
| const unsigned char *pos; | |
| const unsigned char *ptr = (const unsigned char *)line; | |
| int good = 0, addspace = 0, in_qpair = 0, in_qstr = 0, in_com = 0; | |
| if (!line || !tok) { | |
| return 0; | |
| } | |
| if (type == AP_ETAG_STRONG && *tok != '\"') { | |
| return 0; | |
| } | |
| if (type == AP_ETAG_WEAK) { | |
| if (*tok == 'W' && (*(tok+1)) == '/' && (*(tok+2)) == '\"') { | |
| tok += 2; | |
| } | |
| else if (*tok != '\"') { | |
| return 0; | |
| } | |
| } | |
| do { /* loop for each item in line's list */ | |
| /* Find first non-comma, non-whitespace byte */ | |
| while (*ptr == ',' || apr_isspace(*ptr)) { | |
| ++ptr; | |
| } | |
| /* Account for strong or weak Etags, depending on our search */ | |
| if (type == AP_ETAG_STRONG && *ptr != '\"') { | |
| break; | |
| } | |
| if (type == AP_ETAG_WEAK) { | |
| if (*ptr == 'W' && (*(ptr+1)) == '/' && (*(ptr+2)) == '\"') { | |
| ptr += 2; | |
| } | |
| else if (*ptr != '\"') { | |
| break; | |
| } | |
| } | |
| if (*ptr) | |
| good = 1; /* until proven otherwise for this item */ | |
| else | |
| break; /* no items left and nothing good found */ | |
| /* We skip extra whitespace and any whitespace around a '=', '/', | |
| * or ';' and lowercase normal characters not within a comment, | |
| * quoted-string or quoted-pair. | |
| */ | |
| for (pos = (const unsigned char *)tok; | |
| *ptr && (in_qpair || in_qstr || in_com || *ptr != ','); | |
| ++ptr) { | |
| if (in_qpair) { | |
| in_qpair = 0; | |
| if (good) | |
| good = (*pos++ == *ptr); | |
| } | |
| else { | |
| switch (*ptr) { | |
| case '\\': in_qpair = 1; | |
| if (addspace == 1) | |
| good = good && (*pos++ == ' '); | |
| good = good && (*pos++ == *ptr); | |
| addspace = 0; | |
| break; | |
| case '"' : if (!in_com) | |
| in_qstr = !in_qstr; | |
| if (addspace == 1) | |
| good = good && (*pos++ == ' '); | |
| good = good && (*pos++ == *ptr); | |
| addspace = 0; | |
| break; | |
| case '(' : if (!in_qstr) | |
| ++in_com; | |
| if (addspace == 1) | |
| good = good && (*pos++ == ' '); | |
| good = good && (*pos++ == *ptr); | |
| addspace = 0; | |
| break; | |
| case ')' : if (in_com) | |
| --in_com; | |
| good = good && (*pos++ == *ptr); | |
| addspace = 0; | |
| break; | |
| case ' ' : | |
| case '\t': if (addspace || !good) | |
| break; | |
| if (in_com || in_qstr) | |
| good = (*pos++ == *ptr); | |
| else | |
| addspace = 1; | |
| break; | |
| case '=' : | |
| case '/' : | |
| case ';' : if (!(in_com || in_qstr)) | |
| addspace = -1; | |
| good = good && (*pos++ == *ptr); | |
| break; | |
| default : if (!good) | |
| break; | |
| if (addspace == 1) | |
| good = (*pos++ == ' '); | |
| if (in_com || in_qstr) | |
| good = good && (*pos++ == *ptr); | |
| else | |
| good = good | |
| && (apr_tolower(*pos++) == apr_tolower(*ptr)); | |
| addspace = 0; | |
| break; | |
| } | |
| } | |
| } | |
| if (good && *pos) | |
| good = 0; /* not good if only a prefix was matched */ | |
| } while (*ptr && !good); | |
| return good; | |
| } | |
| /* Find an item in canonical form (lowercase, no extra spaces) within | |
| * an HTTP field value list. Returns 1 if found, 0 if not found. | |
| * This would be much more efficient if we stored header fields as | |
| * an array of list items as they are received instead of a plain string. | |
| */ | |
| AP_DECLARE(int) ap_find_list_item(apr_pool_t *p, const char *line, | |
| const char *tok) | |
| { | |
| return find_list_item(p, line, tok, AP_ETAG_NONE); | |
| } | |
| /* Find a strong Etag in canonical form (lowercase, no extra spaces) within | |
| * an HTTP field value list. Returns 1 if found, 0 if not found. | |
| */ | |
| AP_DECLARE(int) ap_find_etag_strong(apr_pool_t *p, const char *line, | |
| const char *tok) | |
| { | |
| return find_list_item(p, line, tok, AP_ETAG_STRONG); | |
| } | |
| /* Find a weak ETag in canonical form (lowercase, no extra spaces) within | |
| * an HTTP field value list. Returns 1 if found, 0 if not found. | |
| */ | |
| AP_DECLARE(int) ap_find_etag_weak(apr_pool_t *p, const char *line, | |
| const char *tok) | |
| { | |
| 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 | |
| * by whitespace at the caller's option. | |
| */ | |
| AP_DECLARE(char *) ap_get_token(apr_pool_t *p, const char **accept_line, | |
| int accept_white) | |
| { | |
| const char *ptr = *accept_line; | |
| const char *tok_start; | |
| char *token; | |
| /* Find first non-white byte */ | |
| while (apr_isspace(*ptr)) | |
| ++ptr; | |
| tok_start = ptr; | |
| /* find token end, skipping over quoted strings. | |
| * (comments are already gone). | |
| */ | |
| while (*ptr && (accept_white || !apr_isspace(*ptr)) | |
| && *ptr != ';' && *ptr != ',') { | |
| if (*ptr++ == '"') | |
| while (*ptr) | |
| if (*ptr++ == '"') | |
| break; | |
| } | |
| token = apr_pstrmemdup(p, tok_start, ptr - tok_start); | |
| /* Advance accept_line pointer to the next non-white byte */ | |
| while (apr_isspace(*ptr)) | |
| ++ptr; | |
| *accept_line = ptr; | |
| return token; | |
| } | |
| /* find http tokens, see the definition of token from RFC2068 */ | |
| AP_DECLARE(int) ap_find_token(apr_pool_t *p, const char *line, const char *tok) | |
| { | |
| const unsigned char *start_token; | |
| const unsigned char *s; | |
| if (!line) | |
| return 0; | |
| s = (const unsigned char *)line; | |
| for (;;) { | |
| /* find start of token, skip all stop characters */ | |
| while (*s && TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { | |
| ++s; | |
| } | |
| if (!*s) { | |
| return 0; | |
| } | |
| start_token = s; | |
| /* find end of the token */ | |
| while (*s && !TEST_CHAR(*s, T_HTTP_TOKEN_STOP)) { | |
| ++s; | |
| } | |
| if (!ap_cstr_casecmpn((const char *)start_token, (const char *)tok, | |
| s - start_token)) { | |
| return 1; | |
| } | |
| if (!*s) { | |
| return 0; | |
| } | |
| } | |
| } | |
| static const char *find_last_token(apr_pool_t *p, const char *line, | |
| const char *tok) | |
| { | |
| int llen, tlen, lidx; | |
| if (!line) | |
| return NULL; | |
| llen = strlen(line); | |
| tlen = strlen(tok); | |
| lidx = llen - tlen; | |
| 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"); | |
| 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) | |
| { | |
| char *cmd; | |
| unsigned char *d; | |
| const unsigned char *s; | |
| apr_size_t len = strlen(str); | |
| ap_assert(len <= (APR_SIZE_MAX - 1) / 2); | |
| cmd = apr_palloc(p, 2 * len + 1); | |
| d = (unsigned char *)cmd; | |
| s = (const unsigned char *)str; | |
| for (; *s; ++s) { | |
| #if defined(OS2) || defined(WIN32) | |
| /* | |
| * Newlines to Win32/OS2 CreateProcess() are ill advised. | |
| * Convert them to spaces since they are effectively white | |
| * space to most applications | |
| */ | |
| if (*s == '\r' || *s == '\n') { | |
| *d++ = ' '; | |
| continue; | |
| } | |
| #endif | |
| if (TEST_CHAR(*s, T_ESCAPE_SHELL_CMD)) { | |
| *d++ = '\\'; | |
| } | |
| *d++ = *s; | |
| } | |
| *d = '\0'; | |
| return cmd; | |
| } | |
| static char x2c(const char *what) | |
| { | |
| char digit; | |
| #if !APR_CHARSET_EBCDIC | |
| digit = ((what[0] >= 'A') ? ((what[0] & 0xdf) - 'A') + 10 | |
| : (what[0] - '0')); | |
| digit *= 16; | |
| digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 | |
| : (what[1] - '0')); | |
| #else /*APR_CHARSET_EBCDIC*/ | |
| char xstr[5]; | |
| xstr[0]='0'; | |
| xstr[1]='x'; | |
| xstr[2]=what[0]; | |
| xstr[3]=what[1]; | |
| xstr[4]='\0'; | |
| digit = apr_xlate_conv_byte(ap_hdrs_from_ascii, | |
| 0xFF & strtol(xstr, NULL, 16)); | |
| #endif /*APR_CHARSET_EBCDIC*/ | |
| return (digit); | |
| } | |
| /* | |
| * Unescapes a URL, leaving reserved characters intact. | |
| * Returns 0 on success, non-zero on error | |
| * Failure is due to | |
| * bad % escape returns HTTP_BAD_REQUEST | |
| * | |
| * decoding %00 or a forbidden character returns HTTP_NOT_FOUND | |
| */ | |
| 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; | |
| badesc = 0; | |
| badpath = 0; | |
| if (url == NULL) { | |
| return OK; | |
| } | |
| /* Initial scan for first '%'. Don't bother writing values before | |
| * seeing a '%' */ | |
| y = strchr(url, '%'); | |
| if (y == NULL) { | |
| return OK; | |
| } | |
| for (x = y; *y; ++x, ++y) { | |
| if (*y != '%') { | |
| *x = *y; | |
| } | |
| else { | |
| if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) { | |
| badesc = 1; | |
| *x = '%'; | |
| } | |
| else { | |
| char decoded; | |
| decoded = x2c(y + 1); | |
| if ((decoded == '\0') | |
| || (forbid_slashes && AP_IS_SLASH(decoded)) | |
| || (forbid && ap_strchr_c(forbid, decoded))) { | |
| badpath = 1; | |
| *x = decoded; | |
| y += 2; | |
| } | |
| else if ((keep_unreserved && TEST_CHAR(decoded, | |
| T_URI_UNRESERVED)) | |
| || (keep_slashes && AP_IS_SLASH(decoded)) | |
| || (reserved && ap_strchr_c(reserved, decoded))) { | |
| *x++ = *y++; | |
| *x++ = *y++; | |
| *x = *y; | |
| } | |
| else { | |
| *x = decoded; | |
| y += 2; | |
| } | |
| } | |
| } | |
| } | |
| *x = '\0'; | |
| if (badesc) { | |
| return HTTP_BAD_REQUEST; | |
| } | |
| else if (badpath) { | |
| return HTTP_NOT_FOUND; | |
| } | |
| else { | |
| return OK; | |
| } | |
| } | |
| AP_DECLARE(int) ap_unescape_url(char *url) | |
| { | |
| /* Traditional */ | |
| return unescape_url(url, AP_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, 0); | |
| } else { | |
| /* reserve (do not decode) encoded slashes */ | |
| return unescape_url(url, NULL, AP_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 | |
| */ | |
| 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, "/;?", 0); | |
| } | |
| AP_DECLARE(int) ap_unescape_url_reserved(char *url, const char *reserved) | |
| { | |
| return unescape_url(url, NULL, reserved); | |
| } | |
| #endif | |
| AP_DECLARE(int) ap_unescape_urlencoded(char *query) | |
| { | |
| char *slider; | |
| /* replace plus with a space */ | |
| if (query) { | |
| for (slider = query; *slider; slider++) { | |
| if (*slider == '+') { | |
| *slider = ' '; | |
| } | |
| } | |
| } | |
| /* unescape everything else */ | |
| return unescape_url(query, NULL, NULL, 0); | |
| } | |
| AP_DECLARE(char *) ap_construct_server(apr_pool_t *p, const char *hostname, | |
| apr_port_t port, const request_rec *r) | |
| { | |
| if (ap_is_default_port(port, r)) { | |
| return apr_pstrdup(p, hostname); | |
| } | |
| else { | |
| return apr_psprintf(p, "%s:%u", hostname, port); | |
| } | |
| } | |
| AP_DECLARE(int) ap_unescape_all(char *url) | |
| { | |
| return unescape_url(url, NULL, NULL, 0); | |
| } | |
| /* c2x takes an unsigned, and expects the caller has guaranteed that | |
| * 0 <= what < 256... which usually means that you have to cast to | |
| * unsigned char first, because (unsigned)(char)(x) first goes through | |
| * signed extension to an int before the unsigned cast. | |
| * | |
| * The reason for this assumption is to assist gcc code generation -- | |
| * the unsigned char -> unsigned extension is already done earlier in | |
| * both uses of this code, so there's no need to waste time doing it | |
| * again. | |
| */ | |
| static const char c2x_table[] = "0123456789abcdef"; | |
| static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, | |
| unsigned char *where) | |
| { | |
| #if APR_CHARSET_EBCDIC | |
| what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); | |
| #endif /*APR_CHARSET_EBCDIC*/ | |
| *where++ = prefix; | |
| *where++ = c2x_table[what >> 4]; | |
| *where++ = c2x_table[what & 0xf]; | |
| return where; | |
| } | |
| /* | |
| * escape_path_segment() escapes a path segment, as defined in RFC 1808. This | |
| * routine is (should be) OS independent. | |
| * | |
| * os_escape_path() converts an OS path to a URL, in an OS dependent way. In all | |
| * cases if a ':' occurs before the first '/' in the URL, the URL should be | |
| * prefixed with "./" (or the ':' escaped). In the case of Unix, this means | |
| * leaving '/' alone, but otherwise doing what escape_path_segment() does. For | |
| * efficiency reasons, we don't use escape_path_segment(), which is provided for | |
| * reference. Again, RFC 1808 is where this stuff is defined. | |
| * | |
| * If partial is set, os_escape_path() assumes that the path will be appended to | |
| * something with a '/' in it (and thus does not prefix "./"). | |
| */ | |
| AP_DECLARE(char *) ap_escape_path_segment_buffer(char *copy, const char *segment) | |
| { | |
| const unsigned char *s = (const unsigned char *)segment; | |
| unsigned char *d = (unsigned char *)copy; | |
| unsigned c; | |
| while ((c = *s)) { | |
| if (TEST_CHAR(c, T_ESCAPE_PATH_SEGMENT)) { | |
| d = c2x(c, '%', d); | |
| } | |
| else { | |
| *d++ = c; | |
| } | |
| ++s; | |
| } | |
| *d = '\0'; | |
| return copy; | |
| } | |
| AP_DECLARE(char *) ap_escape_path_segment(apr_pool_t *p, const char *segment) | |
| { | |
| apr_size_t len = strlen(segment); | |
| ap_assert(len <= (APR_SIZE_MAX - 1) / 3); | |
| return ap_escape_path_segment_buffer(apr_palloc(p, 3 * len + 1), segment); | |
| } | |
| AP_DECLARE(char *) ap_os_escape_path(apr_pool_t *p, const char *path, int partial) | |
| { | |
| /* Allocate +3 for potential "./" and trailing NULL. | |
| * Allocate another +1 to allow the caller to add a trailing '/' (see | |
| * comment in 'ap_sub_req_lookup_dirent') | |
| */ | |
| apr_size_t len = strlen(path); | |
| char *copy; | |
| const unsigned char *s; | |
| unsigned char *d; | |
| unsigned c; | |
| ap_assert(len <= (APR_SIZE_MAX - 4) / 3); | |
| copy = apr_palloc(p, 3 * len + 3 + 1); | |
| s = (const unsigned char *)path; | |
| d = (unsigned char *)copy; | |
| if (!partial) { | |
| const char *colon = ap_strchr_c(path, ':'); | |
| const char *slash = ap_strchr_c(path, '/'); | |
| if (colon && (!slash || colon < slash)) { | |
| *d++ = '.'; | |
| *d++ = '/'; | |
| } | |
| } | |
| while ((c = *s)) { | |
| if (TEST_CHAR(c, T_OS_ESCAPE_PATH)) { | |
| d = c2x(c, '%', d); | |
| } | |
| else { | |
| *d++ = c; | |
| } | |
| ++s; | |
| } | |
| *d = '\0'; | |
| return copy; | |
| } | |
| AP_DECLARE(char *) ap_escape_urlencoded_buffer(char *copy, const char *buffer) | |
| { | |
| const unsigned char *s = (const unsigned char *)buffer; | |
| unsigned char *d = (unsigned char *)copy; | |
| unsigned c; | |
| while ((c = *s)) { | |
| if (TEST_CHAR(c, T_ESCAPE_URLENCODED)) { | |
| d = c2x(c, '%', d); | |
| } | |
| else if (c == ' ') { | |
| *d++ = '+'; | |
| } | |
| else { | |
| *d++ = c; | |
| } | |
| ++s; | |
| } | |
| *d = '\0'; | |
| return copy; | |
| } | |
| AP_DECLARE(char *) ap_escape_urlencoded(apr_pool_t *p, const char *buffer) | |
| { | |
| apr_size_t len = strlen(buffer); | |
| ap_assert(len <= (APR_SIZE_MAX - 1) / 3); | |
| return ap_escape_urlencoded_buffer(apr_palloc(p, 3 * len + 1), buffer); | |
| } | |
| /* ap_escape_uri is now a macro for os_escape_path */ | |
| AP_DECLARE(char *) ap_escape_html2(apr_pool_t *p, const char *s, int toasc) | |
| { | |
| apr_size_t i, j; | |
| char *x; | |
| /* first, count the number of extra characters */ | |
| 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] == '&') | |
| j += 4; | |
| else if (s[i] == '"') | |
| j += 5; | |
| else if (toasc && !apr_isascii(s[i])) | |
| j += 5; | |
| } | |
| if (j == 0) | |
| return apr_pstrmemdup(p, s, i); | |
| x = apr_palloc(p, i + j + 1); | |
| for (i = 0, j = 0; s[i] != '\0'; i++, j++) | |
| if (s[i] == '<') { | |
| memcpy(&x[j], "<", 4); | |
| j += 3; | |
| } | |
| else if (s[i] == '>') { | |
| memcpy(&x[j], ">", 4); | |
| j += 3; | |
| } | |
| else if (s[i] == '&') { | |
| memcpy(&x[j], "&", 5); | |
| j += 4; | |
| } | |
| else if (s[i] == '"') { | |
| memcpy(&x[j], """, 6); | |
| j += 5; | |
| } | |
| else if (toasc && !apr_isascii(s[i])) { | |
| char *esc = apr_psprintf(p, "&#%3.3d;", (unsigned char)s[i]); | |
| memcpy(&x[j], esc, 6); | |
| j += 5; | |
| } | |
| else | |
| x[j] = s[i]; | |
| x[j] = '\0'; | |
| return x; | |
| } | |
| AP_DECLARE(char *) ap_escape_logitem(apr_pool_t *p, const char *str) | |
| { | |
| char *ret; | |
| unsigned char *d; | |
| const unsigned char *s; | |
| apr_size_t length, escapes = 0; | |
| if (!str) { | |
| return NULL; | |
| } | |
| /* Compute how many characters need to be escaped */ | |
| s = (const unsigned char *)str; | |
| for (; *s; ++s) { | |
| if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { | |
| escapes++; | |
| } | |
| } | |
| /* Compute the length of the input string, including NULL */ | |
| length = s - (const unsigned char *)str + 1; | |
| /* Fast path: nothing to escape */ | |
| if (escapes == 0) { | |
| return apr_pmemdup(p, str, length); | |
| } | |
| /* Each escaped character needs up to 3 extra bytes (0 --> \x00) */ | |
| ret = apr_palloc(p, length + 3 * escapes); | |
| d = (unsigned char *)ret; | |
| s = (const unsigned char *)str; | |
| for (; *s; ++s) { | |
| if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { | |
| *d++ = '\\'; | |
| switch(*s) { | |
| case '\b': | |
| *d++ = 'b'; | |
| break; | |
| case '\n': | |
| *d++ = 'n'; | |
| break; | |
| case '\r': | |
| *d++ = 'r'; | |
| break; | |
| case '\t': | |
| *d++ = 't'; | |
| break; | |
| case '\v': | |
| *d++ = 'v'; | |
| break; | |
| case '\\': | |
| case '"': | |
| *d++ = *s; | |
| break; | |
| default: | |
| c2x(*s, 'x', d); | |
| d += 3; | |
| } | |
| } | |
| else { | |
| *d++ = *s; | |
| } | |
| } | |
| *d = '\0'; | |
| return ret; | |
| } | |
| AP_DECLARE(apr_size_t) ap_escape_errorlog_item(char *dest, const char *source, | |
| apr_size_t buflen) | |
| { | |
| unsigned char *d, *ep; | |
| const unsigned char *s; | |
| if (!source || !buflen) { /* be safe */ | |
| return 0; | |
| } | |
| d = (unsigned char *)dest; | |
| s = (const unsigned char *)source; | |
| ep = d + buflen - 1; | |
| for (; d < ep && *s; ++s) { | |
| if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { | |
| *d++ = '\\'; | |
| if (d >= ep) { | |
| --d; | |
| break; | |
| } | |
| switch(*s) { | |
| case '\b': | |
| *d++ = 'b'; | |
| break; | |
| case '\n': | |
| *d++ = 'n'; | |
| break; | |
| case '\r': | |
| *d++ = 'r'; | |
| break; | |
| case '\t': | |
| *d++ = 't'; | |
| break; | |
| case '\v': | |
| *d++ = 'v'; | |
| break; | |
| case '\\': | |
| *d++ = *s; | |
| break; | |
| case '"': /* no need for this in error log */ | |
| d[-1] = *s; | |
| break; | |
| default: | |
| if (d >= ep - 2) { | |
| ep = --d; /* break the for loop as well */ | |
| break; | |
| } | |
| c2x(*s, 'x', d); | |
| d += 3; | |
| } | |
| } | |
| else { | |
| *d++ = *s; | |
| } | |
| } | |
| *d = '\0'; | |
| return (d - (unsigned char *)dest); | |
| } | |
| AP_DECLARE(void) ap_bin2hex(const void *src, apr_size_t srclen, char *dest) | |
| { | |
| const unsigned char *in = src; | |
| apr_size_t i; | |
| for (i = 0; i < srclen; i++) { | |
| *dest++ = c2x_table[in[i] >> 4]; | |
| *dest++ = c2x_table[in[i] & 0xf]; | |
| } | |
| *dest = '\0'; | |
| } | |
| AP_DECLARE(int) ap_is_directory(apr_pool_t *p, const char *path) | |
| { | |
| apr_finfo_t finfo; | |
| if (apr_stat(&finfo, path, APR_FINFO_TYPE, p) != APR_SUCCESS) | |
| return 0; /* in error condition, just return no */ | |
| return (finfo.filetype == APR_DIR); | |
| } | |
| AP_DECLARE(int) ap_is_rdirectory(apr_pool_t *p, const char *path) | |
| { | |
| apr_finfo_t finfo; | |
| if (apr_stat(&finfo, path, APR_FINFO_LINK | APR_FINFO_TYPE, p) != APR_SUCCESS) | |
| return 0; /* in error condition, just return no */ | |
| return (finfo.filetype == APR_DIR); | |
| } | |
| AP_DECLARE(char *) ap_make_full_path(apr_pool_t *a, const char *src1, | |
| const char *src2) | |
| { | |
| apr_size_t len1, len2; | |
| char *path; | |
| len1 = strlen(src1); | |
| len2 = strlen(src2); | |
| /* allocate +3 for '/' delimiter, trailing NULL and overallocate | |
| * one extra byte to allow the caller to add a trailing '/' | |
| */ | |
| path = (char *)apr_palloc(a, len1 + len2 + 3); | |
| if (len1 == 0) { | |
| *path = '/'; | |
| memcpy(path + 1, src2, len2 + 1); | |
| } | |
| else { | |
| char *next; | |
| memcpy(path, src1, len1); | |
| next = path + len1; | |
| if (next[-1] != '/') { | |
| *next++ = '/'; | |
| } | |
| memcpy(next, src2, len2 + 1); | |
| } | |
| return path; | |
| } | |
| /* | |
| * Check for an absoluteURI syntax (see section 3.2 in RFC2068). | |
| */ | |
| AP_DECLARE(int) ap_is_url(const char *u) | |
| { | |
| int x; | |
| for (x = 0; u[x] != ':'; x++) { | |
| if ((!u[x]) || | |
| ((!apr_isalnum(u[x])) && | |
| (u[x] != '+') && (u[x] != '-') && (u[x] != '.'))) { | |
| return 0; | |
| } | |
| } | |
| return (x ? 1 : 0); /* If the first character is ':', it's broken, too */ | |
| } | |
| AP_DECLARE(int) ap_ind(const char *s, char c) | |
| { | |
| const char *p = ap_strchr_c(s, c); | |
| if (p == NULL) | |
| return -1; | |
| return p - s; | |
| } | |
| AP_DECLARE(int) ap_rind(const char *s, char c) | |
| { | |
| const char *p = ap_strrchr_c(s, c); | |
| if (p == NULL) | |
| return -1; | |
| return p - s; | |
| } | |
| AP_DECLARE(void) ap_str_tolower(char *str) | |
| { | |
| while (*str) { | |
| *str = apr_tolower(*str); | |
| ++str; | |
| } | |
| } | |
| AP_DECLARE(void) ap_str_toupper(char *str) | |
| { | |
| while (*str) { | |
| *str = apr_toupper(*str); | |
| ++str; | |
| } | |
| } | |
| /* | |
| * We must return a FQDN | |
| */ | |
| char *ap_get_local_host(apr_pool_t *a) | |
| { | |
| #ifndef MAXHOSTNAMELEN | |
| #define MAXHOSTNAMELEN 256 | |
| #endif | |
| char str[MAXHOSTNAMELEN + 1]; | |
| char *server_hostname = NULL; | |
| apr_sockaddr_t *sockaddr; | |
| char *hostname; | |
| if (apr_gethostname(str, sizeof(str) - 1, a) != APR_SUCCESS) { | |
| ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, APLOGNO(00556) | |
| "%s: apr_gethostname() failed to determine ServerName", | |
| ap_server_argv0); | |
| } else { | |
| str[sizeof(str) - 1] = '\0'; | |
| if (apr_sockaddr_info_get(&sockaddr, str, APR_UNSPEC, 0, 0, a) == APR_SUCCESS) { | |
| if ( (apr_getnameinfo(&hostname, sockaddr, 0) == APR_SUCCESS) && | |
| (ap_strchr_c(hostname, '.')) ) { | |
| server_hostname = apr_pstrdup(a, hostname); | |
| return server_hostname; | |
| } else if (ap_strchr_c(str, '.')) { | |
| server_hostname = apr_pstrdup(a, str); | |
| } else { | |
| apr_sockaddr_ip_get(&hostname, sockaddr); | |
| server_hostname = apr_pstrdup(a, hostname); | |
| } | |
| } else { | |
| ap_log_perror(APLOG_MARK, APLOG_STARTUP | APLOG_WARNING, 0, a, APLOGNO(00557) | |
| "%s: apr_sockaddr_info_get() failed for %s", | |
| ap_server_argv0, str); | |
| } | |
| } | |
| if (!server_hostname) | |
| server_hostname = apr_pstrdup(a, "127.0.0.1"); | |
| ap_log_perror(APLOG_MARK, APLOG_ALERT|APLOG_STARTUP, 0, a, APLOGNO(00558) | |
| "%s: Could not reliably determine the server's fully qualified " | |
| "domain name, using %s. Set the 'ServerName' directive globally " | |
| "to suppress this message", | |
| ap_server_argv0, server_hostname); | |
| return server_hostname; | |
| } | |
| /* simple 'pool' alloc()ing glue to apr_base64.c | |
| */ | |
| AP_DECLARE(char *) ap_pbase64decode(apr_pool_t *p, const char *bufcoded) | |
| { | |
| char *decoded; | |
| decoded = (char *) apr_palloc(p, apr_base64_decode_len(bufcoded)); | |
| apr_base64_decode(decoded, 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; | |
| int l = strlen(string); | |
| encoded = (char *) apr_palloc(p, apr_base64_encode_len(l)); | |
| apr_base64_encode(encoded, string, l); | |
| return encoded; | |
| } | |
| /* we want to downcase the type/subtype for comparison purposes | |
| * but nothing else because ;parameter=foo values are case sensitive. | |
| * XXX: in truth we want to downcase parameter names... but really, | |
| * apache has never handled parameters and such correctly. You | |
| * also need to compress spaces and such to be able to compare | |
| * properly. -djg | |
| */ | |
| AP_DECLARE(void) ap_content_type_tolower(char *str) | |
| { | |
| char *semi; | |
| semi = strchr(str, ';'); | |
| if (semi) { | |
| *semi = '\0'; | |
| } | |
| ap_str_tolower(str); | |
| if (semi) { | |
| *semi = ';'; | |
| } | |
| } | |
| /* | |
| * Given a string, replace any bare " with \" . | |
| */ | |
| AP_DECLARE(char *) ap_escape_quotes(apr_pool_t *p, const char *instring) | |
| { | |
| apr_size_t size, extra = 0; | |
| const char *inchr = instring; | |
| char *outchr, *outstring; | |
| /* | |
| * Look through the input string, jogging the length of the output | |
| * string up by an extra byte each time we find an unescaped ". | |
| */ | |
| while (*inchr != '\0') { | |
| if (*inchr == '"') { | |
| extra++; | |
| } | |
| /* | |
| * If we find a slosh, and it's not the last byte in the string, | |
| * it's escaping something - advance past both bytes. | |
| */ | |
| else if ((*inchr == '\\') && (inchr[1] != '\0')) { | |
| inchr++; | |
| } | |
| inchr++; | |
| } | |
| 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; | |
| /* | |
| * Now copy the input string to the output string, inserting a slosh | |
| * in front of every " that doesn't already have one. | |
| */ | |
| while (*inchr != '\0') { | |
| if (*inchr == '"') { | |
| *outchr++ = '\\'; | |
| } | |
| else if ((*inchr == '\\') && (inchr[1] != '\0')) { | |
| *outchr++ = *inchr++; | |
| } | |
| *outchr++ = *inchr++; | |
| } | |
| *outchr = '\0'; | |
| return outstring; | |
| } | |
| /* | |
| * Given a string, append the PID deliminated by delim. | |
| * Usually used to create a pid-appended filepath name | |
| * (eg: /a/b/foo -> /a/b/foo.6726). A function, and not | |
| * a macro, to avoid unistd.h dependency | |
| */ | |
| AP_DECLARE(char *) ap_append_pid(apr_pool_t *p, const char *string, | |
| const char *delim) | |
| { | |
| return apr_psprintf(p, "%s%s%" APR_PID_T_FMT, string, | |
| delim, getpid()); | |
| } | |
| /** | |
| * 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 (case insensitive): | |
| * | |
| * ms : milliseconds | |
| * s : seconds | |
| * mi[n] : minutes | |
| * h : hours | |
| * | |
| * If no unit is contained in the given timeout parameter the default_time_unit | |
| * will be used instead. | |
| * @param timeout_parameter The string containing the timeout parameter. | |
| * @param timeout The timeout value to be returned. | |
| * @param default_time_unit The default time unit to use if none is specified | |
| * 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, | |
| const char *default_time_unit) | |
| { | |
| char *endp; | |
| const char *time_str; | |
| apr_int64_t tout; | |
| apr_uint64_t check; | |
| tout = apr_strtoi64(timeout_parameter, &endp, 10); | |
| if (errno) { | |
| return errno; | |
| } | |
| if (!endp || !*endp) { | |
| time_str = default_time_unit; | |
| } | |
| else { | |
| time_str = endp; | |
| } | |
| if (tout < 0) { | |
| return APR_EGENERAL; | |
| } | |
| switch (*time_str) { | |
| /* Time is in seconds */ | |
| case 's': | |
| case 'S': | |
| CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX)); | |
| check = apr_time_from_sec(tout); | |
| break; | |
| /* Time is in hours */ | |
| case 'h': | |
| case 'H': | |
| CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 3600)); | |
| check = apr_time_from_sec(tout * 3600); | |
| break; | |
| case 'm': | |
| case 'M': | |
| switch (*(++time_str)) { | |
| /* Time is in milliseconds */ | |
| case 's': | |
| case 'S': | |
| CHECK_OVERFLOW(tout, apr_time_as_msec(APR_INT64_MAX)); | |
| check = apr_time_from_msec(tout); | |
| break; | |
| /* Time is in minutes */ | |
| case 'i': | |
| case 'I': | |
| CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 60)); | |
| check = apr_time_from_sec(tout * 60); | |
| break; | |
| default: | |
| return APR_EGENERAL; | |
| } | |
| break; | |
| default: | |
| return APR_EGENERAL; | |
| } | |
| *timeout = (apr_interval_time_t)check; | |
| return APR_SUCCESS; | |
| } | |
| AP_DECLARE(apr_status_t) ap_size_parameter_parse(const char *parameter, | |
| apr_off_t *value) | |
| { | |
| /* The maximum apr_off_t value. */ | |
| const apr_off_t off_max = sizeof(apr_off_t) == 8 ? APR_INT64_MAX | |
| : APR_INT32_MAX; | |
| apr_off_t parsed, multiplier = 1; | |
| apr_status_t rv; | |
| char *end; | |
| /* Check for no conversion before the status: some libc implementations | |
| * set EINVAL when no digits are present. */ | |
| rv = apr_strtoff(&parsed, parameter, &end, 10); | |
| if (end == parameter) { | |
| return APR_EINVAL; | |
| } | |
| if (rv != APR_SUCCESS) { | |
| return APR_ERANGE; | |
| } | |
| if (*end == 'k' || *end == 'K') { | |
| multiplier = 1024; | |
| end++; | |
| } | |
| else if (*end == 'm' || *end == 'M') { | |
| multiplier = 1024 * 1024; | |
| end++; | |
| } | |
| else if (*end == 'g' || *end == 'G') { | |
| multiplier = 1024 * 1024 * 1024; | |
| end++; | |
| } | |
| else if (*end == 'b' || *end == 'B') { | |
| end++; | |
| } | |
| if (*end != '\0') { | |
| return APR_EINVAL; | |
| } | |
| if (parsed > off_max / multiplier | |
| || parsed < (-off_max - 1) / multiplier) { | |
| return APR_ERANGE; | |
| } | |
| *value = parsed * multiplier; | |
| 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. | |
| * | |
| * @param r the request_rec of the request | |
| * @return truth value | |
| */ | |
| AP_DECLARE(int) ap_request_has_body(request_rec *r) | |
| { | |
| apr_off_t cl; | |
| const char *cls; | |
| 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))); | |
| } | |
| /** | |
| * 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_; | |
| *ptr = NULL; | |
| return APR_SUCCESS; | |
| } | |
| AP_DECLARE(apr_status_t) ap_str2_alnum(const char *src, char *dest) { | |
| for ( ; *src; src++, dest++) | |
| { | |
| if (!apr_isprint(*src)) | |
| *dest = 'x'; | |
| else if (!apr_isalnum(*src)) | |
| *dest = '_'; | |
| else | |
| *dest = (char)*src; | |
| } | |
| *dest = '\0'; | |
| return APR_SUCCESS; | |
| } | |
| AP_DECLARE(apr_status_t) ap_pstr2_alnum(apr_pool_t *p, const char *src, | |
| const char **dest) | |
| { | |
| char *new = apr_palloc(p, strlen(src)+1); | |
| if (!new) | |
| return APR_ENOMEM; | |
| *dest = new; | |
| return ap_str2_alnum(src, new); | |
| } | |
| /** | |
| * Read the body and parse any form found, which must be of the | |
| * type application/x-www-form-urlencoded. | |
| * | |
| * Name/value pairs are returned in an array, with the names as | |
| * strings with a maximum length of HUGE_STRING_LEN, and the | |
| * values as bucket brigades. This allows values to be arbitrarily | |
| * large. | |
| * | |
| * All url-encoding is removed from both the names and the values | |
| * on the fly. The names are interpreted as strings, while the | |
| * values are interpreted as blocks of binary data, that may | |
| * contain the 0 character. | |
| * | |
| * In order to ensure that resource limits are not exceeded, a | |
| * maximum size must be provided. If the sum of the lengths of | |
| * the names and the values exceed this size, this function | |
| * will return HTTP_REQUEST_ENTITY_TOO_LARGE. | |
| * | |
| * An optional number of parameters can be provided, if the number | |
| * of parameters provided exceeds this amount, this function will | |
| * return HTTP_REQUEST_ENTITY_TOO_LARGE. If this value is negative, | |
| * no limit is imposed, and the number of parameters is in turn | |
| * constrained by the size parameter above. | |
| * | |
| * This function honours any kept_body configuration, and the | |
| * original raw request body will be saved to the kept_body brigade | |
| * if so configured, just as ap_discard_request_body does. | |
| * | |
| * NOTE: File upload is not yet supported, but can be without change | |
| * to the function call. | |
| */ | |
| /* form parsing stuff */ | |
| typedef enum { | |
| FORM_NORMAL, | |
| FORM_AMP, | |
| FORM_NAME, | |
| FORM_VALUE, | |
| FORM_PERCENTA, | |
| FORM_PERCENTB, | |
| FORM_ABORT | |
| } ap_form_type_t; | |
| AP_DECLARE(int) ap_parse_form_data(request_rec *r, ap_filter_t *f, | |
| apr_array_header_t **ptr, | |
| apr_size_t num, apr_size_t usize) | |
| { | |
| apr_bucket_brigade *bb = NULL; | |
| int seen_eos = 0; | |
| char buffer[HUGE_STRING_LEN + 1]; | |
| const char *ct; | |
| apr_size_t offset = 0; | |
| apr_ssize_t size; | |
| 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 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 || ap_cstr_casecmpn("application/x-www-form-urlencoded", ct, 33)) { | |
| return ap_discard_request_body(r); | |
| } | |
| if (usize > APR_SIZE_MAX >> 1) | |
| size = APR_SIZE_MAX >> 1; | |
| else | |
| size = usize; | |
| if (!f) { | |
| f = r->input_filters; | |
| } | |
| bb = apr_brigade_create(r->pool, r->connection->bucket_alloc); | |
| do { | |
| apr_bucket *bucket = NULL, *last = NULL; | |
| int rv = ap_get_brigade(f, bb, AP_MODE_READBYTES, | |
| APR_BLOCK_READ, HUGE_STRING_LEN); | |
| if (rv != APR_SUCCESS) { | |
| apr_brigade_destroy(bb); | |
| return ap_map_http_request_error(rv, HTTP_BAD_REQUEST); | |
| } | |
| for (bucket = APR_BRIGADE_FIRST(bb); | |
| bucket != APR_BRIGADE_SENTINEL(bb); | |
| last = bucket, bucket = APR_BUCKET_NEXT(bucket)) { | |
| const char *data; | |
| apr_size_t len, slide; | |
| if (last) { | |
| apr_bucket_delete(last); | |
| } | |
| if (APR_BUCKET_IS_EOS(bucket)) { | |
| seen_eos = 1; | |
| break; | |
| } | |
| if (bucket->length == 0) { | |
| continue; | |
| } | |
| rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ); | |
| if (rv != APR_SUCCESS) { | |
| apr_brigade_destroy(bb); | |
| return HTTP_BAD_REQUEST; | |
| } | |
| slide = len; | |
| while (state != FORM_ABORT && slide-- > 0 && size >= 0 && num != 0) { | |
| char c = *data++; | |
| if ('+' == c) { | |
| c = ' '; | |
| } | |
| else if ('&' == c) { | |
| state = FORM_AMP; | |
| } | |
| if ('%' == c) { | |
| percent = FORM_PERCENTA; | |
| continue; | |
| } | |
| if (FORM_PERCENTA == percent) { | |
| escaped_char[0] = c; | |
| percent = FORM_PERCENTB; | |
| continue; | |
| } | |
| if (FORM_PERCENTB == percent) { | |
| escaped_char[1] = c; | |
| c = x2c(escaped_char); | |
| percent = FORM_NORMAL; | |
| } | |
| switch (state) { | |
| case FORM_AMP: | |
| if (pair) { | |
| const char *tmp = apr_pmemdup(r->pool, buffer, offset); | |
| apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(pair->value, b); | |
| } | |
| state = FORM_NAME; | |
| pair = NULL; | |
| offset = 0; | |
| num--; | |
| break; | |
| case FORM_NAME: | |
| if (offset < HUGE_STRING_LEN) { | |
| if ('=' == c) { | |
| pair = (ap_form_pair_t *) apr_array_push(pairs); | |
| 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; | |
| size--; | |
| } | |
| } | |
| else { | |
| state = FORM_ABORT; | |
| } | |
| break; | |
| case FORM_VALUE: | |
| if (offset >= HUGE_STRING_LEN) { | |
| const char *tmp = apr_pmemdup(r->pool, buffer, offset); | |
| apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(pair->value, b); | |
| offset = 0; | |
| } | |
| buffer[offset++] = c; | |
| size--; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| apr_brigade_cleanup(bb); | |
| } while (!seen_eos); | |
| if (FORM_ABORT == state || size < 0 || num == 0) { | |
| return HTTP_REQUEST_ENTITY_TOO_LARGE; | |
| } | |
| else if (FORM_VALUE == state && pair && offset > 0) { | |
| const char *tmp = apr_pmemdup(r->pool, buffer, offset); | |
| apr_bucket *b = apr_bucket_pool_create(tmp, offset, r->pool, r->connection->bucket_alloc); | |
| APR_BRIGADE_INSERT_TAIL(pair->value, b); | |
| } | |
| return OK; | |
| } | |
| #define VARBUF_SMALL_SIZE 2048 | |
| #define VARBUF_MAX_SIZE (APR_SIZE_MAX - 1 - \ | |
| APR_ALIGN_DEFAULT(sizeof(struct ap_varbuf_info))) | |
| struct ap_varbuf_info { | |
| struct apr_memnode_t *node; | |
| apr_allocator_t *allocator; | |
| }; | |
| static apr_status_t varbuf_cleanup(void *info_) | |
| { | |
| struct ap_varbuf_info *info = info_; | |
| info->node->next = NULL; | |
| apr_allocator_free(info->allocator, info->node); | |
| return APR_SUCCESS; | |
| } | |
| 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, | |
| apr_size_t init_size) | |
| { | |
| vb->buf = varbuf_empty; | |
| vb->avail = 0; | |
| vb->strlen = AP_VARBUF_UNKNOWN; | |
| vb->pool = p; | |
| vb->info = NULL; | |
| ap_varbuf_grow(vb, init_size); | |
| } | |
| AP_DECLARE(void) ap_varbuf_grow(struct ap_varbuf *vb, apr_size_t new_len) | |
| { | |
| apr_memnode_t *new_node = NULL; | |
| apr_allocator_t *allocator; | |
| struct ap_varbuf_info *new_info; | |
| char *new; | |
| AP_DEBUG_ASSERT(vb->strlen == AP_VARBUF_UNKNOWN || vb->avail >= vb->strlen); | |
| if (new_len <= vb->avail) | |
| return; | |
| if (new_len < 2 * vb->avail && vb->avail < VARBUF_MAX_SIZE/2) { | |
| /* at least double the size, to avoid repeated reallocations */ | |
| new_len = 2 * vb->avail; | |
| } | |
| else if (new_len > VARBUF_MAX_SIZE) { | |
| apr_abortfunc_t abort_fn = apr_pool_abort_get(vb->pool); | |
| ap_assert(abort_fn != NULL); | |
| abort_fn(APR_ENOMEM); | |
| return; | |
| } | |
| new_len++; /* add space for trailing \0 */ | |
| if (new_len <= VARBUF_SMALL_SIZE) { | |
| new_len = APR_ALIGN_DEFAULT(new_len); | |
| new = apr_palloc(vb->pool, new_len); | |
| if (vb->avail && vb->strlen != 0) { | |
| AP_DEBUG_ASSERT(vb->buf != NULL); | |
| AP_DEBUG_ASSERT(vb->buf != varbuf_empty); | |
| if (new == vb->buf + vb->avail + 1) { | |
| /* We are lucky: the new memory lies directly after our old | |
| * buffer, we can now use both. | |
| */ | |
| vb->avail += new_len; | |
| return; | |
| } | |
| else { | |
| /* copy up to vb->strlen + 1 bytes */ | |
| memcpy(new, vb->buf, vb->strlen == AP_VARBUF_UNKNOWN ? | |
| vb->avail + 1 : vb->strlen + 1); | |
| } | |
| } | |
| else { | |
| *new = '\0'; | |
| } | |
| vb->avail = new_len - 1; | |
| vb->buf = new; | |
| return; | |
| } | |
| /* 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))); | |
| if (!new_node) { | |
| apr_abortfunc_t abort_fn = apr_pool_abort_get(vb->pool); | |
| ap_assert(abort_fn != NULL); | |
| abort_fn(APR_ENOMEM); | |
| return; | |
| } | |
| new_info = (struct ap_varbuf_info *)new_node->first_avail; | |
| new_node->first_avail += APR_ALIGN_DEFAULT(sizeof(*new_info)); | |
| new_info->node = new_node; | |
| new_info->allocator = allocator; | |
| new = new_node->first_avail; | |
| AP_DEBUG_ASSERT(new_node->endp - new_node->first_avail >= new_len); | |
| new_len = new_node->endp - new_node->first_avail; | |
| if (vb->avail && vb->strlen != 0) | |
| memcpy(new, vb->buf, vb->strlen == AP_VARBUF_UNKNOWN ? | |
| vb->avail + 1 : vb->strlen + 1); | |
| else | |
| *new = '\0'; | |
| if (vb->info) | |
| apr_pool_cleanup_run(vb->pool, vb->info, varbuf_cleanup); | |
| apr_pool_cleanup_register(vb->pool, new_info, varbuf_cleanup, | |
| apr_pool_cleanup_null); | |
| vb->info = new_info; | |
| vb->buf = new; | |
| vb->avail = new_len - 1; | |
| } | |
| AP_DECLARE(void) ap_varbuf_strmemcat(struct ap_varbuf *vb, const char *str, | |
| int len) | |
| { | |
| if (len == 0) | |
| return; | |
| if (!vb->avail) { | |
| ap_varbuf_grow(vb, len); | |
| memcpy(vb->buf, str, len); | |
| vb->buf[len] = '\0'; | |
| vb->strlen = len; | |
| return; | |
| } | |
| if (vb->strlen == AP_VARBUF_UNKNOWN) | |
| vb->strlen = strlen(vb->buf); | |
| ap_varbuf_grow(vb, vb->strlen + len); | |
| memcpy(vb->buf + vb->strlen, str, len); | |
| vb->strlen += len; | |
| vb->buf[vb->strlen] = '\0'; | |
| } | |
| AP_DECLARE(void) ap_varbuf_free(struct ap_varbuf *vb) | |
| { | |
| if (vb->info) { | |
| apr_pool_cleanup_run(vb->pool, vb->info, varbuf_cleanup); | |
| vb->info = NULL; | |
| } | |
| vb->buf = NULL; | |
| } | |
| AP_DECLARE(char *) ap_varbuf_pdup(apr_pool_t *p, struct ap_varbuf *buf, | |
| const char *prepend, apr_size_t prepend_len, | |
| const char *append, apr_size_t append_len, | |
| apr_size_t *new_len) | |
| { | |
| apr_size_t i = 0; | |
| struct iovec vec[3]; | |
| if (prepend) { | |
| vec[i].iov_base = (void *)prepend; | |
| vec[i].iov_len = prepend_len; | |
| i++; | |
| } | |
| if (buf->avail && buf->strlen) { | |
| if (buf->strlen == AP_VARBUF_UNKNOWN) | |
| buf->strlen = strlen(buf->buf); | |
| vec[i].iov_base = (void *)buf->buf; | |
| vec[i].iov_len = buf->strlen; | |
| i++; | |
| } | |
| if (append) { | |
| vec[i].iov_base = (void *)append; | |
| vec[i].iov_len = append_len; | |
| i++; | |
| } | |
| if (i) | |
| return apr_pstrcatv(p, vec, i, new_len); | |
| if (new_len) | |
| *new_len = 0; | |
| return ""; | |
| } | |
| AP_DECLARE(apr_status_t) ap_varbuf_regsub(struct ap_varbuf *vb, | |
| const char *input, | |
| const char *source, | |
| apr_size_t nmatch, | |
| ap_regmatch_t pmatch[], | |
| apr_size_t maxlen) | |
| { | |
| return regsub_core(NULL, NULL, vb, input, source, nmatch, pmatch, maxlen); | |
| } | |
| static const char * const oom_message = "[crit] Memory allocation failed, " | |
| "aborting process." APR_EOL_STR; | |
| AP_DECLARE(void) ap_abort_on_oom(void) | |
| { | |
| int written, count = strlen(oom_message); | |
| const char *buf = oom_message; | |
| do { | |
| written = write(STDERR_FILENO, buf, count); | |
| if (written == count) | |
| break; | |
| if (written > 0) { | |
| buf += written; | |
| count -= written; | |
| } | |
| } while (written >= 0 || errno == EINTR); | |
| abort(); | |
| } | |
| AP_DECLARE(void *) ap_malloc(size_t size) | |
| { | |
| void *p = malloc(size); | |
| if (p == NULL && size != 0) | |
| ap_abort_on_oom(); | |
| return p; | |
| } | |
| AP_DECLARE(void *) ap_calloc(size_t nelem, size_t size) | |
| { | |
| void *p = calloc(nelem, size); | |
| if (p == NULL && nelem != 0 && size != 0) | |
| ap_abort_on_oom(); | |
| return p; | |
| } | |
| AP_DECLARE(void *) ap_realloc(void *ptr, size_t size) | |
| { | |
| void *p = realloc(ptr, size); | |
| if (p == NULL && size != 0) | |
| ap_abort_on_oom(); | |
| return p; | |
| } | |
| #if APR_HAS_THREADS | |
| #if AP_HAS_THREAD_LOCAL && !APR_VERSION_AT_LEAST(1,8,0) | |
| static AP_THREAD_LOCAL apr_thread_t *current_thread = NULL; | |
| #endif | |
| struct thread_ctx { | |
| apr_thread_start_t func; | |
| void *data; | |
| }; | |
| static void *APR_THREAD_FUNC thread_start(apr_thread_t *thread, void *data) | |
| { | |
| struct thread_ctx *ctx = data; | |
| /* Don't let the thread's pool allocator with no limits, though there | |
| * is possibly no allocator with APR <= 1.7 and APR_POOL_DEBUG. | |
| */ | |
| { | |
| apr_pool_t *tp = apr_thread_pool_get(thread); | |
| apr_allocator_t *ta = apr_pool_allocator_get(tp); | |
| if (ta) { | |
| apr_allocator_max_free_set(ta, ap_max_mem_free); | |
| } | |
| } | |
| #if AP_HAS_THREAD_LOCAL && !APR_VERSION_AT_LEAST(1,8,0) | |
| current_thread = thread; | |
| #endif | |
| 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); | |
| } | |
| 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)) | |
| #if APR_VERSION_AT_LEAST(1,8,0) | |
| || (rv = apr_threadattr_max_free_set(attr, ap_max_mem_free)) | |
| #endif | |
| || (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; | |
| } | |
| #if !APR_VERSION_AT_LEAST(1,8,0) | |
| AP_DECLARE(apr_status_t) ap_thread_current_create(apr_thread_t **current, | |
| apr_threadattr_t *attr, | |
| apr_pool_t *pool) | |
| { | |
| #if AP_HAS_THREAD_LOCAL | |
| apr_status_t rv; | |
| apr_allocator_t *ta; | |
| apr_abortfunc_t abort_fn; | |
| apr_os_thread_t osthd; | |
| apr_pool_t *p; | |
| *current = ap_thread_current(); | |
| if (*current) { | |
| return APR_EEXIST; | |
| } | |
| abort_fn = (pool) ? apr_pool_abort_get(pool) : NULL; | |
| rv = apr_allocator_create(&ta); | |
| if (rv != APR_SUCCESS) { | |
| if (abort_fn) | |
| abort_fn(rv); | |
| return rv; | |
| } | |
| /* Don't let the thread's pool allocator with no limits */ | |
| apr_allocator_max_free_set(ta, ap_max_mem_free); | |
| rv = apr_pool_create_unmanaged_ex(&p, abort_fn, ta); | |
| if (rv != APR_SUCCESS) { | |
| return rv; | |
| } | |
| apr_allocator_owner_set(ta, p); | |
| osthd = apr_os_thread_current(); | |
| rv = apr_os_thread_put(current, &osthd, p); | |
| if (rv != APR_SUCCESS) { | |
| apr_pool_destroy(p); | |
| return rv; | |
| } | |
| current_thread = *current; | |
| return APR_SUCCESS; | |
| #else | |
| return APR_ENOTIMPL; | |
| #endif | |
| } | |
| 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) */ | |
| #endif /* APR_HAS_THREADS */ | |
| AP_DECLARE(void) ap_get_sload(ap_sload_t *ld) | |
| { | |
| int i, j, server_limit, thread_limit; | |
| int ready = 0; | |
| int busy = 0; | |
| int total; | |
| ap_generation_t mpm_generation; | |
| /* preload errored fields, we overwrite */ | |
| ld->idle = -1; | |
| ld->busy = -1; | |
| ld->bytes_served = 0; | |
| ld->access_count = 0; | |
| ap_mpm_query(AP_MPMQ_GENERATION, &mpm_generation); | |
| ap_mpm_query(AP_MPMQ_HARD_LIMIT_THREADS, &thread_limit); | |
| ap_mpm_query(AP_MPMQ_HARD_LIMIT_DAEMONS, &server_limit); | |
| for (i = 0; i < server_limit; i++) { | |
| process_score *ps; | |
| ps = ap_get_scoreboard_process(i); | |
| for (j = 0; j < thread_limit; j++) { | |
| int res; | |
| worker_score *ws = NULL; | |
| ws = &ap_scoreboard_image->servers[i][j]; | |
| res = ws->status; | |
| if (!ps->quiescing && ps->pid) { | |
| if (res == SERVER_READY && ps->generation == mpm_generation) { | |
| ready++; | |
| } | |
| else if (res != SERVER_DEAD && | |
| res != SERVER_STARTING && res != SERVER_IDLE_KILL && | |
| ps->generation == mpm_generation) { | |
| busy++; | |
| } | |
| } | |
| if (ap_extended_status && !ps->quiescing && ps->pid) { | |
| if (ws->access_count != 0 | |
| || (res != SERVER_READY && res != SERVER_DEAD)) { | |
| ld->access_count += ws->access_count; | |
| ld->bytes_served += ws->bytes_served; | |
| } | |
| } | |
| } | |
| } | |
| total = busy + ready; | |
| if (total) { | |
| ld->idle = ready * 100 / total; | |
| ld->busy = busy * 100 / total; | |
| } | |
| } | |
| AP_DECLARE(void) ap_get_loadavg(ap_loadavg_t *ld) | |
| { | |
| /* preload errored fields, we overwrite */ | |
| ld->loadavg = -1.0; | |
| ld->loadavg5 = -1.0; | |
| ld->loadavg15 = -1.0; | |
| #if HAVE_GETLOADAVG | |
| { | |
| double la[3]; | |
| int num; | |
| num = getloadavg(la, 3); | |
| if (num > 0) { | |
| ld->loadavg = (float)la[0]; | |
| } | |
| if (num > 1) { | |
| ld->loadavg5 = (float)la[1]; | |
| } | |
| if (num > 2) { | |
| ld->loadavg15 = (float)la[2]; | |
| } | |
| } | |
| #endif | |
| } | |
| static const char * const pw_cache_note_name = "conn_cache_note"; | |
| struct pw_cache { | |
| /* varbuf contains concatenated password and hash */ | |
| struct ap_varbuf vb; | |
| apr_size_t pwlen; | |
| apr_status_t result; | |
| }; | |
| AP_DECLARE(apr_status_t) ap_password_validate(request_rec *r, | |
| const char *username, | |
| const char *passwd, | |
| const char *hash) | |
| { | |
| struct pw_cache *cache; | |
| apr_size_t hashlen; | |
| cache = (struct pw_cache *)apr_table_get(r->connection->notes, pw_cache_note_name); | |
| if (cache != NULL) { | |
| if (strncmp(passwd, cache->vb.buf, cache->pwlen) == 0 | |
| && strcmp(hash, cache->vb.buf + cache->pwlen) == 0) { | |
| return cache->result; | |
| } | |
| /* make ap_varbuf_grow below not copy the old data */ | |
| cache->vb.strlen = 0; | |
| } | |
| else { | |
| cache = apr_palloc(r->connection->pool, sizeof(struct pw_cache)); | |
| ap_varbuf_init(r->connection->pool, &cache->vb, 0); | |
| apr_table_setn(r->connection->notes, pw_cache_note_name, (void *)cache); | |
| } | |
| cache->pwlen = strlen(passwd); | |
| hashlen = strlen(hash); | |
| ap_varbuf_grow(&cache->vb, cache->pwlen + hashlen + 1); | |
| memcpy(cache->vb.buf, passwd, cache->pwlen); | |
| memcpy(cache->vb.buf + cache->pwlen, hash, hashlen + 1); | |
| cache->result = apr_password_validate(passwd, hash); | |
| return cache->result; | |
| } | |
| AP_DECLARE(char *) ap_get_exec_line(apr_pool_t *p, | |
| const char *cmd, | |
| const char * const * argv) | |
| { | |
| char buf[MAX_STRING_LEN]; | |
| apr_procattr_t *procattr; | |
| apr_proc_t *proc; | |
| apr_file_t *fp; | |
| apr_size_t nbytes = 1; | |
| char c; | |
| int k; | |
| if (apr_procattr_create(&procattr, p) != APR_SUCCESS) | |
| return NULL; | |
| if (apr_procattr_io_set(procattr, APR_FULL_BLOCK, APR_FULL_BLOCK, | |
| APR_FULL_BLOCK) != APR_SUCCESS) | |
| return NULL; | |
| if (apr_procattr_dir_set(procattr, | |
| ap_make_dirstr_parent(p, cmd)) != APR_SUCCESS) | |
| return NULL; | |
| if (apr_procattr_cmdtype_set(procattr, APR_PROGRAM) != APR_SUCCESS) | |
| return NULL; | |
| proc = apr_pcalloc(p, sizeof(apr_proc_t)); | |
| if (apr_proc_create(proc, cmd, argv, NULL, procattr, p) != APR_SUCCESS) | |
| return NULL; | |
| fp = proc->out; | |
| if (fp == NULL) | |
| return NULL; | |
| /* XXX: we are reading 1 byte at a time here */ | |
| for (k = 0; apr_file_read(fp, &c, &nbytes) == APR_SUCCESS | |
| && nbytes == 1 && (k < MAX_STRING_LEN-1) ; ) { | |
| if (c == '\n' || c == '\r') | |
| break; | |
| buf[k++] = c; | |
| } | |
| buf[k] = '\0'; | |
| apr_file_close(fp); | |
| 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 unsigned char ucharmap[256] = { | |
| 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 unsigned char ucharmap[256] = { | |
| 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(char *) ap_cstr_stripws(char *str) | |
| { | |
| char *end = str + strlen(str); | |
| while (end > str && apr_isspace(end[-1])) { | |
| --end; | |
| } | |
| *end = '\0'; | |
| return end; | |
| } | |
| AP_DECLARE(int) ap_cstr_casecmp(const char *s1, const char *s2) | |
| { | |
| const unsigned char *u1 = (const unsigned char *)s1; | |
| const unsigned char *u2 = (const unsigned char *)s2; | |
| for (;;) { | |
| const int c2 = ucharmap[*u2++]; | |
| const int cmp = (int)ucharmap[*u1++] - c2; | |
| /* Not necessary to test for !c1, this is caught by cmp */ | |
| if (cmp || !c2) | |
| return cmp; | |
| } | |
| } | |
| AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n) | |
| { | |
| const unsigned char *u1 = (const unsigned char *)s1; | |
| const unsigned char *u2 = (const unsigned char *)s2; | |
| while (n--) { | |
| const int c2 = ucharmap[*u2++]; | |
| const int cmp = (int)ucharmap[*u1++] - c2; | |
| /* Not necessary to test for !c1, this is caught by cmp */ | |
| if (cmp || !c2) | |
| return cmp; | |
| } | |
| 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; | |
| } | |
| #if APR_VERSION_AT_LEAST(1,8,0) | |
| AP_DECLARE(int) ap_memeq_timingsafe(const void *buf1, const void *buf2, | |
| apr_size_t n) | |
| { | |
| return apr_memeq_timingsafe(buf1, buf2, n); | |
| } | |
| AP_DECLARE(int) ap_streq_timingsafe(const char *sec1, const char *str2) | |
| { | |
| return apr_streq_timingsafe(sec1, str2); | |
| } | |
| AP_DECLARE(int) ap_strneq_timingsafe(const char *sec1, const char *str2, | |
| apr_size_t n) | |
| { | |
| return apr_strneq_timingsafe(sec1, str2, n); | |
| } | |
| #else /* !APR_VERSION_AT_LEAST(1,8,0) */ | |
| /* A volatile variable which is always zero but allows to block the compiler | |
| * from optimizing or eliding code using it. Volatile forces the compiler to | |
| * emit a memory load for which no value can be assumed, so for instance an | |
| * add/sub/xor/or with "optblocker" is a noop that will hide the result to | |
| * the optimizer. | |
| */ | |
| static volatile const apr_uint32_t optblocker; | |
| /* Return whether x is not zero, with no branching controlled by x. | |
| * | |
| * Taken from the cryptoint library (public domain) by D. J. Bernstein, | |
| * which provides timing attacks safe integer operations/primitives. | |
| * Code: | |
| * https://lib.mceliece.org/libmceliece-20250507/cryptoint/crypto_uint32.h | |
| * Paper: | |
| * https://cr.yp.to/papers/cryptoint-20250424.pdf | |
| */ | |
| #if __has_attribute(always_inline) | |
| __attribute__((always_inline)) | |
| #endif | |
| static APR_INLINE int test_nonzero_timingsafe(apr_uint32_t x) | |
| { | |
| x |= -x; /* sets the most significant bit unless x == 0 */ | |
| /* shift bit 31 (MSB) to bit 0 */ | |
| x >>= 32-6; /* keep 6 bits */ | |
| x += optblocker; /* lose the optimizer */ | |
| x >>= 5; /* keep the (original) MSB only */ | |
| /* x is now 0 or 1 */ | |
| return x & INT_MAX; | |
| } | |
| AP_DECLARE(int) ap_memeq_timingsafe(const void *buf1, const void *buf2, | |
| apr_size_t n) | |
| { | |
| apr_uint32_t diff = 0; | |
| volatile apr_size_t count = n; /* prevent loop unrolling */ | |
| apr_size_t i = 0; | |
| for (; i < count; ++i) { | |
| const unsigned char c1 = ((volatile const unsigned char *)buf1)[i]; | |
| const unsigned char c2 = ((volatile const unsigned char *)buf2)[i]; | |
| diff |= c1 ^ c2; /* sets diff to non-zero whenever c1 != c2 */ | |
| } | |
| /* (diff == 0) <=> (diff != 0) ^ 1 */ | |
| return test_nonzero_timingsafe(diff) ^ 1; | |
| } | |
| AP_DECLARE(int) ap_streq_timingsafe(const char *sec1, const char *str2) | |
| { | |
| apr_uint32_t diff = 0; | |
| apr_size_t i1 = 0, i2 = 0; | |
| for (;; ++i2) { | |
| const unsigned char c1 = ((volatile const unsigned char *)sec1)[i1]; | |
| const unsigned char c2 = ((volatile const unsigned char *)str2)[i2]; | |
| diff |= c1 ^ c2; /* sets diff to non-zero whenever c1 != c2 */ | |
| /* Not a shortest/longest match because an attacker would usually know | |
| * one of the strings and could then determine the length of the other. | |
| * So assume only sec1 and its length are secret and stop the loop at | |
| * the end of str2. If sec1 is shorter than str2 the loop will continue | |
| * by comparing the rest of str2 with the trailing NUL byte of sec1. | |
| * In any case since the diff above is computed up to and including a | |
| * NUL byte, only the same content and length will raise match. | |
| */ | |
| if (!c2) { | |
| break; | |
| } | |
| /* Don't go above sec1's NUL byte */ | |
| i1 += test_nonzero_timingsafe(c1); | |
| } | |
| /* (diff == 0) <=> (diff != 0) ^ 1 */ | |
| return test_nonzero_timingsafe(diff) ^ 1; | |
| } | |
| AP_DECLARE(int) ap_strneq_timingsafe(const char *sec1, const char *str2, | |
| apr_size_t n) | |
| { | |
| apr_uint32_t diff = 0; | |
| volatile apr_size_t count = n; /* prevent loop unrolling */ | |
| apr_size_t i1 = 0, i2 = 0; | |
| for (; i2 < count; ++i2) { | |
| const unsigned char c1 = ((volatile const unsigned char *)sec1)[i1]; | |
| const unsigned char c2 = ((volatile const unsigned char *)str2)[i2]; | |
| diff |= c1 ^ c2; /* sets diff to non-zero whenever c1 != c2 */ | |
| /* Not a shortest/longest match because an attacker would usually know | |
| * one of the strings and could then determine the length of the other. | |
| * So assume only sec1 and its length are secret and stop the loop at | |
| * the end of str2. If sec1 is shorter than str2 the loop will continue | |
| * by comparing the rest of str2 with the trailing NUL byte of sec1. | |
| * In any case since the diff above is computed up to and including a | |
| * NUL byte, only the same content and length will raise match. | |
| */ | |
| if (!c2) { | |
| break; | |
| } | |
| /* Don't go above sec1's NUL byte */ | |
| i1 += test_nonzero_timingsafe(c1); | |
| } | |
| /* (diff == 0) <=> (diff != 0) ^ 1 */ | |
| return test_nonzero_timingsafe(diff) ^ 1; | |
| } | |
| #endif /* !APR_VERSION_AT_LEAST(1,8,0) */ |