#ifndef lint
static char rcsId[]="$Header: /usr/local/rcs/Newt/XmHTML/RCS/HTTP.c,v 1.1 1997/10/23 00:28:23 newt Exp newt $";
#endif
/*****
* HTTP.c : A first attempt at a simple HTTP library.
*
* This file Version	$Revision: 1.1 $
*
* Creation date:		Tue Oct 21 01:41:31 GMT+0100 1997
* Last modification: 	$Date: 1997/10/23 00:28:23 $
* By:					$Author: newt $
* Current State:		$State: Exp $
*
* Author:				Richard Offer
*
* Copyright (C) 1994-1997 by Richard Offer <offer@sgi.com>
* All Rights Reserved
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Note from the Author:
*
*	A first attempt at a simple HTTP library, mainly used as a test harness for 
*	the forms work in XmHTML, it does lots of bad things and isn't a complete 
*	implementation. I didn't use the W3C libww 'cause its too big and doesn't 
*	seem to work for POSTs --- rmo 
*
*	The code is based on a quick read of the HTTP 1.0 rfc, with ideas for 
*	implementation taken from the Chimera Browser. 
*
*****/
/*****
* ChangeLog 
* $Log: HTTP.c,v $
* Revision 1.1  1997/10/23 00:28:23  newt
* Initial Revision
*
*****/ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "HTTPP.h"

/*** External Function Prototype Declarations ***/

/*** Public Variable Declarations ***/

/*** Private Datatype Declarations ****/

/*** Private Function Prototype Declarations ****/
static char *appendHex(char *dest, char *src);

/*** Private Variable Declarations ***/

/* This is the main routine for sending a request and getting a response, 
 * everything else in this file is waffle */
void
loadHTTPURL(void *unused, HTTPRequest * request, void *unused2)
{
	struct hostent *server;
	struct sockaddr_in name;
	int sock;
	char *scheme = NULL, *username = NULL, *password = NULL;
	char *hostname = NULL, *filename = NULL;
	int port;
	char *buf = NULL;
	size_t offset = 0;
	char line[1025];
	HTTPResponse *res;
	ssize_t val;

	/* see if we have an URI */
	if(request->url == NULL)
	{
		request->ret = HTTPBadURL;
		return;
	}

	/* verify request type */
	if(request->type != HTTPLoadToString &&
		request->type != HTTPLoadToFile)
	{
		request->ret = HTTPBadLoadType;
		return;
	}

	/* resolve the url */
	parseURL(request->url,
			 PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
			 &scheme, &username, &password, &hostname, &port, &filename);

	/* check protocol */
	if (scheme == NULL || strncasecmp(scheme, "http", 4))
	{
		request->ret = HTTPBadProtocol;
		return;
	}

	/* see if we can resolve the host */
	if((server = gethostbyname(hostname)) == NULL)
	{
		request->ret = HTTPBadHost;
		return;
	}

	/* we've got the host, open a socket */
	if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
	{
		request->ret = HTTPNoSocket;
		return;
	}
#ifdef DEBUG
	printf("Connecting to server %s (port %i)\n", hostname, port);
#endif

	name.sin_family = AF_INET;
	name.sin_port = htons(port);

#ifdef linux
	memcpy(&name.sin_addr, server->h_addr, server->h_length);
#else
	memcpy(&name.sin_addr.s_addr, server->h_addr, server->h_length);
#endif

	if(connect(sock, (struct sockaddr*)&name, sizeof(name)) < 0)
	{
		request->ret = HTTPNoConnection;
		close(sock);
		return;
	}
	switch(request->method)
	{
		case HTTPGET:
		{
			char *formStr = NULL, *reqStr = NULL;

			if (request->form_data)
			{
				formStr = encodeFormData((HTTPNamedValues*)request->form_data);
			}
			reqStr = (char*)malloc(strlen(GETMETHOD) + strlen(filename) +
						(formStr ? strlen(formStr) + 1 : 0) +
						strlen(HTTPVERSION) + strlen(NEWLINE) + 3);
			sprintf(reqStr,
					"%s%s%s%s%s%s",
					GETMETHOD,
					filename,
					(formStr ? "?" : ""),	/* cgi stuff requires a ? */
					(formStr ? formStr : ""),
					HTTPVERSION,
					NEWLINE);
#ifdef DEBUG
			fprintf(stderr, "sending request: %s\n", reqStr);
#endif
			val = write(sock, reqStr, strlen(reqStr) + 1);
			free(reqStr);
			if (formStr)
				free(formStr);

		}
		break;

		case HTTPPOST:
		{
			char *formStr = NULL, *fullReqStr;

			char *reqStr = (char *) malloc(strlen(POSTMETHOD) +
							strlen(filename) + strlen(HTTPVERSION) +
							strlen(NEWLINE) + 2);
			sprintf(reqStr, "%s%s%s", POSTMETHOD, filename, HTTPVERSION);

			if (request->form_data)
			{
				formStr = encodeFormData((HTTPNamedValues*)request->form_data);
			}
			fullReqStr = calloc(strlen(reqStr) + strlen(CONTENT_LEN) +
							strlen(CONTENT_TYPE) + MAX_FORM_LEN +
							(formStr ? strlen(formStr) : 0 )+ 10 /* safety */,
							sizeof(char));
			sprintf(fullReqStr,
					"%s%s%d%s%s%s%s%s%s",
					reqStr,
					CONTENT_LEN,
					(formStr ? strlen(formStr) : 0),
					NEWLINE,
					CONTENT_TYPE,
					NEWLINE,
					NEWLINE,
					formStr,
					NEWLINE);
#ifdef DEBUG
			fprintf(stderr, "sending request: %s\n", fullReqStr);
#endif
			val = write(sock, fullReqStr, strlen(fullReqStr) + 1);

			free(reqStr);
			if(formStr)
				free(formStr);
			free(fullReqStr);
		}
		break;

		default:
			/* free off the output from parseURL() */
			freeURL(PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
					scheme, username, password, hostname, port, filename);
			close(sock);
			request->ret = HTTPMethodUnsupported;
			return;
	}

	/* free off the output from parseURL() */
	freeURL(PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
			scheme, username, password, hostname, port, filename);

	/* read output from remote HTTP server */
	offset = 0;
	val = 0;
	buf = calloc(CHUNKSIZE, sizeof(char));
	val = read(sock, buf + offset, CHUNKSIZE);
	if(val == CHUNKSIZE)
	{
		while ((val = read(sock, buf + offset, CHUNKSIZE)) == CHUNKSIZE)
		{
			offset += CHUNKSIZE;
			buf = realloc(buf, offset + CHUNKSIZE);
		}
	}
	/* now parse the read message for headers */
	res = newResponse(buf);
	free(buf);
	request->ret = res->status_code;

#if defined(PRINT_HDRS) && defined(DEBUG)
	{
		int i;
		for(i = 0; i < res->num_headers; i++)
		{
			printf("hdr %s = %s\n", res->headers[i].name,
				res->headers[i].value);
		}
	}
#endif

	/* valid return code? */
	if(request->ret > 199 && request->ret < 399)
	{
		/* store data in string (most likely this was a cgi request) */
		if(request->type == HTTPLoadToString)
		{
			int i;
			size_t len = (res->data ? strlen((char *) res->data) : 0);
			for (i = 0; i < res->num_headers; i++)
			{
				if (!strcasecmp(res->headers[i].name, "Content-length"))
					len = atoi(res->headers[i].value);
			}
			request->out_data = calloc(len + 1, sizeof(char));
			memcpy((void *) request->out_data, res->data, len);
			request->out_data[len] = '\0';
			request->length = len;
		}
		else if(request->type == HTTPLoadToFile)
		{
			/* this was a request for a remote file. Save it */
			FILE *fp;

			if ((fp = fopen((char *) request->in_data, "w")) == NULL)
			{
				request->ret = HTTPCannotCreateFile;
			}
			else
			{
				int i;
				size_t len = (res->data ? strlen((char *) res->data) : 0);
				size_t written;
				for (i = 0; i < res->num_headers; i++)
				{
					if (!strcasecmp(res->headers[i].name, "Content-length"))
						len = atoi(res->headers[i].value);
				}
				/* flush data */
				written = fwrite(res->data, sizeof(char), len, fp);
				fclose(fp);
			}
		}
	}
	deleteResponse(res);

/* write failed */
  write_failed:

	/* all done */
	close(sock);
}

void
deleteHTTPRequest(HTTPRequest * req)
{

	if (req->in_data)
		free(req->in_data);
	if (req->form_data) {
		int i = 0;
		while (req->form_data[i].name != NULL) {
			if (req->form_data[i].name)
				free(req->form_data[i].name);
			if (req->form_data[i].value)
				free(req->form_data[i].value);
			i++;
		}
		free(req->form_data);
	}
	if (req->out_data)
		free(req->out_data);
	if (req->url)
		free(req->url);

	free(req);

}

HTTPRequest *
newHTTPRequest(void)
{

	HTTPRequest *new_r = (HTTPRequest *) calloc(1, sizeof(HTTPRequest));

	new_r->type = HTTPLoadToString;
	new_r->in_data = NULL;
	new_r->form_data = NULL;
	new_r->out_data = NULL;
	new_r->method = HTTPGET;
	new_r->url = NULL;
	new_r->ret = HTTPInvalid;

	return new_r;

}

static void
deleteResponse(HTTPResponse * res)
{
	int i;

	if (res->data)
		free(res->data);

	for (i = 0; i < res->num_headers; i++) {
		if (res->headers[i].name)
			free(res->headers[i].name);

		if (res->headers[i].value)
			free(res->headers[i].value);

	}

	if (res->headers)
		free(res->headers);

	free(res);

}

/* this also comes from chimera */
static unsigned char *
unencodeResponse(char *buf, size_t len)
{
	/****
	* The decoded string _will_ be shorter than the input, so this is okay
	* not really required as the decoding can be done in place -- kdh 
	****/
	unsigned char *tmp = calloc(len + 1, sizeof(char));
	char *bufP;
	unsigned char *tmpP;
	char hex[3];

	for (bufP = buf, tmpP = tmp; *bufP; bufP++, tmpP++) {

		if (*bufP == '%') {
			bufP++;
			if (*bufP == '%')
				*tmpP = *bufP;
			else {
				hex[0] = *bufP;
				bufP++;
				hex[1] = *bufP;
				hex[2] = '\0';
				*tmpP = (char) strtol(hex, NULL, 16);
			}
		} else if (*bufP == '+')
			*tmpP = ' ';
		else if (*bufP == ';')
			*tmpP = '\n';
		else
			*tmpP = *bufP;
	}

	*tmpP = '\0';
	return tmp;
}

static HTTPResponse *
newResponse(char *buf)
{

	HTTPResponse *res = (HTTPResponse *) calloc(1, sizeof(HTTPResponse));
	int ver, code;
	int header = 1;
	int i, start;
	int SOL;
	size_t len = 0;
	char *EOL;
	int unEncode = 0;

	if (strncasecmp(buf, "HTTP", 4))
	{
		res->http_version = HTTP_VERSION_09;
		res->headers = NULL;
		res->num_headers = 0;
		res->status_code = HTTPInvalid;
		res->data = (unsigned char *) NewString(buf);

		return res;
	}
	sscanf(buf, "HTTP/1.%d %d", &ver, &code);

	EOL = strstr(buf, "\r\n");
	start = EOL - buf + 2;
#ifdef DEBUG
	printf("\nHTTP 1.%d error code = %d\n", ver, code);
#endif

	if (ver == 0)
		res->http_version = HTTP_VERSION_10;
	else
		res->http_version = HTTP_VERSION_11;

	res->status_code = (HTTPRequestReturn) code;

	for (i = start, SOL = start; i < strlen(buf); i++)
	{
		if (buf[i] == '\r' || buf[i] == '\n')
		{
			if (buf[i] == '\r' && buf[i + 1] && buf[i + 1] == '\n')
			{
				char *colon = strchr(&buf[SOL], ':');

				if (colon == NULL)
					break;

				if (res->headers == NULL)
					res->headers =
						(HTTPNamedValues *) malloc(sizeof(HTTPNamedValues));
				else
					res->headers = realloc((void *)res->headers,
						sizeof(HTTPNamedValues) * (res->num_headers + 1));

				res->headers[res->num_headers].name = NewNString(&buf[SOL],
						colon - &buf[SOL]);
				res->headers[res->num_headers].value = NewNString(colon + 2,
						&buf[i] - colon - 2);
				if (!strcasecmp(res->headers[res->num_headers].name,
					"Content-length"))
					len = atoi(res->headers[res->num_headers].value);
				if(!strcasecmp(res->headers[res->num_headers].name,
					"Content-type") &&
					!strncasecmp(res->headers[res->num_headers].value,
						"text/html", 9))
					unEncode = 1;	/* non-standard response, decode */

				res->num_headers++;

				if (buf[i + 2] && buf[i + 2] == '\r' &&
					buf[i + 3] && buf[i + 3] == '\n')
				{
					if (len == 0)
						len = strlen(&buf[i + 4]);
					if (unEncode)
						res->data = unencodeResponse(&buf[i + 4], len);
					else
					{
						res->data = calloc(len + 1, sizeof(char));
						memcpy((void *) res->data, &buf[i + 4], len);
						res->data[len] = '\0';
					}
					goto finish;
				}
				i++;
			}
			SOL = i + 1;
		}
	}
  finish:

	return(res);
}

/*****
* Fast lookup table to determine which characters should be left alone and
* which should be encoded. Much faster than the Chimera implementation -- kdh
* const qualifier should put it in the text segment
*****/
static const unsigned char allow[97] =
{/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
	0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,1,	/* 2x   !"#$%&'()*+,-./  */
	1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,	/* 3x  0123456789:;<=>?  */
	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,	/* 4x  @ABCDEFGHIJKLMNO  */
	1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,1,	/* 5X  PQRSTUVWXYZ[\]^_  */
	0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,	/* 6x  `abcdefghijklmno  */
	1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0 	/* 7X  pqrstuvwxyz{\}~  DEL */
};

static const char *hex = "0123456789ABCDEF";

/*****
* Name:			appendHex
* Return Type: 	char*
* Description: 	appends src to dest, translating certain chars to their
*				hexadecimal representation as we do;
* In: 
*	dest:		destination buffer. This buffer must be large enough to contain
*				the expanded source text;
*	src:		text to be appended;
* Returns:
*	a ptr pointing to the next available position in dest.
* Note:
*	added 97/10/21 by kdh and based on HTEscape() from libwww
*****/
static char*
appendHex(char *dest, char *src)
{
	register char *ptr, *chPtr;

	for(ptr = dest, chPtr = src; *chPtr!= '\0'; chPtr++)
	{
		/* no negative values */
		int c = (int)((unsigned char)(*chPtr));
		if(*chPtr == ' ')	/* bloody exception */
			*ptr++ = '+';
		else if(c >= 32 && c <= 127 && allow[c-32])
			*ptr++ = *chPtr; /* acceptable char */
		else
		{
			*ptr++ = '%';	/* hex is following */
			*ptr++ = hex[c >> 4];
			*ptr++ = hex[c & 15];
		}
	}
	return(ptr);
}

/*****
* Name: 		encodeFormData
* Return Type: 	char*
* Description: 	creates a fully valid QUERY_STRING from the given name/value
*				pairs.
* In: 
*	formdata:	array of name/value pairs from a form submit. Encoding
*				terminates when a NULL name has been detected.
* Returns:
*	an allocated and hex-encoded QUERY_STRING.
* Note:
*	- this function is based on the corresponding one from Chimera (rmo)
*	- 97/10/21, heavily modified by kdh
*****/
static char*
encodeFormData(HTTPNamedValues * formdata)
{
	char *data, *chPtr;
	int nvalues, i, len = 0;

	/*****
	* First count how many bytes we have to allocate. Each entry gets two
	* additional bytes: the equal sign and a spacer. Each entry is also
	* multiplied by three to allow full expansion.
	* Count no of entries as well.
	*****/
	for(i = 0; formdata[i].name != NULL; i++)
	{
		if(formdata[i].name)
		{
			len += strlen(formdata[i].name) * 3;
			if(formdata[i].value)
				len += strlen(formdata[i].value) * 3;
			len += 2;	/* equal sign and spacer */
		}
	}
	nvalues = i;
	/* allocate & reset query string */
	data = (char*)calloc(len + 1, sizeof(char));

	/*****
	* Now compose query string: append & convert to hex at the same time.
	* We can safely do this as we've already allocated room for hexadecimal
	* expansion of the *entire* query string.
	* Room for optimisation: appendHex could be done inline
	*****/
	chPtr = data;
	for(i = 0; i < nvalues; i++)
	{
		if(formdata[i].name)
		{
			chPtr = appendHex(chPtr, formdata[i].name);
			*chPtr++ = '=';
			if(formdata[i].value)
				chPtr = appendHex(chPtr, formdata[i].value);
			*chPtr++ = '&';	/* spacer */
		}
	}
	/* mask off last & */
	data[strlen(data)-1] = '\0';

#ifdef DEBUG
	printf("encodeFormData, computed string length: %i, used: %i\n",
		len+1, strlen(data));
	printf("return value: %s\n", data);
#endif

	/*****
	* Could resize data to fit exactly the no of bytes used, but I wonder
	* if it's worth it as this data has a pretty short lifetime --- kdh
	*****/

	return(data);
}

/*
 * stolen from the chimera browser --- rmo.
 * 
 */

void
parseURL(char *url, long parse, char **scheme, char **username,
	char **password, char **hostname, int *port, char **filename)
{
	char *start;
	char *colon, *slash, *fslash;
	char *pound;				/* link pound (#) sign */
	char *at;					/* username/password @ */
	char *ucolon;				/* username colon */
	char *pcolon;				/* port number colon */
	struct _part {
		int start;
		int len;
	} sp, up, pwp, hp, pp, fp;

	sp.start = 0;
	sp.len = 0;
	up.start = 0;
	up.len = 0;
	pwp.start = 0;
	pwp.len = 0;
	hp.start = 0;
	hp.len = 0;
	pp.start = 0;
	pp.len = 0;
	fp.start = 0;
	fp.len = 0;

	if (url == NULL)
		return;

#define isspace8(a) ((a) < 33 && (a) > 0)
	/* skip leading white-space (if any) */
	for (start = url; isspace8(*start); start++);

	/*
	 * Look for indication of a scheme.
	 */
	colon = strchr(start, ':');

	/*
	 * Search for characters that indicate the beginning of the
	 * path/params/query/fragment part.
	 */
	slash = strchr(start, '/');
	if (slash == NULL)
		slash = strchr(start, ';');
	if (slash == NULL)
		slash = strchr(start, '?');
	if (slash == NULL)
		slash = strchr(start, '#');

	/*
	 * Check to see if there is a scheme.  There is a scheme only if
	 * all other separators appear after the colon.
	 */
	if (colon != NULL && (slash == NULL || colon < slash)) {
		sp.start = 0;
		sp.len = colon - start;
	}
	/*
	 * If there is a slash then sort out the hostname and filename.
	 * If there is no slash then there is no hostname but there is a
	 * filename.
	 */
	if (slash != NULL) {
		/*
		 * Check for leading //. If its there then there is a host string.
		 */
		if ((*(slash + 1) == '/') && ((colon == NULL && slash == start) ||
								(colon != NULL && slash == colon + 1))) {
			/*
			 * Check for filename at end of host string.
			 */
			slash += 2;
			if ((fslash = strchr(slash, '/')) != NULL) {
				hp.start = slash - start;;
				hp.len = fslash - slash;
				fp.start = fslash - start;
				fp.len = strlen(fslash);
			} else {			/* there is no filename */
				hp.start = slash - start;
				hp.len = fslash - slash;
			}
		} else {
			/*
			 * the rest is a filename because there is no // or it appears
			 * after other characters
			 */
			if (colon != NULL && colon < slash) {
				fp.start = colon + 1 - start;
				fp.len = strlen(colon + 1);
			} else {
				fp.start = slash - start;
				fp.len = strlen(slash);
			}
		}
	} else {
		/*
		 * No slashes at all so the rest must be a filename.
		 */
		if (colon == NULL) {
			fp.start = 0;
			fp.len = strlen(start);
		} else {
			fp.start = colon - start + 1;
			fp.len = strlen(colon + 1);
		}
	}

	/*
	 * If there is a host string then divide it into
	 * username:password@hostname:port as needed.
	 */
	if (hp.len != 0) {
		/*
		 * Look for username:password.
		 */
		if ((at = strchr(&url[hp.start], '@')) != NULL) {

			up.start = hp.start;
			up.len = at - start - hp.start;

			hp.start = at + 1 - start;


			if ((ucolon = strchr(&url[up.start], ':')) != NULL) {
				if (ucolon - start < hp.start) {
					pwp.start = ucolon + 1 - start;
					pwp.len = hp.start - pwp.start;
				}
			}
		}
		/*
		 * Grab the port.
		 */
		if ((pcolon = strchr(&url[hp.start], ':')) != NULL) {
			pp.start = pcolon + 1 - start;
			pp.len = fp.start - pp.start;
			hp.len -= pp.len + 1;
		}
	}
/* now have all the fragments, make them into strings */

	if (parse & PARSE_SCHEME) {
		if (sp.len > 0)
			*scheme = NewNString(&url[sp.start], sp.len);
		else
			*scheme = NULL;
	}
	if (parse & PARSE_USER) {
		if (up.len > 0)
			*username = NewNString(&url[up.start], up.len);
		else
			*username = NULL;
	}
	if (parse & PARSE_PASSWORD) {

		if (pwp.len > 0)
			*password = NewNString(&url[pwp.start], pwp.len);
		else
			*password = NULL;
	}
	if (parse & PARSE_HOSTNAME) {
		if (hp.len > 0)
			*hostname = NewNString(&url[hp.start], hp.len);
		else
			*hostname = NULL;
	}
	if (parse & PARSE_PORT) {
		if (pp.len > 0) {
			char *tmp = NewNString(&url[pp.start], pp.len);
			*port = atoi(tmp);
			free(tmp);
		} else
			*port = 80;
	}
	if (parse & PARSE_FILENAME) {
		if (fp.len > 0)
			*filename = NewString(&url[fp.start]);
		else
			*filename = NewString("/");
	}
	return;
}

/* this is brain dead, needs to be expanded to cover non http schemes -- rmo */

int
HTTPAbsoluteURL(char *url)
{
	if(strncasecmp(url, "http", 4))
		return(0);
	else
		return(1);
}

/* This is a very flakey routine and it needs a lot of work, it doesn't
   do compression of full paths, but it proved adequet for simple testing */

char *
HTTPFindAbsoluteURL(char *url, char *baseUrl)
{
	char new_url[1024];
	char *tmpP;

	char *u_scheme, *u_username, *u_password, *u_hostname, *u_filename;
	char *b_scheme, *b_username, *b_password, *b_hostname, *b_filename;
	int u_port, b_port;

	if (baseUrl == NULL || *baseUrl == '\0')
		return (NewString(url));

	parseURL(url, PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
		&u_scheme, &u_username, &u_password, &u_hostname, &u_port, &u_filename);

	parseURL(baseUrl, PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
		&b_scheme, &b_username, &b_password, &b_hostname, &b_port, &b_filename);

	if (u_scheme)
		sprintf(new_url, "%s://", u_scheme);
	else
		sprintf(new_url, "%s://", b_scheme);

	if (u_hostname)
		strcat(new_url, u_hostname);
	else if (b_hostname)
		strcat(new_url, b_hostname);
	else
		strcat(new_url, "localhost");

	if (u_filename && u_filename[0] == '/') {
		strcat(new_url, u_filename);
	} else if (u_filename && u_filename[0] == '~') {
		strcat(new_url, u_filename);
		strcat(new_url, "/");
	} else {
		if (b_filename == NULL || b_filename[0] != '/')
			printf("still to do\n");
		else {
			strcat(new_url, b_filename);
			tmpP = strrchr(new_url, '/');
			if (*tmpP++) {
				*tmpP = '\0';
				strcat(tmpP, u_filename);
			} else
				strcat(new_url, u_filename);

		}
	}
	freeURL(PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
	   u_scheme, u_username, u_password, u_hostname, u_port, u_filename);

	freeURL(PARSE_SCHEME | PARSE_HOSTNAME | PARSE_PORT | PARSE_FILENAME,
	   b_scheme, b_username, b_password, b_hostname, b_port, b_filename);

	return (NewString(new_url));
}

void 
freeURL(long parse, char *scheme, char *username, char *password, char *hostname,
	int port /* not used but keep for API compat with parseURL */ , char *filename)
{

	if (parse & PARSE_SCHEME)
		if (scheme)
			free(scheme);

	if (parse & PARSE_USER)
		if (username)
			free(username);

	if (parse & PARSE_PASSWORD)
		if (password)
			free(password);

	if (parse & PARSE_HOSTNAME)
		if (hostname)
			free(hostname);

	if (parse & PARSE_FILENAME)
		if (filename)
			free(filename);
}

void
HTTPError(char *msg, HTTPRequestReturn error)
{
	fprintf(stderr, "%s: ", msg);

	switch(error)
	{
		/* 0 and up (client messages) */
		case HTTPInvalid:
			fprintf(stderr, "Invalid request (client failure).\n");
			break;
		case HTTPBadProtocol:
			fprintf(stderr, "Invalid protocol requested (client failure).\n");
			break;
		case HTTPBadHost:
			fprintf(stderr, "Invalid hostname (client failure).\n");
			break;
		case HTTPBadURL:
			fprintf(stderr, "Invalid URL (client failure).\n");
			break;
		case HTTPBadLoadType:
			fprintf(stderr, "Invalid load type (client failure).\n");
			break;
		case HTTPMethodUnsupported:
			fprintf(stderr, "Unsupported method (client failure).\n");
			break;
		case HTTPNoSocket:
			fprintf(stderr, "Could not open socket (client failure).\n");
			break;
		case HTTPNoConnection:
			fprintf(stderr, "Not connected (client failure).\n");
			break;
		case HTTPBadHttp10:
			fprintf(stderr, "Invalid HTTP/1.0 request (client failure).\n");
			break;
		case HTTPCannotCreateFile:
			fprintf(stderr, "Could not create file (client failure).\n");
			break;

		/* 100 and up (informative messages) */
		case HTTPContinue:
			fprintf(stderr, "Continue.\n");
			break;
		case HTTPSwitchProtcols:
			fprintf(stderr, "Bad protocol, switch required.\n");
			break;

		/* 200 and up (request succeeded) */
		case HTTPSuccess:
			fprintf(stderr, "No error.\n");
			break;
		case HTTPCreated:
			fprintf(stderr, "Document created.\n");
			break;
		case HTTPAccepted:
			fprintf(stderr, "Request accepted.\n");
			break;
		case HTTPNonAuthoritativeInfo:
			fprintf(stderr, "Non-authoritative information.\n");
			break;
		case HTTPNoContent:
			fprintf(stderr, "Document is empty.\n");
			break;
		case HTTPResetContent:
			fprintf(stderr, "Content has been reset.\n");
			break;
		case HTTPPartialContent:
			fprintf(stderr, "Partial content.\n");
			break;

		/* 300 and up (non-fatal errors, retry possible) */
		case HTTPMultipleChoices:
			fprintf(stderr, "Request not unique, multiple choices possible.\n");
			break;
		case HTTPPermMoved:
			fprintf(stderr, "File permissions deny access.\n");
			break;
		case HTTPTempMoved:
			fprintf(stderr, "Document has been temporarely moved.\n");
			break;
		case HTTPSeeOther:
			fprintf(stderr, "Site has moved\n");
			break;
		case HTTPNotModified:
			fprintf(stderr, "Document not modified since last access.\n");
			break;
		case HTTPUseProxy:
			fprintf(stderr, "Document only accessible through proxy.\n");
			break;

		/* 400 and up (fatal request errors) */
		case HTTPBadRequest:
			fprintf(stderr, "Invalid HTTP request.\n");
			break;
		case HTTPUnauthorised:
			fprintf(stderr, "Client not authorized.\n");
			break;
		case HTTPPaymentReq:
			fprintf(stderr, "Payment required.\n");
			break;
		case HTTPForbidden:
			fprintf(stderr, "Access forbidded.\n");
			break;
		case HTTPNotFound:
			fprintf(stderr, "Document not found.\n");
			break;
		case HTTPMethodNotAllowed:
			fprintf(stderr, "Access method not allowed.\n");
			break;
		case HTTPNotAcceptable:
			fprintf(stderr, "Unacceptable request.\n");
			break;
		case HTTPProxyAuthReq:
			fprintf(stderr, "Proxy authorization required.\n");
			break;
		case HTTPRequestTimeOut:
			fprintf(stderr, "Timed out.\n");
			break;
		case HTTPConflict:
			fprintf(stderr, "Conflict of interest.\n");
			break;
		case HTTPGone:
			fprintf(stderr, "Document has moved.\n");
			break;
		case HTTPLengthReq:
			fprintf(stderr, "Invalid request length.\n");
			break;
		case HTTPPreCondFailed:
			fprintf(stderr, "Condition failed.\n");
			break;
		case HTTPReqEntityTooBig:
			fprintf(stderr, "Request entity too large.\n");
			break;
		case HTTPURITooBig:
			fprintf(stderr, "URI specification too big.\n");
			break;
		case HTTPUnsupportedMediaType:
			fprintf(stderr, "Unsupported media type.\n");
			break;

		/* 500 and up (server errors) */
		case HTTPInternalServerError:
			fprintf(stderr, "Internal server error.\n");
			break;
		case HTTPNotImplemented:
			fprintf(stderr, "Method not implemented.\n");
			break;
		case HTTPBadGateway:
			fprintf(stderr, "Invalid gateway.\n");
			break;
		case HTTPServiceUnavailable:
			fprintf(stderr, "Service unavailable.\n");
			break;
		case HTTPGatewayTimeOut:
			fprintf(stderr, "Gateway timed out.\n");
			break;
		case HTTPHTTPVersionNotSupported:
			fprintf(stderr, "Unsupported HTPP version.\n");
			break;

	default:
		fprintf(stderr, "unknown error\n");
	}
}
