Your IP : 18.119.133.172


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/composite/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/modules/main/lib/composite/responder.php

<?
namespace Bitrix\Main\Composite;

use Bitrix\Main;
use Bitrix\Main\Composite\Debug\Logger;

/**
 * Class Responder
 * @package \Bitrix\Main\Composite
 */
class Responder
{
	private static $error = null;
	private static $errorMessage = null;
	/**
	 * Checks many conditions to enable HTML Cache and tries to send
	 *
	 * @return void
	 */
	public static function respond()
	{
		require_once(__DIR__."/helper.php");

		self::setErrorHandler(); //avoid possible PHP warnings or notices
		self::registerAutoloader();

		self::modifyHttpHeaders();

		if (self::isValidRequest())
		{
			if (!Helper::isCompositeRequest())
			{
				self::trySendResponse();
			}

			define("USE_HTML_STATIC_CACHE", true);
		}

		self::unregisterAutoloader();
		self::restoreErrorHandler();
	}

	private static function isValidRequest()
	{
		if (
			isset($_SERVER["HTTP_BX_AJAX"]) ||
			isset($_GET["bxajaxid"]) ||
			(isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] === "XMLHttpRequest")
		)
		{
			self::$error = Logger::TYPE_AJAX_REQUEST;
			return false;
		}

		if (isset($_GET["ncc"]))
		{
			self::$error = Logger::TYPE_NCC_PARAMETER;
			return false;
		}

		if (Helper::isBitrixFolder())
		{
			self::$error = Logger::TYPE_BITRIX_FOLDER;
			return false;
		}

		if (preg_match("#^/index_controller\\.php#", $_SERVER["REQUEST_URI"]) > 0)
		{
			self::$error = Logger::TYPE_CONTROLLER_FILE;
			return false;
		}

		//to warm up localStorage
		define("ENABLE_HTML_STATIC_CACHE_JS", true);

		if ($_SERVER["REQUEST_METHOD"] !== "GET")
		{
			self::$error = Logger::TYPE_GET_METHOD_ONLY;
			self::$errorMessage = "Request Method: ".$_SERVER["REQUEST_METHOD"];
			return false;
		}

		if (isset($_GET["sessid"]))
		{
			self::$error = Logger::TYPE_SESSID_PARAMETER;
			return false;
		}

		$compositeOptions = Helper::getOptions();

		//NCC cookie exists
		if (isset($compositeOptions["COOKIE_NCC"]) &&
			array_key_exists($compositeOptions["COOKIE_NCC"], $_COOKIE) &&
			$_COOKIE[$compositeOptions["COOKIE_NCC"]] === "Y"
		)
		{
			self::$error = Logger::TYPE_NCC_COOKIE;
			return false;
		}

		//A stored authorization exists, but CC cookie doesn't exist
		if (
			isset($compositeOptions["STORE_PASSWORD"]) &&
			$compositeOptions["STORE_PASSWORD"] == "Y" &&
			isset($_COOKIE[$compositeOptions["COOKIE_LOGIN"]]) &&
			$_COOKIE[$compositeOptions["COOKIE_LOGIN"]] !== "" &&
			isset($_COOKIE[$compositeOptions["COOKIE_PASS"]]) &&
			$_COOKIE[$compositeOptions["COOKIE_PASS"]] !== ""
		)
		{
			if (
				!isset($compositeOptions["COOKIE_CC"]) ||
				!array_key_exists($compositeOptions["COOKIE_CC"], $_COOKIE) ||
				$_COOKIE[$compositeOptions["COOKIE_CC"]] !== "Y"
			)
			{
				self::$error = Logger::TYPE_CC_COOKIE_NOT_FOUND;
				return false;
			}
		}

		$queryPos = mb_strpos($_SERVER["REQUEST_URI"], "?");
		$requestUri = $queryPos === false? $_SERVER["REQUEST_URI"] : mb_substr($_SERVER["REQUEST_URI"], 0, $queryPos);

		//Checks excluded masks
		if (isset($compositeOptions["~EXCLUDE_MASK"]) && is_array($compositeOptions["~EXCLUDE_MASK"]))
		{
			foreach ($compositeOptions["~EXCLUDE_MASK"] as $mask)
			{
				if (preg_match($mask, $requestUri) > 0)
				{
					self::$error = Logger::TYPE_EXCLUDE_MASK;
					self::$errorMessage = "Mask: ".$mask;
					return false;
				}
			}
		}

		//Checks excluded GET params
		if (isset($compositeOptions["~EXCLUDE_PARAMS"]) && is_array($compositeOptions["~EXCLUDE_PARAMS"]))
		{
			foreach ($compositeOptions["~EXCLUDE_PARAMS"] as $param)
			{
				if (array_key_exists($param, $_GET))
				{
					self::$error = Logger::TYPE_EXCLUDE_PARAMETER;
					self::$errorMessage = "Parameter: ".$param;
					return false;
				}
			}
		}

		//Checks included masks
		$isRequestInMask = false;
		if (isset($compositeOptions["~INCLUDE_MASK"]) && is_array($compositeOptions["~INCLUDE_MASK"]))
		{
			foreach ($compositeOptions["~INCLUDE_MASK"] as $mask)
			{
				if (preg_match($mask, $requestUri) > 0)
				{
					$isRequestInMask = true;
					break;
				}
			}
		}

		if (!$isRequestInMask)
		{
			self::$error = Logger::TYPE_INCLUDE_MASK;
			return false;
		}

		//Checks hosts
		$host = Helper::getHttpHost();
		if (!in_array($host, Helper::getDomains()))
		{
			self::$error = Logger::TYPE_INVALID_HOST;
			self::$errorMessage = "Host: ".$host;
			return false;
		}

		if (!self::isValidQueryString($compositeOptions))
		{
			self::$error = Logger::TYPE_INVALID_QUERY_STRING;
			return false;
		}

		return true;
	}

	/**
	 * Tries to send a response if cache exists
	 */
	private static function trySendResponse()
	{
		$cacheKey = self::getCacheKey();
		$cache = self::getHtmlCacheResponse($cacheKey);
		if ($cache === null || !$cache->exists())
		{
			return;
		}

		$compositeOptions = Helper::getOptions();

		$etag = $cache->getEtag();
		$lastModified = $cache->getLastModified();
		if ($etag !== false)
		{
			if (array_key_exists("HTTP_IF_NONE_MATCH", $_SERVER) && $_SERVER["HTTP_IF_NONE_MATCH"] === $etag)
			{
				self::setStatus("304 Not Modified");
				self::setHeaders($etag, false, "304");
				die();
			}
		}

		if ($lastModified !== false)
		{
			$sinceModified =
				isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) ?
				strtotime($_SERVER["HTTP_IF_MODIFIED_SINCE"]) :
				false;

			if ($sinceModified && $sinceModified >= $lastModified)
			{
				self::setStatus("304 Not Modified");
				self::setHeaders($etag, false, "304");
				die();
			}
		}

		$contents = $cache->getContents();
		if ($contents !== false)
		{
			self::setHeaders($etag, $lastModified, "200", $cache->getContentType());

			//compression support
			$compress = "";
			if ($compositeOptions["COMPRESS"] && isset($_SERVER["HTTP_ACCEPT_ENCODING"]))
			{
				if (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "x-gzip") !== false)
				{
					$compress = "x-gzip";
				}
				elseif (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "gzip") !== false)
				{
					$compress = "gzip";
				}
			}

			if ($compress)
			{
				header("Content-Encoding: ".$compress);
				echo $cache->isGzipped() ? $contents : gzencode($contents, 4);
			}
			else
			{
				if ($cache->isGzipped())
				{
					$contents = Helper::gzdecode($contents);
				}

				header("Content-Length: " . strlen($contents));
				echo $contents;
			}

			die();
		}
	}

	private static function modifyHttpHeaders()
	{
		Helper::removeRandParam();

		if (isset($_SERVER["HTTP_BX_REF"]))
		{
			$_SERVER["HTTP_REFERER"] = $_SERVER["HTTP_BX_REF"];
		}
	}

	/**
	 *
	 * Sets HTTP headers
	 *
	 * @param string $etag
	 * @param int $lastModified
	 * @param bool $compositeHeader
	 * @param bool $contentType
	 */
	private static function setHeaders($etag, $lastModified, $compositeHeader = false, $contentType = false)
	{
		if ($etag !== false)
		{
			header("ETag: ".$etag);
		}

		header("Expires: Fri, 07 Jun 1974 04:00:00 GMT");

		if ($lastModified !== false)
		{
			$utc = gmdate("D, d M Y H:i:s", $lastModified)." GMT";
			header("Last-Modified: ".$utc);
		}

		if ($contentType !== false)
		{
			header("Content-type: ".$contentType);
		}

		if ($compositeHeader !== false)
		{
			header("X-Bitrix-Composite: Cache (".$compositeHeader.")");
		}
	}

	/**
	 * Sets HTTP status
	 *
	 * @param string $status
	 */
	private static function setStatus($status)
	{
		$bCgi = (mb_stristr(php_sapi_name(), "cgi") !== false);
		$bFastCgi = ($bCgi && (array_key_exists("FCGI_ROLE", $_SERVER) || array_key_exists("FCGI_ROLE", $_ENV)));
		if ($bCgi && !$bFastCgi)
		{
			header("Status: ".$status);
		}
		else
		{
			header($_SERVER["SERVER_PROTOCOL"]." ".$status);
		}
	}

	private static function isValidQueryString($compositeOptions)
	{
		if (!isset($compositeOptions["INDEX_ONLY"]) || !$compositeOptions["INDEX_ONLY"])
		{
			return true;
		}

		$queryString = "";
		if (isset($_SERVER["REQUEST_URI"]) && ($position = mb_strpos($_SERVER["REQUEST_URI"], "?")) !== false)
		{
			$queryString = mb_substr($_SERVER["REQUEST_URI"], $position + 1);
			$queryString = Helper::removeIgnoredParams($queryString);
		}

		if ($queryString === "")
		{
			return true;
		}

		$queryParams = array();
		parse_str($queryString, $queryParams);
		if (isset($compositeOptions["~GET"]) &&
			!empty($compositeOptions["~GET"]) &&
			empty(array_diff(array_keys($queryParams), $compositeOptions["~GET"]))
		)
		{
			return true;
		}

		return false;
	}

	/**
	 * Gets a cache key with a hostname given by $host
	 *
	 * @return string
	 */
	private static function getCacheKey()
	{
		$userPrivateKey = Helper::getUserPrivateKey();

		return Helper::convertUriToPath(
			Helper::getRequestUri(),
			Helper::getHttpHost(),
			Helper::getRealPrivateKey($userPrivateKey)
		);
	}

	/**
	 * Returns the instance of the AbstractResponse
	 *
	 * @param string $cacheKey unique cache identifier
	 *
	 * @return AbstractResponse|null
	 */
	private static function getHtmlCacheResponse($cacheKey)
	{
		$configuration = array();
		$compositeOptions = Helper::getOptions();
		$storage = $compositeOptions["STORAGE"] ?? false;
		if (in_array($storage, array("memcached", "memcached_cluster")))
		{
			if (extension_loaded("memcache"))
			{
				return new MemcachedResponse($cacheKey, $configuration, $compositeOptions);
			}
			else
			{
				return null;
			}
		}
		else
		{
			return new FileResponse($cacheKey, $configuration, $compositeOptions);
		}
	}

	private static function registerAutoloader()
	{
		\spl_autoload_register(array(__CLASS__, "autoLoad"), true);
	}

	private static function unregisterAutoloader()
	{
		\spl_autoload_unregister(array(__CLASS__, "autoLoad"));
	}

	public static function autoLoad($className)
	{
		$className = ltrim($className, "\\"); // fix web env
		if ($className === "Bitrix\\Main\\Composite\\Debug\\Logger")
		{
			require_once(__DIR__."/debug/logger.php");
		}
	}

	/**
	 * @internal
	 * Returns last respond error
	 * @return string
	 */
	public static function getLastError()
	{
		return self::$error;
	}

	/**
	 * @internal
	 * Returns last error message
	 * @return string
	 */
	public static function getLastErrorMessage()
	{
		return self::$errorMessage;
	}

	private static function setErrorHandler()
	{
		set_error_handler(array(__CLASS__, "handleError"));
	}

	private static function restoreErrorHandler()
	{
		restore_error_handler();
	}

	public static function handleError($code, $message, $file, $line)
	{
		return true;
	}
}

/**
 * Represents interface for the html cache response
 * Class AbstractResponse
 */
abstract class AbstractResponse
{
	protected $cacheKey = null;
	protected $configuration = array();
	protected $htmlCacheOptions = array();

	/**
	 * @param string $cacheKey unique cache identifier
	 * @param array $configuration storage configuration
	 * @param array $htmlCacheOptions html cache options
	 */
	public function __construct($cacheKey, array $configuration, array $htmlCacheOptions)
	{
		$this->cacheKey = $cacheKey;
		$this->configuration = $configuration;
		$this->htmlCacheOptions = $htmlCacheOptions;
	}

	/**
	 * Returns the cache contents
	 * @return string|false
	 */
	abstract public function getContents();

	/**
	 * Returns true if content is gzipped
	 * @return bool
	 */
	abstract public function isGzipped();

	/**
	 * Returns the time the cache was last modified
	 * @return int|false
	 */
	abstract public function getLastModified();

	/**
	 * Returns the Entity Tag of the cache
	 * @return string|int
	 */
	abstract public function getEtag();

	/**
	 * Returns the content type of the cache
	 * @return string|false
	 */
	abstract public function getContentType();

	/**
	 * Checks whether the cache exists
	 *
	 * @return bool
	 */
	abstract public function exists();

	/**
	 * Should we count a quota limit
	 * @return bool
	 */
	abstract public function shouldCountQuota();
}

final class MemcachedResponse extends AbstractResponse
{
	/**
	 * @var \stdClass
	 */
	private $props = null;

	/**
	 * @var \Memcache
	 */
	private static $memcached = null;
	private static $connected = null;
	private $contents = null;
	private $flags = 0;

	const MEMCACHED_GZIP_FLAG = 65536;

	public function __construct($cacheKey, array $configuration, array $htmlCacheOptions)
	{
		parent::__construct($cacheKey, $configuration, $htmlCacheOptions);
		self::getConnection($configuration, $htmlCacheOptions);
	}

	public function getContents()
	{
		if (self::$memcached === null)
		{
			return false;
		}

		if ($this->contents === null)
		{
			$this->contents = self::$memcached->get($this->cacheKey, $this->flags);
		}

		return $this->contents;
	}

	public function getLastModified()
	{
		return $this->getProp("mtime");
	}

	public function getEtag()
	{
		return $this->getProp("etag");
	}

	public function getContentType()
	{
		return $this->getProp("type");
	}

	public function exists()
	{
		return $this->getProps() !== false;
	}

	/**
	 * Returns true if content is gzipped
	 * @return bool
	 */
	public function isGzipped()
	{
		$this->getContents();

		return ($this->flags & self::MEMCACHED_GZIP_FLAG) === self::MEMCACHED_GZIP_FLAG;
	}

	/**
	 * Should we count a quota limit
	 * @return bool
	 */
	public function shouldCountQuota()
	{
		return false;
	}

	/**
	 * @param array $htmlCacheOptions html cache options
	 *
	 * @return array
	 */
	private static function getServers(array $htmlCacheOptions)
	{
		$arServers = array();
		if ($htmlCacheOptions["STORAGE"] === "memcached_cluster")
		{
			$groupId = $htmlCacheOptions["MEMCACHED_CLUSTER_GROUP"] ?? 1;
			$arServers = self::getClusterServers($groupId);
		}
		elseif (isset($htmlCacheOptions["MEMCACHED_HOST"]) && isset($htmlCacheOptions["MEMCACHED_PORT"]))
		{
			$arServers[] = array(
				"HOST" => $htmlCacheOptions["MEMCACHED_HOST"],
				"PORT" => $htmlCacheOptions["MEMCACHED_PORT"]
			);
		}

		return $arServers;
	}

	/**
	 * Gets clusters settings
	 *
	 * @param int $groupId
	 *
	 * @return array
	 */
	private static function getClusterServers($groupId)
	{
		$arServers = array();

		$arList = false;
		if (file_exists($_SERVER["DOCUMENT_ROOT"].BX_ROOT."/modules/cluster/memcache.php"))
		{
			include($_SERVER["DOCUMENT_ROOT"].BX_ROOT."/modules/cluster/memcache.php");
		}

		if (defined("BX_MEMCACHE_CLUSTER") && is_array($arList))
		{
			foreach ($arList as $arServer)
			{
				if ($arServer["STATUS"] === "ONLINE" && $arServer["GROUP_ID"] == $groupId)
				{
					$arServers[] = $arServer;
				}
			}
		}

		return $arServers;
	}

	/**
	 * Returns the object that represents the connection to the memcached server
	 *
	 * @param array $configuration memcached configuration
	 * @param array $htmlCacheOptions html cache options
	 *
	 * @return \Memcache|false
	 */
	public static function getConnection(array $configuration, array $htmlCacheOptions)
	{
		if (self::$memcached === null && self::$connected === null)
		{
			$arServers = self::getServers($htmlCacheOptions);
			$memcached = new \Memcache;
			if (count($arServers) === 1)
			{
				if ($memcached->connect($arServers[0]["HOST"], $arServers[0]["PORT"]))
				{
					self::$connected = true;
					self::$memcached = $memcached;
					register_shutdown_function(array(__CLASS__, "close"));
				}
				else
				{
					self::$connected = false;
				}
			}
			elseif (count($arServers) > 1)
			{
				self::$memcached = $memcached;
				foreach ($arServers as $arServer)
				{
					self::$memcached->addServer(
						$arServer["HOST"],
						$arServer["PORT"],
						true, //persistent
						($arServer["WEIGHT"] > 0 ? $arServer["WEIGHT"] : 1),
						1 //timeout
					);
				}
			}
			else
			{
				self::$connected = false;
			}
		}

		return self::$memcached;
	}

	/**
	 * Closes connection to the memcached server
	 */
	public static function close()
	{
		if (self::$memcached !== null)
		{
			self::$memcached->close();
			self::$memcached = null;
		}
	}

	/**
	 * Returns an array of the cache properties
	 *
	 * @return \stdClass|false
	 */
	public function getProps()
	{
		if ($this->props === null)
		{
			if (self::$memcached !== null)
			{
				$props = self::$memcached->get("~".$this->cacheKey);
				$this->props = is_object($props) ? $props : false;
			}
			else
			{
				$this->props = false;
			}
		}

		return $this->props;
	}

	/**
	 * Returns the $property value
	 *
	 * @param string $property the property name
	 *
	 * @return string|false
	 */
	public function getProp($property)
	{
		$props = $this->getProps();
		if ($props !== false && isset($props->{$property}))
		{
			return $props->{$property};
		}

		return false;
	}
}

final class FileResponse extends AbstractResponse
{
	private $cacheFile = null;
	private $lastModified = null;
	private $contents = null;

	public function __construct($cacheKey, array $configuration, array $htmlCacheOptions)
	{
		parent::__construct($cacheKey, $configuration, $htmlCacheOptions);
		$pagesPath = $_SERVER["DOCUMENT_ROOT"].BX_PERSONAL_ROOT."/html_pages";

		if (file_exists($pagesPath.$this->cacheKey))
		{
			$this->cacheFile = $pagesPath.$this->cacheKey;
		}
	}

	public function getContents()
	{
		if ($this->cacheFile === null)
		{
			return false;
		}

		if ($this->contents === null)
		{
			$this->contents = file_get_contents($this->cacheFile);
			if ($this->contents !== false &&
				(mb_strlen($this->contents) < 2500 || !preg_match("/^[a-f0-9]{32}$/", mb_substr($this->contents, -35, 32)))
			)
			{
				$this->contents = false;
			}
		}

		return $this->contents;
	}

	public function getLastModified()
	{
		if ($this->cacheFile === null)
		{
			return false;
		}

		if ($this->lastModified === null)
		{
			$this->lastModified = filemtime($this->cacheFile);
		}

		return $this->lastModified;

	}

	public function getEtag()
	{
		if ($this->cacheFile === null)
		{
			return false;
		}

		return md5(
			$this->cacheFile.filesize($this->cacheFile).$this->getLastModified()
		);
	}

	public function getContentType()
	{
		$contents = $this->getContents();
		$head = mb_strpos($contents, "</head>");
		$meta = "#<meta.*?charset\\s*=\\s*(?:[\"']?)([^\"'>]+)#im";

		if ($head !== false && preg_match($meta, mb_substr($contents, 0, $head), $match))
		{
			return "text/html; charset=".$match[1];
		}

		return false;
	}

	public function exists()
	{
		return $this->cacheFile !== null;
	}

	/**
	 * Should we count a quota limit
	 * @return bool
	 */
	public function shouldCountQuota()
	{
		return true;
	}

	/**
	 * Returns true if content is gzipped
	 * @return bool
	 */
	public function isGzipped()
	{
		return false;
	}
}

class_alias("Bitrix\\Main\\Composite\\AbstractResponse", "StaticHtmlCacheResponse");
class_alias("Bitrix\\Main\\Composite\\MemcachedResponse", "StaticHtmlMemcachedResponse");
class_alias("Bitrix\\Main\\Composite\\FileResponse", "StaticHtmlFileResponse");