//-----------------------------------------------------------------------------
// AScript net.http module
//-----------------------------------------------------------------------------
#include <ascript.h>
#include "ascript/ZLibHelper.h"
#include <algorithm>
#include "Module_net_http.h"

#if defined(HAVE_WINDOWS_H)
typedef int socklen_t;
#else
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define closesocket(x) close(x)
#endif

AScript_BeginModule(net_http)

static Environment *_pEnvSelf = NULL;
static Codec_Encoder *g_pEncoderBase64 = NULL;

static const char *HTTP_VERSION = "HTTP/1.1";

AScript_DeclarePrivSymbol(request);
AScript_DeclarePrivSymbol(status);
AScript_DeclarePrivSymbol(header);
AScript_DeclarePrivSymbol(method);
AScript_DeclarePrivSymbol(uri);
AScript_DeclarePrivSymbol(scheme);
AScript_DeclarePrivSymbol(authority);
AScript_DeclarePrivSymbol(path);
AScript_DeclarePrivSymbol(query);
AScript_DeclarePrivSymbol(query_args);
AScript_DeclarePrivSymbol(fragment);
AScript_DeclarePrivSymbol(version);
AScript_DeclarePrivSymbol(code);
AScript_DeclarePrivSymbol(reason);
AScript_DeclarePrivSymbol(proxies);
AScript_DeclarePrivSymbol(body);
AScript_DeclarePrivSymbol(remote_ip);
AScript_DeclarePrivSymbol(remote_host);
AScript_DeclarePrivSymbol(remote_logname);
AScript_DeclarePrivSymbol(local_ip);
AScript_DeclarePrivSymbol(local_host);
AScript_DeclarePrivSymbol(accept);				// 14.1  Accept
AScript_DeclarePrivSymbol(accept_charset);		// 14.2  Accept-Charset
AScript_DeclarePrivSymbol(accept_encoding);		// 14.3  Accept-Encoding
AScript_DeclarePrivSymbol(accept_language);		// 14.4  Accept-Language
AScript_DeclarePrivSymbol(accept_ranges);		// 14.5  Accept-Ranges
AScript_DeclarePrivSymbol(connection);			// 14.10 Connection
AScript_DeclarePrivSymbol(content_type);		// 14.17 Content-Type
AScript_DeclarePrivSymbol(etag);				// 14.19 ETag
AScript_DeclarePrivSymbol(date);				// 14.18 Date
AScript_DeclarePrivSymbol(expires);				// 14.21 Expires
AScript_DeclarePrivSymbol(host);				// 14.23 Host
AScript_DeclarePrivSymbol(last_modified);		// 14.29 Last-Modified
AScript_DeclarePrivSymbol(server);				// 14.38 Server
AScript_DeclarePrivSymbol(transfer_encoding);	// 14.41 Transfer-Encoding
AScript_DeclarePrivSymbol(user_agent);			// 14.43 User-Agent
AScript_DeclarePrivSymbol(via);					// 14.45 Via

//-----------------------------------------------------------------------------
// utilities
//-----------------------------------------------------------------------------
bool SendStreamBody(Signal sig, int sock, Stream &stream)
{
	const size_t bytesBuff = 32768;
	OAL::Memory memory;
	char *buff = reinterpret_cast<char *>(memory.Allocate(bytesBuff));
	size_t bytesRead;
	while ((bytesRead = stream.Read(sig, buff, bytesBuff)) > 0) {
		::send(sock, buff, static_cast<int>(bytesRead), 0);
	}
	return !sig.IsSignalled();
}

// RFC 3986 3.1. Scheme
String ExtractURIScheme(Signal sig, const char *uri, const char **next)
{
	if (next != NULL) *next = uri;
	const char *p = uri;
	for ( ; *p != '\0' && *p != '?' && *p != '#' && *p != ':'; p++) ;
	if (*p != ':') return String("");
	String rtn = String(uri, p - uri);
	if (next != NULL) *next = p + 1;
	return rtn;
}

// RFC 3986 3.2. Authority
String ExtractURIAuthority(Signal sig, const char *uri, const char **next)
{
	if (next != NULL) *next = uri;
	const char *p = uri;
	String scheme = ExtractURIScheme(sig, uri, &p);
	if (sig.IsSignalled()) return String("");
	if (scheme.empty()) return String(""); // scheme has not been found
	if (*p != '/') return String("");
	while (*p == '/') p++;
	const char *top = p;
	for ( ; *p != '\0' && *p != '?' && *p != '#' && *p != '/'; p++) ;
	String rtn = String(top, p - top);
	if (next != NULL) *next = p;
	return rtn;
}

// RFC 3986 3.3. Path
String ExtractURIPath(Signal sig, const char *uri)
{
	const char *p = uri;
	ExtractURIScheme(sig, p, &p);
	if (sig.IsSignalled()) return String("");
	ExtractURIAuthority(sig, p, &p);
	if (sig.IsSignalled()) return String("");
	const char *top = p;
	for ( ; *p != '\0' && *p != '?' && *p != '#'; p++) ;
	return String(top, p - top);
}

// RFC 3986 3.4. Query
String ExtractURIQuery(Signal sig, const char *uri)
{
	const char *p = uri;
	for ( ; *p != '\0' && *p != '?' && *p != '#'; p++) ;
	if (*p != '?') return String("");
	p++;
	const char *top = p;
	for ( ; *p != '\0' && *p != '#'; p++) ;
	return String(top, p - top);
}

String ExtractURIFragment(Signal sig, const char *uri)
{
	const char *p = uri;
	for ( ; *p != '\0' && *p != '#'; p++) ;
	if (*p == '\0') return String("");
	return String(p + 1);
}

String QuoteURI(const char *str)
{
	String rtn;
	for (const char *p = str; *p != '\0'; p++) {
		char ch = *p;
		// tentative condition
		if (IsAlpha(ch) || IsDigit(ch) ||
						ch == '-' || ch == '/' || ch == '.' || ch == '@') {
			rtn += ch;
		} else {
			char buff[32];
			::sprintf(buff, "%%%02X", ch);
			rtn += buff;
		}
	}
	return rtn;
}

String UnquoteURI(Signal sig, const char *str)
{
	enum {
		STAT_Start, STAT_Hex1, STAT_Hex2,
	} stat = STAT_Start;
	unsigned char chHex = 0x00;
	String rtn;
	for (const char *p = str; *p != '\0'; p++) {
		char ch = *p;
		switch (stat) {
		case STAT_Start: {
			if (ch == '+') {
				rtn += ' ';
			} else if (ch == '%') {
				stat = STAT_Hex1;
			} else {
				rtn += ch;
			}
			break;
		}
		case STAT_Hex1: {
			if (IsHexDigit(ch)) {
				chHex = ConvHexDigit(ch);
				stat = STAT_Hex2;
			} else {
				goto error_done;
			}
			break;
		}
		case STAT_Hex2: {
			if (IsHexDigit(ch)) {
				chHex = (chHex << 4) + ConvHexDigit(ch);
				rtn += static_cast<char>(chHex);
				stat = STAT_Start;
			} else {
				goto error_done;
			}
			break;
		}
		}
	}
	return rtn;
error_done:
	sig.SetError(ERR_FormatError, "invalid format of URI");
	return String("");
}

bool DecodeURIQuery(Signal sig, const char *str, StringList &stringList)
{
	enum {
		STAT_Key, STAT_Value,
		STAT_Hex1, STAT_Hex2,
	} stat = STAT_Key, statNext = STAT_Key;
	String token;
	unsigned char chHex = 0x00;
	for (const char *p = str; ; p++) {
		const char ch = *p;
		switch (stat) {
		case STAT_Key: {
			if (ch == '=') {
				stringList.push_back(token);
				token.clear();
				stat = STAT_Value;
			} else if (ch == '&' || ch == '\0') {
				stringList.push_back(token);
				stringList.push_back("");
				token.clear();
			} else if (ch == '+') {
				token += ' ';
			} else if (ch == '%') {
				statNext = STAT_Key;
				stat = STAT_Hex1;
			} else {
				token += ch;
			}
			break;
		}
		case STAT_Value: {
			if (ch == '&' || ch == '\0') {
				stringList.push_back(token);
				token.clear();
				stat = STAT_Key;
			} else if (ch == '+') {
				token += ' ';
			} else if (ch == '%') {
				statNext = STAT_Value;
				stat = STAT_Hex1;
			} else {
				token += ch;
			}
			break;
		}
		case STAT_Hex1: {
			if (IsHexDigit(ch)) {
				chHex = ConvHexDigit(ch);
				stat = STAT_Hex2;
			} else {
				goto error_done;
			}
			break;
		}
		case STAT_Hex2: {
			if (IsHexDigit(ch)) {
				chHex = (chHex << 4) + ConvHexDigit(ch);
				token += static_cast<char>(chHex);
				stat = statNext;
			} else {
				goto error_done;
			}
			break;
		}
		}
		if (ch == '\0') break;
	}
	return true;
error_done:
	sig.SetError(ERR_FormatError, "invalid format of URI");
	return false;
}

Value DecodeURIQuery(Environment &env, Signal sig, const char *str)
{
	StringList stringList;
	if (!DecodeURIQuery(sig, str, stringList)) return Value::Null;
	Value result;
	ValueDict &valDict = result.InitAsDict(env);
	for (StringList::iterator pStr = stringList.begin();
											pStr != stringList.end(); ) {
		const String &key = *pStr++;
		if (pStr == stringList.end()) break;
		const String &value = *pStr++;
		valDict[Value(env, key.c_str())] = Value(env, value.c_str());
	}
	return result;
}

//-----------------------------------------------------------------------------
// Chunk implementation
//-----------------------------------------------------------------------------
Chunk::~Chunk()
{
}

bool Chunk::ParseChar(Signal sig, char ch)
{
	if (ch == '\r') return true;	// just skip CR code
	bool continueFlag;
	do {
	continueFlag = false;
	switch (_stat) {
	case STAT_Start: {
		if (IsSpace(ch)) {
			// nothing to do
		} else {
			_stat = STAT_Size;
			continueFlag = true;
		}
		break;
	}
	case STAT_Size: {
		if (ch == '\n') {
			_stat = STAT_Complete;
		} else if (ch == ';') {
			_extName.clear(), _extValue.clear();
			_stat = STAT_ExtName;
		} else if (IsHexDigit(ch)) {
			_size = (_size << 4) + ConvHexDigit(ch);
		} else {
			sig.SetError(ERR_FormatError, "invalid character in chunk information");
			return false;
		}
		break;
	}
	case STAT_ExtName: {
		if (ch == '\n') {
			_stat = STAT_Complete;
		} else if (ch == '=') {
			_stat = STAT_ExtValue;
		} else {
			_extName += ch;
		}
		break;
	}
	case STAT_ExtValue: {
		if (ch == '\n') {
			_stat = STAT_Complete;
		} else {
			_extValue += ch;
		}
		break;
	}
	} } while (continueFlag);
	return true;
}

//-----------------------------------------------------------------------------
// SimpleHTMLParser implementation
// This is a very simple HTTP parser to detect the following patterns.
// - <?xml .. encoding="xxxxxxxx"?>
// - <meta http-equiv="Content-Type" content="..; charset=xxxxxxxx />
//-----------------------------------------------------------------------------
bool SimpleHTMLParser::ParseChar(Signal sig, char ch)
{
	if (ch == '\r') return true;	// just skip CR code
	bool continueFlag;
	do {
	continueFlag = false;
	switch (_stat) {
	case STAT_Init: {
		if (ch == '<') {
			_tagPrefix = '\0';
			_tagName.clear();
			_attrName.clear();
			_attrValue.clear();
			_attrs.clear();
			_stat = STAT_Tag1stChar;
		} else {
			// nothing to do
		}
		break;
	}
	case STAT_Tag1stChar: {
		if (ch == '?') {
			_tagPrefix = '?';
			_stat = STAT_TagName;
		} else if (ch == '!') {
			_stat = STAT_SeekTagClose;
		} else if (ch == '/') {
			_tagPrefix = '/';
			_stat = STAT_TagName;
		} else {
			continueFlag = true;
			_stat = STAT_TagName;
		}
		break;
	}
	case STAT_SeekTagClose: {
		if (ch == '>') {
			_stat = STAT_Init;
		} else {
			// nothing to do
		}
		break;
	}
	case STAT_TagName: {
		if (ch == '?') {
			// just ignore it
		} else if (ch == '>') {
			if (!AcceptTag(sig, _tagPrefix, _tagName.c_str(), _attrs)) return false;
			if (!IsComplete()) _stat = STAT_Init;
		} else if (IsSpace(ch)) {
			_stat = STAT_SkipSpace;
			_statNext = STAT_AttrName;
		} else if (0x20 < ch && ch < 0x7f) {
			_tagName += ToLower(ch);
		} else {
			_stat = STAT_Error;
		}
		break;
	}
	case STAT_AttrName: {
		if (ch == '?') {
			// just ignore it
		} else if (ch == '>') {
			if (!AcceptTag(sig, _tagPrefix, _tagName.c_str(), _attrs)) return false;
			if (!IsComplete()) _stat = STAT_Init;
		} else if (IsSpace(ch)) {
			_stat = STAT_SeekEqual;
		} else if (ch == '=') {
			_stat = STAT_SkipSpace;
			_statNext = STAT_AttrValue;
		} else if (0x20 < ch && ch < 0x7f) {
			_attrName += ToLower(ch);
		} else {
			_stat = STAT_Error;
		}
		break;
	}
	case STAT_AttrValue: {
		if (ch == '\'') {
			_stat = STAT_AttrValueQuotedS;
		} else if (ch == '"') {
			_stat = STAT_AttrValueQuotedD;
		} else {
			continueFlag = true;
			_stat = STAT_AttrValueNaked;
		}
		break;
	}
	case STAT_AttrValueQuotedS: {
		if (ch == '>') {
			_attrs[_attrName] = _attrValue;
			if (!AcceptTag(sig, _tagPrefix, _tagName.c_str(), _attrs)) return false;
			if (!IsComplete()) _stat = STAT_Init;
		} else if (ch == '\'') {
			_attrs[_attrName] = _attrValue;
			_attrName.clear(), _attrValue.clear();
			_stat = STAT_SkipSpace;
			_statNext = STAT_AttrName;
		} else {
			_attrValue += ch;
		}
		break;
	}
	case STAT_AttrValueQuotedD: {
		if (ch == '>') {
			_attrs[_attrName] = _attrValue;
			if (!AcceptTag(sig, _tagPrefix, _tagName.c_str(), _attrs)) return false;
			if (!IsComplete()) _stat = STAT_Init;
		} else if (ch == '"') {
			_attrs[_attrName] = _attrValue;
			_attrName.clear(), _attrValue.clear();
			_stat = STAT_SkipSpace;
			_statNext = STAT_AttrName;
		} else {
			_attrValue += ch;
		}
		break;
	}
	case STAT_AttrValueNaked: {
		if (ch == '>') {
			_attrs[_attrName] = _attrValue;
			if (!AcceptTag(sig, _tagPrefix, _tagName.c_str(), _attrs)) return false;
			if (!IsComplete()) _stat = STAT_Init;
		} else if (IsSpace(ch)) {
			_attrs[_attrName] = _attrValue;
			_attrName.clear(), _attrValue.clear();
			_stat = STAT_SkipSpace;
			_statNext = STAT_AttrName;
		} else {
			_attrValue += ch;
		}
		break;
	}
	case STAT_SeekEqual: {
		if (ch == '=') {
			_stat = STAT_SkipSpace;
			_statNext = STAT_AttrValue;
		} else if (IsSpace(ch)) {
			// nothing to do
		} else {
			_stat = STAT_Error;
		}
		break;
	}
	case STAT_Comment: {
		if (ch == '-') {
			_stat = STAT_Comment1stChar;
		} else {
			// nothing to do
		}
		break;
	}
	case STAT_Comment1stChar: {
		if (ch == '-') {
			_stat = STAT_Comment2ndChar;
		} else {
			_stat = STAT_Comment;
		}
		break;
	}
	case STAT_Comment2ndChar: {
		if (ch == '>') {
			_stat = STAT_Init;
		} else {
			_stat = STAT_Comment;
		}
		break;
	}
	case STAT_SkipSpace: {
		if (IsSpace(ch)) {
			// nothing to do
		} else {
			continueFlag = true;
			_stat = _statNext;
		}
		break;
	}
	case STAT_Error: {
		break;
	}
	case STAT_Complete: {
		break;
	}
	} } while (continueFlag);
	return true;
}

//-----------------------------------------------------------------------------
// EncodingDetector implementation
// This class detects the following patterns in HTML text.
// - <?xml .. encoding="xxxxxxxx"?>
// - <meta http-equiv="Content-Type" content="..; charset=xxxxxxxx />
//-----------------------------------------------------------------------------
bool EncodingDetector::AcceptTag(Signal sig,
					char tagPrefix, const char *tagName, const Attrs &attrs)
{
	if (tagPrefix == '?' && ::strcasecmp(tagName, "xml") == 0) {
		Attrs::const_iterator iter = attrs.find("encoding");
		if (iter != attrs.end()) {
			_encoding = iter->second;
			_stat = STAT_Complete;
		}
	} else if (tagPrefix == '\0' && ::strcasecmp(tagName, "meta") == 0) {
		Attrs::const_iterator iter = attrs.find("http-equiv");
		if (iter != attrs.end() &&
				::strcasecmp(iter->second.c_str(), "Content-Type") == 0) {
			Attrs::const_iterator iter = attrs.find("content");
			if (iter != attrs.end()) {
				ContentType contentType;
				if (!contentType.Parse(sig, iter->second.c_str())) {
					return false;
				}
				_encoding = contentType.GetCharset();
			}
			_stat = STAT_Complete;
		}
	} else if (_tagPrefix == '/' && ::strcasecmp(tagName, "head") == 0) {
		_stat = STAT_Complete;
	}
	return true;
}

//-----------------------------------------------------------------------------
// LinkDetector implementation
//-----------------------------------------------------------------------------
bool LinkDetector::AcceptTag(Signal sig,
					char tagPrefix, const char *tagName, const Attrs &attrs)
{
	return true;
}

//-----------------------------------------------------------------------------
// ContentType implementation
//-----------------------------------------------------------------------------
bool ContentType::Parse(Signal sig, const char *str)
{
	const char *p = ::strchr(str, ';');
	if (p == NULL) {
		_type = str;
		return true;
	}
	_type = String(str, p - str);
	//p++;
	//while (IsSpace(*p)) p++;
	// too-easy parsing
	for ( ; *p != '=' && *p != '\0'; p++) ;
	if (*p == '=') _charset = p + 1;
	return true;
}

//-----------------------------------------------------------------------------
// Header implementation
//-----------------------------------------------------------------------------
Header::Header(const Header &header) : _stat(STAT_Complete)
{
	foreach_const (Dict, iter, header._dict) {
		StringList *pStringList = iter->second;
		_dict[iter->first] = new StringList(*pStringList);
	}
}

Header::~Header()
{
	foreach (Dict, iter, _dict) {
		StringList *pStringList = iter->second;
		delete pStringList;
	}
}

void Header::Reset()
{
	_stat = STAT_LineTop;
	foreach (Dict, iter, _dict) {
		StringList *pStringList = iter->second;
		delete pStringList;
	}
	_dict.clear();
}

bool Header::ParseChar(Signal sig, char ch)
{
	if (ch == '\r') return true;	// just skip CR code
	bool continueFlag;
	do {
	continueFlag = false;
	switch (_stat) {
	case STAT_LineTop: {
		if (ch == '\n') {
			_stat = STAT_Complete;
		} else {
			_fieldName.clear();
			_fieldValue.clear();
			continueFlag = true;
			_stat = STAT_FieldName;
		}
		break;
	}
	case STAT_FieldName: {
		if (ch == ':') {
			_fieldName = Strip(_fieldName.c_str());
			if (_fieldName.empty()) {
				SetError_InvalidFormat(sig);
				return false;
			}
			_stat = STAT_FieldValue;
		} else if (ch == '\n') {
			SetError_InvalidFormat(sig);
			return false;
		} else {
			_fieldName += ch;
		}
		break;
	}
	case STAT_FieldValue: {
		if (ch == '\n') {
			_fieldValue = Strip(_fieldValue.c_str());
			_stat = STAT_LineFolded;
		} else {
			_fieldValue += ch;
		}
		break;
	}
	case STAT_LineFolded: {
		if (ch == ' ' || ch == '\t') {
			_fieldValue += ' ';
			_stat = STAT_SkipWhiteSpace;
		} else {
			SetField(_fieldName.c_str(), _fieldValue.c_str());
			continueFlag = true;
			_stat = STAT_LineTop;
		}
		break;
	}
	case STAT_SkipWhiteSpace: {
		if (ch == ' ' || ch == '\t') {
			// nothing to do
		} else {
			continueFlag = true;
			_stat = STAT_FieldValue;
		}
		break;
	}
	case STAT_Complete: {
		break;
	}
	} } while (continueFlag);
	return true;
}

void Header::SetField(const char *fieldName, const char *fieldValue)
{
	Dict::iterator iter = _dict.find(fieldName);
	StringList *pStringList = NULL;
	if (iter == _dict.end()) {
		pStringList = new StringList();
		_dict[fieldName] = pStringList;
	} else {
		pStringList = iter->second;
	}
	pStringList->push_back(fieldValue);
}

bool Header::GetField(const char *fieldName, StringList **ppStringList) const
{
	Dict::const_iterator iter = _dict.find(fieldName);
	if (iter == _dict.end()) return false;
	if (ppStringList != NULL) *ppStringList = iter->second;
	return true;
}

bool Header::GetField(Environment &env, Signal sig, const Symbol *pSymbol, Value &value) const
{
	StringList *pStringList = NULL;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(accept))) {
		// 14.1  Accept
		if (GetField("Accept", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(accept_charset))) {
		// 14.2  Accept-Charset
		if (GetField("Accept-Charset", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(accept_encoding))) {
		// 14.3  Accept-Encoding
		if (GetField("Accept-Encoding", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(accept_language))) {
		// 14.4  Accept-Language
		if (GetField("Accept-Language", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(accept_ranges))) {
		// 14.5  Accept-Ranges
		if (GetField("Accept-Ranges", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(connection))) {
		// 14.10 Connection
		if (GetField("Connection", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(content_type))) {
		// 14.17 Content-Type
		if (GetField("Content-Type", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(date))) {
		// 14.18 Date
		if (GetField("Date", &pStringList)) {
			DateTime dt;
			if (!dt.Parse(pStringList->back().c_str())) {
				sig.SetError(ERR_FormatError, "invalid format of Date");
				return false;
			}
			value = Value(env, dt);
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(etag))) {
		// 14.19 ETag
		if (GetField("ETag", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(expires))) {
		// 14.21 Expires
		if (GetField("Expires", &pStringList)) {
			DateTime dt;
			if (!dt.Parse(pStringList->back().c_str())) {
				sig.SetError(ERR_FormatError, "invalid format of Expires");
				return false;
			}
			value = Value(env, dt);
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(host))) {
		// 14.23 Host
		if (GetField("Host", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(last_modified))) {
		// 14.29 Last-Modified
		if (GetField("Last-Modified", &pStringList)) {
			DateTime dt;
			if (!dt.Parse(pStringList->back().c_str())) {
				sig.SetError(ERR_FormatError, "invalid format of Last-Modified");
				return false;
			}
			value = Value(env, dt);
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(server))) {
		// 14.38 Server
		if (GetField("Server", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(transfer_encoding))) {
		// 14.41 Transfer-Encoding
		if (GetField("Transfer-Encoding", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(user_agent))) {
		// 14.43 User-Agent
		if (GetField("User-Agent", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(via))) {
		// 14.45 Via
		if (GetField("Via", &pStringList)) {
			value = Value(env, pStringList->back().c_str());
		}
		return true;
	}
	return false;
}

bool Header::IsField(const char *fieldName, const char *value, bool *pFoundFlag) const
{
	StringList *pStringList;
	if (!GetField(fieldName, &pStringList)) {
		if (pFoundFlag != NULL) *pFoundFlag = false;
		return false;
	}
	if (pFoundFlag != NULL) *pFoundFlag = true;
	return ::strcasecmp(pStringList->back().c_str(), value) == 0;
}

bool Header::SetFields(Signal sig, const ValueDict &valueDict, Stream *pStreamBody)
{
	foreach_const (ValueDict, iter, valueDict) {
		String fieldName = iter->first.ToString(sig, false);
		if (sig.IsSignalled()) return false;
		String fieldValue = iter->second.ToString(sig, false);
		if (sig.IsSignalled()) return false;
		SetField(fieldName.c_str(), fieldValue.c_str());
	}
	if (pStreamBody == NULL) {
		SetField("Content-Length", "0");
	} else {
		size_t bytes = pStreamBody->GetSize();
		if (bytes != InvalidSize) {
			bytes -= pStreamBody->Tell();
			SetField("Content-Length", NumberToString(bytes).c_str());
		}
	}
	return true;
}

Value Header::GetFieldsAsDict(Environment &env) const
{
	Value result;
	ValueDict &valueDict = result.InitAsDict(env);
	foreach_const (Dict, iterSrc, _dict) {
		Value valueKey(env, iterSrc->first.c_str());
		StringList *pStringList = iterSrc->second;
		if (pStringList->size() == 1) {
			Value value(env, pStringList->back().c_str());
			valueDict[valueKey] = value;
		} else {
			Value value;
			ValueList &valList = value.InitAsList(env);
			foreach_const (StringList, pStr, *pStringList) {
				Value valueItem(env, pStr->c_str());
				valList.push_back(valueItem);
			}
			valueDict[valueKey] = value;
		}
	}
	return result;
}

String Header::GetString() const
{
	String str;
	foreach_const (Dict, iter, _dict) {
		StringList *pStringList = iter->second;
		foreach_const (StringList, pStr, *pStringList) {
			str += iter->first;
			str += ": ";
			str += *pStr;
			str += "\r\n";
		}
	}
	return str;
}

Stream_Http *Header::GenerateUpStream(Signal sig,
				Object *pObjOwner, int sock, const char *name) const
{
	Stream *pStream = new Stream_Socket(sig, Object::Reference(pObjOwner), sock);
	Stream_Http *pStreamHttp = new Stream_Http(sig, pStream,
						Stream::ATTR_Writable, name, InvalidSize, *this);
	//pStreamHttp->InstallCodec(contentType.GetCharset(), false);
	return pStreamHttp;
}

Stream_Http *Header::GenerateDownStream(Signal sig,
				Object *pObjOwner, int sock, const char *name) const
{
	bool chunkedFlag = false;
	bool gzipFlag = false;
	size_t bytes = InvalidSize;
	StringList *pStringList = NULL;
	// RFC 2616 4.4 Message Length
	if (GetField("Transfer-Encoding", &pStringList)) {
		// RFC 2616 3.6 Transfer Codings
		// RFC 2616 14.41 Transfer-Encoding
		const char *fieldValue = pStringList->back().c_str();
		if (::strcasecmp(fieldValue, "chunked") == 0) {
			chunkedFlag = true;
			bytes = InvalidSize;
		}
	}
	if (GetField("Content-Length", &pStringList)) {
		// RFC 2616 14.13 Content-Length
		const char *fieldValue = pStringList->back().c_str();
		if (!chunkedFlag) {
			bytes = ::strtoul(fieldValue, NULL, 0);
		}
	}
	ContentType contentType;
	if (GetField("Content-Type", &pStringList)) {
		// RFC 2616 14.17 Content-Type
		if (!contentType.Parse(sig, pStringList->back().c_str())) return NULL;
	}
	if (GetField("Content-Encoding", &pStringList)) {
		// RFC 2616 14.11 Content-Encoding
		const char *fieldValue = pStringList->back().c_str();
		if (::strcasecmp(fieldValue, "gzip") == 0) {
			bytes = InvalidSize;
			gzipFlag = true;
		}
	}
	Stream *pStream = new Stream_Socket(sig, Object::Reference(pObjOwner), sock);
	if (chunkedFlag) {
		Stream_Chunked *pStreamChunked =
						new Stream_Chunked(sig, pStream, Stream::ATTR_Readable);
		pStream = pStreamChunked;
	}
	if (gzipFlag) {
		// Specifying 31 to windowBits doesn't work correctly here.
		ZLib::GZHeader hdr;
		hdr.Read(sig, *pStream);	// skip gz header
		ZLib::Stream_Inflater *pStreamInflater =
							new ZLib::Stream_Inflater(sig, pStream, InvalidSize);
		pStreamInflater->Initialize(sig, -15);
		pStream = pStreamInflater;
	}
	Stream_Http *pStreamHttp = new Stream_Http(sig, pStream,
								Stream::ATTR_Readable, name, bytes, *this);
	const char *type = contentType.GetType();
	if (::strcasecmp(type, "text/html") == 0 || ::strcasecmp(type, "text/xml") == 0) {
		pStreamHttp->ActivateEncodingDetector();
	}
	if (contentType.IsValidCharset()) {
		pStreamHttp->InstallCodec(contentType.GetCharset(), false);
	}
	return pStreamHttp;
}

//-----------------------------------------------------------------------------
// Request implementation
//-----------------------------------------------------------------------------
bool Request::ParseChar(Signal sig, char ch)
{
	if (ch == '\r') return true;	// just skip CR code
	bool continueFlag;
	do {
	continueFlag = false;
	switch (_stat) {
	case STAT_Start: {
		if (IsSpace(ch)) {
			// nothing to do
		} else {
			_stat = STAT_Method;
			continueFlag = true;
		}
		break;
	}
	case STAT_Method: {
		if (IsAlpha(ch)) {
			_method += ch;
		} else if (ch == ' ') {
			_stat = STAT_SkipSpace;
			_statNext = STAT_RequestURI;
		} else {
			SetError_InvalidFormat(sig);
			return false;
		}
		break;
	}
	case STAT_RequestURI: {
		if (ch == ' ') {
			_stat = STAT_SkipSpace;
			_statNext = STAT_HttpVersion;
		} else if (0x20 < ch && ch < 0x7f) {
			_requestURI += ch;
		} else {
			SetError_InvalidFormat(sig);
			return false;
		}
		break;
	}
	case STAT_HttpVersion: {
		if (ch == '\n') {
			_stat = STAT_Header;
		} else if (0x20 < ch && ch < 0x7f) {
			_httpVersion += ch;
		} else {
			SetError_InvalidFormat(sig);
			return false;
		}
		break;
	}
	case STAT_SkipSpace: {
		if (ch == ' ') {
			// nothing to do
		} else {
			continueFlag = true;
			_stat = _statNext;
		}
		break;
	}
	case STAT_Header: {
		if (!_header.ParseChar(sig, ch)) return false;
		break;
	}
	} } while (continueFlag);
	return true;
}

bool Request::Send(Signal sig, int sock)
{
	String str;
	str += _method;
	str += " ";
	str += _requestURI;
	str += " ";
	str += _httpVersion;
	str += "\r\n";
	str += _header.GetString();
	str += "\r\n";
	::send(sock, str.data(), static_cast<int>(str.size()), 0);
	return true;
}

bool Request::Receive(Signal sig, int sock)
{
	char ch;
	Reset();
	while (::recv(sock, &ch, 1, 0) > 0) {
		if (!ParseChar(sig, ch)) return false;
		if (IsComplete()) return true;
	}
	//sig.SetError(ERR_IOError, "connection closed while getting request");
	return true;
}

//-----------------------------------------------------------------------------
// Status implementation
//-----------------------------------------------------------------------------
const Status::CodePhrase Status::_codePhraseTbl[] = {
	// RFC 2616 10.1 Informational 1xx
	{ "100", "Continue",						CODE_Continue,						},
	{ "101", "Switching Protocols",				CODE_SwitchingProtocols,			},
	// RFC 2616 10.2 Successful 2xx
	{ "200", "OK",								CODE_OK,							},
	{ "201", "Created",							CODE_Created,						},
	{ "202", "Accepted",						CODE_Accepted,						},
	{ "203", "Non-Authoritative Information",	CODE_NonAuthoritativeInformation,	},
	{ "204", "No Content",						CODE_NoContent,						},
	{ "205", "Reset Content",					CODE_ResetContent,					},
	{ "206", "Partial content",					CODE_PartialContent,				},
	// RFC 2616 10.3 Redirection 3xx
	{ "300", "Multiple Choices",				CODE_MultipleChoices,				},
	{ "301", "Moved Permanently",				CODE_MovedPermanently,				},
	{ "302", "Found",							CODE_Found,							},
	{ "303", "See Other",						CODE_SeeOther,						},
	{ "304", "Not Modified",					CODE_NotModified,					},
	{ "305", "Use Proxy",						CODE_UseProxy,						},
	{ "307", "Temporary Redirect",				CODE_TemporaryRedirect,				},
	// RFC 2616 10.4 Client Error 4xx
	{ "400", "Bad Request",						CODE_BadRequest,					},
	{ "401", "Unauthorized",					CODE_Unauthorized,					},
	{ "402", "Payment Required",				CODE_PaymentRequired,				},
	{ "403", "Forbidden",						CODE_Forbidden,						},
	{ "404", "Not Found",						CODE_NotFound,						},
	{ "405", "Method Not Allowed",				CODE_MethodNotAllowed,				},
	{ "406", "Not Acceptable",					CODE_NotAcceptable,					},
	{ "407", "Proxy Authentication Required",	CODE_ProxyAuthenticationRequired,	},
	{ "408", "Request Time-out",				CODE_RequestTimeOut,				},
	{ "409", "Conflict",						CODE_Conflict,						},
	{ "410", "Gone",							CODE_Gone,							},
	{ "411", "Length Required",					CODE_LengthRequired,				},
	{ "412", "Precondition Failed",				CODE_PreconditionFailed,			},
	{ "413", "Request Entity Too Large",		CODE_RequestEntityTooLarge,			},
	{ "414", "Request-URI Too Large",			CODE_RequestURITooLarge,			},
	{ "415", "Unsupported Media Type",			CODE_UnsupportedMediaType,			},
	{ "416", "Requested range not satisfiable",	CODE_RequestedRangeNotSatisfiable,	},
	{ "417", "Expectation Failed",				CODE_ExpectationFailed,				},
	// RFC 2616 10.5 Server Error 5xx
	{ "500", "Internal Server Error",			CODE_InternalServerError,			},
	{ "501", "Not Implemented",					CODE_NotImplemented,				},
	{ "502", "Bad Gateway",						CODE_BadGateway,					},
	{ "503", "Service Unavailable",				CODE_ServiceUnavailable,			},
	{ "504", "Gateway Time-out",				CODE_GatewayTimeOut,				},
	{ "505", "HTTP Version not supported",		CODE_HTTPVersionNotSupported,		},
};

void Status::SetStatus(const char *httpVersion,
							const char *statusCode, const char *reasonPhrase)
{
	_httpVersion = httpVersion, _statusCode = statusCode;
	if (reasonPhrase == NULL) {
		const CodePhrase *p = _codePhraseTbl;
		for (int i = 0; i < NUMBEROF(_codePhraseTbl); i++, p++) {
			if (::strcasecmp(p->statusCode, statusCode) == 0) {
				reasonPhrase = p->reasonPhrase;
				break;
			}
		}
		if (reasonPhrase == NULL) {
			reasonPhrase = "Unknown Status";
		}
	}
	_reasonPhrase = reasonPhrase;
	_header.Reset();
}

bool Status::ParseChar(Signal sig, char ch)
{
	if (ch == '\r') return true;	// just skip CR code
	bool continueFlag;
	do {
	continueFlag = false;
	switch (_stat) {
	case STAT_Start: {
		if (IsSpace(ch)) {
			// nothing to do
		} else {
			_stat = STAT_HttpVersion;
			continueFlag = true;
		}
		break;
	}
	case STAT_HttpVersion: {
		if (ch == ' ') {
			_stat = STAT_SkipSpace;
			_statNext = STAT_StatusCode;
		} else if (0x20 < ch && ch < 0x7f) {
			_httpVersion += ch;
		} else {
			SetError_InvalidFormat(sig);
			return false;
		}
		break;
	}
	case STAT_StatusCode: {
		if (ch == ' ') {
			_stat = STAT_SkipSpace;
			_statNext = STAT_ReasonPhrase;
		} else if (0x20 < ch && ch < 0x7f) {
			_statusCode += ch;
		} else {
			SetError_InvalidFormat(sig);
			return false;
		}
		break;
	}
	case STAT_ReasonPhrase: {
		if (ch == '\n') {
			_stat = STAT_Header;
		} else if (0x20 <= ch && ch < 0x7f) {
			_reasonPhrase += ch;
		} else {
			SetError_InvalidFormat(sig);
			return false;
		}
		break;
	}
	case STAT_SkipSpace: {
		if (ch == ' ') {
			// nothing to do
		} else {
			continueFlag = true;
			_stat = _statNext;
		}
		break;
	}
	case STAT_Header: {
		if (!_header.ParseChar(sig, ch)) return false;
		break;
	}
	} } while (continueFlag);
	return true;
}

bool Status::Send(Signal sig, int sock)
{
	String str;
	str += _httpVersion;
	str += " ";
	str += _statusCode;
	str += " ";
	str += _reasonPhrase;
	str += "\r\n";
	str += _header.GetString();
	str += "\r\n";
	::send(sock, str.data(), static_cast<int>(str.size()), 0);
	return true;
}

bool Status::Receive(Signal sig, int sock)
{
	char ch;
	Reset();
	while (::recv(sock, &ch, 1, 0) > 0) {
		if (!ParseChar(sig, ch)) return false;
		if (IsComplete()) return true;
	}
	sig.SetError(ERR_IOError, "connection closed while getting status");
	return false;
}

//-----------------------------------------------------------------------------
// Stream_Socket implementation
//-----------------------------------------------------------------------------
Stream_Socket::Stream_Socket(Signal sig, Object *pObjOwner, int sock) :
		Stream(sig, ATTR_Readable | ATTR_Writable), _pObjOwner(pObjOwner), _sock(sock)
{
}

Stream_Socket::~Stream_Socket()
{
	Object::Delete(_pObjOwner);
}

const char *Stream_Socket::GetName() const
{
	return "socket";
}

size_t Stream_Socket::DoRead(Signal sig, void *buff, size_t bytes)
{
	int rtn = ::recv(_sock, reinterpret_cast<char *>(buff), static_cast<int>(bytes), 0);
	if (rtn < 0) {
		sig.SetError(ERR_IOError, "error while receiving from socket");
		return 0;
	}
	return rtn;
}

size_t Stream_Socket::DoWrite(Signal sig, const void *buff, size_t bytes)
{
	int rtn = ::send(_sock, reinterpret_cast<const char *>(buff), static_cast<int>(bytes), 0);
	if (rtn < 0) {
		sig.SetError(ERR_IOError, "error while sending to socket");
		return 0;
	}
	return rtn;
}

bool Stream_Socket::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	if (_sock < 0) return false;
	return false;
}

bool Stream_Socket::DoFlush(Signal sig)
{
	if (_sock < 0) return false;
	return true;
}

bool Stream_Socket::DoClose(Signal sig)
{
	return true;
}

size_t Stream_Socket::DoGetSize()
{
	return InvalidSize;
}

Object *Stream_Socket::DoGetStatObj(Signal sig)
{
	sig.SetError(ERR_IOError, "can't retrieve stat object");
	return NULL;
}

//-----------------------------------------------------------------------------
// Stream_Chunked implementation
//-----------------------------------------------------------------------------
Stream_Chunked::Stream_Chunked(Signal sig, Stream *pStream, unsigned long attr) :
		Stream(sig, attr), _pStream(pStream), _bytesChunk(0), _doneFlag(false)
{
}

Stream_Chunked::~Stream_Chunked()
{
	Stream::Delete(_pStream);
}

const char *Stream_Chunked::GetName() const
{
	return _pStream->GetName();
}

size_t Stream_Chunked::DoRead(Signal sig, void *buff, size_t bytes)
{
	char *buffp = reinterpret_cast<char *>(buff);
	size_t offset = 0;
	while (offset < bytes) {
		if (_bytesChunk == 0) {
			if (_doneFlag) break;
			Chunk chunk;
			while (!chunk.IsComplete()) {
				char ch;
				if (_pStream->Read(sig, &ch, 1) < 1) return 0;
				if (!chunk.ParseChar(sig, ch)) return 0;
			}
			_bytesChunk = chunk.GetSize();
			if (_bytesChunk == 0) {
				_doneFlag = true;	// chunk end
				break;
			}
		}
		size_t bytesToRecv = ChooseMin(bytes - offset, _bytesChunk);
		size_t bytesRecved = _pStream->Read(sig, buffp + offset, bytesToRecv);
		if (sig.IsSignalled()) return 0;
		if (bytesRecved == 0) {
			// session closed
			break;
		}
		_bytesChunk -= bytesRecved;
		offset += bytesRecved;
	}
	return offset;
}

size_t Stream_Chunked::DoWrite(Signal sig, const void *buff, size_t bytes)
{
#if 0
	const char *buffp = reinterpret_cast<const char *>(buff);
	size_t offset = 0;
	while (offset < bytes) {
		size_t bytesToSend = bytes - offset;
		size_t bytesSent = _pStream->Write(sig, buffp + offset, bytesToSend);
		if (sig.IsSignalled()) return 0;
		offset += bytesSent;
	}
	return offset;
#endif
	return 0;
}

bool Stream_Chunked::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	size_t bytesToSkip = 0;
	if (seekMode == SeekSet) {
		if (static_cast<size_t>(offset) > offsetPrev) {
			bytesToSkip = static_cast<size_t>(offset) - offsetPrev;
		}
	} else if (seekMode == SeekCur) {
		if (offset > 0) bytesToSkip = static_cast<size_t>(offset);
	}
	const size_t bytesBuff = 32768;
	void *buff = _memory.Allocate(bytesBuff);
	while (bytesToSkip > 0) {
		size_t bytesToRead = ChooseMin(bytesToSkip, bytesBuff);
		size_t bytesRead = DoRead(sig, buff, bytesToRead);
		if (sig.IsSignalled()) return false;
		if (bytesRead == 0) {
			sig.SetError(ERR_IOError, "seek error");
			return false;
		}
		bytesToSkip -= bytesRead;
	}
	return true;
}

bool Stream_Chunked::DoFlush(Signal sig)
{
	return true;
}

bool Stream_Chunked::DoClose(Signal sig)
{
	return true;
}

size_t Stream_Chunked::DoGetSize()
{
	return InvalidSize;
}

//-----------------------------------------------------------------------------
// Stream_Http implementation
//-----------------------------------------------------------------------------
Stream_Http::Stream_Http(Signal sig, Stream *pStream, unsigned long attr,
					const char *name, size_t bytes, const Header &header) :
		Stream(sig, attr), _pStream(pStream), _name(name),
		_bytesRead(bytes), _header(header)
{
}

Stream_Http::~Stream_Http()
{
	Stream::Delete(_pStream);
}

const char *Stream_Http::GetName() const
{
	return _name.c_str();
}

size_t Stream_Http::DoRead(Signal sig, void *buff, size_t bytes)
{
	char *buffp = reinterpret_cast<char *>(buff);
	size_t offset = 0;
	while (offset < bytes) {
		size_t bytesToRecv = ChooseMin(bytes - offset, _bytesRead);
		size_t bytesRecved = _pStream->Read(sig, buffp + offset, bytesToRecv);
		if (sig.IsSignalled()) return 0;
		if (bytesRecved == 0) {
			// session closed
			break;
		}
		if (_encodingDetector.IsActive()) {
			for (size_t i = 0; i < bytesRecved && _encodingDetector.IsActive(); i++) {
				char ch = *(buffp + offset + i);
				if (!_encodingDetector.ParseChar(sig, ch)) return false;
			}
			if (_encodingDetector.IsValidEncoding()) {
				//::printf("** encoding:%s **\n", _encodingDetector.GetEncoding());
				InstallCodec(_encodingDetector.GetEncoding(), false);
			}
		}
		_bytesRead -= bytesRecved;
		offset += bytesRecved;
	}
	return offset;
}

size_t Stream_Http::DoWrite(Signal sig, const void *buff, size_t bytes)
{
	const char *buffp = reinterpret_cast<const char *>(buff);
	size_t offset = 0;
	while (offset < bytes) {
		size_t bytesToSend = bytes - offset;
		size_t bytesSent = _pStream->Write(sig, buffp + offset, bytesToSend);
		if (sig.IsSignalled()) return 0;
		offset += bytesSent;
	}
	return offset;
}

bool Stream_Http::DoSeek(Signal sig, long offset, size_t offsetPrev, SeekMode seekMode)
{
	size_t bytesToSkip = 0;
	if (seekMode == SeekSet) {
		if (static_cast<size_t>(offset) > offsetPrev) {
			bytesToSkip = static_cast<size_t>(offset) - offsetPrev;
		}
	} else if (seekMode == SeekCur) {
		if (offset > 0) bytesToSkip = static_cast<size_t>(offset);
	}
	const size_t bytesBuff = 32768;
	void *buff = _memory.Allocate(bytesBuff);
	while (bytesToSkip > 0) {
		size_t bytesToRead = ChooseMin(bytesToSkip, bytesBuff);
		size_t bytesRead = DoRead(sig, buff, bytesToRead);
		if (sig.IsSignalled()) return false;
		if (bytesRead == 0) {
			sig.SetError(ERR_IOError, "seek error");
			return false;
		}
		bytesToSkip -= bytesRead;
	}
	return true;
}

bool Stream_Http::DoFlush(Signal sig)
{
	return true;
}

bool Stream_Http::DoClose(Signal sig)
{
	return true;
}

size_t Stream_Http::DoGetSize()
{
	return _bytesRead;
}

Object *Stream_Http::DoGetStatObj(Signal sig)
{
	return new Object_Stat(_header);
}

bool Stream_Http::Cleanup(Signal sig)
{
	if (GetSize() == InvalidSize) return true;
	const size_t bytesBuff = 32768;
	void *buff = _memory.Allocate(bytesBuff);
	for (;;) {
		size_t bytesRead = Read(sig, buff, bytesBuff);
		if (bytesRead == 0) break;
	}
	return !sig.IsSignalled();
}

//-----------------------------------------------------------------------------
// Object_Stat
//-----------------------------------------------------------------------------
Object_Stat::~Object_Stat()
{
}

Object *Object_Stat::Clone() const
{
	return new Object_Stat(*this);
}

Value Object_Stat::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	Value value;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(header))) {
		return Value(_header.GetFieldsAsDict(env));
	} else if (_header.GetField(env, sig, pSymbol, value)) {
		return value;
	}
	evaluatedFlag = false;
	return Value::Null;
}

String Object_Stat::ToString(Signal sig, bool exprFlag)
{
	return String("<net.http.stat>");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Stat
//-----------------------------------------------------------------------------
// implementation of class Stat
AScript_ImplementPrivClass(Stat)
{
}

//-----------------------------------------------------------------------------
// Object_Connection implementation
//-----------------------------------------------------------------------------
Object_Connection::~Object_Connection()
{
	Object::Delete(_pObjServer);
	Stream::Delete(_pStreamHttp);
	if (_sock >= 0) ::closesocket(_sock);
}

Value Object_Connection::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(remote_ip))) {
		return Value(env, _remoteIP.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(remote_host))) {
		return Value(env, _remoteHost.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(remote_logname))) {
		return Value(env, _remoteLogname.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(local_ip))) {
		return Value(env, _localIP.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(local_host))) {
		return Value(env, _localHost.c_str());
	}
	evaluatedFlag = false;
	return Value::Null;
}

Object *Object_Connection::Clone() const
{
	return new Object_Connection(*this);
}

String Object_Connection::ToString(Signal sig, bool exprFlag)
{
	return String("<connection>");
}

bool Object_Connection::ReceiveRequest(Signal sig)
{
	if (!_request.Receive(sig, _sock)) return false;
	if (_request.HasBody()) {
		Header &header = _request.GetHeader();
		_pStreamHttp = header.GenerateDownStream(sig, this, _sock, "");
		if (sig.IsSignalled()) return false;
	}
	return true;
}

bool Object_Connection::CleanupRequest(Signal sig)
{
	if (_pStreamHttp == NULL) return true;
	bool rtn = _pStreamHttp->Cleanup(sig);
	Stream::Delete(_pStreamHttp);
	_pStreamHttp = NULL;
	return rtn;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Connection
//-----------------------------------------------------------------------------
// implementation of class Connection
AScript_ImplementPrivClass(Connection)
{
}

//-----------------------------------------------------------------------------
// Object_Request implementation
// for reference:
// http://httpd.apache.org/dev/apidoc/index.html
// http://www.temme.net/sander/api/httpd/httpd_8h-source.html
//-----------------------------------------------------------------------------
Object_Request::~Object_Request()
{
	Object::Delete(_pObjConnection);
}

Value Object_Request::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	Request &request = _pObjConnection->GetRequest();
	Header &header = request.GetHeader();
	Value value;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(method))) {
		return Value(env, request.GetMethod());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(uri))) {
		return Value(env, request.GetRequestURI());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(version))) {
		return Value(env, request.GetHttpVersion());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(body))) {
		Stream *pStream = _pObjConnection->GetStream();
		if (pStream == NULL) return Value::Null;
		return Value(new Object_Stream(env, Stream::Reference(pStream)));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(connection))) {
		return Value(Object_Connection::Reference(_pObjConnection));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(scheme))) {
		String str = ExtractURIScheme(sig, request.GetRequestURI(), NULL);
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		return Value(env, strUnquote.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(authority))) {
		String str = ExtractURIAuthority(sig, request.GetRequestURI(), NULL);
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		return Value(env, strUnquote.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(path))) {
		String str = ExtractURIPath(sig, request.GetRequestURI());
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		return Value(env, strUnquote.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(query))) {
		String str = ExtractURIQuery(sig, request.GetRequestURI());
		if (sig.IsSignalled()) return Value::Null;
		return Value(env, str.c_str()); // don't unescape query value
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(query_args))) {
		String str = ExtractURIQuery(sig, request.GetRequestURI());
		if (sig.IsSignalled()) return Value::Null;
		Value result = DecodeURIQuery(env, sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		return result;
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(fragment))) {
		String str = ExtractURIFragment(sig, request.GetRequestURI());
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		return Value(env, strUnquote.c_str());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(header))) {
		return header.GetFieldsAsDict(env);
	} else if (header.GetField(env, sig, pSymbol, value)) {
		return value;
	}
	evaluatedFlag = false;
	return Value::Null;
}

Object *Object_Request::Clone() const
{
	return NULL;
}

String Object_Request::ToString(Signal sig, bool exprFlag)
{
	Request &request = _pObjConnection->GetRequest();
	String str = "<request:";
	str += request.GetMethod();
	str += " ";
	str += request.GetRequestURI();
	str += " ";
	str += request.GetHttpVersion();
	str += ">";
	return str;
}

bool Object_Request::SendResponse(Signal sig,
		const char *statusCode, Stream *pStreamBody, const char *reasonPhrase,
		const char *httpVersion, const ValueDict &valueDict)
{
	int sock = _pObjConnection->GetSocket();
	Header &header = _status.GetHeader();
	_status.SetStatus(httpVersion, statusCode, reasonPhrase);
	if (!header.SetFields(sig, valueDict, pStreamBody)) return false;
	if (!_status.Send(sig, sock)) return false;
	if (pStreamBody != NULL) {
		if (!SendStreamBody(sig, sock, *pStreamBody)) return false;
	}
	return true;
}

Stream *Object_Request::SendRespChunk(Signal sig,
		const char *statusCode, const char *reasonPhrase,
		const char *httpVersion, const ValueDict &valueDict)
{
	int sock = _pObjConnection->GetSocket();
	Header &header = _status.GetHeader();
	_status.SetStatus(httpVersion, statusCode, reasonPhrase);
	if (!header.SetFields(sig, valueDict, NULL)) return NULL;
	header.SetField("Transfer-Encoding", "chunked");
	if (!_status.Send(sig, sock)) return NULL;
	return header.GenerateUpStream(sig, this, sock, "");
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Request
//-----------------------------------------------------------------------------
// net.http.request#response(code:string, body?:stream, reason?:string,
//                       version:string => 'HTTP/1.1', header%):reduce
AScript_DeclareMethod(Request, response)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "code", VTYPE_String);
	DeclareArg(env, "body", VTYPE_Stream, OCCUR_ZeroOrOnce);
	DeclareArg(env, "reason", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "version", VTYPE_String,
				OCCUR_Once, false, false, new Expr_String(HTTP_VERSION));
	DeclareDictArg("header");
}

AScript_ImplementMethod(Request, response)
{
	Object_Request *pSelf = Object_Request::GetSelfObj(args);
	if (!pSelf->SendResponse(sig,
			args.GetString(0), args.IsStream(1)? &args.GetStream(1) : NULL,
			args.IsString(2)? args.GetString(2) : NULL, args.GetString(3),
			args.GetDictArg())) {
		return Value::Null;
	}
	return args.GetSelf();
}

// net.http.request#respchunk(code:string, reason?:string,
//                        version:string => 'HTTP/1.1', header%):reduce
AScript_DeclareMethod(Request, respchunk)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "code", VTYPE_String);
	DeclareArg(env, "reason", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "version", VTYPE_String,
				OCCUR_Once, false, false, new Expr_String(HTTP_VERSION));
	DeclareDictArg("header");
}

AScript_ImplementMethod(Request, respchunk)
{
	Object_Request *pSelf = Object_Request::GetSelfObj(args);
	Stream *pStream = pSelf->SendRespChunk(sig, args.GetString(0),
			args.IsString(1)? args.GetString(1) : NULL, args.GetString(2),
			args.GetDictArg());
	if (sig.IsSignalled()) return Value::Null;
	return Value(new Object_Stream(env, pStream));
}

// net.http.request#ismethod(method:string)
AScript_DeclareMethod(Request, ismethod)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
	DeclareArg(env, "method", VTYPE_String);
}

AScript_ImplementMethod(Request, ismethod)
{
	Object_Request *pSelf = Object_Request::GetSelfObj(args);
	const char *method = pSelf->GetConnectionObj()->GetRequest().GetMethod();
	return Value(::strcasecmp(method, args.GetString(0)) == 0);
}

// implementation of class Request
AScript_ImplementPrivClass(Request)
{
	AScript_AssignMethod(Request, response);
	AScript_AssignMethod(Request, respchunk);
	AScript_AssignMethod(Request, ismethod);
}

//-----------------------------------------------------------------------------
// Object_Response implementation
//-----------------------------------------------------------------------------
Object_Response::~Object_Response()
{
	Object::Delete(_pObjClient);
}

Value Object_Response::DoPropGet(Signal sig, const Symbol *pSymbol, bool &evaluatedFlag)
{
	Environment &env = *this;
	evaluatedFlag = true;
	Status &status = _pObjClient->GetStatus();
	Header &header = status.GetHeader();
	Value value;
	if (pSymbol->IsIdentical(AScript_PrivSymbol(version))) {
		return Value(env, status.GetHttpVersion());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(code))) {
		return Value(env, status.GetStatusCode());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(reason))) {
		return Value(env, status.GetReasonPhrase());
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(body))) {
		Stream *pStream = _pObjClient->GetStream();
		if (pStream == NULL) return Value::Null;
		return Value(new Object_Stream(env, Stream::Reference(pStream)));
	} else if (pSymbol->IsIdentical(AScript_PrivSymbol(header))) {
		return header.GetFieldsAsDict(env);
	} else if (header.GetField(env, sig, pSymbol, value)) {
		return value;
	}
	evaluatedFlag = false;
	return Value::Null;
}

Object *Object_Response::Clone() const
{
	return NULL;
}

String Object_Response::ToString(Signal sig, bool exprFlag)
{
	Status &status = _pObjClient->GetStatus();
	String str = "<response:";
	str += status.GetHttpVersion();
	str += " ";
	str += status.GetStatusCode();
	str += " ";
	str += status.GetReasonPhrase();
	str += ">";
	return str;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Response
//-----------------------------------------------------------------------------
// implementation of class Response
AScript_ImplementPrivClass(Response)
{
}

//-----------------------------------------------------------------------------
// Object_Server implementation
//-----------------------------------------------------------------------------
Object_Server::Object_Server() :
		Object(AScript_PrivClass(Server)), _port(0), _sockListen(-1)
{
	FD_ZERO(&_fdsRead);
}

Object_Server::~Object_Server()
{
	if (_sockListen >= 0) ::closesocket(_sockListen);
	foreach (ConnectionList, ppObjConnection, _connectionList) {
		Object::Delete(*ppObjConnection);
	}
}

Object *Object_Server::Clone() const
{
	return NULL; //new Object_Server(*this);
}

String Object_Server::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += "<server:";
	if (_sockListen < 0) {
		str += "invalid";
	} else {
		str += _addr;
		str += ":";
		str += NumberToString(_port);
	}
	str += ">";
	return str;
}

bool Object_Server::Prepare(Signal sig, const char *addr, short port)
{
	_addr = (addr == NULL)? "localhost" : addr;
	_port = port;
	_sockListen = static_cast<int>(::socket(AF_INET, SOCK_STREAM, 0));
	if (_sockListen < 0) {
		sig.SetError(ERR_IOError, "failed to create a socket");
		return false;
	}
	sockaddr_in saddr;
	::memset(&saddr, 0x00, sizeof(saddr));
	unsigned long addrNum = ::htonl(INADDR_ANY);
	saddr.sin_family = AF_INET;
	if (addr != NULL) {
		addrNum = ::inet_addr(addr);
		if (addrNum == 0xffffffff) {
			hostent *pHostEnt = ::gethostbyname(addr);
			if (pHostEnt == NULL) {
				sig.SetError(ERR_IOError, "binding address not found: %s", addr);
				return false;
			}
			saddr.sin_family = pHostEnt->h_addrtype;
			addrNum = **reinterpret_cast<unsigned long **>(pHostEnt->h_addr_list);
		}
	}
	saddr.sin_addr.s_addr = addrNum;
	saddr.sin_port = ::htons(port);
	if (::bind(_sockListen, reinterpret_cast<sockaddr *>(&saddr), sizeof(saddr)) < 0) {
		sig.SetError(ERR_IOError, "failed to bind address to socket");
		return false;
	}
	if (::listen(_sockListen, 5) < 0) {
		sig.SetError(ERR_IOError, "failed to listen to port");
		return false;
	}
	return true;
}

Object_Request *Object_Server::Wait(Signal sig)
{
	Environment &env = *this;
	Object_Connection *pObjConnectionCur = NULL;
	for (ConnectionList::iterator ppObjConnection = _connectionList.begin();
								ppObjConnection != _connectionList.end(); ) {
		Object_Connection *pObjConnection = *ppObjConnection;
		Header &header = pObjConnection->GetRequest().GetHeader();
		if (header.IsField("Connection", "keep-alive")) {
			ppObjConnection++;
		} else {
			::closesocket(pObjConnection->GetSocket());
			Object::Delete(pObjConnection);
			ppObjConnection = _connectionList.erase(ppObjConnection);
		}
	}
	foreach (ConnectionList, ppObjConnection, _connectionList) {
		Object_Connection *pObjConnection = *ppObjConnection;
		if (!pObjConnection->CleanupRequest(sig)) return NULL;
	}
	for (;;) {
		if (FD_ISSET(_sockListen, &_fdsRead)) {
			FD_CLR(static_cast<unsigned int>(_sockListen), &_fdsRead);
			sockaddr_in saddr;
			socklen_t bytesAddr = sizeof(saddr);
			int sockClient = static_cast<int>(::accept(_sockListen,
							reinterpret_cast<sockaddr *>(&saddr), &bytesAddr));
			if (sockClient < 0) {
				sig.SetError(ERR_IOError, "failed to accept connection request");
				return NULL;
			}
			const char *remoteIP = ::inet_ntoa(saddr.sin_addr);
			const hostent *pHostEnt = ::gethostbyaddr(
						reinterpret_cast<char *>(&saddr.sin_addr), 4, AF_INET);
			const char *remoteHost = (pHostEnt == NULL)? remoteIP : pHostEnt->h_name;
			const char *remoteLogname = "";
			const char *localIP = "";
			const char *localHost = "";
			Object_Connection *pObjConnection = new Object_Connection(
					Object_Server::Reference(this), sockClient,
					remoteIP, remoteHost, remoteLogname, localIP, localHost);
			_connectionList.push_back(pObjConnection);
			pObjConnectionCur = Object_Connection::Reference(pObjConnection);
		} else {
			foreach (ConnectionList, ppObjConnection, _connectionList) {
				Object_Connection *pObjConnection = *ppObjConnection;
				if (pObjConnection->IsValid()) {
					int sock = pObjConnection->GetSocket();
					if (FD_ISSET(static_cast<unsigned int>(sock), &_fdsRead)) {
						FD_CLR(static_cast<unsigned int>(sock), &_fdsRead);
						pObjConnectionCur = Object_Connection::Reference(pObjConnection);
						break;
					}
				}
			}
		}
		if (pObjConnectionCur != NULL) {
			if (!pObjConnectionCur->ReceiveRequest(sig)) return NULL;
			if (pObjConnectionCur->GetRequest().IsComplete()) break;
			ConnectionList::iterator ppObjConnection = std::find(_connectionList.begin(),
										_connectionList.end(), pObjConnectionCur);
			_connectionList.erase(ppObjConnection);
			::closesocket(pObjConnectionCur->GetSocket());
			Object::Delete(pObjConnectionCur);
			pObjConnectionCur = NULL;
		}
		//timeval tv;
		FD_ZERO(&_fdsRead);
		FD_SET(static_cast<unsigned int>(_sockListen), &_fdsRead);
		int sockMax = _sockListen;
		foreach (ConnectionList, ppObjConnection, _connectionList) {
			Object_Connection *pObjConnection = *ppObjConnection;
			if (pObjConnection->IsValid()) {
				int sock = pObjConnection->GetSocket();
				FD_SET(static_cast<unsigned int>(sock), &_fdsRead);
				if (sockMax < sock) sockMax = sock;
			}
		}
		::select(sockMax + 1, &_fdsRead, NULL, NULL, NULL);
	}
	return new Object_Request(pObjConnectionCur);
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Server
//-----------------------------------------------------------------------------
// net.http.server#wait() {block?}
AScript_DeclareMethod(Server, wait)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementMethod(Server, wait)
{
	Object_Server *pSelf = Object_Server::GetSelfObj(args);
	if (!args.IsBlockSpecified()) {
		Object_Request *pObjRequest = pSelf->Wait(sig);
		if (sig.IsSignalled()) return Value::Null;
		return Value(pObjRequest);
	}
	Environment envBlock(&env, ENVTYPE_Block);
	const Function *pFuncBlock =
					args.GetBlockFunc(envBlock, sig, GetSymbolForBlock());
	if (pFuncBlock == NULL) return Value::Null;
	for (;;) {
		Object_Request *pObjRequest = pSelf->Wait(sig);
		if (sig.IsSignalled()) return Value::Null;
		Value value(pObjRequest);
		ValueList valListArg(value);
		Args argsSub(valListArg);
		pFuncBlock->Eval(env, sig, argsSub);
		if (sig.IsBreak()) {
			sig.ClearSignal();
			break;
		} else if (sig.IsContinue()) {
			sig.ClearSignal();
		} else if (sig.IsSignalled()) {
			break;
		}
	}
	return Value::Null;
}

// implementation of class Server
AScript_ImplementPrivClass(Server)
{
	AScript_AssignMethod(Server, wait);
}

//-----------------------------------------------------------------------------
// Object_Client implementation
//-----------------------------------------------------------------------------
Object_Client::~Object_Client()
{
	Stream::Delete(_pStreamHttp);
	if (_sock >= 0) ::closesocket(_sock);
}

Object *Object_Client::Clone() const
{
	return NULL; //new Object_Client(*this);
}

String Object_Client::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += "<client:";
	str += _addr;
	str += ":";
	str += NumberToString(_port);
	if (IsViaProxy()) {
		str += " via ";
		str += _addrProxy;
		str += ":";
		str += NumberToString(_portProxy);
	}
	str += (_sock < 0)? ":closed" : ":connected";
	str += ">";
	return str;
}

bool Object_Client::Prepare(Signal sig, const char *addr, short port,
				const char *addrProxy, short portProxy,
				const char *userIdProxy, const char *passwordProxy)
{
	if (addrProxy == NULL) {
		Environment &env = *this;
		const Value *pValueOfList = _pEnvSelf->LookupValue(AScript_PrivSymbol(proxies), false);
		if (pValueOfList != NULL && pValueOfList->IsList()) {
			foreach_const_reverse (ValueList, pValue, pValueOfList->GetList()) {
				if (!pValue->IsType(AScript_PrivVTYPE(Proxy))) continue;
				Object_Proxy *pObjProxy = Object_Proxy::GetObject(*pValue);
				if (pObjProxy->IsResponsible(env, sig, addr)) {
					addrProxy = pObjProxy->GetAddr();
					portProxy = pObjProxy->GetPort();
					userIdProxy = pObjProxy->GetUserId();
					passwordProxy = pObjProxy->GetPassword();
					break;
				}
				if (sig.IsSignalled()) return false;
			}
		}
	}
	const char *addrToConnect = addr;
	short portToConnect = port;
	_addr = addr, _port = port;
	if (addrProxy != NULL) {
		_addrProxy = addrProxy, _portProxy = portProxy;
		_userIdProxy = userIdProxy, _passwordProxy = passwordProxy;
		addrToConnect = addrProxy, portToConnect = portProxy;
	}
	_sock = static_cast<int>(::socket(AF_INET, SOCK_STREAM, 0)); //IPPROTO_TCP);
	if (_sock < 0) {
		sig.SetError(ERR_IOError, "failed to create a socket");
		return false;
	}
	sockaddr_in saddr;
	::memset(&saddr, 0x00, sizeof(saddr));
	unsigned long addrNum = ::inet_addr(addrToConnect);
	if (addrNum == 0xffffffff) {
		hostent *pHostEnt = ::gethostbyname(addrToConnect);
		if (pHostEnt == NULL) {
			sig.SetError(ERR_IOError, "host not found: %s", addrToConnect);
			return false;
		}
		saddr.sin_family = pHostEnt->h_addrtype;
		addrNum = **reinterpret_cast<unsigned long **>(pHostEnt->h_addr_list);
	} else {
		saddr.sin_family = AF_INET;
	}
	saddr.sin_addr.s_addr = addrNum;
	saddr.sin_port = ::htons(portToConnect);
	if (::connect(_sock, reinterpret_cast<sockaddr *>(&saddr), sizeof(saddr)) < 0) {
		sig.SetError(ERR_IOError, "failed to connect to%s host %s",
							IsViaProxy()? " proxy" : "", addrToConnect);
		return false;
	}
	return true;
}

Object_Response *Object_Client::SendRequest(Signal sig,
		const char *method, const char *uri, Stream *pStreamBody,
		const char *httpVersion, const ValueDict &valueDict)
{
	Environment &env = *this;
	if (!CleanupResponse(sig)) return NULL;
	if (_sock < 0) {
		sig.SetError(ERR_IOError, "access to invalid socket");
		return NULL;
	}
	String uriFull;
	uriFull = "http://";
	uriFull += _addr;
	uriFull += uri;
	_request.SetRequest(method, IsViaProxy()? uriFull.c_str() : uri, httpVersion);
	Header &header = _request.GetHeader();
	if (!header.SetFields(sig, valueDict, pStreamBody)) return NULL;
	do {
		String str;
		str += _addr;
		str += ":";
		str += NumberToString(_port);
		header.SetField("Host", str.c_str());
	} while (0);
	if (!_userIdProxy.empty()) {
		String str;
		str = _userIdProxy;
		str += ":";
		str += _passwordProxy;
		Binary buff;
		buff = "Basic ";
		if (!g_pEncoderBase64->Encode(sig, buff, str.c_str())) return NULL;
		header.SetField("Proxy-Authorization", buff.c_str());
	}
	if (!_request.Send(sig, _sock)) return NULL;
	if (pStreamBody != NULL) {
		if (!SendStreamBody(sig, _sock, *pStreamBody)) return NULL;
	}
	if (!_status.Receive(sig, _sock)) return NULL;
	if (::strcasecmp(method, "HEAD") != 0 && _status.HasBody()) {
		Header &header = _status.GetHeader();
		_pStreamHttp = header.GenerateDownStream(sig, this, _sock, uriFull.c_str());
		if (sig.IsSignalled()) return NULL;
	}
	return new Object_Response(Object_Client::Reference(this));
}

bool Object_Client::CleanupResponse(Signal sig)
{
	if (_pStreamHttp == NULL) return true;
	Header &header = _status.GetHeader();
	bool rtn = true;
	if (header.IsField("Connection", "close")) {
		::closesocket(_sock);
		_sock = -1;
	} else if (header.IsField("Proxy-Connection", "close")) {
		::closesocket(_sock);
		_sock = -1;
	} else {
		rtn = _pStreamHttp->Cleanup(sig);
	}
	Stream::Delete(_pStreamHttp);
	_pStreamHttp = NULL;
	return rtn;
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Client
//-----------------------------------------------------------------------------
// net.http.client#request(method:string, uri:string, body?:stream,
//                     version:string => 'HTTP/1.1', header%)
AScript_DeclareMethod(Client, request)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "method", VTYPE_String);
	DeclareArg(env, "uri", VTYPE_String);
	DeclareArg(env, "body", VTYPE_Stream, OCCUR_ZeroOrOnce);
	DeclareArg(env, "version", VTYPE_String,
					OCCUR_Once, false, false, new Expr_String(HTTP_VERSION));
	DeclareDictArg("header");
}

AScript_ImplementMethod(Client, request)
{
	Object_Client *pSelf = Object_Client::GetSelfObj(args);
	Object_Response *pObjResponse = pSelf->SendRequest(sig,
			args.GetString(0), args.GetString(1),
			args.IsStream(2)? &args.GetStream(2) : NULL,
			args.GetString(3), args.GetDictArg());
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObjResponse);
}

// net.http.client#_request(uri:string, body?:stream,
//                     version:string => 'HTTP/1.1', header%)
AScript_DeclareMethod(Client, _request)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "uri", VTYPE_String);
	DeclareArg(env, "body", VTYPE_Stream, OCCUR_ZeroOrOnce);
	DeclareArg(env, "version", VTYPE_String,
					OCCUR_Once, false, false, new Expr_String(HTTP_VERSION));
	DeclareDictArg("header");
}

AScript_ImplementMethod(Client, _request)
{
	Object_Client *pSelf = Object_Client::GetSelfObj(args);
	Object_Response *pObjResponse = pSelf->SendRequest(sig,
			Upper(GetName()).c_str(), args.GetString(0),
			args.IsStream(1)? &args.GetStream(1) : NULL,
			args.GetString(2), args.GetDictArg());
	if (sig.IsSignalled()) return Value::Null;
	return Value(pObjResponse);
}

// net.http.client#cleanup()
AScript_DeclareMethod(Client, cleanup)
{
	SetMode(RSLTMODE_Reduce, FLAG_None);
}

AScript_ImplementMethod(Client, cleanup)
{
	Object_Client *pSelf = Object_Client::GetSelfObj(args);
	if (!pSelf->CleanupResponse(sig)) return Value::Null;
	return args.GetSelf();
}

// implementation of class Client
AScript_ImplementPrivClass(Client)
{
	AScript_AssignMethod(Client, request);
	AScript_AssignMethod(Client, cleanup);
	AScript_AssignMethodEx(Client, _request, "options");	// RFC 2616 9.2
	AScript_AssignMethodEx(Client, _request, "get");		// RFC 2616 9.3
	AScript_AssignMethodEx(Client, _request, "head");		// RFC 2616 9.4
	AScript_AssignMethodEx(Client, _request, "post");		// RFC 2616 9.5
	AScript_AssignMethodEx(Client, _request, "put");		// RFC 2616 9.6
	AScript_AssignMethodEx(Client, _request, "delete");		// RFC 2616 9.7
	AScript_AssignMethodEx(Client, _request, "trace");		// RFC 2616 9.8
	AScript_AssignMethodEx(Client, _request, "connect");	// RFC 2616 9.9
}

//-----------------------------------------------------------------------------
// Object_Proxy implementation
//-----------------------------------------------------------------------------
Object_Proxy::~Object_Proxy()
{
	Function::Delete(_pFuncCriteria);
}

Object *Object_Proxy::Clone() const
{
	return NULL;
}

String Object_Proxy::ToString(Signal sig, bool exprFlag)
{
	String str;
	str += "<proxy:";
	str += _addr;
	str += ":";
	str += NumberToString(_port);
	str += ">";
	return str;
}

bool Object_Proxy::IsResponsible(Environment &env, Signal sig, const char *addr) const
{
	if (_pFuncCriteria == NULL) return true;
	ValueList valListArg(Value(env, addr));
	Args args(valListArg);
	Value result = _pFuncCriteria->Eval(env, sig, args);
	if (sig.IsSignalled()) return false;
	return result.GetBoolean();
}

//-----------------------------------------------------------------------------
// AScript interfaces for Object_Proxy
//-----------------------------------------------------------------------------
// implementation of class Proxy
AScript_ImplementPrivClass(Proxy)
{
}

//-----------------------------------------------------------------------------
// AScript module functions: http
//-----------------------------------------------------------------------------
// net.http.uri(scheme:string, authority:string, path:string, query?:string, fragment?:string)
AScript_DeclareFunction(uri)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "scheme", VTYPE_String);
	DeclareArg(env, "authority", VTYPE_String);
	DeclareArg(env, "path", VTYPE_String);
	DeclareArg(env, "query", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "fragment", VTYPE_String, OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(uri)
{
	String str;
	if (*args.GetString(0) != '\0') {
		str += QuoteURI(args.GetString(0));
		str += "://";
	}
	str += QuoteURI(args.GetString(1));
	const char *path = args.GetString(2);
	if (*path != '/') str += "/";
	str += QuoteURI(path);
	if (args.IsString(3)) {
		str += "?";
		str += args.GetString(3);
	}
	if (args.IsString(4)) {
		str += "#";
		str += QuoteURI(args.GetString(4));
	}
	return Value(env, str.c_str());
}

// net.http.splituri(uri:string)
AScript_DeclareFunction(splituri)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "uri", VTYPE_String);
}

AScript_ImplementFunction(splituri)
{
	const char *uri = args.GetString(0);
	Value result;
	ValueList &valList = result.InitAsList(env);
	do {
		String str = ExtractURIScheme(sig, uri, NULL);
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		valList.push_back(Value(env, strUnquote.c_str()));
	} while (0);
	do {
		String str = ExtractURIAuthority(sig, uri, NULL);
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		valList.push_back(Value(env, strUnquote.c_str()));
	} while (0);
	do {
		String str = ExtractURIPath(sig, uri);
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		valList.push_back(Value(env, strUnquote.c_str()));
	} while (0);
	do {
		String str = ExtractURIQuery(sig, uri);
		if (sig.IsSignalled()) return Value::Null;
		valList.push_back(Value(env, str.c_str()));
	} while (0);
	do {
		String str = ExtractURIFragment(sig, uri);
		if (sig.IsSignalled()) return Value::Null;
		String strUnquote = UnquoteURI(sig, str.c_str());
		if (sig.IsSignalled()) return Value::Null;
		valList.push_back(Value(env, strUnquote.c_str()));
	} while (0);
	return result;
}

// net.http.decquery(query:string)
AScript_DeclareFunction(decquery)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "query", VTYPE_String);
}

AScript_ImplementFunction(decquery)
{
	return DecodeURIQuery(env, sig, args.GetString(0));
}

// net.http.addproxy(addr:string, port:number, userid:string, password:string) {criteria?}
AScript_DeclareFunction(addproxy)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "addr", VTYPE_String);
	DeclareArg(env, "port", VTYPE_Number);
	DeclareArg(env, "userid", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "password", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce, Symbol::Add("criteria"));
}

AScript_ImplementFunction(addproxy)
{
	ValueList *pValList = NULL;
	Value *pValue = _pEnvSelf->LookupValue(AScript_PrivSymbol(proxies), false);
	if (pValue == NULL || !pValue->IsList()) {
		Value value;
		pValList = &value.InitAsList(env);
		_pEnvSelf->AssignValue(AScript_PrivSymbol(proxies), value, false);
	} else {
		pValList = &pValue->GetList();
	}
	const Function *pFuncCriteria = args.GetBlockFunc(env, sig, GetSymbolForBlock());
	Value value(new Object_Proxy(args.GetString(0), args.GetShort(1),
				args.IsString(2)? args.GetString(2) : "",
				args.IsString(3)? args.GetString(3) : "",
				Function::Reference(pFuncCriteria)));
	pValList->push_back(value);
	return Value::Null;
}

// net.http.server(addr?:string, port:number => 80) {block?}
AScript_DeclareFunction(server)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "addr", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "port", VTYPE_Number,
						OCCUR_Once, false, false, new Expr_Value(80));
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(server)
{
	Object_Server *pObjServer = new Object_Server();
	if (!pObjServer->Prepare(sig,
				args.IsString(0)? args.GetString(0) : NULL, args.GetShort(1))) {
		Object::Delete(pObjServer);
		return Value::Null;
	}
	return ReturnValue(env, sig, args, Value(pObjServer));
}

// net.http.client(addr:string, port:number => 80,
//             addrProxy?:string, portProxy?:number,
//             useridProxy?:string, passwordProxy?:string) {block?}
AScript_DeclareFunction(client)
{
	SetMode(RSLTMODE_Normal, FLAG_None);
	DeclareArg(env, "addr", VTYPE_String);
	DeclareArg(env, "port", VTYPE_Number,
						OCCUR_Once, false, false, new Expr_Value(80));
	DeclareArg(env, "addrProxy", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "portProxy", VTYPE_Number, OCCUR_ZeroOrOnce);
	DeclareArg(env, "useridProxy", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareArg(env, "passwordProxy", VTYPE_String, OCCUR_ZeroOrOnce);
	DeclareBlock(OCCUR_ZeroOrOnce);
}

AScript_ImplementFunction(client)
{
	Object_Client *pObjClient = new Object_Client();
	const char *addrProxy = NULL;
	short portProxy = 0;
	const char *userIdProxy = "";
	const char *passwordProxy = "";
	if (args.IsString(2)) {
		if (!args.IsNumber(3)) {
			Declaration::SetError_NotEnoughArguments(sig);
			return Value::Null;
		}
		addrProxy = args.GetString(2);
		portProxy = args.GetShort(3);
		if (args.IsString(4)) userIdProxy = args.GetString(4);
		if (args.IsString(5)) passwordProxy = args.GetString(5);
	}
	if (!pObjClient->Prepare(sig, args.GetString(0), args.GetShort(1),
							addrProxy, portProxy, userIdProxy, passwordProxy)) {
		Object::Delete(pObjClient);
		return Value::Null;
	}
	return ReturnValue(env, sig, args, Value(pObjClient));
}

//-----------------------------------------------------------------------------
// Directory_Http implementation
//-----------------------------------------------------------------------------
Directory_Http::Directory_Http(Directory *pParent, const char *name, Type type) :
							Directory(pParent, name, type, '/')
{
}

Directory_Http::~Directory_Http()
{
}

Directory *Directory_Http::DoNext(Environment &env, Signal sig)
{
	sig.SetError(ERR_SystemError, "");
	return NULL;
}

Stream *Directory_Http::DoOpenStream(Environment &env, Signal sig,
									unsigned long attr, const char *encoding)
{
	Object_Client *pObjClient = new Object_Client();
	String pathName;
	Directory_Http *pDirectoryTop = NULL;
	if (IsContainer()) pathName = "/";
	for (Directory_Http *pDirectory = this; pDirectory != NULL;
		pDirectory = dynamic_cast<Directory_Http *>(pDirectory->GetParent())) {
		if (pDirectory->GetParent() == NULL) {
			pDirectoryTop = pDirectory;
		} else {
			String str = "/";
			str += pDirectory->GetName();
			pathName = str + pathName;
		}
	}
	if (pathName.empty()) pathName = "/";
	do {
		const char *query = pDirectoryTop->GetQuery();
		if (*query != '\0') {
			pathName += "?";
			pathName += query;
		}
	} while (0);
	do {
		const char *fragment = pDirectoryTop->GetFragment();
		if (*fragment != '\0') {
			pathName += "#";
			pathName += fragment;
		}
	} while (0);
	//::printf("%s %s\n", pDirectoryTop->GetAuthority(), pathName.c_str());
	short port = 80;
	const char *addrProxy = NULL;
	short portProxy = 0;
	const char *userIdProxy = "";
	const char *passwordProxy = "";
	if (!pObjClient->Prepare(sig, pDirectoryTop->GetAuthority(), port, NULL, 0, "", "")) {
		Object::Delete(pObjClient);
		return NULL;
	}
	Object_Response *pObjResponse = pObjClient->SendRequest(sig,
			"GET", pathName.c_str(), NULL, HTTP_VERSION, ValueDict::Null);
	if (sig.IsSignalled()) return NULL;
	Object::Delete(pObjClient);
	Stream *pStream = pObjClient->GetStream();
	if (pStream == NULL) {
		sig.SetError(ERR_IOError, "no body");
		return NULL;
	}
	return Stream::Reference(pStream);
}

//-----------------------------------------------------------------------------
// DirectoryFactory_Http implementation
//-----------------------------------------------------------------------------
bool DirectoryFactory_Http::IsResponsible(Environment &env, Signal sig,
						const Directory *pParent, const char *pathName)
{
	return pParent == NULL &&
		(StartsWith(pathName, "http:", 0, false) ||
		 StartsWith(pathName, "https:", 0, false));
}

Directory *DirectoryFactory_Http::DoOpenDirectory(Environment &env, Signal sig,
	Directory *pParent, const char **pPathName, Directory::NotFoundMode notFoundMode)
{
	const char *uri = *pPathName;
	String scheme = ExtractURIScheme(sig, uri, NULL);
	const char *p = uri;
	String authority = ExtractURIAuthority(sig, p, &p);
	String field;
	Directory_Http *pDirectoryTop, *pDirectory;
	do {
		String name = scheme;
		name += "://";
		name += authority;
		name += field;
		pDirectoryTop = pDirectory = new Directory_Http(
						NULL, name.c_str(), Directory::TYPE_Container);
		pParent = pDirectory;
		for ( ; *p == '/'; p++) ;
	} while (0);
	for ( ; ; p++) {
		char ch = *p;
		if (ch == '/' || ch == '\0' || ch == '?' || ch == '#') {
			Directory::Type type = IsFileSeparator(ch)?
						Directory::TYPE_Container : Directory::TYPE_Item;
			pDirectory = new Directory_Http(
						Directory::Reference(pParent), field.c_str(), type);
			pParent = pDirectory;
			field.clear();
			if (!IsFileSeparator(ch)) break;
		} else {
			field += ch;
		}
	}
	pDirectoryTop->SetScheme(scheme.c_str());
	pDirectoryTop->SetAuthority(authority.c_str());
	pDirectoryTop->SetQuery(ExtractURIQuery(sig, p).c_str());
	if (sig.IsSignalled()) return NULL;
	pDirectoryTop->SetFragment(ExtractURIFragment(sig, p).c_str());
	if (sig.IsSignalled()) return NULL;
	*pPathName = uri + ::strlen(uri);
	return pDirectory;
}

// Module entry
AScript_ModuleEntry()
{
	_pEnvSelf = &env;
#if defined(HAVE_WINDOWS_H)
	WSADATA wsaData;
	::WSAStartup(MAKEWORD(2, 0), &wsaData);
#endif
	// symbol realization
	AScript_RealizePrivSymbol(request);
	AScript_RealizePrivSymbol(status);
	AScript_RealizePrivSymbol(header);
	AScript_RealizePrivSymbol(method);
	AScript_RealizePrivSymbol(uri);
	AScript_RealizePrivSymbol(scheme);
	AScript_RealizePrivSymbol(authority);
	AScript_RealizePrivSymbol(path);
	AScript_RealizePrivSymbol(query);
	AScript_RealizePrivSymbol(query_args);
	AScript_RealizePrivSymbol(fragment);
	AScript_RealizePrivSymbol(version);
	AScript_RealizePrivSymbol(code);
	AScript_RealizePrivSymbol(reason);
	AScript_RealizePrivSymbol(proxies);
	AScript_RealizePrivSymbol(body);
	AScript_RealizePrivSymbol(remote_ip);
	AScript_RealizePrivSymbol(remote_host);
	AScript_RealizePrivSymbol(remote_logname);
	AScript_RealizePrivSymbol(local_ip);
	AScript_RealizePrivSymbol(local_host);
	AScript_RealizePrivSymbol(accept);				// 14.1  Accept
	AScript_RealizePrivSymbol(accept_charset);		// 14.2  Accept-Charset
	AScript_RealizePrivSymbol(accept_encoding);		// 14.3  Accept-Encoding
	AScript_RealizePrivSymbol(accept_language);		// 14.4  Accept-Language
	AScript_RealizePrivSymbol(accept_ranges);		// 14.5  Accept-Ranges
	AScript_RealizePrivSymbol(connection);			// 14.10 Connection
	AScript_RealizePrivSymbol(content_type);		// 14.17 Content-Type
	AScript_RealizePrivSymbol(etag);				// 14.19 ETag
	AScript_RealizePrivSymbol(date);				// 14.18 Date
	AScript_RealizePrivSymbol(expires);				// 14.21 Expires
	AScript_RealizePrivSymbol(host);				// 14.23 Host
	AScript_RealizePrivSymbol(last_modified);		// 14.29 Last-Modified
	AScript_RealizePrivSymbol(server);				// 14.38 Server
	AScript_RealizePrivSymbol(transfer_encoding);	// 14.41 Transfer-Encoding
	AScript_RealizePrivSymbol(user_agent);			// 14.43 User-Agent
	AScript_RealizePrivSymbol(via);					// 14.45 Via
	// class realization
	AScript_RealizePrivClass(Stat, "stat", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Request, "request", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Response, "response", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Connection, "connection", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Server, "server", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Client, "client", env.LookupClass(VTYPE_Object));
	AScript_RealizePrivClass(Proxy, "proxy", env.LookupClass(VTYPE_Object));
	// function assignment
	AScript_AssignFunction(uri);
	AScript_AssignFunction(splituri);
	AScript_AssignFunction(decquery);
	AScript_AssignFunction(addproxy);
	AScript_AssignFunction(server);
	AScript_AssignFunction(client);
	// registration of directory factory
	DirectoryFactory::Register(new DirectoryFactory_Http());
	CodecFactory *pCodecFactory = CodecFactory::Lookup("base64");
	g_pEncoderBase64 = pCodecFactory->CreateEncoder(false);
}

AScript_ModuleTerminate()
{
}

AScript_EndModule(net_http, http)

AScript_RegisterModule(net_http)
