summaryrefslogtreecommitdiff
path: root/srclib/libapreq/util.c
diff options
context:
space:
mode:
Diffstat (limited to 'srclib/libapreq/util.c')
-rw-r--r--srclib/libapreq/util.c1168
1 files changed, 1168 insertions, 0 deletions
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 <assert.h>
+
+#undef MAX
+#undef MIN
+#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
+#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
+
+/* used for specifying file sizes */
+
+APREQ_DECLARE(apr_int64_t) apreq_atoi64f(const char *s)
+{
+ apr_int64_t n = 0;
+ char *p;
+ if (s == NULL)
+ return 0;
+
+ n = apr_strtoi64(s, &p, 0);
+
+ if (p == NULL)
+ return n;
+ while (apr_isspace(*p))
+ ++p;
+
+ switch (*p) {
+ case 'G': /* fall thru */
+ case 'g': return n * 1024*1024*1024;
+ case 'M': /* fall thru */
+ case 'm': return n * 1024*1024;
+ case 'K': /* fall thru */
+ case 'k': return n * 1024;
+ }
+
+ return n;
+}
+
+
+/* converts date offsets (e.g. "+3M") to seconds */
+
+APREQ_DECLARE(apr_int64_t) apreq_atoi64t(const char *s)
+{
+ apr_int64_t n = 0;
+ char *p;
+ if (s == NULL)
+ return 0;
+ n = apr_strtoi64(s, &p, 0); /* XXX: what about overflow? */
+
+ if (p == NULL)
+ return n;
+ while (apr_isspace(*p))
+ ++p;
+
+ switch (*p) {
+ case 'Y': /* fall thru */
+ case 'y': return n * 60*60*24*365;
+ case 'M': return n * 60*60*24*30;
+ case 'D': /* fall thru */
+ case 'd': return n * 60*60*24;
+ case 'H': /* fall thru */
+ case 'h': return n * 60*60;
+ case 'm': return n * 60;
+ case 's': /* fall thru */
+ default:
+ return n;
+ }
+ /* should never get here */
+ return -1;
+}
+
+
+APREQ_DECLARE(apr_ssize_t ) apreq_index(const char* hay, apr_size_t hlen,
+ const char* ndl, apr_size_t nlen,
+ const apreq_match_t type)
+{
+ apr_size_t len = hlen;
+ const char *end = hay + hlen;
+ const char *begin = hay;
+
+ while ( (hay = memchr(hay, ndl[0], len)) ) {
+ len = end - hay;
+
+ /* done if matches up to capacity of buffer */
+ if ( memcmp(hay, ndl, MIN(nlen, len)) == 0 ) {
+ if (type == APREQ_MATCH_FULL && len < nlen)
+ hay = NULL; /* insufficient room for match */
+ break;
+ }
+ --len;
+ ++hay;
+ }
+
+ return hay ? hay - begin : -1;
+}
+
+
+static const char c2x_table[] = "0123456789ABCDEF";
+static APR_INLINE unsigned char hex2_to_char(const char *what)
+{
+ register unsigned char digit;
+
+#if !APR_CHARSET_EBCDIC
+ digit = (what[0] >= 'A' ? ((what[0] & 0xdf) - 'A') + 10 : (what[0] - '0'));
+ digit *= 16;
+ digit += (what[1] >= 'A' ? ((what[1] & 0xdf) - 'A') + 10 : (what[1] - '0'));
+#else /*APR_CHARSET_EBCDIC*/
+ char xstr[5];
+ xstr[0]='0';
+ xstr[1]='x';
+ xstr[2]=what[0];
+ xstr[3]=what[1];
+ xstr[4]='\0';
+ digit = apr_xlate_conv_byte(ap_hdrs_from_ascii, 0xFF & strtol(xstr, NULL, 16));
+#endif /*APR_CHARSET_EBCDIC*/
+ return (digit);
+}
+
+
+/* Unicode notes: "bmp" refers to the 16-bit
+ * Unicode Basic Multilingual Plane. Here we're
+ * restricting our unicode internals to 16-bit
+ * codepoints, to keep the code as simple as possible.
+ * This should be sufficient for apreq itself, since
+ * we really only need to validate RFC3986-encoded utf8.
+ */
+
+/* Converts Windows cp1252 to Unicode. */
+
+static APR_INLINE
+apr_uint16_t cp1252_to_bmp(unsigned char c)
+{
+ /* We only need to deal with iso-8859-1 control chars
+ * in the 0x80 - 0x9F range.
+ */
+ if ((c & 0xE0) != 0x80)
+ return c;
+
+ switch (c) {
+ case 0x80: return 0x20AC;
+ case 0x82: return 0x201A;
+ case 0x83: return 0x192;
+ case 0x84: return 0x201E;
+ case 0x85: return 0x2026;
+ case 0x86: return 0x2020;
+ case 0x87: return 0x2021;
+ case 0x88: return 0x2C6;
+ case 0x89: return 0x2030;
+ case 0x8A: return 0x160;
+ case 0x8B: return 0x2039;
+ case 0x8C: return 0x152;
+ case 0x8E: return 0x17D;
+ case 0x91: return 0x2018;
+ case 0x92: return 0x2019;
+ case 0x93: return 0x201C;
+ case 0x94: return 0x201D;
+ case 0x95: return 0x2022;
+ case 0x96: return 0x2013;
+ case 0x97: return 0x2014;
+ case 0x98: return 0x2DC;
+ case 0x99: return 0x2122;
+ case 0x9A: return 0x161;
+ case 0x9B: return 0x203A;
+ case 0x9C: return 0x153;
+ case 0x9E: return 0x17E;
+ case 0x9F: return 0x178;
+ }
+ return c;
+}
+
+/* converts cp1252 to utf8 */
+APREQ_DECLARE(apr_size_t) apreq_cp1252_to_utf8(char *dest,
+ const char *src, apr_size_t slen)
+{
+ const unsigned char *s = (unsigned const char *)src;
+ const unsigned char *end = s + slen;
+ unsigned char *d = (unsigned char *)dest;
+ apr_uint16_t c;
+
+ while (s < end) {
+ c = cp1252_to_bmp(*s++);
+
+ if (c < 0x80) {
+ *d++ = c;
+ }
+ else if (c < 0x800) {
+ *d++ = 0xC0 | (c >> 6);
+ *d++ = 0x80 | (c & 0x3F);
+ }
+ else {
+ *d++ = 0xE0 | (c >> 12);
+ *d++ = 0x80 | ((c >> 6) & 0x3F);
+ *d++ = 0x80 | (c & 0x3F);
+ }
+ }
+ *d = 0;
+ return d - (unsigned char *)dest;
+}
+
+
+/**
+ * Valid utf8 bit patterns: (true utf8 must satisfy a minimality condition)
+ *
+ * 0aaaaaaa
+ * 110bbbba 10aaaaaa minimality mask: 0x1E
+ * 1110cccc 10cbbbba 10aaaaaa 0x0F || 0x20
+ * 11110ddd 10ddcccc 10cbbbba 10aaaaaa 0x07 || 0x30
+ * 111110ee 10eeeddd 10ddcccc 10cbbbba 10aaaaaa 0x03 || 0x38
+ * 1111110f 10ffffee 10eeeddd 10ddcccc 10cbbbba 10aaaaaa 0x01 || 0x3C
+ *
+ * Charset divination heuristics:
+ * 1) presume ascii; if not, then
+ * 2) presume utf8; if not, then
+ * 3) presume latin1; unless there are control chars, in which case
+ * 4) punt to cp1252.
+ *
+ * Note: in downgrading from 2 to 3, we need to be careful
+ * about earlier control characters presumed to be valid utf8.
+ */
+
+APREQ_DECLARE(apreq_charset_t) apreq_charset_divine(const char *src,
+ apr_size_t slen)
+
+{
+ apreq_charset_t rv = APREQ_CHARSET_ASCII;
+ register unsigned char trail = 0, saw_cntrl = 0, mask = 0;
+ register const unsigned char *s = (const unsigned char *)src;
+ const unsigned char *end = s + slen;
+
+ for (; s < end; ++s) {
+ if (trail) {
+ if ((*s & 0xC0) == 0x80 && (mask == 0 || (mask & *s))) {
+ mask = 0;
+ --trail;
+
+ if ((*s & 0xE0) == 0x80) {
+ saw_cntrl = 1;
+ }
+ }
+ else {
+ trail = 0;
+ if (saw_cntrl)
+ return APREQ_CHARSET_CP1252;
+ rv = APREQ_CHARSET_LATIN1;
+ }
+ }
+ else if (*s < 0x80) {
+ /* do nothing */
+ }
+ else if (*s < 0xA0) {
+ return APREQ_CHARSET_CP1252;
+ }
+ else if (*s < 0xC0) {
+ if (saw_cntrl)
+ return APREQ_CHARSET_CP1252;
+ rv = APREQ_CHARSET_LATIN1;
+ }
+ else if (rv == APREQ_CHARSET_LATIN1) {
+ /* do nothing */
+ }
+
+ /* utf8 cases */
+
+ else if (*s < 0xE0) {
+ if (*s & 0x1E) {
+ rv = APREQ_CHARSET_UTF8;
+ trail = 1;
+ mask = 0;
+ }
+ else if (saw_cntrl)
+ return APREQ_CHARSET_CP1252;
+ else
+ rv = APREQ_CHARSET_LATIN1;
+ }
+ else if (*s < 0xF0) {
+ mask = (*s & 0x0F) ? 0 : 0x20;
+ rv = APREQ_CHARSET_UTF8;
+ trail = 2;
+ }
+ else if (*s < 0xF8) {
+ mask = (*s & 0x07) ? 0 : 0x30;
+ rv = APREQ_CHARSET_UTF8;
+ trail = 3;
+ }
+ else if (*s < 0xFC) {
+ mask = (*s & 0x03) ? 0 : 0x38;
+ rv = APREQ_CHARSET_UTF8;
+ trail = 4;
+ }
+ else if (*s < 0xFE) {
+ mask = (*s & 0x01) ? 0 : 0x3C;
+ rv = APREQ_CHARSET_UTF8;
+ trail = 5;
+ }
+ else {
+ rv = APREQ_CHARSET_UTF8;
+ }
+ }
+
+ return trail ? saw_cntrl ?
+ APREQ_CHARSET_CP1252 : APREQ_CHARSET_LATIN1 : rv;
+}
+
+
+static APR_INLINE apr_uint16_t hex4_to_bmp(const char *what) {
+ register apr_uint16_t digit = 0;
+
+#if !APR_CHARSET_EBCDIC
+ digit = (what[0] >= 'A' ? ((what[0] & 0xDF)-'A') + 10 : (what[0]-'0'));
+ digit *= 16;
+ digit += (what[1] >= 'A' ? ((what[1] & 0xDF)-'A') + 10 : (what[1]-'0'));
+ digit *= 16;
+ digit += (what[2] >= 'A' ? ((what[2] & 0xDF)-'A') + 10 : (what[2]-'0'));
+ digit *= 16;
+ digit += (what[3] >= 'A' ? ((what[3] & 0xDF)-'A') + 10 : (what[3]-'0'));
+
+#else /*APR_CHARSET_EBCDIC*/
+ char xstr[7];
+ xstr[0]='0';
+ xstr[1]='x';
+ xstr[2]=what[0];
+ xstr[3]=what[1];
+ xstr[4]=what[2];
+ xstr[5]=what[3];
+ xstr[6]='\0';
+ digit = apr_xlate_conv_byte(ap_hdrs_from_ascii, 0xFFFF & strtol(xstr, NULL, 16));
+#endif /*APR_CHARSET_EBCDIC*/
+ return (digit);
+}
+
+
+static apr_status_t url_decode(char *dest, apr_size_t *dlen,
+ const char *src, apr_size_t *slen)
+{
+ register const char *s = src;
+ unsigned char *start = (unsigned char *)dest;
+ register unsigned char *d = (unsigned char *)dest;
+ const char *end = src + *slen;
+
+ for (; s < end; ++d, ++s) {
+ switch (*s) {
+
+ case '+':
+ *d = ' ';
+ break;
+
+ case '%':
+ if (s + 2 < end && apr_isxdigit(s[1]) && apr_isxdigit(s[2]))
+ {
+ *d = hex2_to_char(s + 1);
+ s += 2;
+ }
+ else if (s + 5 < end && (s[1] == 'u' || s[1] == 'U') &&
+ apr_isxdigit(s[2]) && apr_isxdigit(s[3]) &&
+ apr_isxdigit(s[4]) && apr_isxdigit(s[5]))
+ {
+ apr_uint16_t c = hex4_to_bmp(s+2);
+
+ if (c < 0x80) {
+ *d = c;
+ }
+ else if (c < 0x800) {
+ *d++ = 0xC0 | (c >> 6);
+ *d = 0x80 | (c & 0x3F);
+ }
+ else {
+ *d++ = 0xE0 | (c >> 12);
+ *d++ = 0x80 | ((c >> 6) & 0x3F);
+ *d = 0x80 | (c & 0x3F);
+ }
+ s += 5;
+ }
+ else {
+ *dlen = d - start;
+ *slen = s - src;
+ if (s + 5 < end
+ || (s + 2 < end && !apr_isxdigit(s[2]))
+ || (s + 1 < end && !apr_isxdigit(s[1])
+ && s[1] != 'u' && s[1] != 'U'))
+ {
+ *d = 0;
+ return APREQ_ERROR_BADSEQ;
+ }
+
+ memmove(d, s, end - s);
+ d[end - s] = 0;
+ return APR_INCOMPLETE;
+ }
+ break;
+
+ default:
+ if (*s > 0) {
+ *d = *s;
+ }
+ else {
+ *d = 0;
+ *dlen = d - start;
+ *slen = s - src;
+ return APREQ_ERROR_BADCHAR;
+ }
+ }
+ }
+
+ *d = 0;
+ *dlen = d - start;
+ *slen = s - src;
+ return APR_SUCCESS;
+}
+
+
+APREQ_DECLARE(apr_status_t) apreq_decode(char *d, apr_size_t *dlen,
+ const char *s, apr_size_t slen)
+{
+ apr_size_t len = 0;
+ const char *end = s + slen;
+
+ if (s == (const char *)d) { /* optimize for src = dest case */
+ for ( ; d < end; ++d) {
+ if (*d == '%' || *d == '+')
+ break;
+ else if (*d == 0) {
+ *dlen = (const char *)d - s;
+ return APREQ_ERROR_BADCHAR;
+ }
+ }
+ len = (const char *)d - s;
+ s = (const char *)d;
+ slen -= len;
+ }
+
+ return url_decode(d, dlen, s, &slen);
+}
+
+APREQ_DECLARE(apr_status_t) apreq_decodev(char *d, apr_size_t *dlen,
+ struct iovec *v, int nelts)
+{
+ apr_status_t status = APR_SUCCESS;
+ int n = 0;
+
+ *dlen = 0;
+
+ while (n < nelts) {
+ apr_size_t slen, len;
+
+ slen = v[n].iov_len;
+ switch (status = url_decode(d, &len, v[n].iov_base, &slen)) {
+
+ case APR_SUCCESS:
+ d += len;
+ *dlen += len;
+ ++n;
+ continue;
+
+ case APR_INCOMPLETE:
+ d += len;
+ *dlen += len;
+ slen = v[n].iov_len - slen;
+
+ if (++n == nelts) {
+ return status;
+ }
+ memcpy(d + slen, v[n].iov_base, v[n].iov_len);
+ v[n].iov_len += slen;
+ v[n].iov_base = d;
+ continue;
+
+ default:
+ *dlen += len;
+ return status;
+ }
+ }
+
+ return status;
+}
+
+
+APREQ_DECLARE(apr_size_t) apreq_encode(char *dest, const char *src,
+ const apr_size_t slen)
+{
+ char *d = dest;
+ const unsigned char *s = (const unsigned char *)src;
+ unsigned char c;
+
+ for ( ; s < (const unsigned char *)src + slen; ++s) {
+ c = *s;
+ if ( c < 0x80 && (apr_isalnum(c)
+ || c == '-' || c == '.'
+ || c == '_' || c == '~') )
+ *d++ = c;
+
+ else if ( c == ' ' )
+ *d++ = '+';
+
+ else {
+#if APR_CHARSET_EBCDIC
+ c = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)c);
+#endif
+ *d++ = '%';
+ *d++ = c2x_table[c >> 4];
+ *d++ = c2x_table[c & 0xf];
+ }
+ }
+ *d = 0;
+
+ return d - dest;
+}
+
+static int is_quoted(const char *p, const apr_size_t len) {
+ if (len > 1 && p[0] == '"' && p[len-1] == '"') {
+ apr_size_t i;
+ int backslash = 0;
+
+ for (i = 1; i < len - 1; i++) {
+ if (p[i] == '\\')
+ backslash = !backslash;
+ else if (p[i] == 0 || (p[i] == '"' && !backslash))
+ return 0;
+ else
+ backslash = 0;
+ }
+
+ return !backslash;
+ }
+
+ return 0;
+}
+
+APREQ_DECLARE(apr_size_t) apreq_quote_once(char *dest, const char *src,
+ const apr_size_t slen)
+{
+ if (is_quoted(src, slen)) {
+ /* looks like src is already quoted */
+ memcpy(dest, src, slen);
+ dest[slen] = 0;
+ return slen;
+ }
+ else
+ return apreq_quote(dest, src, slen);
+}
+
+APREQ_DECLARE(apr_size_t) apreq_quote(char *dest, const char *src,
+ const apr_size_t slen)
+{
+ char *d = dest;
+ const char *s = src;
+ const char *const last = src + slen - 1;
+
+ if (slen == 0) {
+ *d = 0;
+ return 0;
+ }
+
+ *d++ = '"';
+
+ while (s <= last) {
+ switch (*s) {
+ case 0:
+ *d++ = '\\';
+ *d++ = '0';
+ s++;
+ break;
+
+ case '\\':
+ case '"':
+ *d++ = '\\';
+
+ default:
+ *d++ = *s++;
+ }
+ }
+
+ *d++ = '"';
+ *d = 0;
+
+ return d - dest;
+}
+
+APREQ_DECLARE(char *) apreq_join(apr_pool_t *p,
+ const char *sep,
+ const apr_array_header_t *arr,
+ apreq_join_t mode)
+{
+ apr_size_t len, slen;
+ char *rv;
+ const apreq_value_t **a = (const apreq_value_t **)arr->elts;
+ char *d;
+ const int n = arr->nelts;
+ int j;
+
+ slen = sep ? strlen(sep) : 0;
+
+ if (n == 0)
+ return apr_pstrdup(p, "");
+
+ for (j=0, len=0; j < n; ++j)
+ len += a[j]->dlen + slen + 1;
+
+ /* Allocated the required space */
+
+ switch (mode) {
+ case APREQ_JOIN_ENCODE:
+ len += 2 * len;
+ break;
+ case APREQ_JOIN_QUOTE:
+ len = 2 * (len + n);
+ break;
+ case APREQ_JOIN_AS_IS:
+ case APREQ_JOIN_DECODE:
+ /* nothing special required, just here to keep noisy compilers happy */
+ break;
+ }
+
+ rv = apr_palloc(p, len);
+
+ /* Pass two --- copy the argument strings into the result space */
+
+ d = rv;
+
+ switch (mode) {
+
+ case APREQ_JOIN_ENCODE:
+ d += apreq_encode(d, a[0]->data, a[0]->dlen);
+
+ for (j = 1; j < n; ++j) {
+ memcpy(d, sep, slen);
+ d += slen;
+ d += apreq_encode(d, a[j]->data, a[j]->dlen);
+ }
+ break;
+
+ case APREQ_JOIN_DECODE:
+ if (apreq_decode(d, &len, a[0]->data, a[0]->dlen))
+ return NULL;
+ else
+ d += len;
+
+ for (j = 1; j < n; ++j) {
+ memcpy(d, sep, slen);
+ d += slen;
+
+ if (apreq_decode(d, &len, a[j]->data, a[j]->dlen))
+ return NULL;
+ else
+ d += len;
+ }
+ break;
+
+
+ case APREQ_JOIN_QUOTE:
+ d += apreq_quote_once(d, a[0]->data, a[0]->dlen);
+
+ for (j = 1; j < n; ++j) {
+ memcpy(d, sep, slen);
+ d += slen;
+ d += apreq_quote_once(d, a[j]->data, a[j]->dlen);
+ }
+ break;
+
+
+ case APREQ_JOIN_AS_IS:
+ memcpy(d,a[0]->data, a[0]->dlen);
+ d += a[0]->dlen;
+
+ for (j = 1; j < n ; ++j) {
+ memcpy(d, sep, slen);
+ d += slen;
+ memcpy(d, a[j]->data, a[j]->dlen);
+ d += a[j]->dlen;
+ }
+ break;
+ }
+
+ *d = 0;
+ return rv;
+}
+
+/*
+ * This is intentionally not apr_file_writev()
+ * note, this is iterative and not recursive
+ */
+APR_INLINE
+static apr_status_t apreq_fwritev(apr_file_t *f, struct iovec *v,
+ int *nelts, apr_size_t *bytes_written)
+{
+ apr_size_t len;
+ int n;
+ apr_status_t s;
+
+ *bytes_written = 0;
+
+ while (1) {
+ /* try to write */
+ s = apr_file_writev(f, v, *nelts, &len);
+
+ *bytes_written += len;
+
+ if (s != APR_SUCCESS)
+ return s;
+
+ /* see how far we've come */
+ n = 0;
+
+#ifdef SOLARIS2
+# ifdef __GNUC__
+ /*
+ * iovec.iov_len is a long here
+ * which causes a comparison between
+ * signed(long) and unsigned(apr_size_t)
+ *
+ */
+ while (n < *nelts && len >= (apr_size_t)v[n].iov_len)
+# else
+ /*
+ * Sun C however defines this as size_t which is unsigned
+ *
+ */
+ while (n < *nelts && len >= v[n].iov_len)
+# endif /* !__GNUC__ */
+#else
+ /*
+ * Hopefully everything else does this
+ * (this was the default for years)
+ */
+ while (n < *nelts && len >= v[n].iov_len)
+#endif
+ len -= v[n++].iov_len;
+
+ if (n == *nelts) {
+ /* nothing left to write, report success */
+ *nelts = 0;
+ return APR_SUCCESS;
+ }
+
+ /* incomplete write: must shift v */
+ v[n].iov_len -= len;
+ v[n].iov_base = (char *)(v[n].iov_base) + len;
+
+ if (n > 0) {
+ /* we're satisfied for now if we can remove one iovec from
+ the "v" array */
+ (*nelts) -= n;
+ memmove(v, v + n, sizeof(*v) * *nelts);
+
+ return APR_SUCCESS;
+ }
+
+ /* we're still in the first iovec - check for endless loop,
+ and then try again */
+ if (len == 0)
+ return APREQ_ERROR_GENERAL;
+ }
+}
+
+
+
+
+struct cleanup_data {
+ const char *fname;
+ apr_pool_t *pool;
+};
+
+static apr_status_t apreq_file_cleanup(void *d)
+{
+ struct cleanup_data *data = d;
+ return apr_file_remove(data->fname, data->pool);
+}
+
+/*
+ * The reason we need the above cleanup is because on Windows, APR_DELONCLOSE
+ * forces applications to open the file with FILE_SHARED_DELETE
+ * set, which is, unfortunately, a property that is preserved
+ * across NTFS "hard" links. This breaks apps that link() the temp
+ * file to a permanent location, and subsequently expect to open it
+ * before the original tempfile is closed+deleted. In fact, even
+ * Apache::Upload does this, so it is a common enough event that the
+ * apreq_file_cleanup workaround is necessary.
+ */
+
+APREQ_DECLARE(apr_status_t) apreq_file_mktemp(apr_file_t **fp,
+ apr_pool_t *pool,
+ const char *path)
+{
+ apr_status_t rc;
+ char *tmpl;
+ struct cleanup_data *data;
+ apr_int32_t flag;
+
+ if (path == NULL) {
+ rc = apr_temp_dir_get(&path, pool);
+ if (rc != APR_SUCCESS)
+ return rc;
+ }
+ rc = apr_filepath_merge(&tmpl, path, "apreqXXXXXX",
+ APR_FILEPATH_NOTRELATIVE, pool);
+
+ if (rc != APR_SUCCESS)
+ return rc;
+
+ data = apr_palloc(pool, sizeof *data);
+ /* cleanups are LIFO, so this one will run just after
+ the cleanup set by mktemp */
+ apr_pool_cleanup_register(pool, data,
+ apreq_file_cleanup, apreq_file_cleanup);
+
+ /* NO APR_DELONCLOSE! see comment above */
+ flag = APR_CREATE | APR_READ | APR_WRITE | APR_EXCL | APR_BINARY;
+
+ rc = apr_file_mktemp(fp, tmpl, flag, pool);
+
+ if (rc == APR_SUCCESS) {
+ apr_file_name_get(&data->fname, *fp);
+ data->pool = pool;
+ }
+ else {
+ apr_pool_cleanup_kill(pool, data, apreq_file_cleanup);
+ }
+
+ return rc;
+}
+
+
+/*
+ * is_2616_token() is the verbatim definition from section 2.2
+ * in the rfc itself. We try to optimize it around the
+ * expectation that the argument is not a token, which
+ * should be the typical usage.
+ */
+
+static APR_INLINE
+unsigned is_2616_token(const char c) {
+ switch (c) {
+ case ' ': case ';': case ',': case '"': case '\t':
+ /* The chars we are expecting are listed above;
+ the chars below are just for completeness. */
+ case '?': case '=': case '@': case ':': case '\\': case '/':
+ case '(': case ')':
+ case '<': case '>':
+ case '{': case '}':
+ case '[': case ']':
+ return 0;
+ default:
+ if (apr_iscntrl(c))
+ return 0;
+ }
+ return 1;
+}
+
+APREQ_DECLARE(apr_status_t)
+ apreq_header_attribute(const char *hdr,
+ const char *name, const apr_size_t nlen,
+ const char **val, apr_size_t *vlen)
+{
+ const char *key, *v;
+
+ /* Must ensure first char isn't '=', so we can safely backstep. */
+ while (*hdr == '=')
+ ++hdr;
+
+ while ((key = strchr(hdr, '=')) != NULL) {
+
+ v = key + 1;
+ --key;
+
+ while (apr_isspace(*key) && key > hdr + nlen)
+ --key;
+
+ key -= nlen - 1;
+
+ while (apr_isspace(*v))
+ ++v;
+
+ if (*v == '"') {
+ ++v;
+ *val = v;
+
+ look_for_end_quote:
+ switch (*v) {
+ case '"':
+ break;
+ case 0:
+ return APREQ_ERROR_BADSEQ;
+ case '\\':
+ if (v[1] != 0)
+ ++v;
+ default:
+ ++v;
+ goto look_for_end_quote;
+ }
+ }
+ else {
+ *val = v;
+
+ look_for_terminator:
+ switch (*v) {
+ case 0:
+ case ' ':
+ case ';':
+ case ',':
+ case '\t':
+ case '\r':
+ case '\n':
+ break;
+ default:
+ ++v;
+ goto look_for_terminator;
+ }
+ }
+
+ if (key >= hdr && strncasecmp(key, name, nlen) == 0) {
+ *vlen = v - *val;
+ if (key == hdr || ! is_2616_token(key[-1]))
+ return APR_SUCCESS;
+ }
+ hdr = v;
+ }
+
+ return APREQ_ERROR_NOATTR;
+}
+
+
+
+#define BUCKET_IS_SPOOL(e) ((e)->type == &spool_bucket_type)
+#define FILE_BUCKET_LIMIT ((apr_size_t)-1 - 1)
+
+static
+void spool_bucket_destroy(void *data)
+{
+ apr_bucket_type_file.destroy(data);
+}
+
+static
+apr_status_t spool_bucket_read(apr_bucket *e, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ return apr_bucket_type_file.read(e, str, len, block);
+}
+
+static
+apr_status_t spool_bucket_setaside(apr_bucket *data, apr_pool_t *reqpool)
+{
+ return apr_bucket_type_file.setaside(data, reqpool);
+}
+
+static
+apr_status_t spool_bucket_split(apr_bucket *a, apr_size_t point)
+{
+ apr_status_t rv = apr_bucket_shared_split(a, point);
+ a->type = &apr_bucket_type_file;
+ return rv;
+}
+
+static
+apr_status_t spool_bucket_copy(apr_bucket *e, apr_bucket **c)
+{
+ apr_status_t rv = apr_bucket_shared_copy(e, c);
+ (*c)->type = &apr_bucket_type_file;
+ return rv;
+}
+
+static const apr_bucket_type_t spool_bucket_type = {
+ "APREQ_SPOOL", 5, APR_BUCKET_DATA,
+ spool_bucket_destroy,
+ spool_bucket_read,
+ spool_bucket_setaside,
+ spool_bucket_split,
+ spool_bucket_copy,
+};
+
+APREQ_DECLARE(apr_file_t *)apreq_brigade_spoolfile(apr_bucket_brigade *bb)
+{
+ apr_bucket *last;
+
+ last = APR_BRIGADE_LAST(bb);
+ if (BUCKET_IS_SPOOL(last))
+ return ((apr_bucket_file *)last->data)->fd;
+
+ return NULL;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_brigade_concat(apr_pool_t *pool,
+ const char *temp_dir,
+ apr_size_t heap_limit,
+ apr_bucket_brigade *out,
+ apr_bucket_brigade *in)
+{
+ apr_status_t s;
+ apr_bucket_file *f;
+ apr_off_t wlen;
+ apr_file_t *file;
+ apr_off_t in_len, out_len;
+ apr_bucket *last_in, *last_out;
+
+ last_out = APR_BRIGADE_LAST(out);
+
+ if (APR_BUCKET_IS_EOS(last_out))
+ return APR_EOF;
+
+ s = apr_brigade_length(out, 0, &out_len);
+ if (s != APR_SUCCESS)
+ return s;
+
+ /* This cast, when out_len = -1, is intentional */
+ if ((apr_uint64_t)out_len < heap_limit) {
+
+ s = apr_brigade_length(in, 0, &in_len);
+ if (s != APR_SUCCESS)
+ return s;
+
+ /* This cast, when in_len = -1, is intentional */
+ if ((apr_uint64_t)in_len < heap_limit - (apr_uint64_t)out_len) {
+ APR_BRIGADE_CONCAT(out, in);
+ return APR_SUCCESS;
+ }
+ }
+
+ if (!BUCKET_IS_SPOOL(last_out)) {
+
+ s = apreq_file_mktemp(&file, pool, temp_dir);
+ if (s != APR_SUCCESS)
+ return s;
+
+ s = apreq_brigade_fwrite(file, &wlen, out);
+
+ if (s != APR_SUCCESS)
+ return s;
+
+ last_out = apr_bucket_file_create(file, wlen, 0,
+ out->p, out->bucket_alloc);
+ last_out->type = &spool_bucket_type;
+ APR_BRIGADE_INSERT_TAIL(out, last_out);
+ f = last_out->data;
+ }
+ else {
+ f = last_out->data;
+ /* Need to seek here, just in case our spool bucket
+ * was read from between apreq_brigade_concat calls.
+ */
+ wlen = last_out->start + last_out->length;
+ s = apr_file_seek(f->fd, APR_SET, &wlen);
+ if (s != APR_SUCCESS)
+ return s;
+ }
+
+ if (in == out)
+ return APR_SUCCESS;
+
+ last_in = APR_BRIGADE_LAST(in);
+
+ if (APR_BUCKET_IS_EOS(last_in))
+ APR_BUCKET_REMOVE(last_in);
+
+ s = apreq_brigade_fwrite(f->fd, &wlen, in);
+
+ if (s == APR_SUCCESS) {
+
+ /* We have to deal with the possibility that the new
+ * data may be too large to be represented by a single
+ * temp_file bucket.
+ */
+
+ while ((apr_uint64_t)wlen > FILE_BUCKET_LIMIT - last_out->length) {
+ apr_bucket *e;
+
+ apr_bucket_copy(last_out, &e);
+ e->length = 0;
+ e->start = last_out->start + FILE_BUCKET_LIMIT;
+ wlen -= FILE_BUCKET_LIMIT - last_out->length;
+ last_out->length = FILE_BUCKET_LIMIT;
+
+ /* Copying makes the bucket types exactly the
+ * opposite of what we need here.
+ */
+ last_out->type = &apr_bucket_type_file;
+ e->type = &spool_bucket_type;
+
+ APR_BRIGADE_INSERT_TAIL(out, e);
+ last_out = e;
+ }
+
+ last_out->length += wlen;
+
+ if (APR_BUCKET_IS_EOS(last_in))
+ APR_BRIGADE_INSERT_TAIL(out, last_in);
+
+ }
+ else if (APR_BUCKET_IS_EOS(last_in))
+ APR_BRIGADE_INSERT_TAIL(in, last_in);
+
+ apr_brigade_cleanup(in);
+ return s;
+}
+
+APREQ_DECLARE(apr_status_t) apreq_brigade_fwrite(apr_file_t *f,
+ apr_off_t *wlen,
+ apr_bucket_brigade *bb)
+{
+ struct iovec v[APREQ_DEFAULT_NELTS];
+ apr_status_t s;
+ apr_bucket *e, *first;
+ int n = 0;
+ apr_bucket_brigade *tmp = bb;
+ *wlen = 0;
+
+ if (BUCKET_IS_SPOOL(APR_BRIGADE_LAST(bb))) {
+ tmp = apr_brigade_create(bb->p, bb->bucket_alloc);
+
+ s = apreq_brigade_copy(tmp, bb);
+ if (s != APR_SUCCESS)
+ return s;
+ }
+
+ for (e = APR_BRIGADE_FIRST(tmp); e != APR_BRIGADE_SENTINEL(tmp);
+ e = APR_BUCKET_NEXT(e))
+ {
+ apr_size_t len;
+ if (n == APREQ_DEFAULT_NELTS) {
+ s = apreq_fwritev(f, v, &n, &len);
+ if (s != APR_SUCCESS)
+ return s;
+
+ if (tmp != bb) {
+ while ((first = APR_BRIGADE_FIRST(tmp)) != e)
+ apr_bucket_delete(first);
+ }
+
+ *wlen += len;
+ }
+ s = apr_bucket_read(e, (const char **)&(v[n].iov_base),
+ &len, APR_BLOCK_READ);
+ if (s != APR_SUCCESS)
+ return s;
+
+ v[n++].iov_len = len;
+ }
+
+ while (n > 0) {
+ apr_size_t len;
+ s = apreq_fwritev(f, v, &n, &len);
+ if (s != APR_SUCCESS)
+ return s;
+ *wlen += len;
+
+ if (tmp != bb) {
+ while ((first = APR_BRIGADE_FIRST(tmp)) != e)
+ apr_bucket_delete(first);
+ }
+ }
+ return APR_SUCCESS;
+}