]> granicus.if.org Git - apache/commitdiff
relocate apreq/library as a srclib
authorPhilip M. Gollucci <pgollucci@apache.org>
Thu, 10 Nov 2011 18:04:56 +0000 (18:04 +0000)
committerPhilip M. Gollucci <pgollucci@apache.org>
Thu, 10 Nov 2011 18:04:56 +0000 (18:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1200454 13f79535-47bb-0310-9956-ffa450edef68

22 files changed:
srclib/libapreq/Makefile.am [new file with mode: 0644]
srclib/libapreq/cookie.c [new file with mode: 0644]
srclib/libapreq/error.c [new file with mode: 0644]
srclib/libapreq/module.c [new file with mode: 0644]
srclib/libapreq/module_cgi.c [new file with mode: 0644]
srclib/libapreq/module_custom.c [new file with mode: 0644]
srclib/libapreq/param.c [new file with mode: 0644]
srclib/libapreq/parser.c [new file with mode: 0644]
srclib/libapreq/parser_header.c [new file with mode: 0644]
srclib/libapreq/parser_multipart.c [new file with mode: 0644]
srclib/libapreq/parser_urlencoded.c [new file with mode: 0644]
srclib/libapreq/t/Makefile.am [new file with mode: 0644]
srclib/libapreq/t/at.c [new file with mode: 0644]
srclib/libapreq/t/at.h [new file with mode: 0644]
srclib/libapreq/t/cookie.c [new file with mode: 0644]
srclib/libapreq/t/error.c [new file with mode: 0644]
srclib/libapreq/t/params.c [new file with mode: 0644]
srclib/libapreq/t/parsers.c [new file with mode: 0644]
srclib/libapreq/t/util.c [new file with mode: 0644]
srclib/libapreq/t/version.c [new file with mode: 0644]
srclib/libapreq/util.c [new file with mode: 0644]
srclib/libapreq/version.c [new file with mode: 0644]

diff --git a/srclib/libapreq/Makefile.am b/srclib/libapreq/Makefile.am
new file mode 100644 (file)
index 0000000..122e522
--- /dev/null
@@ -0,0 +1,42 @@
+EXTRA_DIST = t
+AM_CPPFLAGS = @APR_INCLUDES@
+BUILT_SOURCES = @APR_LA@ @APU_LA@
+lib_LTLIBRARIES = libapreq2.la
+libapreq2_la_SOURCES = util.c version.c cookie.c param.c parser.c \
+                       parser_urlencoded.c parser_header.c parser_multipart.c \
+                      module.c module_custom.c module_cgi.c error.c
+libapreq2_la_LDFLAGS = -version-info @APREQ_LIBTOOL_VERSION@ @APR_LTFLAGS@ @APR_LIBS@
+
+test: all
+       cd t; $(MAKE) test
+
+if ENABLE_PROFILE
+
+AM_CFLAGS= -pg -fprofile-arcs -ftest-coverage
+
+clean-local:
+       -rm *.bb *.bbg *.da *.gcov
+       cd t; $(MAKE) clean
+else
+
+clean-local:
+       cd t; $(MAKE) clean
+endif
+
+distclean-am: clean-am distclean-compile distclean-generic \
+       distclean-tags
+       cd t; $(MAKE) distclean
+
+if BUILD_APR
+
+@APR_LA@:
+       cd `@APR_CONFIG@ --srcdir` && $(MAKE)
+
+endif
+
+if BUILD_APU
+
+@APU_LA@: @APR_LA@
+       cd `@APU_CONFIG@ --srcdir` && $(MAKE)
+
+endif
diff --git a/srclib/libapreq/cookie.c b/srclib/libapreq/cookie.c
new file mode 100644 (file)
index 0000000..417df9e
--- /dev/null
@@ -0,0 +1,537 @@
+/*
+**  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.
+*/
+
+#include "apreq_cookie.h"
+#include "apreq_error.h"
+#include "apreq_util.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_date.h"
+
+
+#define RFC      1
+#define NETSCAPE 0
+
+#define ADD_COOKIE(j,c) apreq_value_table_add(&c->v, j)
+
+APREQ_DECLARE(void) apreq_cookie_expires(apreq_cookie_t *c,
+                                         const char *time_str)
+{
+    if (time_str == NULL) {
+        c->max_age = -1;
+        return;
+    }
+
+    if (!strcasecmp(time_str, "now"))
+        c->max_age = 0;
+    else {
+        c->max_age = apr_date_parse_rfc(time_str);
+        if (c->max_age == APR_DATE_BAD)
+            c->max_age = apr_time_from_sec(apreq_atoi64t(time_str));
+        else
+            c->max_age -= apr_time_now();
+    }
+}
+
+static apr_status_t apreq_cookie_attr(apr_pool_t *p,
+                                      apreq_cookie_t *c,
+                                      const char *attr,
+                                      apr_size_t alen,
+                                      const char *val,
+                                      apr_size_t vlen)
+{
+    if (alen < 2)
+        return APR_EBADARG;
+
+    if ( attr[0] ==  '-' || attr[0] == '$' ) {
+        ++attr;
+        --alen;
+    }
+
+    switch (apr_tolower(*attr)) {
+
+    case 'n': /* name is not an attr */
+        return APR_ENOTIMPL;
+
+    case 'v': /* version; value is not an attr */
+        if (alen == 5 && strncasecmp(attr,"value", 5) == 0)
+            return APR_ENOTIMPL;
+
+        while (!apr_isdigit(*val)) {
+            if (vlen == 0)
+                return APREQ_ERROR_BADSEQ;
+            ++val;
+            --vlen;
+        }
+        apreq_cookie_version_set(c, *val - '0');
+        return APR_SUCCESS;
+
+    case 'e': case 'm': /* expires, max-age */
+        apreq_cookie_expires(c, val);
+        return APR_SUCCESS;
+
+    case 'd':
+        c->domain = apr_pstrmemdup(p,val,vlen);
+        return APR_SUCCESS;
+
+    case 'p':
+        if (alen != 4)
+            break;
+        if (!strncasecmp("port", attr, 4)) {
+            c->port = apr_pstrmemdup(p,val,vlen);
+            return APR_SUCCESS;
+        }
+        else if (!strncasecmp("path", attr, 4)) {
+            c->path = apr_pstrmemdup(p,val,vlen);
+            return APR_SUCCESS;
+        }
+        break;
+
+    case 'c':
+        if (!strncasecmp("commentURL", attr, 10)) {
+            c->commentURL = apr_pstrmemdup(p,val,vlen);
+            return APR_SUCCESS;
+        }
+        else if (!strncasecmp("comment", attr, 7)) {
+            c->comment = apr_pstrmemdup(p,val,vlen);
+            return APR_SUCCESS;
+        }
+        break;
+
+    case 's':
+        if (vlen > 0 && *val != '0' && strncasecmp("off",val,vlen))
+            apreq_cookie_secure_on(c);
+        else
+            apreq_cookie_secure_off(c);
+        return APR_SUCCESS;
+
+    case 'h': /* httponly */
+        if (vlen > 0 && *val != '0' && strncasecmp("off",val,vlen))
+            apreq_cookie_httponly_on(c);
+        else
+            apreq_cookie_httponly_off(c);
+        return APR_SUCCESS;
+
+    };
+
+    return APR_ENOTIMPL;
+}
+
+APREQ_DECLARE(apreq_cookie_t *) apreq_cookie_make(apr_pool_t *p,
+                                                  const char *name,
+                                                  const apr_size_t nlen,
+                                                  const char *value,
+                                                  const apr_size_t vlen)
+{
+    apreq_cookie_t *c;
+    apreq_value_t *v;
+
+    c = apr_palloc(p, nlen + vlen + 1 + sizeof *c);
+
+    if (c == NULL)
+        return NULL;
+
+    *(const apreq_value_t **)&v = &c->v;
+
+    if (vlen > 0 && value != NULL)
+        memcpy(v->data, value, vlen);
+    v->data[vlen] = 0;
+    v->dlen = vlen;
+    v->name = v->data + vlen + 1;
+    if (nlen && name != NULL)
+        memcpy(v->name, name, nlen);
+    v->name[nlen] = 0;
+    v->nlen = nlen;
+
+    c->path = NULL;
+    c->domain = NULL;
+    c->port = NULL;
+    c->comment = NULL;
+    c->commentURL = NULL;
+    c->max_age = -1;    /* session cookie is the default */
+    c->flags = 0;
+
+
+    return c;
+}
+
+static APR_INLINE
+apr_status_t get_pair(apr_pool_t *p, const char **data,
+                      const char **n, apr_size_t *nlen,
+                      const char **v, apr_size_t *vlen, unsigned unquote)
+{
+    const char *hdr, *key, *val;
+    int nlen_set = 0;
+    hdr = *data;
+
+    while (apr_isspace(*hdr) || *hdr == '=')
+        ++hdr;
+
+    key = hdr;
+    *n = hdr;
+
+ scan_name:
+
+    switch (*hdr) {
+
+    case 0:
+    case ';':
+    case ',':
+        if (!nlen_set)
+            *nlen = hdr - key;
+        *v = hdr;
+        *vlen = 0;
+        *data = hdr;
+        return *nlen ? APREQ_ERROR_NOTOKEN : APREQ_ERROR_BADCHAR;
+
+    case '=':
+        if (!nlen_set) {
+            *nlen = hdr - key;
+            nlen_set = 1;
+        }
+        break;
+
+    case ' ':
+    case '\t':
+    case '\r':
+    case '\n':
+        if (!nlen_set) {
+            *nlen = hdr - key;
+            nlen_set = 1;
+        }
+        /* fall thru */
+
+    default:
+        ++hdr;
+        goto scan_name;
+    }
+
+    val = hdr + 1;
+
+    while (apr_isspace(*val))
+        ++val;
+
+    if (*val == '"') {
+        unsigned saw_backslash = 0;
+        for (*v = (unquote) ? ++val : val++; *val; ++val) {
+            switch (*val) {
+            case '"':
+                *data = val + 1;
+
+                if (!unquote) {
+                    *vlen = (val - *v) + 1;
+                }
+                else if (!saw_backslash) {
+                    *vlen = val - *v;
+                }
+                else {
+                    char *dest = apr_palloc(p, val - *v), *d = dest;
+                    const char *s = *v;
+                    while (s < val) {
+                        if (*s == '\\')
+                            ++s;
+                        *d++ = *s++;
+                    }
+
+                    *vlen = d - dest;
+                    *v = dest;
+                }
+
+                return APR_SUCCESS;
+            case '\\':
+                saw_backslash = 1;
+                if (val[1] != 0)
+                    ++val;
+            default:
+                break;
+            }
+        }
+        /* bad sequence: no terminating quote found */
+        *data = val;
+        return APREQ_ERROR_BADSEQ;
+    }
+    else {
+        /* value is not wrapped in quotes */
+        for (*v = val; *val; ++val) {
+            switch (*val) {
+            case ';':
+            case ',':
+            case ' ':
+            case '\t':
+            case '\r':
+            case '\n':
+                *data = val;
+                *vlen = val - *v;
+                return APR_SUCCESS;
+            default:
+                break;
+            }
+        }
+    }
+
+    *data = val;
+    *vlen = val - *v;
+
+    return APR_SUCCESS;
+}
+
+
+
+APREQ_DECLARE(apr_status_t)apreq_parse_cookie_header(apr_pool_t *p,
+                                                     apr_table_t *j,
+                                                     const char *hdr)
+{
+    apreq_cookie_t *c;
+    unsigned version;
+    apr_status_t rv = APR_SUCCESS;
+
+ parse_cookie_header:
+
+    c = NULL;
+    version = NETSCAPE;
+
+    while (apr_isspace(*hdr))
+        ++hdr;
+
+
+    if (*hdr == '$' && strncasecmp(hdr, "$Version", 8) == 0) {
+        /* XXX cheat: assume "$Version" => RFC Cookie header */
+        version = RFC;
+    skip_version_string:
+        switch (*hdr++) {
+        case 0:
+            return rv;
+        case ',':
+            goto parse_cookie_header;
+        case ';':
+            break;
+        default:
+            goto skip_version_string;
+        }
+    }
+
+    for (;;) {
+        apr_status_t status;
+        const char *name, *value;
+        apr_size_t nlen, vlen;
+
+        while (*hdr == ';' || apr_isspace(*hdr))
+            ++hdr;
+
+        switch (*hdr) {
+
+        case 0:
+            /* this is the normal exit point */
+            if (c != NULL) {
+                ADD_COOKIE(j, c);
+            }
+            return rv;
+
+        case ',':
+            ++hdr;
+            if (c != NULL) {
+                ADD_COOKIE(j, c);
+            }
+            goto parse_cookie_header;
+
+        case '$':
+            ++hdr;
+            if (c == NULL) {
+                rv = APREQ_ERROR_BADCHAR;
+                goto parse_cookie_error;
+            }
+            else if (version == NETSCAPE) {
+                rv = APREQ_ERROR_MISMATCH;
+            }
+
+            status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 1);
+            if (status != APR_SUCCESS) {
+                rv = status;
+                goto parse_cookie_error;
+            }
+
+            status = apreq_cookie_attr(p, c, name, nlen, value, vlen);
+
+            switch (status) {
+
+            case APR_ENOTIMPL:
+                rv = APREQ_ERROR_BADATTR;
+                /* fall thru */
+
+            case APR_SUCCESS:
+                break;
+
+            default:
+                rv = status;
+                goto parse_cookie_error;
+            }
+
+            break;
+
+        default:
+            if (c != NULL) {
+                ADD_COOKIE(j, c);
+            }
+
+            status = get_pair(p, &hdr, &name, &nlen, &value, &vlen, 0);
+
+            if (status != APR_SUCCESS) {
+                c = NULL;
+                rv = status;
+                goto parse_cookie_error;
+            }
+
+            c = apreq_cookie_make(p, name, nlen, value, vlen);
+            apreq_cookie_tainted_on(c);
+            if (version != NETSCAPE)
+                apreq_cookie_version_set(c, version);
+        }
+    }
+
+ parse_cookie_error:
+
+    switch (*hdr) {
+
+    case 0:
+        return rv;
+
+    case ',':
+    case ';':
+        if (c != NULL)
+            ADD_COOKIE(j, c);
+        ++hdr;
+        goto parse_cookie_header;
+
+    default:
+        ++hdr;
+        goto parse_cookie_error;
+    }
+
+    /* not reached */
+    return rv;
+}
+
+
+APREQ_DECLARE(int) apreq_cookie_serialize(const apreq_cookie_t *c,
+                                          char *buf, apr_size_t len)
+{
+    /*  The format string must be large enough to accomodate all
+     *  of the cookie attributes.  The current attributes sum to
+     *  ~90 characters (w/ 6-8 padding chars per attr), so anything
+     *  over 100 should be fine.
+     */
+
+    unsigned version = apreq_cookie_version(c);
+    char format[128] = "%s=%s";
+    char *f = format + strlen(format);
+
+    /* XXX protocol enforcement (for debugging, anyway) ??? */
+
+    if (c->v.name == NULL)
+        return -1;
+
+#define NULL2EMPTY(attr) (attr ? attr : "")
+
+
+    if (version == NETSCAPE) {
+        char expires[APR_RFC822_DATE_LEN] = {0};
+
+#define ADD_NS_ATTR(name) do {                  \
+    if (c->name != NULL)                        \
+        strcpy(f, "; " #name "=%s");            \
+    else                                        \
+        strcpy(f, "%0.s");                      \
+    f += strlen(f);                             \
+} while (0)
+
+        ADD_NS_ATTR(path);
+        ADD_NS_ATTR(domain);
+
+        if (c->max_age != -1) {
+            strcpy(f, "; expires=%s");
+            apr_rfc822_date(expires, c->max_age + apr_time_now());
+            expires[7] = '-';
+            expires[11] = '-';
+        }
+        else
+            strcpy(f, "");
+
+        f += strlen(f);
+
+        if (apreq_cookie_is_secure(c))
+            strcpy(f, "; secure");
+
+        f += strlen(f);
+
+        if (apreq_cookie_is_httponly(c))
+            strcpy(f, "; HttpOnly");
+
+        return apr_snprintf(buf, len, format, c->v.name, c->v.data,
+           NULL2EMPTY(c->path), NULL2EMPTY(c->domain), expires);
+    }
+
+    /* c->version == RFC */
+
+    strcpy(f,"; Version=%u");
+    f += strlen(f);
+
+/* ensure RFC attributes are always quoted */
+#define ADD_RFC_ATTR(name) do {                 \
+    if (c->name != NULL)                        \
+        if (*c->name == '"')                    \
+            strcpy(f, "; " #name "=%s");        \
+        else                                    \
+            strcpy(f, "; " #name "=\"%s\"");    \
+    else                                        \
+        strcpy(f, "%0.s");                      \
+    f += strlen (f);                            \
+} while (0)
+
+    ADD_RFC_ATTR(path);
+    ADD_RFC_ATTR(domain);
+    ADD_RFC_ATTR(port);
+    ADD_RFC_ATTR(comment);
+    ADD_RFC_ATTR(commentURL);
+
+    strcpy(f, c->max_age != -1 ? "; max-age=%" APR_TIME_T_FMT : "");
+
+    f += strlen(f);
+
+    if (apreq_cookie_is_secure(c))
+        strcpy(f, "; secure");
+
+    f += strlen(f);
+
+    if (apreq_cookie_is_httponly(c))
+        strcpy(f, "; HttpOnly");
+
+    return apr_snprintf(buf, len, format, c->v.name, c->v.data, version,
+                        NULL2EMPTY(c->path), NULL2EMPTY(c->domain),
+                        NULL2EMPTY(c->port), NULL2EMPTY(c->comment),
+                        NULL2EMPTY(c->commentURL), apr_time_sec(c->max_age));
+}
+
+
+APREQ_DECLARE(char*) apreq_cookie_as_string(const apreq_cookie_t *c,
+                                            apr_pool_t *p)
+{
+    int n = apreq_cookie_serialize(c, NULL, 0);
+    char *s = apr_palloc(p, n + 1);
+    apreq_cookie_serialize(c, s, n + 1);
+    return s;
+}
+
diff --git a/srclib/libapreq/error.c b/srclib/libapreq/error.c
new file mode 100644 (file)
index 0000000..92a270b
--- /dev/null
@@ -0,0 +1,105 @@
+/* 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.
+ */
+
+#include "apreq_error.h"
+#include "apr_strings.h"
+
+/*
+ * stuffbuffer - like apr_cpystrn() but returns the address of the
+ * dest buffer instead of the address of the terminating '\0'
+ */
+static char *stuffbuffer(char *buf, apr_size_t bufsize, const char *s)
+{
+    apr_cpystrn(buf,s,bufsize);
+    return buf;
+}
+
+static const char *apreq_error_string(apr_status_t statcode)
+{
+    switch (statcode) {
+
+
+/* 0's: generic error status codes */
+
+    case APREQ_ERROR_GENERAL:
+        return "Internal apreq error";
+
+    case APREQ_ERROR_TAINTED:
+        return "Attempt to perform unsafe action with tainted data";
+
+
+/* 10's: malformed input */
+
+    case APREQ_ERROR_BADDATA:
+        return "Malformed input data";
+
+    case APREQ_ERROR_BADCHAR:
+        return "Invalid character";
+
+    case APREQ_ERROR_BADSEQ:
+        return "Invalid byte sequence";
+
+    case APREQ_ERROR_BADATTR:
+        return "Unrecognized attribute";
+
+    case APREQ_ERROR_BADHEADER:
+        return "Malformed header string";
+
+
+/* 20's: missing input */
+
+    case APREQ_ERROR_NODATA:
+        return "Missing input data";
+
+    case APREQ_ERROR_NOTOKEN:
+        return "Expected token not present";
+
+    case APREQ_ERROR_NOATTR:
+        return "Missing attribute";
+
+    case APREQ_ERROR_NOHEADER:
+        return "Missing header";
+
+    case APREQ_ERROR_NOPARSER:
+        return "Missing parser";
+
+
+/* 30's: configuration conflicts */
+
+    case APREQ_ERROR_MISMATCH:
+        return "Conflicting information";
+
+    case APREQ_ERROR_OVERLIMIT:
+        return "Exceeds configured maximum limit";
+
+    case APREQ_ERROR_NOTEMPTY:
+        return "Setting already configured";
+
+
+    default:
+        return "Error string not yet specified by apreq";
+    }
+}
+
+
+APREQ_DECLARE(char *) apreq_strerror(apr_status_t statcode, char *buf,
+                                 apr_size_t bufsize)
+{
+    if (statcode < APR_OS_START_USERERR || statcode >= APR_OS_START_EAIERR)
+        return apr_strerror(statcode, buf, bufsize);
+    return stuffbuffer(buf, bufsize, apreq_error_string(statcode));
+}
+
diff --git a/srclib/libapreq/module.c b/srclib/libapreq/module.c
new file mode 100644 (file)
index 0000000..9ba5a76
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+**  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.
+*/
+
+#include "apreq_module.h"
+#include "apreq_error.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_file_io.h"
+
+APREQ_DECLARE(apreq_param_t *)apreq_param(apreq_handle_t *req, const char *key)
+{
+    apreq_param_t *param = apreq_args_get(req, key);
+    if (param == NULL)
+        return apreq_body_get(req, key);
+    else
+        return param;
+}
+
+APREQ_DECLARE(apr_table_t *)apreq_params(apreq_handle_t *req, apr_pool_t *p)
+{
+    const apr_table_t *args, *body;
+    apreq_args(req, &args);
+    apreq_body(req, &body);
+
+    if (args != NULL)
+        if (body != NULL)
+            return apr_table_overlay(p, args, body);
+        else
+            return apr_table_copy(p, args);
+    else
+        if (body != NULL)
+            return apr_table_copy(p, body);
+        else
+            return NULL;
+
+}
+
+APREQ_DECLARE(apr_table_t *)apreq_cookies(apreq_handle_t *req, apr_pool_t *p)
+{
+    const apr_table_t *jar;
+    apreq_jar(req, &jar);
+
+    if (jar != NULL)
+        return apr_table_copy(p, jar);
+    else
+        return NULL;
+
+}
+
+
+/** @} */
diff --git a/srclib/libapreq/module_cgi.c b/srclib/libapreq/module_cgi.c
new file mode 100644 (file)
index 0000000..14fb39b
--- /dev/null
@@ -0,0 +1,1013 @@
+/*
+**  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.
+*/
+#include <assert.h>
+
+#define APR_WANT_STRFUNC
+#include "apr_want.h"
+#include "apreq_module.h"
+#include "apreq_error.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include "apr_env.h"
+#include "apreq_util.h"
+
+#define USER_DATA_KEY "apreq"
+
+/* Parroting APLOG_* ... */
+
+#define        CGILOG_EMERG    0       /* system is unusable */
+#define        CGILOG_ALERT    1       /* action must be taken immediately */
+#define        CGILOG_CRIT     2       /* critical conditions */
+#define        CGILOG_ERR      3       /* error conditions */
+#define        CGILOG_WARNING  4       /* warning conditions */
+#define        CGILOG_NOTICE   5       /* normal but significant condition */
+#define        CGILOG_INFO     6       /* informational */
+#define        CGILOG_DEBUG    7       /* debug-level messages */
+
+#define CGILOG_LEVELMASK 7
+#define CGILOG_MARK     __FILE__, __LINE__
+
+/** Interactive patch:
+ * TODO Don't use 65K buffer
+ * TODO Handle empty/non-existant parameters
+ * TODO Allow body elements to be files
+ * TODO When running body/get/cookies all at once, include previous cached
+ * values (and don't start at 0 in count)
+ * TODO What happens if user does apreq_param, but needs POST value - we'll
+ * never catch it now, as args param will match...
+ */
+
+struct cgi_handle {
+    struct apreq_handle_t       handle;
+
+    apr_table_t                 *jar, *args, *body;
+    apr_status_t                 jar_status,
+                                 args_status,
+                                 body_status;
+
+    apreq_parser_t              *parser;
+    apreq_hook_t                *hook_queue;
+    apreq_hook_t                *find_param;
+
+    const char                  *temp_dir;
+    apr_size_t                   brigade_limit;
+    apr_uint64_t                 read_limit;
+    apr_uint64_t                 bytes_read;
+
+    apr_bucket_brigade          *in;
+    apr_bucket_brigade          *tmpbb;
+
+    int                         interactive_mode;
+    const char                  *promptstr;
+    apr_file_t                  *sout, *sin;
+};
+
+#define CRLF "\015\012"
+static const char *nullstr = 0;
+#define DEFAULT_PROMPT "([$t] )$n(\\($l\\))([$d]): "
+#define MAX_PROMPT_NESTING_LEVELS 8
+#define MAX_BUFFER_SIZE 65536
+
+typedef struct {
+    const char *t_name;
+    int      t_val;
+} TRANS;
+
+static const TRANS priorities[] = {
+    {"emerg",   CGILOG_EMERG},
+    {"alert",   CGILOG_ALERT},
+    {"crit",    CGILOG_CRIT},
+    {"error",   CGILOG_ERR},
+    {"warn",    CGILOG_WARNING},
+    {"notice",  CGILOG_NOTICE},
+    {"info",    CGILOG_INFO},
+    {"debug",   CGILOG_DEBUG},
+    {NULL,      -1},
+};
+
+static char* chomp(char* str) {
+    apr_size_t p = strlen(str);
+    while (--p >= 0) {
+        switch ((char)(str[p])) {
+        case '\015':
+        case '\012':str[p]='\000';
+                    break;
+        default:return str;
+        }
+    }
+    return str;
+}
+
+/** TODO: Support wide-characters */
+/* prompt takes a apreq_handle and 2 strings - name and type - and prompts a
+   user for input via stdin/stdout.  used in interactive mode.
+   
+   name must be defined.  type can be null.
+   
+   we take the promptstring defined in the handle and interpolate variables as
+   follows:
+   
+   $n - name of the variable we're asking for (param 2 to prompt())
+   $t - type of the variable we're asking for - like cookie, get, post, etc
+        (param 3 to prompt())
+   parentheses - if a variable is surrounded by parentheses, and interpolates
+                 as null, then nothing else in the parentheses will be displayed
+                 Useful if you want a string to only show up if a given variable
+                 is available
+                 
+   These are planned for forward-compatibility, but the underlying features
+   need some love...  I left these in here just as feature reminders, rather
+   than completely removing them from the code - at least they provide sanity
+   testing of the default prompt & parentheses - issac
+   
+   $l - label for the param  - the end-user-developer can provide a textual
+        description of the param (name) being requested (currently unused in
+        lib)
+   $d - default value for the param (currently unused in lib)
+   
+*/
+static char *prompt(apreq_handle_t *handle, const char *name,
+                    const char *type) {
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    const char *defval = nullstr;
+    const char *label = NULL;
+    const char *cprompt;
+    char buf[MAX_PROMPT_NESTING_LEVELS][MAX_BUFFER_SIZE];
+    /* Array of current arg for given p-level */
+    char *start, curarg[MAX_PROMPT_NESTING_LEVELS] = ""; 
+    /* Parenthesis level (for argument/text grouping) */
+    int plevel; 
+
+    cprompt = req->promptstr - 1;
+    *buf[0] = plevel = 0;
+    start = buf[0];
+
+    while (*(++cprompt) != 0) {
+        switch (*cprompt) {
+        case '$':  /* interpolate argument; curarg[plevel] => 1 */
+            cprompt++;           
+            switch (*cprompt) {
+            case 't':
+                if (type != NULL) {
+                    strcpy(start, type);
+                    start += strlen(type);
+                    curarg[plevel] = 1;
+                } else {
+                    curarg[plevel] = curarg[plevel] | 0;
+                }
+                break;
+            case 'n':
+                /* Name can't be null :-) [If it can, we should 
+                 * immediately return NULL] */
+                strcpy(start, name);
+                start += strlen(name);
+                curarg[plevel] = 1;
+                break;
+            case 'l':
+                if (label != NULL) {
+                    strcpy(start, label);
+                    start += strlen(label);
+                    curarg[plevel] = 1;
+                } else {
+                    curarg[plevel] = curarg[plevel] | 0;
+                }
+                break;
+            case 'd':
+                /* TODO: Once null defaults are available, 
+                 * remove if and use nullstr if defval == NULL */
+                if (defval != NULL) {
+                    strcpy(start, defval);
+                    start += strlen(defval);
+                    curarg[plevel] = 1;
+                } else {
+                    curarg[plevel] = curarg[plevel] | 0;
+                }
+                break;
+            default:
+                /* Handle this? */
+                break;
+            }
+            break;
+
+        case '(':
+            if (plevel <= MAX_PROMPT_NESTING_LEVELS) {
+                plevel++;
+                curarg[plevel] = *buf[plevel] = 0;
+                start = buf[plevel];
+            }
+            /* else? */
+            break;
+
+        case ')':
+            if (plevel > 0) {
+                *start = 0; /* Null terminate current string */
+                
+                /* Move pointer to end of string */
+                plevel--;
+                start = buf[plevel] + strlen(buf[plevel]);
+                
+                /* If old curarg was set, concat buffer with level down */
+                if (curarg[plevel + 1]) {
+                    strcpy(start, buf[plevel + 1]);
+                    start += strlen(buf[plevel + 1]);
+                }
+
+                break;
+            }
+        case '\\': /* Check next character for escape sequence 
+                    * (just ignore it for now) */
+            (void)*cprompt++;
+            /* Fallthrough */
+
+        default:       
+            *start++ = *cprompt;
+        }
+    }
+
+    *start = 0; /* Null terminate the string */
+    
+    apr_file_printf(req->sout, "%s", buf[0]);
+    apr_file_gets(buf[0], MAX_BUFFER_SIZE, req->sin);
+    chomp(buf[0]);
+    if (strcmp(buf[0], "")) {
+/*        if (strcmp(buf[0], nullstr)) */
+            return apr_pstrdup(handle->pool, buf[0]);
+/*        return NULL; */
+    }
+
+    if (defval != nullstr)
+        return apr_pstrdup(handle->pool, defval);
+
+    return NULL;
+}
+
+static const char *cgi_header_in(apreq_handle_t *handle,
+                                 const char *name)
+{
+    apr_pool_t *p = handle->pool;
+    char *key = apr_pstrcat(p, "HTTP_", name, NULL);
+    char *k, *value = NULL;
+    for (k = key; *k; ++k) {
+        if (*k == '-')
+            *k = '_';
+        else
+            *k = apr_toupper(*k);
+    }
+
+    if (!strcmp(key, "HTTP_CONTENT_TYPE")
+        || !strcmp(key, "HTTP_CONTENT_LENGTH"))
+        {
+            key += 5; /* strlen("HTTP_") */
+        }
+
+    apr_env_get(&value, key, p);
+
+    return value;
+}
+
+
+
+
+static void cgi_log_error(const char *file, int line, int level,
+                          apr_status_t status, apreq_handle_t *handle,
+                          const char *fmt, ...)
+{
+    apr_pool_t *p = handle->pool;
+    char buf[256];
+    char *log_level_string, *ra;
+    const char *remote_addr;
+    unsigned log_level = CGILOG_WARNING;
+    char date[APR_CTIME_LEN];
+    va_list vp;
+#ifndef WIN32
+    apr_file_t *err;
+#endif
+
+    va_start(vp, fmt);
+
+    if (apr_env_get(&log_level_string, "LOG_LEVEL", p) == APR_SUCCESS)
+        log_level = (log_level_string[0] - '0');
+
+    level &= CGILOG_LEVELMASK;
+
+    if (level < (int)log_level) {
+
+        if (apr_env_get(&ra, "REMOTE_ADDR", p) == APR_SUCCESS)
+            remote_addr = ra;
+        else
+            remote_addr = "address unavailable";
+
+        apr_ctime(date, apr_time_now());
+
+#ifndef WIN32
+
+        apr_file_open_stderr(&err, p);
+        apr_file_printf(err, "[%s] [%s] [%s] %s(%d): %s: %s\n",
+                        date, priorities[level].t_name, remote_addr, file, line,
+                        apr_strerror(status,buf,255),apr_pvsprintf(p,fmt,vp));
+        apr_file_flush(err);
+
+#else
+        fprintf(stderr, "[%s] [%s] [%s] %s(%d): %s: %s\n",
+                date, priorities[level].t_name, remote_addr, file, line,
+                apr_strerror(status,buf,255),apr_pvsprintf(p,fmt,vp));
+#endif
+    }
+
+    va_end(vp);
+
+}
+
+
+APR_INLINE
+static const char *cgi_query_string(apreq_handle_t *handle)
+{
+    char *value = NULL, qs[] = "QUERY_STRING";
+    apr_env_get(&value, qs, handle->pool);
+    return value;
+}
+
+
+static void init_body(apreq_handle_t *handle)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    const char *cl_header = cgi_header_in(handle, "Content-Length");
+    apr_bucket_alloc_t *ba = handle->bucket_alloc;
+    apr_pool_t *pool = handle->pool;
+    apr_file_t *file;
+    apr_bucket *eos, *pipe;
+
+    if (cl_header != NULL) {
+        char *dummy;
+        apr_int64_t content_length = apr_strtoi64(cl_header, &dummy, 0);
+
+        if (dummy == NULL || *dummy != 0) {
+            req->body_status = APREQ_ERROR_BADHEADER;
+            cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
+                          "Invalid Content-Length header (%s)", cl_header);
+            return;
+        }
+        else if ((apr_uint64_t)content_length > req->read_limit) {
+            req->body_status = APREQ_ERROR_OVERLIMIT;
+            cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
+                          "Content-Length header (%s) exceeds configured "
+                          "max_body limit (%" APR_UINT64_T_FMT ")",
+                          cl_header, req->read_limit);
+            return;
+        }
+    }
+
+    if (req->parser == NULL) {
+        const char *ct_header = cgi_header_in(handle, "Content-Type");
+
+        if (ct_header != NULL) {
+            apreq_parser_function_t pf = apreq_parser(ct_header);
+
+            if (pf != NULL) {
+                req->parser = apreq_parser_make(pool,
+                                                ba,
+                                                ct_header,
+                                                pf,
+                                                req->brigade_limit,
+                                                req->temp_dir,
+                                                req->hook_queue,
+                                                NULL);
+            }
+            else {
+                req->body_status = APREQ_ERROR_NOPARSER;
+                return;
+            }
+        }
+        else {
+            req->body_status = APREQ_ERROR_NOHEADER;
+            return;
+        }
+    }
+    else {
+        if (req->parser->brigade_limit > req->brigade_limit)
+            req->parser->brigade_limit = req->brigade_limit;
+        if (req->temp_dir != NULL)
+            req->parser->temp_dir = req->temp_dir;
+        if (req->hook_queue != NULL)
+            apreq_parser_add_hook(req->parser, req->hook_queue);
+    }
+
+    req->hook_queue = NULL;
+    req->in         = apr_brigade_create(pool, ba);
+    req->tmpbb      = apr_brigade_create(pool, ba);
+
+    apr_file_open_stdin(&file, pool); // error status?
+    pipe = apr_bucket_pipe_create(file, ba);
+    eos = apr_bucket_eos_create(ba);
+    APR_BRIGADE_INSERT_HEAD(req->in, pipe);
+    APR_BRIGADE_INSERT_TAIL(req->in, eos);
+
+    req->body_status = APR_INCOMPLETE;
+
+}
+
+static apr_status_t cgi_read(apreq_handle_t *handle,
+                             apr_off_t bytes)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    apr_bucket *e;
+    apr_status_t s;
+
+    if (req->body_status == APR_EINIT)
+        init_body(handle);
+
+    if (req->body_status != APR_INCOMPLETE)
+        return req->body_status;
+
+
+    switch (s = apr_brigade_partition(req->in, bytes, &e)) {
+        apr_off_t len;
+
+    case APR_SUCCESS:
+
+        apreq_brigade_move(req->tmpbb, req->in, e);
+        req->bytes_read += bytes;
+
+        if (req->bytes_read > req->read_limit) {
+            req->body_status = APREQ_ERROR_OVERLIMIT;
+            cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status,
+                          handle, "Bytes read (%" APR_UINT64_T_FMT
+                          ") exceeds configured limit (%" APR_UINT64_T_FMT ")",
+                          req->bytes_read, req->read_limit);
+            break;
+        }
+
+        req->body_status =
+            apreq_parser_run(req->parser, req->body, req->tmpbb);
+        apr_brigade_cleanup(req->tmpbb);
+        break;
+
+
+    case APR_INCOMPLETE:
+
+        apreq_brigade_move(req->tmpbb, req->in, e);
+        s = apr_brigade_length(req->tmpbb, 1, &len);
+
+        if (s != APR_SUCCESS) {
+            req->body_status = s;
+            break;
+        }
+        req->bytes_read += len;
+
+        if (req->bytes_read > req->read_limit) {
+            req->body_status = APREQ_ERROR_OVERLIMIT;
+            cgi_log_error(CGILOG_MARK, CGILOG_ERR, req->body_status, handle,
+                          "Bytes read (%" APR_UINT64_T_FMT
+                          ") exceeds configured limit (%" APR_UINT64_T_FMT ")",
+                          req->bytes_read, req->read_limit);
+
+            break;
+        }
+
+        req->body_status =
+            apreq_parser_run(req->parser, req->body, req->tmpbb);
+        apr_brigade_cleanup(req->tmpbb);
+        break;
+
+    default:
+        req->body_status = s;
+    }
+
+    return req->body_status;
+}
+
+
+
+static apr_status_t cgi_jar(apreq_handle_t *handle,
+                            const apr_table_t **t)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    if (req->interactive_mode && req->jar_status != APR_SUCCESS) {
+        char buf[65536];
+        const char *name, *val;
+        apreq_cookie_t *p;
+        int i = 1;
+        apr_file_printf(req->sout, "[CGI] Requested all cookies\n");
+        while (1) {
+            apr_file_printf(req->sout, "[CGI] Please enter a name for cookie %d (or just hit ENTER to end): ",
+                     i++);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            if (!strcmp(buf, "")) {
+                break;
+            }
+            name = apr_pstrdup(handle->pool, buf);
+            val = prompt(handle, name, "cookie");
+            if (val == NULL)
+                val = "";
+            p = apreq_cookie_make(handle->pool, name, strlen(name), val, strlen(val));
+            apreq_cookie_tainted_on(p);
+            apreq_value_table_add(&p->v, req->jar);
+            val = p->v.data;
+        }
+        req->jar_status = APR_SUCCESS;
+    } /** Fallthrough */
+
+    if (req->jar_status == APR_EINIT) {
+        const char *cookies = cgi_header_in(handle, "Cookie");
+        if (cookies != NULL) {
+            req->jar_status =
+                apreq_parse_cookie_header(handle->pool, req->jar, cookies);
+        }
+        else
+            req->jar_status = APREQ_ERROR_NODATA;
+    }
+
+    *t = req->jar;
+    return req->jar_status;
+}
+
+static apr_status_t cgi_args(apreq_handle_t *handle,
+                             const apr_table_t **t)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    if (req->interactive_mode && req->args_status != APR_SUCCESS) {
+        char buf[65536];
+        const char *name, *val;
+        apreq_param_t *p;
+        int i = 1;
+        apr_file_printf(req->sout, "[CGI] Requested all argument parameters\n");
+        while (1) {
+            apr_file_printf(req->sout, "[CGI] Please enter a name for parameter %d (or just hit ENTER to end): ",
+                     i++);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            if (!strcmp(buf, "")) {
+                break;
+            }
+            name = apr_pstrdup(handle->pool, buf);
+            val = prompt(handle, name, "parameter");
+            if (val == NULL)
+                val = "";
+            p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->args);
+            val = p->v.data;
+        }
+        req->args_status = APR_SUCCESS;
+    } /** Fallthrough */
+
+    if (req->args_status == APR_EINIT) {
+        const char *qs = cgi_query_string(handle);
+        if (qs != NULL) {
+            req->args_status =
+                apreq_parse_query_string(handle->pool, req->args, qs);
+        }
+        else
+            req->args_status = APREQ_ERROR_NODATA;
+    }
+
+    *t = req->args;
+    return req->args_status;
+}
+
+
+
+
+static apreq_cookie_t *cgi_jar_get(apreq_handle_t *handle,
+                                   const char *name)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    const apr_table_t *t;
+    const char *val = NULL;
+
+    if (req->jar_status == APR_EINIT && !req->interactive_mode)
+        cgi_jar(handle, &t);
+    else
+        t = req->jar;
+
+    val = apr_table_get(t, name);
+    if (val == NULL) {
+        if (!req->interactive_mode) {
+            return NULL;
+        } else {
+            apreq_cookie_t *p;
+            val = prompt(handle, name, "cookie");
+            if (val == NULL)
+                return NULL;
+            p = apreq_cookie_make(handle->pool, name, strlen(name), val, strlen(val));
+            apreq_cookie_tainted_on(p);
+            apreq_value_table_add(&p->v, req->jar);
+            val = p->v.data;
+        }
+    }
+
+
+    return apreq_value_to_cookie(val);
+}
+
+static apreq_param_t *cgi_args_get(apreq_handle_t *handle,
+                                   const char *name)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    const apr_table_t *t;
+    const char *val = NULL;
+
+    if (req->args_status == APR_EINIT && !req->interactive_mode)
+        cgi_args(handle, &t);
+    else
+        t = req->args;
+
+    val = apr_table_get(t, name);
+    if (val == NULL) {
+        if (!req->interactive_mode) {
+            return NULL;
+        } else {
+            apreq_param_t *p;
+            val = prompt(handle, name, "parameter");
+            if (val == NULL)
+                return NULL;
+            p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->args);
+            val = p->v.data;
+        }
+    }
+
+
+    return apreq_value_to_param(val);
+}
+
+
+
+static apr_status_t cgi_body(apreq_handle_t *handle,
+                             const apr_table_t **t)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    if (req->interactive_mode && req->body_status != APR_SUCCESS) {
+        const char *name, *val;
+        apreq_param_t *p;
+        int i = 1;
+        apr_file_printf(req->sout, "[CGI] Requested all body parameters\n");
+        while (1) {
+            char buf[65536];
+            apr_file_printf(req->sout, "[CGI] Please enter a name for parameter %d (or just hit ENTER to end): ",
+                     i++);
+            apr_file_gets(buf, 65536, req->sin);
+            chomp(buf);
+            if (!strcmp(buf, "")) {
+                break;
+            }
+            name = apr_pstrdup(handle->pool, buf);
+            val = prompt(handle, name, "parameter");
+            if (val == NULL)
+                val = "";
+            p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->body);
+            val = p->v.data;
+        }
+        req->body_status = APR_SUCCESS;
+    } /** Fallthrough */
+    
+    switch (req->body_status) {
+
+    case APR_EINIT:
+        init_body(handle);
+        if (req->body_status != APR_INCOMPLETE)
+            break;
+
+    case APR_INCOMPLETE:
+        while (cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE)
+               == APR_INCOMPLETE)
+            ;   /*loop*/
+    }
+
+    *t = req->body;
+    return req->body_status;
+}
+
+static apreq_param_t *cgi_body_get(apreq_handle_t *handle,
+                                   const char *name)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    const char *val = NULL;
+    apreq_hook_t *h;
+    apreq_hook_find_param_ctx_t *hook_ctx;
+
+    if (req->interactive_mode) {
+        val = apr_table_get(req->body, name);
+        if (val == NULL) {
+            return NULL;
+        } else {
+            apreq_param_t *p;
+            val = prompt(handle, name, "parameter");
+            if (val == NULL)
+                return NULL;
+            p = apreq_param_make(handle->pool, name, strlen(name), val, strlen(val));
+            apreq_param_tainted_on(p);
+            apreq_value_table_add(&p->v, req->body);
+            val = p->v.data;
+            return apreq_value_to_param(val);
+        }
+    }
+
+
+    switch (req->body_status) {
+
+    case APR_SUCCESS:
+
+        val = apr_table_get(req->body, name);
+        if (val != NULL)
+            return apreq_value_to_param(val);
+        return NULL;
+
+
+    case APR_EINIT:
+
+        init_body(handle);
+        if (req->body_status != APR_INCOMPLETE)
+            return NULL;
+        cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE);
+
+
+    case APR_INCOMPLETE:
+
+        val = apr_table_get(req->body, name);
+        if (val != NULL)
+            return apreq_value_to_param(val);
+
+        /* Not seen yet, so we need to scan for
+           param while prefetching the body */
+
+        hook_ctx = apr_palloc(handle->pool, sizeof *hook_ctx);
+
+        if (req->find_param == NULL)
+            req->find_param = apreq_hook_make(handle->pool,
+                                              apreq_hook_find_param,
+                                              NULL, NULL);
+        h = req->find_param;
+        h->next = req->parser->hook;
+        req->parser->hook = h;
+        h->ctx = hook_ctx;
+        hook_ctx->name = name;
+        hook_ctx->param = NULL;
+        hook_ctx->prev = req->parser->hook;
+
+        do {
+            cgi_read(handle, APREQ_DEFAULT_READ_BLOCK_SIZE);
+            if (hook_ctx->param != NULL)
+                return hook_ctx->param;
+        } while (req->body_status == APR_INCOMPLETE);
+
+        req->parser->hook = h->next;
+        return NULL;
+
+
+    default:
+
+        if (req->body == NULL)
+            return NULL;
+
+        val = apr_table_get(req->body, name);
+        if (val != NULL)
+            return apreq_value_to_param(val);
+        return NULL;
+    }
+
+    /* not reached */
+    return NULL;
+}
+
+static apr_status_t cgi_parser_get(apreq_handle_t *handle,
+                                   const apreq_parser_t **parser)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    *parser = req->parser;
+    return APR_SUCCESS;
+}
+
+static apr_status_t cgi_parser_set(apreq_handle_t *handle,
+                                   apreq_parser_t *parser)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    if (req->parser == NULL) {
+
+        if (req->hook_queue != NULL) {
+            apr_status_t s = apreq_parser_add_hook(parser, req->hook_queue);
+            if (s != APR_SUCCESS)
+                return s;
+        }
+        if (req->temp_dir != NULL) {
+            parser->temp_dir = req->temp_dir;
+        }
+        if (req->brigade_limit < parser->brigade_limit) {
+            parser->brigade_limit = req->brigade_limit;
+        }
+
+        req->hook_queue = NULL;
+        req->parser = parser;
+        return APR_SUCCESS;
+    }
+    else
+        return APREQ_ERROR_MISMATCH;
+}
+
+
+static apr_status_t cgi_hook_add(apreq_handle_t *handle,
+                                     apreq_hook_t *hook)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    if (req->parser != NULL) {
+        return apreq_parser_add_hook(req->parser, hook);
+    }
+    else if (req->hook_queue != NULL) {
+        apreq_hook_t *h = req->hook_queue;
+        while (h->next != NULL)
+            h = h->next;
+        h->next = hook;
+    }
+    else {
+        req->hook_queue = hook;
+    }
+    return APR_SUCCESS;
+
+}
+
+static apr_status_t cgi_brigade_limit_set(apreq_handle_t *handle,
+                                          apr_size_t bytes)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    apr_size_t *limit = (req->parser == NULL)
+                      ? &req->brigade_limit
+                      : &req->parser->brigade_limit;
+
+    if (*limit > bytes) {
+        *limit = bytes;
+        return APR_SUCCESS;
+    }
+
+    return APREQ_ERROR_MISMATCH;
+}
+
+static apr_status_t cgi_brigade_limit_get(apreq_handle_t *handle,
+                                          apr_size_t *bytes)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    *bytes = (req->parser == NULL)
+           ?  req->brigade_limit
+           :  req->parser->brigade_limit;
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t cgi_read_limit_set(apreq_handle_t *handle,
+                                       apr_uint64_t bytes)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+
+    if (req->read_limit > bytes && req->bytes_read < bytes) {
+        req->read_limit = bytes;
+        return APR_SUCCESS;
+    }
+
+    return APREQ_ERROR_MISMATCH;
+}
+
+
+static apr_status_t cgi_read_limit_get(apreq_handle_t *handle,
+                                       apr_uint64_t *bytes)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    *bytes = req->read_limit;
+    return APR_SUCCESS;
+}
+
+
+static apr_status_t cgi_temp_dir_set(apreq_handle_t *handle,
+                                     const char *path)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    const char **temp_dir = (req->parser == NULL)
+                          ? &req->temp_dir
+                          : &req->parser->temp_dir;
+
+
+    if (*temp_dir == NULL && req->bytes_read == 0) {
+        if (path != NULL)
+            *temp_dir = apr_pstrdup(handle->pool, path);
+        return APR_SUCCESS;
+    }
+
+    return APREQ_ERROR_MISMATCH;
+}
+
+
+static apr_status_t cgi_temp_dir_get(apreq_handle_t *handle,
+                                     const char **path)
+{
+    struct cgi_handle *req = (struct cgi_handle *)handle;
+    *path = (req->parser == NULL)
+           ? req->temp_dir
+           : req->parser->temp_dir;
+    return APR_SUCCESS;
+}
+
+
+
+#ifdef APR_POOL_DEBUG
+static apr_status_t ba_cleanup(void *data)
+{
+    apr_bucket_alloc_t *ba = data;
+    apr_bucket_alloc_destroy(ba);
+    return APR_SUCCESS;
+}
+#endif
+
+/** Determine if we're interactive mode or not.  Order is
+  QUERY_STRING ? NO : Interactive
+
+ I think we should just rely on GATEWAY_INTERFACE to set
+ non-interactive mode, and be interactive if it's not there
+
+ Behaviour change should really be:
+ Always check query_string before prompting user,
+  but rewrite body/cookies to get if interactive
+
+ Definately more work needed here...
+*/
+static int is_interactive_mode(apr_pool_t *pool) {
+    char *value = NULL, qs[] = "GATEWAY_INTERFACE";
+    apr_status_t rv;
+
+    rv = apr_env_get(&value, qs, pool);
+    if (rv != APR_SUCCESS)
+        if (rv == APR_ENOENT)
+            return 1;
+        
+        /** handle else? (!SUCCESS && !ENOENT) */
+    return 0;
+}
+
+static APREQ_MODULE(cgi, 20090110);
+
+APREQ_DECLARE(apreq_handle_t *)apreq_handle_cgi(apr_pool_t *pool)
+{
+    apr_bucket_alloc_t *ba;
+    struct cgi_handle *req;
+    void *data;
+
+    apr_pool_userdata_get(&data, USER_DATA_KEY, pool);
+
+    if (data != NULL)
+        return data;
+
+    req = apr_pcalloc(pool, sizeof *req);
+    ba = apr_bucket_alloc_create(pool);
+
+    /* check pool's userdata first. */
+
+    req->handle.module        = &cgi_module;
+    req->handle.pool          = pool;
+    req->handle.bucket_alloc  = ba;
+    req->read_limit           = (apr_uint64_t) -1;
+    req->brigade_limit        = APREQ_DEFAULT_BRIGADE_LIMIT;
+
+    req->args = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+    req->body = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+    req->jar  = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+
+    req->args_status =
+        req->jar_status =
+            req->body_status = APR_EINIT;
+
+    if (is_interactive_mode(pool)) {
+        req->interactive_mode = 1;
+        apr_file_open_stdout(&(req->sout), pool);
+        apr_file_open_stdin(&(req->sin), pool);
+        req->promptstr=apr_pstrdup(pool, DEFAULT_PROMPT);
+    }
+
+    apr_pool_userdata_setn(&req->handle, USER_DATA_KEY, NULL, pool);
+
+#ifdef APR_POOL_DEBUG
+    apr_pool_cleanup_register(pool, ba, ba_cleanup, ba_cleanup);
+#endif
+
+    return &req->handle;
+}
diff --git a/srclib/libapreq/module_custom.c b/srclib/libapreq/module_custom.c
new file mode 100644 (file)
index 0000000..e1e6f58
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+**  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.
+*/
+
+#include "apr_strings.h"
+#include "apreq_module.h"
+#include "apreq_error.h"
+#include "apreq_util.h"
+
+#define READ_BYTES (64 * 1024)
+
+struct custom_handle {
+    struct apreq_handle_t        handle;
+
+    apr_table_t                 *jar, *args, *body;
+    apr_status_t                 jar_status,
+                                 args_status,
+                                 body_status;
+
+    apreq_parser_t              *parser;
+
+    apr_uint64_t                 read_limit;
+    apr_uint64_t                 bytes_read;
+    apr_bucket_brigade          *in;
+    apr_bucket_brigade          *tmpbb;
+};
+
+
+static apr_status_t custom_parse_brigade(apreq_handle_t *handle, apr_uint64_t bytes)
+{
+    struct custom_handle *req = (struct custom_handle *)handle;
+    apr_status_t s;
+    apr_bucket *e;
+
+    if (req->body_status != APR_INCOMPLETE)
+        return req->body_status;
+
+    switch (s = apr_brigade_partition(req->in, bytes, &e)) {
+        apr_off_t len;
+
+    case APR_SUCCESS:
+        apreq_brigade_move(req->tmpbb, req->in, e);
+        req->bytes_read += bytes;
+
+        if (req->bytes_read > req->read_limit) {
+            req->body_status = APREQ_ERROR_OVERLIMIT;
+            break;
+        }
+
+        req->body_status =
+            apreq_parser_run(req->parser, req->body, req->tmpbb);
+
+        apr_brigade_cleanup(req->tmpbb);
+        break;
+
+    case APR_INCOMPLETE:
+        apreq_brigade_move(req->tmpbb, req->in, e);
+        s = apr_brigade_length(req->tmpbb, 1, &len);
+        if (s != APR_SUCCESS) {
+            req->body_status = s;
+            break;
+        }
+        req->bytes_read += len;
+
+        if (req->bytes_read > req->read_limit) {
+            req->body_status = APREQ_ERROR_OVERLIMIT;
+            break;
+        }
+        req->body_status =
+            apreq_parser_run(req->parser, req->body, req->tmpbb);
+
+        apr_brigade_cleanup(req->tmpbb);
+        break;
+
+    default:
+        req->body_status = s;
+    }
+
+    return req->body_status;
+}
+
+
+
+static apr_status_t custom_jar(apreq_handle_t *handle, const apr_table_t **t)
+{
+    struct custom_handle *req = (struct custom_handle *)handle;
+    *t = req->jar;
+    return req->jar_status;
+}
+
+static apr_status_t custom_args(apreq_handle_t *handle, const apr_table_t **t)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    *t = req->args;
+    return req->args_status;
+}
+
+static apr_status_t custom_body(apreq_handle_t *handle, const apr_table_t **t)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    while (req->body_status == APR_INCOMPLETE)
+        custom_parse_brigade(handle, READ_BYTES);
+    *t = req->body;
+    return req->body_status;
+}
+
+
+
+static apreq_cookie_t *custom_jar_get(apreq_handle_t *handle, const char *name)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    const char *val;
+
+    if (req->jar == NULL || name == NULL)
+        return NULL;
+
+    val = apr_table_get(req->jar, name);
+
+    if (val == NULL)
+        return NULL;
+
+    return apreq_value_to_cookie(val);
+}
+
+static apreq_param_t *custom_args_get(apreq_handle_t *handle, const char *name)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    const char *val;
+
+    if (req->args == NULL || name == NULL)
+        return NULL;
+
+    val = apr_table_get(req->args, name);
+
+    if (val == NULL)
+        return NULL;
+
+    return apreq_value_to_param(val);
+}
+
+static apreq_param_t *custom_body_get(apreq_handle_t *handle, const char *name)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    const char *val;
+
+    if (req->body == NULL || name == NULL)
+        return NULL;
+
+    while (1) {
+        *(const char **)&val = apr_table_get(req->body, name);
+        if (val != NULL)
+            break;
+
+        if (req->body_status == APR_INCOMPLETE)
+            custom_parse_brigade(handle, READ_BYTES);
+        else
+            return NULL;
+    }
+
+    return apreq_value_to_param(val);
+}
+
+
+
+static apr_status_t custom_parser_get(apreq_handle_t *handle,
+                                      const apreq_parser_t **parser)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    *parser = req->parser;
+
+    return APR_SUCCESS;
+}
+
+static apr_status_t custom_parser_set(apreq_handle_t *handle,
+                                      apreq_parser_t *parser)
+{
+    (void)handle;
+    (void)parser;
+    return APR_ENOTIMPL;
+}
+
+static apr_status_t custom_hook_add(apreq_handle_t *handle,
+                                    apreq_hook_t *hook)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    apreq_parser_add_hook(req->parser, hook);
+    return APR_SUCCESS;
+}
+
+static apr_status_t custom_brigade_limit_get(apreq_handle_t *handle,
+                                             apr_size_t *bytes)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    *bytes = req->parser->brigade_limit;
+    return APR_SUCCESS;
+}
+
+static apr_status_t custom_brigade_limit_set(apreq_handle_t *handle,
+                                             apr_size_t bytes)
+{
+    (void)handle;
+    (void)bytes;
+    return APR_ENOTIMPL;
+}
+
+static apr_status_t custom_read_limit_get(apreq_handle_t *handle,
+                                          apr_uint64_t *bytes)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+    *bytes = req->read_limit;
+    return APR_SUCCESS;
+}
+
+static apr_status_t custom_read_limit_set(apreq_handle_t *handle,
+                                          apr_uint64_t bytes)
+{
+    (void)handle;
+    (void)bytes;
+    return APR_ENOTIMPL;
+}
+
+static apr_status_t custom_temp_dir_get(apreq_handle_t *handle,
+                                        const char **path)
+{
+    struct custom_handle *req = (struct custom_handle*)handle;
+
+    *path = req->parser->temp_dir;
+    return APR_SUCCESS;
+}
+
+static apr_status_t custom_temp_dir_set(apreq_handle_t *handle,
+                                        const char *path)
+{
+    (void)handle;
+    (void)path;
+    return APR_ENOTIMPL;
+}
+
+
+static APREQ_MODULE(custom, 20070428);
+
+APREQ_DECLARE(apreq_handle_t *)apreq_handle_custom(apr_pool_t *pool,
+                                                   const char *query_string,
+                                                   const char *cookie,
+                                                   apreq_parser_t *parser,
+                                                   apr_uint64_t read_limit,
+                                                   apr_bucket_brigade *in)
+{
+    struct custom_handle *req;
+    req = apr_palloc(pool, sizeof(*req));
+    req->handle.module = &custom_module;
+    req->handle.pool = pool;
+    req->handle.bucket_alloc = in->bucket_alloc;
+    req->read_limit = read_limit;
+    req->bytes_read = 0;
+    req->parser = parser;
+    req->in = apr_brigade_create(pool, in->bucket_alloc);
+    req->tmpbb = apr_brigade_create(pool, in->bucket_alloc);
+    req->body = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+    req->body_status = APR_INCOMPLETE;
+    APR_BRIGADE_CONCAT(req->in, in);
+
+    if (cookie != NULL) {
+        req->jar = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+        req->jar_status =
+            apreq_parse_cookie_header(pool, req->jar, cookie);
+    }
+    else {
+        req->jar = NULL;
+        req->jar_status = APREQ_ERROR_NODATA;
+    }
+
+
+    if (query_string != NULL) {
+        req->args = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+        req->args_status =
+            apreq_parse_query_string(pool, req->args, query_string);
+    }
+    else {
+        req->args = NULL;
+        req->args_status = APREQ_ERROR_NODATA;
+    }
+
+    if (!APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(req->in))) {
+        apr_bucket *eos = apr_bucket_eos_create(in->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(req->in, eos);
+    }
+
+    return &req->handle;
+}
+
diff --git a/srclib/libapreq/param.c b/srclib/libapreq/param.c
new file mode 100644 (file)
index 0000000..83e185b
--- /dev/null
@@ -0,0 +1,272 @@
+/*
+**  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.
+*/
+
+#include "apreq_param.h"
+#include "apreq_error.h"
+#include "apreq_util.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+
+#define MAX_LEN         (1024 * 1024)
+#define MAX_BRIGADE_LEN (1024 * 256)
+#define MAX_READ_AHEAD  (1024 * 64)
+
+
+APREQ_DECLARE(apreq_param_t *) apreq_param_make(apr_pool_t *p,
+                                                const char *name,
+                                                const apr_size_t nlen,
+                                                const char *val,
+                                                const apr_size_t vlen)
+{
+    apreq_param_t *param;
+    apreq_value_t *v;
+
+    param = apr_palloc(p, nlen + vlen + 1 + sizeof *param);
+
+    if (param == NULL)
+        return NULL;
+
+    param->info = NULL;
+    param->upload = NULL;
+    param->flags = 0;
+
+    *(const apreq_value_t **)&v = &param->v;
+
+    if (vlen && val != NULL)
+        memcpy(v->data, val, vlen);
+    v->data[vlen] = 0;
+    v->dlen = vlen;
+
+    v->name = v->data + vlen + 1;
+    if (nlen && name != NULL)
+        memcpy(v->name, name, nlen);
+    v->name[nlen] = 0;
+    v->nlen = nlen;
+
+    return param;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_param_decode(apreq_param_t **param,
+                                               apr_pool_t *pool,
+                                               const char *word,
+                                               apr_size_t nlen,
+                                               apr_size_t vlen)
+{
+    apr_status_t status;
+    apreq_value_t *v;
+    apreq_param_t *p;
+    apreq_charset_t charset;
+
+    if (nlen == 0) {
+        *param = NULL;
+        return APR_EBADARG;
+    }
+
+    p = apr_palloc(pool, nlen + vlen + 1 + sizeof *p);
+    p->info = NULL;
+    p->upload = NULL;
+    p->flags = 0;
+    *(const apreq_value_t **)&v = &p->v;
+
+    if (vlen > 0) {
+        status = apreq_decode(v->data, &v->dlen, word + nlen + 1, vlen);
+        if (status != APR_SUCCESS) {
+            *param = NULL;
+            return status;
+        }
+        charset = apreq_charset_divine(v->data, v->dlen);
+    }
+    else {
+        v->data[0] = 0;
+        v->dlen = 0;
+        charset = APREQ_CHARSET_ASCII;
+    }
+    v->name = v->data + vlen + 1;
+
+    status = apreq_decode(v->name, &v->nlen, word, nlen);
+    if (status != APR_SUCCESS) {
+        *param = NULL;
+        return status;
+    }
+
+    switch (apreq_charset_divine(v->name, v->nlen)) {
+    case APREQ_CHARSET_UTF8:
+        if (charset == APREQ_CHARSET_ASCII)
+            charset = APREQ_CHARSET_UTF8;
+    case APREQ_CHARSET_ASCII:
+        break;
+
+    case APREQ_CHARSET_LATIN1:
+        if (charset != APREQ_CHARSET_CP1252)
+            charset = APREQ_CHARSET_LATIN1;
+        break;
+    case APREQ_CHARSET_CP1252:
+        charset = APREQ_CHARSET_CP1252;
+    }
+
+    apreq_param_charset_set(p, charset);
+    *param = p;
+
+    return APR_SUCCESS;
+}
+
+
+APREQ_DECLARE(char *) apreq_param_encode(apr_pool_t *pool,
+                                         const apreq_param_t *param)
+{
+    apr_size_t dlen;
+    char *data;
+    data = apr_palloc(pool, 3 * (param->v.nlen + param->v.dlen) + 2);
+    dlen = apreq_encode(data, param->v.name, param->v.nlen);
+    data[dlen++] = '=';
+    dlen += apreq_encode(data + dlen, param->v.data, param->v.dlen);
+
+    return data;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_parse_query_string(apr_pool_t *pool,
+                                                     apr_table_t *t,
+                                                     const char *qs)
+{
+    const char *start = qs;
+    apr_size_t nlen = 0;
+
+    for (;;++qs) {
+        switch (*qs) {
+
+        case '=':
+            if (nlen == 0) {
+                nlen = qs - start;
+            }
+            break;
+
+        case '&':
+        case ';':
+        case 0:
+            if (qs > start) {
+                apr_size_t vlen = 0;
+                apreq_param_t *param;
+                apr_status_t s;
+                if (nlen == 0)
+                    nlen = qs - start;
+                else
+                    vlen = qs - start - nlen - 1;
+
+                s = apreq_param_decode(&param, pool, start, nlen, vlen);
+                if (s != APR_SUCCESS)
+                    return s;
+
+                apreq_param_tainted_on(param);
+                apreq_value_table_add(&param->v, t);
+            }
+
+            if (*qs == 0)
+                return APR_SUCCESS;
+
+            nlen = 0;
+            start = qs + 1;
+        }
+    }
+    /* not reached */
+    return APR_INCOMPLETE;
+}
+
+
+
+
+static int param_push(void *data, const char *key, const char *val)
+{
+    apr_array_header_t *arr = data;
+    *(apreq_param_t **)apr_array_push(arr) =
+        apreq_value_to_param(val);
+    return 1;   /* keep going */
+}
+
+
+APREQ_DECLARE(apr_array_header_t *) apreq_params_as_array(apr_pool_t *p,
+                                                          const apr_table_t *t,
+                                                          const char *key)
+{
+    apr_array_header_t *arr;
+
+    arr = apr_array_make(p, apr_table_elts(t)->nelts,
+                         sizeof(apreq_param_t *));
+
+    apr_table_do(param_push, arr, t, key, NULL);
+    return arr;
+}
+
+APREQ_DECLARE(const char *) apreq_params_as_string(apr_pool_t *p,
+                                                   const apr_table_t *t,
+                                                   const char *key,
+                                                   apreq_join_t mode)
+{
+    apr_array_header_t *arr = apreq_params_as_array(p, t, key);
+    apreq_param_t **elt = (apreq_param_t **)arr->elts;
+    apreq_param_t **const end = elt + arr->nelts;
+    if (arr->nelts == 0)
+        return apr_pstrdup(p, "");
+
+    while (elt < end) {
+        *(const apreq_value_t **)elt = &(**elt).v;
+        ++elt;
+    }
+    return apreq_join(p, ", ", arr, mode);
+}
+
+
+
+static int upload_push(void *data, const char *key, const char *val)
+{
+    apr_table_t *t = data;
+    apreq_param_t *p = apreq_value_to_param(val);
+
+    if (p->upload != NULL)
+        apreq_value_table_add(&p->v, t);
+    return 1;   /* keep going */
+}
+
+
+APREQ_DECLARE(const apr_table_t *) apreq_uploads(const apr_table_t *body,
+                                                 apr_pool_t *pool)
+{
+    apr_table_t *t = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+    apr_table_do(upload_push, t, body, NULL);
+    return t;
+}
+
+static int upload_set(void *data, const char *key, const char *val)
+{
+    const apreq_param_t **q = data;
+    apreq_param_t *p = apreq_value_to_param(val);
+
+    if (p->upload != NULL) {
+        *q = p;
+        return 0; /* upload found, stop */
+    }
+    else
+        return 1; /* keep searching */
+}
+
+
+APREQ_DECLARE(const apreq_param_t *) apreq_upload(const apr_table_t *body,
+                                                  const char *name)
+{
+    apreq_param_t *param = NULL;
+    apr_table_do(upload_set, &param, body, name, NULL);
+    return param;
+}
diff --git a/srclib/libapreq/parser.c b/srclib/libapreq/parser.c
new file mode 100644 (file)
index 0000000..69c0c4f
--- /dev/null
@@ -0,0 +1,356 @@
+/*
+**  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.
+*/
+
+#include "apreq_error.h"
+#include "apreq_parser.h"
+#include "apreq_util.h"
+#include "apr_strings.h"
+#include "apr_xml.h"
+#include "apr_hash.h"
+
+#define PARSER_STATUS_CHECK(PREFIX)   do {         \
+    if (ctx->status == PREFIX##_ERROR)             \
+        return APREQ_ERROR_GENERAL;                \
+    else if (ctx->status == PREFIX##_COMPLETE)     \
+        return APR_SUCCESS;                        \
+    else if (bb == NULL)                           \
+        return APR_INCOMPLETE;                     \
+} while (0);
+
+APREQ_DECLARE(apreq_parser_t *) apreq_parser_make(apr_pool_t *pool,
+                                                  apr_bucket_alloc_t *ba,
+                                                  const char *content_type,
+                                                  apreq_parser_function_t pfn,
+                                                  apr_size_t brigade_limit,
+                                                  const char *temp_dir,
+                                                  apreq_hook_t *hook,
+                                                  void *ctx)
+{
+    apreq_parser_t *p = apr_palloc(pool, sizeof *p);
+    p->content_type = content_type;
+    p->parser = pfn;
+    p->hook = hook;
+    p->pool = pool;
+    p->bucket_alloc = ba;
+    p->brigade_limit = brigade_limit;
+    p->temp_dir = temp_dir;
+    p->ctx = ctx;
+    return p;
+}
+
+APREQ_DECLARE(apreq_hook_t *) apreq_hook_make(apr_pool_t *pool,
+                                              apreq_hook_function_t hook,
+                                              apreq_hook_t *next,
+                                              void *ctx)
+{
+    apreq_hook_t *h = apr_palloc(pool, sizeof *h);
+    h->hook = hook;
+    h->next = next;
+    h->pool = pool;
+    h->ctx = ctx;
+    return h;
+}
+
+
+/*XXX this may need to check the parser's state before modifying the hook list */
+APREQ_DECLARE(apr_status_t) apreq_parser_add_hook(apreq_parser_t *p,
+                                                  apreq_hook_t *h)
+{
+    apreq_hook_t *last = h;
+
+    while (last->next)
+        last = last->next;
+
+    last->next = p->hook;
+    p->hook = h;
+
+    return APR_SUCCESS;
+}
+
+static int default_parsers_lock = 0;
+static apr_hash_t *default_parsers = NULL;
+static apr_pool_t *default_parser_pool = NULL;
+
+static apr_status_t apreq_parsers_cleanup(void *data)
+{
+    default_parsers_lock = 0;
+    default_parsers = NULL;
+    default_parser_pool = NULL;
+
+    return APR_SUCCESS;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_pre_initialize(apr_pool_t *pool)
+{
+    apr_status_t status;
+
+    if (default_parser_pool != NULL)
+        return APR_SUCCESS;
+
+    if (default_parsers_lock)
+        return APREQ_ERROR_GENERAL;
+
+    status = apr_pool_create(&default_parser_pool, pool);
+    if (status != APR_SUCCESS)
+        return status;
+
+    apr_pool_cleanup_register(default_parser_pool, NULL,
+                              apreq_parsers_cleanup,
+                              apr_pool_cleanup_null);
+
+    default_parsers = apr_hash_make(default_parser_pool);
+
+    apreq_register_parser("application/x-www-form-urlencoded",
+                          apreq_parse_urlencoded);
+    apreq_register_parser("multipart/form-data", apreq_parse_multipart);
+    apreq_register_parser("multipart/related", apreq_parse_multipart);
+
+    return APR_SUCCESS;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_post_initialize(apr_pool_t *pool)
+{
+    (void)pool;
+
+    if (default_parser_pool == NULL)
+        return APREQ_ERROR_GENERAL;
+
+    default_parsers_lock = 1;
+    return APR_SUCCESS;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_initialize(apr_pool_t *pool)
+{
+    apr_status_t s = apreq_pre_initialize(pool);
+
+    if (s != APR_SUCCESS)
+        return s;
+
+    return apreq_post_initialize(pool);
+}
+
+
+APREQ_DECLARE(apr_status_t) apreq_register_parser(const char *enctype,
+                                                  apreq_parser_function_t pfn)
+{
+    apreq_parser_function_t *f = NULL;
+
+    if (default_parsers == NULL)
+        return APR_EINIT;
+
+    if (enctype == NULL)
+        return APR_EINVAL;
+
+    if (default_parsers_lock)
+        return APREQ_ERROR_GENERAL;
+
+    if (pfn != NULL) {
+        f = apr_palloc(default_parser_pool, sizeof *f);
+        *f = pfn;
+    }
+    apr_hash_set(default_parsers, apr_pstrdup(default_parser_pool, enctype),
+                 APR_HASH_KEY_STRING, f);
+
+    return APR_SUCCESS;
+}
+
+APREQ_DECLARE(apreq_parser_function_t)apreq_parser(const char *enctype)
+{
+    apreq_parser_function_t *f;
+    apr_size_t tlen = 0;
+
+    if (enctype == NULL || default_parsers_lock == 0)
+        return NULL;
+
+    while(enctype[tlen] && enctype[tlen] != ';')
+        ++tlen;
+
+    f = apr_hash_get(default_parsers, enctype, tlen);
+
+    if (f != NULL)
+        return *f;
+    else
+        return NULL;
+}
+
+APREQ_DECLARE_HOOK(apreq_hook_disable_uploads)
+{
+    return (bb == NULL) ? APR_SUCCESS : APREQ_ERROR_GENERAL;
+}
+
+APREQ_DECLARE_HOOK(apreq_hook_discard_brigade)
+{
+    apr_status_t s = APR_SUCCESS;
+    if (hook->next)
+        s = apreq_hook_run(hook->next, param, bb);
+    if (bb != NULL)
+        apr_brigade_cleanup(bb);
+    return s;
+}
+
+
+/* generic parser */
+
+struct gen_ctx {
+    apreq_param_t               *param;
+    enum {
+        GEN_INCOMPLETE,
+        GEN_COMPLETE,
+        GEN_ERROR
+    }                            status;
+};
+
+APREQ_DECLARE_PARSER(apreq_parse_generic)
+{
+    struct gen_ctx *ctx = parser->ctx;
+    apr_pool_t *pool = parser->pool;
+    apr_status_t s = APR_SUCCESS;
+    apr_bucket *e = APR_BRIGADE_LAST(bb);
+    unsigned saw_eos = 0;
+
+    if (ctx == NULL) {
+        parser->ctx = ctx = apr_palloc(pool, sizeof *ctx);
+        ctx->status = GEN_INCOMPLETE;
+        ctx->param = apreq_param_make(pool,
+                                      "_dummy_", strlen("_dummy_"), "", 0);
+        ctx->param->upload = apr_brigade_create(pool, parser->bucket_alloc);
+        ctx->param->info = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+    }
+
+
+    PARSER_STATUS_CHECK(GEN);
+
+    while (e != APR_BRIGADE_SENTINEL(bb)) {
+        if (APR_BUCKET_IS_EOS(e)) {
+            saw_eos = 1;
+            break;
+        }
+        e = APR_BUCKET_PREV(e);
+    }
+
+    if (parser->hook != NULL) {
+        s = apreq_hook_run(parser->hook, ctx->param, bb);
+        if (s != APR_SUCCESS) {
+            ctx->status = GEN_ERROR;
+            return s;
+        }
+    }
+
+    apreq_brigade_setaside(bb, pool);
+    s = apreq_brigade_concat(pool, parser->temp_dir, parser->brigade_limit,
+                             ctx->param->upload, bb);
+
+    if (s != APR_SUCCESS) {
+        ctx->status = GEN_ERROR;
+        return s;
+    }
+
+    if (saw_eos) {
+        ctx->status = GEN_COMPLETE;
+        return APR_SUCCESS;
+    }
+    else
+        return APR_INCOMPLETE;
+}
+
+
+struct xml_ctx {
+    apr_xml_doc                 *doc;
+    apr_xml_parser              *xml_parser;
+    enum {
+        XML_INCOMPLETE,
+        XML_COMPLETE,
+        XML_ERROR
+    }                            status;
+};
+
+
+APREQ_DECLARE_HOOK(apreq_hook_apr_xml_parser)
+{
+    apr_pool_t *pool = hook->pool;
+    struct xml_ctx *ctx = hook->ctx;
+    apr_status_t s = APR_SUCCESS;
+    apr_bucket *e;
+
+    if (ctx == NULL) {
+        hook->ctx = ctx = apr_palloc(pool, sizeof *ctx);
+        ctx->doc = NULL;
+        ctx->xml_parser = apr_xml_parser_create(pool);
+        ctx->status = XML_INCOMPLETE;
+    }
+
+    PARSER_STATUS_CHECK(XML);
+
+    for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb);
+         e = APR_BUCKET_NEXT(e))
+    {
+        const char *data;
+        apr_size_t dlen;
+
+        if (APR_BUCKET_IS_EOS(e)) {
+            s = apr_xml_parser_done(ctx->xml_parser, &ctx->doc);
+            if (s == APR_SUCCESS) {
+                ctx->status = XML_COMPLETE;
+                if (hook->next)
+                    s = apreq_hook_run(hook->next, param, bb);
+            }
+            else {
+                ctx->status = XML_ERROR;
+            }
+           return s;
+        }
+        else if (APR_BUCKET_IS_METADATA(e)) {
+            continue;
+        }
+
+        s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ);
+
+        if (s != APR_SUCCESS) {
+            ctx->status = XML_ERROR;
+            return s;
+        }
+
+        s = apr_xml_parser_feed(ctx->xml_parser, data, dlen);
+
+        if (s != APR_SUCCESS) {
+            ctx->status = XML_ERROR;
+            return s;
+        }
+
+    }
+
+    if (hook->next)
+        return apreq_hook_run(hook->next, param, bb);
+
+    return APR_SUCCESS;
+}
+
+
+APREQ_DECLARE_HOOK(apreq_hook_find_param)
+{
+    apreq_hook_find_param_ctx_t *ctx = hook->ctx;
+    int is_final = (bb == NULL) || APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb));
+    apr_status_t s = (hook->next == NULL)
+        ? APR_SUCCESS : apreq_hook_run(hook->next, param, bb);
+
+    if (is_final && s == APR_SUCCESS
+        && strcasecmp(ctx->name, param->v.name) == 0) {
+        ctx->param = param;
+        ctx->prev->next = hook->next;
+    }
+    return s;
+}
diff --git a/srclib/libapreq/parser_header.c b/srclib/libapreq/parser_header.c
new file mode 100644 (file)
index 0000000..ae2e030
--- /dev/null
@@ -0,0 +1,365 @@
+/*
+**  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.
+*/
+#include <assert.h>
+#include "apreq_parser.h"
+#include "apreq_error.h"
+#include "apreq_util.h"
+
+#define PARSER_STATUS_CHECK(PREFIX)   do {         \
+    if (ctx->status == PREFIX##_ERROR)             \
+        return APREQ_ERROR_GENERAL;                \
+    else if (ctx->status == PREFIX##_COMPLETE)     \
+        return APR_SUCCESS;                        \
+    else if (bb == NULL)                           \
+        return APR_INCOMPLETE;                     \
+} while (0);
+
+
+struct hdr_ctx {
+    apr_bucket_brigade *bb;
+    apr_size_t          nlen;
+    apr_size_t          glen;
+    apr_size_t          vlen;
+    enum {
+        HDR_NAME,
+        HDR_GAP,
+        HDR_VALUE,
+        HDR_NEWLINE,
+        HDR_CONTINUE,
+        HDR_COMPLETE,
+        HDR_ERROR
+    }                   status;
+};
+
+/********************* header parsing utils ********************/
+
+
+static apr_status_t split_header_line(apreq_param_t **p,
+                                      apr_pool_t *pool,
+                                      apr_bucket_brigade *bb,
+                                      apr_size_t nlen,
+                                      apr_size_t glen,
+                                      apr_size_t vlen)
+{
+    apreq_param_t *param;
+    apreq_value_t *v;
+    apr_bucket *e, *f;
+    apr_status_t s;
+    struct iovec vec[APREQ_DEFAULT_NELTS], *iov, *end;
+    apr_array_header_t arr;
+    char *dest;
+    const char *data;
+    apr_size_t dlen;
+
+    if (nlen == 0)
+        return APR_EBADARG;
+
+    param = apreq_param_make(pool, NULL, nlen, NULL, vlen - 1); /*drop (CR)LF */
+    *(const apreq_value_t **)&v = &param->v;
+
+    arr.pool     = pool;
+    arr.elt_size = sizeof(struct iovec);
+    arr.nelts    = 0;
+    arr.nalloc   = APREQ_DEFAULT_NELTS;
+    arr.elts     = (char *)vec;
+
+    e = APR_BRIGADE_FIRST(bb);
+
+    /* store name in a temporary iovec array */
+
+    while (nlen > 0) {
+        apr_size_t len;
+        end = apr_array_push(&arr);
+        s = apr_bucket_read(e, (const char **)&end->iov_base,
+                            &len, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        assert(nlen >= len);
+        end->iov_len = len;
+        nlen -= len;
+
+        e = APR_BUCKET_NEXT(e);
+    }
+
+    /* skip gap */
+
+    while (glen > 0) {
+        s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        assert(glen >= dlen);
+        glen -= dlen;
+        e = APR_BUCKET_NEXT(e);
+    }
+
+    /* copy value */
+    assert(vlen > 0);
+    dest = v->data;
+    while (vlen > 0) {
+
+        s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        memcpy(dest, data, dlen);
+        dest += dlen;
+        assert(vlen >= dlen);
+        vlen -= dlen;
+        e = APR_BUCKET_NEXT(e);
+    }
+
+    assert(dest[-1] == '\n');
+
+    if (dest[-2] == '\r')
+        --dest;
+
+    dest[-1] = 0;
+    v->dlen = (dest - v->data) - 1;
+
+    /* write name */
+    v->name = dest;
+    iov = (struct iovec *)arr.elts;
+
+    while (iov <= end) {
+        memcpy(dest, iov->iov_base, iov->iov_len);
+        dest += iov->iov_len;
+        ++iov;
+    }
+    *dest = 0;
+    nlen = dest - v->name;
+
+    while ((f = APR_BRIGADE_FIRST(bb)) != e)
+        apr_bucket_delete(f);
+
+    apreq_param_tainted_on(param);
+    *p = param;
+    return APR_SUCCESS;
+
+}
+
+
+APREQ_DECLARE_PARSER(apreq_parse_headers)
+{
+    apr_pool_t *pool = parser->pool;
+    apr_bucket *e;
+    struct hdr_ctx *ctx;
+
+    if (parser->ctx == NULL) {
+        ctx = apr_pcalloc(pool, sizeof *ctx);
+        ctx->bb = apr_brigade_create(pool, parser->bucket_alloc);
+        parser->ctx = ctx;
+        ctx->status = HDR_NAME;
+    }
+    else
+        ctx = parser->ctx;
+
+    PARSER_STATUS_CHECK(HDR);
+    e = APR_BRIGADE_LAST(ctx->bb);
+    APR_BRIGADE_CONCAT(ctx->bb, bb);
+
+ parse_hdr_brigade:
+
+
+    /* parse the brigade for CRLF_CRLF-terminated header block,
+     * each time starting from the front of the brigade.
+     */
+
+    for (e = APR_BUCKET_NEXT(e);
+         e != APR_BRIGADE_SENTINEL(ctx->bb);
+         e = APR_BUCKET_NEXT(e))
+    {
+        apr_size_t off = 0, dlen;
+        const char *data;
+        apr_status_t s;
+        apreq_param_t *param = NULL; /* silences gcc-4.0 warning */
+
+        if (APR_BUCKET_IS_EOS(e)) {
+            ctx->status = HDR_COMPLETE;
+            APR_BRIGADE_CONCAT(bb, ctx->bb);
+            return APR_SUCCESS;
+        }
+        s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ);
+
+        if ( s != APR_SUCCESS ) {
+            ctx->status = HDR_ERROR;
+            return s;
+        }
+        if (dlen == 0)
+            continue;
+
+    parse_hdr_bucket:
+
+        /*              gap           nlen = 13
+         *              vvv           glen =  3
+         * Sample-Header:  grape      vlen =  5
+         * ^^^^^^^^^^^^^   ^^^^^
+         *     name        value
+         */
+
+        switch (ctx->status) {
+
+        case HDR_NAME:
+
+            while (off < dlen) {
+                switch (data[off++]) {
+
+                case '\n':
+                    if (off < dlen)
+                        apr_bucket_split(e, off);
+                    e = APR_BUCKET_NEXT(e);
+
+                    do {
+                        apr_bucket *f = APR_BRIGADE_FIRST(ctx->bb);
+                        apr_bucket_delete(f);
+                    } while (e != APR_BRIGADE_FIRST(ctx->bb));
+                    APR_BRIGADE_CONCAT(bb, ctx->bb);
+                    ctx->status = HDR_COMPLETE;
+                    return APR_SUCCESS;
+
+                case ':':
+                    if (off > 1) {
+                        apr_bucket_split(e, off - 1);
+                        dlen -= off - 1;
+                        data += off - 1;
+                        off = 1;
+                        e = APR_BUCKET_NEXT(e);
+                    }
+                    ++ctx->glen;
+                    ctx->status = HDR_GAP;
+                    goto parse_hdr_bucket;
+
+                default:
+                    ++ctx->nlen;
+                }
+
+            }
+
+            break;
+
+
+        case HDR_GAP:
+
+            while (off < dlen) {
+                switch (data[off++]) {
+                case ' ':
+                case '\t':
+                    ++ctx->glen;
+                    break;
+
+                case '\n':
+                    ctx->status = HDR_NEWLINE;
+                    goto parse_hdr_bucket;
+
+                default:
+                    ctx->status = HDR_VALUE;
+                    if (off > 1) {
+                        apr_bucket_split(e, off - 1);
+                        dlen -= off - 1;
+                        data += off - 1;
+                        off = 1;
+                        e = APR_BUCKET_NEXT(e);
+                    }
+                    ++ctx->vlen;
+                    goto parse_hdr_bucket;
+                }
+            }
+            break;
+
+
+        case HDR_VALUE:
+
+            while (off < dlen) {
+                ++ctx->vlen;
+                if (data[off++] == '\n') {
+                    ctx->status = HDR_NEWLINE;
+                    goto parse_hdr_bucket;
+                }
+            }
+            break;
+
+
+        case HDR_NEWLINE:
+
+            if (off == dlen)
+                break;
+            else {
+                switch (data[off]) {
+
+                case ' ':
+                case '\t':
+                    ctx->status = HDR_CONTINUE;
+                    ++off;
+                    ++ctx->vlen;
+                    break;
+
+                default:
+                    /* can parse brigade now */
+                    if (off > 0)
+                        apr_bucket_split(e, off);
+                    s = split_header_line(&param, pool, ctx->bb, ctx->nlen, ctx->glen, ctx->vlen);
+                    if (parser->hook != NULL && s == APR_SUCCESS)
+                        s = apreq_hook_run(parser->hook, param, NULL);
+
+                    if (s != APR_SUCCESS) {
+                        ctx->status = HDR_ERROR;
+                        return s;
+                    }
+
+                    apreq_value_table_add(&param->v, t);
+                    e = APR_BRIGADE_SENTINEL(ctx->bb);
+                    ctx->status = HDR_NAME;
+                    ctx->nlen = 0;
+                    ctx->vlen = 0;
+                    ctx->glen = 0;
+
+                    goto parse_hdr_brigade;
+                }
+
+                /* cases ' ', '\t' fall through to HDR_CONTINUE */
+            }
+
+
+        case HDR_CONTINUE:
+
+            while (off < dlen) {
+                switch (data[off++]) {
+                case ' ':
+                case '\t':
+                    ++ctx->vlen;
+                    break;
+
+                case '\n':
+                    ctx->status = HDR_NEWLINE;
+                    goto parse_hdr_bucket;
+
+                default:
+                    ctx->status = HDR_VALUE;
+                    ++ctx->vlen;
+                    goto parse_hdr_bucket;
+                }
+            }
+            break;
+
+        default:
+            ; /* not reached */
+        }
+    }
+    apreq_brigade_setaside(ctx->bb,pool);
+    return APR_INCOMPLETE;
+}
diff --git a/srclib/libapreq/parser_multipart.c b/srclib/libapreq/parser_multipart.c
new file mode 100644 (file)
index 0000000..60b5bad
--- /dev/null
@@ -0,0 +1,661 @@
+/*
+**  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.
+*/
+
+#include "apreq_parser.h"
+#include "apreq_error.h"
+#include "apreq_util.h"
+#include "apr_strings.h"
+#include "apr_strmatch.h"
+
+#ifndef CRLF
+#define CRLF    "\015\012"
+#endif
+
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
+
+#define PARSER_STATUS_CHECK(PREFIX)   do {         \
+    if (ctx->status == PREFIX##_ERROR)             \
+        return APREQ_ERROR_GENERAL;                \
+    else if (ctx->status == PREFIX##_COMPLETE)     \
+        return APR_SUCCESS;                        \
+    else if (bb == NULL)                           \
+        return APR_INCOMPLETE;                     \
+} while (0);
+
+/* maximum recursion level in the mfd parser */
+#define MAX_LEVEL 8
+
+struct mfd_ctx {
+    apr_table_t                 *info;
+    apr_bucket_brigade          *in;
+    apr_bucket_brigade          *bb;
+    apreq_parser_t              *hdr_parser;
+    apreq_parser_t              *next_parser;
+    const apr_strmatch_pattern  *pattern;
+    char                        *bdry;
+    enum {
+        MFD_INIT,
+        MFD_NEXTLINE,
+        MFD_HEADER,
+        MFD_POST_HEADER,
+        MFD_PARAM,
+        MFD_UPLOAD,
+        MFD_MIXED,
+        MFD_COMPLETE,
+        MFD_ERROR
+    }                            status;
+    apr_bucket                  *eos;
+    const char                  *param_name;
+    apreq_param_t               *upload;
+    unsigned                    level;
+};
+
+
+/********************* multipart/form-data *********************/
+
+APR_INLINE
+static apr_status_t brigade_start_string(apr_bucket_brigade *bb,
+                                         const char *start_string)
+{
+    apr_bucket *e;
+    apr_size_t slen = strlen(start_string);
+
+    for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb);
+         e = APR_BUCKET_NEXT(e))
+    {
+        const char *buf;
+        apr_status_t s, bytes_to_check;
+        apr_size_t blen;
+
+        if (slen == 0)
+            return APR_SUCCESS;
+
+        if (APR_BUCKET_IS_EOS(e))
+            return APR_EOF;
+
+        s = apr_bucket_read(e, &buf, &blen, APR_BLOCK_READ);
+
+        if (s != APR_SUCCESS)
+            return s;
+
+        if (blen == 0)
+            continue;
+
+        bytes_to_check = MIN(slen,blen);
+
+        if (strncmp(buf,start_string,bytes_to_check) != 0)
+            return APREQ_ERROR_GENERAL;
+
+        slen -= bytes_to_check;
+        start_string += bytes_to_check;
+    }
+
+    /* slen > 0, so brigade isn't large enough yet */
+    return APR_INCOMPLETE;
+}
+
+
+static apr_status_t split_on_bdry(apr_bucket_brigade *out,
+                                  apr_bucket_brigade *in,
+                                  const apr_strmatch_pattern *pattern,
+                                  const char *bdry)
+{
+    apr_bucket *e = APR_BRIGADE_FIRST(in);
+    apr_size_t blen = strlen(bdry), off = 0;
+
+    while ( e != APR_BRIGADE_SENTINEL(in) ) {
+        apr_ssize_t idx;
+        apr_size_t len;
+        const char *buf;
+        apr_status_t s;
+
+        if (APR_BUCKET_IS_EOS(e))
+            return APR_EOF;
+
+        s = apr_bucket_read(e, &buf, &len, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        if (len == 0) {
+            apr_bucket *f = e;
+            e = APR_BUCKET_NEXT(e);
+            apr_bucket_delete(f);
+            continue;
+        }
+
+    look_for_boundary_up_front:
+        if (strncmp(bdry + off, buf, MIN(len, blen - off)) == 0) {
+            if ( len >= blen - off ) {
+                /* complete match */
+                if (len > blen - off)
+                    apr_bucket_split(e, blen - off);
+                e = APR_BUCKET_NEXT(e);
+
+                do {
+                    apr_bucket *f = APR_BRIGADE_FIRST(in);
+                    apr_bucket_delete(f);
+                } while (APR_BRIGADE_FIRST(in) != e);
+
+                return APR_SUCCESS;
+            }
+            /* partial match */
+            off += len;
+            e = APR_BUCKET_NEXT(e);
+            continue;
+        }
+        else if (off > 0) {
+            /* prior (partial) strncmp failed,
+             * so we can move previous buckets across
+             * and retest buf against the full bdry.
+             */
+
+            /* give hints to GCC by making the brigade volatile, otherwise the
+             * loop below will end up being endless. See:
+             * https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=193740
+             */
+            apr_bucket_brigade * volatile in_v = in;
+
+            do {
+                apr_bucket *f = APR_BRIGADE_FIRST(in_v);
+                APR_BUCKET_REMOVE(f);
+                APR_BRIGADE_INSERT_TAIL(out, f);
+            } while (e != APR_BRIGADE_FIRST(in_v));
+            off = 0;
+            goto look_for_boundary_up_front;
+        }
+
+        if (pattern != NULL && len >= blen) {
+            const char *match = apr_strmatch(pattern, buf, len);
+            if (match != NULL)
+                idx = match - buf;
+            else {
+                idx = apreq_index(buf + len-blen, blen, bdry, blen,
+                                  APREQ_MATCH_PARTIAL);
+                if (idx >= 0)
+                    idx += len-blen;
+            }
+        }
+        else
+            idx = apreq_index(buf, len, bdry, blen, APREQ_MATCH_PARTIAL);
+
+        /* Theoretically idx should never be 0 here, because we
+         * already tested the front of the brigade for a potential match.
+         * However, it doesn't hurt to allow for the possibility,
+         * since this will just start the whole loop over again.
+         */
+        if (idx >= 0)
+            apr_bucket_split(e, idx);
+
+        APR_BUCKET_REMOVE(e);
+        APR_BRIGADE_INSERT_TAIL(out, e);
+        e = APR_BRIGADE_FIRST(in);
+    }
+
+    return APR_INCOMPLETE;
+}
+
+
+static
+struct mfd_ctx * create_multipart_context(const char *content_type,
+                                          apr_pool_t *pool,
+                                          apr_bucket_alloc_t *ba,
+                                          apr_size_t brigade_limit,
+                                          const char *temp_dir,
+                                          unsigned level)
+
+{
+    apr_status_t s;
+    apr_size_t blen;
+    struct mfd_ctx *ctx = apr_palloc(pool, sizeof *ctx);
+    char *ct = apr_pstrdup(pool, content_type);
+
+    ct = strchr(ct, ';');
+    if (ct == NULL)
+        return NULL; /* missing semicolon */
+
+    *ct++ = 0;
+    s = apreq_header_attribute(ct, "boundary", 8,
+                               (const char **)&ctx->bdry, &blen);
+
+    if (s != APR_SUCCESS)
+        return NULL; /* missing boundary */
+
+    ctx->bdry[blen] = 0;
+
+    *--ctx->bdry = '-';
+    *--ctx->bdry = '-';
+    *--ctx->bdry = '\n';
+    *--ctx->bdry = '\r';
+
+    ctx->status = MFD_INIT;
+    ctx->pattern = apr_strmatch_precompile(pool, ctx->bdry, 1);
+    ctx->hdr_parser = apreq_parser_make(pool, ba, "",
+                                        apreq_parse_headers,
+                                        brigade_limit,
+                                        temp_dir, NULL, NULL);
+    ctx->info = NULL;
+    ctx->bb = apr_brigade_create(pool, ba);
+    ctx->in = apr_brigade_create(pool, ba);
+    ctx->eos = apr_bucket_eos_create(ba);
+    ctx->next_parser = NULL;
+    ctx->param_name = NULL;
+    ctx->upload = NULL;
+    ctx->level = level;
+
+    return ctx;
+}
+
+APREQ_DECLARE_PARSER(apreq_parse_multipart)
+{
+    apr_pool_t *pool = parser->pool;
+    apr_bucket_alloc_t *ba = parser->bucket_alloc;
+    struct mfd_ctx *ctx = parser->ctx;
+    apr_status_t s;
+
+    if (ctx == NULL) {
+        ctx = create_multipart_context(parser->content_type,
+                                       pool, ba,
+                                       parser->brigade_limit,
+                                       parser->temp_dir, 1);
+        if (ctx == NULL)
+            return APREQ_ERROR_GENERAL;
+
+
+        parser->ctx = ctx;
+    }
+
+    PARSER_STATUS_CHECK(MFD);
+    APR_BRIGADE_CONCAT(ctx->in, bb);
+
+ mfd_parse_brigade:
+
+    switch (ctx->status) {
+
+    case MFD_INIT:
+        {
+            s = split_on_bdry(ctx->bb, ctx->in, NULL, ctx->bdry + 2);
+            if (s != APR_SUCCESS) {
+                apreq_brigade_setaside(ctx->in, pool);
+                apreq_brigade_setaside(ctx->bb, pool);
+                return s;
+            }
+            ctx->status = MFD_NEXTLINE;
+            /* Be polite and return any preamble text to the caller. */
+            APR_BRIGADE_CONCAT(bb, ctx->bb);
+        }
+
+        /* fall through */
+
+    case MFD_NEXTLINE:
+        {
+            s = split_on_bdry(ctx->bb, ctx->in, NULL, CRLF);
+            if (s == APR_EOF) {
+                ctx->status = MFD_COMPLETE;
+                return APR_SUCCESS;
+            }
+            if (s != APR_SUCCESS) {
+                apreq_brigade_setaside(ctx->in, pool);
+                apreq_brigade_setaside(ctx->bb, pool);
+                return s;
+            }
+            if (!APR_BRIGADE_EMPTY(ctx->bb)) {
+                char *line;
+                apr_size_t len;
+                apr_brigade_pflatten(ctx->bb, &line, &len, pool);
+
+                if (len >= 2 && strncmp(line, "--", 2) == 0) {
+                    APR_BRIGADE_CONCAT(bb, ctx->in);
+                    ctx->status = MFD_COMPLETE;
+                    return APR_SUCCESS;
+                }
+                apr_brigade_cleanup(ctx->bb);
+            }
+
+            ctx->status = MFD_HEADER;
+            ctx->info = NULL;
+        }
+        /* fall through */
+
+    case MFD_HEADER:
+        {
+            if (ctx->info == NULL) {
+                ctx->info = apr_table_make(pool, APREQ_DEFAULT_NELTS);
+                /* flush out header parser internal structs for reuse */
+                ctx->hdr_parser->ctx = NULL;
+            }
+            s = apreq_parser_run(ctx->hdr_parser, ctx->info, ctx->in);
+            switch (s) {
+            case APR_SUCCESS:
+                ctx->status = MFD_POST_HEADER;
+                break;
+            case APR_INCOMPLETE:
+                apreq_brigade_setaside(ctx->in, pool);
+                return APR_INCOMPLETE;
+            default:
+                ctx->status = MFD_ERROR;
+                return s;
+            }
+        }
+        /* fall through */
+
+    case MFD_POST_HEADER:
+        {
+            /*  Must handle special case of missing CRLF (mainly
+             *  coming from empty file uploads). See RFC2065 S5.1.1:
+             *
+             *    body-part = MIME-part-header [CRLF *OCTET]
+             *
+             *  So the CRLF we already matched in MFD_HEADER may have been
+             *  part of the boundary string! Both Konqueror (v??) and
+             *  Mozilla-0.97 are known to emit such blocks.
+             *
+             *  Here we first check for this condition with
+             *  brigade_start_string, and prefix the brigade with
+             *  an additional CRLF bucket if necessary.
+             */
+
+            const char *cd, *ct, *name, *filename;
+            apr_size_t nlen, flen;
+            apr_bucket *e;
+
+            switch (brigade_start_string(ctx->in, ctx->bdry + 2)) {
+
+            case APR_INCOMPLETE:
+                apreq_brigade_setaside(ctx->in, pool);
+                return APR_INCOMPLETE;
+
+            case APR_SUCCESS:
+                /* part has no body- return CRLF to front */
+                e = apr_bucket_immortal_create(CRLF, 2,
+                                                ctx->bb->bucket_alloc);
+                APR_BRIGADE_INSERT_HEAD(ctx->in, e);
+                break;
+
+            default:
+                ; /* has body, ok */
+            }
+
+            cd = apr_table_get(ctx->info, "Content-Disposition");
+
+            /*  First check to see if must descend into a new multipart
+             *  block.  If we do, create a new parser and pass control
+             *  to it.
+             */
+
+            ct = apr_table_get(ctx->info, "Content-Type");
+
+            if (ct != NULL && strncmp(ct, "multipart/", 10) == 0) {
+                struct mfd_ctx *next_ctx;
+
+                if (ctx->level >= MAX_LEVEL) {
+                    ctx->status = MFD_ERROR;
+                    goto mfd_parse_brigade;
+                }
+
+                next_ctx = create_multipart_context(ct, pool, ba,
+                                                    parser->brigade_limit,
+                                                    parser->temp_dir,
+                                                    ctx->level + 1);
+
+                next_ctx->param_name = "";
+
+                if (cd != NULL) {
+                    s = apreq_header_attribute(cd, "name", 4,
+                                               &name, &nlen);
+                    if (s == APR_SUCCESS) {
+                        next_ctx->param_name
+                            = apr_pstrmemdup(pool, name, nlen);
+                    }
+                    else {
+                        const char *cid = apr_table_get(ctx->info,
+                                                        "Content-ID");
+                        if (cid != NULL)
+                            next_ctx->param_name = apr_pstrdup(pool, cid);
+                    }
+
+                }
+
+                ctx->next_parser = apreq_parser_make(pool, ba, ct,
+                                                     apreq_parse_multipart,
+                                                     parser->brigade_limit,
+                                                     parser->temp_dir,
+                                                     parser->hook,
+                                                     next_ctx);
+                ctx->status = MFD_MIXED;
+                goto mfd_parse_brigade;
+
+            }
+
+            /* Look for a normal form-data part. */
+
+            if (cd != NULL && strncmp(cd, "form-data", 9) == 0) {
+                s = apreq_header_attribute(cd, "name", 4, &name, &nlen);
+                if (s != APR_SUCCESS) {
+                    ctx->status = MFD_ERROR;
+                    goto mfd_parse_brigade;
+                }
+
+                s = apreq_header_attribute(cd, "filename",
+                                           8, &filename, &flen);
+                if (s == APR_SUCCESS) {
+                    apreq_param_t *param;
+
+                    param = apreq_param_make(pool, name, nlen,
+                                             filename, flen);
+                    apreq_param_tainted_on(param);
+                    param->info = ctx->info;
+                    param->upload
+                        = apr_brigade_create(pool, ctx->bb->bucket_alloc);
+                    ctx->upload = param;
+                    ctx->status = MFD_UPLOAD;
+                    goto mfd_parse_brigade;
+                }
+                else {
+                    ctx->param_name = apr_pstrmemdup(pool, name, nlen);
+                    ctx->status = MFD_PARAM;
+                    /* fall thru */
+                }
+            }
+
+            /* else check for a file part in a multipart section */
+            else if (cd != NULL && strncmp(cd, "file", 4) == 0) {
+                apreq_param_t *param;
+
+                s = apreq_header_attribute(cd, "filename",
+                                           8, &filename, &flen);
+                if (s != APR_SUCCESS || ctx->param_name == NULL) {
+                    ctx->status = MFD_ERROR;
+                    goto mfd_parse_brigade;
+                }
+                name = ctx->param_name;
+                nlen = strlen(name);
+                param = apreq_param_make(pool, name, nlen,
+                                         filename, flen);
+                apreq_param_tainted_on(param);
+                param->info = ctx->info;
+                param->upload = apr_brigade_create(pool,
+                                                   ctx->bb->bucket_alloc);
+                ctx->upload = param;
+                ctx->status = MFD_UPLOAD;
+                goto mfd_parse_brigade;
+            }
+
+            /* otherwise look for Content-ID in multipart/mixed case */
+            else {
+                const char *cid = apr_table_get(ctx->info, "Content-ID");
+                apreq_param_t *param;
+
+                if (cid != NULL) {
+                    name = cid;
+                    nlen = strlen(name);
+                }
+                else {
+                    name = "";
+                    nlen = 0;
+                }
+
+                filename = "";
+                flen = 0;
+                param = apreq_param_make(pool, name, nlen,
+                                         filename, flen);
+                apreq_param_tainted_on(param);
+                param->info = ctx->info;
+                param->upload = apr_brigade_create(pool,
+                                               ctx->bb->bucket_alloc);
+                ctx->upload = param;
+                ctx->status = MFD_UPLOAD;
+                goto mfd_parse_brigade;
+            }
+        }
+        /* fall through */
+
+    case MFD_PARAM:
+        {
+            apreq_param_t *param;
+            apreq_value_t *v;
+            apr_size_t len;
+            apr_off_t off;
+
+            s = split_on_bdry(ctx->bb, ctx->in, ctx->pattern, ctx->bdry);
+
+            switch (s) {
+
+            case APR_INCOMPLETE:
+                apreq_brigade_setaside(ctx->in, pool);
+                apreq_brigade_setaside(ctx->bb, pool);
+                return s;
+
+            case APR_SUCCESS:
+                s = apr_brigade_length(ctx->bb, 1, &off);
+                if (s != APR_SUCCESS) {
+                    ctx->status = MFD_ERROR;
+                    return s;
+                }
+                len = off;
+                param = apreq_param_make(pool, ctx->param_name,
+                                         strlen(ctx->param_name),
+                                         NULL, len);
+                apreq_param_tainted_on(param);
+                param->info = ctx->info;
+
+                *(const apreq_value_t **)&v = &param->v;
+                apr_brigade_flatten(ctx->bb, v->data, &len);
+                v->data[len] = 0;
+
+                if (parser->hook != NULL) {
+                    s = apreq_hook_run(parser->hook, param, NULL);
+                    if (s != APR_SUCCESS) {
+                        ctx->status = MFD_ERROR;
+                        return s;
+                    }
+                }
+
+                apreq_param_charset_set(param,
+                                        apreq_charset_divine(v->data, len));
+                apreq_value_table_add(v, t);
+                ctx->status = MFD_NEXTLINE;
+                ctx->param_name = NULL;
+                apr_brigade_cleanup(ctx->bb);
+                goto mfd_parse_brigade;
+
+            default:
+                ctx->status = MFD_ERROR;
+                return s;
+            }
+
+
+        }
+        break;  /* not reached */
+
+    case MFD_UPLOAD:
+        {
+            apreq_param_t *param = ctx->upload;
+
+            s = split_on_bdry(ctx->bb, ctx->in, ctx->pattern, ctx->bdry);
+            switch (s) {
+
+            case APR_INCOMPLETE:
+                if (parser->hook != NULL) {
+                    s = apreq_hook_run(parser->hook, param, ctx->bb);
+                    if (s != APR_SUCCESS) {
+                        ctx->status = MFD_ERROR;
+                        return s;
+                    }
+                }
+                apreq_brigade_setaside(ctx->bb, pool);
+                apreq_brigade_setaside(ctx->in, pool);
+                s = apreq_brigade_concat(pool, parser->temp_dir,
+                                         parser->brigade_limit,
+                                         param->upload, ctx->bb);
+                return (s == APR_SUCCESS) ? APR_INCOMPLETE : s;
+
+            case APR_SUCCESS:
+                if (parser->hook != NULL) {
+                    APR_BRIGADE_INSERT_TAIL(ctx->bb, ctx->eos);
+                    s = apreq_hook_run(parser->hook, param, ctx->bb);
+                    APR_BUCKET_REMOVE(ctx->eos);
+                    if (s != APR_SUCCESS) {
+                        ctx->status = MFD_ERROR;
+                        return s;
+                    }
+                }
+                apreq_value_table_add(&param->v, t);
+                apreq_brigade_setaside(ctx->bb, pool);
+                s = apreq_brigade_concat(pool, parser->temp_dir,
+                                         parser->brigade_limit,
+                                         param->upload, ctx->bb);
+
+                if (s != APR_SUCCESS)
+                    return s;
+
+                ctx->status = MFD_NEXTLINE;
+                goto mfd_parse_brigade;
+
+            default:
+                ctx->status = MFD_ERROR;
+                return s;
+            }
+
+        }
+        break;  /* not reached */
+
+
+    case MFD_MIXED:
+        {
+            s = apreq_parser_run(ctx->next_parser, t, ctx->in);
+            switch (s) {
+            case APR_SUCCESS:
+                ctx->status = MFD_INIT;
+                ctx->param_name = NULL;
+                goto mfd_parse_brigade;
+            case APR_INCOMPLETE:
+                APR_BRIGADE_CONCAT(bb, ctx->in);
+                return APR_INCOMPLETE;
+            default:
+                ctx->status = MFD_ERROR;
+                return s;
+            }
+
+        }
+        break; /* not reached */
+
+    default:
+        return APREQ_ERROR_GENERAL;
+    }
+
+    return APR_INCOMPLETE;
+}
diff --git a/srclib/libapreq/parser_urlencoded.c b/srclib/libapreq/parser_urlencoded.c
new file mode 100644 (file)
index 0000000..e90d0dd
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+**  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.
+*/
+
+#include "apreq_parser.h"
+#include "apreq_util.h"
+#include "apreq_error.h"
+
+
+#define PARSER_STATUS_CHECK(PREFIX)   do {         \
+    if (ctx->status == PREFIX##_ERROR)             \
+        return APREQ_ERROR_GENERAL;                \
+    else if (ctx->status == PREFIX##_COMPLETE)     \
+        return APR_SUCCESS;                        \
+    else if (bb == NULL)                           \
+        return APR_INCOMPLETE;                     \
+} while (0);
+
+
+
+struct url_ctx {
+    apr_bucket_brigade *bb;
+    apr_size_t          nlen;
+    apr_size_t          vlen;
+    enum {
+        URL_NAME,
+        URL_VALUE,
+        URL_COMPLETE,
+        URL_ERROR
+    }                   status;
+};
+
+
+/******************** application/x-www-form-urlencoded ********************/
+
+static apr_status_t split_urlword(apreq_param_t **p, apr_pool_t *pool,
+                                  apr_bucket_brigade *bb,
+                                  apr_size_t nlen,
+                                  apr_size_t vlen)
+{
+    apreq_param_t *param;
+    apreq_value_t *v;
+    apr_bucket *e, *f;
+    apr_status_t s;
+    struct iovec vec[APREQ_DEFAULT_NELTS];
+    apr_array_header_t arr;
+    apr_size_t mark;
+    apreq_charset_t charset;
+
+    if (nlen == 0)
+        return APR_EBADARG;
+
+    param = apreq_param_make(pool, NULL, nlen, NULL, vlen);
+    *(const apreq_value_t **)&v = &param->v;
+
+    arr.pool     = pool;
+    arr.elt_size = sizeof(struct iovec);
+    arr.nelts    = 0;
+    arr.nalloc   = APREQ_DEFAULT_NELTS;
+    arr.elts     = (char *)vec;
+
+    ++nlen, ++vlen;
+    e = APR_BRIGADE_FIRST(bb);
+
+    while (!APR_BUCKET_IS_EOS(e)) {
+        struct iovec *iov = apr_array_push(&arr);
+        apr_size_t len;
+        s = apr_bucket_read(e, (const char **)&iov->iov_base,
+                            &len, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        iov->iov_len = len;
+        nlen -= len;
+
+        e = APR_BUCKET_NEXT(e);
+
+        if (nlen == 0) {
+            iov->iov_len--;
+            break;
+        }
+    }
+
+    mark = arr.nelts;
+
+    while (!APR_BUCKET_IS_EOS(e)) {
+        struct iovec *iov = apr_array_push(&arr);
+        apr_size_t len;
+        s = apr_bucket_read(e, (const char **)&iov->iov_base,
+                            &len, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        iov->iov_len = len;
+        vlen -= len;
+
+        e = APR_BUCKET_NEXT(e);
+
+        if (vlen == 0) {
+            iov->iov_len--;
+            break;
+        }
+
+    }
+
+    s = apreq_decodev(v->data, &vlen,
+                      (struct iovec *)arr.elts + mark, arr.nelts - mark);
+    if (s != APR_SUCCESS)
+        return s;
+
+    charset = apreq_charset_divine(v->data, vlen);
+
+    v->name = v->data + vlen + 1;
+    v->dlen = vlen;
+
+    s = apreq_decodev(v->name, &nlen, (struct iovec *)arr.elts, mark);
+    if (s != APR_SUCCESS)
+        return s;
+
+    switch (apreq_charset_divine(v->name, nlen)) {
+    case APREQ_CHARSET_UTF8:
+        if (charset == APREQ_CHARSET_ASCII)
+            charset = APREQ_CHARSET_UTF8;
+    case APREQ_CHARSET_ASCII:
+        break;
+
+    case APREQ_CHARSET_LATIN1:
+        if (charset != APREQ_CHARSET_CP1252)
+            charset = APREQ_CHARSET_LATIN1;
+        break;
+    case APREQ_CHARSET_CP1252:
+        charset = APREQ_CHARSET_CP1252;
+    }
+
+    v->nlen = nlen;
+
+    while ((f = APR_BRIGADE_FIRST(bb)) != e)
+        apr_bucket_delete(f);
+
+    apreq_param_tainted_on(param);
+    apreq_param_charset_set(param, charset);
+    *p = param;
+    return APR_SUCCESS;
+}
+
+APREQ_DECLARE_PARSER(apreq_parse_urlencoded)
+{
+    apr_pool_t *pool = parser->pool;
+    apr_bucket *e;
+    struct url_ctx *ctx;
+
+    if (parser->ctx == NULL) {
+        ctx = apr_pcalloc(pool, sizeof *ctx);
+        ctx->bb = apr_brigade_create(pool, parser->bucket_alloc);
+        parser->ctx = ctx;
+        ctx->status = URL_NAME;
+    }
+    else
+        ctx = parser->ctx;
+
+    PARSER_STATUS_CHECK(URL);
+    e = APR_BRIGADE_LAST(ctx->bb);
+    APR_BRIGADE_CONCAT(ctx->bb, bb);
+
+ parse_url_brigade:
+
+    for (e  = APR_BUCKET_NEXT(e);
+         e != APR_BRIGADE_SENTINEL(ctx->bb);
+         e  = APR_BUCKET_NEXT(e))
+    {
+        apreq_param_t *param;
+        apr_size_t off = 0, dlen;
+        const char *data;
+        apr_status_t s;
+
+        if (APR_BUCKET_IS_EOS(e)) {
+            if (ctx->status == URL_NAME) {
+                s = APR_SUCCESS;
+            }
+            else {
+                s = split_urlword(&param, pool, ctx->bb, ctx->nlen, ctx->vlen);
+                if (parser->hook != NULL && s == APR_SUCCESS)
+                    s = apreq_hook_run(parser->hook, param, NULL);
+
+                if (s == APR_SUCCESS) {
+                    apreq_value_table_add(&param->v, t);
+                    ctx->status = URL_COMPLETE;
+                }
+                else {
+                    ctx->status = URL_ERROR;
+                }
+            }
+
+            APR_BRIGADE_CONCAT(bb, ctx->bb);
+            return s;
+        }
+
+        s = apr_bucket_read(e, &data, &dlen, APR_BLOCK_READ);
+        if ( s != APR_SUCCESS ) {
+            ctx->status = URL_ERROR;
+            return s;
+        }
+
+    parse_url_bucket:
+
+        switch (ctx->status) {
+
+        case URL_NAME:
+            while (off < dlen) {
+                switch (data[off++]) {
+                case '=':
+                    apr_bucket_split(e, off);
+                    dlen -= off;
+                    data += off;
+                    off = 0;
+                    e = APR_BUCKET_NEXT(e);
+                    ctx->status = URL_VALUE;
+                    goto parse_url_bucket;
+                default:
+                    ++ctx->nlen;
+                }
+            }
+            break;
+
+        case URL_VALUE:
+            while (off < dlen) {
+
+                switch (data[off++]) {
+                case '&':
+                case ';':
+                    apr_bucket_split(e, off);
+                    s = split_urlword(&param, pool, ctx->bb,
+                                      ctx->nlen, ctx->vlen);
+                    if (parser->hook != NULL && s == APR_SUCCESS)
+                        s = apreq_hook_run(parser->hook, param, NULL);
+
+                    if (s != APR_SUCCESS) {
+                        ctx->status = URL_ERROR;
+                        return s;
+                    }
+
+                    apreq_value_table_add(&param->v, t);
+                    ctx->status = URL_NAME;
+                    ctx->nlen = 0;
+                    ctx->vlen = 0;
+                    e = APR_BRIGADE_SENTINEL(ctx->bb);
+                    goto parse_url_brigade;
+
+                default:
+                    ++ctx->vlen;
+                }
+            }
+            break;
+        default:
+            ; /* not reached */
+        }
+    }
+    apreq_brigade_setaside(ctx->bb, pool);
+    return APR_INCOMPLETE;
+}
+
+
diff --git a/srclib/libapreq/t/Makefile.am b/srclib/libapreq/t/Makefile.am
new file mode 100644 (file)
index 0000000..8b2d6f5
--- /dev/null
@@ -0,0 +1,19 @@
+AM_CPPFLAGS = @APR_INCLUDES@
+AM_LDFLAGS = `@APREQ_CONFIG@ --link-libtool --libs` @APR_LTFLAGS@
+noinst_LIBRARIES = libapache_test.a
+libapache_test_a_SOURCES = at.h at.c
+
+check_PROGRAMS = version cookie params parsers error util
+LDADD  = libapache_test.a
+
+check_SCRIPTS = version.t cookie.t params.t parsers.t error.t util.t
+TESTS = $(check_SCRIPTS)
+TESTS_ENVIRONMENT = @PERL@ -MTest::Harness -e 'runtests(@ARGV)'
+CLEANFILES = $(check_PROGRAMS) $(check_SCRIPTS)
+
+%.t: %
+       echo "#!perl" > $@
+       echo "exec './$*'" >> $@
+
+test: $(check_SCRIPTS)
+       $(TESTS_ENVIRONMENT) $(check_SCRIPTS)
diff --git a/srclib/libapreq/t/at.c b/srclib/libapreq/t/at.c
new file mode 100644 (file)
index 0000000..19e59cb
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+**  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.
+*/
+
+#include "at.h"
+#include <errno.h>
+#include <ctype.h>
+
+#define AT_SUCCESS 0
+#define AT_EGENERAL 14
+#define min(a, b) ((a < b) ? a : b)
+
+int at_begin(at_t *t, int total)
+{
+    char buf[32];
+    at_snprintf(buf, 32, "1..%d", total);
+    return at_report(t, buf);
+}
+
+static int test_cleanup(void *data)
+{
+    at_t *t = data;
+    if (t->current < t->plan)
+        return at_report(t, "Bail out!");
+    else
+        return at_report(t, "END");
+}
+
+void at_end(at_t *t)
+{
+    test_cleanup(t);
+    free(t);
+}
+
+int at_comment(at_t *t, const char *fmt, va_list vp)
+{
+    int s;
+    char buf[256], *b = buf + 2;
+    char *end;
+    int rv;
+    rv = at_vsnprintf(b, 250, fmt, vp);
+
+    if (rv <= 0)
+        return EINVAL;
+
+
+    end = b + min(rv, 250);
+
+    buf[0] = '#';
+    buf[1] = ' ';
+
+    if (rv >= 250) {
+        end[-1] = '.';
+        *end++ = '.';
+        *end++ = '.';
+        *end++ = '\n';
+        *end = 0;
+    }
+    else if (end[-1] != '\n') {
+        *end++ = '\n';
+        *end = 0;
+    }
+
+    b = buf;
+    while (1) {
+        char *eol;
+
+        eol = strchr(b + 2, '\n');
+        *eol = 0;
+        s = at_report(t, b);
+        if (s != AT_SUCCESS || eol == end - 1)
+            break;
+
+        b    = eol - 1;
+        b[0] = '#';
+        b[1] = ' ';
+    }
+
+    return s;
+}
+
+void at_ok(at_t *t, int is_ok, const char *label, const char *file, int line)
+{
+    char format[] = "not ok %d - %s # %s (%s:%d) test %d in %s";
+    char *end = format + 10;
+    char *fmt = is_ok ? format + 4 : format;
+    char buf[256];
+    const char *comment = NULL;
+    int rv, is_fatal = 0, is_skip = 0, is_todo = 0;
+
+    t->current++;
+
+    if (*t->fatal == t->current) {
+        t->fatal++;
+        is_fatal = 1;
+    }
+
+    if (*t->skip == t->current) {
+        t->skip++;
+        is_skip = 1;
+    }
+
+    if (*t->todo == t->current) {
+        t->todo++;
+        is_todo = 1;
+    }
+    
+    if (AT_FLAG_TODO(t->flags))
+        is_todo = 1;
+
+    if (AT_FLAG_CONCISE(t->flags))
+        format[9] = '\0';
+    else if (is_ok && ! is_todo && !AT_FLAG_TRACE(t->flags))
+        format[14] = '\0';
+    else if (is_fatal && ! is_ok)
+        comment = "fatal";
+    else
+        comment = is_todo ? "todo" : is_skip ? "skip" : "at";
+
+    rv = at_snprintf(buf, 256, fmt, t->current + t->prior,
+                     label, comment,  file, line, t->current, t->name);
+
+    if (rv <= 0)
+        exit(-1);
+
+    end = buf + min(rv, 250);
+
+    if (rv >= 250) {
+        *end++ = '.';
+        *end++ = '.';
+        *end++ = '.';
+        *end = '\0';
+    }
+
+    if (memchr(buf, '\n', rv) != NULL || at_report(t, buf) != AT_SUCCESS)
+        exit(-1);
+
+    if (!is_ok && is_fatal) {
+        while (t->current++ < t->plan) {
+            at_snprintf(buf, 256, "not ok %d # skipped: aborting test %s",
+                        t->prior + t->current, t->name);
+            at_report(t, buf);
+        }
+        longjmp(*t->abort, 0);
+    }
+}
+
+struct at_report_file {
+    at_report_t module;
+    FILE *file;
+};
+
+static int at_report_file_write(at_report_t *ctx, const char *msg)
+{
+    struct at_report_file *r = (struct at_report_file *)ctx;
+    FILE *f = r->file;
+    size_t len = strlen(msg);
+    size_t bytes_written;
+    int status;
+
+    bytes_written = fwrite(msg, sizeof(char), len, f);
+    if (bytes_written != len)
+        return errno;
+
+    status = putc('\n', f);
+    if (status == EOF)
+        return errno;
+
+    return fflush(f);
+}
+
+at_report_t *at_report_file_make(FILE *f)
+{
+    struct at_report_file *r = malloc(sizeof *r);
+    r->module.func = at_report_file_write;
+    r->file = f;
+    return &r->module;
+}
+
+void at_report_file_cleanup(at_report_t *r)
+{
+    free(r);
+}
+
+struct at_report_local {
+    at_report_t  module;
+    at_t        *t;
+    at_report_t *saved_report;
+    const int   *saved_fatal;
+    const int   *saved_skip;
+    const int   *saved_todo;
+    int          dummy_fatal;
+    char        *file;
+    int          line;
+    int          passed;
+};
+
+static int report_local_cleanup(void *data)
+{
+    struct at_report_local *q = data;
+    dAT = q->t;
+    char label[32];
+
+    at_snprintf(label, 32, "collected %d passing tests", q->passed);
+
+    AT->report = q->saved_report;
+    AT->fatal  = q->saved_fatal;
+    AT->skip   = q->saved_skip;
+    AT->todo   = q->saved_todo;
+
+    at_ok(q->t, 1, label, q->file, q->line);
+
+    free(q->file);
+    free(q);
+
+    return AT_SUCCESS;
+}
+
+void at_report_delocalize(at_t *AT) {
+    report_local_cleanup(AT->report);
+}
+
+static int at_report_local_write(at_report_t *ctx, const char *msg)
+{
+    char buf[256];
+    struct at_report_local *q = (struct at_report_local *)ctx;
+    dAT = q->t;
+
+    if (strncmp(msg, "not ok", 6) == 0) {
+        q->saved_report->func(q->saved_report, msg);
+        report_local_cleanup(q);
+        while (AT->current++ < AT->plan) {
+            at_snprintf(buf, 256, "not ok %d # skipped: aborting test %s",
+                        AT->prior + AT->current, AT->name);
+            at_report(AT, buf);
+        }
+        longjmp(*AT->abort, 0);
+    }
+    else if (strncmp(msg, "ok", 2) == 0) {
+        AT->current--;
+        q->passed++;
+    }
+    return AT_SUCCESS;
+}
+
+void at_report_local(at_t *AT, const char *file, int line)
+{
+    struct at_report_local *q = malloc(sizeof *q);
+    size_t len;
+
+    q->module.func = at_report_local_write;
+    q->t = AT;
+    q->saved_report = AT->report;
+    q->saved_fatal = AT->fatal;
+    q->saved_skip = AT->skip;
+    q->saved_todo = AT->todo;
+    q->dummy_fatal = 0;
+    q->line = line;
+    q->passed = 0;
+
+    len = strlen(file) + 1;
+    q->file = (char*)malloc(len);
+    memcpy(q->file, file, len);
+
+    AT->fatal = AT->skip = AT->todo = &q->dummy_fatal;
+    AT->report = &q->module;
+
+    if (*q->saved_fatal == AT->current + 1)
+        q->saved_fatal++;
+
+}
+
+at_t *at_create(unsigned char flags, at_report_t *report)
+{
+    at_t *t = calloc(sizeof *t, 1);
+    t->flags = flags;
+    t->report = report;
+    return t;
+}
+
+static int* at_list(const char *spec)
+{
+    int prev, current = 0, count = 0, idx = 0, *elts;
+    const char *start = spec;
+
+    do {
+        while (*spec && !isdigit((unsigned char)*spec))
+            ++spec;
+        prev = current;
+        current = (int)strtol(spec, (char **)(void *)&spec, 10);
+        ++count;
+
+    } while (prev <= current);
+
+    elts = malloc(++count * sizeof *elts);
+    spec = start;
+    current = 0;
+
+    do {
+        while (*spec && !isdigit((unsigned char)*spec))
+            ++spec;
+        prev = current;
+        current = (int)strtol(spec, (char **)(void *)&spec, 10);
+        elts[idx++] = current;
+
+    } while (prev <= current);
+
+    elts[idx] = 0; /* sentinel */
+
+    return elts;
+}
+
+#define at_free_lists do { if (flist) free((void *)flist);       \
+                           if (slist) free((void *)slist);       \
+                           if (tlist) free((void *)tlist); } while (0)
+
+int at_run(at_t *AT, const at_test_t *test)
+{
+    int dummy = 0;
+    const int *flist = NULL, *slist = NULL, *tlist = NULL;
+    jmp_buf j;
+
+    AT->current = 0;
+    AT->prior   += AT->plan;
+    AT->name    = test->name;
+    AT->plan    = test->plan;
+
+    if (test->fatals)
+        flist = AT->fatal = at_list(test->fatals);
+    else
+        AT->fatal = &dummy;
+
+    if (test->skips)
+        slist = AT->skip = at_list(test->skips);
+    else
+        AT->skip = &dummy;
+
+    if (test->todos)
+        tlist = AT->todo = at_list(test->todos);
+    else
+        AT->todo = &dummy;
+
+    AT->abort = &j;
+    if (setjmp(j) == 0) {
+        test->func(AT, test->ctx);
+        at_free_lists;
+        return AT_SUCCESS;
+    }
+    at_free_lists;
+    AT->abort = NULL;
+    return AT_EGENERAL;
+}
+
+int at_snprintf(char *buf, int size, const char *format, ...)
+{
+    va_list args;
+    int status;
+    va_start(args, format);
+    status = at_vsnprintf(buf, size, format, args);
+    va_end(args);
+    return status;
+}
+
+int at_vsnprintf(char *buf, int size, const char *format, va_list args)
+{
+#ifdef _MSC_VER
+    int status = _vsnprintf(buf, size, format, args);
+    /* Make Microsoft's _vsnprintf behave like C99 vsnprintf on overflow:
+     * NULL-terminate, and return the number of chars printed minus one. */
+    if (status < 0) {
+        if (errno != EINVAL) {
+            status = size - 1;
+            buf[status] = '\0';
+        }
+    }
+    return status;
+#else 
+    return vsnprintf(buf, size, format, args);
+#endif /* _MSC_VER */
+}
+
diff --git a/srclib/libapreq/t/at.h b/srclib/libapreq/t/at.h
new file mode 100644 (file)
index 0000000..2ec318f
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+**  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.
+*/
+
+
+/* at.h: TAP-compliant C utilities for the Apache::Test framework. */
+
+#ifndef AT_H
+#define AT_H
+
+#include <stdarg.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef struct at_t at_t;
+typedef struct at_report_t at_report_t;
+
+typedef int (*at_report_function_t)(at_report_t *r, const char *msg);
+typedef void(*at_test_function_t)(at_t *t, void *ctx);
+typedef struct at_test_t at_test_t;
+
+struct at_test_t {
+    const char          *name;
+    at_test_function_t   func;
+    int                  plan;
+    void                *ctx;
+    const char          *fatals;
+    const char          *skips;
+    const char          *todos;
+};
+
+struct at_report_t {
+    at_report_function_t func;
+};
+
+/* Private, portable snprintf implementation. 
+ */
+int at_snprintf(char *buf, int size, const char *format, ...);
+int at_vsnprintf(char *buf, int size, const char *format, va_list args); 
+
+/* We only need one at_t struct per test suite, so lets call it *AT.
+ * The mnemonic we follow is that (for lowercase foo) "AT_foo(bar)"
+ * should be syntactically equivalent to "at_foo(AT, bar)".
+ *
+ * Terminology: test == an at_test_t,
+ *              check == an assertion which produces TAP.
+ */
+
+#define dAT at_t *AT
+
+struct at_t {
+    int                  current; /* current check for this test */
+    int                  prior;   /* total # of checks prior to this test */
+    const char          *name;    /* name of current test */
+    int                  plan;    /* total # of checks in this test */
+    const int           *fatal;   /* list of unrecoverables */
+    const int           *skip;    /* list of ignorabe assertions */
+    const int           *todo;    /* list of expected failures */
+    at_report_t         *report  ;/* handles the results of each check */
+    unsigned char        flags;   /* verbosity: concise, trace, debug, etc. */
+    jmp_buf             *abort;   /* where fatals go to die */
+};
+
+
+
+static inline
+int at_report(at_t *t, const char *msg) {
+    at_report_t *r = t->report;
+    return r->func(r, msg);
+}
+#define AT_report(msg) at_report(AT, msg)
+
+/* The core assertion checker; the rest just wind up invoking this one. */
+void at_ok(at_t *t, int is_ok, const char *label, const char *file, int line);
+#define AT_ok(is_ok, label) at_ok(AT, is_ok, label, __FILE__, __LINE__)
+
+at_t *at_create(unsigned char flags, at_report_t *report);
+int at_begin(at_t *t, int total);
+#define AT_begin(total) at_begin(AT, total)
+
+int at_run(at_t *AT, const at_test_t *test);
+#define AT_run(test) at_run(AT, test)
+
+void at_end(at_t *t);
+#define AT_end() at_end(AT)
+
+
+#define AT_FLAG_TODO(f)       ((f) & 8)
+#define AT_FLAG_TODO_ON(f)    ((f) |= 8)
+#define AT_FLAG_TODO_OFF(f)   ((f) &= ~8)
+#define AT_FLAG_DEBUG(f)       ((f) & 4)
+#define AT_FLAG_DEBUG_ON(f)    ((f) |= 4)
+#define AT_FLAG_DEBUG_OFF(f)   ((f) &= ~4)
+#define AT_FLAG_TRACE(f)       ((f) & 2)
+#define AT_FLAG_TRACE_ON(f)    ((f) |= 2)
+#define AT_FLAG_TRACE_OFF(f)   ((f) &= ~2)
+#define AT_FLAG_CONCISE(f)     ((f) & 1)
+#define AT_FLAG_CONCISE_ON(f)  ((f) |= 1)
+#define AT_FLAG_CONCISE_OFF(f) ((f) &= ~1)
+
+#define AT_todo_on()       AT_FLAG_TODO_ON(AT->flags)
+#define AT_todo_off()      AT_FLAG_TODO_OFF(AT->flags)
+#define AT_debug_on()      AT_FLAG_DEBUG_ON(AT->flags)
+#define AT_debug_off()     AT_FLAG_DEBUG_OFF(AT->flags)
+#define AT_trace_on()      AT_FLAG_TRACE_ON(AT->flags)
+#define AT_trace_off()     AT_FLAG_TRACE_OFF(AT->flags)
+#define AT_concise_on()    AT_FLAG_CONCISE_ON(AT->flags)
+#define AT_concise_off()   AT_FLAG_CONCISE_OFF(AT->flags)
+
+
+
+/* Additional reporting utils.
+   These emit TAP comments, and are not "checks". */
+
+int at_comment(at_t *t, const char *fmt, va_list vp);
+
+static inline
+void at_debug(at_t *t, const char *fmt, ...) {
+    va_list vp;
+    va_start(vp, fmt);
+    if (AT_FLAG_DEBUG(t->flags))
+        at_comment(t, fmt, vp);
+    va_end(vp);
+}
+
+static inline
+void at_trace(at_t *t, const char *fmt, ...) {
+    va_list vp;
+    va_start(vp, fmt);
+    if (AT_FLAG_TRACE(t->flags))
+        at_comment(t, fmt, vp);
+    va_end(vp);
+}
+
+
+/* These are "checks". */
+
+static inline
+void at_check(at_t *t, int is_ok, const char *label, const char *file,
+           int line, const char *fmt, ...)
+{
+    va_list vp;
+
+    va_start(vp, fmt);
+    if (AT_FLAG_TRACE(t->flags)) {
+        char format[32] = "testing: %s (%s:%d)";
+        at_trace(t, format, label, file, line);
+
+        if (fmt != NULL) {
+            char *f;
+            at_snprintf(format, sizeof format, " format: %s", fmt);
+            at_trace(t, "%s", format);
+            memcpy(format, "   left:", 8);
+            f = format + strlen(format);
+            at_snprintf(f, sizeof format - strlen(format), "\n  right: %s", fmt);
+            at_comment(t, format, vp);
+        }
+    }
+    else if (AT_FLAG_DEBUG(t->flags) && !is_ok) {
+        char format[32] = "testing: %s (%s:%d)";
+        at_debug(t, format, label, file, line);
+
+        if (fmt != NULL) {
+            char *f;
+            at_snprintf(format, sizeof format, " format: %s", fmt);
+            at_debug(t, "%s", format);
+            memcpy(format, "   left:", 8);
+            f = format + strlen(format);
+            at_snprintf(f, sizeof format - strlen(format), "\n  right: %s", fmt);
+            at_comment(t, format, vp);
+        }
+    }
+    va_end(vp);
+    at_ok(t, is_ok, label, file, line);
+}
+
+
+#define AT_mem_ne(a, b, n) do {                                         \
+    unsigned sz = n;                                                    \
+    const void *left = a, *right = b;                                   \
+    char fmt[] =  ", as %u-byte struct pointers";                       \
+    char buf[256] = #a " != " #b;                                       \
+    const unsigned blen = sizeof(#a " != " #b);                         \
+    at_snprintf(buf + blen - 1, 256 - blen, fmt, sz);                   \
+    at_snprintf(fmt, sizeof(fmt), "%%.%us", sz);                        \
+    at_check(AT, memcmp(left, right, sz), buf, __FILE__, __LINE__,      \
+           fmt, left, right);                                           \
+} while (0)                                                             \
+
+#define AT_mem_eq(a, b, n) do {                                         \
+    unsigned sz = n;                                                    \
+    const void *left = a, *right = b;                                   \
+    char fmt[] =  ", as %u-byte struct pointers";                       \
+    char buf[256] = #a " == " #b;                                       \
+    const unsigned blen = sizeof(#a " == " #b);                         \
+    at_snprintf(buf + blen - 1, 256 - blen , fmt, sz);                  \
+    at_snprintf(fmt, sizeof(fmt), "%%.%us", sz);                        \
+    at_check(AT, !memcmp(left, right, sz), buf, __FILE__, __LINE__,     \
+           fmt, left, right);                                           \
+} while (0)
+
+
+
+#define AT_str_eq(a, b) do {                                            \
+    const char *left = a, *right = b;                                   \
+    at_check(AT,!strcmp(left, right), #a " == " #b ", as strings",      \
+            __FILE__, __LINE__, "%s", left, right);                     \
+} while (0)
+
+
+#define AT_str_ne(a, b) do {                                           \
+    const char *left = a, *right = b;                                  \
+    at_check(AT, strcmp(left, right), #a " != " #b ", as strings",     \
+            __FILE__, __LINE__, "%s", left, right);                    \
+} while (0)
+
+#define AT_ptr_eq(a, b) do {                                    \
+    const void *left = a, *right = b;                           \
+    at_check(AT, left == right, #a " == " #b ", as pointers",   \
+            __FILE__, __LINE__, "%p", left, right);             \
+} while (0)
+
+#define AT_ptr_ne(a, b) do {                                    \
+    const void *left = a, *right = b;                           \
+    at_check(AT, left != right, #a " != " #b ", as pointers",   \
+            __FILE__, __LINE__, "%p", left, right);             \
+} while (0)
+
+
+#define AT_int_eq(a, b) do {                                    \
+    const int left = a, right = b;                              \
+    at_check(AT, left == right, #a " == " #b ", as integers",   \
+            __FILE__, __LINE__, "%d", left, right);             \
+} while (0)
+
+#define AT_int_ne(a, b) do {                                    \
+    const int left = a, right = b;                              \
+    at_check(AT, left != right, #a " != " #b ", as integers",   \
+            __FILE__, __LINE__, "%d", left, right);             \
+} while (0)
+
+#define AT_is_null(a)  AT_ptr_eq(a, NULL)
+#define AT_not_null(a) AT_ptr_ne(a, NULL)
+
+
+/* XXX these two macro checks evaluate a & b more than once, but the
+ * upshot is that they don't care too much about their types.
+ */
+
+#define AT_EQ(a, b, fmt) at_check(AT, ((a) == (b)), #a " == " #b,\
+                                  __FILE__, __LINE__, fmt, a, b)
+#define AT_NE(a, b, fmt) at_check(AT, ((a) != (b)), #a " != " #b,\
+                                  __FILE__, __LINE__, fmt, a, b)
+
+
+static inline
+void at_skip(at_t *t, int n, const char *reason, const char *file, int line) {
+    char buf[256];
+    while (n-- > 0) {
+        ++t->current;
+        at_snprintf(buf, 256, "ok %d - %s (%d) #skipped: %s (%s:%d)",
+                    t->current + t->prior, t->name, t->current, reason, file, line);
+        at_report(t, buf);
+    }
+}
+
+#define AT_skip(n, reason) at_skip(AT, n, reason, __FILE__, __LINE__)
+
+
+/* Report utilities. */
+
+at_report_t *at_report_file_make(FILE *f);
+inline
+static at_report_t *at_report_stdout_make(void)
+{
+    return at_report_file_make(stdout);
+}
+void at_report_file_cleanup(at_report_t *r);
+#define at_report_stdout_cleanup(r) at_report_file_cleanup(r)
+
+void at_report_local(at_t *AT, const char *file, int line);
+#define AT_localize() at_report_local(AT, __FILE__, __LINE__)
+void at_report_delocalize(at_t *AT);
+#define AT_delocalize() at_report_delocalize(AT)
+
+#endif /* AT_H */
diff --git a/srclib/libapreq/t/cookie.c b/srclib/libapreq/t/cookie.c
new file mode 100644 (file)
index 0000000..4399e4f
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+**  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.
+*/
+
+#include "apr_strings.h"
+#include "apreq_cookie.h"
+#include "apreq_error.h"
+#include "apreq_module.h"
+#include "apreq_util.h"
+#include "at.h"
+
+static const char nscookies[] = "a=1; foo=bar; fl=left; fr=right;bad; "
+                                "ns=foo=1&bar=2,frl=right-left; "
+                                "flr=left-right; fll=left-left; "
+                                "good_one=1;=;bad";
+
+static const char rfccookies[] = "$Version=1; first=a;$domain=quux;second=be,"
+                                 "$Version=1;third=cie";
+
+static const char wpcookies[] = "wordpressuser_c580712eb86cad2660b3601ac"
+                                "04202b2=admin; wordpresspass_c580712eb8"
+                                "6cad2660b3601ac04202b2=7ebeeed42ef50720"
+                                "940f5b8db2f9db49; rs_session=59ae9b8b50"
+                                "3e3af7d17b97e7f77f7ea5; dbx-postmeta=gr"
+                                "abit=0-,1-,2-,3-,4-,5-,6-&advancedstuff"
+                                "=0-,1+,2-";
+
+static const char cgcookies1[] = "UID=MTj9S8CoAzMAAFEq21YAAAAG|c85a9e59db"
+                                 "92b261408eb7539ff7f949b92c7d58; $Versio"
+                                 "n=0;SID=MTj9S8CoAzMAAFEq21YAAAAG|c85a9e"
+                                 "59db92b261408eb7539ff7f949b92c7d58;$Dom"
+                                 "ain=www.xxxx.com;$Path=/";
+
+static const char cgcookies2[] = "UID=Gh9VxX8AAAIAAHP7h6AAAAAC|2e809a9cc9"
+                                 "9c2dca778c385ebdefc5cb86c95dc3; SID=Gh9"
+                                 "VxX8AAAIAAHP7h6AAAAAC|2e809a9cc99c2dca7"
+                                 "78c385ebdefc5cb86c95dc3; $Version=1";
+
+static const char cgcookies3[] = "UID=hCijN8CoAzMAAGVDO2QAAAAF|50299f0793"
+                                 "43fd6146257c105b1370f2da78246a; SID=hCi"
+                                 "jN8CoAzMAAGVDO2QAAAAF|50299f079343fd614"
+                                 "6257c105b1370f2da78246a; $Path=\"/\"; $"
+                                 "Domain=\"www.xxxx.com\"";
+
+static const char cgcookies4[] = "SID=66XUEH8AAAIAAFmLLRkAAAAV|2a48c4ae2e"
+                                 "9fb8355e75192db211f0779bdce244; UID=66X"
+                                 "UEH8AAAIAAFmLLRkAAAAV|2a48c4ae2e9fb8355"
+                                 "e75192db211f0779bdce244; __utma=1441491"
+                                 "62.4479471199095321000.1234471650.12344"
+                                 "71650.1234471650.1; __utmb=144149162.24"
+                                 ".10.1234471650; __utmc=144149162; __utm"
+                                 "z=\"144149162.1234471650.1.1.utmcsr=szu"
+                                 "kaj.xxxx.pl|utmccn=(referral)|utmcmd=re"
+                                 "ferral|utmcct=/internet/0,0.html\"";
+
+static apr_table_t *jar, *jar2, *jar3, *jar4, *jar5, *jar6, *jar7;
+static apr_pool_t *p;
+
+static void jar_make(dAT, void *ctx)
+{
+    jar = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar);
+    AT_int_eq(apreq_parse_cookie_header(p, jar, nscookies), APREQ_ERROR_NOTOKEN);
+    jar2 = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar2);
+    AT_int_eq(apreq_parse_cookie_header(p, jar2, rfccookies), APR_SUCCESS);
+    jar3 = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar3);
+    AT_int_eq(apreq_parse_cookie_header(p, jar3, wpcookies), APREQ_ERROR_NOTOKEN);
+    jar4 = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar4);
+    AT_int_eq(apreq_parse_cookie_header(p, jar4, cgcookies1), APREQ_ERROR_MISMATCH);
+    jar5 = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar5);
+    AT_int_eq(apreq_parse_cookie_header(p, jar5, cgcookies2), APREQ_ERROR_MISMATCH);
+    jar6 = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar6);
+    AT_int_eq(apreq_parse_cookie_header(p, jar6, cgcookies3), APREQ_ERROR_MISMATCH);
+    jar7 = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(jar7);
+    AT_int_eq(apreq_parse_cookie_header(p, jar7, cgcookies4), APR_SUCCESS);
+}
+
+static void jar_get_rfc(dAT, void *ctx)
+{
+    const char *val;
+    AT_not_null(val = apr_table_get(jar2, "first"));
+    AT_str_eq(val, "a");
+    AT_not_null(val = apr_table_get(jar2, "second"));
+    AT_str_eq(val, "be");
+    AT_not_null(val = apr_table_get(jar2, "third"));
+    AT_str_eq(val, "cie");
+}
+
+static void jar_get_ns(dAT, void *ctx)
+{
+
+    AT_str_eq(apr_table_get(jar, "a"), "1");
+
+    /* ignore wacky cookies that don't have an '=' sign */
+    AT_is_null(apr_table_get(jar, "bad"));
+
+    /* accept wacky cookies that contain multiple '=' */
+    AT_str_eq(apr_table_get(jar, "ns"), "foo=1&bar=2");
+
+    AT_str_eq(apr_table_get(jar,"foo"), "bar");
+    AT_str_eq(apr_table_get(jar,"fl"),  "left");
+    AT_str_eq(apr_table_get(jar,"fr"),  "right");
+    AT_str_eq(apr_table_get(jar,"frl"), "right-left");
+    AT_str_eq(apr_table_get(jar,"flr"), "left-right");
+    AT_str_eq(apr_table_get(jar,"fll"), "left-left");
+    AT_is_null(apr_table_get(jar,""));
+}
+
+
+static void netscape_cookie(dAT, void *ctx)
+{
+    char expires[APR_RFC822_DATE_LEN];
+    char *val;
+    apreq_cookie_t *c;
+
+    *(const char **)&val = apr_table_get(jar, "foo");
+    AT_not_null(val);
+
+    c = apreq_value_to_cookie(val);
+
+    AT_str_eq(c->v.data, "bar");
+    AT_int_eq(apreq_cookie_version(c), 0);
+    AT_str_eq(apreq_cookie_as_string(c, p), "foo=bar");
+
+    c->domain = apr_pstrdup(p, "example.com");
+    AT_str_eq(apreq_cookie_as_string(c, p), "foo=bar; domain=example.com");
+
+    c->path = apr_pstrdup(p, "/quux");
+    AT_str_eq(apreq_cookie_as_string(c, p),
+              "foo=bar; path=/quux; domain=example.com");
+
+    apreq_cookie_expires(c, "+1y");
+    apr_rfc822_date(expires, apr_time_now()
+                             + apr_time_from_sec(apreq_atoi64t("+1y")));
+    expires[7] = '-';
+    expires[11] = '-';
+    val = apr_pstrcat(p, "foo=bar; path=/quux; domain=example.com; expires=",
+                      expires, NULL);
+
+    AT_str_eq(apreq_cookie_as_string(c, p), val);
+}
+
+
+static void rfc_cookie(dAT, void *ctx)
+{
+    apreq_cookie_t *c = apreq_cookie_make(p,"rfc",3,"out",3);
+    const char *expected;
+    long expires;
+
+    AT_str_eq(c->v.data, "out");
+
+    apreq_cookie_version_set(c, 1);
+    AT_int_eq(apreq_cookie_version(c), 1);
+    AT_str_eq(apreq_cookie_as_string(c,p),"rfc=out; Version=1");
+
+    c->domain = apr_pstrdup(p, "example.com");
+
+#ifndef WIN32
+
+    AT_str_eq(apreq_cookie_as_string(c,p),
+              "rfc=out; Version=1; domain=\"example.com\"");
+    c->path = apr_pstrdup(p, "/quux");
+    AT_str_eq(apreq_cookie_as_string(c,p),
+              "rfc=out; Version=1; path=\"/quux\"; domain=\"example.com\"");
+
+    apreq_cookie_expires(c, "+3m");
+    expires = apreq_atoi64t("+3m");
+    expected = apr_psprintf(p, "rfc=out; Version=1; path=\"/quux\"; "
+                       "domain=\"example.com\"; max-age=%ld",
+                       expires);
+    AT_str_eq(apreq_cookie_as_string(c,p), expected);
+
+#else
+
+    expected = "rfc=out; Version=1; domain=\"example.com\"";
+    AT_str_eq(apreq_cookie_as_string(c,p), expected);
+
+    c->path = apr_pstrdup(p, "/quux");
+    expected = "rfc=out; Version=1; path=\"/quux\"; domain=\"example.com\"";
+    AT_str_eq(apreq_cookie_as_string(c,p), expected);
+
+    apreq_cookie_expires(c, "+3m");
+    expires = apreq_atoi64t("+3m");
+    expected = apr_psprintf(p, "rfc=out; Version=1; path=\"/quux\"; "
+                           "domain=\"example.com\"; max-age=%ld",
+                           expires);
+    AT_str_eq(apreq_cookie_as_string(c,p), expected);
+
+#endif
+
+}
+
+
+#define dT(func, plan) #func, func, plan, NULL
+
+
+int main(int argc, char *argv[])
+{
+    unsigned i, plan = 0;
+    dAT;
+    at_test_t test_list [] = {
+        { dT(jar_make, 14) },
+        { dT(jar_get_rfc, 6), "1 3 5" },
+        { dT(jar_get_ns, 10) },
+        { dT(netscape_cookie, 7) },
+        { dT(rfc_cookie, 6) },
+    };
+
+    apr_initialize();
+    atexit(apr_terminate);
+
+    apr_pool_create(&p, NULL);
+
+    AT = at_create(0, at_report_stdout_make());
+    AT_trace_on();
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        plan += test_list[i].plan;
+
+    AT_begin(plan);
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        AT_run(&test_list[i]);
+
+    AT_end();
+
+    return 0;
+}
diff --git a/srclib/libapreq/t/error.c b/srclib/libapreq/t/error.c
new file mode 100644 (file)
index 0000000..49c21d1
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+**  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.
+*/
+
+#include "apr_strings.h"
+#include "apreq_error.h"
+#include "at.h"
+
+
+static void test_strerror(dAT, void *ctx)
+{
+    char buf[256], *str;
+
+    str = apreq_strerror(APREQ_ERROR_GENERAL, buf, sizeof buf);
+    AT_ptr_eq(str, buf);
+    AT_str_eq(str, "Internal apreq error");
+
+    str = apreq_strerror(APREQ_ERROR_TAINTED, buf, sizeof buf);
+    AT_str_eq(str, "Attempt to perform unsafe action with tainted data");
+
+    str = apreq_strerror(APREQ_ERROR_BADSEQ, buf, sizeof buf);
+    AT_str_eq(str, "Invalid byte sequence");
+
+    str = apreq_strerror(APREQ_ERROR_NODATA, buf, sizeof buf);
+    AT_str_eq(str, "Missing input data");
+
+    str = apreq_strerror(APREQ_ERROR_GENERAL+99, buf, sizeof buf);
+    AT_str_eq(str, "Error string not yet specified by apreq");
+
+
+
+
+    /* Test some common APR status codes also */
+
+    str = apreq_strerror(APR_EINIT, buf, sizeof buf);
+    AT_str_eq(str, "There is no error, this value signifies an initialized "
+                   "error code");
+
+    str = apreq_strerror(APR_INCOMPLETE, buf, sizeof buf);
+    AT_str_eq(str, "Partial results are valid but processing is incomplete");
+
+    str = apreq_strerror(APR_EOF, buf, sizeof buf);
+    AT_str_eq(str, "End of file found");
+
+    str = apreq_strerror(APR_ENOTIMPL, buf, sizeof buf);
+    AT_str_eq(str, "This function has not been implemented on this platform");
+
+ }
+
+#define dT(func, plan) #func, func, plan, NULL
+
+
+int main(int argc, char *argv[])
+{
+    unsigned i, plan = 0;
+    apr_pool_t *p;
+    dAT;
+    at_test_t test_list [] = {
+        { dT(test_strerror, 10), "1" }
+    };
+
+    apr_initialize();
+    atexit(apr_terminate);
+
+    apr_pool_create(&p, NULL);
+
+    AT = at_create(0, at_report_stdout_make());
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        plan += test_list[i].plan;
+
+    AT_begin(plan);
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        AT_run(&test_list[i]);
+
+    AT_end();
+
+    return 0;
+}
diff --git a/srclib/libapreq/t/params.c b/srclib/libapreq/t/params.c
new file mode 100644 (file)
index 0000000..b142450
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+**  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.
+*/
+
+#include "apreq_param.h"
+#include "apreq_util.h"
+#include "apreq_error.h"
+#include "apr_strings.h"
+#include "at.h"
+
+
+static const char query_string[] = "a=1;quux=foo+bar&a=2&plus=%2B;"
+                                   "uplus=%U002b;okie=dokie;foo=a%E1;"
+                                   "novalue1;novalue2=";
+static apr_table_t *args;
+static apr_pool_t *p;
+
+
+static void request_make(dAT, void *ctx)
+{
+    apr_status_t s;
+    args = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    AT_not_null(args);
+    s = apreq_parse_query_string(p, args, query_string);
+    AT_int_eq(s, APR_SUCCESS);
+    AT_int_eq(apr_table_elts(args)->nelts, 9);
+}
+
+
+static void request_args_get(dAT, void *ctx)
+{
+    const char *val;
+    const apreq_param_t *param;
+
+    AT_str_eq(apr_table_get(args,"a"), "1");
+
+    val = apr_table_get(args,"quux");
+    AT_str_eq(val, "foo bar");
+    param = apreq_value_to_param(val);
+    AT_int_eq(param->v.dlen, 7);
+
+    AT_str_eq(apr_table_get(args,"plus"), "+");
+    AT_str_eq(apr_table_get(args,"uplus"), "+");
+    AT_str_eq(apr_table_get(args,"okie"), "dokie");
+    AT_str_eq(apr_table_get(args,"novalue1"), "");
+    AT_str_eq(apr_table_get(args,"novalue2"),"");
+}
+
+static void params_as(dAT, void *ctx)
+{
+    const char *val;
+    apr_array_header_t *arr;
+    arr = apreq_params_as_array(p,args,"a");
+    AT_int_eq(arr->nelts, 2);
+    val = apreq_params_as_string(p,args,"a",APREQ_JOIN_AS_IS);
+    AT_str_eq(val, "1, 2");
+    val = apreq_params_as_string(p,args,"does_not_exist",APREQ_JOIN_AS_IS);
+    AT_str_eq(val, "");
+}
+
+static void string_decoding_in_place(dAT, void *ctx)
+{
+    char *s1 = apr_palloc(p,4096);
+    char *s2 = apr_palloc(p,4096);
+    char *s3;
+
+    strcpy(s1, "bend it like beckham");
+    strcpy(s2, "dandy %3Edons");
+
+    AT_str_eq(s1,"bend it like beckham");
+    apreq_unescape(s1);
+    AT_str_eq(s1, "bend it like beckham");
+    s3 = apreq_escape(p, s1, 20);
+    AT_str_eq(s3, "bend+it+like+beckham");
+    apreq_unescape(s3);
+    AT_str_eq(s3,"bend it like beckham");
+
+    AT_str_eq(s2,"dandy %3Edons");
+    apreq_unescape(s2);
+    AT_str_eq(s2,"dandy >dons");
+    s3 = apreq_escape(p, s2, 11);
+    AT_str_eq(s3,"dandy+%3Edons");
+    apreq_unescape(s3);
+    AT_str_eq(s3,"dandy >dons");
+}
+
+static void header_attributes(dAT, void *ctx)
+{
+    const char *hdr = "text/plain; boundary=\"-foo-\", charset=ISO-8859-1";
+    const char *val;
+    apr_size_t vlen;
+    apr_status_t s;
+
+    s = apreq_header_attribute(hdr, "none", 4, &val, &vlen);
+    AT_int_eq(s, APREQ_ERROR_NOATTR);
+
+    s = apreq_header_attribute(hdr, "set", 3, &val, &vlen);
+    AT_int_eq(s, APREQ_ERROR_NOATTR);
+
+    s = apreq_header_attribute(hdr, "boundary", 8, &val, &vlen);
+    AT_int_eq(s, APR_SUCCESS);
+    AT_int_eq(vlen, 5);
+    AT_mem_eq(val, "-foo-", 5);
+
+    s = apreq_header_attribute(hdr, "charset", 7, &val, &vlen);
+    AT_int_eq(s, APR_SUCCESS);
+    AT_int_eq(vlen, 10);
+    AT_mem_eq(val, "ISO-8859-1", 10);
+
+    hdr = "max-age=20; no-quote=\"...";
+
+    s = apreq_header_attribute(hdr, "max-age", 7, &val, &vlen);
+    AT_int_eq(s, APR_SUCCESS);
+    AT_int_eq(vlen, 2);
+    AT_mem_eq(val, "20", 2);
+
+    s = apreq_header_attribute(hdr, "age", 3, &val, &vlen);
+    AT_int_eq(s, APREQ_ERROR_BADSEQ);
+
+    s = apreq_header_attribute(hdr, "no-quote", 8, &val, &vlen);
+    AT_int_eq(s, APREQ_ERROR_BADSEQ);
+
+}
+
+
+static void make_param(dAT, void *ctx)
+{
+    apreq_param_t *param, *decode;
+    apr_status_t s;
+    apr_size_t nlen = 3, vlen = 11;
+    char *name = apr_palloc(p,nlen+1);
+    char *val = apr_palloc(p,vlen+1);
+    char *encode;
+    strcpy(name, "foo");
+    strcpy(val, "bar > alpha");
+
+    param = apreq_param_make(p, name, nlen, val, vlen);
+    AT_str_eq(param->v.name, name);
+    AT_int_eq(param->v.dlen, vlen);
+    AT_str_eq(param->v.data, val);
+
+    encode = apreq_param_encode(p, param);
+    AT_str_eq(encode, "foo=bar+%3E+alpha");
+
+    s = apreq_param_decode(&decode, p, encode, nlen, vlen+2);
+    AT_int_eq(s, APR_SUCCESS);
+    AT_str_eq(decode->v.name, name);
+    AT_int_eq(decode->v.dlen, vlen);
+    AT_str_eq(decode->v.data, val);
+}
+
+static void quote_strings(dAT, void *ctx)
+{
+    apr_size_t exp_len, res_len, res_quote_len;
+    char *res = apr_palloc(p,24);
+    char *res_quote = apr_palloc(p,24);
+    const char *expr;
+    int i;
+    const char * arr[] = {"cest", "\"cest", "ce\"st", "\"cest\""};
+    const char * arr_quote[] =
+        {"\"cest\"", "\"\\\"cest\"", "\"ce\\\"st\"", "\"\\\"cest\\\"\""};
+    apr_size_t arr_len[] = {4, 5, 5, 6};
+    apr_size_t arr_quote_len[] = {6, 8, 8, 10};
+
+    for (i=0; i<4; i++) {
+        res_len = apreq_quote(res, arr[i], arr_len[i]);
+        AT_int_eq(res_len, arr_quote_len[i]);
+        AT_mem_eq(res, arr_quote[i], res_len);
+        res_quote_len = apreq_quote_once(res_quote, res, res_len);
+        AT_int_eq(res_quote_len, res_len);
+        AT_mem_eq(res_quote, res, res_len);
+        res_len = apreq_quote_once(res, arr[i], arr_len[i]);
+        exp_len = (i == 3) ? arr_len[i] : arr_quote_len[i];
+        expr = (i == 3) ? arr[i] : arr_quote[i];
+        AT_int_eq(res_len, exp_len);
+        AT_mem_eq(res, expr, exp_len);
+    }
+}
+
+#define dT(func, plan) {#func, func, plan}
+
+int main(int argc, char *argv[])
+{
+    unsigned i, plan = 0;
+    dAT;
+    at_test_t test_list [] = {
+        dT(request_make, 3),
+        dT(request_args_get, 8),
+        dT(params_as, 3),
+        dT(string_decoding_in_place, 8),
+        dT(header_attributes, 13),
+        dT(make_param, 8),
+        dT(quote_strings, 24),
+    };
+
+    apr_initialize();
+    atexit(apr_terminate);
+    apr_pool_create(&p, NULL);
+
+    apreq_initialize(p);
+
+    AT = at_create(0, at_report_stdout_make());
+    AT_trace_on();
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        plan += test_list[i].plan;
+
+    AT_begin(plan);
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        AT_run(&test_list[i]);
+
+    AT_end();
+
+    return 0;
+}
+
diff --git a/srclib/libapreq/t/parsers.c b/srclib/libapreq/t/parsers.c
new file mode 100644 (file)
index 0000000..f8449d1
--- /dev/null
@@ -0,0 +1,555 @@
+/*
+**  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.
+*/
+
+#include "apreq_parser.h"
+#include "apreq_util.h"
+#include "apreq_error.h"
+#include "apr_strings.h"
+#include "apr_xml.h"
+#include "at.h"
+
+#define CRLF "\015\012"
+
+static apr_pool_t *p;
+
+static char url_data[] = "alpha=one&beta=two;omega=last%2";
+
+static char form_data[] =
+"--AaB03x" CRLF                                           /* 10 chars
+ 012345678901234567890123456789012345678901234567890123456789 */
+"content-disposition: form-data; name=\"field1\"" CRLF    /* 47 chars */
+"content-type: text/plain;charset=windows-1250" CRLF
+"content-transfer-encoding: quoted-printable" CRLF CRLF
+"Joe owes =80100." CRLF
+"--AaB03x" CRLF
+"content-disposition: form-data; name=\"pics\"; filename=\"file1.txt\"" CRLF
+"Content-Type: text/plain" CRLF CRLF
+"... contents of file1.txt ..." CRLF CRLF
+"--AaB03x" CRLF
+"content-disposition: form-data; name=\"\"" CRLF
+"content-type: text/plain;" CRLF " charset=windows-1250" CRLF
+"content-transfer-encoding: quoted-printable" CRLF CRLF
+"Joe owes =80100." CRLF
+"--AaB03x--" CRLF;
+
+static char xml_data[] =
+"<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"
+"<methodCall>"
+"  <methodName>foo.bar</methodName>"
+"  <params>"
+"    <param><value><int>1</int></value></param>"
+"  </params>"
+"</methodCall>";
+
+static char rel_data[] = /*offsets: 122, 522, */
+"--f93dcbA3" CRLF
+"Content-Type: application/xml; charset=UTF-8" CRLF
+"Content-Length: 400" CRLF
+"Content-ID: <980119.X53GGT@example.com>" CRLF CRLF /*122*/
+"<?xml version=\"1.0\"?>" CRLF
+"<uploadDocument>"
+"  <title>My Proposal</title>"
+"  <author>E. X. Ample</author>"
+"  <summary>A proposal for a new project.</summary>"
+"  <notes image=\"cid:980119.X17AXM@example.com\">(see handwritten region)</notes>"
+"  <keywords>project proposal funding</keywords>"
+"  <readonly>false</readonly>"
+"  <filename>image.png</filename>"
+"  <content>cid:980119.X25MNC@example.com</content>"
+"</uploadDocument>" /*400*/ CRLF
+"--f93dcbA3" CRLF /*14*/
+"Content-Type: image/png" CRLF
+"Content-Transfer-Encoding: binary" CRLF
+"Content-ID: <980119.X25MNC@example.com>" CRLF CRLF /*103*/
+"...Binary data here..."  /*22*/ CRLF
+"--f93dcbA3" CRLF /*14*/
+"Content-Type: image/png" CRLF
+"Content-Transfer-Encoding: binary" CRLF
+"Content-ID: <980119.X17AXM@example.com>" CRLF CRLF
+"...Binary data here..." CRLF
+"--f93dcbA3--" CRLF;
+
+static char mix_data[] =
+"--AaB03x" CRLF
+"Content-Disposition: form-data; name=\"submit-name\"" CRLF CRLF
+"Larry" CRLF
+"--AaB03x" CRLF
+"Content-Disposition: form-data; name=\"files\"" CRLF
+"Content-Type: multipart/mixed; boundary=BbC04y" CRLF CRLF
+"--BbC04y" CRLF
+"Content-Disposition: file; filename=\"file1.txt\"" CRLF
+"Content-Type: text/plain" CRLF CRLF
+"... contents of file1.txt ..." CRLF
+"--BbC04y" CRLF
+"Content-Disposition: file; filename=\"file2.gif\"" CRLF
+"Content-Type: image/gif" CRLF
+"Content-Transfer-Encoding: binary" CRLF CRLF
+"...contents of file2.gif..." CRLF
+"--BbC04y--" CRLF
+"--AaB03x  " CRLF
+"content-disposition: form-data; name=\"field1\"" CRLF
+"content-type: text/plain;charset=windows-1250" CRLF
+"content-transfer-encoding: quoted-printable" CRLF CRLF
+"Joe owes =80100." CRLF
+"--AaB03x--"; /* omit CRLF, which is ok per rfc 2046 */
+
+
+#define URL_ENCTYPE "application/x-www-form-urlencoded"
+#define MFD_ENCTYPE "multipart/form-data"
+#define MR_ENCTYPE "multipart/related"
+#define XML_ENCTYPE "application/xml"
+
+static void locate_default_parsers(dAT, void *ctx)
+{
+
+#ifdef __ELF__
+    apreq_parser_function_t f;
+
+    AT_trace_on();
+
+    f = apreq_parser(URL_ENCTYPE);
+    AT_EQ(f, (apreq_parser_function_t)apreq_parse_urlencoded, "%pp");
+
+    f = apreq_parser(MFD_ENCTYPE);
+    AT_EQ(f, (apreq_parser_function_t)apreq_parse_multipart, "%pp");
+
+    f = apreq_parser(MR_ENCTYPE);
+    AT_EQ(f, (apreq_parser_function_t)apreq_parse_multipart, "%pp");
+
+    AT_trace_off();
+#else
+    AT_skip(3, "skipping ELF-dependent tests");
+#endif
+
+}
+
+static void parse_urlencoded(dAT, void *ctx)
+{
+    apr_status_t rv;
+    apr_bucket_alloc_t *ba;
+    apr_bucket_brigade *bb;
+    apreq_parser_t *parser;
+    apr_table_t *body;
+
+    body = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    ba = apr_bucket_alloc_create(p);
+    bb = apr_brigade_create(p, ba);
+    parser = apreq_parser_make(p, ba, URL_ENCTYPE, apreq_parse_urlencoded,
+                               100, NULL, NULL, NULL);
+
+    APR_BRIGADE_INSERT_HEAD(bb,
+        apr_bucket_immortal_create(url_data,strlen(url_data),
+                                   bb->bucket_alloc));
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APR_INCOMPLETE);
+
+    APR_BRIGADE_INSERT_HEAD(bb,
+        apr_bucket_immortal_create("blast",5,
+                                   bb->bucket_alloc));
+    APR_BRIGADE_INSERT_TAIL(bb,
+           apr_bucket_eos_create(bb->bucket_alloc));
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APR_SUCCESS);
+
+    AT_str_eq(apr_table_get(body,"alpha"), "one");
+    AT_str_eq(apr_table_get(body,"beta"), "two");
+    AT_str_eq(apr_table_get(body,"omega"),"last+last");
+
+}
+
+static void parse_multipart(dAT, void *ctx)
+{
+    apr_size_t i, j;
+    apr_bucket_alloc_t *ba;
+
+
+    for (j = 0; j <= strlen(form_data); ++j) {
+
+        ba = apr_bucket_alloc_create(p);
+
+        /* AT_localize checks the inner loop tests itself
+         * (and interprets any such failures as being fatal),
+         * because doing IO to Test::Harness is just too slow
+         * when this many (~1M) tests are involved.
+         */
+
+        AT_localize();
+
+        for (i = 0; i <= strlen(form_data); ++i) {
+            const char *val;
+            char *val2;
+            apr_size_t len;
+            apr_table_t *t, *body;
+            apreq_parser_t *parser;
+            apr_bucket_brigade *bb, *vb, *tail;
+            apr_status_t rv;
+            apr_bucket *e, *f;
+
+            bb = apr_brigade_create(p, ba);
+            body = apr_table_make(p, APREQ_DEFAULT_NELTS);
+            parser = apreq_parser_make(p, ba, MFD_ENCTYPE
+                                       "; charset=\"iso-8859-1\""
+                                       "; boundary=\"AaB03x\"",
+                                       apreq_parse_multipart,
+                                       1000, NULL, NULL, NULL);
+
+            e = apr_bucket_immortal_create(form_data,
+                                           strlen(form_data),
+                                           bb->bucket_alloc);
+            APR_BRIGADE_INSERT_HEAD(bb, e);
+            APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc));
+
+            /* Split e into three buckets */
+            apr_bucket_split(e, j);
+            f = APR_BUCKET_NEXT(e);
+            if (i < j)
+                apr_bucket_split(e, i);
+            else
+                apr_bucket_split(f, i - j);
+
+            tail = apr_brigade_split(bb, f);
+            rv = apreq_parser_run(parser, body, bb);
+            AT_int_eq(rv, (j < strlen(form_data)) ? APR_INCOMPLETE : APR_SUCCESS);
+            rv = apreq_parser_run(parser, body, tail);
+            AT_int_eq(rv, APR_SUCCESS);
+            AT_int_eq(apr_table_elts(body)->nelts, 3);
+
+            val = apr_table_get(body,"field1");
+            AT_str_eq(val, "Joe owes =80100.");
+            t = apreq_value_to_param(val)->info;
+            val = apr_table_get(t, "content-transfer-encoding");
+            AT_str_eq(val, "quoted-printable");
+
+            val = apr_table_get(body, "pics");
+            AT_str_eq(val, "file1.txt");
+            t = apreq_value_to_param(val)->info;
+            vb = apreq_value_to_param(val)->upload;
+            apr_brigade_pflatten(vb, &val2, &len, p);
+            AT_int_eq(len, strlen("... contents of file1.txt ..." CRLF));
+            AT_mem_eq(val2 ,"... contents of file1.txt ..." CRLF, len);
+            val = apr_table_get(t, "content-type");
+            AT_str_eq(val, "text/plain");
+
+            val = apr_table_get(body, "");
+            AT_str_eq(val, "Joe owes =80100.");
+            t = apreq_value_to_param(val)->info;
+            val = apr_table_get(t, "content-type");
+            AT_int_eq(apreq_header_attribute(val, "charset", 7, &val, &len),
+                      APR_SUCCESS);
+            AT_str_eq(val, "windows-1250");
+
+            apr_brigade_cleanup(vb);
+            apr_brigade_cleanup(bb);
+        }
+
+#ifdef APR_POOL_DEBUG
+        apr_bucket_alloc_destroy(ba);
+#endif
+        AT_delocalize();
+        apr_pool_clear(p);
+    }
+}
+
+static void parse_disable_uploads(dAT, void *ctx)
+{
+    const char *val;
+    apr_table_t *t, *body;
+    apr_status_t rv;
+    apr_bucket_alloc_t *ba;
+    apr_bucket_brigade *bb;
+    apr_bucket *e;
+    apreq_parser_t *parser;
+    apreq_hook_t *hook;
+
+    ba = apr_bucket_alloc_create(p);
+    bb = apr_brigade_create(p, ba);
+
+    e = apr_bucket_immortal_create(form_data, strlen(form_data), ba);
+    APR_BRIGADE_INSERT_HEAD(bb, e);
+    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc));
+
+    body = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    hook = apreq_hook_make(p, apreq_hook_disable_uploads, NULL, NULL);
+
+    parser = apreq_parser_make(p, ba, MFD_ENCTYPE
+                               "; charset=\"iso-8859-1\""
+                               "; boundary=\"AaB03x\"",
+                               apreq_parse_multipart,
+                               1000, NULL, hook, NULL);
+
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APREQ_ERROR_GENERAL);
+    AT_int_eq(apr_table_elts(body)->nelts, 1);
+
+    val = apr_table_get(body,"field1");
+    AT_str_eq(val, "Joe owes =80100.");
+    t = apreq_value_to_param(val)->info;
+    val = apr_table_get(t, "content-transfer-encoding");
+    AT_str_eq(val, "quoted-printable");
+
+    val = apr_table_get(body, "pics");
+    AT_is_null(val);
+}
+
+
+static void parse_generic(dAT, void *ctx)
+{
+    char *val;
+    apr_size_t vlen;
+    apr_status_t rv;
+    apreq_param_t *dummy;
+    apreq_parser_t *parser;
+    apr_table_t *body;
+    apr_bucket_alloc_t *ba = apr_bucket_alloc_create(p);
+    apr_bucket_brigade *bb = apr_brigade_create(p, ba);
+    apr_bucket *e = apr_bucket_immortal_create(xml_data,
+                                               strlen(xml_data),
+                                               ba);
+
+    APR_BRIGADE_INSERT_HEAD(bb, e);
+    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(ba));
+
+    body = apr_table_make(p, APREQ_DEFAULT_NELTS);
+
+    parser = apreq_parser_make(p, ba, "application/xml",
+                               apreq_parse_generic, 1000, NULL, NULL, NULL);
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APR_SUCCESS);
+    dummy = *(apreq_param_t **)parser->ctx;
+    AT_not_null(dummy);
+    apr_brigade_pflatten(dummy->upload, &val, &vlen, p);
+
+    AT_int_eq(vlen, strlen(xml_data));
+    AT_mem_eq(val, xml_data, vlen);
+}
+
+static void hook_discard(dAT, void *ctx)
+{
+    apr_status_t rv;
+    apreq_param_t *dummy;
+    apreq_parser_t *parser;
+    apreq_hook_t *hook;
+    apr_table_t *body;
+    apr_bucket_alloc_t *ba = apr_bucket_alloc_create(p);
+    apr_bucket_brigade *bb = apr_brigade_create(p, ba);
+    apr_bucket *e = apr_bucket_immortal_create(xml_data,
+                                               strlen(xml_data),
+                                               ba);
+
+    APR_BRIGADE_INSERT_HEAD(bb, e);
+    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(ba));
+
+    body = apr_table_make(p, APREQ_DEFAULT_NELTS);
+
+    hook = apreq_hook_make(p, apreq_hook_discard_brigade, NULL, NULL);
+    parser = apreq_parser_make(p, ba, "application/xml",
+                               apreq_parse_generic, 1000, NULL, hook, NULL);
+
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APR_SUCCESS);
+    dummy = *(apreq_param_t **)parser->ctx;
+    AT_not_null(dummy);
+    AT_not_null(dummy->upload);
+    AT_ok(APR_BRIGADE_EMPTY(dummy->upload), "brigade has no contents");
+}
+
+
+static void parse_related(dAT, void *ctx)
+{
+    char ct[] = "multipart/related; boundary=f93dcbA3; "
+        "type=application/xml; start=\"<980119.X53GGT@example.com>\"";
+    char data[] = "...Binary data here...";
+    int dlen = strlen(data);
+    const char *val;
+    char *val2;
+    apr_size_t vlen;
+    apr_status_t rv;
+    int ns_map = 0;
+    apr_xml_doc *doc;
+    apr_table_t *body;
+    apreq_parser_t *parser;
+    apreq_hook_t *xml_hook;
+    apreq_param_t *param;
+    apr_bucket_alloc_t *ba = apr_bucket_alloc_create(p);
+    apr_bucket_brigade *bb = apr_brigade_create(p, ba);
+    apr_bucket *e = apr_bucket_immortal_create(rel_data,
+                                                   strlen(rel_data),
+                                                   bb->bucket_alloc);
+
+    APR_BRIGADE_INSERT_HEAD(bb, e);
+    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc));
+    xml_hook = apreq_hook_make(p, apreq_hook_apr_xml_parser, NULL, NULL);
+
+    body =   apr_table_make(p, APREQ_DEFAULT_NELTS);
+    parser = apreq_parser_make(p, ba, ct, apreq_parse_multipart,
+                               1000, NULL, xml_hook, NULL);
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APR_SUCCESS);
+
+    val = apr_table_get(body, "<980119.X53GGT@example.com>");
+    AT_not_null(val);
+    param = apreq_value_to_param(val);
+
+    AT_not_null(param);
+    AT_not_null(param->info);
+    val = apr_table_get(param->info, "Content-Length");
+    AT_str_eq(val, "400");
+    AT_not_null(param->upload);
+    apr_brigade_pflatten(param->upload, &val2, &vlen, p);
+    AT_int_eq(vlen, 400);
+    AT_mem_eq(val2, rel_data + 122, 400);
+
+    doc = *(apr_xml_doc **)xml_hook->ctx;
+    apr_xml_to_text(p, doc->root, APR_XML_X2T_FULL,
+                    doc->namespaces, &ns_map, &val, &vlen);
+    AT_int_eq(vlen, 400 - 22);
+    AT_mem_eq(val, rel_data + 122 + 23, 400 - 23);
+
+
+    val = apr_table_get(body, "<980119.X25MNC@example.com>");
+    AT_not_null(val);
+    param = apreq_value_to_param(val);
+    AT_not_null(param);
+    AT_not_null(param->upload);
+    apr_brigade_pflatten(param->upload, &val2, &vlen, p);
+    AT_int_eq(vlen, dlen);
+    AT_mem_eq(val2, data, vlen);
+
+    val = apr_table_get(body, "<980119.X17AXM@example.com>");
+    AT_not_null(val);
+    param = apreq_value_to_param(val);
+    AT_not_null(param);
+    AT_not_null(param->upload);
+    apr_brigade_pflatten(param->upload, &val2, &vlen, p);
+    AT_int_eq(vlen, dlen);
+    AT_mem_eq(val2, data, vlen);
+}
+
+typedef struct {
+    const char *key;
+    const char *val;
+} array_elt;
+
+
+static void parse_mixed(dAT, void *ctx)
+{
+    const char *val;
+    char *val2;
+    apr_size_t vlen;
+    apr_status_t rv;
+    apreq_param_t *param;
+    const apr_array_header_t *arr;
+    array_elt *elt;
+    char ct[] = MFD_ENCTYPE "; charset=\"iso-8859-1\"; boundary=\"AaB03x\"";
+    apreq_parser_t *parser;
+    apr_table_t *body = apr_table_make(p, APREQ_DEFAULT_NELTS);
+    apr_bucket_alloc_t *ba = apr_bucket_alloc_create(p);
+    apr_bucket_brigade *bb = apr_brigade_create(p, ba);
+    apr_bucket *e = apr_bucket_immortal_create(mix_data,
+                                                   strlen(mix_data),
+                                                   bb->bucket_alloc);
+
+    APR_BRIGADE_INSERT_HEAD(bb, e);
+    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc));
+
+    parser = apreq_parser_make(p, ba, ct, apreq_parse_multipart,
+                               1000, NULL, NULL, NULL);
+
+    rv = apreq_parser_run(parser, body, bb);
+    AT_int_eq(rv, APR_SUCCESS);
+
+    val = apr_table_get(body, "submit-name");
+    AT_not_null(val);
+    AT_str_eq(val, "Larry");
+
+    val = apr_table_get(body,"field1");
+    AT_str_eq(val, "Joe owes =80100.");
+
+    val = apr_table_get(body, "files");
+    AT_not_null(val);
+    AT_str_eq(val, "file1.txt");
+    param = apreq_value_to_param(val);
+
+    AT_not_null(param->upload);
+    apr_brigade_pflatten(param->upload, &val2, &vlen, p);
+    AT_int_eq(vlen, strlen("... contents of file1.txt ..."));
+    AT_mem_eq(val2, "... contents of file1.txt ...", vlen);
+
+    arr = apr_table_elts(body);
+    AT_int_eq(arr->nelts, 4);
+
+    elt = (array_elt *)&arr->elts[2 * arr->elt_size];
+    AT_str_eq(elt->key, "files");
+    AT_str_eq(elt->val, "file2.gif");
+
+    param = apreq_value_to_param(elt->val);
+    AT_not_null(param->upload);
+    apr_brigade_pflatten(param->upload, &val2, &vlen, p);
+    AT_int_eq(vlen, strlen("...contents of file2.gif..."));
+    AT_mem_eq(val2, "...contents of file2.gif...", vlen);
+
+}
+
+
+#define dT(func, plan) {#func, func, plan}
+
+int main(int argc, char *argv[])
+{
+    apr_pool_t *test_pool;
+    unsigned i, plan = 0;
+    dAT;
+    at_test_t test_list [] = {
+        dT(locate_default_parsers, 3),
+        dT(parse_urlencoded, 5),
+        dT(parse_multipart, sizeof form_data),
+        dT(parse_disable_uploads, 5),
+        dT(parse_generic, 4),
+        dT(hook_discard, 4),
+        dT(parse_related, 20),
+        dT(parse_mixed, 15)
+    };
+
+    apr_initialize();
+    atexit(apr_terminate);
+
+    apr_pool_create(&p, NULL);
+    apr_pool_create(&test_pool, NULL);
+    apreq_initialize(p);
+
+
+    AT = at_create(0, at_report_stdout_make());
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        plan += test_list[i].plan;
+
+    AT_begin(plan);
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        AT_run(&test_list[i]);
+
+    AT_end();
+
+    return 0;
+}
+
+
diff --git a/srclib/libapreq/t/util.c b/srclib/libapreq/t/util.c
new file mode 100644 (file)
index 0000000..f857626
--- /dev/null
@@ -0,0 +1,340 @@
+/*
+**  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.
+*/
+
+#include "apr_strings.h"
+#include "apreq_error.h"
+#include "apreq_util.h"
+#include "at.h"
+
+
+static void test_atoi64f(dAT, void *ctx)
+{
+    AT_int_eq(apreq_atoi64f("0"), 0);
+    AT_int_eq(apreq_atoi64f("-1"), -1);
+    AT_int_eq(apreq_atoi64f("-"), 0);
+    AT_int_eq(apreq_atoi64f("5"), 5);
+    AT_int_eq(apreq_atoi64f("3.333"), 3);
+    AT_int_eq(apreq_atoi64f("33k"), 33 * 1024);
+    AT_int_eq(apreq_atoi64f(" +8M "), 8 * 1024 * 1024);
+    AT_ok(apreq_atoi64f("44GB") == (apr_int64_t)44 * 1024 * 1024 * 1024,
+          "44GB test");
+    AT_ok(apreq_atoi64f("0xaBcDefg") == (apr_int64_t)11259375 * 1024 * 1024 * 1024,
+          "hex test");
+}
+
+static void test_atoi64t(dAT, void *ctx)
+{
+    AT_int_eq(apreq_atoi64t("0"), 0);
+    AT_int_eq(apreq_atoi64t("-1"), -1);
+    AT_int_eq(apreq_atoi64t("-g088l3dyg00k"), 0);
+    AT_int_eq(apreq_atoi64t("5s"), 5);
+    AT_int_eq(apreq_atoi64t("3.333"), 3);
+    AT_int_eq(apreq_atoi64t("33d"), 33 * 60 * 60 * 24);
+    AT_int_eq(apreq_atoi64t(" +8M "), 8 * 60 * 60 * 24 * 30);
+    AT_int_eq(apreq_atoi64t("+9m"), 9 * 60);
+    AT_int_eq(apreq_atoi64t("6h"), 6 * 60 * 60);
+
+}
+
+static void test_index(dAT, void *ctx)
+{
+    const char haystack[] = "Four score and seven years ago";
+    apr_size_t hlen = sizeof haystack - 1;
+    AT_int_eq(apreq_index(haystack, hlen, "Four", 4, APREQ_MATCH_FULL),
+              0);
+    AT_int_eq(apreq_index(haystack, hlen, "Four", 4, APREQ_MATCH_PARTIAL),
+              0);
+    AT_int_eq(apreq_index(haystack, hlen, "Fourteen", 8, APREQ_MATCH_FULL),
+              -1);
+    AT_int_eq(apreq_index(haystack, hlen, "Fourteen", 8, APREQ_MATCH_PARTIAL),
+              -1);
+    AT_int_eq(apreq_index(haystack, hlen, "agoraphobia", 11, APREQ_MATCH_FULL),
+              -1);
+    AT_int_eq(apreq_index(haystack, hlen, "agoraphobia", 11, APREQ_MATCH_PARTIAL),
+              hlen - 3);
+}
+
+#define A_GRAVE  0xE5
+#define KATAKANA_A 0xFF71
+
+static void test_decode(dAT, void *ctx)
+{
+    apr_size_t elen;
+    char src1[] = "%C3%80%E3%82%a2"; /* A_GRAVE KATAKANA_A as utf8 */
+    unsigned char expect[6];
+
+    AT_int_eq(apreq_decode((char *)expect, &elen, src1, sizeof(src1) -1),
+              APR_SUCCESS);
+    AT_int_eq(elen, 5);
+    AT_int_eq(expect[0], 0xC3);
+    AT_int_eq(expect[1], 0x80);
+    AT_int_eq(expect[2], 0xE3);
+    AT_int_eq(expect[3], 0x82);
+    AT_int_eq(expect[4], 0xA2);
+}
+
+static void test_charset_divine(dAT, void *ctx)
+{
+    apr_size_t elen;
+    char src1[] = "%C3%80%E3%82%a2"; /* A_GRAVE KATAKANA_A as utf8 */
+    char src2[] = "pound%A3";/* latin-1 */
+    char src3[] = "euro%80";/* cp-1252 */
+    char expect[7];
+
+    AT_int_eq(apreq_decode(expect, &elen, src1, sizeof(src1) -1),
+              APR_SUCCESS);
+
+    AT_int_eq(apreq_charset_divine(expect, elen), APREQ_CHARSET_UTF8);
+
+    AT_int_eq(apreq_decode(expect, &elen, src2, sizeof(src2) -1),
+              APR_SUCCESS);
+
+    AT_int_eq(apreq_charset_divine(expect, elen), APREQ_CHARSET_LATIN1);
+    AT_int_eq(apreq_decode(expect, &elen, src3, sizeof(src3) -1),
+              APR_SUCCESS);
+
+    AT_int_eq(apreq_charset_divine(expect, elen), APREQ_CHARSET_CP1252);
+
+}
+
+
+static void test_decodev(dAT, void *ctx)
+{
+    char src1[] = "%2540%2";
+    char src2[] = "0%u0";
+    char src3[] = "041";
+    struct iovec iovec1[] = {
+        { src1, sizeof(src1) - 1 },
+        { src2, sizeof(src2) - 1 },
+        { src3, sizeof(src3) - 1 },
+    };
+    struct iovec iovec2[] = {
+        { src1, sizeof(src1) - 1 },
+        { src2, sizeof(src2) - 1 },
+    };
+    const char expect1[] = "%40 A";
+    const char expect2[] = "%40 ";
+    char dest[sizeof(src1) + sizeof(src2) + sizeof(src3)];
+    apr_size_t dest_len;
+    apr_status_t status;
+
+    status = apreq_decodev(dest, &dest_len, iovec1, 3);
+    AT_int_eq(status, APR_SUCCESS);
+    AT_int_eq(dest_len, sizeof(expect1) - 1);
+    AT_mem_eq(dest, expect1, sizeof(expect1) - 1);
+
+    status = apreq_decodev(dest, &dest_len, iovec2, 2);
+    AT_int_eq(status, APR_INCOMPLETE);
+    AT_int_eq(dest_len, sizeof(expect2) - 1);
+    AT_mem_eq(dest, expect2, sizeof(expect2) - 1);
+}
+
+
+static void test_encode(dAT, void *ctx)
+{
+
+}
+
+static void test_cp1252_to_utf8(dAT, void *ctx)
+{
+    char src1[] = "%C3%80%E3%82%a2"; /* A_GRAVE KATAKANA_A as utf8 */
+    char src2[5];
+    unsigned char expect[16];
+    apr_size_t slen;
+
+    AT_int_eq(apreq_decode((char *)src2, &slen, src1, sizeof(src1) -1),
+              APR_SUCCESS);
+    AT_int_eq(apreq_cp1252_to_utf8((char *)expect, src2, 5),
+              12);
+
+    /* 0xC3 */
+    AT_int_eq(expect[0], 0xC0 | (0xC3 >> 6));
+    AT_int_eq(expect[1], 0xC3 - 0x40);
+
+    /* 0x20AC */
+    AT_int_eq(expect[2], 0xE0 | (0x20AC >> 12));
+    AT_int_eq(expect[3], 0x80 | ((0x20AC >> 6) & 0x3F));
+    AT_int_eq(expect[4], 0x80 | (0x20AC & 0x3F));
+
+    /* 0xE3 */
+    AT_int_eq(expect[5], 0xC3);
+    AT_int_eq(expect[6], 0xE3 - 0x40);
+
+    /* 0x201A */
+    AT_int_eq(expect[7], 0xE0 | (0x201A >> 12));
+    AT_int_eq(expect[8], 0x80 | ((0x201A >> 6) & 0x3F));
+    AT_int_eq(expect[9], 0x80 | (0x201A & 0x3F));
+
+
+    /* 0xA2 */
+    AT_int_eq(expect[10], 0xC0 | (0xA2 >> 6));
+    AT_int_eq(expect[11], 0xA2);
+
+}
+
+static void test_quote(dAT, void *ctx)
+{
+    size_t len;
+    char dst[64];
+
+    len = apreq_quote(dst, "foo", 3);
+    AT_int_eq(len, 5);
+    AT_str_eq(dst, "\"foo\"");
+
+    len = apreq_quote(dst, "\"foo", 4);
+    AT_int_eq(len, 7);
+    AT_str_eq(dst, "\"\\\"foo\"");
+
+    len = apreq_quote(dst, "foo\\bar", 7);
+    AT_int_eq(len, 10);
+    AT_str_eq(dst, "\"foo\\\\bar\"");
+
+    len = apreq_quote(dst, "foo\0bar", 7);
+    AT_int_eq(len, 10);
+    AT_str_eq(dst, "\"foo\\0bar\"");
+}
+
+static void test_quote_once(dAT, void *ctx)
+{
+    size_t len;
+    char dst[64];
+
+    len = apreq_quote_once(dst, "foo", 3);
+    AT_int_eq(len, 5);
+    AT_str_eq(dst, "\"foo\"");
+
+    len = apreq_quote_once(dst, "\"foo", 4);
+    AT_int_eq(len, 7);
+    AT_str_eq(dst, "\"\\\"foo\"");
+
+    len = apreq_quote_once(dst, "foo\"", 4);
+    AT_int_eq(len, 7);
+    AT_str_eq(dst, "\"foo\\\"\"");
+
+    len = apreq_quote_once(dst, "foo\0bar", 7);
+    AT_int_eq(len, 10);
+    AT_str_eq(dst, "\"foo\\0bar\"");
+
+    /* null byte must be escaped, even when there are already double
+       quotes */
+    len = apreq_quote_once(dst, "\"foo\0bar\"", 9);
+    AT_int_eq(len, 14);
+    AT_str_eq(dst, "\"\\\"foo\\0bar\\\"\"");
+
+    len = apreq_quote_once(dst, "\"foo\"", 5);
+    AT_int_eq(len, 5);
+    AT_str_eq(dst, "\"foo\"");
+
+    len = apreq_quote_once(dst, "'foo'", 5);
+    AT_int_eq(len, 7);
+    AT_str_eq(dst, "\"'foo'\"");
+
+    len = apreq_quote_once(dst, "\"fo\\o\"", 6);
+    AT_int_eq(len, 6);
+    AT_str_eq(dst, "\"fo\\o\"");
+
+    len = apreq_quote_once(dst, "\"foo\"bar\"", 9);
+    AT_int_eq(len, 14);
+    AT_str_eq(dst, "\"\\\"foo\\\"bar\\\"\"");
+}
+
+static void test_join(dAT, void *ctx)
+{
+
+}
+
+static void test_brigade_fwrite(dAT, void *ctx)
+{
+
+}
+
+static void test_file_mktemp(dAT, void *ctx)
+{
+
+
+}
+
+static void test_header_attribute(dAT, void *ctx)
+{
+    const char hdr[] = "filename=\"filename=foo\" filename=\"quux.txt\"";
+    const char *val;
+    apr_size_t vlen;
+
+    AT_int_eq(apreq_header_attribute(hdr+4, "name", 4, &val, &vlen),
+              APR_SUCCESS);
+    AT_int_eq(vlen, 12);
+    AT_mem_eq("filename=foo", val, 12);
+
+    AT_int_eq(apreq_header_attribute(hdr+4, "filename", 8, &val, &vlen),
+              APR_SUCCESS);
+    AT_int_eq(vlen, 8);
+    AT_mem_eq("quux.txt", val, 8);
+
+}
+
+static void test_brigade_concat(dAT, void *ctx)
+{
+
+}
+
+
+
+#define dT(func, plan) #func, func, plan, NULL
+
+
+int main(int argc, char *argv[])
+{
+    unsigned i, plan = 0;
+    apr_pool_t *p;
+    dAT;
+    at_test_t test_list [] = {
+        { dT(test_atoi64f, 9) },
+        { dT(test_atoi64t, 9) },
+        { dT(test_index, 6) },
+        { dT(test_decode, 7) },
+        { dT(test_charset_divine, 6) },
+        { dT(test_decodev, 6) },
+        { dT(test_encode, 0) },
+        { dT(test_cp1252_to_utf8, 14) },
+        { dT(test_quote, 8) },
+        { dT(test_quote_once, 18), },
+        { dT(test_join, 0) },
+        { dT(test_brigade_fwrite, 0) },
+        { dT(test_file_mktemp, 0) },
+        { dT(test_header_attribute, 6) },
+        { dT(test_brigade_concat, 0) },
+    };
+
+    apr_initialize();
+    atexit(apr_terminate);
+
+    apr_pool_create(&p, NULL);
+
+    AT = at_create(0, at_report_stdout_make());
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        plan += test_list[i].plan;
+
+    AT_begin(plan);
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        AT_run(&test_list[i]);
+
+    AT_end();
+
+    return 0;
+}
diff --git a/srclib/libapreq/t/version.c b/srclib/libapreq/t/version.c
new file mode 100644 (file)
index 0000000..955b6a9
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+**  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.
+*/
+
+#include "apreq_version.h"
+#include "at.h"
+
+static void version_string(dAT, void *ctx)
+{
+    const char *vstring = apreq_version_string();
+    AT_not_null(vstring);
+    AT_str_eq(vstring, APREQ_VERSION_STRING);
+}
+static void version_type(dAT, void *ctx)
+{
+    apr_version_t v;
+    apreq_version(&v);
+    AT_int_eq(v.major, APREQ_MAJOR_VERSION);
+    AT_int_eq(v.minor, APREQ_MINOR_VERSION);
+    AT_int_eq(v.patch, APREQ_PATCH_VERSION);
+#ifdef APREQ_IS_DEV_VERSION
+    AT_int_eq(v.is_dev, 1);
+#else
+    AT_int_eq(v.is_dev, 0);
+#endif
+}
+
+int main(int argc, char *argv[])
+{
+    apr_pool_t *p;
+    unsigned i, plan = 0;
+    dAT;
+    at_test_t test_list [] = {
+        {"version_string", version_string, 2, NULL, "1"},
+        {"version_type", version_type, 4}
+    };
+
+    apr_initialize();
+    atexit(apr_terminate);
+
+    apr_pool_create(&p, NULL);
+
+    AT = at_create(0, at_report_stdout_make());
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        plan += test_list[i].plan;
+
+    AT_begin(plan);
+
+    for (i = 0; i < sizeof(test_list) / sizeof(at_test_t);  ++i)
+        AT_run(&test_list[i]);
+
+    AT_end();
+
+    return 0;
+}
diff --git a/srclib/libapreq/util.c b/srclib/libapreq/util.c
new file mode 100644 (file)
index 0000000..6d33fa9
--- /dev/null
@@ -0,0 +1,1168 @@
+/*
+**  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.
+*/
+
+#include "apreq_util.h"
+#include "apreq_error.h"
+#include "apr_time.h"
+#include "apr_strings.h"
+#include "apr_lib.h"
+#include <assert.h>
+
+#undef MAX
+#undef MIN
+#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
+#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
+
+/* used for specifying file sizes */
+
+APREQ_DECLARE(apr_int64_t) apreq_atoi64f(const char *s)
+{
+    apr_int64_t n = 0;
+    char *p;
+    if (s == NULL)
+        return 0;
+
+    n = apr_strtoi64(s, &p, 0);
+
+    if (p == NULL)
+        return n;
+    while (apr_isspace(*p))
+        ++p;
+
+    switch (*p) {
+      case 'G': /* fall thru */
+      case 'g': return n * 1024*1024*1024;
+      case 'M': /* fall thru */
+      case 'm': return n * 1024*1024;
+      case 'K': /* fall thru */
+      case 'k': return n * 1024;
+    }
+
+    return n;
+}
+
+
+/* converts date offsets (e.g. "+3M") to seconds */
+
+APREQ_DECLARE(apr_int64_t) apreq_atoi64t(const char *s)
+{
+    apr_int64_t n = 0;
+    char *p;
+    if (s == NULL)
+        return 0;
+    n = apr_strtoi64(s, &p, 0); /* XXX: what about overflow? */
+
+    if (p == NULL)
+        return n;
+    while (apr_isspace(*p))
+        ++p;
+
+    switch (*p) {
+      case 'Y': /* fall thru */
+      case 'y': return n * 60*60*24*365;
+      case 'M': return n * 60*60*24*30;
+      case 'D': /* fall thru */
+      case 'd': return n * 60*60*24;
+      case 'H': /* fall thru */
+      case 'h': return n * 60*60;
+      case 'm': return n * 60;
+      case 's': /* fall thru */
+      default:
+          return n;
+    }
+    /* should never get here */
+    return -1;
+}
+
+
+APREQ_DECLARE(apr_ssize_t ) apreq_index(const char* hay, apr_size_t hlen,
+                                        const char* ndl, apr_size_t nlen,
+                                        const apreq_match_t type)
+{
+    apr_size_t len = hlen;
+    const char *end = hay + hlen;
+    const char *begin = hay;
+
+    while ( (hay = memchr(hay, ndl[0], len)) ) {
+       len = end - hay;
+
+       /* done if matches up to capacity of buffer */
+       if ( memcmp(hay, ndl, MIN(nlen, len)) == 0 ) {
+            if (type == APREQ_MATCH_FULL && len < nlen)
+                hay = NULL;     /* insufficient room for match */
+           break;
+        }
+        --len;
+        ++hay;
+    }
+
+    return hay ? hay - begin : -1;
+}
+
+
+static const char c2x_table[] = "0123456789ABCDEF";
+static APR_INLINE unsigned char hex2_to_char(const char *what)
+{
+    register unsigned 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);
+}
+
+
+/* Unicode notes: "bmp" refers to the 16-bit
+ * Unicode Basic Multilingual Plane. Here we're
+ * restricting our unicode internals to 16-bit
+ * codepoints, to keep the code as simple as possible.
+ * This should be sufficient for apreq itself, since
+ * we really only need to validate RFC3986-encoded utf8.
+ */
+
+/* Converts Windows cp1252 to Unicode. */
+
+static APR_INLINE
+apr_uint16_t cp1252_to_bmp(unsigned char c)
+{
+    /* We only need to deal with iso-8859-1 control chars
+     * in the 0x80 - 0x9F range.
+     */
+    if ((c & 0xE0) != 0x80)
+        return c;
+
+    switch (c) {
+    case 0x80: return 0x20AC;
+    case 0x82: return 0x201A;
+    case 0x83: return 0x192;
+    case 0x84: return 0x201E;
+    case 0x85: return 0x2026;
+    case 0x86: return 0x2020;
+    case 0x87: return 0x2021;
+    case 0x88: return 0x2C6;
+    case 0x89: return 0x2030;
+    case 0x8A: return 0x160;
+    case 0x8B: return 0x2039;
+    case 0x8C: return 0x152;
+    case 0x8E: return 0x17D;
+    case 0x91: return 0x2018;
+    case 0x92: return 0x2019;
+    case 0x93: return 0x201C;
+    case 0x94: return 0x201D;
+    case 0x95: return 0x2022;
+    case 0x96: return 0x2013;
+    case 0x97: return 0x2014;
+    case 0x98: return 0x2DC;
+    case 0x99: return 0x2122;
+    case 0x9A: return 0x161;
+    case 0x9B: return 0x203A;
+    case 0x9C: return 0x153;
+    case 0x9E: return 0x17E;
+    case 0x9F: return 0x178;
+    }
+    return c;
+}
+
+/* converts cp1252 to utf8 */
+APREQ_DECLARE(apr_size_t) apreq_cp1252_to_utf8(char *dest,
+                                               const char *src, apr_size_t slen)
+{
+    const unsigned char *s = (unsigned const char *)src;
+    const unsigned char *end = s + slen;
+    unsigned char *d = (unsigned char *)dest;
+    apr_uint16_t c;
+
+    while (s < end) {
+        c = cp1252_to_bmp(*s++);
+
+        if (c < 0x80) {
+            *d++ = c;
+        }
+        else if (c < 0x800) {
+            *d++ = 0xC0 | (c >> 6);
+            *d++ = 0x80 | (c & 0x3F);
+        }
+        else {
+            *d++ = 0xE0 | (c >> 12);
+            *d++ = 0x80 | ((c >> 6) & 0x3F);
+            *d++ = 0x80 | (c & 0x3F);
+        }
+    }
+    *d = 0;
+    return d - (unsigned char *)dest;
+}
+
+
+/**
+ * Valid utf8 bit patterns: (true utf8 must satisfy a minimality condition)
+ *
+ * 0aaaaaaa
+ * 110bbbba 10aaaaaa                        minimality mask: 0x1E
+ * 1110cccc 10cbbbba 10aaaaaa                                0x0F || 0x20
+ * 11110ddd 10ddcccc 10cbbbba 10aaaaaa                       0x07 || 0x30
+ * 111110ee 10eeeddd 10ddcccc 10cbbbba 10aaaaaa              0x03 || 0x38
+ * 1111110f 10ffffee 10eeeddd 10ddcccc 10cbbbba 10aaaaaa     0x01 || 0x3C
+ *
+ * Charset divination heuristics:
+ * 1) presume ascii; if not, then
+ * 2) presume utf8; if not, then
+ * 3) presume latin1; unless there are control chars, in which case
+ * 4) punt to cp1252.
+ *
+ * Note: in downgrading from 2 to 3, we need to be careful
+ * about earlier control characters presumed to be valid utf8.
+ */
+
+APREQ_DECLARE(apreq_charset_t) apreq_charset_divine(const char *src,
+                                                    apr_size_t slen)
+
+{
+    apreq_charset_t rv = APREQ_CHARSET_ASCII;
+    register unsigned char trail = 0, saw_cntrl = 0, mask = 0;
+    register const unsigned char *s = (const unsigned char *)src;
+    const unsigned char *end = s + slen;
+
+    for (; s < end; ++s) {
+        if (trail) {
+            if ((*s & 0xC0) == 0x80 && (mask == 0 || (mask & *s))) {
+                mask = 0;
+                --trail;
+
+                if ((*s & 0xE0) == 0x80) {
+                    saw_cntrl = 1;
+                }
+            }
+            else {
+                trail = 0;
+                if (saw_cntrl)
+                    return APREQ_CHARSET_CP1252;
+                rv = APREQ_CHARSET_LATIN1;
+            }
+        }
+        else if (*s < 0x80) {
+            /* do nothing */
+        }
+        else if (*s < 0xA0) {
+            return APREQ_CHARSET_CP1252;
+        }
+        else if (*s < 0xC0) {
+            if (saw_cntrl)
+                return APREQ_CHARSET_CP1252;
+            rv = APREQ_CHARSET_LATIN1;
+        }
+        else if (rv == APREQ_CHARSET_LATIN1) {
+            /* do nothing */
+        }
+
+        /* utf8 cases */
+
+        else if (*s < 0xE0) {
+            if (*s & 0x1E) {
+                rv = APREQ_CHARSET_UTF8;
+                trail = 1;
+                mask = 0;
+            }
+            else if (saw_cntrl)
+                return APREQ_CHARSET_CP1252;
+            else
+                rv = APREQ_CHARSET_LATIN1;
+        }
+        else if (*s < 0xF0) {
+            mask = (*s & 0x0F) ? 0 : 0x20;
+            rv = APREQ_CHARSET_UTF8;
+            trail = 2;
+        }
+        else if (*s < 0xF8) {
+            mask = (*s & 0x07) ? 0 : 0x30;
+            rv = APREQ_CHARSET_UTF8;
+            trail = 3;
+        }
+        else if (*s < 0xFC) {
+            mask = (*s & 0x03) ? 0 : 0x38;
+            rv = APREQ_CHARSET_UTF8;
+            trail = 4;
+        }
+        else if (*s < 0xFE) {
+            mask = (*s & 0x01) ? 0 : 0x3C;
+            rv = APREQ_CHARSET_UTF8;
+            trail = 5;
+        }
+        else {
+            rv = APREQ_CHARSET_UTF8;
+        }
+    }
+
+    return trail ? saw_cntrl ?
+        APREQ_CHARSET_CP1252 : APREQ_CHARSET_LATIN1 : rv;
+}
+
+
+static APR_INLINE apr_uint16_t hex4_to_bmp(const char *what) {
+    register apr_uint16_t digit = 0;
+
+#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'));
+    digit *= 16;
+    digit += (what[2] >= 'A' ? ((what[2] & 0xDF)-'A') + 10 : (what[2]-'0'));
+    digit *= 16;
+    digit += (what[3] >= 'A' ? ((what[3] & 0xDF)-'A') + 10 : (what[3]-'0'));
+
+#else /*APR_CHARSET_EBCDIC*/
+    char xstr[7];
+    xstr[0]='0';
+    xstr[1]='x';
+    xstr[2]=what[0];
+    xstr[3]=what[1];
+    xstr[4]=what[2];
+    xstr[5]=what[3];
+    xstr[6]='\0';
+    digit = apr_xlate_conv_byte(ap_hdrs_from_ascii, 0xFFFF & strtol(xstr, NULL, 16));
+#endif /*APR_CHARSET_EBCDIC*/
+    return (digit);
+}
+
+
+static apr_status_t url_decode(char *dest, apr_size_t *dlen,
+                               const char *src, apr_size_t *slen)
+{
+    register const char *s = src;
+    unsigned char *start = (unsigned char *)dest;
+    register unsigned char *d = (unsigned char *)dest;
+    const char *end = src + *slen;
+
+    for (; s < end; ++d, ++s) {
+        switch (*s) {
+
+        case '+':
+            *d = ' ';
+            break;
+
+        case '%':
+           if (s + 2 < end && apr_isxdigit(s[1]) && apr_isxdigit(s[2]))
+            {
+                *d = hex2_to_char(s + 1);
+                s += 2;
+           }
+            else if (s + 5 < end && (s[1] == 'u' || s[1] == 'U') &&
+                     apr_isxdigit(s[2]) && apr_isxdigit(s[3]) &&
+                     apr_isxdigit(s[4]) && apr_isxdigit(s[5]))
+            {
+                apr_uint16_t c = hex4_to_bmp(s+2);
+
+                if (c < 0x80) {
+                    *d = c;
+                }
+                else if (c < 0x800) {
+                    *d++ = 0xC0 | (c >> 6);
+                    *d   = 0x80 | (c & 0x3F);
+                }
+                else {
+                    *d++ = 0xE0 | (c >> 12);
+                    *d++ = 0x80 | ((c >> 6) & 0x3F);
+                    *d   = 0x80 | (c & 0x3F);
+                }
+                s += 5;
+            }
+           else {
+                *dlen = d - start;
+                *slen = s - src;
+                if (s + 5 < end
+                    || (s + 2 < end && !apr_isxdigit(s[2]))
+                    || (s + 1 < end && !apr_isxdigit(s[1])
+                        && s[1] != 'u' && s[1] != 'U'))
+                {
+                    *d = 0;
+                    return APREQ_ERROR_BADSEQ;
+                }
+
+                memmove(d, s, end - s);
+                d[end - s] = 0;
+                return APR_INCOMPLETE;
+           }
+            break;
+
+        default:
+            if (*s > 0) {
+                *d = *s;
+            }
+            else {
+                *d = 0;
+                *dlen = d - start;
+                *slen = s - src;
+                return APREQ_ERROR_BADCHAR;
+            }
+        }
+    }
+
+    *d = 0;
+    *dlen = d - start;
+    *slen = s - src;
+    return APR_SUCCESS;
+}
+
+
+APREQ_DECLARE(apr_status_t) apreq_decode(char *d, apr_size_t *dlen,
+                                         const char *s, apr_size_t slen)
+{
+    apr_size_t len = 0;
+    const char *end = s + slen;
+
+    if (s == (const char *)d) {     /* optimize for src = dest case */
+        for ( ; d < end; ++d) {
+            if (*d == '%' || *d == '+')
+                break;
+            else if (*d == 0) {
+                *dlen = (const char *)d - s;
+                return APREQ_ERROR_BADCHAR;
+            }
+        }
+        len = (const char *)d - s;
+        s = (const char *)d;
+        slen -= len;
+    }
+
+    return url_decode(d, dlen, s, &slen);
+}
+
+APREQ_DECLARE(apr_status_t) apreq_decodev(char *d, apr_size_t *dlen,
+                                          struct iovec *v, int nelts)
+{
+    apr_status_t status = APR_SUCCESS;
+    int n = 0;
+
+    *dlen = 0;
+
+    while (n < nelts) {
+        apr_size_t slen, len;
+
+        slen = v[n].iov_len;
+        switch (status = url_decode(d, &len, v[n].iov_base, &slen)) {
+
+        case APR_SUCCESS:
+            d += len;
+            *dlen += len;
+            ++n;
+            continue;
+
+        case APR_INCOMPLETE:
+            d += len;
+            *dlen += len;
+            slen = v[n].iov_len - slen;
+
+            if (++n == nelts) {
+                return status;
+            }
+            memcpy(d + slen, v[n].iov_base, v[n].iov_len);
+            v[n].iov_len += slen;
+            v[n].iov_base = d;
+            continue;
+
+        default:
+            *dlen += len;
+            return status;
+        }
+    }
+
+    return status;
+}
+
+
+APREQ_DECLARE(apr_size_t) apreq_encode(char *dest, const char *src,
+                                       const apr_size_t slen)
+{
+    char *d = dest;
+    const unsigned char *s = (const unsigned char *)src;
+    unsigned char c;
+
+    for ( ; s < (const unsigned char *)src + slen; ++s) {
+        c = *s;
+        if ( c < 0x80 && (apr_isalnum(c)
+                          || c == '-' || c == '.'
+                          || c == '_' || c == '~') )
+            *d++ = c;
+
+        else if ( c == ' ' )
+            *d++ = '+';
+
+        else {
+#if APR_CHARSET_EBCDIC
+            c = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)c);
+#endif
+            *d++ = '%';
+            *d++ = c2x_table[c >> 4];
+            *d++ = c2x_table[c & 0xf];
+        }
+    }
+    *d = 0;
+
+    return d - dest;
+}
+
+static int is_quoted(const char *p, const apr_size_t len) {
+    if (len > 1 && p[0] == '"' && p[len-1] == '"') {
+        apr_size_t i;
+        int backslash = 0;
+
+        for (i = 1; i < len - 1; i++) {
+            if (p[i] == '\\')
+                backslash = !backslash;
+            else if (p[i] == 0 || (p[i] == '"' && !backslash))
+                return 0;
+            else
+                backslash = 0;
+        }
+
+        return !backslash;
+    }
+
+    return 0;
+}
+
+APREQ_DECLARE(apr_size_t) apreq_quote_once(char *dest, const char *src,
+                                           const apr_size_t slen)
+{
+    if (is_quoted(src, slen)) {
+        /* looks like src is already quoted */
+        memcpy(dest, src, slen);
+        dest[slen] = 0;
+        return slen;
+    }
+    else
+        return apreq_quote(dest, src, slen);
+}
+
+APREQ_DECLARE(apr_size_t) apreq_quote(char *dest, const char *src,
+                                      const apr_size_t slen)
+{
+    char *d = dest;
+    const char *s = src;
+    const char *const last = src + slen - 1;
+
+    if (slen == 0) {
+        *d = 0;
+        return 0;
+    }
+
+    *d++ = '"';
+
+    while (s <= last) {
+        switch (*s) {
+        case 0:
+            *d++ = '\\';
+            *d++ = '0';
+            s++;
+            break;
+
+        case '\\':
+        case '"':
+            *d++ = '\\';
+
+        default:
+            *d++ = *s++;
+        }
+    }
+
+    *d++ = '"';
+    *d = 0;
+
+    return d - dest;
+}
+
+APREQ_DECLARE(char *) apreq_join(apr_pool_t *p,
+                                 const char *sep,
+                                 const apr_array_header_t *arr,
+                                 apreq_join_t mode)
+{
+    apr_size_t len, slen;
+    char *rv;
+    const apreq_value_t **a = (const apreq_value_t **)arr->elts;
+    char *d;
+    const int n = arr->nelts;
+    int j;
+
+    slen = sep ? strlen(sep) : 0;
+
+    if (n == 0)
+        return apr_pstrdup(p, "");
+
+    for (j=0, len=0; j < n; ++j)
+        len += a[j]->dlen + slen + 1;
+
+    /* Allocated the required space */
+
+    switch (mode) {
+    case APREQ_JOIN_ENCODE:
+        len += 2 * len;
+        break;
+    case APREQ_JOIN_QUOTE:
+        len = 2 * (len + n);
+        break;
+    case APREQ_JOIN_AS_IS:
+    case APREQ_JOIN_DECODE:
+        /* nothing special required, just here to keep noisy compilers happy */
+        break;
+    }
+
+    rv = apr_palloc(p, len);
+
+    /* Pass two --- copy the argument strings into the result space */
+
+    d = rv;
+
+    switch (mode) {
+
+    case APREQ_JOIN_ENCODE:
+        d += apreq_encode(d, a[0]->data, a[0]->dlen);
+
+        for (j = 1; j < n; ++j) {
+                memcpy(d, sep, slen);
+                d += slen;
+                d += apreq_encode(d, a[j]->data, a[j]->dlen);
+        }
+        break;
+
+    case APREQ_JOIN_DECODE:
+        if (apreq_decode(d, &len, a[0]->data, a[0]->dlen))
+            return NULL;
+        else
+            d += len;
+
+        for (j = 1; j < n; ++j) {
+            memcpy(d, sep, slen);
+            d += slen;
+
+            if (apreq_decode(d, &len, a[j]->data, a[j]->dlen))
+                return NULL;
+            else
+                d += len;
+        }
+        break;
+
+
+    case APREQ_JOIN_QUOTE:
+        d += apreq_quote_once(d, a[0]->data, a[0]->dlen);
+
+        for (j = 1; j < n; ++j) {
+            memcpy(d, sep, slen);
+            d += slen;
+            d += apreq_quote_once(d, a[j]->data, a[j]->dlen);
+        }
+        break;
+
+
+    case APREQ_JOIN_AS_IS:
+        memcpy(d,a[0]->data, a[0]->dlen);
+        d += a[0]->dlen;
+
+        for (j = 1; j < n ; ++j) {
+            memcpy(d, sep, slen);
+            d += slen;
+            memcpy(d, a[j]->data, a[j]->dlen);
+            d += a[j]->dlen;
+        }
+        break;
+    }
+
+    *d = 0;
+    return rv;
+}
+
+/*
+ * This is intentionally not apr_file_writev()
+ * note, this is iterative and not recursive
+ */
+APR_INLINE
+static apr_status_t apreq_fwritev(apr_file_t *f, struct iovec *v,
+                                  int *nelts, apr_size_t *bytes_written)
+{
+    apr_size_t len;
+    int n;
+    apr_status_t s;
+
+    *bytes_written = 0;
+
+    while (1) {
+        /* try to write */
+        s = apr_file_writev(f, v, *nelts, &len);
+
+        *bytes_written += len;
+
+        if (s != APR_SUCCESS)
+            return s;
+
+        /* see how far we've come */
+        n = 0;
+
+#ifdef SOLARIS2
+# ifdef __GNUC__
+        /*
+         * iovec.iov_len is a long here
+         * which causes a comparison between 
+         * signed(long) and unsigned(apr_size_t)
+         *
+         */
+        while (n < *nelts && len >= (apr_size_t)v[n].iov_len)
+# else
+          /*
+           * Sun C however defines this as size_t which is unsigned
+           * 
+           */
+        while (n < *nelts && len >= v[n].iov_len)
+# endif /* !__GNUC__ */
+#else
+          /*
+           * Hopefully everything else does this
+           * (this was the default for years)
+           */
+        while (n < *nelts && len >= v[n].iov_len)
+#endif
+            len -= v[n++].iov_len;
+
+        if (n == *nelts) {
+            /* nothing left to write, report success */
+            *nelts = 0;
+            return APR_SUCCESS;
+        }
+
+        /* incomplete write: must shift v */
+        v[n].iov_len -= len;
+        v[n].iov_base = (char *)(v[n].iov_base) + len;
+
+        if (n > 0) {
+            /* we're satisfied for now if we can remove one iovec from
+               the "v" array */
+            (*nelts) -= n;
+            memmove(v, v + n, sizeof(*v) * *nelts);
+
+            return APR_SUCCESS;
+        }
+
+        /* we're still in the first iovec - check for endless loop,
+           and then try again */
+        if (len == 0)
+            return APREQ_ERROR_GENERAL;
+    }
+}
+
+
+
+
+struct cleanup_data {
+    const char *fname;
+    apr_pool_t *pool;
+};
+
+static apr_status_t apreq_file_cleanup(void *d)
+{
+    struct cleanup_data *data = d;
+    return apr_file_remove(data->fname, data->pool);
+}
+
+/*
+ * The reason we need the above cleanup is because on Windows, APR_DELONCLOSE
+ * forces applications to open the file with FILE_SHARED_DELETE
+ * set, which is, unfortunately, a property that is preserved
+ * across NTFS "hard" links.  This breaks apps that link() the temp
+ * file to a permanent location, and subsequently expect to open it
+ * before the original tempfile is closed+deleted. In fact, even
+ * Apache::Upload does this, so it is a common enough event that the
+ * apreq_file_cleanup workaround is necessary.
+ */
+
+APREQ_DECLARE(apr_status_t) apreq_file_mktemp(apr_file_t **fp,
+                                              apr_pool_t *pool,
+                                              const char *path)
+{
+    apr_status_t rc;
+    char *tmpl;
+    struct cleanup_data *data;
+    apr_int32_t flag;
+
+    if (path == NULL) {
+        rc = apr_temp_dir_get(&path, pool);
+        if (rc != APR_SUCCESS)
+            return rc;
+    }
+    rc = apr_filepath_merge(&tmpl, path, "apreqXXXXXX",
+                            APR_FILEPATH_NOTRELATIVE, pool);
+
+    if (rc != APR_SUCCESS)
+        return rc;
+
+    data = apr_palloc(pool, sizeof *data);
+    /* cleanups are LIFO, so this one will run just after
+       the cleanup set by mktemp */
+    apr_pool_cleanup_register(pool, data,
+                              apreq_file_cleanup, apreq_file_cleanup);
+
+    /* NO APR_DELONCLOSE! see comment above */
+    flag = APR_CREATE | APR_READ | APR_WRITE | APR_EXCL | APR_BINARY;
+
+    rc = apr_file_mktemp(fp, tmpl, flag, pool);
+
+    if (rc == APR_SUCCESS) {
+        apr_file_name_get(&data->fname, *fp);
+        data->pool = pool;
+    }
+    else {
+        apr_pool_cleanup_kill(pool, data, apreq_file_cleanup);
+    }
+
+    return rc;
+}
+
+
+/*
+ * is_2616_token() is the verbatim definition from section 2.2
+ * in the rfc itself.  We try to optimize it around the
+ * expectation that the argument is not a token, which
+ * should be the typical usage.
+ */
+
+static APR_INLINE
+unsigned is_2616_token(const char c) {
+    switch (c) {
+    case ' ': case ';': case ',': case '"': case '\t':
+        /* The chars we are expecting are listed above;
+           the chars below are just for completeness. */
+    case '?': case '=': case '@': case ':': case '\\': case '/':
+    case '(': case ')':
+    case '<': case '>':
+    case '{': case '}':
+    case '[': case ']':
+        return 0;
+    default:
+        if (apr_iscntrl(c))
+            return 0;
+    }
+    return 1;
+}
+
+APREQ_DECLARE(apr_status_t)
+    apreq_header_attribute(const char *hdr,
+                           const char *name, const apr_size_t nlen,
+                           const char **val, apr_size_t *vlen)
+{
+    const char *key, *v;
+
+    /* Must ensure first char isn't '=', so we can safely backstep. */
+    while (*hdr == '=')
+        ++hdr;
+
+    while ((key = strchr(hdr, '=')) != NULL) {
+
+        v = key + 1;
+        --key;
+
+        while (apr_isspace(*key) && key > hdr + nlen)
+            --key;
+
+        key -= nlen - 1;
+
+        while (apr_isspace(*v))
+            ++v;
+
+        if (*v == '"') {
+            ++v;
+            *val = v;
+
+        look_for_end_quote:
+            switch (*v) {
+            case '"':
+                break;
+            case 0:
+                return APREQ_ERROR_BADSEQ;
+            case '\\':
+                if (v[1] != 0)
+                    ++v;
+            default:
+                ++v;
+                goto look_for_end_quote;
+            }
+        }
+        else {
+            *val = v;
+
+        look_for_terminator:
+            switch (*v) {
+            case 0:
+            case ' ':
+            case ';':
+            case ',':
+            case '\t':
+            case '\r':
+            case '\n':
+                break;
+            default:
+                ++v;
+                goto look_for_terminator;
+            }
+        }
+
+        if (key >= hdr && strncasecmp(key, name, nlen) == 0) {
+            *vlen = v - *val;
+            if (key == hdr || ! is_2616_token(key[-1]))
+                return APR_SUCCESS;
+        }
+        hdr = v;
+    }
+
+    return APREQ_ERROR_NOATTR;
+}
+
+
+
+#define BUCKET_IS_SPOOL(e) ((e)->type == &spool_bucket_type)
+#define FILE_BUCKET_LIMIT      ((apr_size_t)-1 - 1)
+
+static
+void spool_bucket_destroy(void *data)
+{
+    apr_bucket_type_file.destroy(data);
+}
+
+static
+apr_status_t spool_bucket_read(apr_bucket *e, const char **str,
+                                   apr_size_t *len, apr_read_type_e block)
+{
+    return apr_bucket_type_file.read(e, str, len, block);
+}
+
+static
+apr_status_t spool_bucket_setaside(apr_bucket *data, apr_pool_t *reqpool)
+{
+    return apr_bucket_type_file.setaside(data, reqpool);
+}
+
+static
+apr_status_t spool_bucket_split(apr_bucket *a, apr_size_t point)
+{
+    apr_status_t rv = apr_bucket_shared_split(a, point);
+    a->type = &apr_bucket_type_file;
+    return rv;
+}
+
+static
+apr_status_t spool_bucket_copy(apr_bucket *e, apr_bucket **c)
+{
+    apr_status_t rv = apr_bucket_shared_copy(e, c);
+    (*c)->type = &apr_bucket_type_file;
+    return rv;
+}
+
+static const apr_bucket_type_t spool_bucket_type = {
+    "APREQ_SPOOL", 5, APR_BUCKET_DATA,
+    spool_bucket_destroy,
+    spool_bucket_read,
+    spool_bucket_setaside,
+    spool_bucket_split,
+    spool_bucket_copy,
+};
+
+APREQ_DECLARE(apr_file_t *)apreq_brigade_spoolfile(apr_bucket_brigade *bb)
+{
+    apr_bucket *last;
+
+    last = APR_BRIGADE_LAST(bb);
+    if (BUCKET_IS_SPOOL(last))
+        return ((apr_bucket_file *)last->data)->fd;
+
+    return NULL;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_brigade_concat(apr_pool_t *pool,
+                                                 const char *temp_dir,
+                                                 apr_size_t heap_limit,
+                                                 apr_bucket_brigade *out,
+                                                 apr_bucket_brigade *in)
+{
+    apr_status_t s;
+    apr_bucket_file *f;
+    apr_off_t wlen;
+    apr_file_t *file;
+    apr_off_t in_len, out_len;
+    apr_bucket *last_in, *last_out;
+
+    last_out = APR_BRIGADE_LAST(out);
+
+    if (APR_BUCKET_IS_EOS(last_out))
+        return APR_EOF;
+
+    s = apr_brigade_length(out, 0, &out_len);
+    if (s != APR_SUCCESS)
+        return s;
+
+    /* This cast, when out_len = -1, is intentional */
+    if ((apr_uint64_t)out_len < heap_limit) {
+
+        s = apr_brigade_length(in, 0, &in_len);
+        if (s != APR_SUCCESS)
+            return s;
+
+        /* This cast, when in_len = -1, is intentional */
+        if ((apr_uint64_t)in_len < heap_limit - (apr_uint64_t)out_len) {
+            APR_BRIGADE_CONCAT(out, in);
+            return APR_SUCCESS;
+        }
+    }
+
+    if (!BUCKET_IS_SPOOL(last_out)) {
+
+        s = apreq_file_mktemp(&file, pool, temp_dir);
+        if (s != APR_SUCCESS)
+            return s;
+
+        s = apreq_brigade_fwrite(file, &wlen, out);
+
+        if (s != APR_SUCCESS)
+            return s;
+
+        last_out = apr_bucket_file_create(file, wlen, 0,
+                                          out->p, out->bucket_alloc);
+        last_out->type = &spool_bucket_type;
+        APR_BRIGADE_INSERT_TAIL(out, last_out);
+        f = last_out->data;
+    }
+    else {
+        f = last_out->data;
+        /* Need to seek here, just in case our spool bucket
+         * was read from between apreq_brigade_concat calls.
+         */
+        wlen = last_out->start + last_out->length;
+        s = apr_file_seek(f->fd, APR_SET, &wlen);
+        if (s != APR_SUCCESS)
+            return s;
+    }
+
+    if (in == out)
+        return APR_SUCCESS;
+
+    last_in = APR_BRIGADE_LAST(in);
+
+    if (APR_BUCKET_IS_EOS(last_in))
+        APR_BUCKET_REMOVE(last_in);
+
+    s = apreq_brigade_fwrite(f->fd, &wlen, in);
+
+    if (s == APR_SUCCESS) {
+
+        /* We have to deal with the possibility that the new
+         * data may be too large to be represented by a single
+         * temp_file bucket.
+         */
+
+        while ((apr_uint64_t)wlen > FILE_BUCKET_LIMIT - last_out->length) {
+            apr_bucket *e;
+
+            apr_bucket_copy(last_out, &e);
+            e->length = 0;
+            e->start = last_out->start + FILE_BUCKET_LIMIT;
+            wlen -= FILE_BUCKET_LIMIT - last_out->length;
+            last_out->length = FILE_BUCKET_LIMIT;
+
+            /* Copying makes the bucket types exactly the
+             * opposite of what we need here.
+             */
+            last_out->type = &apr_bucket_type_file;
+            e->type = &spool_bucket_type;
+
+            APR_BRIGADE_INSERT_TAIL(out, e);
+            last_out = e;
+        }
+
+        last_out->length += wlen;
+
+        if (APR_BUCKET_IS_EOS(last_in))
+            APR_BRIGADE_INSERT_TAIL(out, last_in);
+
+    }
+    else if (APR_BUCKET_IS_EOS(last_in))
+        APR_BRIGADE_INSERT_TAIL(in, last_in);
+
+    apr_brigade_cleanup(in);
+    return s;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_brigade_fwrite(apr_file_t *f,
+                                                 apr_off_t *wlen,
+                                                 apr_bucket_brigade *bb)
+{
+    struct iovec v[APREQ_DEFAULT_NELTS];
+    apr_status_t s;
+    apr_bucket *e, *first;
+    int n = 0;
+    apr_bucket_brigade *tmp = bb;
+    *wlen = 0;
+
+    if (BUCKET_IS_SPOOL(APR_BRIGADE_LAST(bb))) {
+        tmp = apr_brigade_create(bb->p, bb->bucket_alloc);
+
+        s = apreq_brigade_copy(tmp, bb);
+        if (s != APR_SUCCESS)
+            return s;
+    }
+
+    for (e = APR_BRIGADE_FIRST(tmp); e != APR_BRIGADE_SENTINEL(tmp);
+         e = APR_BUCKET_NEXT(e))
+    {
+        apr_size_t len;
+        if (n == APREQ_DEFAULT_NELTS) {
+            s = apreq_fwritev(f, v, &n, &len);
+            if (s != APR_SUCCESS)
+                return s;
+
+            if (tmp != bb) {
+                while ((first = APR_BRIGADE_FIRST(tmp)) != e)
+                    apr_bucket_delete(first);
+            }
+
+            *wlen += len;
+        }
+        s = apr_bucket_read(e, (const char **)&(v[n].iov_base),
+                            &len, APR_BLOCK_READ);
+        if (s != APR_SUCCESS)
+            return s;
+
+        v[n++].iov_len = len;
+    }
+
+    while (n > 0) {
+        apr_size_t len;
+        s = apreq_fwritev(f, v, &n, &len);
+        if (s != APR_SUCCESS)
+            return s;
+        *wlen += len;
+
+        if (tmp != bb) {
+            while ((first = APR_BRIGADE_FIRST(tmp)) != e)
+                apr_bucket_delete(first);
+        }
+    }
+    return APR_SUCCESS;
+}
diff --git a/srclib/libapreq/version.c b/srclib/libapreq/version.c
new file mode 100644 (file)
index 0000000..5cfd767
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+**  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.
+*/
+
+#include "apreq_version.h"
+#include "apr_general.h" /* for APR_STRINGIFY */
+
+APREQ_DECLARE(void) apreq_version(apr_version_t *pvsn)
+{
+    pvsn->major = APREQ_MAJOR_VERSION;
+    pvsn->minor = APREQ_MINOR_VERSION;
+    pvsn->patch = APREQ_PATCH_VERSION;
+#ifdef APREQ_IS_DEV_VERSION
+    pvsn->is_dev = 1;
+#else
+    pvsn->is_dev = 0;
+#endif
+}
+
+APREQ_DECLARE(const char *) apreq_version_string(void)
+{
+    return APREQ_VERSION_STRING;
+}