| /* 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 = &start[strlen(start)]; |
| while (--end >= start && apr_isspace(*end)) |
| *end = '\0'; |
| /* Zap leading whitespace by shifting */ |
| if (start != buf) |
| memmove(buf, start, end - start + 2); |
| #ifdef DEBUG_CFG_LINES |
| ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, NULL, APLOGNO(00555) "Read config: '%s'", buf); |
| #endif |
| return end - start + 1; |
| } |
| |
| /* 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; |
| |
| cmd = apr_palloc(p, 2 * strlen(str) + 1); /* Be safe */ |
| 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) |
| { |
| return ap_escape_path_segment_buffer(apr_palloc(p, 3 * strlen(segment) + 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') |
| */ |
| char *copy = apr_palloc(p, 3 * strlen(path) + 3 + 1); |
| const unsigned char *s = (const unsigned char *)path; |
| unsigned char *d = (unsigned char *)copy; |
| unsigned c; |
| |
| 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) |
| { |
| return ap_escape_urlencoded_buffer(apr_palloc(p, 3 * strlen(buffer) + 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; |
| } |
| #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(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; |
| } |