/* ====================================================================
 * 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 "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;
}

int ApacheRequest___parse(ApacheRequest *req)
{
    request_rec *r = req->r;

    req->parsed = 1;

    if (r->method_number == M_POST) { 
	const char *ct = ap_table_get(r->headers_in, "Content-type"); 
	if (ct && *ct == 'a' && strEQ(ct, DEFAULT_ENCTYPE)) {
	    return ApacheRequest_parse_urlencoded(req); 
	}
	else if (ct && *ct == 'm' && strstr(ct, "multipart/form-data")) {
	   return ApacheRequest_parse_multipart(req); 
	}
	else {
	    ap_log_rerror(REQ_ERROR, 
			  "[libapreq] unknown content-type: `%s'", ct); 
	    return HTTP_INTERNAL_SERVER_ERROR;
	}
    } 
    else {
	return ApacheRequest_parse_urlencoded(req); 
    }
}

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->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_urlencoded(ApacheRequest *req)
{
    request_rec *r = req->r; 
    int rc = OK;

    if (r->method_number == M_POST) { 
	const char *data, *type;

	type = ap_table_get(r->headers_in, "Content-Type");

	if (!strcaseEQ(type, DEFAULT_ENCTYPE)) {
	    return DECLINED;
	}
	if ((rc = util_read(req, &data)) != OK) {
	    return rc;
	}
	if (data) {
	    split_to_parms(req, data);
	}
    }

    if (r->args) {
	split_to_parms(req, r->args);
    }        

    return OK;
}

FILE *ApacheRequest_tmpfile(ApacheRequest *req)
{
    request_rec *r = req->r;
    FILE *fp;

    if (!(fp = tmpfile())) {
	ap_log_rerror(REQ_ERROR,
		      "[libapreq] could not create tmpfile()"); 
    }
    else {
	ap_note_cleanups_for_file(r->pool, fp);
    }

    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, *buff;
	int blen;

	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;
	    }
	    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 (!(upload->fp = ApacheRequest_tmpfile(req))) {
		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 ((buff = multipart_buffer_read(mbuff, 0, &blen))) {
		/* write the file */
		upload->size += fwrite(buff, 1, blen, upload->fp); 	
	    }

	    if (upload->size > 0) {
		fseek(upload->fp, 0, 0);
	    }
	    else {
		upload->fp = NULL;
	    }
	}
    }

    if (r->args) {
	split_to_parms(req, r->args);
    }        

    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);
}
