blob: 1cbda0d3d974d04b0944d425629752abc3258437 [file] [log] [blame]
/* apache_request.c -- Apache multipart form data handling */
/*
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 <errno.h>
#include <string.h>
#include <stdio.h>
#include <apr_lib.h>
#include <apr_strings.h>
#include "apache_request.h"
#include "apache_multipart_buffer.h"
int fill_buffer(multipart_buffer *self); /* needed for mozilla hack */
static void req_plustospace(char *str)
{
register int x;
for(x=0;str[x];x++) if(str[x] == '+') str[x] = ' ';
}
static int
util_read(ApacheRequest *req, const char **rbuf, int *rlen)
{
request_rec *r = req->r;
int rc = OK;
if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
return rc;
}
if (ap_should_client_block(r)) {
char buff[HUGE_STRING_LEN];
int len_read;
apr_off_t rpos;
apr_off_t rsize;
apr_off_t length = r->remaining;
rpos = 0;
if (length > req->post_max && req->post_max > 0) {
ap_log_rerror(REQ_ERROR,"entity too large (%d, max=%d)",
(int)length, req->post_max);
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
*rbuf = apr_pcalloc(r->pool, length + 1);
*rlen = length;
while ((len_read =
ap_get_client_block(r, buff, sizeof(buff))) > 0) {
if ((rpos + len_read) > length) {
rsize = length - rpos;
}
else {
rsize = len_read;
}
memcpy((char*)*rbuf + rpos, buff, rsize);
rpos += rsize;
}
}
return rc;
}
char *ApacheRequest_script_name(ApacheRequest *req)
{
request_rec *r = req->r;
char *tmp;
if (r->path_info && *r->path_info) {
int path_info_start = ap_find_path_info(r->uri, r->path_info);
tmp = (char*) apr_pstrndup(r->pool, r->uri, path_info_start);
}
else {
tmp = r->uri;
}
return tmp;
}
char *ApacheRequest_script_path(ApacheRequest *req)
{
return ap_make_dirstr_parent(req->r->pool, ApacheRequest_script_name(req));
}
const char *ApacheRequest_param(ApacheRequest *req, const char *key)
{
ApacheRequest_parse(req);
return (char*) apr_table_get(req->parms, key);
}
static int make_params(void *data, const char *key, const char *val)
{
//array_header *arr = (array_header *)data;
apr_array_header_t *arr = (apr_array_header_t *)data;
//*(char **)apr_push_array(arr) = (char *)val;
*(char **)apr_array_push(arr) = (char *)val;
return 1;
}
//array_header *ApacheRequest_params(ApacheRequest *req, const char *key)
apr_array_header_t *ApacheRequest_params(ApacheRequest *req, const char *key)
{
//array_header *values = ap_make_array(req->r->pool, 4, sizeof(char *));
apr_array_header_t *values = apr_array_make(req->r->pool, 4, sizeof(char *));
ApacheRequest_parse(req);
apr_table_do(make_params, (void*)values, req->parms, key, NULL);
return values;
}
char *ApacheRequest_params_as_string(ApacheRequest *req, const char *key)
{
char *retval = NULL;
//array_header *values = ApacheRequest_params(req, key);
apr_array_header_t *values = ApacheRequest_params(req, key);
int i;
for (i=0; i<values->nelts; i++) {
retval = (char*) apr_pstrcat(req->r->pool,
retval ? retval : "",
((char **)values->elts)[i],
(i == (values->nelts - 1)) ? NULL : ", ",
NULL);
}
return retval;
}
//table *ApacheRequest_query_params(ApacheRequest *req, ap_pool *p)
apr_table_t *ApacheRequest_query_params(ApacheRequest *req, /*ap_pool*/apr_pool_t *p)
{
//array_header *a = ap_palloc(p, sizeof *a);
//array_header *b = (array_header *)req->parms;
apr_array_header_t *a = apr_palloc(p, sizeof *a);
apr_array_header_t *b = (apr_array_header_t *)req->parms;
a->elts = b->elts;
a->nelts = req->nargs;
a->nalloc = a->nelts; /* COW hack: array push will induce copying */
//a->elt_size = sizeof(table_entry);
//return (table *)a;
a->elt_size = sizeof(apr_table_entry_t);
return (apr_table_t *)a;
}
apr_table_t *ApacheRequest_post_params(ApacheRequest *req, apr_pool_t *p)
{
apr_array_header_t *a = apr_palloc(p, sizeof *a);
apr_array_header_t *b = (apr_array_header_t *)req->parms;
a->elts = (void *)( (apr_table_entry_t *)b->elts + req->nargs );
a->nelts = b->nelts - req->nargs;
a->nalloc = a->nelts; /* COW hack: array push will induce copying */
a->elt_size = sizeof(apr_table_entry_t);
return (apr_table_t *)a;
}
ApacheUpload *ApacheUpload_new(ApacheRequest *req)
{
ApacheUpload *upload = (ApacheUpload *)
apr_pcalloc(req->r->pool, sizeof(ApacheUpload));
upload->next = NULL;
upload->name = NULL;
upload->info = NULL;
upload->fp = NULL;
upload->size = 0;
upload->req = req;
return upload;
}
ApacheUpload *ApacheUpload_find(ApacheUpload *upload, char *name)
{
ApacheUpload *uptr;
for (uptr = upload; uptr; uptr = uptr->next) {
if (strEQ(uptr->name, name)) {
return uptr;
}
}
return NULL;
}
ApacheRequest *ApacheRequest_new(apr_pool_t *pool)
{
ApacheRequest *req = (ApacheRequest *)
apr_pcalloc(pool, sizeof(ApacheRequest));
req->status = OK;
req->parms = apr_table_make(pool, DEFAULT_TABLE_NELTS);
req->upload = NULL;
req->post_max = -1;
req->disable_uploads = 0;
req->upload_hook = NULL;
req->hook_data = NULL;
req->temp_dir = NULL;
req->raw_post = NULL;
req->raw_length = 0;
req->parsed = 0;
req->r = NULL;
req->nargs = 0;
return req;
}
ApacheRequest *ApacheRequest_init(ApacheRequest* req, request_rec *r)
{
req->status = OK;
apr_table_clear(req->parms);
req->upload = NULL;
req->post_max = -1;
req->disable_uploads = 0;
req->upload_hook = NULL;
req->hook_data = NULL;
req->temp_dir = NULL;
req->raw_post = NULL;
req->raw_length = 0;
req->parsed = 0;
req->r = r;
req->nargs = 0;
return req;
}
static char x2c(const char *what)
{
register char digit;
#ifndef 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 /*CHARSET_EBCDIC*/
char xstr[5];
xstr[0]='0';
xstr[1]='x';
xstr[2]=what[0];
xstr[3]=what[1];
xstr[4]='\0';
digit = os_toebcdic[0xFF & ap_strtol(xstr, NULL, 16)];
#endif /*CHARSET_EBCDIC*/
return (digit);
}
static unsigned int utf8_convert(char *str) {
long x = 0;
int i = 0;
while (i < 4 ) {
if ( apr_isxdigit(str[i]) != 0 ) {
if( apr_isdigit(str[i]) != 0 ) {
x = x * 16 + str[i] - '0';
}
else {
str[i] = tolower( str[i] );
x = x * 16 + str[i] - 'a' + 10;
}
}
else {
return 0;
}
i++;
}
if(i < 3)
return 0;
return (x);
}
static int ap_unescape_url_u(char *url)
{
register int x, y, badesc, badpath;
badesc = 0;
badpath = 0;
for (x = 0, y = 0; url[y]; ++x, ++y) {
if (url[y] != '%'){
url[x] = url[y];
}
else {
if(url[y + 1] == 'u' || url[y + 1] == 'U'){
unsigned int c = utf8_convert(&url[y + 2]);
y += 5;
if(c < 0x80){
url[x] = c;
}
else if(c < 0x800) {
url[x] = 0xc0 | (c >> 6);
url[++x] = 0x80 | (c & 0x3f);
}
else if(c < 0x10000){
url[x] = (0xe0 | (c >> 12));
url[++x] = (0x80 | ((c >> 6) & 0x3f));
url[++x] = (0x80 | (c & 0x3f));
}
else if(c < 0x200000){
url[x] = 0xf0 | (c >> 18);
url[++x] = 0x80 | ((c >> 12) & 0x3f);
url[++x] = 0x80 | ((c >> 6) & 0x3f);
url[++x] = 0x80 | (c & 0x3f);
}
else if(c < 0x4000000){
url[x] = 0xf8 | (c >> 24);
url[++x] = 0x80 | ((c >> 18) & 0x3f);
url[++x] = 0x80 | ((c >> 12) & 0x3f);
url[++x] = 0x80 | ((c >> 6) & 0x3f);
url[++x] = 0x80 | (c & 0x3f);
}
else if(c < 0x8000000){
url[x] = 0xfe | (c >> 30);
url[++x] = 0x80 | ((c >> 24) & 0x3f);
url[++x] = 0x80 | ((c >> 18) & 0x3f);
url[++x] = 0x80 | ((c >> 12) & 0x3f);
url[++x] = 0x80 | ((c >> 6) & 0x3f);
url[++x] = 0x80 | (c & 0x3f);
}
}
else {
if (!apr_isxdigit(url[y + 1]) || !apr_isxdigit(url[y + 2])) {
badesc = 1;
url[x] = '%';
}
else {
url[x] = x2c(&url[y + 1]);
y += 2;
if (url[x] == '/' || url[x] == '\0')
badpath = 1;
}
}
}
}
url[x] = '\0';
if (badesc)
return HTTP_BAD_REQUEST;
else if (badpath)
return HTTP_NOT_FOUND;
else
return OK;
}
//static int urlword_dlm[] = {'&', ';', 0};
static char *my_urlword(apr_pool_t *p, const char **line)
{
char *res = NULL;
const char *pos = *line;
char ch;
while ( (ch = *pos) != '\0' && ch != ';' && ch != '&') {
++pos;
}
res = (char*) apr_pstrndup(p, *line, pos - *line);
while (ch == ';' || ch == '&') {
++pos;
ch = *pos;
}
*line = pos;
return res;
}
static void split_to_parms(ApacheRequest *req, const char *data)
{
request_rec *r = req->r;
const char *val;
while (*data && (val = my_urlword(r->pool, &data))) {
const char *key = ap_getword(r->pool, &val, '=');
req_plustospace((char*)key);
ap_unescape_url_u((char*)key);
req_plustospace((char*)val);
ap_unescape_url_u((char*)val);
apr_table_add(req->parms, key, val);
}
}
int ApacheRequest___parse(ApacheRequest *req)
{
request_rec *r = req->r;
const char *ct = apr_table_get(r->headers_in, "Content-type");
int result;
if (r->args) {
split_to_parms(req, r->args);
req->nargs = ((apr_array_header_t *)req->parms)->nelts;
}
if ((r->method_number == M_POST) && ct && strncaseEQ(ct, MULTIPART_ENCTYPE, MULTIPART_ENCTYPE_LENGTH))
{
//
//ap_log_rerror(REQ_INFO, "content-type: `%s'", ct);
//
result = ApacheRequest_parse_multipart(req,ct);
}
else
{
result = ApacheRequest_parse_urlencoded(req);
}
req->parsed = 1;
return result;
}
int ApacheRequest_parse_urlencoded(ApacheRequest *req)
{
request_rec *r = req->r;
int rc = OK;
if (r->method_number == M_POST || r->method_number == M_PUT || r->method_number == M_DELETE) {
const char *data = NULL;
int length = 0;
/*
const char *type;
type = apr_table_get(r->headers_in, "Content-Type");
if (!strncaseEQ(type, DEFAULT_ENCTYPE, DEFAULT_ENCTYPE_LENGTH) &&
!strncaseEQ(type, TEXT_XML_ENCTYPE, TEXT_XML_ENCTYPE_LENGTH)) {
return DECLINED;
}
*/
if ((rc = util_read(req, &data, &length)) != OK) {
return rc;
}
if (data) {
req->raw_post = (char*) data; /* Give people a way of getting at the raw data. */
req->raw_length = length;
split_to_parms(req, data);
}
}
return OK;
}
static apr_status_t remove_tmpfile(void *data)
{
ApacheUpload *upload = (ApacheUpload *) data;
// ApacheRequest *req = upload->req;
//TODO: fix ap_pfclose
//if( ap_pfclose(req->r->pool, upload->fp) )
//TODO: fix logging apr_log_rerror
//apr_log_rerror(REQ_ERROR,"[libapreq] close error on '%s'", upload->tempname);
#ifndef DEBUG
if( remove(upload->tempname) )
{
//TODO: fix logging apr_log_rerror
//apr_log_rerror(REQ_ERROR,"[libapreq] remove error on '%s'", upload->tempname);
}
#endif
// free(upload->tempname);
return 0;
}
apr_file_t *ApacheRequest_tmpfile(ApacheRequest *req, ApacheUpload *upload)
{
request_rec *r = req->r;
apr_file_t *fp = NULL;
char *name = NULL;
char *file = NULL ;
const char *tempdir;
apr_status_t rv;
tempdir = req->temp_dir;
/* file = (char *)apr_palloc(r->pool,sizeof(apr_time_t)); */
file = apr_psprintf(r->pool,"%u.XXXXXX", (unsigned int)r->request_time);
rv = apr_temp_dir_get(&tempdir,r->pool);
if (rv != APR_SUCCESS) {
ap_log_perror(APLOG_MARK, APLOG_ERR, rv, r->pool, "No temp dir!");
return NULL;
}
rv = apr_filepath_merge(&name,tempdir,file,APR_FILEPATH_NATIVE,r->pool);
if (rv != APR_SUCCESS) {
ap_log_perror(APLOG_MARK, APLOG_ERR, rv, r->pool, "File path error!");
return NULL;
}
rv = apr_file_mktemp(&fp,name,0,r->pool);
if (rv != APR_SUCCESS) {
char* errorBuffer = (char*) apr_palloc(r->pool,256);
ap_log_perror(APLOG_MARK, APLOG_ERR, rv, r->pool, "Failed to open temp file: %s",apr_strerror(rv,errorBuffer,256));
return NULL;
}
upload->fp = fp;
upload->tempname = name;
apr_pool_cleanup_register (r->pool, (void *)upload, remove_tmpfile, apr_pool_cleanup_null);
return fp;
}
int
ApacheRequest_parse_multipart(ApacheRequest *req,const char* ct)
{
request_rec* r = req->r;
int rc = OK;
apr_off_t length;
char* boundary;
multipart_buffer* mbuff;
ApacheUpload* upload = NULL;
apr_status_t status;
char error[1024];
if ((rc = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR))) {
return rc;
}
if (!ap_should_client_block(r)) {
return rc;
}
if ((length = r->remaining) > req->post_max && req->post_max > 0) {
ap_log_rerror(REQ_ERROR,"entity too large (%d, max=%d)",
(int)length,req->post_max);
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
do {
size_t blen;
boundary = ap_getword(r->pool, &ct, '=');
if (boundary == NULL)
return DECLINED;
blen = strlen(boundary);
if (blen == 0 || blen < strlen("boundary"))
return DECLINED;
boundary += blen - strlen("boundary");
} while (strcasecmp(boundary,"boundary") != 0);
boundary = ap_getword_conf(r->pool, &ct);
if (!(mbuff = multipart_buffer_new(boundary, length, r))) {
return DECLINED;
}
while (!multipart_buffer_eof(mbuff)) {
apr_table_t* header = (apr_table_t*) multipart_buffer_headers(mbuff);
const char* cd;
const char* param = NULL;
const char* filename=NULL;
char buff[FILLUNIT];
size_t blen;
if (!header) {
#ifdef DEBUG
ap_log_rerror(REQ_ERROR,"Silently dropping remaining '%ld' bytes", r->remaining);
#endif
do { } while ( ap_get_client_block(r, buff, sizeof(buff)) > 0 );
return OK;
}
if ((cd = apr_table_get(header, "Content-Disposition"))) {
const char *pair;
while (*cd && (pair = ap_getword(r->pool, &cd, ';'))) {
const char *key;
while (apr_isspace(*cd)) {
++cd;
}
if (ap_ind(pair, '=')) {
key = ap_getword(r->pool, &pair, '=');
if(strcaseEQ(key, "name")) {
param = ap_getword_conf(r->pool, &pair);
}
else if(strcaseEQ(key, "filename")) {
filename = ap_getword_conf(r->pool, &pair);
}
}
}
if (!filename) {
char *value = multipart_buffer_read_body(mbuff);
apr_table_add(req->parms, param, value);
continue;
}
if (!param) continue; /* shouldn't happen, but just in case. */
if (req->disable_uploads) {
#if DEBUG
ap_log_rerror(REQ_ERROR, "[libapreq] file upload forbidden");
#endif
return HTTP_FORBIDDEN;
}
apr_table_add(req->parms, param, filename);
if (upload) {
upload->next = ApacheUpload_new(req);
upload = upload->next;
}
else {
upload = ApacheUpload_new(req);
req->upload = upload;
}
if (! req->upload_hook && ! ApacheRequest_tmpfile(req, upload) ) {
return HTTP_INTERNAL_SERVER_ERROR;
}
upload->info = header;
upload->filename = (char*)apr_pstrdup(req->r->pool, filename);
upload->name = (char*)apr_pstrdup(req->r->pool, param);
/* mozilla empty-file (missing CRLF) hack */
fill_buffer(mbuff);
if( strEQN(mbuff->buf_begin, mbuff->boundary,
strlen(mbuff->boundary)) ) {
r->remaining -= 2;
continue;
}
while ((blen = multipart_buffer_read(mbuff, buff, sizeof(buff)))) {
apr_size_t bytes_to_write = (apr_size_t) blen;
status = apr_file_write(upload->fp,buff,&bytes_to_write);
if (status != 0) {
apr_strerror(status,error,1024);
return HTTP_INTERNAL_SERVER_ERROR;
}
upload->size += blen;
}
}
}
return OK;
}
#define Mult_s 1
#define Mult_m 60
#define Mult_h (60*60)
#define Mult_d (60*60*24)
#define Mult_M (60*60*24*30)
#define Mult_y (60*60*24*365)
static int expire_mult(char s)
{
switch (s) {
case 's':
return Mult_s;
case 'm':
return Mult_m;
case 'h':
return Mult_h;
case 'd':
return Mult_d;
case 'M':
return Mult_M;
case 'y':
return Mult_y;
default:
return 1;
};
}
static time_t expire_calc(char *time_str)
{
int is_neg = 0, offset = 0;
char buf[256];
int ix = 0;
if (*time_str == '-') {
is_neg = 1;
++time_str;
}
else if (*time_str == '+') {
++time_str;
}
else if (strcaseEQ(time_str, "now")) {
/*ok*/
}
else {
return 0;
}
/* wtf, ap_isdigit() returns false for '1' !? */
while (*time_str && (apr_isdigit(*time_str) || (*time_str == '1'))) {
buf[ix++] = *time_str++;
}
buf[ix] = '\0';
offset = atoi(buf);
return time(NULL) +
(expire_mult(*time_str) * (is_neg ? (0 - offset) : offset));
}
char *ApacheUtil_expires(apr_pool_t *p, char *time_str, int type)
{
time_t when;
struct tm *tms;
int sep = (type == EXPIRES_HTTP) ? ' ' : '-';
if (!time_str) {
return NULL;
}
when = expire_calc(time_str);
if (!when) {
return (char*) apr_pstrdup(p, time_str);
}
tms = gmtime(&when);
return (char*) 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);
}
char *ApacheRequest_expires(ApacheRequest *req, char *time_str)
{
return ApacheUtil_expires(req->r->pool, time_str, EXPIRES_HTTP);
}
char *ApacheRequest_get_raw_post(ApacheRequest *req, int *len)
{
if (len)
*len = req->raw_length;
return req->raw_post;
}