/* ====================================================================
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2000 The Apache Software Foundation.  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. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Apache" and "Apache Software Foundation" 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 name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``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 SOFTWARE FOUNDATION 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 Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * Portions of this software are based upon public domain software
 * originally written at the National Center for Supercomputing Applications,
 * University of Illinois, Urbana-Champaign.
 */

#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)
{
    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 (!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) {
#ifdef DEBUG
            ap_log_rerror(REQ_ERROR,
		      "[libapreq] silently drop remaining '%ld' bytes", r->remaining);
#endif
            ap_hard_timeout("[libapreq] parse_multipart", r);
            while ( ap_get_client_block(r, buff, sizeof(buff)) > 0 )
                /* wait for more input to ignore */ ;
            ap_kill_timeout(r);
	    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. */

            if (req->disable_uploads) {
                ap_log_rerror(REQ_ERROR, "[libapreq] file upload forbidden");
                return HTTP_FORBIDDEN;
            }

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

            /* 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)))) {
		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);
}