/*-*-linux-c-*-*/

/*
 *  gnewtellium - Newtella for Unix
 *  Copyright (C) 2001 Elias Athanasopoulos
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/* HTTP header build/parse functions */

#include <stdlib.h>
#include <string.h>

#include <gtk/gtk.h>

#include "http.h"
#include "memory.h"

char *http_header_build(struct http_header *http)
{
	char *http_string = NULL;
	char end[6] = "\0";
	char *t;

	switch (http->func) {
	case HTTP_RESPONSE:
		/* HTTP 200 = OK */
		if (http->code == 200) {
			http_string = newtella_malloc(HTTP_RESPONSE_SIZE);
		        t = memrchr(http->file_name, '.', strlen(http->file_name));
			memcpy(end, ++t, strlen(t));
			g_snprintf(http_string, HTTP_RESPONSE_SIZE,
				   "HTTP/1.1 200 OK\r\n"
				   "Server: %s\r\n"
				   "Content-type: %s\r\n"
				   "Content-length: %u\r\n\r\n",
				   http->user_agent, end, http->content_length);
		}
		/* HTTP 404 = Not Found */
		if (http->code == 404) {
			http_string = newtella_malloc(HTTP_RESPONSE_SIZE);
			g_snprintf(http_string, HTTP_RESPONSE_SIZE,
				   "HTTP/1.1 404 Not Found\r\n"
				   "Server: %s\r\n\r\n", http->user_agent);
		}
		/* HTTP 503 = Busy */
		if (http->code == 503) {
			http_string = newtella_malloc(HTTP_RESPONSE_SIZE);
			g_snprintf(http_string, HTTP_RESPONSE_SIZE,
				   "HTTP/1.1 503 Server is busy\r\n"
				   "Server: %s\r\n\r\n", http->user_agent);
		}
		break;
	case HTTP_GET:
		http_string = newtella_malloc(HTTP_GET_SIZE + strlen(http->file_name));

		g_snprintf(http_string, HTTP_GET_SIZE + strlen(http->file_name),
			   "GET /get/%i/%s HTTP/1.0\r\n"
			   "Connection: Keep-Alive\r\n"
			   "Range: bytes=%u-\r\n"
			   "User-Agent: %s\r\n\r\n",
			   http->file_index, http->file_name, http->range_start,
			   http->user_agent);
		break;
	case HTTP_GIV:
		/* remember that we add 16 bytes for the guid */
		// Igor: Remember that we have two Bytes for \n\n -> + 2. Dann klappt's auch mit dem Nachbarn
		http_string = newtella_malloc(HTTP_GIV_SIZE + strlen(http->file_name) + 16 + 2);

		g_snprintf(http_string, HTTP_GIV_SIZE + strlen(http->file_name) + 16 + 2,
			   "GIV %d:%s/%s\n\n",
			   http->file_index, http->guid, http->file_name);
		break;
	}

	return http_string;
}

int http_header_size(char *buf)
{
	/* Returns the size of an HTTP header.
	   A 'valid' header must end in a '\r\n\r\n'.
	   If we don't find the double '\r\n' we
	   return 0.
	*/
        char *seek, *tmp = buf;
        int buf_size = strlen(buf);

try_again:
        seek = memchr(tmp, '\r', buf_size);

        if (!seek)
                return 0;

        if (!memcmp(seek, "\r\n\r\n", 4))
                return (seek-buf)+4;
        else {
                tmp = seek + 1;
                goto try_again;
        }
}

int http_giv_size(char *buf)
{
	/* Returns the size of a GIV header.
	   A 'valid' header must end in a '\n\n'.
	   If we don't find the double '\n' we
	   return 0.
	*/
        char *seek, *tmp = buf;
        int buf_size = strlen(buf);

try_again:
        seek = memchr(tmp, '\n', buf_size);

        if (!seek)
                return 0;

        if (!memcmp(seek, "\n\n", 2))
                return (seek-buf)+2;
        else {
                tmp = seek + 1;
                goto try_again;
        }
}

char *http_extract_var(const char *tag, char *buf)
{
	/* This function returns the value of a specific tag (i.e. Server)
	   of an HTTP header response. It's very kludgy but it works. :-)
	   I'll try to add lots of comments because in a week I will be totaly
	   unaware of how it works.

	   Consider: foo: bar\r\n

	   bar = http_extract_var("foo", http_buffer);
	*/

        char *v, *n, *seek;
        char *tmp;
        int buf_size = http_header_size(buf);
	int parsed = 0;

	/* a temp buffer to copy the HTTP header, cause
	   we want to manipulate its contents */
        tmp = newtella_malloc(buf_size);
        memcpy(tmp, buf, buf_size);

        seek = tmp;


try_again:
	/* find the first '\n'. there is great possibility that
	   the next byte is the start of the wanted tag. if we
	   don't find any '\n's the wanted tag doesn't exist, so
	   we return null */

	if (parsed == buf_size)
		return NULL;

	n = memchr(seek, '\n', buf_size);

        if (!n)
                return NULL;

        *n = '\0'; n++;

	parsed = (n - tmp);
	/* find the first ':' after an '\n' in order to isolate
	   the _tag_ */

	v = memchr(n, ':', buf_size-parsed);


	if (!v)
                return NULL;

        *v = '\0'; v++;

	parsed = (v - tmp);
	/* is it the tag we wanted? if yes isolate the _value_
	   otherwise start again from where we stopped

	   infinite loop is not possible because we return
	   null if we don't find either an '\n' or an ':'
	*/
        if (!g_strncasecmp(tag, n, strlen(tag))) {
		seek = memchr(v, '\r', buf_size-parsed);
		*seek = '\0';
        } else
                goto try_again;

	/* copy the value in a new buffer, in order to return
	   it, and free the temporary one. The increment is for
	   the space after the ':' */
	n = newtella_malloc(strlen(++v)+1);
	strcpy(n, v);

	newtella_free(tmp);

        return n;
}

struct http_header *http_header_parse(char *http_string)
{
	struct http_header *http = newtella_malloc(sizeof(struct http_header));
	gchar *p;

	/* Try to parse the header. It might *not* be a
	   complete or a valid header. If we return
	   the http structure with valid=1, then this means
	   that we parsed it all correctly.

	   Remember: \r\n\r\n (double) means end of the
	   HTTP message.
	*/
	http->valid = 0;
	if (!memcmp(http_string, "HTTP", 4))
		goto parse_response;
	else if (!memcmp(http_string, "GET ", 4))
		goto parse_get;
	else if (!memcmp(http_string, "GIV ", 4))
		goto parse_giv;

 parse_response:
	/* get the HTTP code */
	http_string = memchr(http_string, ' ', strlen(http_string));
	http_string++;
	http->code = atoi(http_string);
	switch (http->code) {
		/* OK */
	case 200:
		/* get the server */
		http->user_agent = http_extract_var("Server", http_string);
	     	/* get the content type */
		http->content_type = http_extract_var("Content-type", http_string);
		/* get the content length */
		http->content_length = atol(http_extract_var("Content-length", http_string));
		/* get the content range start -- Content-Range: bytes= */

		p = http_extract_var("Content-Range", http_string);
		if (p)
			http->range_start = atol(p+6);

                /* not very safe */
		if (http->content_length)
			http->valid = 1;
		break;
		/* RESUME */
	case 206:
                /* get the server */
		http->user_agent = http_extract_var("Server", http_string);
	     	/* get the content type */
		http->content_type = http_extract_var("Content-type", http_string);
		/* get the content length */
		http->content_length = atol(http_extract_var("Content-length" , http_string));
		/* get the content range start -- Content-Range: bytes= */
		http->range_start = atol(http_extract_var("Content-Range", http_string)+6);
		/* not very safe */
		if (http->content_length)
			http->valid = 1;
		break;
		/* BUSY */
	case 404:
		http->user_agent = http_extract_var("Server", http_string);
		http->valid = 1;
		break;
	case 503:
		http->user_agent = http_extract_var("Server", http_string);
		http->valid = 1;
		break;
	default:
		http->valid = 0;
		break;
	}

	return http;

 parse_get:
	/* GET /get/file_index/file_name HTTP/1.0\r\n */
	http->file_index = atoi(http_string + 9);
 	if (http->file_index) {
    		/* get the file name */
		char *seek, *range_str = NULL;
		int   step = 0;
		seek = strchr(http_string + 9, '/');

		if (!seek)
			return http;

		seek++;

		while (*seek++ != ' ') {
			step++;

			/* not found */
			if (*seek == '\0')
				return http;
		}

		http->file_name = newtella_malloc(step + 1);
		memcpy(http->file_name, seek-(step + 1), step);

		/* Connection: Keep-Alive */
		http->connection = http_extract_var("Connection", seek);
	       	/* get range */
		range_str = http_extract_var("Range", seek);
		if (range_str) {
			/* parse the range string */
			seek = strchr(range_str, '=');
			if (!seek)
				return http;

			http->range_start = atol(seek++);
			seek = strchr(seek, '-');
			http->range_end = atol(seek++);
			http->valid = 1;
		}

		return http;
	}
 parse_giv:
	/* TODO */
	// Igor: Done (I hope so :))
	// GIV <File Index>:<Servent Identifier>/<File Name>\r\n
	http->file_index = atoi(http_string + 4);
	if (http->file_index){
		char *seek;
		int   step = 0;

		seek = strchr(http_string + 4, ':');
		if (!seek)
			return http;

		seek++;

		while (*seek++ != '/') {
			step++;

			/* not found */
			if (*seek == '\0')
				return http;
		}

		memcpy(http->guid, seek - (step + 1), step);
		step = 0;
		while (*seek++ != '\n') {
			step++;

			/* not found */
			if (*seek == '\0')
				return http;
		}

		http->file_name = malloc(step + 1);
		memcpy(http->file_name, seek-(step + 1), step);

		seek = strchr(http->file_name , '/');
		if (seek)
			return http;
		if (!has_ending(http->file_name))
			return http;
		http->valid = 1;

	}
	return http;
}
