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

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

    /*
        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)) != OK) {
    	    return rc;
    	}

    	if (data) {
    	    req->raw_post = (char*) data; /* Give people a way of getting at the raw data. */
    	    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);
}

