Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Apache/server/   (Apache Software Stiftung Version 2.4.65©)  Datei vom 26.5.2024 mit Größe 109 kB image not shown  

Quelle  util.c

  Sprache: C
 

/* 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"

#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"

/* 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[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;
    /* 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)
{
    char *copy = apr_palloc(p, 3 * strlen(path) + 3);
    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;
}

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:
 *
 * 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':
        CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX));
        check = apr_time_from_sec(tout);
        break;
        /* Time is in hours */
    case 'h':
        CHECK_OVERFLOW(tout, apr_time_sec(APR_INT64_MAX / 3600));
        check = apr_time_from_sec(tout * 3600);
        break;
    case 'm':
        switch (*(++time_str)) {
        /* Time is in milliseconds */
        case 's':
            CHECK_OVERFLOW(tout, apr_time_as_msec(APR_INT64_MAX));
            check = apr_time_from_msec(tout);
            break;
        /* Time is in minutes */
        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)));
}

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 APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL)

#define ap_thread_current_create apr_thread_current_create

#else /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */

#if AP_HAS_THREAD_LOCAL

struct thread_ctx {
    apr_thread_start_t func;
    void *data;
};

static AP_THREAD_LOCAL apr_thread_t *current_thread = NULL;

static void *APR_THREAD_FUNC thread_start(apr_thread_t *thread, void *data)
{
    struct thread_ctx *ctx = data;

    current_thread = thread;
    return ctx->func(thread, ctx->data);
}

AP_DECLARE(apr_status_t) ap_thread_create(apr_thread_t **thread, 
                                          apr_threadattr_t *attr, 
                                          apr_thread_start_t func, 
                                          void *data, apr_pool_t *pool)
{
    struct thread_ctx *ctx = apr_palloc(pool, sizeof(*ctx));

    ctx->func = func;
    ctx->data = data;
    return apr_thread_create(thread, attr, thread_start, ctx, pool);
}

#endif /* AP_HAS_THREAD_LOCAL */

AP_DECLARE(apr_status_t) ap_thread_current_create(apr_thread_t **current,
                                                  apr_threadattr_t *attr,
                                                  apr_pool_t *pool)
{
    apr_status_t rv;
    apr_abortfunc_t abort_fn = apr_pool_abort_get(pool);
    apr_allocator_t *allocator;
    apr_os_thread_t osthd;
    apr_pool_t *p;

    *current = ap_thread_current();
    if (*current) {
        return APR_EEXIST;
    }

    rv = apr_allocator_create(&allocator);
    if (rv != APR_SUCCESS) {
        if (abort_fn)
            abort_fn(rv);
        return rv;
    }
    rv = apr_pool_create_unmanaged_ex(&p, abort_fn, allocator);
    if (rv != APR_SUCCESS) {
        apr_allocator_destroy(allocator);
        return rv;
    }
    apr_allocator_owner_set(allocator, p);

    osthd = apr_os_thread_current();
    rv = apr_os_thread_put(current, &osthd, p);
    if (rv != APR_SUCCESS) {
        apr_pool_destroy(p);
        return rv;
    }

#if AP_HAS_THREAD_LOCAL
    current_thread = *current;
#endif
    return APR_SUCCESS;
}

AP_DECLARE(void) ap_thread_current_after_fork(void)
{
#if AP_HAS_THREAD_LOCAL
    current_thread = NULL;
#endif
}

AP_DECLARE(apr_thread_t *) ap_thread_current(void)
{
#if AP_HAS_THREAD_LOCAL
    return current_thread;
#else
    return NULL;
#endif
}

#endif /* APR_VERSION_AT_LEAST(1,8,0) && !defined(AP_NO_THREAD_LOCAL) */

static apr_status_t main_thread_cleanup(void *arg)
{
    apr_thread_t *thd = arg;
    apr_pool_destroy(apr_thread_pool_get(thd));
    return APR_SUCCESS;
}

AP_DECLARE(apr_status_t) ap_thread_main_create(apr_thread_t **thread,
                                               apr_pool_t *pool)
{
    apr_status_t rv;
    apr_threadattr_t *attr = NULL;

    /* Create an apr_thread_t for the main child thread to set up its Thread
     * Local Storage. Since it's detached and won't apr_thread_exit(), destroy
     * its pool before exiting via a cleanup of the given pool.
     */

    if ((rv = apr_threadattr_create(&attr, pool))
            || (rv = apr_threadattr_detach_set(attr, 1))
            || (rv = ap_thread_current_create(thread, attr, pool))) {
        *thread = NULL;
        return rv;
    }

    apr_pool_cleanup_register(pool, *thread, main_thread_cleanup,
                              apr_pool_cleanup_null);
    return APR_SUCCESS;
}

#endif /* APR_HAS_THREADS */

AP_DECLARE(void) ap_get_sload(ap_sload_t *ld)
{
    int i, j, server_limit, thread_limit;
    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
}

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 short ucharmap[] = {
    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 short ucharmap[] = {
    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 *str1 = (const unsigned char *)s1;
    const unsigned char *str2 = (const unsigned char *)s2;
    for (;;)
    {
        const int c1 = (int)(*str1);
        const int c2 = (int)(*str2);
        const int cmp = ucharmap[c1] - ucharmap[c2];
        /* Not necessary to test for !c2, this is caught by cmp */
        if (cmp || !c1)
            return cmp;
        str1++;
        str2++;
    }
}

AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n)
{
    const unsigned char *str1 = (const unsigned char *)s1;
    const unsigned char *str2 = (const unsigned char *)s2;
    while (n--)
    {
        const int c1 = (int)(*str1);
        const int c2 = (int)(*str2);
        const int cmp = ucharmap[c1] - ucharmap[c2];
        /* Not necessary to test for !c2, this is caught by cmp */
        if (cmp || !c1)
            return cmp;
        str1++;
        str2++;
    }
    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;
}

Messung V0.5 in Prozent
C=96 H=90 G=93

¤ Dauer der Verarbeitung: 0.59 Sekunden  (vorverarbeitet am  2026-04-25) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.