#define LOG_MODULE PacketLogModuleHttpLayer

#include "Logger.h"
#include "GeneralUtils.h"
#include "HttpLayer.h"
#include <string.h>
#include <algorithm>
#include <stdlib.h>
#include <exception>
#include <utility>
#include <unordered_map>

namespace pcpp
{


// -------- Class HttpMessage -----------------


HeaderField* HttpMessage::addField(const std::string& fieldName, const std::string& fieldValue)
{
	if (getFieldByName(fieldName) != nullptr)
	{
		PCPP_LOG_ERROR("Field '" << fieldName << "' already exists!");
		return nullptr;
	}

	return TextBasedProtocolMessage::addField(fieldName, fieldValue);
}

HeaderField* HttpMessage::addField(const HeaderField& newField)
{
	if (getFieldByName(newField.getFieldName()) != nullptr)
	{
		PCPP_LOG_ERROR("Field '" << newField.getFieldName() << "' already exists!");
		return nullptr;
	}

	return TextBasedProtocolMessage::addField(newField);
}

HeaderField* HttpMessage::insertField(HeaderField* prevField, const std::string& fieldName, const std::string& fieldValue)
{
	if (getFieldByName(fieldName) != nullptr)
	{
		PCPP_LOG_ERROR("Field '" << fieldName << "' already exists!");
		return nullptr;
	}

	return TextBasedProtocolMessage::insertField(prevField, fieldName, fieldValue);
}

HeaderField* HttpMessage::insertField(HeaderField* prevField, const HeaderField& newField)
{
	if (getFieldByName(newField.getFieldName()) != nullptr)
	{
		PCPP_LOG_ERROR("Field '" << newField.getFieldName() << "' already exists!");
		return nullptr;
	}

	return TextBasedProtocolMessage::insertField(prevField, newField);
}



// -------- Class HttpRequestLayer -----------------

HttpRequestLayer::HttpRequestLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet) : HttpMessage(data, dataLen, prevLayer, packet)
{
	m_Protocol = HTTPRequest;
	m_FirstLine = new HttpRequestFirstLine(this);
	m_FieldsOffset = m_FirstLine->getSize();
	parseFields();
}

HttpRequestLayer::HttpRequestLayer(HttpMethod method, const std::string& uri, HttpVersion version)
{
	m_Protocol = HTTPRequest;
	m_FirstLine = new HttpRequestFirstLine(this, method, version, uri);
	m_FieldsOffset = m_FirstLine->getSize();
}

HttpRequestLayer::HttpRequestLayer(const HttpRequestLayer& other) : HttpMessage(other)
{
	m_FirstLine = new HttpRequestFirstLine(this);
}

HttpRequestLayer& HttpRequestLayer::operator=(const HttpRequestLayer& other)
{
	HttpMessage::operator=(other);

	if (m_FirstLine != nullptr)
		delete m_FirstLine;

	m_FirstLine = new HttpRequestFirstLine(this);

	return *this;
}


std::string HttpRequestLayer::getUrl() const
{
	HeaderField* hostField = getFieldByName(PCPP_HTTP_HOST_FIELD);
	if (hostField == nullptr)
		return m_FirstLine->getUri();

	return hostField->getFieldValue() + m_FirstLine->getUri();
}

HttpRequestLayer::~HttpRequestLayer()
{
	delete m_FirstLine;
}

std::string HttpRequestLayer::toString() const
{
	static const int maxLengthToPrint = 120;
	std::string result = "HTTP request, ";
	int size = m_FirstLine->getSize() - 2; // the -2 is to remove \r\n at the end of the first line
	if (size <= 0)
	{
		result += std::string("CORRUPT DATA");
		return result;
	}
	if (size <= maxLengthToPrint)
	{
		char* firstLine = new char[size+1];
		strncpy(firstLine, (char*)m_Data, size);
		firstLine[size] = 0;
		result += std::string(firstLine);
		delete[] firstLine;
	}
	else
	{
		char firstLine[maxLengthToPrint+1];
		strncpy(firstLine, (char*)m_Data, maxLengthToPrint-3);
		firstLine[maxLengthToPrint-3] = '.';
		firstLine[maxLengthToPrint-2] = '.';
		firstLine[maxLengthToPrint-1] = '.';
		firstLine[maxLengthToPrint] = 0;
		result += std::string(firstLine);
	}

	return result;
}







// -------- Class HttpRequestFirstLine -----------------


const std::string MethodEnumToString[9] = {
		"GET",
		"HEAD",
		"POST",
		"PUT",
		"DELETE",
		"TRACE",
		"OPTIONS",
		"CONNECT",
		"PATCH"
};

const std::unordered_map<std::string, HttpRequestLayer::HttpMethod> HttpMethodStringToEnum {
		{"GET", HttpRequestLayer::HttpMethod::HttpGET },
		{"HEAD", HttpRequestLayer::HttpMethod::HttpHEAD },
		{"POST", HttpRequestLayer::HttpMethod::HttpPOST },
		{"PUT", HttpRequestLayer::HttpMethod::HttpPUT },
		{"DELETE", HttpRequestLayer::HttpMethod::HttpDELETE },
		{"TRACE", HttpRequestLayer::HttpMethod::HttpTRACE },
		{"OPTIONS", HttpRequestLayer::HttpMethod::HttpOPTIONS },
		{"CONNECT", HttpRequestLayer::HttpMethod::HttpCONNECT },
		{"PATCH", HttpRequestLayer::HttpMethod::HttpPATCH }
};

const std::string VersionEnumToString[3] = {
		"0.9",
		"1.0",
		"1.1"
};

const std::unordered_map<std::string, HttpVersion> HttpVersionStringToEnum {
		{ "0.9", HttpVersion::ZeroDotNine },
		{ "1.0", HttpVersion::OneDotZero },
		{ "1.1", HttpVersion::OneDotOne }
};


HttpRequestFirstLine::HttpRequestFirstLine(HttpRequestLayer* httpRequest) : m_HttpRequest(httpRequest)
{
	m_Method = parseMethod((char*)m_HttpRequest->m_Data, m_HttpRequest->getDataLen());
	if (m_Method == HttpRequestLayer::HttpMethodUnknown)
	{
		m_UriOffset = -1;
		PCPP_LOG_DEBUG("Couldn't resolve HTTP request method");
		m_IsComplete = false;
		m_Version = HttpVersionUnknown;
		m_VersionOffset = -1;
		m_FirstLineEndOffset = m_HttpRequest->getDataLen();
		return;
	}
	else
		m_UriOffset = MethodEnumToString[m_Method].length() + 1;

	parseVersion();
	if(m_VersionOffset < 0)
	{
		m_IsComplete = false;
		m_FirstLineEndOffset = m_HttpRequest->getDataLen();
		return;
	}

	char* endOfFirstLine;
	if ((endOfFirstLine = (char*)memchr((char*)(m_HttpRequest->m_Data + m_VersionOffset), '\n', m_HttpRequest->m_DataLen-(size_t)m_VersionOffset)) != nullptr)
	{
		m_FirstLineEndOffset = endOfFirstLine - (char*)m_HttpRequest->m_Data + 1;
		m_IsComplete = true;
	}
	else
	{
		m_FirstLineEndOffset = m_HttpRequest->getDataLen();
		m_IsComplete = false;
	}

	if (Logger::getInstance().isDebugEnabled(PacketLogModuleHttpLayer))
	{
		std::string method = m_Method == HttpRequestLayer::HttpMethodUnknown? "Unknown" : MethodEnumToString[m_Method];
		PCPP_LOG_DEBUG(
			"Method='" << method << "'; "
			<< "HTTP version='" << VersionEnumToString[m_Version] << "'; "
			<< "URI='" << getUri() << "'");
	}
}

HttpRequestFirstLine::HttpRequestFirstLine(HttpRequestLayer* httpRequest, HttpRequestLayer::HttpMethod method, HttpVersion version, const std::string &uri)
{
	try		// throw(HttpRequestFirstLineException)
	{
		if (method == HttpRequestLayer::HttpMethodUnknown)
		{
			m_Exception.setMessage("Method supplied was HttpMethodUnknown");
			throw m_Exception;
		}

		if (version == HttpVersionUnknown)
		{
			m_Exception.setMessage("Version supplied was HttpVersionUnknown");
			throw m_Exception;
		}

		m_HttpRequest = httpRequest;

		m_Method = method;
		m_Version = version;

		std::string firstLine = MethodEnumToString[m_Method] + " " + uri + " "  + "HTTP/" + VersionEnumToString[m_Version] + "\r\n";

		m_UriOffset =  MethodEnumToString[m_Method].length() + 1;
		m_FirstLineEndOffset = firstLine.length();
		m_VersionOffset = m_UriOffset + uri.length() + 6;

		m_HttpRequest->m_DataLen = firstLine.length();
		m_HttpRequest->m_Data = new uint8_t[m_HttpRequest->m_DataLen];
		memcpy(m_HttpRequest->m_Data, firstLine.c_str(), m_HttpRequest->m_DataLen);

		m_IsComplete = true;
	}
	catch(const HttpRequestFirstLineException&)
	{
		throw;
	}
	catch(...)
	{
		std::terminate();
	}
}

HttpRequestLayer::HttpMethod HttpRequestFirstLine::parseMethod(const char* data, size_t dataLen)
{
	if (!data || dataLen < 4)
	{
		return HttpRequestLayer::HttpMethodUnknown;
	}

	size_t spaceIndex = 0;
	while (spaceIndex < dataLen && data[spaceIndex] != ' ' )
	{
		spaceIndex++;
	}

	if (spaceIndex == 0 || spaceIndex == dataLen)
	{
		return HttpRequestLayer::HttpMethodUnknown;
	}

	auto methodAdEnum = HttpMethodStringToEnum.find(std::string(data, data + spaceIndex));
	if (methodAdEnum == HttpMethodStringToEnum.end())
	{
		return HttpRequestLayer::HttpMethodUnknown;
	}
	return methodAdEnum->second;
}

void HttpRequestFirstLine::parseVersion()
{
	char* data = (char*)(m_HttpRequest->m_Data + m_UriOffset);
	char* verPos = cross_platform_memmem(data, m_HttpRequest->getDataLen() - m_UriOffset, " HTTP/", 6);
	if (verPos == nullptr)
	{
		m_Version = HttpVersionUnknown;
		m_VersionOffset = -1;
		return;
	}

	// verify packet doesn't end before the version, meaning still left place for " HTTP/x.y" (9 chars)
	std::ptrdiff_t actualLen = verPos + 9 - (char*)m_HttpRequest->m_Data;
	if (static_cast<size_t>(actualLen) > m_HttpRequest->getDataLen())
	{
		m_Version = HttpVersionUnknown;
		m_VersionOffset = -1;
		return;
	}

	//skip " HTTP/" (6 chars)
	verPos += 6;
	auto versionAsEnum = HttpVersionStringToEnum.find(std::string(verPos, verPos + 3));
	if (versionAsEnum == HttpVersionStringToEnum.end())
	{
		m_Version = HttpVersionUnknown;
	}
	else
	{
		m_Version = versionAsEnum->second;
	}

	m_VersionOffset = verPos - (char*)m_HttpRequest->m_Data;
}

bool HttpRequestFirstLine::setMethod(HttpRequestLayer::HttpMethod newMethod)
{
	if (newMethod == HttpRequestLayer::HttpMethodUnknown)
	{
		PCPP_LOG_ERROR("Requested method is HttpMethodUnknown");
		return false;
	}

	//extend or shorten layer
	int lengthDifference = MethodEnumToString[newMethod].length() - MethodEnumToString[m_Method].length();
	if (lengthDifference > 0)
	{
		if (!m_HttpRequest->extendLayer(0, lengthDifference))
		{
			PCPP_LOG_ERROR("Cannot change layer size");
			return false;
		}
	}
	else if (lengthDifference < 0)
	{
		if (!m_HttpRequest->shortenLayer(0, 0-lengthDifference))
		{
			PCPP_LOG_ERROR("Cannot change layer size");
			return false;

		}
	}

	if (lengthDifference != 0)
		m_HttpRequest->shiftFieldsOffset(m_HttpRequest->getFirstField(), lengthDifference);

	memcpy(m_HttpRequest->m_Data, MethodEnumToString[newMethod].c_str(), MethodEnumToString[newMethod].length());

	m_Method = newMethod;
	m_UriOffset += lengthDifference;
	m_VersionOffset += lengthDifference;

	return true;
}

std::string HttpRequestFirstLine::getUri() const
{
	std::string result;
	if (m_UriOffset != -1 && m_VersionOffset != -1)
		result.assign((const char*)m_HttpRequest->m_Data + m_UriOffset, m_VersionOffset - 6 - m_UriOffset);

	//else first line is illegal, return empty string

	return result;
}

bool HttpRequestFirstLine::setUri(std::string newUri)
{
	// make sure the new URI begins with "/"
	if (newUri.compare(0, 1, "/") != 0)
		newUri = "/" + newUri;

	//extend or shorten layer
	std::string currentUri = getUri();
	int lengthDifference = newUri.length() - currentUri.length();
	if (lengthDifference > 0)
	{
		if (!m_HttpRequest->extendLayer(m_UriOffset, lengthDifference))
		{
			PCPP_LOG_ERROR("Cannot change layer size");
			return false;
		}
	}
	else if (lengthDifference < 0)
	{
		if (!m_HttpRequest->shortenLayer(m_UriOffset, 0-lengthDifference))
		{
			PCPP_LOG_ERROR("Cannot change layer size");
			return false;
		}
	}

	if (lengthDifference != 0)
		m_HttpRequest->shiftFieldsOffset(m_HttpRequest->getFirstField(), lengthDifference);

	memcpy(m_HttpRequest->m_Data + m_UriOffset, newUri.c_str(), newUri.length());

	m_VersionOffset += lengthDifference;

	return true;
}

void HttpRequestFirstLine::setVersion(HttpVersion newVersion)
{
	if (m_VersionOffset == -1)
		return;

	if (newVersion == HttpVersionUnknown)
		return;

	char* verPos = (char*)(m_HttpRequest->m_Data + m_VersionOffset);
	memcpy(verPos, VersionEnumToString[newVersion].c_str(), 3);

	m_Version = newVersion;
}






// -------- Class HttpResponseLayer -----------------

static const std::unordered_map<int, HttpResponseStatusCode> intStatusCodeMap = {
	{100, HttpResponseStatusCode::Http100Continue},
	{101, HttpResponseStatusCode::Http101SwitchingProtocols},
	{102, HttpResponseStatusCode::Http102Processing},
	{103, HttpResponseStatusCode::Http103EarlyHints},
	{200, HttpResponseStatusCode::Http200OK},
	{201, HttpResponseStatusCode::Http201Created},
	{202, HttpResponseStatusCode::Http202Accepted},
	{203, HttpResponseStatusCode::Http203NonAuthoritativeInformation},
	{204, HttpResponseStatusCode::Http204NoContent},
	{205, HttpResponseStatusCode::Http205ResetContent},
	{206, HttpResponseStatusCode::Http206PartialContent},
	{207, HttpResponseStatusCode::Http207MultiStatus},
	{208, HttpResponseStatusCode::Http208AlreadyReported},
	{226, HttpResponseStatusCode::Http226IMUsed},
	{300, HttpResponseStatusCode::Http300MultipleChoices},
	{301, HttpResponseStatusCode::Http301MovedPermanently},
	{302, HttpResponseStatusCode::Http302},
	{303, HttpResponseStatusCode::Http303SeeOther},
	{304, HttpResponseStatusCode::Http304NotModified},
	{305, HttpResponseStatusCode::Http305UseProxy},
	{306, HttpResponseStatusCode::Http306SwitchProxy},
	{307, HttpResponseStatusCode::Http307TemporaryRedirect},
	{308, HttpResponseStatusCode::Http308PermanentRedirect},
	{400, HttpResponseStatusCode::Http400BadRequest},
	{401, HttpResponseStatusCode::Http401Unauthorized},
	{402, HttpResponseStatusCode::Http402PaymentRequired},
	{403, HttpResponseStatusCode::Http403Forbidden},
	{404, HttpResponseStatusCode::Http404NotFound},
	{405, HttpResponseStatusCode::Http405MethodNotAllowed},
	{406, HttpResponseStatusCode::Http406NotAcceptable},
	{407, HttpResponseStatusCode::Http407ProxyAuthenticationRequired},
	{408, HttpResponseStatusCode::Http408RequestTimeout},
	{409, HttpResponseStatusCode::Http409Conflict},
	{410, HttpResponseStatusCode::Http410Gone},
	{411, HttpResponseStatusCode::Http411LengthRequired},
	{412, HttpResponseStatusCode::Http412PreconditionFailed},
	{413, HttpResponseStatusCode::Http413RequestEntityTooLarge},
	{414, HttpResponseStatusCode::Http414RequestURITooLong},
	{415, HttpResponseStatusCode::Http415UnsupportedMediaType},
	{416, HttpResponseStatusCode::Http416RequestedRangeNotSatisfiable},
	{417, HttpResponseStatusCode::Http417ExpectationFailed},
	{418, HttpResponseStatusCode::Http418ImATeapot},
	{419, HttpResponseStatusCode::Http419AuthenticationTimeout},
	{420, HttpResponseStatusCode::Http420},
	{421, HttpResponseStatusCode::Http421MisdirectedRequest},
	{422, HttpResponseStatusCode::Http422UnprocessableEntity},
	{423, HttpResponseStatusCode::Http423Locked},
	{424, HttpResponseStatusCode::Http424FailedDependency},
	{425, HttpResponseStatusCode::Http425TooEarly},
	{426, HttpResponseStatusCode::Http426UpgradeRequired},
	{428, HttpResponseStatusCode::Http428PreconditionRequired},
	{429, HttpResponseStatusCode::Http429TooManyRequests},
	{431, HttpResponseStatusCode::Http431RequestHeaderFieldsTooLarge},
	{440, HttpResponseStatusCode::Http440LoginTimeout},
	{444, HttpResponseStatusCode::Http444NoResponse},
	{449, HttpResponseStatusCode::Http449RetryWith},
	{450, HttpResponseStatusCode::Http450BlockedByWindowsParentalControls},
	{451, HttpResponseStatusCode::Http451},
	{494, HttpResponseStatusCode::Http494RequestHeaderTooLarge},
	{495, HttpResponseStatusCode::Http495CertError},
	{496, HttpResponseStatusCode::Http496NoCert},
	{497, HttpResponseStatusCode::Http497HTTPtoHTTPS},
	{498, HttpResponseStatusCode::Http498TokenExpiredInvalid},
	{499, HttpResponseStatusCode::Http499},
	{500, HttpResponseStatusCode::Http500InternalServerError},
	{501, HttpResponseStatusCode::Http501NotImplemented},
	{502, HttpResponseStatusCode::Http502BadGateway},
	{503, HttpResponseStatusCode::Http503ServiceUnavailable},
	{504, HttpResponseStatusCode::Http504GatewayTimeout},
	{505, HttpResponseStatusCode::Http505HTTPVersionNotSupported},
	{506, HttpResponseStatusCode::Http506VariantAlsoNegotiates},
	{507, HttpResponseStatusCode::Http507InsufficientStorage},
	{508, HttpResponseStatusCode::Http508LoopDetected},
	{509, HttpResponseStatusCode::Http509BandwidthLimitExceeded},
	{510, HttpResponseStatusCode::Http510NotExtended},
	{511, HttpResponseStatusCode::Http511NetworkAuthenticationRequired},
	{520, HttpResponseStatusCode::Http520OriginError},
	{521, HttpResponseStatusCode::Http521WebServerIsDown},
	{522, HttpResponseStatusCode::Http522ConnectionTimedOut},
	{523, HttpResponseStatusCode::Http523ProxyDeclinedRequest},
	{524, HttpResponseStatusCode::Http524aTimeoutOccurred},
	{598, HttpResponseStatusCode::Http598NetworkReadTimeoutError},
	{599, HttpResponseStatusCode::Http599NetworkConnectTimeoutError},
};

HttpResponseStatusCode::HttpResponseStatusCode(const int &statusCodeNumber, const std::string& statusMessage)
{
	if(statusMessage != "")
	{
		m_CustomizedMessage = statusMessage;
	}

	if(intStatusCodeMap.find(statusCodeNumber) != intStatusCodeMap.end())
	{
		m_Value = intStatusCodeMap.at(statusCodeNumber);
		return;
	}

	if(statusCodeNumber >= 100 && statusCodeNumber <= 199)
	{
		m_Value = HttpResponseStatusCode::HttpStatus1xxCodeUnknown;
	}
	else if (statusCodeNumber >= 200 && statusCodeNumber <= 299)
	{
		m_Value = HttpResponseStatusCode::HttpStatus2xxCodeUnknown;
	}
	else if (statusCodeNumber >= 300 && statusCodeNumber <= 399)
	{
		m_Value = HttpResponseStatusCode::HttpStatus3xxCodeUnknown;
	}
	else if (statusCodeNumber >= 400 && statusCodeNumber <= 499)
	{
		m_Value = HttpResponseStatusCode::HttpStatus4xxCodeUnknown;
	}
	else if (statusCodeNumber >= 500 && statusCodeNumber <= 599)
	{
		m_Value = HttpResponseStatusCode::HttpStatus5xxCodeUnknown;
	}
}

/**
 * @struct HttpResponseStatusCodeHash
 * @brief The helper structure for hash HttpResponseStatusCode while using std::unordered_map
 */
struct HttpResponseStatusCodeHash
{
	size_t operator()(const HttpResponseStatusCode& status) const
	{
		return static_cast<int>(status);
	}
};

static const std::unordered_map<HttpResponseStatusCode, std::string, HttpResponseStatusCodeHash> statusCodeExplanationStringMap = {
    {HttpResponseStatusCode::Http100Continue, "Continue"},
    {HttpResponseStatusCode::Http101SwitchingProtocols, "Switching Protocols"},
    {HttpResponseStatusCode::Http102Processing, "Processing"},
    {HttpResponseStatusCode::Http103EarlyHints, "Early Hints"},
    {HttpResponseStatusCode::Http200OK, "OK"},
    {HttpResponseStatusCode::Http201Created, "Created"},
    {HttpResponseStatusCode::Http202Accepted, "Accepted"},
    {HttpResponseStatusCode::Http203NonAuthoritativeInformation, "Non-Authoritative Information"},
    {HttpResponseStatusCode::Http204NoContent, "No Content"},
    {HttpResponseStatusCode::Http205ResetContent, "Reset Content"},
    {HttpResponseStatusCode::Http206PartialContent, "Partial Content"},
    {HttpResponseStatusCode::Http207MultiStatus, "Multi-Status"},
    {HttpResponseStatusCode::Http208AlreadyReported, "Already Reported"},
    {HttpResponseStatusCode::Http226IMUsed, "IM Used"},
    {HttpResponseStatusCode::Http300MultipleChoices, "Multiple Choices"},
    {HttpResponseStatusCode::Http301MovedPermanently, "Moved Permanently"},
    {HttpResponseStatusCode::Http302, "(various messages)"},
    {HttpResponseStatusCode::Http303SeeOther, "See Other"},
    {HttpResponseStatusCode::Http304NotModified, "Not Modified"},
    {HttpResponseStatusCode::Http305UseProxy, "Use Proxy"},
    {HttpResponseStatusCode::Http306SwitchProxy, "Switch Proxy"},
    {HttpResponseStatusCode::Http307TemporaryRedirect, "Temporary Redirect"},
    {HttpResponseStatusCode::Http308PermanentRedirect, "Permanent Redirect"},
    {HttpResponseStatusCode::Http400BadRequest, "Bad Request"},
    {HttpResponseStatusCode::Http401Unauthorized, "Unauthorized"},
    {HttpResponseStatusCode::Http402PaymentRequired, "Payment Required"},
    {HttpResponseStatusCode::Http403Forbidden, "Forbidden"},
    {HttpResponseStatusCode::Http404NotFound, "Not Found"},
    {HttpResponseStatusCode::Http405MethodNotAllowed, "Method Not Allowed"},
    {HttpResponseStatusCode::Http406NotAcceptable, "Not Acceptable"},
    {HttpResponseStatusCode::Http407ProxyAuthenticationRequired, "Proxy Authentication Required"},
    {HttpResponseStatusCode::Http408RequestTimeout, "Request Timeout"},
    {HttpResponseStatusCode::Http409Conflict, "Conflict"},
    {HttpResponseStatusCode::Http410Gone, "Gone"},
    {HttpResponseStatusCode::Http411LengthRequired, "Length Required"},
    {HttpResponseStatusCode::Http412PreconditionFailed, "Precondition Failed"},
    {HttpResponseStatusCode::Http413RequestEntityTooLarge, "Request Entity Too Large"},
    {HttpResponseStatusCode::Http414RequestURITooLong, "Request-URI Too Long"},
    {HttpResponseStatusCode::Http415UnsupportedMediaType, "Unsupported Media Type"},
    {HttpResponseStatusCode::Http416RequestedRangeNotSatisfiable, "Requested Range Not Satisfiable"},
    {HttpResponseStatusCode::Http417ExpectationFailed, "Expectation Failed"},
    {HttpResponseStatusCode::Http418ImATeapot, "I'm a teapot"},
    {HttpResponseStatusCode::Http419AuthenticationTimeout, "Authentication Timeout"},
    {HttpResponseStatusCode::Http420, "(various messages)"},
    {HttpResponseStatusCode::Http421MisdirectedRequest, "Misdirected Request"},
    {HttpResponseStatusCode::Http422UnprocessableEntity, "Unprocessable Entity"},
    {HttpResponseStatusCode::Http423Locked, "Locked"},
    {HttpResponseStatusCode::Http424FailedDependency, "Failed Dependency"},
    {HttpResponseStatusCode::Http425TooEarly, "Too Early"},
    {HttpResponseStatusCode::Http426UpgradeRequired, "Upgrade Required"},
    {HttpResponseStatusCode::Http428PreconditionRequired, "Precondition Required"},
    {HttpResponseStatusCode::Http429TooManyRequests, "Too Many Requests"},
    {HttpResponseStatusCode::Http431RequestHeaderFieldsTooLarge, "Request Header Fields Too Large"},
    {HttpResponseStatusCode::Http440LoginTimeout, "Login Timeout"},
    {HttpResponseStatusCode::Http444NoResponse, "No Response"},
    {HttpResponseStatusCode::Http449RetryWith, "Retry With"},
    {HttpResponseStatusCode::Http450BlockedByWindowsParentalControls, "Blocked by Windows Parental Controls"},
    {HttpResponseStatusCode::Http451, "(various messages)"},
    {HttpResponseStatusCode::Http494RequestHeaderTooLarge, "Request Header Too Large"},
    {HttpResponseStatusCode::Http495CertError, "Cert Error"},
    {HttpResponseStatusCode::Http496NoCert, "No Cert"},
    {HttpResponseStatusCode::Http497HTTPtoHTTPS, "HTTP to HTTPS"},
    {HttpResponseStatusCode::Http498TokenExpiredInvalid, "Token expired/invalid"},
    {HttpResponseStatusCode::Http499, "(various messages)"},
    {HttpResponseStatusCode::Http500InternalServerError, "Internal Server Error"},
    {HttpResponseStatusCode::Http501NotImplemented, "Not Implemented"},
    {HttpResponseStatusCode::Http502BadGateway, "Bad Gateway"},
    {HttpResponseStatusCode::Http503ServiceUnavailable, "Service Unavailable"},
    {HttpResponseStatusCode::Http504GatewayTimeout, "Gateway Timeout"},
    {HttpResponseStatusCode::Http505HTTPVersionNotSupported, "HTTP Version Not Supported"},
    {HttpResponseStatusCode::Http506VariantAlsoNegotiates, "Variant Also Negotiates"},
    {HttpResponseStatusCode::Http507InsufficientStorage, "Insufficient Storage"},
    {HttpResponseStatusCode::Http508LoopDetected, "Loop Detected"},
    {HttpResponseStatusCode::Http509BandwidthLimitExceeded, "Bandwidth Limit Exceeded"},
    {HttpResponseStatusCode::Http510NotExtended, "Not Extended"},
    {HttpResponseStatusCode::Http511NetworkAuthenticationRequired, "Network Authentication Required"},
    {HttpResponseStatusCode::Http520OriginError, "Origin Error"},
    {HttpResponseStatusCode::Http521WebServerIsDown, "Web server is down"},
    {HttpResponseStatusCode::Http522ConnectionTimedOut, "Connection timed out"},
    {HttpResponseStatusCode::Http523ProxyDeclinedRequest, "Proxy Declined Request"},
    {HttpResponseStatusCode::Http524aTimeoutOccurred, "A timeout occurred"},
    {HttpResponseStatusCode::Http598NetworkReadTimeoutError, "Network read timeout error"},
    {HttpResponseStatusCode::Http599NetworkConnectTimeoutError, "Network connect timeout error"},
    {HttpResponseStatusCode::HttpStatus1xxCodeUnknown, "1XX Status Code Unknown"},
    {HttpResponseStatusCode::HttpStatus2xxCodeUnknown, "2XX Status Code Unknown"},
    {HttpResponseStatusCode::HttpStatus3xxCodeUnknown, "3XX Status Code Unknown"},
    {HttpResponseStatusCode::HttpStatus4xxCodeUnknown, "4XX Status Code Unknown"},
    {HttpResponseStatusCode::HttpStatus5xxCodeUnknown, "5XX Status Code Unknown"},
    {HttpResponseStatusCode::HttpStatusCodeUnknown, "Status Code Unknown"},
};

HttpResponseStatusCode::HttpResponseStatusCode(const Value& statusCode, const std::string& statusMessage) : m_Value(statusCode)
{
	if(statusMessage != "")
	{
		m_CustomizedMessage = statusMessage;
	}
}

std::string HttpResponseStatusCode::getMessage() const
{
	if(m_CustomizedMessage != "")
	{
		return m_CustomizedMessage;
	}
	return statusCodeExplanationStringMap.at(m_Value);
}

HttpResponseLayer::HttpResponseLayer(uint8_t* data, size_t dataLen, Layer* prevLayer, Packet* packet)  : HttpMessage(data, dataLen, prevLayer, packet)
{
	m_Protocol = HTTPResponse;
	m_FirstLine = new HttpResponseFirstLine(this);
	m_FieldsOffset = m_FirstLine->getSize();
	parseFields();
}

HttpResponseLayer::HttpResponseLayer(HttpVersion version, const HttpResponseStatusCode& statusCode, const std::string& statusCodeString)
{
	m_Protocol = HTTPResponse;
	m_FirstLine = new HttpResponseFirstLine(this, version, HttpResponseStatusCode(statusCode, statusCodeString));
	m_FieldsOffset = m_FirstLine->getSize();
}

HttpResponseLayer::HttpResponseLayer(HttpVersion version, const HttpResponseStatusCode& statusCode)
{
	m_Protocol = HTTPResponse;
	m_FirstLine = new HttpResponseFirstLine(this, version, statusCode);
	m_FieldsOffset = m_FirstLine->getSize();
}

HttpResponseLayer::~HttpResponseLayer()
{
	delete m_FirstLine;
}


HttpResponseLayer::HttpResponseLayer(const HttpResponseLayer& other) : HttpMessage(other)
{
	m_FirstLine = new HttpResponseFirstLine(this);
}

HttpResponseLayer& HttpResponseLayer::operator=(const HttpResponseLayer& other)
{
	HttpMessage::operator=(other);

	if (m_FirstLine != nullptr)
		delete m_FirstLine;

	m_FirstLine = new HttpResponseFirstLine(this);

	return *this;
}


HeaderField* HttpResponseLayer::setContentLength(int contentLength, const std::string &prevFieldName)
{
	std::ostringstream contentLengthAsString;
	contentLengthAsString << contentLength;
	std::string contentLengthFieldName(PCPP_HTTP_CONTENT_LENGTH_FIELD);
	HeaderField* contentLengthField = getFieldByName(contentLengthFieldName);
	if (contentLengthField == nullptr)
	{
		HeaderField* prevField = getFieldByName(prevFieldName);
		contentLengthField = insertField(prevField, PCPP_HTTP_CONTENT_LENGTH_FIELD, contentLengthAsString.str());
	}
	else
		contentLengthField->setFieldValue(contentLengthAsString.str());

	return contentLengthField;
}

int HttpResponseLayer::getContentLength() const
{
	std::string contentLengthFieldName(PCPP_HTTP_CONTENT_LENGTH_FIELD);
	std::transform(contentLengthFieldName.begin(), contentLengthFieldName.end(), contentLengthFieldName.begin(), ::tolower);
	HeaderField* contentLengthField = getFieldByName(contentLengthFieldName);
	if (contentLengthField != nullptr)
		return atoi(contentLengthField->getFieldValue().c_str());
	return 0;
}

std::string HttpResponseLayer::toString() const
{
	static const int maxLengthToPrint = 120;
	std::string result = "HTTP response, ";
	int size = m_FirstLine->getSize() - 2; // the -2 is to remove \r\n at the end of the first line
	if (size <= maxLengthToPrint)
	{
		char* firstLine = new char[size+1];
		strncpy(firstLine, (char*)m_Data, size);
		firstLine[size] = 0;
		result += std::string(firstLine);
		delete[] firstLine;
	}
	else
	{
		char firstLine[maxLengthToPrint+1];
		strncpy(firstLine, (char*)m_Data, maxLengthToPrint-3);
		firstLine[maxLengthToPrint-3] = '.';
		firstLine[maxLengthToPrint-2] = '.';
		firstLine[maxLengthToPrint-1] = '.';
		firstLine[maxLengthToPrint] = 0;
		result += std::string(firstLine);
	}

	return result;
}


// -------- Class HttpResponseFirstLine -----------------


int HttpResponseFirstLine::getStatusCodeAsInt() const
{
	return m_StatusCode.toInt();
}

std::string HttpResponseFirstLine::getStatusCodeString() const
{
	if (!m_StatusCode.isUnsupportedCode())
	{
		return m_StatusCode.getMessage();
	}

	//else first line is illegal, return empty string
	return "";
}

bool HttpResponseFirstLine::setStatusCode(const HttpResponseStatusCode& newStatusCode, const std::string& statusCodeString)
{
	return setStatusCode(HttpResponseStatusCode(newStatusCode, statusCodeString));
}

bool HttpResponseFirstLine::setStatusCode(const HttpResponseStatusCode& newStatusCode)
{
	if (newStatusCode.isUnsupportedCode())
	{
		PCPP_LOG_ERROR("Requested status code is " << newStatusCode.toString() << ":" << statusCodeExplanationStringMap.at(newStatusCode));
		return false;
	}

	//extend or shorten layer

	size_t statusStringOffset = 13;
	auto newStatusCodeMessage = newStatusCode.getMessage();

	int lengthDifference = newStatusCodeMessage.length() - getStatusCodeString().length();
	if (lengthDifference > 0)
	{
		if (!m_HttpResponse->extendLayer(statusStringOffset, lengthDifference))
		{
			PCPP_LOG_ERROR("Cannot change layer size");
			return false;
		}
	}
	else if (lengthDifference < 0)
	{
		if (!m_HttpResponse->shortenLayer(statusStringOffset, 0-lengthDifference))
		{
			PCPP_LOG_ERROR("Cannot change layer size");
			return false;

		}
	}

	if (lengthDifference != 0)
		m_HttpResponse->shiftFieldsOffset(m_HttpResponse->getFirstField(), lengthDifference);

	// copy status string
	memcpy(m_HttpResponse->m_Data+statusStringOffset, newStatusCodeMessage.c_str(), newStatusCodeMessage.length());

	// change status code
	memcpy(m_HttpResponse->m_Data+9, newStatusCode.toString().c_str(), 3);

	m_StatusCode = newStatusCode;

	m_FirstLineEndOffset += lengthDifference;

	return true;

}

void HttpResponseFirstLine::setVersion(HttpVersion newVersion)
{
	if (newVersion == HttpVersionUnknown)
		return;

	char* verPos = (char*)(m_HttpResponse->m_Data + 5);
	memcpy(verPos, VersionEnumToString[newVersion].c_str(), 3);

	m_Version = newVersion;
}

HttpResponseStatusCode HttpResponseFirstLine::parseStatusCode(const char* data, size_t dataLen)
{
	// minimum data should be 12B long: "HTTP/x.y XXX"
	if (!data || dataLen < 12)
	{
		return HttpResponseStatusCode::HttpStatusCodeUnknown;
	}

	const std::string codeString = std::string(data + 9, 3);

	if(codeString.empty() || (std::find_if(codeString.begin(), codeString.end(), [](unsigned char c){ return !std::isdigit(c); }) != codeString.end()))
	{
		return HttpResponseStatusCode::HttpStatusCodeUnknown;
	}

	constexpr size_t messageOffset = 13;  // expect "HTTP/x.y XXX YYY", YYY starts from 13
	size_t offset = messageOffset;
	bool isMessageFound = false;
	while(offset < dataLen)
	{
		if(data[offset] == '\n')
		{
			isMessageFound = true;
			break;
		}
		offset++;
	}

	if(!isMessageFound)
	{
		return HttpResponseStatusCode::HttpStatusCodeUnknown;
	}

	std::string messageString(data + messageOffset, offset - messageOffset);
	if(!messageString.empty() && messageString.back() == '\r')
	{
		messageString.pop_back();
	}
	if(messageString.empty())
	{
		return HttpResponseStatusCode::HttpStatusCodeUnknown;
	}

	return HttpResponseStatusCode(std::stoi(codeString), messageString);
}

HttpResponseFirstLine::HttpResponseFirstLine(HttpResponseLayer* httpResponse) : m_HttpResponse(httpResponse)
{
	m_Version = parseVersion((char*)m_HttpResponse->m_Data, m_HttpResponse->getDataLen());
	if (m_Version == HttpVersionUnknown)
	{
		m_StatusCode = HttpResponseStatusCode::HttpStatusCodeUnknown;
	}
	else
	{
		m_StatusCode = parseStatusCode((char*)m_HttpResponse->m_Data, m_HttpResponse->getDataLen());
	}


	char* endOfFirstLine;
	if ((endOfFirstLine = (char*)memchr((char*)(m_HttpResponse->m_Data), '\n', m_HttpResponse->m_DataLen)) != nullptr)
	{
		m_FirstLineEndOffset = endOfFirstLine - (char*)m_HttpResponse->m_Data + 1;
		m_IsComplete = true;
	}
	else
	{
		m_FirstLineEndOffset = m_HttpResponse->getDataLen();
		m_IsComplete = false;
	}

	if (Logger::getInstance().isDebugEnabled(PacketLogModuleHttpLayer))
	{
		std::string version = (m_Version == HttpVersionUnknown ? "Unknown" : VersionEnumToString[m_Version]);
		int statusCode = (m_StatusCode == HttpResponseStatusCode::HttpStatusCodeUnknown ? 0 : m_StatusCode.toInt());
		PCPP_LOG_DEBUG("Version='" << version << "'; Status code=" << statusCode << " '" << getStatusCodeString() << "'");
	}
}


HttpResponseFirstLine::HttpResponseFirstLine(HttpResponseLayer* httpResponse,  HttpVersion version, const HttpResponseStatusCode& statusCode)
{
	if (statusCode.isUnsupportedCode())
	{
		m_Exception.setMessage("Status code supplied was " + statusCodeExplanationStringMap.at(statusCode));
		throw m_Exception;
	}

	if (version == HttpVersionUnknown)
	{
		m_Exception.setMessage("Version supplied was HttpVersionUnknown");
		throw m_Exception;
	}

	m_HttpResponse = httpResponse;

	m_StatusCode = statusCode;
	m_Version = version;

	std::string firstLine = "HTTP/" + VersionEnumToString[m_Version] + " " + m_StatusCode.toString() + " " +  m_StatusCode.getMessage() +  "\r\n";

	m_FirstLineEndOffset = firstLine.length();

	m_HttpResponse->m_DataLen = firstLine.length();
	m_HttpResponse->m_Data = new uint8_t[m_HttpResponse->m_DataLen];
	memcpy(m_HttpResponse->m_Data, firstLine.c_str(), m_HttpResponse->m_DataLen);

	m_IsComplete = true;
}

HttpVersion HttpResponseFirstLine::parseVersion(const char* data, size_t dataLen)
{
	if (!data || dataLen < 8) // "HTTP/x.y"
	{
		PCPP_LOG_DEBUG("HTTP response length < 8, cannot identify version");
		return HttpVersionUnknown;
	}

	if (data[0] != 'H' || data[1] != 'T' || data[2] != 'T' || data[3] != 'P' || data[4] != '/')
	{
		PCPP_LOG_DEBUG("HTTP response does not begin with 'HTTP/'");
		return HttpVersionUnknown;
	}

	const char* verPos = data + 5;
	auto versionAsEnum = HttpVersionStringToEnum.find(std::string(verPos, verPos + 3));
	if (versionAsEnum == HttpVersionStringToEnum.end())
	{
		return HttpVersionUnknown;
	}
	return versionAsEnum->second;
}

} // namespace pcpp
