//
// HTTPResponse.cpp
//
// Library: Net
// Package: HTTP
// Module:  HTTPResponse
//
// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/Net/HTTPResponse.h"
#include "Poco/Net/NetException.h"
#include "Poco/NumberFormatter.h"
#include "Poco/NumberParser.h"
#include "Poco/DateTime.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/DateTimeParser.h"
#include "Poco/Ascii.h"
#include "Poco/String.h"


using Poco::DateTime;
using Poco::NumberFormatter;
using Poco::NumberParser;
using Poco::DateTimeFormatter;
using Poco::DateTimeFormat;
using Poco::DateTimeParser;


namespace Poco {
namespace Net {


const std::string HTTPResponse::HTTP_REASON_CONTINUE                        = "Continue";
const std::string HTTPResponse::HTTP_REASON_SWITCHING_PROTOCOLS             = "Switching Protocols";
const std::string HTTPResponse::HTTP_REASON_PROCESSING                      = "Processing";
const std::string HTTPResponse::HTTP_REASON_OK                              = "OK";
const std::string HTTPResponse::HTTP_REASON_CREATED                         = "Created";
const std::string HTTPResponse::HTTP_REASON_ACCEPTED                        = "Accepted";
const std::string HTTPResponse::HTTP_REASON_NONAUTHORITATIVE                = "Non-Authoritative Information";
const std::string HTTPResponse::HTTP_REASON_NO_CONTENT                      = "No Content";
const std::string HTTPResponse::HTTP_REASON_RESET_CONTENT                   = "Reset Content";
const std::string HTTPResponse::HTTP_REASON_PARTIAL_CONTENT                 = "Partial Content";
const std::string HTTPResponse::HTTP_REASON_MULTI_STATUS                    = "Multi Status";
const std::string HTTPResponse::HTTP_REASON_ALREADY_REPORTED                = "Already Reported";
const std::string HTTPResponse::HTTP_REASON_IM_USED                         = "IM Used";
const std::string HTTPResponse::HTTP_REASON_MULTIPLE_CHOICES                = "Multiple Choices";
const std::string HTTPResponse::HTTP_REASON_MOVED_PERMANENTLY               = "Moved Permanently";
const std::string HTTPResponse::HTTP_REASON_FOUND                           = "Found";
const std::string HTTPResponse::HTTP_REASON_SEE_OTHER                       = "See Other";
const std::string HTTPResponse::HTTP_REASON_NOT_MODIFIED                    = "Not Modified";
const std::string HTTPResponse::HTTP_REASON_USE_PROXY                       = "Use Proxy";
const std::string HTTPResponse::HTTP_REASON_TEMPORARY_REDIRECT              = "Temporary Redirect";
const std::string HTTPResponse::HTTP_REASON_PERMANENT_REDIRECT              = "Permanent Redirect";
const std::string HTTPResponse::HTTP_REASON_BAD_REQUEST                     = "Bad Request";
const std::string HTTPResponse::HTTP_REASON_UNAUTHORIZED                    = "Unauthorized";
const std::string HTTPResponse::HTTP_REASON_PAYMENT_REQUIRED                = "Payment Required";
const std::string HTTPResponse::HTTP_REASON_FORBIDDEN                       = "Forbidden";
const std::string HTTPResponse::HTTP_REASON_NOT_FOUND                       = "Not Found";
const std::string HTTPResponse::HTTP_REASON_METHOD_NOT_ALLOWED              = "Method Not Allowed";
const std::string HTTPResponse::HTTP_REASON_NOT_ACCEPTABLE                  = "Not Acceptable";
const std::string HTTPResponse::HTTP_REASON_PROXY_AUTHENTICATION_REQUIRED   = "Proxy Authentication Required";
const std::string HTTPResponse::HTTP_REASON_REQUEST_TIMEOUT                 = "Request Time-out";
const std::string HTTPResponse::HTTP_REASON_CONFLICT                        = "Conflict";
const std::string HTTPResponse::HTTP_REASON_GONE                            = "Gone";
const std::string HTTPResponse::HTTP_REASON_LENGTH_REQUIRED                 = "Length Required";
const std::string HTTPResponse::HTTP_REASON_PRECONDITION_FAILED             = "Precondition Failed";
const std::string HTTPResponse::HTTP_REASON_REQUEST_ENTITY_TOO_LARGE        = "Request Entity Too Large";
const std::string HTTPResponse::HTTP_REASON_REQUEST_URI_TOO_LONG            = "Request-URI Too Large";
const std::string HTTPResponse::HTTP_REASON_UNSUPPORTED_MEDIA_TYPE          = "Unsupported Media Type";
const std::string HTTPResponse::HTTP_REASON_REQUESTED_RANGE_NOT_SATISFIABLE = "Requested Range Not Satisfiable";
const std::string HTTPResponse::HTTP_REASON_EXPECTATION_FAILED              = "Expectation Failed";
const std::string HTTPResponse::HTTP_REASON_IM_A_TEAPOT                     = "I'm a Teapot";
const std::string HTTPResponse::HTTP_REASON_ENCHANCE_YOUR_CALM              = "Enchance Your Calm";
const std::string HTTPResponse::HTTP_REASON_MISDIRECTED_REQUEST             = "Misdirected Request";
const std::string HTTPResponse::HTTP_REASON_UNPROCESSABLE_ENTITY            = "Unprocessable Entity";
const std::string HTTPResponse::HTTP_REASON_LOCKED                          = "Locked";
const std::string HTTPResponse::HTTP_REASON_FAILED_DEPENDENCY               = "Failed Dependency";
const std::string HTTPResponse::HTTP_REASON_UPGRADE_REQUIRED                = "Upgrade Required";
const std::string HTTPResponse::HTTP_REASON_PRECONDITION_REQUIRED           = "Precondition Required";
const std::string HTTPResponse::HTTP_REASON_TOO_MANY_REQUESTS               = "Too Many Requests";
const std::string HTTPResponse::HTTP_REASON_REQUEST_HEADER_FIELDS_TOO_LARGE = "Request Header Fields Too Large";
const std::string HTTPResponse::HTTP_REASON_UNAVAILABLE_FOR_LEGAL_REASONS   = "Unavailable For Legal Reasons";
const std::string HTTPResponse::HTTP_REASON_INTERNAL_SERVER_ERROR           = "Internal Server Error";
const std::string HTTPResponse::HTTP_REASON_NOT_IMPLEMENTED                 = "Not Implemented";
const std::string HTTPResponse::HTTP_REASON_BAD_GATEWAY                     = "Bad Gateway";
const std::string HTTPResponse::HTTP_REASON_SERVICE_UNAVAILABLE             = "Service Unavailable";
const std::string HTTPResponse::HTTP_REASON_GATEWAY_TIMEOUT                 = "Gateway Time-Out";
const std::string HTTPResponse::HTTP_REASON_VERSION_NOT_SUPPORTED           = "HTTP Version Not Supported";
const std::string HTTPResponse::HTTP_REASON_VARIANT_ALSO_NEGOTIATES         = "Variant Also Negotiates";
const std::string HTTPResponse::HTTP_REASON_INSUFFICIENT_STORAGE            = "Insufficient Storage";
const std::string HTTPResponse::HTTP_REASON_LOOP_DETECTED                   = "Loop Detected";
const std::string HTTPResponse::HTTP_REASON_NOT_EXTENDED                    = "Not Extended";
const std::string HTTPResponse::HTTP_REASON_NETWORK_AUTHENTICATION_REQUIRED = "Network Authentication Required";
const std::string HTTPResponse::HTTP_REASON_UNKNOWN                         = "???";
const std::string HTTPResponse::DATE       = "Date";
const std::string HTTPResponse::SET_COOKIE = "Set-Cookie";


HTTPResponse::HTTPResponse():
	_status(HTTP_OK),
	_reason(getReasonForStatus(HTTP_OK))
{
}

	
HTTPResponse::HTTPResponse(HTTPStatus status, const std::string& reason):
	_status(status),
	_reason(reason)
{
}


	
HTTPResponse::HTTPResponse(const std::string& version, HTTPStatus status, const std::string& reason):
	HTTPMessage(version),
	_status(status),
	_reason(reason)
{
}

	
HTTPResponse::HTTPResponse(HTTPStatus status):
	_status(status),
	_reason(getReasonForStatus(status))
{
}


HTTPResponse::HTTPResponse(const std::string& version, HTTPStatus status):
	HTTPMessage(version),
	_status(status),
	_reason(getReasonForStatus(status))
{
}


HTTPResponse::~HTTPResponse()
{
}


void HTTPResponse::setStatus(HTTPStatus status)
{
	_status = status;
}


void HTTPResponse::setStatus(const std::string& status)
{
	setStatus((HTTPStatus) NumberParser::parse(status));
}
	
	
void HTTPResponse::setReason(const std::string& reason)
{
	_reason = reason;
}


void HTTPResponse::setStatusAndReason(HTTPStatus status, const std::string& reason)
{
	_status = status;
	_reason = reason;
}

	
void HTTPResponse::setStatusAndReason(HTTPStatus status)
{
	setStatusAndReason(status, getReasonForStatus(status));
}


void HTTPResponse::setDate(const Poco::Timestamp& dateTime)
{
	set(DATE, DateTimeFormatter::format(dateTime, DateTimeFormat::HTTP_FORMAT));
}

	
Poco::Timestamp HTTPResponse::getDate() const
{
	const std::string& dateTime = get(DATE);
	int tzd;
	return DateTimeParser::parse(dateTime, tzd).timestamp();
}


void HTTPResponse::addCookie(const HTTPCookie& cookie)
{
	add(SET_COOKIE, cookie.toString());
}


void HTTPResponse::getCookies(std::vector<HTTPCookie>& cookies) const
{
	cookies.clear();
	NameValueCollection::ConstIterator it = find(SET_COOKIE);
	while (it != end() && Poco::icompare(it->first, SET_COOKIE) == 0)
	{
		NameValueCollection nvc;
		splitParameters(it->second.begin(), it->second.end(), nvc);
		cookies.push_back(HTTPCookie(nvc));
		++it;
	}
}


void HTTPResponse::write(std::ostream& ostr) const
{
	ostr << getVersion() << " " << static_cast<int>(_status) << " " << _reason << "\r\n";
	HTTPMessage::write(ostr);
	ostr << "\r\n";
}


void HTTPResponse::read(std::istream& istr)
{
	static const int eof = std::char_traits<char>::eof();

	std::string version;
	std::string status;
	std::string reason;
	
	int ch =  istr.get();
	if (istr.bad()) throw NetException("Error reading HTTP response header");
	if (ch == eof) throw NoMessageException();
	while (Poco::Ascii::isSpace(ch)) ch = istr.get();
	if (ch == eof) throw MessageException("No HTTP response header");
	while (!Poco::Ascii::isSpace(ch) && ch != eof && version.length() < MAX_VERSION_LENGTH) { version += (char) ch; ch = istr.get(); }
	if (!Poco::Ascii::isSpace(ch)) throw MessageException("Invalid HTTP version string");
	while (Poco::Ascii::isSpace(ch)) ch = istr.get();
	while (!Poco::Ascii::isSpace(ch) && ch != eof && status.length() < MAX_STATUS_LENGTH) { status += (char) ch; ch = istr.get(); }
	if (!Poco::Ascii::isSpace(ch)) throw MessageException("Invalid HTTP status code");
	while (Poco::Ascii::isSpace(ch) && ch != '\r' && ch != '\n' && ch != eof) ch = istr.get();
	while (ch != '\r' && ch != '\n' && ch != eof && reason.length() < MAX_REASON_LENGTH) { reason += (char) ch; ch = istr.get(); }
	if (!Poco::Ascii::isSpace(ch)) throw MessageException("HTTP reason string too long");
	if (ch == '\r') ch = istr.get();
	if (ch != '\n') throw MessageException("Unterminated HTTP response line");

	HTTPMessage::read(istr);
	ch = istr.get();
	while (ch != '\n' && ch != eof) { ch = istr.get(); }
	setVersion(version);
	setStatus(status);
	setReason(reason);
}


const std::string& HTTPResponse::getReasonForStatus(HTTPStatus status)
{
	switch (status)
	{
	case HTTP_CONTINUE: 
		return HTTP_REASON_CONTINUE;
	case HTTP_SWITCHING_PROTOCOLS: 
		return HTTP_REASON_SWITCHING_PROTOCOLS;
	case HTTP_PROCESSING:
		return HTTP_REASON_PROCESSING;
	case HTTP_OK: 
		return HTTP_REASON_OK;
	case HTTP_CREATED: 
		return HTTP_REASON_CREATED;
	case HTTP_ACCEPTED: 
		return HTTP_REASON_ACCEPTED;
	case HTTP_NONAUTHORITATIVE:	
		return HTTP_REASON_NONAUTHORITATIVE;
	case HTTP_NO_CONTENT: 
		return HTTP_REASON_NO_CONTENT;
	case HTTP_RESET_CONTENT: 
		return HTTP_REASON_RESET_CONTENT;
	case HTTP_PARTIAL_CONTENT: 
		return HTTP_REASON_PARTIAL_CONTENT;
	case HTTP_MULTI_STATUS:
		return HTTP_REASON_MULTI_STATUS;
	case HTTP_ALREADY_REPORTED:
		return HTTP_REASON_ALREADY_REPORTED;
	case HTTP_IM_USED:
		return HTTP_REASON_IM_USED;
	case HTTP_MULTIPLE_CHOICES: 
		return HTTP_REASON_MULTIPLE_CHOICES;
	case HTTP_MOVED_PERMANENTLY: 
		return HTTP_REASON_MOVED_PERMANENTLY;
	case HTTP_FOUND: 
		return HTTP_REASON_FOUND;
	case HTTP_SEE_OTHER: 
		return HTTP_REASON_SEE_OTHER;
	case HTTP_NOT_MODIFIED: 
		return HTTP_REASON_NOT_MODIFIED;
	case HTTP_USE_PROXY: 
		return HTTP_REASON_USE_PROXY;
	case HTTP_TEMPORARY_REDIRECT: 
		return HTTP_REASON_TEMPORARY_REDIRECT;
	case HTTP_BAD_REQUEST: 
		return HTTP_REASON_BAD_REQUEST;
	case HTTP_UNAUTHORIZED: 
		return HTTP_REASON_UNAUTHORIZED;
	case HTTP_PAYMENT_REQUIRED: 
		return HTTP_REASON_PAYMENT_REQUIRED;
	case HTTP_FORBIDDEN: 
		return HTTP_REASON_FORBIDDEN;
	case HTTP_NOT_FOUND: 
		return HTTP_REASON_NOT_FOUND;
	case HTTP_METHOD_NOT_ALLOWED:
		return HTTP_REASON_METHOD_NOT_ALLOWED;
	case HTTP_NOT_ACCEPTABLE: 
		return HTTP_REASON_NOT_ACCEPTABLE;
	case HTTP_PROXY_AUTHENTICATION_REQUIRED: 
		return HTTP_REASON_PROXY_AUTHENTICATION_REQUIRED;
	case HTTP_REQUEST_TIMEOUT: 
		return HTTP_REASON_REQUEST_TIMEOUT;
	case HTTP_CONFLICT: 
		return HTTP_REASON_CONFLICT;
	case HTTP_GONE: 
		return HTTP_REASON_GONE;
	case HTTP_LENGTH_REQUIRED: 
		return HTTP_REASON_LENGTH_REQUIRED;
	case HTTP_PRECONDITION_FAILED: 
		return HTTP_REASON_PRECONDITION_FAILED;
	case HTTP_REQUEST_ENTITY_TOO_LARGE: 
		return HTTP_REASON_REQUEST_ENTITY_TOO_LARGE;
	case HTTP_REQUEST_URI_TOO_LONG: 
		return HTTP_REASON_REQUEST_URI_TOO_LONG;
	case HTTP_UNSUPPORTED_MEDIA_TYPE: 
		return HTTP_REASON_UNSUPPORTED_MEDIA_TYPE;
	case HTTP_REQUESTED_RANGE_NOT_SATISFIABLE: 
		return HTTP_REASON_REQUESTED_RANGE_NOT_SATISFIABLE;
	case HTTP_EXPECTATION_FAILED: 
		return HTTP_REASON_EXPECTATION_FAILED;
	case HTTP_IM_A_TEAPOT:
		return HTTP_REASON_IM_A_TEAPOT;            
	case HTTP_ENCHANCE_YOUR_CALM:
		return HTTP_REASON_ENCHANCE_YOUR_CALM;
	case HTTP_MISDIRECTED_REQUEST:
		return HTTP_REASON_MISDIRECTED_REQUEST;
	case HTTP_UNPROCESSABLE_ENTITY:
		return HTTP_REASON_UNPROCESSABLE_ENTITY;
	case HTTP_LOCKED:
		return HTTP_REASON_LOCKED;
	case HTTP_FAILED_DEPENDENCY:
		return HTTP_REASON_FAILED_DEPENDENCY;
	case HTTP_UPGRADE_REQUIRED:
		return HTTP_REASON_UPGRADE_REQUIRED;
	case HTTP_PRECONDITION_REQUIRED:
		return HTTP_REASON_PRECONDITION_REQUIRED;
	case HTTP_TOO_MANY_REQUESTS:
		return HTTP_REASON_TOO_MANY_REQUESTS;
	case HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE:
		return HTTP_REASON_REQUEST_HEADER_FIELDS_TOO_LARGE;
	case HTTP_UNAVAILABLE_FOR_LEGAL_REASONS:
		return HTTP_REASON_UNAVAILABLE_FOR_LEGAL_REASONS;
	case HTTP_INTERNAL_SERVER_ERROR: 
		return HTTP_REASON_INTERNAL_SERVER_ERROR;
	case HTTP_NOT_IMPLEMENTED: 
		return HTTP_REASON_NOT_IMPLEMENTED;
	case HTTP_BAD_GATEWAY: 
		return HTTP_REASON_BAD_GATEWAY;
	case HTTP_SERVICE_UNAVAILABLE:
		return HTTP_REASON_SERVICE_UNAVAILABLE;
	case HTTP_GATEWAY_TIMEOUT: 
		return HTTP_REASON_GATEWAY_TIMEOUT;
	case HTTP_VERSION_NOT_SUPPORTED: 
		return HTTP_REASON_VERSION_NOT_SUPPORTED;
	case HTTP_VARIANT_ALSO_NEGOTIATES:
		return HTTP_REASON_VARIANT_ALSO_NEGOTIATES;
	case HTTP_INSUFFICIENT_STORAGE:
		return HTTP_REASON_INSUFFICIENT_STORAGE;
	case HTTP_LOOP_DETECTED:
		return HTTP_REASON_LOOP_DETECTED;
	case HTTP_NOT_EXTENDED:
		return HTTP_REASON_NOT_EXTENDED;
	case HTTP_NETWORK_AUTHENTICATION_REQUIRED:
		return HTTP_REASON_NETWORK_AUTHENTICATION_REQUIRED;
	default: 
		return HTTP_REASON_UNKNOWN;
	}
}


} } // namespace Poco::Net
