/*	$NetBSD: file_http.cpp,v 1.12 2008/04/28 20:23:20 martin Exp $	*/

/*-
 * Copyright (c) 2001, 2002, 2004 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by UCHIYAMA Yasushi.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 THE FOUNDATION 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.
 */

#include <file.h>
#include <file_http.h>
#include <console.h>
#include <libsa_string.h>

#define	wcsicmp		_wcsicmp
static int WCE210_WSAStartup(WORD, LPWSADATA);
static int WCE210_WSACleanup(void);

HttpFile::HttpFile(Console *&cons)
	: File(cons),
	  _req_get("GET "),
	  _req_head("HEAD "),
	  _req_host(" HTTP/1.0\r\nHOST: "),
	  _req_ua("\r\nUser-Agent: HPCBOOT/ZERO(1st impact; Windows CE; "
#if defined MIPS
	      "MIPS"
#elif defined ARM
	      "ARM"
#elif defined SH3
	      "SH3"
#elif defined SH4
	      "SH4"
#else
	      "Unknown"
#endif
	      ")\r\n\r\n")
{

	_server_name[0] = '\0';
	_debug = 1;
	_memory_cache = TRUE;
	//    _memory_cache = FALSE; // not recomended.
	_buffer = 0;
	_reset_state();
	DPRINTF((TEXT("FileManager: HTTP\n")));

#if _WIN32_WCE <= 200
	_wsa_startup = WCE210_WSAStartup;
	_wsa_cleanup = WCE210_WSACleanup;
#else
	if (WinCEVersion.dwMajorVersion > 3 ||
	    (WinCEVersion.dwMajorVersion > 2) &&
	    (WinCEVersion.dwMinorVersion >= 11)) {
		_wsa_startup = WSAStartup;
		_wsa_cleanup = WSACleanup;
	} else {
		_wsa_startup = WCE210_WSAStartup;
		_wsa_cleanup = WCE210_WSACleanup;
	}
#endif
}

int
WCE210_WSAStartup(WORD ver, LPWSADATA data)
{

	data->wVersion		= ver;
	data->wHighVersion	= ver;
	data->szDescription[0]	= '\0';
	data->szSystemStatus[0]	= '\0';
	data->iMaxSockets	= 10;
	data->iMaxUdpDg		= 0;
	data->lpVendorInfo	= NULL;

	return (0);
}

int
WCE210_WSACleanup()
{

	return (0);
}

HttpFile::~HttpFile(void)
{
	if (_buffer)
		free(_buffer);
	_wsa_cleanup();
}

void
HttpFile::_reset_state(void)
{
	_ascii_filename[0] = '\0';
	_cached = FALSE;
	if (_buffer)
		free(_buffer);
	_buffer = 0;
	_header_size = 0;
	_cur_pos = 0;
}

BOOL
HttpFile::setRoot(TCHAR *server)
{
	SOCKET h;
	int ret, port;

	// parse server name and its port #
	TCHAR sep[] = TEXT(":/");

	TCHAR *token = wcstok(server, sep);
	for (int i = 0; i < 3 && token; i++, token = wcstok(0, sep)) {
		switch(i) {
		case 0:
			if (wcsicmp(token, TEXT("http"))) {
				return FALSE;
			}
			break;
		case 1:
			if (!_to_ascii(_server_name, token, MAX_PATH))
				return FALSE;
			port = 80;
			break;
		case 2:
			port = _wtoi(token);
			break;
		}
	}

	WORD version = MAKEWORD(1, 1);
	ret = _wsa_startup(version, &_winsock);
	if (ret != 0) {
		DPRINTF((TEXT("WinSock initialize failed.\n")));
		return FALSE;
	}
	if (LOBYTE(_winsock.wVersion) != 1 ||
	    HIBYTE(_winsock.wVersion) != 1) {
		DPRINTF((TEXT("can't use WinSock DLL.\n")));
		return FALSE;
	}

	h = socket(AF_INET, SOCK_STREAM, 0);
	if (h == INVALID_SOCKET) {
		DPRINTF((TEXT("can't open socket. cause=%d\n"),
		    WSAGetLastError()));
		return FALSE;
	}

	memset(&_sockaddr, 0, sizeof(sockaddr_in));
	_sockaddr.sin_family = AF_INET;
	_sockaddr.sin_port = htons(port);

	struct hostent *entry = gethostbyname(_server_name);
	if (entry == 0) {
		_sockaddr.sin_addr.S_un.S_addr = inet_addr(_server_name);
		if (_sockaddr.sin_addr.S_un.S_addr == INADDR_NONE) {
			DPRINTF((TEXT("can't get host by name.\n")));
			return FALSE;
		}
		uint8_t *b = &_sockaddr.sin_addr.S_un.S_un_b.s_b1;
		DPRINTF((TEXT("%d.%d.%d.%d "), b[0], b[1], b[2], b[3]));
		if (connect(h,(const struct sockaddr *)&_sockaddr,
		    sizeof(struct sockaddr_in)) == 0)
			goto connected;
	} else {
		for (uint8_t **addr_list =(uint8_t **)entry->h_addr_list;
		    *addr_list; addr_list++) {
			uint8_t *b = &_sockaddr.sin_addr.S_un.S_un_b.s_b1;
			for (int i = 0; i < 4; i++)
				b[i] = addr_list[0][i];

			DPRINTF((TEXT("%d.%d.%d.%d "), b[0], b[1], b[2],b[3]));
			if (connect(h,(const struct sockaddr *)&_sockaddr,
			    sizeof(struct sockaddr_in)) == 0)
				goto connected;
		}
	}
	DPRINTF((TEXT("can't connect server.\n")));
	return FALSE;

 connected:
	DPRINTF((TEXT("(%S) connected.\n"), _server_name));
	closesocket(h);

	return TRUE;
}

BOOL
HttpFile::open(const TCHAR *name, uint32_t flag)
{

	_reset_state();

	return _to_ascii(_ascii_filename, name, MAX_PATH);
}

size_t
HttpFile::_read_from_cache(void *buf, size_t bytes, off_t ofs)
{
	size_t transfer;

	if (ofs >= _buffer_size)
		return 0;

	transfer = ofs + bytes > _buffer_size ? _buffer_size - ofs : bytes;

	memcpy(buf, &_buffer[ofs], transfer);

	return transfer;
}

BOOL
HttpFile::seek(off_t offset)
{
	_cur_pos = offset;

	return TRUE;
}

size_t
HttpFile::read(void *buf, size_t bytes, off_t offset)
{
	char *b;
	off_t ofs;

	if (offset != -1) {
		ofs = offset;
	} else {
		ofs = _cur_pos;
		_cur_pos += bytes;
	}

	if (_memory_cache && _cached)
		return _read_from_cache(buf, bytes, ofs);

	// HEAD request(get header size).
	if (_header_size == 0)
		_buffer_size = _parse_header(_header_size);

	// reconnect
	Socket sock(_sockaddr);
	SOCKET h;
	if ((h = sock) == INVALID_SOCKET)
		return 0;

	// GET request
	strcpy(_request, _req_get);
	_set_request();
	send(h, _request, strlen(_request), 0);

	// skip header.
	b = static_cast <char *>(malloc(_header_size));
	_recv_buffer(h, b, _header_size);
	free(b);

	// read contents.
	size_t readed;
	if (_memory_cache) {
		_buffer = static_cast <char *>(malloc(_buffer_size));
		_recv_buffer(h, _buffer, _buffer_size);
		_cached = TRUE;
		return _read_from_cache(buf, bytes, ofs);
	} else {
		int i, n = ofs / bytes;
		b = static_cast <char *>(buf);

		for (readed = 0, i = 0; i < n; i++)
			readed += _recv_buffer(h, b, bytes);
		if ((n =(ofs % bytes)))
			readed += _recv_buffer(h, b, n);
		DPRINTF((TEXT("skip contents %d byte.\n"), readed));

		readed = _recv_buffer(h, b, bytes);
	}
	return readed;
}

size_t
HttpFile::_parse_header(size_t &header_size)
{
	int cnt, ret;
	char *buf;
	size_t sz = 0;

	// reconnect.
	Socket sock(_sockaddr);
	SOCKET h;
	if ((h = sock) == INVALID_SOCKET) {
		DPRINTF((TEXT("can't open socket.\n")));
		return 0;
	}

	// HEAD request
	strcpy(_request, _req_head);
	_set_request();
	send(h, _request, strlen(_request), 0);

	// Receive and search Content-Length: field.
	if ((buf = static_cast<char *>(malloc(TMP_BUFFER_SIZE))) == 0) {
		DPRINTF((TEXT("can't allocate receive buffer.\n")));
		return 0;
	}

	BOOL found = FALSE;
	for (cnt = 0; ret = _recv_buffer(h, buf, TMP_BUFFER_SIZE - 1);
	    cnt += ret) {
		buf[ret] = '\0';
		char sep[] = " :\r\n";
		char *token = libsa::strtok(buf, sep);
		while (token) {
			DPRINTFN(2, (TEXT("+token: %S\n"), token));
			if (libsa::stricmp(token, "content-length") == 0) {
				DPRINTFN(2, (TEXT("*token: %S\n"), token));
				token = libsa::strtok(0, sep);
				sz = atoi(token);
				found = TRUE;
				DPRINTFN(2, (TEXT("*content-length=%d\n"), sz));
			} else {
				token = libsa::strtok(0, sep);
			}
		}
	}
	header_size = cnt;

	if (!found) {
		DPRINTF((TEXT("No Content-Length.\n")));
	} else {
		DPRINTF((TEXT
		    ("open http://%S%S - header %d byte contents %d byte\n"),
		    _server_name, _ascii_filename, header_size, sz));
	}
	free(buf);

	return sz;
}

size_t
HttpFile::_recv_buffer(SOCKET h, char *buf, size_t size)
{
	size_t cnt, total = 0;

	do {
		cnt = recv(h, buf + total, size - total, 0);
		total += cnt;
		DPRINTFN(2,(TEXT("size %d readed %d byte(+%d)\n"),
		    size, total, cnt));
	} while (total < size && cnt > 0);

	DPRINTFN(1,(TEXT("total read %d byte\n"), total));
	return total;
}

void
HttpFile::_set_request(void)
{

	strcat(_request, _ascii_filename);
	strcat(_request, _req_host);
	strcat(_request, _server_name);
	strcat(_request, _req_ua);
}

Socket::Socket(struct sockaddr_in &sock)
	: _sockaddr(sock)
{

	_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (_socket != INVALID_SOCKET)
		connect(_socket,
		    reinterpret_cast <const struct sockaddr *>(&_sockaddr),
		    sizeof(struct sockaddr_in));
}

Socket::~Socket(void)
{

	if (_socket != INVALID_SOCKET)
		closesocket(_socket);
}