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


#include "Poco/Net/HTTPCookie.h"
#include "Poco/Net/NameValueCollection.h"
#include "Poco/Timestamp.h"
#include "Poco/DateTime.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/DateTimeParser.h"
#include "Poco/NumberFormatter.h"
#include "Poco/NumberParser.h"
#include "Poco/String.h"
#include "Poco/URI.h"


using Poco::Timestamp;
using Poco::DateTime;
using Poco::DateTimeFormatter;
using Poco::DateTimeFormat;
using Poco::DateTimeParser;
using Poco::NumberFormatter;
using Poco::NumberParser;
using Poco::icompare;
using namespace std::string_literals;


namespace Poco {
namespace Net {


HTTPCookie::HTTPCookie():
	_version(0),
	_secure(false),
	_maxAge(-1),
	_httpOnly(false),
	_sameSite(SAME_SITE_NOT_SPECIFIED)
{
}


HTTPCookie::HTTPCookie(const std::string& name):
	_version(0),
	_name(name),
	_secure(false),
	_maxAge(-1),
	_httpOnly(false),
	_sameSite(SAME_SITE_NOT_SPECIFIED)
{
}


HTTPCookie::HTTPCookie(const NameValueCollection& nvc):
	_version(0),
	_secure(false),
	_maxAge(-1),
	_httpOnly(false),
	_sameSite(SAME_SITE_NOT_SPECIFIED)
{
	for (const auto& p: nvc)
	{
		const std::string& name  = p.first;
		const std::string& value = p.second;
		if (icompare(name, "Comment"s) == 0)
		{
			_comment = value;
		}
		else if (icompare(name, "Domain"s) == 0)
		{
			setDomain(value);
		}
		else if (icompare(name, "Path"s) == 0)
		{
			setPath(value);
		}
		else if (icompare(name, "Priority"s) == 0)
		{
			setPriority(value);
		}
		else if (icompare(name, "Max-Age"s) == 0)
		{
			setMaxAge(NumberParser::parse(value));
		}
		else if (icompare(name, "Expires"s) == 0)
		{
			int tzd;
			DateTime exp = DateTimeParser::parse(value, tzd);
			Timestamp now;
			setMaxAge((int) ((exp.timestamp() - now) / Timestamp::resolution()));
		}
		else if (icompare(name, "Secure"s) == 0)
		{
			setSecure(true);
		}
		else if (icompare(name, "SameSite"s) == 0)
		{
			if (icompare(value, "None"s) == 0)
				_sameSite = SAME_SITE_NONE;
			else if (icompare(value, "Lax"s) == 0)
				_sameSite = SAME_SITE_LAX;
			else if (icompare(value, "Strict"s) == 0)
				_sameSite = SAME_SITE_STRICT;
		}
		else if (icompare(name, "Version"s) == 0)
		{
			_version = NumberParser::parse(value);
		}
		else if (icompare(name, "HttpOnly"s) == 0)
		{
			setHttpOnly(true);
		}
		else if (_name.empty())
		{
			setName(name);
			setValue(value);
		}
	}
}


HTTPCookie::HTTPCookie(const std::string& name, const std::string& value):
	_version(0),
	_name(name),
	_value(value),
	_secure(false),
	_maxAge(-1),
	_httpOnly(false),
	_sameSite(SAME_SITE_NOT_SPECIFIED)
{
}


HTTPCookie::HTTPCookie(const HTTPCookie& cookie):
	_version(cookie._version),
	_name(cookie._name),
	_value(cookie._value),
	_comment(cookie._comment),
	_domain(cookie._domain),
	_path(cookie._path),
	_priority(cookie._priority),
	_secure(cookie._secure),
	_maxAge(cookie._maxAge),
	_httpOnly(cookie._httpOnly),
	_sameSite(cookie._sameSite)
{
}


HTTPCookie::~HTTPCookie()
{
}


HTTPCookie& HTTPCookie::operator = (const HTTPCookie& cookie)
{
	if (&cookie != this)
	{
		_version  = cookie._version;
		_name     = cookie._name;
		_value    = cookie._value;
		_comment  = cookie._comment;
		_domain   = cookie._domain;
		_path     = cookie._path;
		_priority = cookie._priority;
		_secure   = cookie._secure;
		_maxAge   = cookie._maxAge;
		_httpOnly = cookie._httpOnly;
		_sameSite = cookie._sameSite;
	}
	return *this;
}


void HTTPCookie::setVersion(int version)
{
	_version = version;
}


void HTTPCookie::setName(const std::string& name)
{
	_name = name;
}


void HTTPCookie::setValue(const std::string& value)
{
	_value = value;
}


void HTTPCookie::setComment(const std::string& comment)
{
	_comment = comment;
}


void HTTPCookie::setDomain(const std::string& domain)
{
	_domain = domain;
}


void HTTPCookie::setPath(const std::string& path)
{
	_path = path;
}


void HTTPCookie::setPriority(const std::string& priority)
{
	_priority = priority;
}


void HTTPCookie::setSecure(bool secure)
{
	_secure = secure;
}


void HTTPCookie::setMaxAge(int maxAge)
{
	_maxAge = maxAge;
}


void HTTPCookie::setHttpOnly(bool flag)
{
	_httpOnly = flag;
}


void HTTPCookie::setSameSite(SameSite value)
{
	_sameSite = value;
}


std::string HTTPCookie::toString() const
{
	std::string result;
	result.reserve(256);
	result.append(_name);
	result.append("=");
	if (_version == 0)
	{
		// RFC 6265 (formerly Netscape) cookie
		result.append(_value);
		if (!_domain.empty())
		{
			result.append("; Domain=");
			result.append(_domain);
		}
		if (!_path.empty())
		{
			result.append("; Path=");
			result.append(_path);
		}
		if (!_priority.empty())
		{
			result.append("; Priority=");
			result.append(_priority);
		}
		if (_maxAge != -1)
		{
			Timestamp ts;
			ts += _maxAge * Timestamp::resolution();
			result.append("; Expires=");
			DateTimeFormatter::append(result, ts, DateTimeFormat::HTTP_FORMAT);
		}
		switch (_sameSite)
		{
		case SAME_SITE_NONE:
			result.append("; SameSite=None");
			break;
		case SAME_SITE_LAX:
			result.append("; SameSite=Lax");
			break;
		case SAME_SITE_STRICT:
			result.append("; SameSite=Strict");
			break;
		case SAME_SITE_NOT_SPECIFIED:
			break;
		}
		if (_secure)
		{
			result.append("; Secure");
		}
		if (_httpOnly)
		{
			result.append("; HttpOnly");
		}
	}
	else
	{
		// RFC 2109 cookie
		result.append("\"");
		result.append(_value);
		result.append("\"");
		if (!_comment.empty())
		{
			result.append("; Comment=\"");
			result.append(_comment);
			result.append("\"");
		}
		if (!_domain.empty())
		{
			result.append("; Domain=\"");
			result.append(_domain);
			result.append("\"");
		}
		if (!_path.empty())
		{
			result.append("; Path=\"");
			result.append(_path);
			result.append("\"");
		}
		if (!_priority.empty())
		{
			result.append("; Priority=\"");
			result.append(_priority);
			result.append("\"");
		}

		if (_maxAge != -1)
		{
			result.append("; Max-Age=\"");
			NumberFormatter::append(result, _maxAge);
			result.append("\"");
		}
		switch (_sameSite)
		{
		case SAME_SITE_NONE:
			result.append("; SameSite=None");
			break;
		case SAME_SITE_LAX:
			result.append("; SameSite=Lax");
			break;
		case SAME_SITE_STRICT:
			result.append("; SameSite=Strict");
			break;
		case SAME_SITE_NOT_SPECIFIED:
			break;
		}
		if (_secure)
		{
			result.append("; Secure");
		}
		if (_httpOnly)
		{
			result.append("; HttpOnly");
		}
		result.append("; Version=\"1\"");
	}
	return result;
}


namespace
{
	static const std::string ILLEGAL_CHARS("()[]/|\\',;");
}


std::string HTTPCookie::escape(const std::string& str)
{
	std::string result;
	Poco::URI::encode(str, ILLEGAL_CHARS, result);
	return result;
}


std::string HTTPCookie::unescape(const std::string& str)
{
	std::string result;
	Poco::URI::decode(str, result);
	return result;
}


} } // namespace Poco::Net
