| /* |
| ** 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; |
| } |
| |
| |
| #define IS_SPACE_CHAR(c) ((c) == '\t' || (c) == ' ') |
| #define IS_TOKEN_CHAR(c) (apr_isalnum(c) \ |
| || ((c) && strchr("!#$%&'*+-.^_`|~", (c)))) |
| |
| 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) |
| { |
| int done = 0; |
| |
| if (!nlen) |
| return APREQ_ERROR_NOATTR; |
| |
| do { |
| const char *hde, *v; |
| apr_size_t tail = 0; |
| |
| /* Parse the name => [hdr:hde[ */ |
| hde = hdr; |
| look_for_end_name: |
| switch (*hde) { |
| case 0: |
| case '\r': |
| case '\n': |
| done = 1; |
| case '=': |
| case ';': |
| case ',': |
| v = hde; |
| hde -= tail; |
| break; |
| case ' ': |
| case '\t': |
| if (hde == hdr) |
| ++hdr; |
| else |
| ++tail; |
| ++hde; |
| goto look_for_end_name; |
| default: |
| /* The name is a token */ |
| if (!IS_TOKEN_CHAR(*hde)) |
| return APREQ_ERROR_BADCHAR; |
| /* Nothing after the tail */ |
| if (tail) |
| return APREQ_ERROR_BADATTR; |
| ++hde; |
| goto look_for_end_name; |
| } |
| |
| /* Parse the value => (*val, *vlen) */ |
| if (*v == '=') { |
| if (hde == hdr) { |
| /* The name can't be empty */ |
| return APREQ_ERROR_BADATTR; |
| } |
| |
| ++v; |
| while (IS_SPACE_CHAR(*v)) |
| ++v; |
| |
| /* Quoted string ? */ |
| if (*v == '"') { |
| *val = ++v; |
| |
| /* XXX: the interface does not permit unescaping, |
| * it should have pool to allocate from. |
| * The caller can't know whether a returned '\\' is |
| * a quoted-char or not.. |
| */ |
| look_for_end_quote: |
| switch (*v) { |
| case 0: |
| case '\r': |
| case '\n': |
| return APREQ_ERROR_BADSEQ; |
| case '"': |
| *vlen = v - *val; |
| break; |
| case '\\': |
| if (v[1] != 0) |
| ++v; |
| ++v; |
| goto look_for_end_quote; |
| default: |
| if (apr_iscntrl(*v)) |
| return APREQ_ERROR_BADCHAR; |
| ++v; |
| goto look_for_end_quote; |
| } |
| |
| look_for_after_quote: |
| switch (*v) { |
| case 0: |
| case '\r': |
| case '\n': |
| done = 1; |
| case ';': |
| case ',': |
| break; |
| case ' ': |
| case '\t': |
| goto look_for_after_quote; |
| default: |
| if (apr_iscntrl(*v)) |
| return APREQ_ERROR_BADCHAR; |
| return APREQ_ERROR_BADSEQ; |
| } |
| } |
| else { |
| *val = v; |
| tail = 0; |
| |
| look_for_end_value: |
| switch (*v) { |
| case 0: |
| case '\r': |
| case '\n': |
| done = 1; |
| case ';': |
| case ',': |
| *vlen = v - *val - tail; |
| break; |
| case ' ': |
| case '\t': |
| if (*val == v) |
| ++*val; |
| else |
| ++tail; |
| ++v; |
| goto look_for_end_value; |
| default: |
| if (apr_iscntrl(*v)) |
| return APREQ_ERROR_BADCHAR; |
| ++v; |
| tail = 0; |
| goto look_for_end_value; |
| } |
| } |
| } |
| else { |
| *val = NULL; |
| *vlen = 0; |
| } |
| |
| if (hdr + nlen == hde && strncasecmp(hdr, name, nlen) == 0) { |
| return APR_SUCCESS; |
| } |
| |
| hdr = v + 1; |
| } while (!done); |
| |
| 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; |
| |
| apr_brigade_cleanup(out); |
| last_out = apr_bucket_file_create(file, 0, wlen, |
| 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; |
| } |