<?php
/**
 * @copyright Copyright (C) 2020, Friendica
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Friendica\Model\User;

use Friendica\App;
use Friendica\Core\Config\IConfig;

/**
 * Interacting with the Friendica Cookie of a user
 */
class Cookie
{
	/** @var int Default expire duration in days */
	const DEFAULT_EXPIRE = 7;
	/** @var string The name of the Friendica cookie */
	const NAME = 'Friendica';
	/** @var string The path of the Friendica cookie */
	const PATH = '/';
	/** @var string The domain name of the Friendica cookie */
	const DOMAIN = '';
	/** @var bool True, if the cookie should only be accessible through HTTP */
	const HTTPONLY = true;

	/** @var string The remote address of this node */
	private $remoteAddr = '0.0.0.0';
	/** @var bool True, if the connection is ssl enabled */
	private $sslEnabled = false;
	/** @var string The private key of this Friendica node */
	private $sitePrivateKey;
	/** @var int The default cookie lifetime */
	private $lifetime = self::DEFAULT_EXPIRE * 24 * 60 * 60;
	/** @var array The $_COOKIE array */
	private $cookie;

	public function __construct(IConfig $config, App\BaseURL $baseURL, array $server = [], array $cookie = [])
	{
		if (!empty($server['REMOTE_ADDR'])) {
			$this->remoteAddr = $server['REMOTE_ADDR'];
		}

		$this->sslEnabled     = $baseURL->getSSLPolicy() === App\BaseURL::SSL_POLICY_FULL;
		$this->sitePrivateKey = $config->get('system', 'site_prvkey');

		$authCookieDays = $config->get('system', 'auth_cookie_lifetime',
			self::DEFAULT_EXPIRE);
		$this->lifetime = $authCookieDays * 24 * 60 * 60;
		$this->cookie   = $cookie;
	}

	/**
	 * Checks if the Friendica cookie is set for a user
	 *
	 * @param string $hash       The cookie hash
	 * @param string $password   The user password
	 * @param string $privateKey The private Key of the user
	 *
	 * @return boolean True, if the cookie is set
	 *
	 */
	public function check(string $hash, string $password, string $privateKey)
	{
		return hash_equals(
			$this->getHash($password, $privateKey),
			$hash
		);
	}

	/**
	 * Set the Friendica cookie for a user
	 *
	 * @param int      $uid        The user id
	 * @param string   $password   The user password
	 * @param string   $privateKey The user private key
	 * @param int|null $seconds    optional the seconds
	 *
	 * @return bool
	 */
	public function set(int $uid, string $password, string $privateKey, int $seconds = null)
	{
		if (!isset($seconds)) {
			$seconds = $this->lifetime + time();
		} elseif (isset($seconds) && $seconds != 0) {
			$seconds = $seconds + time();
		}

		$value = json_encode([
			'uid'  => $uid,
			'hash' => $this->getHash($password, $privateKey),
			'ip'   => $this->remoteAddr,
		]);

		return $this->setCookie(self::NAME, $value, $seconds, $this->sslEnabled);
	}

	/**
	 * Returns the data of the Friendicas user cookie
	 *
	 * @return mixed|null The JSON data, null if not set
	 */
	public function getData()
	{
		// When the "Friendica" cookie is set, take the value to authenticate and renew the cookie.
		if (isset($this->cookie[self::NAME])) {
			$data = json_decode($this->cookie[self::NAME]);
			if (!empty($data)) {
				return $data;
			}
		}

		return null;
	}

	/**
	 * Clears the Friendica cookie of this user after leaving the page
	 */
	public function clear()
	{
		// make sure cookie is deleted on browser close, as a security measure
		return $this->setCookie(self::NAME, '', -3600, $this->sslEnabled);
	}

	/**
	 * Calculate the hash that is needed for the Friendica cookie
	 *
	 * @param string $password   The user password
	 * @param string $privateKey The private key of the user
	 *
	 * @return string Hashed data
	 */
	private function getHash(string $password, string $privateKey)
	{
		return hash_hmac(
			'sha256',
			hash_hmac('sha256', $password, $privateKey),
			$this->sitePrivateKey
		);
	}

	/**
	 * Send a cookie - protected, internal function for test-mocking possibility
	 *
	 * @link  https://php.net/manual/en/function.setcookie.php
	 *
	 * @param string $name
	 * @param string $value  [optional]
	 * @param int    $expire [optional]
	 * @param bool   $secure [optional]
	 *
	 * @return bool If output exists prior to calling this function,
	 *
	 */
	protected function setCookie(string $name, string $value = null, int $expire = null,
	                             bool $secure = null)
	{
		return setcookie($name, $value, $expire, self::PATH, self::DOMAIN, $secure, self::HTTPONLY);
	}
}
