blob: 7bfc250d3f083e87fa47ca98ff8cdd96d41f5a02 [file] [log] [blame]
/* ====================================================================
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see <http://www.apache.org/>.
*
*/
#include "apache_request.h"
#include "apache_multipart_buffer.h"
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)
{
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 rsize, len_read, rpos=0;
long length = r->remaining;
if (length > req->post_max && req->post_max > 0) {
ap_log_rerror(REQ_ERROR, "[libapreq] entity too large (%d, max=%d)",
(int)length, req->post_max);
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
*rbuf = ap_pcalloc(r->pool, length + 1);
ap_hard_timeout("[libapreq] util_read", r);
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;
}
ap_kill_timeout(r);
}
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 = ap_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 ap_table_get(req->parms, key);
}
static int make_params(void *data, const char *key, const char *val)
{
array_header *arr = (array_header *)data;
*(char **)ap_push_array(arr) = (char *)val;
return 1;
}
array_header *ApacheRequest_params(ApacheRequest *req, const char *key)
{
array_header *values = ap_make_array(req->r->pool, 4, sizeof(char *));
ApacheRequest_parse(req);
ap_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);
int i;
for (i=0; i<values->nelts; i++) {
retval = ap_pstrcat(req->r->pool,
retval ? retval : "",
((char **)values->elts)[i],
(i == (values->nelts - 1)) ? NULL : ", ",
NULL);
}
return retval;
}
ApacheUpload *ApacheUpload_new(ApacheRequest *req)
{
ApacheUpload *upload = (ApacheUpload *)
ap_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(request_rec *r)
{
ApacheRequest *req = (ApacheRequest *)
ap_pcalloc(r->pool, sizeof(ApacheRequest));
req->status = OK;
req->parms = ap_make_table(r->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->parsed = 0;
req->r = r;
return req;
}
static int urlword_dlm[] = {'&', ';', 0};
static char *my_urlword(pool *p, const char **line)
{
int i;
for (i = 0; urlword_dlm[i]; i++) {
int stop = urlword_dlm[i];
char *pos = strchr(*line, stop);
char *res;
if (!pos) {
if (!urlword_dlm[i+1]) {
int len = strlen(*line);
res = ap_pstrndup(p, *line, len);
*line += len;
return res;
}
continue;
}
res = ap_pstrndup(p, *line, pos - *line);
while (*pos == stop) {
++pos;
}
*line = pos;
return res;
}
return NULL;
}
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((char*)key);
req_plustospace((char*)val);
ap_unescape_url((char*)val);
ap_table_add(req->parms, key, val);
}
}
int ApacheRequest___parse(ApacheRequest *req)
{
request_rec *r = req->r;
int result;
if (r->args) {
split_to_parms(req, r->args);
}
if (r->method_number == M_POST) {
const char *ct = ap_table_get(r->headers_in, "Content-type");
if (ct && strncaseEQ(ct, DEFAULT_ENCTYPE, DEFAULT_ENCTYPE_LENGTH)) {
result = ApacheRequest_parse_urlencoded(req);
}
else if (ct && strncaseEQ(ct, MULTIPART_ENCTYPE, MULTIPART_ENCTYPE_LENGTH)) {
result = ApacheRequest_parse_multipart(req);
}
else {
ap_log_rerror(REQ_ERROR,
"[libapreq] unknown content-type: `%s'", ct);
result = HTTP_INTERNAL_SERVER_ERROR;
}
}
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) {
const char *data = NULL, *type;
type = ap_table_get(r->headers_in, "Content-Type");
if (!strncaseEQ(type, DEFAULT_ENCTYPE, DEFAULT_ENCTYPE_LENGTH)) {
return DECLINED;
}
if ((rc = util_read(req, &data)) != OK) {
return rc;
}
if (data) {
split_to_parms(req, data);
}
}
return OK;
}
static void remove_tmpfile(void *data) {
ApacheUpload *upload = (ApacheUpload *) data;
ApacheRequest *req = upload->req;
if( ap_pfclose(req->r->pool, upload->fp) )
ap_log_rerror(REQ_ERROR,
"[libapreq] close error on '%s'", upload->tempname);
#ifndef DEBUG
if( remove(upload->tempname) )
ap_log_rerror(REQ_ERROR,
"[libapreq] remove error on '%s'", upload->tempname);
#endif
free(upload->tempname);
}
FILE *ApacheRequest_tmpfile(ApacheRequest *req, ApacheUpload *upload)
{
request_rec *r = req->r;
FILE *fp;
char prefix[] = "apreq";
char *name = NULL;
int fd = 0;
int tries = 100;
while (--tries > 0) {
if ( (name = tempnam(req->temp_dir, prefix)) == NULL )
continue;
fd = ap_popenf(r->pool, name, O_CREAT|O_EXCL|O_RDWR|O_BINARY, 0600);
if ( fd >= 0 )
break; /* success */
else
free(name);
}
if ( tries == 0 || (fp = ap_pfdopen(r->pool, fd, "w+" "b") ) == NULL ) {
ap_log_rerror(REQ_ERROR, "[libapreq] could not create/open temp file");
if ( fd >= 0 ) { remove(name); free(name); }
return NULL;
}
upload->fp = fp;
upload->tempname = name;
ap_register_cleanup(r->pool, (void *)upload,
remove_tmpfile, ap_null_cleanup);
return fp;
}
int ApacheRequest_parse_multipart(ApacheRequest *req)
{
request_rec *r = req->r;
int rc = OK;
const char *ct = ap_table_get(r->headers_in, "Content-Type");
long length;
char *boundary;
multipart_buffer *mbuff;
ApacheUpload *upload = NULL;
if (req->disable_uploads) {
ap_log_rerror(REQ_ERROR, "[libapreq] file upload forbidden");
return HTTP_FORBIDDEN;
}
if (!ct) {
ap_log_rerror(REQ_ERROR, "[libapreq] no Content-type header!");
return HTTP_INTERNAL_SERVER_ERROR;
}
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, "[libapreq] entity too large (%d, max=%d)",
(int)length, req->post_max);
return HTTP_REQUEST_ENTITY_TOO_LARGE;
}
(void)ap_getword(r->pool, &ct, '=');
boundary = ap_getword_conf(r->pool, &ct);
if (!(mbuff = multipart_buffer_new(boundary, length, r))) {
return DECLINED;
}
while (!multipart_buffer_eof(mbuff)) {
table *header = multipart_buffer_headers(mbuff);
const char *cd, *param=NULL, *filename=NULL;
char buff[FILLUNIT];
int blen, wlen;
if (!header) {
return OK;
}
if ((cd = ap_table_get(header, "Content-Disposition"))) {
const char *pair;
while (*cd && (pair = ap_getword(r->pool, &cd, ';'))) {
const char *key;
while (ap_isspace(*cd)) {
++cd;
}
if (ap_ind(pair, '=')) {
key = ap_getword(r->pool, &pair, '=');
if(strEQ(key, "name")) {
param = ap_getword_conf(r->pool, &pair);
}
else if(strEQ(key, "filename")) {
filename = ap_getword_conf(r->pool, &pair);
}
}
}
if (!filename) {
char *value = multipart_buffer_read_body(mbuff);
ap_table_add(req->parms, param, value);
continue;
}
if (!param) continue; /* shouldn't happen, but just in case. */
ap_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 = ap_pstrdup(req->r->pool, filename);
upload->name = ap_pstrdup(req->r->pool, param);
while ((blen = multipart_buffer_read(mbuff, buff, sizeof(buff)))) {
if (req->upload_hook != NULL) {
wlen = req->upload_hook(req->hook_data, buff, blen, upload);
} else {
wlen = fwrite(buff, 1, blen, upload->fp);
}
if (wlen != blen) {
return HTTP_INTERNAL_SERVER_ERROR;
}
upload->size += wlen;
}
if (upload->size > 0 && (upload->fp != NULL)) {
fseek(upload->fp, 0, 0);
}
}
}
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 && (ap_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(pool *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 ap_pstrdup(p, time_str);
}
tms = gmtime(&when);
return ap_psprintf(p,
"%s, %.2d%c%s%c%.2d %.2d:%.2d:%.2d GMT",
ap_day_snames[tms->tm_wday],
tms->tm_mday, sep, ap_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);
}