blob: 0a1efc5c2c18115dc1ad876cecbf10edc0ed06f3 [file] [log] [blame]
/*
** Copyright 2003-2004 The Apache Software Foundation
**
** Licensed 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.h"
#include "apreq_env.h"
#include "apr_time.h"
#include "apr_strings.h"
#include "apr_lib.h"
#define MIN(a,b) ( (a) < (b) ? (a) : (b) )
#define MAX(a,b) ( (a) > (b) ? (a) : (b) )
APREQ_DECLARE(apreq_value_t *)apreq_make_value(apr_pool_t *p,
const char *name,
const apr_size_t nlen,
const char *val,
const apr_size_t vlen)
{
apreq_value_t *v = apr_palloc(p, vlen + nlen + 1 + sizeof *v);
if (v == NULL)
return NULL;
v->size = vlen;
memcpy(v->data, val, vlen);
v->data[vlen] = 0;
v->name = v->data + vlen + 1;
memcpy((char *)v->name, name, nlen);
((char *)v->name)[nlen] = 0;
v->flags = 0;
return v;
}
APREQ_DECLARE(apreq_value_t *)apreq_copy_value(apr_pool_t *p,
const apreq_value_t *val)
{
apreq_value_t *v;
if (val == NULL)
return NULL;
v = apr_palloc(p, val->size + sizeof *v);
*v = *val;
if ( v->size > 0 )
memcpy(v->data, val->data, v->size);
return v;
}
apreq_value_t * apreq_merge_values(apr_pool_t *p,
const apr_array_header_t *arr)
{
apreq_value_t *a = *(apreq_value_t **)(arr->elts);
apreq_value_t *v = apreq_char_to_value(
apreq_join(p, ", ", arr, APREQ_JOIN_AS_IS) );
if (arr->nelts > 0)
v->name = a->name;
return v;
}
APREQ_DECLARE(const char *)apreq_enctype(void *env)
{
char *enctype;
const char *ct = apreq_env_content_type(env);
if (ct == NULL)
return NULL;
else {
const char *semicolon = strchr(ct, ';');
if (semicolon) {
enctype = apr_pstrdup(apreq_env_pool(env), ct);
enctype[semicolon - ct] = 0;
return enctype;
}
else
return ct;
}
}
APREQ_DECLARE(char *) apreq_expires(apr_pool_t *p, const char *time_str,
const apreq_expires_t type)
{
apr_time_t when;
apr_time_exp_t tms;
char sep = (type == APREQ_EXPIRES_HTTP) ? ' ' : '-';
if (time_str == NULL) {
return NULL;
}
when = apr_time_now();
if (strcasecmp(time_str,"now"))
when += apr_time_from_sec(apreq_atoi64t(time_str));
if ( apr_time_exp_gmt(&tms, when) != APR_SUCCESS )
return NULL;
return apr_psprintf(p,
"%s, %.2d%c%s%c%.2d %.2d:%.2d:%.2d GMT",
apr_day_snames[tms.tm_wday],
tms.tm_mday, sep, apr_month_snames[tms.tm_mon], sep,
tms.tm_year + 1900,
tms.tm_hour, tms.tm_min, tms.tm_sec);
}
/* used for specifying file & byte 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 (apr_tolower(*p)) {
case 'g': return n * 1024*1024*1024;
case 'm': return n * 1024*1024;
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;
}
/*
search for a string in a fixed-length byte string.
if partial is true, partial matches are allowed at the end of the buffer.
returns NULL if not found, or a pointer to the start of the first match.
*/
/* XXX: should we drop this and replace it with apreq_index ? */
APREQ_DECLARE(char *) apreq_memmem(char* hay, apr_size_t hlen,
const char* ndl, apr_size_t nlen,
const apreq_match_t type)
{
apr_size_t len = hlen;
char *end = hay + hlen;
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;
}
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 char x2c(const char *what)
{
register 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);
}
static APR_INLINE unsigned int x2ui(const char *what) {
register unsigned int 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);
}
APR_INLINE
static apr_status_t url_decode(char *d, apr_size_t *dlen,
const char *s, apr_size_t *slen)
{
const char *src = s;
char *start = d;
const char *end = s + *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 = x2c(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]))
{
unsigned int c = x2ui(s+2);
if (c < 0x80) {
*d = c;
}
else if (c < 0x800) {
*d++ = 0xc0 | (c >> 6);
*d = 0x80 | (c & 0x3f);
}
else if (c < 0x10000) {
*d++ = 0xe0 | (c >> 12);
*d++ = 0x80 | ((c >> 6) & 0x3f);
*d = 0x80 | (c & 0x3f);
}
else if (c < 0x200000) {
*d++ = 0xf0 | (c >> 18);
*d++ = 0x80 | ((c >> 12) & 0x3f);
*d++ = 0x80 | ((c >> 6) & 0x3f);
*d = 0x80 | (c & 0x3f);
}
else if (c < 0x4000000) {
*d++ = 0xf8 | (c >> 24);
*d++ = 0x80 | ((c >> 18) & 0x3f);
*d++ = 0x80 | ((c >> 12) & 0x3f);
*d++ = 0x80 | ((c >> 6) & 0x3f);
*d = 0x80 | (c & 0x3f);
}
else if (c < 0x8000000) {
*d++ = 0xfe | (c >> 30);
*d++ = 0x80 | ((c >> 24) & 0x3f);
*d++ = 0x80 | ((c >> 18) & 0x3f);
*d++ = 0x80 | ((c >> 12) & 0x3f);
*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 && s[1] != 'u' && s[1] != 'U')) {
*d = 0;
return APR_EGENERAL;
}
memcpy(d, s, end - s);
d[end - s] = 0;
return APR_INCOMPLETE;
}
break;
case 0:
*d = 0;
*dlen = d - start;
*slen = s - src;
return APR_BADCH;
default:
*d = *s;
}
}
*d = 0;
*dlen = d - start;
*slen = s - src;
return APR_SUCCESS;
}
APREQ_DECLARE(apr_ssize_t) apreq_decode(char *d, const char *s,
apr_size_t slen)
{
apr_size_t dlen;
const char *end = s + slen;
if (s == NULL || d == NULL)
return -1;
if (s == (const char *)d) { /* optimize for src = dest case */
for ( ; s < end; ++s) {
if (*s == '%' || *s == '+')
break;
else if (*s == 0)
return s - (const char *)d;
}
d = (char *)s;
}
switch (url_decode(d, &dlen, s, &slen)) {
case APR_SUCCESS:
return dlen;
case APR_INCOMPLETE:
return -2;
default:
return -1;
}
}
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;
for (n = 0; n < nelts; ++n) {
apr_size_t slen, len;
start_decodev:
slen = v[n].iov_len;
switch (status = url_decode(d,&len,v[n].iov_base, &slen)) {
case APR_SUCCESS:
d += len;
*dlen += len;
continue;
case APR_INCOMPLETE:
d += len;
*dlen += len;
if (++n == nelts)
return APR_INCOMPLETE;
len = v[n-1].iov_len - slen;
memcpy(d + len, v[n].iov_base, v[n].iov_len);
v[n].iov_len += len;
v[n].iov_base = d;
goto start_decodev;
default:
*dlen = len;
return status;
}
}
return APR_SUCCESS;
}
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 c;
for ( ;s < (const unsigned char *)src + slen; ++s) {
c = *s;
if ( apr_isalnum(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;
}
APREQ_DECLARE(apr_size_t) apreq_quote_once(char *dest, const char *src,
const apr_size_t slen)
{
if (slen > 1 && src[0] == '"' && src[slen-1] == '"') {
/* looks like src is already quoted */
memcpy(dest, src, slen);
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 '\\':
if (s < last) {
*d++ = *s++;
break;
}
/* else fall through */
case '"':
*d++ = '\\';
}
*d++ = *s++;
}
*d++ = '"';
*d = 0;
return d - dest;
}
APREQ_DECLARE(const char *) apreq_join(apr_pool_t *p,
const char *sep,
const apr_array_header_t *arr,
apreq_join_t mode)
{
apr_ssize_t len, slen;
apreq_value_t *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 NULL;
for (j=0, len=0; j < n; ++j)
len += a[j]->size + slen;
/* Allocated the required space */
switch (mode) {
case APREQ_JOIN_ENCODE:
len += 2 * len;
break;
case APREQ_JOIN_QUOTE:
len = 2 * (len + n);
break;
default:
/* nothing special required, just here to keep noisy compilers happy */
break;
}
rv = apr_palloc(p, len + sizeof *rv);
rv->name = 0;
rv->size = 0;
rv->data[0] = 0;
if (n == 0)
return rv->data;
/* Pass two --- copy the argument strings into the result space */
d = rv->data;
switch (mode) {
case APREQ_JOIN_ENCODE:
d += apreq_encode(d, a[0]->data, a[0]->size);
for (j = 1; j < n; ++j) {
memcpy(d, sep, slen);
d += slen;
d += apreq_encode(d, a[j]->data, a[j]->size);
}
break;
case APREQ_JOIN_DECODE:
len = apreq_decode(d, a[0]->data, a[0]->size);
if (len < 0)
return NULL;
else
d += len;
for (j = 1; j < n; ++j) {
memcpy(d, sep, slen);
d += slen;
len = apreq_decode(d, a[j]->data, a[j]->size);
if (len < 0)
return NULL;
else
d += len;
}
break;
case APREQ_JOIN_QUOTE:
d += apreq_quote_once(d, a[0]->data, a[0]->size);
for (j = 1; j < n; ++j) {
memcpy(d, sep, slen);
d += slen;
d += apreq_quote_once(d, a[j]->data, a[j]->size);
}
break;
case APREQ_JOIN_AS_IS:
memcpy(d,a[0]->data,a[0]->size);
d += a[0]->size;
for (j = 1; j < n ; ++j) {
memcpy(d, sep, slen);
d += slen;
memcpy(d, a[j]->data, a[j]->size);
d += a[j]->size;
}
break;
default:
break;
}
*d = 0;
rv->size = d - rv->data;
return rv->data;
}
APREQ_DECLARE(char *) apreq_escape(apr_pool_t *p,
const char *src, const apr_size_t slen)
{
apreq_value_t *rv;
if (src == NULL)
return NULL;
rv = apr_palloc(p, 3 * slen + sizeof *rv);
rv->name = NULL;
rv->size = apreq_encode(rv->data, src, slen);
return rv->data;
}
APREQ_DECLARE(apr_ssize_t) apreq_unescape(char *str)
{
return apreq_decode(str,str,strlen(str));
}
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, bytes_avail = 0;
int n = *nelts;
apr_status_t s = apr_file_writev(f, v, n, &len);
*bytes_written = len;
if (s != APR_SUCCESS)
return s;
while (--n >= 0)
bytes_avail += v[n].iov_len;
if (bytes_avail > len) {
/* incomplete write: must shift v */
n = 0;
while (v[n].iov_len <= len) {
len -= v[n].iov_len;
++n;
}
v[n].iov_len -= len;
v[n].iov_base = (char *)(v[n].iov_base) + len;
if (n > 0) {
struct iovec *dest = v;
do {
*dest++ = v[n++];
} while (n < *nelts);
*nelts = dest - v;
}
else {
s = apreq_fwritev(f, v, nelts, &len);
*bytes_written += len;
}
}
else
*nelts = 0;
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_NELTS];
apr_status_t s;
apr_bucket *e;
int n = 0;
*wlen = 0;
for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb);
e = APR_BUCKET_NEXT(e))
{
apr_size_t len;
if (n == APREQ_NELTS) {
s = apreq_fwritev(f, v, &n, &len);
if (s != APR_SUCCESS)
return s;
*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;
}
return APR_SUCCESS;
}
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;
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);
rc = apr_file_mktemp(fp, tmpl, /* NO APR_DELONCLOSE! see comment above */
APR_CREATE | APR_READ | APR_WRITE
| APR_EXCL | APR_BINARY, 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;
}
APREQ_DECLARE(apr_file_t *)apreq_brigade_spoolfile(apr_bucket_brigade *bb)
{
apr_bucket *last = APR_BRIGADE_LAST(bb);
apr_bucket_file *f;
if (APR_BUCKET_IS_FILE(last)) {
f = last->data;
return f->fd;
}
else
return NULL;
}
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;
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;
/* value is inside quotes */
for (*val = v; *v; ++v) {
switch (*v) {
case '"':
goto finish;
case '\\':
if (v[1] != 0)
++v;
default:
break;
}
}
/* bad attr: no terminating quote found */
return APR_EGENERAL;
}
else {
/* value is not wrapped in quotes */
for (*val = v; *v; ++v) {
switch (*v) {
case ' ':
case ';':
case ',':
case '\t':
case '\r':
case '\n':
goto finish;
default:
break;
}
}
}
finish:
if (strncasecmp(key, name, nlen) == 0) {
*vlen = v - *val;
if (key > hdr) {
/* ensure preceding character isn't a token, per rfc2616, s2.2 */
switch (key[-1]) {
case '(': case ')': case '<': case '>': case '@':
case ',': case ';': case ':': case '\\': case '"':
case '/': case '[': case ']': case '?': case '=':
case '{': case '}': case ' ': case '\t':
return APR_SUCCESS;
default:
if (apr_iscntrl(key[-1]))
return APR_SUCCESS;
}
}
else
return APR_SUCCESS;
}
hdr = v;
}
return APR_NOTFOUND;
}