/*******************************************************************************/
/*                                                                             */
/*  Copyright 2005 Pascal Gloor <pascal.gloor@spale.com>                       */
/*                                                                             */
/*  Licensed 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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <assert.h>
#include <errno.h>

#ifndef __FILE__
#define __FILE__ unknown_file
#endif

#ifndef __LINE__
#define __LINE__ unknown_line
#endif

#include "user.h"
#include "internal.h"
#include "memmem.h"
#include "hexval.h"
#include "errors.h"

void __parsepost(CGI_CTX *ctx)
{
	static char *multipart  = "multipart/form-data";
	static char *multibound = "multipart/form-data; boundary=";
	char *ctype, *cur, *cl;
	size_t clen,pos=0;

	assert(ctx!=NULL);

	if ( ( cl = getenv("CONTENT_LENGTH") ) == NULL )
		return;

	if ( ( ctype = getenv("CONTENT_TYPE") ) == NULL )
		return;

	if ( ( ctx->ct = malloc(strlen(ctype)+1) ) == NULL )
		Error(__FILE__, __LINE__, 1, "malloc failed");

	strcpy(ctx->ct, ctype);

	if ( memcmp(ctype,multibound,strlen(multibound)) == 0 )
	{
		char *boundary = ctx->ct + strlen(multibound);

		/* add '--' before the boundary */
		boundary--;
		*boundary = '-';
		boundary--;
		*boundary = '-';

		ctx->boundary = boundary;

		*(ctx->ct + strlen(multipart)) = '\0';
	}

	ctx->ctype = ctype;

	clen = atol(cl);

	if ( clen < 1 || clen > ctx->max_len )
	{
		Error(__FILE__, __LINE__, 0, "Invalid CONTENT_LENGTH too big or lower than 1");
		return;
	}

	if ( ( ctx->buffer = malloc(clen+1) ) == NULL )
		Error(__FILE__, __LINE__,1, "malloc failed");

	memset(ctx->buffer, 0, clen);

	while(1)
	{
		size_t rlen = fread(ctx->buffer+pos, 1, clen-pos, stdin);

		if ( rlen < 1 )
		{
			Error(__FILE__, __LINE__, 0, "Could not read everything from STDIN");
			break;
		}

		pos += rlen;

		if ( pos == clen )
			break;
	}
	fclose(stdin);

	*(ctx->buffer+clen) = '\0';

	cur = ctx->buffer;

	if ( ctx->boundary != NULL )
	{
		regex_t re[2];
		regmatch_t rematch[4];

		if ( regcomp(&re[0], "^Content-Disposition: form-data; name=\"([^\"]+)\"(; filename=\"([^\"]+)\")?",REG_EXTENDED|REG_ICASE) != 0 )
			Error(__FILE__, __LINE__, 1, "regcomp failed");

		if ( regcomp(&re[1], "^Content-Type: ([^(\r|\n)]+)",REG_EXTENDED|REG_ICASE) != 0 )
			Error(__FILE__, __LINE__, 1, "regcomp failed");


		while( ( cur = __memmem(cur, pos-(cur-ctx->buffer), ctx->boundary, strlen(ctx->boundary)) ) != NULL )
		{
			size_t nl;
			struct CGIVar *myvar;

			cur += strlen(ctx->boundary);

			if ( ( nl = __is_newline(cur,(pos - (cur-ctx->buffer))) ) != 0 )
			{
				cur+=nl;
			}
			else if ( (pos-(cur-ctx->buffer)) >= 1 && *cur == '-' &&  *(cur+1) == '-' )
				break;
			else
			{
				Error(__FILE__, __LINE__, 0, "parsing error in HTTP POST");
				break;
			}

			if ( regexec(&re[0], cur, 4, rematch, 0) != 0)
			{
				Error(__FILE__, __LINE__, 0, "could not find Content-Disposition after boundary");
				continue;
			}

			if ( rematch[0].rm_so == -1 || rematch[0].rm_eo == -1 )
				Error(__FILE__, __LINE__, 1, "regexec failure");

			if ( rematch[1].rm_so == -1 || rematch[1].rm_eo == -1 )
				Error(__FILE__, __LINE__, 1, "regexec failure");

			/* we're ready for a new var */

			if ( ( myvar = malloc(sizeof(struct CGIVar)) ) == NULL )
				Error(__FILE__, __LINE__,1, "malloc failed");

			ctx->var[ctx->var_num] = myvar;

			memset(myvar, 0, sizeof(struct CGIVar));

			*(cur + rematch[1].rm_eo) = '\0';
			myvar->name = cur + rematch[1].rm_so;

			if ( rematch[3].rm_so != -1 && rematch[3].rm_eo != -1 )
			{
				*(cur + rematch[3].rm_eo) = '\0';
				myvar->filename = cur + rematch[3].rm_so;
			}

			cur += rematch[0].rm_eo;

			if ( ( nl = __is_newline(cur, pos - (cur-ctx->buffer)) ) == 0 )
			{
				Error(__FILE__, __LINE__, 0, "could not find newline when expected");
				continue;
			}
			cur+=nl;

			if ( regexec(&re[1], cur, 2, rematch, 0) == 0)
			{
				if ( rematch[1].rm_so != -1 && rematch[1].rm_eo != -1 )
				{
					myvar->ctype = cur + rematch[1].rm_so;

					cur += rematch[0].rm_eo;

					if ( ( nl = __is_newline(cur, pos - (cur-ctx->buffer)) ) == 0 )
					{
						Error(__FILE__, __LINE__, 0, "could not find newline when expected");
						continue;
					}

					*cur = '\0';
					cur+=nl;
				}
				else
					Error(__FILE__, __LINE__, 1, "regexec failure");
			}

			if ( ( nl = __is_newline(cur, pos - (cur-ctx->buffer)) ) != 0 )
				cur+=nl;
			else
			{
				Error(__FILE__, __LINE__, 0, "could not find newline when expected");
				continue;
			}

			{
				char *end = __memmem(cur, pos-(cur-ctx->buffer), ctx->boundary, strlen(ctx->boundary));

				if ( end == NULL )
				{
					Error(__FILE__, __LINE__, 0, "missing boundary");
					break;
				}

				if ( *(end-1) == '\n' )
					end--;

				if ( *(end-1) == '\r' )
					end--;

				*end = '\0';
				myvar->value = cur;
				myvar->value_len = end-cur;
			}
			ctx->var_num++;

		}
		regfree(&re[0]);
		regfree(&re[1]);
	}
	else
	{
		__parse_urlencoded(ctx, ctx->buffer, pos);
	}

}

void __alloc_var(CGI_CTX *ctx)
{
	assert(ctx!=NULL);
	if ( ( ctx->var[ctx->var_num] = malloc(sizeof(CGI_VAR)) ) == NULL )
		Error(__FILE__, __LINE__, 1, "malloc failed");

	memset(ctx->var[ctx->var_num], 0, sizeof(CGI_VAR));
}

void __parse_urlencoded(CGI_CTX *ctx, char *buf, size_t buflen)
{
	char *cur = buf;
	regex_t re;
	regmatch_t rematch[3];

	assert(ctx!=NULL);
	assert(buf!=NULL);

	if ( regcomp(&re, "^([^=]*)=([^&]*)", REG_EXTENDED) != 0 )
		Error(__FILE__, __LINE__,1, "regcomp failed");

	while( regexec(&re,cur,3,rematch,0) == 0 )
	{
		if ( rematch[1].rm_so != -1 \
			&& rematch[1].rm_eo != -1 \
			&& rematch[1].rm_so < rematch[1].rm_eo )
		{
			assert(ctx->var[ctx->var_num]==NULL);
			__alloc_var(ctx);

			__decodeurl(cur+rematch[1].rm_so, \
				rematch[1].rm_eo-rematch[1].rm_so);

			ctx->var[ctx->var_num]->name = cur+rematch[1].rm_so;

			if ( rematch[2].rm_so != -1
				&& rematch[2].rm_eo != -1 )
			{
				assert(ctx->var[ctx->var_num]!=NULL);
				ctx->var[ctx->var_num]->value_len = __decodeurl( \
					cur+rematch[2].rm_so, \
					rematch[2].rm_eo-rematch[2].rm_so);

				ctx->var[ctx->var_num]->value = cur+rematch[2].rm_so;
				ctx->var_num++;
			}
		}
		else
			Error(__FILE__, __LINE__, 1, "regexec failed");


		if ( rematch[0].rm_eo > 0 )
			cur += rematch[0].rm_eo;

		if ( cur < buf+buflen )
			cur++;

		rematch[0].rm_so = 0;
		rematch[0].rm_eo = buflen - (cur-buf);
	}

	regfree(&re);
}

size_t __decodeurl(char *buf, size_t len)
{
	char *r,*w=buf;

	assert(buf!=NULL);

	for(r=buf; r<buf+len; ++r)
	{
		if ( *r == '%'
			&& r+2 < buf+len
			&& __is_hex(*(r+1))
			&& __is_hex(*(r+2)) )
		{
			*w = ( __hex_val(*(r+1)) * 16 ) + __hex_val(*(r+2));
			r+=2;
		}
		else if ( *r == '+' )
			*w = ' ';
		else if ( r != w )
			*w = *r;

		++w;
	}

	*w = '\0';

	return w - buf;
}

void __parseget(CGI_CTX *ctx)
{
	char *buf;

	assert(ctx!=NULL);

	if ( ( buf = getenv("QUERY_STRING") ) == NULL )
		return;

	if ( ( ctx->qs = malloc(strlen(buf)+1) ) == NULL )
		Error(__FILE__, __LINE__, 1, "malloc failed");

	strcpy(ctx->qs,buf);

	__parse_urlencoded(ctx, buf, strlen(buf));
}

size_t __is_newline(char *buf, size_t len)
{
	if ( len >= 1 && *buf == '\n' )
		return 1;

	if ( len >= 2 && *buf == '\r' && *(buf+1) == '\n' )
		return 2;

	return 0;
}