/*******************************************************************************
 *
 * Copyright (c) 2000-2003 Intel Corporation
 * All rights reserved.
 * Copyright (c) 2012 France Telecom All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - 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.
 * - Neither name of Intel Corporation nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS 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 INTEL OR
 * 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.
 *
 ******************************************************************************/

/*!
 * \file
 *
 * Purpose: This file defines the functionality making use of the http.
 * It defines functions to receive messages, process messages, send messages.
 */

#include "config.h"

#include "httpreadwrite.h"

#include "UpnpExtraHeaders.h"
#include "UpnpInet.h"
#include "membuffer.h"
#include "sock.h"
#include "statcodes.h"
#include "upnp.h"
#include "upnpapi.h"
#include "uri.h"
#include "webserver.h"

#include <assert.h>
#include <inttypes.h>
#include <stdarg.h>
#include <string.h>

#include "posix_overwrites.h" // IWYU pragma: keep

#ifdef _WIN32
	#include <malloc.h>
	#define fseeko fseek
	#if defined(_MSC_VER) && _MSC_VER < 1900
		#define snprintf _snprintf
	#endif
#else /* _WIN32 */
	#include <arpa/inet.h>
	#include <sys/time.h>
	#include <sys/types.h>
	#include <sys/utsname.h>
	#include <sys/wait.h>
	#if defined(__ANDROID__) && \
		(!defined(__USE_FILE_OFFSET64) || __ANDROID_API__ < 24)
		#define fseeko fseek
	#endif
#endif /* _WIN32 */

/*
 * Please, do not change these to const int while MSVC cannot understand
 * const int in array dimensions.
 */
/*
const int CHUNK_HEADER_SIZE = 10;
const int CHUNK_TAIL_SIZE = 10;
*/
#define CHUNK_HEADER_SIZE (size_t)10
#define CHUNK_TAIL_SIZE (size_t)10

#ifndef UPNP_ENABLE_BLOCKING_TCP_CONNECTIONS

	/* in seconds */
	#define DEFAULT_TCP_CONNECT_TIMEOUT 5

/*!
 * \brief Checks socket connection and wait if it is not connected.
 * It should be called just after connect.
 *
 * \return 0 if successful, else -1.
 */
static int Check_Connect_And_Wait_Connection(
	/*! [in] socket. */
	SOCKET sock,
	/*! [in] result of connect. */
	int connect_res)
{
	struct timeval tmvTimeout = {DEFAULT_TCP_CONNECT_TIMEOUT, 0};
	int result;
	#ifdef _WIN32
	struct fd_set fdSet;
	#else
	fd_set fdSet;
	#endif
	FD_ZERO(&fdSet);
	FD_SET(sock, &fdSet);

	if (connect_res < 0) {
	#ifdef _WIN32
		if (WSAEWOULDBLOCK == WSAGetLastError()) {
	#else
		if (EINPROGRESS == errno) {
	#endif
			result = select(
				(int)sock + 1, NULL, &fdSet, NULL, &tmvTimeout);
			if (result < 0) {
	#ifdef _WIN32
					/* WSAGetLastError(); */
	#else
					/* errno */
	#endif
				return -1;
			} else if (result == 0) {
				/* timeout */
				return -1;
	#ifndef _WIN32
			} else {
				int valopt = 0;
				socklen_t len = sizeof(valopt);
				if (getsockopt(sock,
					    SOL_SOCKET,
					    SO_ERROR,
					    (void *)&valopt,
					    &len) < 0) {
					/* failed to read delayed error */
					return -1;
				} else if (valopt) {
					/* delayed error = valopt */
					return -1;
				}
	#endif
			}
		}
	}

	return 0;
}
#endif /* UPNP_ENABLE_BLOCKING_TCP_CONNECTIONS */

static int private_connect(
	SOCKET sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)
{
#ifndef UPNP_ENABLE_BLOCKING_TCP_CONNECTIONS
	int ret = sock_make_no_blocking(sockfd);
	if (ret != -1) {
		ret = connect(sockfd, serv_addr, addrlen);
		ret = Check_Connect_And_Wait_Connection(sockfd, ret);
		if (ret != -1) {
			ret = sock_make_blocking(sockfd);
		}
	}

	return ret;
#else
	return connect(sockfd, serv_addr, addrlen);
#endif /* UPNP_ENABLE_BLOCKING_TCP_CONNECTIONS */
}

static int get_hoststr(
	const char *url_str, const char **hoststr, size_t *hostlen)
{
	const char *start;
	const char *finish;
	int ret_code = UPNP_E_INVALID_URL;

	start = strstr(url_str, "//");
	if (!start) {
		goto end_function;
	}
	start += 2;
	finish = strchr(start, '/');
	if (finish) {
		*hostlen = (size_t)(finish - start);
	} else {
		*hostlen = strlen(start);
	}
	*hoststr = start;

	ret_code = UPNP_E_SUCCESS;

end_function:
	return ret_code;
}

static void copy_msg_headers(LinkedList *msgHeaders, UpnpString *headers)
{
	(void)msgHeaders;
	(void)headers;
	return;
/* TODO: */
#if 0
	ListNode *node;
	UpnpHttpHeader *header;
	http_header_t *msgHeader;
	if (headers) {
		ListInit(headers, NULL, (free_function) UpnpHttpHeader_delete);
		node = ListHead(msgHeaders);
		while(node) {
			msgHeader = (http_header_t*) node->item;
			header = UpnpHttpHeader_new();
			UpnpHttpHeader_strncpy_Name(
				header,
				msgHeader->name.buf,
				msgHeader->name.length);
			UpnpHttpHeader_strncpy_Value(
				header,
				msgHeader->value.buf,
				msgHeader->value.length);
			node = ListNext(msgHeaders, node);
		}
	}
#endif
}

int http_FixUrl(uri_type *url, uri_type *fixed_url)
{
	const char *temp_path = "/";

	*fixed_url = *url;
#ifdef UPNP_ENABLE_OPEN_SSL
	if (token_string_casecmp(&fixed_url->scheme, "http") != 0 &&
		token_string_casecmp(&fixed_url->scheme, "https") != 0) {
		return UPNP_E_INVALID_URL;
	}
#else
	if (token_string_casecmp(&fixed_url->scheme, "http") != 0) {
		return UPNP_E_INVALID_URL;
	}
#endif
	if (fixed_url->hostport.text.size == (size_t)0) {
		return UPNP_E_INVALID_URL;
	}
	/* set pathquery to "/" if it is empty */
	if (fixed_url->pathquery.size == (size_t)0) {
		fixed_url->pathquery.buff = temp_path;
		fixed_url->pathquery.size = (size_t)1;
	}

	return UPNP_E_SUCCESS;
}

int http_FixStrUrl(const char *url_str, size_t url_str_len, uri_type *fixed_url)
{
	uri_type url;

	if (parse_uri(url_str, url_str_len, &url) != HTTP_SUCCESS) {
		return UPNP_E_INVALID_URL;
	}

	return http_FixUrl(&url, fixed_url);
}

/************************************************************************
 * Function: http_Connect
 *
 * Parameters:
 *	IN uri_type* destination_url;	URL containing destination information
 *	OUT uri_type *url;		Fixed and corrected URL
 *
 * Description:
 *	Gets destination address from URL and then connects to the remote end
 *
 *  Returns:
 *	socket descriptor on success
 *	UPNP_E_OUTOF_SOCKET
 *	UPNP_E_SOCKET_CONNECT on error
 ************************************************************************/
SOCKET http_Connect(uri_type *destination_url, uri_type *url)
{
	SOCKET http_socket;
	socklen_t sockaddr_len;
	int ret_connect;
	char errorBuffer[ERROR_BUFFER_LEN];

	http_FixUrl(destination_url, url);

	http_socket =
		socket((int)url->hostport.IPaddress.ss_family, SOCK_STREAM, 0);
	if (http_socket == INVALID_SOCKET) {
		return (SOCKET)(UPNP_E_OUTOF_SOCKET);
	}
	sockaddr_len = (socklen_t)(url->hostport.IPaddress.ss_family == AF_INET6
					   ? sizeof(struct sockaddr_in6)
					   : sizeof(struct sockaddr_in));
	ret_connect = private_connect(http_socket,
		(struct sockaddr *)&url->hostport.IPaddress,
		sockaddr_len);
	if (ret_connect == -1) {
#ifdef _WIN32
		UpnpPrintf(UPNP_CRITICAL,
			HTTP,
			__FILE__,
			__LINE__,
			"connect error: %d\n",
			WSAGetLastError());
#endif
		if (shutdown(http_socket, SD_BOTH) == -1) {
			strerror_r(errno, errorBuffer, ERROR_BUFFER_LEN);
			UpnpPrintf(UPNP_INFO,
				HTTP,
				__FILE__,
				__LINE__,
				"Error in shutdown: %s\n",
				errorBuffer);
		}
		UpnpCloseSocket(http_socket);
		return (SOCKET)(UPNP_E_SOCKET_CONNECT);
	}

	return http_socket;
}

/*!
 * \brief Get the data on the socket and take actions based on the read data to
 * modify the parser objects buffer.
 *
 * If an error is reported while parsing the data, the error code is passed in
 * the http_error_code parameter.
 *
 * Parameters:
 *	IN SOCKINFO *info;			Socket information object
 *	OUT http_parser_t* parser;		HTTP parser object
 *	IN http_method_t request_method;	HTTP request method
 *	IN OUT int* timeout_secs;		time out
 *	OUT int* http_error_code;		HTTP error code returned
 *
 * \return
 * 	 UPNP_E_SUCCESS
 *	 UPNP_E_BAD_HTTPMSG
 */
int http_RecvMessage(SOCKINFO *info,
	http_parser_t *parser,
	http_method_t request_method,
	int *timeout_secs,
	int *http_error_code)
{
	int ret = UPNP_E_SUCCESS;
	int line = 0;
	parse_status_t status;
	int num_read = 0;
	int ok_on_close = 0;
	char *buf;
	size_t buf_len = 1024;

	*http_error_code = HTTP_INTERNAL_SERVER_ERROR;
	buf = malloc(buf_len);
	if (!buf) {
		ret = UPNP_E_OUTOF_MEMORY;
		goto ExitFunction;
	}
	if (request_method == (http_method_t)HTTPMETHOD_UNKNOWN) {
		parser_request_init(parser);
	} else {
		parser_response_init(parser, request_method);
	}

	while (1) {
		/* Double the bet if needed */
		/* We should have already exited the loop if the value was <= 0
		 * so the cast is safe */
		if ((size_t)num_read >= buf_len) {
			free(buf);
			buf_len = 2 * buf_len;
			buf = malloc(buf_len);
			if (!buf) {
				ret = UPNP_E_OUTOF_MEMORY;
				goto ExitFunction;
			}
		}
		num_read = sock_read(info, buf, buf_len, timeout_secs);
		if (num_read > 0) {
			/* got data */
			status = parser_append(parser, buf, (size_t)num_read);
			switch (status) {
			case PARSE_SUCCESS:
				UpnpPrintf(UPNP_INFO,
					HTTP,
					__FILE__,
					__LINE__,
					"<<< (RECVD) "
					"<<<\n%s\n-----------------\n",
					parser->msg.msg.buf);
				print_http_headers(&parser->msg);
				if (g_maxContentLength > 0 &&
					parser->content_length >
						(unsigned int)
							g_maxContentLength) {
					*http_error_code =
						HTTP_REQ_ENTITY_TOO_LARGE;
					line = __LINE__;
					ret = UPNP_E_OUTOF_BOUNDS;
					goto ExitFunction;
				}
				line = __LINE__;
				ret = 0;
				goto ExitFunction;
			case PARSE_FAILURE:
			case PARSE_NO_MATCH:
				*http_error_code = parser->http_error_code;
				line = __LINE__;
				ret = UPNP_E_BAD_HTTPMSG;
				goto ExitFunction;
			case PARSE_INCOMPLETE_ENTITY:
				/* read until close */
				ok_on_close = 1;
				break;
			case PARSE_CONTINUE_1:
				/* Web post request. */
				line = __LINE__;
				ret = PARSE_SUCCESS;
				goto ExitFunction;
			default:
				break;
			}
		} else if (num_read == 0) {
			if (ok_on_close) {
				UpnpPrintf(UPNP_INFO,
					HTTP,
					__FILE__,
					__LINE__,
					"<<< (RECVD) "
					"<<<\n%s\n-----------------\n",
					parser->msg.msg.buf);
				print_http_headers(&parser->msg);
				line = __LINE__;
				ret = 0;
				goto ExitFunction;
			} else {
				/* partial msg */
				*http_error_code =
					HTTP_BAD_REQUEST; /* or response */
				line = __LINE__;
				ret = UPNP_E_BAD_HTTPMSG;
				goto ExitFunction;
			}
		} else {
			*http_error_code = parser->http_error_code;
			line = __LINE__;
			ret = num_read;
			goto ExitFunction;
		}
	}

ExitFunction:
	free(buf);
	if (ret != UPNP_E_SUCCESS) {
		UpnpPrintf(UPNP_ALL,
			HTTP,
			__FILE__,
			line,
			"(http_RecvMessage): Error %d, http_error_code = %d.\n",
			ret,
			*http_error_code);
	}

	return ret;
}

int http_SendMessage(SOCKINFO *info, int *TimeOut, const char *fmt, ...)
{
#if EXCLUDE_WEB_SERVER == 0
	FILE *Fp;
	struct SendInstruction *Instr = NULL;
	char *filename = NULL;
	char *file_buf = NULL;
	char *ChunkBuf = NULL;
	/* 10 byte allocated for chunk header. */
	char Chunk_Header[CHUNK_HEADER_SIZE];
	size_t num_read;
	off_t amount_to_be_read = 0;
	size_t Data_Buf_Size = WEB_SERVER_BUF_SIZE;
#endif /* EXCLUDE_WEB_SERVER */
	va_list argp;
	char *buf = NULL;
	char c;
	int nw;
	int RetVal = 0;
	size_t buf_length;
	size_t num_written;
	int I_fmt_processed = 0;

#if EXCLUDE_WEB_SERVER == 0
	memset(Chunk_Header, 0, sizeof(Chunk_Header));
#endif /* EXCLUDE_WEB_SERVER */
	va_start(argp, fmt);
	while ((c = *fmt++)) {
#if EXCLUDE_WEB_SERVER == 0
		if (c == 'I' && !I_fmt_processed) {
			I_fmt_processed = 1;
			Instr = va_arg(argp, struct SendInstruction *);
			if (Instr->ReadSendSize >= 0)
				amount_to_be_read = Instr->ReadSendSize;
			else
				amount_to_be_read = (off_t)Data_Buf_Size;
			if (amount_to_be_read < (off_t)WEB_SERVER_BUF_SIZE)
				Data_Buf_Size = (size_t)amount_to_be_read;
			ChunkBuf = malloc(
				(size_t)(Data_Buf_Size + CHUNK_HEADER_SIZE +
					 CHUNK_TAIL_SIZE));
			if (!ChunkBuf) {
				RetVal = UPNP_E_OUTOF_MEMORY;
				goto ExitFunction;
			}
			file_buf = ChunkBuf + CHUNK_HEADER_SIZE;
		} else if (c == 'f') {
			/* file name */
			filename = va_arg(argp, char *);
			if (Instr && Instr->IsVirtualFile)
				Fp = (virtualDirCallback.open)(filename,
					UPNP_READ,
					Instr->Cookie,
					Instr->RequestCookie);
			else
	#ifdef _WIN32
				fopen_s(&Fp, filename, "rb");
	#else
				Fp = fopen(filename, "rb");
	#endif
			if (Fp == NULL) {
				RetVal = UPNP_E_FILE_READ_ERROR;
				goto ExitFunction;
			}
			if (Instr && Instr->IsRangeActive &&
				Instr->IsVirtualFile) {
				if (virtualDirCallback.seek(Fp,
					    Instr->RangeOffset,
					    SEEK_CUR,
					    Instr->Cookie,
					    Instr->RequestCookie) != 0) {
					RetVal = UPNP_E_FILE_READ_ERROR;
					goto Cleanup_File;
				}
			} else if (Instr && Instr->IsRangeActive) {
				if (fseeko(Fp, Instr->RangeOffset, SEEK_CUR) !=
					0) {
					RetVal = UPNP_E_FILE_READ_ERROR;
					goto Cleanup_File;
				}
			}
			while (amount_to_be_read) {
				if (Instr) {
					size_t n =
						amount_to_be_read >=
								(off_t)Data_Buf_Size
							? Data_Buf_Size
							: (size_t)amount_to_be_read;
					if (Instr->IsVirtualFile) {
						int nr = virtualDirCallback.read(
							Fp,
							file_buf,
							n,
							Instr->Cookie,
							Instr->RequestCookie);
						if (nr < 0) {
							RetVal =
								UPNP_E_FILE_READ_ERROR;
							goto Cleanup_File;
						}
						num_read = (size_t)nr;
					} else {
						num_read = fread(file_buf,
							(size_t)1,
							n,
							Fp);
					}
					amount_to_be_read -= (off_t)num_read;
					if (Instr->ReadSendSize < 0) {
						/* read until close */
						amount_to_be_read =
							(off_t)Data_Buf_Size;
					}
				} else {
					num_read = fread(file_buf,
						(size_t)1,
						Data_Buf_Size,
						Fp);
				}
				if (num_read == (size_t)0) {
					/* EOF so no more to send. */
					if (Instr && Instr->IsChunkActive) {
						const char *str = "0\r\n\r\n";
						nw = sock_write(info,
							str,
							strlen(str),
							TimeOut);
					} else {
						RetVal = UPNP_E_FILE_READ_ERROR;
					}
					goto Cleanup_File;
				}
				/* Create chunk for the current buffer. */
				if (Instr && Instr->IsChunkActive) {
					int rc;
					/* Copy CRLF at the end of the chunk */
					memcpy(file_buf + num_read,
						"\r\n",
						(size_t)2);
					/* Hex length for the chunk size. */
					memset(Chunk_Header,
						0,
						sizeof(Chunk_Header));
					rc = snprintf(Chunk_Header,
						sizeof(Chunk_Header),
						"%" PRIzx "\r\n",
						num_read);
					if (rc < 0 ||
						(unsigned int)rc >=
							sizeof(Chunk_Header)) {
						RetVal = UPNP_E_INTERNAL_ERROR;
						goto Cleanup_File;
					}
					/* Copy the chunk size header  */
					memcpy(file_buf - strlen(Chunk_Header),
						Chunk_Header,
						strlen(Chunk_Header));
					/* on the top of the buffer. */
					/*file_buf[num_read+strlen(Chunk_Header)]
					 * = NULL; */
					/*upnpprintf("Sending
					 * %s\n",file_buf-strlen(Chunk_Header));*/
					nw = sock_write(info,
						file_buf - strlen(Chunk_Header),
						num_read +
							strlen(Chunk_Header) +
							(size_t)2,
						TimeOut);
					num_written = (size_t)nw;
					if (nw <= 0 ||
						num_written !=
							num_read +
								strlen(Chunk_Header) +
								(size_t)2)
						/* Send error nothing we can do.
						 */
						goto Cleanup_File;
				} else {
					/* write data */
					nw = sock_write(info,
						file_buf,
						num_read,
						TimeOut);
					UpnpPrintf(UPNP_INFO,
						HTTP,
						__FILE__,
						__LINE__,
						">>> (SENT) "
						">>>\n%.*s\n------------\n",
						nw,
						file_buf);
					/* Send error nothing we can do */
					num_written = (size_t)nw;
					if (nw <= 0 ||
						num_written != num_read) {
						goto Cleanup_File;
					}
				}
			} /* while */
		Cleanup_File:
			if (Instr && Instr->IsVirtualFile) {
				virtualDirCallback.close(Fp,
					Instr->Cookie,
					Instr->RequestCookie);
			} else {
				fclose(Fp);
			}
			goto ExitFunction;
		} else
#endif /* EXCLUDE_WEB_SERVER */
			if (c == 'b') {
				/* memory buffer */
				buf = va_arg(argp, char *);
				buf_length = va_arg(argp, size_t);
				if (buf_length > (size_t)0) {
					nw = sock_write(
						info, buf, buf_length, TimeOut);
					num_written = (size_t)nw;
					UpnpPrintf(UPNP_INFO,
						HTTP,
						__FILE__,
						__LINE__,
						">>> (SENT) >>>\n"
						"%.*s\nbuf_length=%" PRIzd
						", num_written=%" PRIzd "\n"
						"------------\n",
						(int)buf_length,
						buf,
						buf_length,
						num_written);
					if (num_written != buf_length) {
						RetVal = 0;
						goto ExitFunction;
					}
				}
			}
	}

ExitFunction:
	va_end(argp);
#if EXCLUDE_WEB_SERVER == 0
	free(ChunkBuf);
#endif /* EXCLUDE_WEB_SERVER */
	return RetVal;
}

/************************************************************************
 * Function: http_RequestAndResponse
 *
 * Parameters:
 *	IN uri_type* destination;	Destination URI object which contains
 *					remote IP address among other elements
 *	IN const char* request;		Request to be sent
 *	IN size_t request_length;	Length of the request
 *	IN http_method_t req_method;	HTTP Request method
 *	IN int timeout_secs;		time out value
 *	OUT http_parser_t* response;	Parser object to receive the response
 *
 * Description:
 *	Initiates socket, connects to the destination, sends a
 *	request and waits for the response from the remote end
 *
 * Returns:
 *	UPNP_E_SOCKET_ERROR
 * 	UPNP_E_SOCKET_CONNECT
 *	Error Codes returned by http_SendMessage
 *	Error Codes returned by http_RecvMessage
 ************************************************************************/
int http_RequestAndResponse(uri_type *destination,
	const char *request,
	size_t request_length,
	http_method_t req_method,
	int timeout_secs,
	http_parser_t *response)
{
	SOCKET tcp_connection;
	int ret_code;
	size_t sockaddr_len;
	int http_error_code;
	SOCKINFO info;

	tcp_connection = socket(
		(int)destination->hostport.IPaddress.ss_family, SOCK_STREAM, 0);
	if (tcp_connection == INVALID_SOCKET) {
		parser_response_init(response, req_method);
		return UPNP_E_SOCKET_ERROR;
	}
	if (sock_init(&info, tcp_connection) != UPNP_E_SUCCESS) {
		parser_response_init(response, req_method);
		ret_code = UPNP_E_SOCKET_ERROR;
		goto end_function;
	}
	/* connect */
	sockaddr_len = destination->hostport.IPaddress.ss_family == AF_INET6
			       ? sizeof(struct sockaddr_in6)
			       : sizeof(struct sockaddr_in);
	ret_code = private_connect(info.socket,
		(struct sockaddr *)&(destination->hostport.IPaddress),
		(socklen_t)sockaddr_len);
	if (ret_code == -1) {
		parser_response_init(response, req_method);
		ret_code = UPNP_E_SOCKET_CONNECT;
		goto end_function;
	}
	/* send request */
	ret_code = http_SendMessage(
		&info, &timeout_secs, "b", request, request_length);
	if (ret_code != 0) {
		parser_response_init(response, req_method);
		goto end_function;
	}
	/* recv response */
	ret_code = http_RecvMessage(
		&info, response, req_method, &timeout_secs, &http_error_code);

end_function:
	/* should shutdown completely */
	sock_destroy(&info, SD_BOTH);

	return ret_code;
}

/************************************************************************
 * Function: http_Download
 *
 * Parameters:
 *	IN const char* url_str;	String as a URL
 *	IN int timeout_secs;	time out value
 *	OUT char** document;	buffer to store the document extracted
 *				from the downloaded message.
 *	OUT int* doc_length;	length of the extracted document
 *	OUT char* content_type;	Type of content
 *
 * Description:
 *	Download the document message and extract the document
 *	from the message.
 *
 * Return: int
 *	UPNP_E_SUCCESS
 *	UPNP_E_INVALID_URL
 ************************************************************************/
int http_Download(const char *url_str,
	int timeout_secs,
	char **document,
	size_t *doc_length,
	char *content_type)
{
	int ret_code;
	uri_type url;
	char *msg_start;
	char *entity_start;
	const char *hoststr;
	http_parser_t response;
	size_t msg_length;
	size_t hostlen;
	memptr ctype;
	size_t copy_len;
	membuffer request;
	size_t url_str_len;

	url_str_len = strlen(url_str);
	/*ret_code = parse_uri( (char*)url_str, url_str_len, &url ); */
	UpnpPrintf(UPNP_INFO,
		HTTP,
		__FILE__,
		__LINE__,
		"DOWNLOAD URL : %s\n",
		url_str);
	ret_code = http_FixStrUrl((char *)url_str, url_str_len, &url);
	if (ret_code != UPNP_E_SUCCESS) {
		return ret_code;
	}
	/* make msg */
	membuffer_init(&request);
	ret_code = get_hoststr(url_str, &hoststr, &hostlen);
	if (ret_code != UPNP_E_SUCCESS) {
		return ret_code;
	}
	UpnpPrintf(UPNP_INFO,
		HTTP,
		__FILE__,
		__LINE__,
		"HOSTNAME : %s Length : %" PRIzu "\n",
		hoststr,
		hostlen);
	ret_code = http_MakeMessage(&request,
		1,
		1,
		"Q"
		"s"
		"bcDCUc",
		HTTPMETHOD_GET,
		url.pathquery.buff,
		url.pathquery.size,
		"HOST: ",
		hoststr,
		hostlen);
	if (ret_code != 0) {
		UpnpPrintf(UPNP_INFO,
			HTTP,
			__FILE__,
			__LINE__,
			"HTTP Makemessage failed\n");
		membuffer_destroy(&request);
		return ret_code;
	}
	UpnpPrintf(UPNP_INFO,
		HTTP,
		__FILE__,
		__LINE__,
		"HTTP Buffer:\n%s\n"
		"----------END--------\n",
		request.buf);
	/* get doc msg */
	ret_code = http_RequestAndResponse(&url,
		request.buf,
		request.length,
		HTTPMETHOD_GET,
		timeout_secs,
		&response);

	if (ret_code != 0) {
		httpmsg_destroy(&response.msg);
		membuffer_destroy(&request);
		return ret_code;
	}
	UpnpPrintf(UPNP_INFO, HTTP, __FILE__, __LINE__, "Response\n");
	print_http_headers(&response.msg);
	/* optional content-type */
	if (content_type) {
		if (httpmsg_find_hdr(&response.msg, HDR_CONTENT_TYPE, &ctype) ==
			NULL) {
			*content_type = '\0'; /* no content-type */
		} else {
			/* safety */
			copy_len = ctype.length < LINE_SIZE - (size_t)1
					   ? ctype.length
					   : LINE_SIZE - (size_t)1;

			memcpy(content_type, ctype.buf, copy_len);
			content_type[copy_len] = '\0';
		}
	}
	/* extract doc from msg */
	if ((*doc_length = response.msg.entity.length) == (size_t)0) {
		/* 0-length msg */
		*document = NULL;
	} else if (response.msg.status_code == HTTP_OK) {
		/*LEAK_FIX_MK */
		/* copy entity */
		entity_start = response.msg.entity.buf; /* what we want */
		msg_length = response.msg.msg.length; /* save for posterity   */
		msg_start = membuffer_detach(&response.msg.msg); /* whole msg */
		/* move entity to the start; copy null-terminator too */
		memmove(msg_start, entity_start, *doc_length + (size_t)1);
		/* save mem for body only */
		*document = realloc(
			msg_start, *doc_length + (size_t)1); /*LEAK_FIX_MK */
		/* *document = Realloc( msg_start,msg_length, *doc_length + 1 );
		 * LEAK_FIX_MK */
		/* shrink can't fail */
		assert(msg_length > *doc_length);
		assert(*document != NULL);
		if (msg_length <= *doc_length || *document == NULL)
			UpnpPrintf(UPNP_INFO,
				HTTP,
				__FILE__,
				__LINE__,
				"msg_length(%" PRIzu ") <= *doc_length(%" PRIzu
				") or document is NULL",
				msg_length,
				*doc_length);
	}
	if (response.msg.status_code == HTTP_OK) {
		ret_code = 0; /* success */
	} else {
		/* server sent error msg (not requested doc) */
		ret_code = response.msg.status_code;
	}
	httpmsg_destroy(&response.msg);
	membuffer_destroy(&request);

	return ret_code;
}

/************************************************************************
 * Function: MakeGenericMessage
 *
 * Parameters:
 *  http_method_t method;   The type of HTTP method.
 *	const char *url_str;		String as a URL
 *	membuffer *request;		Buffer containing the request
 *	uri_type *url; 			URI object containing the scheme,
 *					path query token, etc.
 *	int contentLength;		length of content
 *	const char *contentType;	Type of content
 *
 * Description:
 *	Makes the message for the HTTP POST message
 *
 * Returns:
 *	UPNP_E_INVALID_URL
 * 	UPNP_E_INVALID_PARAM
 *	UPNP_E_SUCCESS
 ************************************************************************/
int MakeGenericMessage(http_method_t method,
	const char *url_str,
	membuffer *request,
	uri_type *url,
	int contentLength,
	const char *contentType,
	const UpnpString *headers)
{
	int ret_code = 0;
	size_t hostlen = 0;
	const char *hoststr;

	UpnpPrintf(UPNP_INFO,
		HTTP,
		__FILE__,
		__LINE__,
		"URL: %s method: %d\n",
		url_str,
		method);
	ret_code = http_FixStrUrl(url_str, strlen(url_str), url);
	if (ret_code != UPNP_E_SUCCESS)
		return ret_code;
	/* make msg */
	membuffer_init(request);
	ret_code = http_MakeMessage(request,
		1,
		1,
		"Q",
		method,
		url->pathquery.buff,
		url->pathquery.size);
	/* add request headers if specified, otherwise use default headers */
	if (ret_code == 0) {
		if (headers) {
			ret_code = http_MakeMessage(request,
				1,
				1,
				"s",
				UpnpString_get_String(headers));
		} else {
			ret_code = get_hoststr(url_str, &hoststr, &hostlen);
			if (ret_code != UPNP_E_SUCCESS)
				return ret_code;
			UpnpPrintf(UPNP_INFO,
				HTTP,
				__FILE__,
				__LINE__,
				"HOSTNAME : %s Length : %" PRIzu "\n",
				hoststr,
				hostlen);
			ret_code = http_MakeMessage(request,
				1,
				1,
				"s"
				"bcDCU",
				"HOST: ",
				hoststr,
				hostlen);
		}
	}

	/* add the content-type header */
	if (ret_code == 0 && contentType) {
		ret_code = http_MakeMessage(request, 1, 1, "T", contentType);
	}
	/* add content-length header. */
	if (ret_code == 0) {
		if (contentLength >= 0)
			ret_code = http_MakeMessage(
				request, 1, 1, "Nc", (off_t)contentLength);
		else if (contentLength == UPNP_USING_CHUNKED)
			ret_code = http_MakeMessage(request, 1, 1, "Kc");
		else if (contentLength == UPNP_UNTIL_CLOSE)
			ret_code = http_MakeMessage(request, 1, 1, "c");
		else
			ret_code = UPNP_E_INVALID_PARAM;
	}
	if (ret_code != 0) {
		UpnpPrintf(UPNP_INFO,
			HTTP,
			__FILE__,
			__LINE__,
			"HTTP Makemessage failed\n");
		membuffer_destroy(request);
		return ret_code;
	}
	UpnpPrintf(UPNP_INFO,
		HTTP,
		__FILE__,
		__LINE__,
		"HTTP Buffer:\n%s\n"
		"----------END--------\n",
		request->buf);

	return UPNP_E_SUCCESS;
}

typedef struct HTTPCONNECTIONHANDLE
{
	SOCKINFO sock_info;
	int contentLength;
	http_parser_t response;
	int requestStarted;
	int cancel;
} http_connection_handle_t;

/*!
 * \brief Parses already exiting data. If not complete reads more
 * data on the connected socket. The read data is then parsed. The
 * same method is carried out for headers.
 *
 * \return integer:
 *	\li \c PARSE_OK - On Success
 *	\li \c PARSE_FAILURE - Failure to parse data correctly
 *	\li \c UPNP_E_BAD_HTTPMSG - Socket read() returns an error
 */
static int ReadResponseLineAndHeaders(
	/*! Socket information object. */
	SOCKINFO *info,
	/*! HTTP Parser object. */
	http_parser_t *parser,
	/*! Time out value. */
	int *timeout_secs,
	/*! HTTP error code returned. */
	int *http_error_code)
{
	parse_status_t status;
	int num_read;
	char buf[2 * 1024];
	int done = 0;
	int ret_code = 0;

	/*read response line */
	status = parser_parse_responseline(parser);
	switch (status) {
	case PARSE_OK:
		done = 1;
		break;
	case PARSE_INCOMPLETE:
		done = 0;
		break;
	default:
		/*error */
		return (int)status;
	}
	while (!done) {
		num_read = sock_read(info, buf, sizeof(buf), timeout_secs);
		if (num_read > 0) {
			/* append data to buffer */
			ret_code = membuffer_append(
				&parser->msg.msg, buf, (size_t)num_read);
			if (ret_code != 0) {
				/* set failure status */
				parser->http_error_code =
					HTTP_INTERNAL_SERVER_ERROR;
				return PARSE_FAILURE;
			}
			status = parser_parse_responseline(parser);
			switch (status) {
			case PARSE_OK:
				done = 1;
				break;
			case PARSE_INCOMPLETE:
				done = 0;
				break;
			default:
				/*error */
				return (int)status;
			}
		} else if (num_read == 0) {
			/* partial msg */
			*http_error_code = HTTP_BAD_REQUEST; /* or response */
			return UPNP_E_BAD_HTTPMSG;
		} else {
			/* num_read is negative, this is an error code */
			*http_error_code = parser->http_error_code;
			return num_read;
		}
	}
	status = parser_parse_headers(parser);
	if ((status == (parse_status_t)PARSE_OK) &&
		(parser->position == (parser_pos_t)POS_ENTITY)) {
		done = 1;
	} else if (status == (parse_status_t)PARSE_INCOMPLETE) {
		done = 0;
	} else {
		/*error */
		return (int)status;
	}
	/*read headers */
	while (!done) {
		num_read = sock_read(info, buf, sizeof(buf), timeout_secs);
		if (num_read > 0) {
			/* append data to buffer */
			ret_code = membuffer_append(
				&parser->msg.msg, buf, (size_t)num_read);
			if (ret_code != 0) {
				/* set failure status */
				parser->http_error_code =
					HTTP_INTERNAL_SERVER_ERROR;
				return PARSE_FAILURE;
			}
			status = parser_parse_headers(parser);
			if (status == (parse_status_t)PARSE_OK &&
				parser->position == (parser_pos_t)POS_ENTITY)
				done = 1;
			else if (status == (parse_status_t)PARSE_INCOMPLETE)
				done = 0;
			else
				/*error */
				return (int)status;
		} else if (num_read == 0) {
			/* partial msg */
			*http_error_code = HTTP_BAD_REQUEST; /* or response */
			return UPNP_E_BAD_HTTPMSG;
		} else {
			*http_error_code = parser->http_error_code;
			return num_read;
		}
	}

	return PARSE_OK;
}

/************************************************************************
 * Function: http_HttpGetProgress
 *
 * Parameters:
 *	IN void *Handle;	Handle to the HTTP get object
 *	OUT size_t *length;	Buffer to get the read and parsed data
 *	OUT size_t *total;	Size of tge buffer passed
 *
 * Description:
 *	Extracts information from the Handle to the HTTP get object.
 *
 * Return: int
 *	UPNP_E_SUCCESS		- On Success
 *	UPNP_E_INVALID_PARAM	- Invalid Parameter
 ************************************************************************/
int http_HttpGetProgress(void *Handle, size_t *length, size_t *total)
{
	http_connection_handle_t *handle = Handle;

	if (!handle || !length || !total) {
		return UPNP_E_INVALID_PARAM;
	}
	*length = handle->response.msg.entity.length;
	*total = handle->response.content_length;

	return UPNP_E_SUCCESS;
}

/************************************************************************
 * Function: http_CancelHttpGet
 *
 * Parameters:
 *	IN void *Handle;	Handle to HTTP get object
 *
 * Description:
 *	Set the cancel flag of the HttpGet handle
 *
 * Return: int
 *	UPNP_E_SUCCESS		- On Success
 *	UPNP_E_INVALID_PARAM	- Invalid Parameter
 ************************************************************************/
int http_CancelHttpGet(void *Handle)
{
	http_connection_handle_t *handle = Handle;

	if (!handle)
		return UPNP_E_INVALID_PARAM;
	handle->cancel = 1;

	return UPNP_E_SUCCESS;
}

int http_OpenHttpConnection(const char *url_str, void **Handle, int timeout)
{
	int ret_code;
	size_t sockaddr_len;
	SOCKET tcp_connection;
	http_connection_handle_t *handle = NULL;
	uri_type url;
	(void)timeout; /* Unused parameter */
	if (!url_str || !Handle)
		return UPNP_E_INVALID_PARAM;
	*Handle = handle;
	/* parse url_str */
	ret_code = http_FixStrUrl(url_str, strlen(url_str), &url);
	if (ret_code != UPNP_E_SUCCESS)
		return ret_code;
	/* create the handle */
	handle = malloc(sizeof(http_connection_handle_t));
	if (!handle) {
		return UPNP_E_OUTOF_MEMORY;
	}
	handle->requestStarted = 0;
	memset(&handle->response, 0, sizeof(handle->response));
	/* connect to the server */
	tcp_connection =
		socket(url.hostport.IPaddress.ss_family, SOCK_STREAM, 0);
	if (tcp_connection == INVALID_SOCKET) {
		ret_code = UPNP_E_SOCKET_ERROR;
		goto errorHandler;
	}
	if (sock_init(&handle->sock_info, tcp_connection) != UPNP_E_SUCCESS) {
		sock_destroy(&handle->sock_info, SD_BOTH);
		ret_code = UPNP_E_SOCKET_ERROR;
		goto errorHandler;
	}
	sockaddr_len = url.hostport.IPaddress.ss_family == AF_INET6
			       ? sizeof(struct sockaddr_in6)
			       : sizeof(struct sockaddr_in);
	ret_code = private_connect(handle->sock_info.socket,
		(struct sockaddr *)&(url.hostport.IPaddress),
		(socklen_t)sockaddr_len);
	if (ret_code == -1) {
		sock_destroy(&handle->sock_info, SD_BOTH);
		ret_code = UPNP_E_SOCKET_CONNECT;
		goto errorHandler;
	}
#ifdef UPNP_ENABLE_OPEN_SSL
	/* For HTTPS connections start the TLS/SSL handshake. */
	if (token_string_casecmp(&url.scheme, "https") == 0) {
		ret_code = sock_ssl_connect(&handle->sock_info);
		if (ret_code != UPNP_E_SUCCESS) {
			sock_destroy(&handle->sock_info, SD_BOTH);
			goto errorHandler;
		}
	}
#endif
errorHandler:
	*Handle = handle;
	return ret_code;
}

int http_MakeHttpRequest(Upnp_HttpMethod method,
	const char *url_str,
	void *Handle,
	UpnpString *headers,
	const char *contentType,
	int contentLength,
	int timeout)
{
	int ret_code;
	membuffer request;
	http_connection_handle_t *handle = Handle;
	uri_type url;
	if (!url_str || !Handle)
		return UPNP_E_INVALID_PARAM;
	if (handle->requestStarted) {
		/* TODO: Log an error that a previous request is already in
		 * progress. */
	}
	handle->requestStarted = 1;
	handle->cancel = 0;
	ret_code = MakeGenericMessage((http_method_t)method,
		url_str,
		&request,
		&url,
		contentLength,
		contentType,
		headers);
	if (ret_code != UPNP_E_SUCCESS)
		return ret_code;
	/* send request */
	ret_code = http_SendMessage(
		&handle->sock_info, &timeout, "b", request.buf, request.length);
	membuffer_destroy(&request);
	httpmsg_destroy(&handle->response.msg);
	parser_response_init(&handle->response, (http_method_t)method);
	return ret_code;
}

int http_WriteHttpRequest(void *Handle, char *buf, size_t *size, int timeout)
{
	http_connection_handle_t *handle = (http_connection_handle_t *)Handle;
	char *tempbuf = NULL;
	size_t tempbufSize = 0;
	int freeTempbuf = 0;
	int numWritten = 0;

	if (!handle || !size || !buf) {
		if (size)
			*size = 0;
		return UPNP_E_INVALID_PARAM;
	}
	if (handle->contentLength == UPNP_USING_CHUNKED) {
		if (*size) {
			size_t tempSize = 0;
			tempbuf = malloc(
				*size + CHUNK_HEADER_SIZE + CHUNK_TAIL_SIZE);
			if (!tempbuf)
				return UPNP_E_OUTOF_MEMORY;
			/* begin chunk */
			sprintf(tempbuf, "%zx\r\n", *size);
			tempSize = strlen(tempbuf);
			memcpy(tempbuf + tempSize, buf, *size);
			memcpy(tempbuf + tempSize + *size, "\r\n", 2);
			/* end of chunk */
			tempbufSize = tempSize + *size + 2;
			freeTempbuf = 1;
		}
	} else {
		tempbuf = buf;
		tempbufSize = *size;
	}
	numWritten =
		sock_write(&handle->sock_info, tempbuf, tempbufSize, &timeout);
	if (freeTempbuf)
		free(tempbuf);
	if (numWritten < 0) {
		*size = 0;
		return numWritten;
	} else {
		*size = (size_t)numWritten;
		return UPNP_E_SUCCESS;
	}
}

int http_EndHttpRequest(void *Handle, int timeout)
{
	int ret_code = 0;
	const char *zero_cr_lf = "0\r\n\r\n";
	http_connection_handle_t *handle = Handle;
	if (!handle)
		return UPNP_E_INVALID_PARAM;
	if (!handle->requestStarted) {
		return UPNP_E_SUCCESS;
	}
	handle->requestStarted = 0;
	if (handle->contentLength == UPNP_USING_CHUNKED)
		/*send last chunk */
		ret_code = sock_write(&handle->sock_info,
			zero_cr_lf,
			strlen(zero_cr_lf),
			&timeout);

	return ret_code >= 0 ? UPNP_E_SUCCESS : UPNP_E_SOCKET_WRITE;
}

int http_GetHttpResponse(void *Handle,
	UpnpString *headers,
	char **contentType,
	int *contentLength,
	int *httpStatus,
	int timeout)
{
	int ret_code;
	int http_error_code;
	memptr ctype;
	http_connection_handle_t *handle = Handle;
	parse_status_t status;

	status = (parse_status_t)ReadResponseLineAndHeaders(&handle->sock_info,
		&handle->response,
		&timeout,
		&http_error_code);
	if (status != (parse_status_t)PARSE_OK) {
		ret_code = UPNP_E_BAD_RESPONSE;
		goto errorHandler;
	}
	status = parser_get_entity_read_method(&handle->response);
	switch (status) {
	case PARSE_CONTINUE_1:
	case PARSE_SUCCESS:
		break;
	default:
		ret_code = UPNP_E_BAD_RESPONSE;
		goto errorHandler;
	}
	ret_code = UPNP_E_SUCCESS;
	if (httpStatus) {
		*httpStatus = handle->response.msg.status_code;
	}
	if (contentType) {
		if (!httpmsg_find_hdr(
			    &handle->response.msg, HDR_CONTENT_TYPE, &ctype))
			/* no content-type */
			*contentType = NULL;
		else
			*contentType = ctype.buf;
	}
	if (contentLength) {
		if (handle->response.position == (parser_pos_t)POS_COMPLETE)
			*contentLength = 0;
		else if (handle->response.ent_position == ENTREAD_USING_CHUNKED)
			*contentLength = UPNP_USING_CHUNKED;
		else if (handle->response.ent_position == ENTREAD_USING_CLEN)
			*contentLength = (int)handle->response.content_length;
		else if (handle->response.ent_position == ENTREAD_UNTIL_CLOSE)
			*contentLength = UPNP_UNTIL_CLOSE;
	}

	if (headers) {
		copy_msg_headers(&handle->response.msg.headers, headers);
	}

errorHandler:
	if (ret_code != UPNP_E_SUCCESS)
		httpmsg_destroy(&handle->response.msg);
	return ret_code;
}

int http_ReadHttpResponse(void *Handle, char *buf, size_t *size, int timeout)
{
	http_connection_handle_t *handle = Handle;
	parse_status_t status;
	int num_read;
	int ok_on_close = 0;
	char tempbuf[2 * 1024];
	int ret_code = 0;

	if (!handle || !size || (*size > 0 && !buf)) {
		if (size)
			*size = 0;
		return UPNP_E_INVALID_PARAM;
	}
	/* first parse what has already been gotten */
	if (handle->response.position != POS_COMPLETE)
		status = parser_parse_entity(&handle->response);
	else
		status = PARSE_SUCCESS;
	if (status == PARSE_INCOMPLETE_ENTITY)
		/* read until close */
		ok_on_close = 1;
	else if ((status != PARSE_SUCCESS) && (status != PARSE_CONTINUE_1) &&
		 (status != PARSE_INCOMPLETE)) {
		/*error */
		*size = 0;
		return UPNP_E_BAD_RESPONSE;
	}
	/* read more if necessary entity */
	while (handle->response.msg.amount_discarded + *size >
			handle->response.msg.entity.length &&
		!handle->cancel && handle->response.position != POS_COMPLETE) {
		num_read = sock_read(
			&handle->sock_info, tempbuf, sizeof(tempbuf), &timeout);
		if (num_read > 0) {
			/* append data to buffer */
			ret_code = membuffer_append(&handle->response.msg.msg,
				tempbuf,
				(size_t)num_read);
			if (ret_code != 0) {
				/* set failure status */
				handle->response.http_error_code =
					HTTP_INTERNAL_SERVER_ERROR;
				*size = 0;
				return PARSE_FAILURE;
			}
			status = parser_parse_entity(&handle->response);
			if (status == PARSE_INCOMPLETE_ENTITY) {
				/* read until close */
				ok_on_close = 1;
			} else if ((status != PARSE_SUCCESS) &&
				   (status != PARSE_CONTINUE_1) &&
				   (status != PARSE_INCOMPLETE)) {
				/*error */
				*size = 0;
				return UPNP_E_BAD_RESPONSE;
			}
		} else if (num_read == 0) {
			if (ok_on_close) {
				UpnpPrintf(UPNP_INFO,
					HTTP,
					__FILE__,
					__LINE__,
					"<<< (RECVD) "
					"<<<\n%s\n-----------------\n",
					handle->response.msg.msg.buf);
				handle->response.position = POS_COMPLETE;
			} else {
				/* partial msg */
				*size = 0;
				handle->response.http_error_code =
					HTTP_BAD_REQUEST; /* or response */
				return UPNP_E_BAD_HTTPMSG;
			}
		} else {
			*size = 0;
			return num_read;
		}
	}
	if (handle->cancel) {
		return UPNP_E_CANCELED;
	}
	/* truncate size to fall within available data */
	if (handle->response.msg.amount_discarded + *size >
		handle->response.msg.entity.length)
		*size = handle->response.msg.entity.length -
			handle->response.msg.amount_discarded;
	/* copy data to user buffer. delete copied data */
	if (*size > 0) {
		memcpy(buf,
			&handle->response.msg.msg
				.buf[handle->response.entity_start_position],
			*size);
		membuffer_delete(&handle->response.msg.msg,
			handle->response.entity_start_position,
			*size);
		/* update scanner position. needed for chunked transfers */
		handle->response.scanner.cursor -= *size;
		/* update amount discarded */
		handle->response.msg.amount_discarded += *size;
	}

	return UPNP_E_SUCCESS;
}

int http_CloseHttpConnection(void *Handle)
{
	http_connection_handle_t *handle = Handle;
	if (!handle)
		return UPNP_E_INVALID_PARAM;
	/*should shutdown completely */
	sock_destroy(&handle->sock_info, SD_BOTH);
	httpmsg_destroy(&handle->response.msg);
	free(handle);
	return UPNP_E_SUCCESS;
}

/************************************************************************
 * Function: http_SendStatusResponse
 *
 * Parameters:
 *	IN SOCKINFO *info;		Socket information object
 *	IN int http_status_code;	error code returned while making
 *					or sending the response message
 *	IN int request_major_version;	request major version
 *	IN int request_minor_version;	request minor version
 *
 * Description:
 *	Generate a response message for the status query and send the
 *	status response.
 *
 * Return: int
 *	0 -- success
 *	UPNP_E_OUTOF_MEMORY
 *	UPNP_E_SOCKET_WRITE
 *	UPNP_E_TIMEDOUT
 ************************************************************************/
int http_SendStatusResponse(SOCKINFO *info,
	int http_status_code,
	int request_major_version,
	int request_minor_version)
{
	int response_major, response_minor;
	membuffer membuf;
	int ret;
	int timeout;

	http_CalcResponseVersion(request_major_version,
		request_minor_version,
		&response_major,
		&response_minor);
	membuffer_init(&membuf);
	membuf.size_inc = (size_t)70;
	/* response start line */
	ret = http_MakeMessage(&membuf,
		response_major,
		response_minor,
		"RSCB",
		http_status_code,
		http_status_code);
	if (ret == 0) {
		timeout = HTTP_DEFAULT_TIMEOUT;
		ret = http_SendMessage(
			info, &timeout, "b", membuf.buf, membuf.length);
	}
	membuffer_destroy(&membuf);

	return ret;
}

int http_MakeMessage(membuffer *buf,
	int http_major_version,
	int http_minor_version,
	const char *fmt,
	...)
{
	char c;
	char *s = NULL;
	size_t num;
	off_t bignum;
	size_t length;
	time_t *loc_time;
	time_t curr_time;
	struct tm date_storage;
	struct tm *date;
	const char *start_str;
	const char *end_str;
	int status_code;
	const char *status_msg;
	http_method_t method;
	const char *method_str;
	const char *url_str;
	const char *temp_str;
	uri_type url;
	uri_type *uri_ptr;
	int error_code = 0;
	va_list argp;
	char tempbuf[200];
	const char *weekday_str = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
	const char *month_str = "Jan\0Feb\0Mar\0Apr\0May\0Jun\0"
				"Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
	int rc = 0;

	memset(tempbuf, 0, sizeof(tempbuf));
	va_start(argp, fmt);
	while ((c = *fmt++)) {
		if (c == 'E') {
			/* list of extra headers */
			UpnpListIter pos;
			UpnpListHead *head;
			UpnpExtraHeaders *extra;
			const DOMString resp;
			head = (UpnpListHead *)va_arg(argp, UpnpListHead *);
			if (head) {
				for (pos = UpnpListBegin(head);
					pos != UpnpListEnd(head);
					pos = UpnpListNext(head, pos)) {
					extra = (UpnpExtraHeaders *)pos;
					resp = UpnpExtraHeaders_get_resp(extra);
					if (resp) {
						if (membuffer_append(buf,
							    resp,
							    strlen(resp)))
							goto error_handler;
						if (membuffer_append(buf,
							    "\r\n",
							    (size_t)2))
							goto error_handler;
					}
				}
			}
		} else if (c == 's') {
			/* C string */
			s = (char *)va_arg(argp, char *);
			assert(s);
			UpnpPrintf(UPNP_ALL,
				HTTP,
				__FILE__,
				__LINE__,
				"Adding a string : %s\n",
				s);
			if (membuffer_append(buf, s, strlen(s)))
				goto error_handler;
		} else if (c == 'K') {
			/* Add Chunky header */
			if (membuffer_append_str(
				    buf, "TRANSFER-ENCODING: chunked\r\n"))
				goto error_handler;
		} else if (c == 'A') {
			/* Add Access-Control-Allow-Origin header only if
			 * set by UpnpSetWebServerCorsString */
			struct SendInstruction *RespInstr;
			RespInstr = (struct SendInstruction *)va_arg(
				argp, struct SendInstruction *);
			assert(RespInstr);
			if (RespInstr->CorsHeader &&
				strcmp(RespInstr->CorsHeader, "") &&
				http_MakeMessage(buf,
					http_major_version,
					http_minor_version,
					"ssc",
					"Access-Control-Allow-Origin: ",
					RespInstr->CorsHeader) != 0)
				goto error_handler;
		} else if (c == 'G') {
			/* Add Range header */
			struct SendInstruction *RespInstr;
			RespInstr = (struct SendInstruction *)va_arg(
				argp, struct SendInstruction *);
			assert(RespInstr);
			/* connection header */
			if (membuffer_append(buf,
				    RespInstr->RangeHeader,
				    strlen(RespInstr->RangeHeader)))
				goto error_handler;
		} else if (c == 'b') {
			/* mem buffer */
			s = (char *)va_arg(argp, char *);
			UpnpPrintf(UPNP_ALL,
				HTTP,
				__FILE__,
				__LINE__,
				"Adding a char Buffer starting with: %c\n",
				(int)s[0]);
			assert(s);
			length = (size_t)va_arg(argp, size_t);
			if (membuffer_append(buf, s, length))
				goto error_handler;
		} else if (c == 'c') {
			/* crlf */
			if (membuffer_append(buf, "\r\n", (size_t)2))
				goto error_handler;
		} else if (c == 'd') {
			/* integer */
			num = (size_t)va_arg(argp, int);
			rc = snprintf(tempbuf, sizeof(tempbuf), "%" PRIzu, num);
			if (rc < 0 || (unsigned int)rc >= sizeof(tempbuf) ||
				membuffer_append(buf, tempbuf, strlen(tempbuf)))
				goto error_handler;
		} else if (c == 'h') {
			/* off_t */
			bignum = (off_t)va_arg(argp, off_t);
			rc = snprintf(tempbuf,
				sizeof(tempbuf),
				"%" PRId64,
				(int64_t)bignum);
			if (rc < 0 || (unsigned int)rc >= sizeof(tempbuf) ||
				membuffer_append(buf, tempbuf, strlen(tempbuf)))
				goto error_handler;
		} else if (c == 't' || c == 'D') {
			/* date */
			if (c == 'D') {
				/* header */
				start_str = "DATE: ";
				end_str = "\r\n";
				curr_time = time(NULL);
				loc_time = &curr_time;
			} else {
				/* date value only */
				start_str = end_str = "";
				loc_time = (time_t *)va_arg(argp, time_t *);
			}
			assert(loc_time);
			date = http_gmtime_r(loc_time, &date_storage);
			if (date == NULL)
				goto error_handler;
			rc = snprintf(tempbuf,
				sizeof(tempbuf),
				"%s%s, %02d %s %d %02d:%02d:%02d GMT%s",
				start_str,
				&weekday_str[date->tm_wday * 4],
				date->tm_mday,
				&month_str[date->tm_mon * 4],
				date->tm_year + 1900,
				date->tm_hour,
				date->tm_min,
				date->tm_sec,
				end_str);
			if (rc < 0 || (unsigned int)rc >= sizeof(tempbuf) ||
				membuffer_append(buf, tempbuf, strlen(tempbuf)))
				goto error_handler;
		} else if (c == 'L') {
			/* Add CONTENT-LANGUAGE header only if
			 * WEB_SERVER_CONTENT_LANGUAGE */
			/* is not empty and if Accept-Language header is not
			 * empty */
			struct SendInstruction *RespInstr;
			RespInstr = (struct SendInstruction *)va_arg(
				argp, struct SendInstruction *);
			assert(RespInstr);
			if (strcmp(RespInstr->AcceptLanguageHeader, "") &&
				strcmp(WEB_SERVER_CONTENT_LANGUAGE, "") &&
				http_MakeMessage(buf,
					http_major_version,
					http_minor_version,
					"ssc",
					"CONTENT-LANGUAGE: ",
					WEB_SERVER_CONTENT_LANGUAGE) != 0)
				goto error_handler;
		} else if (c == 'C') {
			if ((http_major_version > 1) ||
				(http_major_version == 1 &&
					http_minor_version == 1)) {
				/* connection header */
				if (membuffer_append_str(
					    buf, "CONNECTION: close\r\n"))
					goto error_handler;
			}
		} else if (c == 'N') {
			/* content-length header */
			bignum = (off_t)va_arg(argp, off_t);
			assert(bignum >= 0);
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "shc",
				    "CONTENT-LENGTH: ",
				    bignum) != 0)
				goto error_handler;
			/* Add accept ranges */
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "sc",
				    "Accept-Ranges: bytes") != 0)
				goto error_handler;
		} else if (c == 'S' || c == 'U') {
			/* SERVER or USER-AGENT header */
			temp_str = (c == 'S') ? "SERVER: " : "USER-AGENT: ";
			get_sdk_info(tempbuf, sizeof(tempbuf));
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "ss",
				    temp_str,
				    tempbuf) != 0)
				goto error_handler;
		} else if (c == 'X') {
			/* C string */
			s = (char *)va_arg(argp, char *);
			assert(s);
			if (membuffer_append_str(buf, "X-User-Agent: ") != 0)
				goto error_handler;
			if (membuffer_append(buf, s, strlen(s)) != 0)
				goto error_handler;
		} else if (c == 'R') {
			/* response start line */
			/*   e.g.: 'HTTP/1.1 200 OK' code */
			status_code = (int)va_arg(argp, int);
			assert(status_code > 0);
			rc = snprintf(tempbuf,
				sizeof(tempbuf),
				"HTTP/%d.%d %d ",
				http_major_version,
				http_minor_version,
				status_code);
			/* str */
			status_msg = http_get_code_text(status_code);
			if (rc < 0 || (unsigned int)rc >= sizeof(tempbuf) ||
				http_MakeMessage(buf,
					http_major_version,
					http_minor_version,
					"ssc",
					tempbuf,
					status_msg) != 0)
				goto error_handler;
		} else if (c == 'B') {
			/* body of a simple reply */
			status_code = (int)va_arg(argp, int);
			rc = snprintf(tempbuf,
				sizeof(tempbuf),
				"%s%d %s%s",
				"<html><body><h1>",
				status_code,
				http_get_code_text(status_code),
				"</h1></body></html>");
			if (rc < 0 || (unsigned int)rc >= sizeof(tempbuf))
				goto error_handler;
			bignum = (off_t)strlen(tempbuf);
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "NTcs",
				    bignum,	  /* content-length */
				    "text/html",  /* content-type */
				    tempbuf) != 0 /* body */
			)
				goto error_handler;
		} else if (c == 'Q') {
			/* request start line */
			/* GET /foo/bar.html HTTP/1.1\r\n */
			method = (http_method_t)va_arg(argp, http_method_t);
			method_str = method_to_str(method);
			url_str = (const char *)va_arg(argp, const char *);
			num = (size_t)va_arg(
				argp, size_t); /* length of url_str */
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "ssbsdsdc",
				    method_str, /* method */
				    " ",
				    url_str,
				    num, /* url */
				    " HTTP/",
				    http_major_version,
				    ".",
				    http_minor_version) != 0)
				goto error_handler;
		} else if (c == 'q') {
			/* request start line and HOST header */
			method = (http_method_t)va_arg(argp, http_method_t);
			uri_ptr = (uri_type *)va_arg(argp, uri_type *);
			assert(uri_ptr);
			if (http_FixUrl(uri_ptr, &url) != 0) {
				error_code = UPNP_E_INVALID_URL;
				goto error_handler;
			}
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "Q"
				    "sbc",
				    method,
				    url.pathquery.buff,
				    url.pathquery.size,
				    "HOST: ",
				    url.hostport.text.buff,
				    url.hostport.text.size) != 0)
				goto error_handler;
		} else if (c == 'T') {
			/* content type header */
			temp_str = (const char *)va_arg(
				argp, const char *); /* type/subtype format */
			if (http_MakeMessage(buf,
				    http_major_version,
				    http_minor_version,
				    "ssc",
				    "CONTENT-TYPE: ",
				    temp_str) != 0)
				goto error_handler;
		} else {
			assert(0);
		}
	}
	goto ExitFunction;

error_handler:
	/* Default is out of memory error. */
	if (!error_code)
		error_code = UPNP_E_OUTOF_MEMORY;
	membuffer_destroy(buf);

ExitFunction:
	va_end(argp);
	return error_code;
}

/************************************************************************
 * Function: http_CalcResponseVersion
 *
 * Parameters:
 *	IN int request_major_vers;	Request major version
 *	IN int request_minor_vers;	Request minor version
 *	OUT int* response_major_vers;	Response major version
 *	OUT int* response_minor_vers;	Response minor version
 *
 * Description:
 *	Calculate HTTP response versions based on the request versions.
 *
 * Return: void
 ************************************************************************/
void http_CalcResponseVersion(int request_major_vers,
	int request_minor_vers,
	int *response_major_vers,
	int *response_minor_vers)
{
	if ((request_major_vers > 1) ||
		(request_major_vers == 1 && request_minor_vers >= 1)) {
		*response_major_vers = 1;
		*response_minor_vers = 1;
	} else {
		*response_major_vers = request_major_vers;
		*response_minor_vers = request_minor_vers;
	}
}

/************************************************************************
 * Function: MakeGetMessageEx
 *
 * Parameters:
 *	const char *url_str;	String as a URL
 *	membuffer *request;	Buffer containing the request
 *	uri_type *url; 		URI object containing the scheme, path
 *				query token, etc.
 *
 * Description:
 *	Makes the message for the HTTP GET method
 *
 * Returns:
 *	UPNP_E_INVALID_URL
 * 	Error Codes returned by http_MakeMessage
 *	UPNP_E_SUCCESS
 ************************************************************************/
int MakeGetMessageEx(const char *url_str,
	membuffer *request,
	uri_type *url,
	struct SendInstruction *pRangeSpecifier)
{
	size_t url_str_len;
	int ret_code = UPNP_E_SUCCESS;
	size_t hostlen = 0;
	const char *hoststr;

	url_str_len = strlen(url_str);
	do {
		UpnpPrintf(UPNP_INFO,
			HTTP,
			__FILE__,
			__LINE__,
			"DOWNLOAD URL : %s\n",
			url_str);
		ret_code = http_FixStrUrl(url_str, url_str_len, url);
		if (ret_code != UPNP_E_SUCCESS) {
			break;
		}
		/* make msg */
		membuffer_init(request);
		ret_code = get_hoststr(url_str, &hoststr, &hostlen);
		if (ret_code != UPNP_E_SUCCESS) {
			break;
		}
		UpnpPrintf(UPNP_INFO,
			HTTP,
			__FILE__,
			__LINE__,
			"HOSTNAME : %s Length : %" PRIzu "\n",
			hoststr,
			hostlen);
		ret_code = http_MakeMessage(request,
			1,
			1,
			"Q"
			"s"
			"bc"
			"GDCUc",
			HTTPMETHOD_GET,
			url->pathquery.buff,
			url->pathquery.size,
			"HOST: ",
			hoststr,
			hostlen,
			pRangeSpecifier);
		if (ret_code != 0) {
			UpnpPrintf(UPNP_INFO,
				HTTP,
				__FILE__,
				__LINE__,
				"HTTP Makemessage failed\n");
			membuffer_destroy(request);
			return ret_code;
		}
	} while (0);
	UpnpPrintf(UPNP_INFO,
		HTTP,
		__FILE__,
		__LINE__,
		"HTTP Buffer:\n%s\n"
		"----------END--------\n",
		request->buf);

	return ret_code;
}

#define SIZE_RANGE_BUFFER 50

/************************************************************************
 * Function: http_OpenHttpGetEx
 *
 * Parameters:
 *	IN const char *url_str;		String as a URL
 *	IN OUT void **Handle;		Pointer to buffer to store HTTP
 *					post handle
 *	IN OUT char **contentType;	Type of content
 *	OUT int *contentLength;		length of content
 *	OUT int *httpStatus;		HTTP status returned on receiving a
 *					response message
 *	IN int timeout;			time out value
 *
 * Description:
 *	Makes the HTTP GET message, connects to the peer,
 *	sends the HTTP GET request, gets the response and parses the
 *	response.
 *
 * Return: int
 *	UPNP_E_SUCCESS		- On Success
 *	UPNP_E_INVALID_PARAM	- Invalid Parameters
 *	UPNP_E_OUTOF_MEMORY
 *	UPNP_E_SOCKET_ERROR
 *	UPNP_E_BAD_RESPONSE
 ************************************************************************/
int http_OpenHttpGetEx(const char *url_str,
	void **Handle,
	char **contentType,
	int *contentLength,
	int *httpStatus,
	int lowRange,
	int highRange,
	int timeout)
{
	int http_error_code;
	memptr ctype;
	SOCKET tcp_connection;
	size_t sockaddr_len;
	membuffer request;
	http_connection_handle_t *handle = NULL;
	uri_type url;
	parse_status_t status;
	int errCode = UPNP_E_SUCCESS;
	/* char rangeBuf[SIZE_RANGE_BUFFER]; */
	struct SendInstruction rangeBuf;
	int rc = 0;

	membuffer_init(&request);

	do {
		/* Checking Input parameters */
		if (!url_str || !Handle || !contentType || !httpStatus) {
			errCode = UPNP_E_INVALID_PARAM;
			break;
		}
		/* Initialize output parameters */
		*httpStatus = 0;
		*Handle = handle;
		*contentType = NULL;
		*contentLength = 0;
		if (lowRange > highRange) {
			errCode = UPNP_E_INTERNAL_ERROR;
			break;
		}
		memset(&rangeBuf, 0, sizeof(rangeBuf));
		rc = snprintf(rangeBuf.RangeHeader,
			sizeof(rangeBuf.RangeHeader),
			"Range: bytes=%d-%d\r\n",
			lowRange,
			highRange);
		if (rc < 0 || (unsigned int)rc >= sizeof(rangeBuf.RangeHeader))
			break;
		membuffer_init(&request);
		errCode = MakeGetMessageEx(url_str, &request, &url, &rangeBuf);
		if (errCode != UPNP_E_SUCCESS)
			break;
		handle = (http_connection_handle_t *)malloc(
			sizeof(http_connection_handle_t));
		if (!handle) {
			errCode = UPNP_E_OUTOF_MEMORY;
			break;
		}
		memset(handle, 0, sizeof(*handle));
		parser_response_init(&handle->response, HTTPMETHOD_GET);
		tcp_connection = socket(
			(int)url.hostport.IPaddress.ss_family, SOCK_STREAM, 0);
		if (tcp_connection == INVALID_SOCKET) {
			errCode = UPNP_E_SOCKET_ERROR;
			free(handle);
			break;
		}
		if (sock_init(&handle->sock_info, tcp_connection) !=
			UPNP_E_SUCCESS) {
			sock_destroy(&handle->sock_info, SD_BOTH);
			errCode = UPNP_E_SOCKET_ERROR;
			free(handle);
			break;
		}
		sockaddr_len = url.hostport.IPaddress.ss_family == AF_INET6
				       ? sizeof(struct sockaddr_in6)
				       : sizeof(struct sockaddr_in);
		errCode = private_connect(handle->sock_info.socket,
			(struct sockaddr *)&(url.hostport.IPaddress),
			(socklen_t)sockaddr_len);
		if (errCode == -1) {
			sock_destroy(&handle->sock_info, SD_BOTH);
			errCode = UPNP_E_SOCKET_CONNECT;
			free(handle);
			break;
		}
		/* send request */
		errCode = http_SendMessage(&handle->sock_info,
			&timeout,
			"b",
			request.buf,
			request.length);
		if (errCode != UPNP_E_SUCCESS) {
			sock_destroy(&handle->sock_info, SD_BOTH);
			free(handle);
			break;
		}
		if (ReadResponseLineAndHeaders(&handle->sock_info,
			    &handle->response,
			    &timeout,
			    &http_error_code) != (int)PARSE_OK) {
			errCode = UPNP_E_BAD_RESPONSE;
			free(handle);
			break;
		}
		status = parser_get_entity_read_method(&handle->response);
		if (status != (parse_status_t)PARSE_CONTINUE_1 &&
			status != (parse_status_t)PARSE_SUCCESS) {
			errCode = UPNP_E_BAD_RESPONSE;
			free(handle);
			break;
		}
		*httpStatus = handle->response.msg.status_code;
		errCode = UPNP_E_SUCCESS;

		if (!httpmsg_find_hdr(
			    &handle->response.msg, HDR_CONTENT_TYPE, &ctype))
			/* no content-type */
			*contentType = NULL;
		else
			*contentType = ctype.buf;
		if (handle->response.position == (parser_pos_t)POS_COMPLETE)
			*contentLength = 0;
		else if (handle->response.ent_position == ENTREAD_USING_CHUNKED)
			*contentLength = UPNP_USING_CHUNKED;
		else if (handle->response.ent_position == ENTREAD_USING_CLEN)
			*contentLength = (int)handle->response.content_length;
		else if (handle->response.ent_position == ENTREAD_UNTIL_CLOSE)
			*contentLength = UPNP_UNTIL_CLOSE;
		*Handle = handle;
	} while (0);

	membuffer_destroy(&request);

	return errCode;
}

/************************************************************************
 * Function: get_sdk_info
 *
 * Parameters:
 *	OUT char *info;	buffer to store the operating system information
 *	IN size_t infoSize; size of buffer
 *
 * Description:
 *	Returns the server information for the operating system
 *
 * Return:
 *	UPNP_INLINE void
 ************************************************************************/
/* 'info' should have a size of at least 100 bytes */
void get_sdk_info(char *info, size_t infoSize)
{
#ifdef UPNP_ENABLE_UNSPECIFIED_SERVER
	snprintf(info, infoSize, "Unspecified, UPnP/1.0, Unspecified\r\n");
#else /* UPNP_ENABLE_UNSPECIFIED_SERVER */
	#ifdef _WIN32
	snprintf(info,
		infoSize,
		"UPnP/1.0, Portable SDK for UPnP devices/" UPNP_VERSION_STRING
		"on windows\r\n");
	#else
	int ret_code;
	struct utsname sys_info;

	ret_code = uname(&sys_info);
	if (ret_code == -1)
		*info = '\0';
	snprintf(info,
		infoSize,
		"%s/%s, UPnP/1.0, Portable SDK for UPnP "
		"devices/" UPNP_VERSION_STRING "\r\n",
		sys_info.sysname,
		sys_info.release);
	#endif
#endif /* UPNP_ENABLE_UNSPECIFIED_SERVER */
}
