From c1902d9d282a57c6af2ab2265d4fa378b7549892 Mon Sep 17 00:00:00 2001 From: "Philip M. Gollucci" Date: Thu, 10 Nov 2011 18:04:56 +0000 Subject: [PATCH] relocate apreq/library as a srclib git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1200454 13f79535-47bb-0310-9956-ffa450edef68 --- srclib/libapreq/Makefile.am | 42 + srclib/libapreq/cookie.c | 537 ++++++++++++ srclib/libapreq/error.c | 105 +++ srclib/libapreq/module.c | 65 ++ srclib/libapreq/module_cgi.c | 1013 +++++++++++++++++++++++ srclib/libapreq/module_custom.c | 304 +++++++ srclib/libapreq/param.c | 272 +++++++ srclib/libapreq/parser.c | 356 ++++++++ srclib/libapreq/parser_header.c | 365 +++++++++ srclib/libapreq/parser_multipart.c | 661 +++++++++++++++ srclib/libapreq/parser_urlencoded.c | 275 +++++++ srclib/libapreq/t/Makefile.am | 19 + srclib/libapreq/t/at.c | 394 +++++++++ srclib/libapreq/t/at.h | 301 +++++++ srclib/libapreq/t/cookie.c | 246 ++++++ srclib/libapreq/t/error.c | 93 +++ srclib/libapreq/t/params.c | 229 ++++++ srclib/libapreq/t/parsers.c | 555 +++++++++++++ srclib/libapreq/t/util.c | 340 ++++++++ srclib/libapreq/t/version.c | 69 ++ srclib/libapreq/util.c | 1168 +++++++++++++++++++++++++++ srclib/libapreq/version.c | 36 + 22 files changed, 7445 insertions(+) create mode 100644 srclib/libapreq/Makefile.am create mode 100644 srclib/libapreq/cookie.c create mode 100644 srclib/libapreq/error.c create mode 100644 srclib/libapreq/module.c create mode 100644 srclib/libapreq/module_cgi.c create mode 100644 srclib/libapreq/module_custom.c create mode 100644 srclib/libapreq/param.c create mode 100644 srclib/libapreq/parser.c create mode 100644 srclib/libapreq/parser_header.c create mode 100644 srclib/libapreq/parser_multipart.c create mode 100644 srclib/libapreq/parser_urlencoded.c create mode 100644 srclib/libapreq/t/Makefile.am create mode 100644 srclib/libapreq/t/at.c create mode 100644 srclib/libapreq/t/at.h create mode 100644 srclib/libapreq/t/cookie.c create mode 100644 srclib/libapreq/t/error.c create mode 100644 srclib/libapreq/t/params.c create mode 100644 srclib/libapreq/t/parsers.c create mode 100644 srclib/libapreq/t/util.c create mode 100644 srclib/libapreq/t/version.c create mode 100644 srclib/libapreq/util.c create mode 100644 srclib/libapreq/version.c diff --git a/srclib/libapreq/Makefile.am b/srclib/libapreq/Makefile.am new file mode 100644 index 0000000000..122e52238b --- /dev/null +++ b/srclib/libapreq/Makefile.am @@ -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 index 0000000000..417df9e715 --- /dev/null +++ b/srclib/libapreq/cookie.c @@ -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 index 0000000000..92a270bbe9 --- /dev/null +++ b/srclib/libapreq/error.c @@ -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 index 0000000000..9ba5a765ed --- /dev/null +++ b/srclib/libapreq/module.c @@ -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 index 0000000000..14fb39ba39 --- /dev/null +++ b/srclib/libapreq/module_cgi.c @@ -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 + +#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 index 0000000000..e1e6f58bfe --- /dev/null +++ b/srclib/libapreq/module_custom.c @@ -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 index 0000000000..83e185b595 --- /dev/null +++ b/srclib/libapreq/param.c @@ -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 = ¶m->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(¶m, pool, start, nlen, vlen); + if (s != APR_SUCCESS) + return s; + + apreq_param_tainted_on(param); + apreq_value_table_add(¶m->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, ¶m, body, name, NULL); + return param; +} diff --git a/srclib/libapreq/parser.c b/srclib/libapreq/parser.c new file mode 100644 index 0000000000..69c0c4f035 --- /dev/null +++ b/srclib/libapreq/parser.c @@ -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 index 0000000000..ae2e030062 --- /dev/null +++ b/srclib/libapreq/parser_header.c @@ -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 +#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 = ¶m->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(¶m, 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(¶m->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 index 0000000000..60b5bad9de --- /dev/null +++ b/srclib/libapreq/parser_multipart.c @@ -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 = ¶m->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(¶m->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 index 0000000000..e90d0dd382 --- /dev/null +++ b/srclib/libapreq/parser_urlencoded.c @@ -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 = ¶m->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(¶m, 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(¶m->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(¶m, 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(¶m->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 index 0000000000..8b2d6f55a7 --- /dev/null +++ b/srclib/libapreq/t/Makefile.am @@ -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 index 0000000000..19e59cbb93 --- /dev/null +++ b/srclib/libapreq/t/at.c @@ -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 +#include + +#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 index 0000000000..2ec318fc37 --- /dev/null +++ b/srclib/libapreq/t/at.h @@ -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 +#include +#include +#include +#include + +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 index 0000000000..4399e4fd5a --- /dev/null +++ b/srclib/libapreq/t/cookie.c @@ -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 index 0000000000..49c21d1e3c --- /dev/null +++ b/srclib/libapreq/t/error.c @@ -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 index 0000000000..b142450886 --- /dev/null +++ b/srclib/libapreq/t/params.c @@ -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 index 0000000000..f8449d1424 --- /dev/null +++ b/srclib/libapreq/t/parsers.c @@ -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[] = +"" +"" +" foo.bar" +" " +" 1" +" " +""; + +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*/ +"" CRLF +"" +" My Proposal" +" E. X. Ample" +" A proposal for a new project." +" (see handwritten region)" +" project proposal funding" +" false" +" image.png" +" cid:980119.X25MNC@example.com" +"" /*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 index 0000000000..f857626eca --- /dev/null +++ b/srclib/libapreq/t/util.c @@ -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 index 0000000000..955b6a9e34 --- /dev/null +++ b/srclib/libapreq/t/version.c @@ -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 index 0000000000..6d33fa94fb --- /dev/null +++ b/srclib/libapreq/util.c @@ -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 + +#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 index 0000000000..5cfd767c12 --- /dev/null +++ b/srclib/libapreq/version.c @@ -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; +} -- 2.40.0