Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
responder.php
1<?
3
6
12{
13 private static $error = null;
14 private static $errorMessage = null;
20 public static function respond()
21 {
22 require_once(__DIR__."/helper.php");
23
24 self::setErrorHandler(); //avoid possible PHP warnings or notices
25 self::registerAutoloader();
26
27 self::modifyHttpHeaders();
28
29 if (self::isValidRequest())
30 {
32 {
33 self::trySendResponse();
34 }
35
36 define("USE_HTML_STATIC_CACHE", true);
37 }
38
39 self::unregisterAutoloader();
40 self::restoreErrorHandler();
41 }
42
43 private static function isValidRequest()
44 {
45 if (
46 isset($_SERVER["HTTP_BX_AJAX"]) ||
47 isset($_GET["bxajaxid"]) ||
48 (isset($_SERVER["HTTP_X_REQUESTED_WITH"]) && $_SERVER["HTTP_X_REQUESTED_WITH"] === "XMLHttpRequest")
49 )
50 {
51 self::$error = Logger::TYPE_AJAX_REQUEST;
52 return false;
53 }
54
55 if (isset($_GET["ncc"]))
56 {
57 self::$error = Logger::TYPE_NCC_PARAMETER;
58 return false;
59 }
60
62 {
63 self::$error = Logger::TYPE_BITRIX_FOLDER;
64 return false;
65 }
66
67 if (preg_match("#^/index_controller\\.php#", $_SERVER["REQUEST_URI"]) > 0)
68 {
69 self::$error = Logger::TYPE_CONTROLLER_FILE;
70 return false;
71 }
72
73 //to warm up localStorage
74 define("ENABLE_HTML_STATIC_CACHE_JS", true);
75
76 if ($_SERVER["REQUEST_METHOD"] !== "GET")
77 {
78 self::$error = Logger::TYPE_GET_METHOD_ONLY;
79 self::$errorMessage = "Request Method: ".$_SERVER["REQUEST_METHOD"];
80 return false;
81 }
82
83 if (isset($_GET["sessid"]))
84 {
85 self::$error = Logger::TYPE_SESSID_PARAMETER;
86 return false;
87 }
88
89 $compositeOptions = Helper::getOptions();
90
91 //NCC cookie exists
92 if (isset($compositeOptions["COOKIE_NCC"]) &&
93 array_key_exists($compositeOptions["COOKIE_NCC"], $_COOKIE) &&
94 $_COOKIE[$compositeOptions["COOKIE_NCC"]] === "Y"
95 )
96 {
97 self::$error = Logger::TYPE_NCC_COOKIE;
98 return false;
99 }
100
101 //A stored authorization exists, but CC cookie doesn't exist
102 if (
103 isset($compositeOptions["STORE_PASSWORD"]) &&
104 $compositeOptions["STORE_PASSWORD"] == "Y" &&
105 isset($_COOKIE[$compositeOptions["COOKIE_LOGIN"]]) &&
106 $_COOKIE[$compositeOptions["COOKIE_LOGIN"]] !== "" &&
107 isset($_COOKIE[$compositeOptions["COOKIE_PASS"]]) &&
108 $_COOKIE[$compositeOptions["COOKIE_PASS"]] !== ""
109 )
110 {
111 if (
112 !isset($compositeOptions["COOKIE_CC"]) ||
113 !array_key_exists($compositeOptions["COOKIE_CC"], $_COOKIE) ||
114 $_COOKIE[$compositeOptions["COOKIE_CC"]] !== "Y"
115 )
116 {
117 self::$error = Logger::TYPE_CC_COOKIE_NOT_FOUND;
118 return false;
119 }
120 }
121
122 $queryPos = mb_strpos($_SERVER["REQUEST_URI"], "?");
123 $requestUri = $queryPos === false? $_SERVER["REQUEST_URI"] : mb_substr($_SERVER["REQUEST_URI"], 0, $queryPos);
124
125 //Checks excluded masks
126 if (isset($compositeOptions["~EXCLUDE_MASK"]) && is_array($compositeOptions["~EXCLUDE_MASK"]))
127 {
128 foreach ($compositeOptions["~EXCLUDE_MASK"] as $mask)
129 {
130 if (preg_match($mask, $requestUri) > 0)
131 {
132 self::$error = Logger::TYPE_EXCLUDE_MASK;
133 self::$errorMessage = "Mask: ".$mask;
134 return false;
135 }
136 }
137 }
138
139 //Checks excluded GET params
140 if (isset($compositeOptions["~EXCLUDE_PARAMS"]) && is_array($compositeOptions["~EXCLUDE_PARAMS"]))
141 {
142 foreach ($compositeOptions["~EXCLUDE_PARAMS"] as $param)
143 {
144 if (array_key_exists($param, $_GET))
145 {
146 self::$error = Logger::TYPE_EXCLUDE_PARAMETER;
147 self::$errorMessage = "Parameter: ".$param;
148 return false;
149 }
150 }
151 }
152
153 //Checks included masks
154 $isRequestInMask = false;
155 if (isset($compositeOptions["~INCLUDE_MASK"]) && is_array($compositeOptions["~INCLUDE_MASK"]))
156 {
157 foreach ($compositeOptions["~INCLUDE_MASK"] as $mask)
158 {
159 if (preg_match($mask, $requestUri) > 0)
160 {
161 $isRequestInMask = true;
162 break;
163 }
164 }
165 }
166
167 if (!$isRequestInMask)
168 {
169 self::$error = Logger::TYPE_INCLUDE_MASK;
170 return false;
171 }
172
173 //Checks hosts
174 $host = Helper::getHttpHost();
175 if (!in_array($host, Helper::getDomains()))
176 {
177 self::$error = Logger::TYPE_INVALID_HOST;
178 self::$errorMessage = "Host: ".$host;
179 return false;
180 }
181
182 if (!self::isValidQueryString($compositeOptions))
183 {
184 self::$error = Logger::TYPE_INVALID_QUERY_STRING;
185 return false;
186 }
187
188 return true;
189 }
190
194 private static function trySendResponse()
195 {
196 $cacheKey = self::getCacheKey();
197 $cache = self::getHtmlCacheResponse($cacheKey);
198 if ($cache === null || !$cache->exists())
199 {
200 return;
201 }
202
203 $compositeOptions = Helper::getOptions();
204
205 $etag = $cache->getEtag();
206 $lastModified = $cache->getLastModified();
207 if ($etag !== false)
208 {
209 if (array_key_exists("HTTP_IF_NONE_MATCH", $_SERVER) && $_SERVER["HTTP_IF_NONE_MATCH"] === $etag)
210 {
211 self::setStatus("304 Not Modified");
212 self::setHeaders($etag, false, "304");
213 die();
214 }
215 }
216
217 if ($lastModified !== false)
218 {
219 $sinceModified =
220 isset($_SERVER["HTTP_IF_MODIFIED_SINCE"]) ?
221 strtotime($_SERVER["HTTP_IF_MODIFIED_SINCE"]) :
222 false;
223
224 if ($sinceModified && $sinceModified >= $lastModified)
225 {
226 self::setStatus("304 Not Modified");
227 self::setHeaders($etag, false, "304");
228 die();
229 }
230 }
231
232 $contents = $cache->getContents();
233 if ($contents !== false)
234 {
235 self::setHeaders($etag, $lastModified, "200", $cache->getContentType());
236
237 //compression support
238 $compress = "";
239 if ($compositeOptions["COMPRESS"] && isset($_SERVER["HTTP_ACCEPT_ENCODING"]))
240 {
241 if (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "x-gzip") !== false)
242 {
243 $compress = "x-gzip";
244 }
245 elseif (strpos($_SERVER["HTTP_ACCEPT_ENCODING"], "gzip") !== false)
246 {
247 $compress = "gzip";
248 }
249 }
250
251 if ($compress)
252 {
253 header("Content-Encoding: ".$compress);
254 echo $cache->isGzipped() ? $contents : gzencode($contents, 4);
255 }
256 else
257 {
258 if ($cache->isGzipped())
259 {
260 $contents = Helper::gzdecode($contents);
261 }
262
263 header("Content-Length: " . strlen($contents));
264 echo $contents;
265 }
266
267 die();
268 }
269 }
270
271 private static function modifyHttpHeaders()
272 {
274
275 if (isset($_SERVER["HTTP_BX_REF"]))
276 {
277 $_SERVER["HTTP_REFERER"] = $_SERVER["HTTP_BX_REF"];
278 }
279 }
280
290 private static function setHeaders($etag, $lastModified, $compositeHeader = false, $contentType = false)
291 {
292 if ($etag !== false)
293 {
294 header("ETag: ".$etag);
295 }
296
297 header("Expires: Fri, 07 Jun 1974 04:00:00 GMT");
298
299 if ($lastModified !== false)
300 {
301 $utc = gmdate("D, d M Y H:i:s", $lastModified)." GMT";
302 header("Last-Modified: ".$utc);
303 }
304
305 if ($contentType !== false)
306 {
307 header("Content-type: ".$contentType);
308 }
309
310 if ($compositeHeader !== false)
311 {
312 header("X-Bitrix-Composite: Cache (".$compositeHeader.")");
313 }
314 }
315
321 private static function setStatus($status)
322 {
323 $bCgi = (mb_stristr(php_sapi_name(), "cgi") !== false);
324 $bFastCgi = ($bCgi && (array_key_exists("FCGI_ROLE", $_SERVER) || array_key_exists("FCGI_ROLE", $_ENV)));
325 if ($bCgi && !$bFastCgi)
326 {
327 header("Status: ".$status);
328 }
329 else
330 {
331 header($_SERVER["SERVER_PROTOCOL"]." ".$status);
332 }
333 }
334
335 private static function isValidQueryString($compositeOptions)
336 {
337 if (!isset($compositeOptions["INDEX_ONLY"]) || !$compositeOptions["INDEX_ONLY"])
338 {
339 return true;
340 }
341
342 $queryString = "";
343 if (isset($_SERVER["REQUEST_URI"]) && ($position = mb_strpos($_SERVER["REQUEST_URI"], "?")) !== false)
344 {
345 $queryString = mb_substr($_SERVER["REQUEST_URI"], $position + 1);
346 $queryString = Helper::removeIgnoredParams($queryString);
347 }
348
349 if ($queryString === "")
350 {
351 return true;
352 }
353
354 $queryParams = array();
355 parse_str($queryString, $queryParams);
356 if (isset($compositeOptions["~GET"]) &&
357 !empty($compositeOptions["~GET"]) &&
358 empty(array_diff(array_keys($queryParams), $compositeOptions["~GET"]))
359 )
360 {
361 return true;
362 }
363
364 return false;
365 }
366
372 private static function getCacheKey()
373 {
374 $userPrivateKey = Helper::getUserPrivateKey();
375
379 Helper::getRealPrivateKey($userPrivateKey)
380 );
381 }
382
390 private static function getHtmlCacheResponse($cacheKey)
391 {
392 $configuration = array();
393 $compositeOptions = Helper::getOptions();
394 $storage = $compositeOptions["STORAGE"] ?? false;
395 if (in_array($storage, array("memcached", "memcached_cluster")))
396 {
397 if (extension_loaded("memcache"))
398 {
399 return new MemcachedResponse($cacheKey, $configuration, $compositeOptions);
400 }
401 else
402 {
403 return null;
404 }
405 }
406 else
407 {
408 return new FileResponse($cacheKey, $configuration, $compositeOptions);
409 }
410 }
411
412 private static function registerAutoloader()
413 {
414 \spl_autoload_register(array(__CLASS__, "autoLoad"), true);
415 }
416
417 private static function unregisterAutoloader()
418 {
419 \spl_autoload_unregister(array(__CLASS__, "autoLoad"));
420 }
421
422 public static function autoLoad($className)
423 {
424 $className = ltrim($className, "\\"); // fix web env
425 if ($className === "Bitrix\\Main\\Composite\\Debug\\Logger")
426 {
427 require_once(__DIR__."/debug/logger.php");
428 }
429 }
430
436 public static function getLastError()
437 {
438 return self::$error;
439 }
440
446 public static function getLastErrorMessage()
447 {
448 return self::$errorMessage;
449 }
450
451 private static function setErrorHandler()
452 {
453 set_error_handler(array(__CLASS__, "handleError"));
454 }
455
456 private static function restoreErrorHandler()
457 {
458 restore_error_handler();
459 }
460
461 public static function handleError($code, $message, $file, $line)
462 {
463 return true;
464 }
465}
466
471abstract class AbstractResponse
472{
473 protected $cacheKey = null;
474 protected $configuration = array();
475 protected $htmlCacheOptions = array();
476
483 {
484 $this->cacheKey = $cacheKey;
485 $this->configuration = $configuration;
486 $this->htmlCacheOptions = $htmlCacheOptions;
487 }
488
493 abstract public function getContents();
494
499 abstract public function isGzipped();
500
505 abstract public function getLastModified();
506
511 abstract public function getEtag();
512
517 abstract public function getContentType();
518
524 abstract public function exists();
525
530 abstract public function shouldCountQuota();
531}
532
534{
538 private $props = null;
539
543 private static $memcached = null;
544 private static $connected = null;
545 private $contents = null;
546 private $flags = 0;
547
548 const MEMCACHED_GZIP_FLAG = 65536;
549
555
556 public function getContents()
557 {
558 if (self::$memcached === null)
559 {
560 return false;
561 }
562
563 if ($this->contents === null)
564 {
565 $this->contents = self::$memcached->get($this->cacheKey, $this->flags);
566 }
567
568 return $this->contents;
569 }
570
571 public function getLastModified()
572 {
573 return $this->getProp("mtime");
574 }
575
576 public function getEtag()
577 {
578 return $this->getProp("etag");
579 }
580
581 public function getContentType()
582 {
583 return $this->getProp("type");
584 }
585
586 public function exists()
587 {
588 return $this->getProps() !== false;
589 }
590
595 public function isGzipped()
596 {
597 $this->getContents();
598
599 return ($this->flags & self::MEMCACHED_GZIP_FLAG) === self::MEMCACHED_GZIP_FLAG;
600 }
601
606 public function shouldCountQuota()
607 {
608 return false;
609 }
610
616 private static function getServers(array $htmlCacheOptions)
617 {
618 $arServers = array();
619 if ($htmlCacheOptions["STORAGE"] === "memcached_cluster")
620 {
621 $groupId = $htmlCacheOptions["MEMCACHED_CLUSTER_GROUP"] ?? 1;
622 $arServers = self::getClusterServers($groupId);
623 }
624 elseif (isset($htmlCacheOptions["MEMCACHED_HOST"]) && isset($htmlCacheOptions["MEMCACHED_PORT"]))
625 {
626 $arServers[] = array(
627 "HOST" => $htmlCacheOptions["MEMCACHED_HOST"],
628 "PORT" => $htmlCacheOptions["MEMCACHED_PORT"]
629 );
630 }
631
632 return $arServers;
633 }
634
642 private static function getClusterServers($groupId)
643 {
644 $arServers = array();
645
646 $arList = false;
647 if (file_exists($_SERVER["DOCUMENT_ROOT"].BX_ROOT."/modules/cluster/memcache.php"))
648 {
649 include($_SERVER["DOCUMENT_ROOT"].BX_ROOT."/modules/cluster/memcache.php");
650 }
651
652 if (defined("BX_MEMCACHE_CLUSTER") && is_array($arList))
653 {
654 foreach ($arList as $arServer)
655 {
656 if ($arServer["STATUS"] === "ONLINE" && $arServer["GROUP_ID"] == $groupId)
657 {
658 $arServers[] = $arServer;
659 }
660 }
661 }
662
663 return $arServers;
664 }
665
674 public static function getConnection(array $configuration, array $htmlCacheOptions)
675 {
676 if (self::$memcached === null && self::$connected === null)
677 {
678 $arServers = self::getServers($htmlCacheOptions);
679 $memcached = new \Memcache;
680 if (count($arServers) === 1)
681 {
682 if ($memcached->connect($arServers[0]["HOST"], $arServers[0]["PORT"]))
683 {
684 self::$connected = true;
685 self::$memcached = $memcached;
686 register_shutdown_function(array(__CLASS__, "close"));
687 }
688 else
689 {
690 self::$connected = false;
691 }
692 }
693 elseif (count($arServers) > 1)
694 {
695 self::$memcached = $memcached;
696 foreach ($arServers as $arServer)
697 {
698 self::$memcached->addServer(
699 $arServer["HOST"],
700 $arServer["PORT"],
701 true, //persistent
702 ($arServer["WEIGHT"] > 0 ? $arServer["WEIGHT"] : 1),
703 1 //timeout
704 );
705 }
706 }
707 else
708 {
709 self::$connected = false;
710 }
711 }
712
713 return self::$memcached;
714 }
715
719 public static function close()
720 {
721 if (self::$memcached !== null)
722 {
723 self::$memcached->close();
724 self::$memcached = null;
725 }
726 }
727
733 public function getProps()
734 {
735 if ($this->props === null)
736 {
737 if (self::$memcached !== null)
738 {
739 $props = self::$memcached->get("~".$this->cacheKey);
740 $this->props = is_object($props) ? $props : false;
741 }
742 else
743 {
744 $this->props = false;
745 }
746 }
747
748 return $this->props;
749 }
750
758 public function getProp($property)
759 {
760 $props = $this->getProps();
761 if ($props !== false && isset($props->{$property}))
762 {
763 return $props->{$property};
764 }
765
766 return false;
767 }
768}
769
771{
772 private $cacheFile = null;
773 private $lastModified = null;
774 private $contents = null;
775
776 public function __construct($cacheKey, array $configuration, array $htmlCacheOptions)
777 {
778 parent::__construct($cacheKey, $configuration, $htmlCacheOptions);
779 $pagesPath = $_SERVER["DOCUMENT_ROOT"].BX_PERSONAL_ROOT."/html_pages";
780
781 if (file_exists($pagesPath.$this->cacheKey))
782 {
783 $this->cacheFile = $pagesPath.$this->cacheKey;
784 }
785 }
786
787 public function getContents()
788 {
789 if ($this->cacheFile === null)
790 {
791 return false;
792 }
793
794 if ($this->contents === null)
795 {
796 $this->contents = file_get_contents($this->cacheFile);
797 if ($this->contents !== false &&
798 (mb_strlen($this->contents) < 2500 || !preg_match("/^[a-f0-9]{32}$/", mb_substr($this->contents, -35, 32)))
799 )
800 {
801 $this->contents = false;
802 }
803 }
804
805 return $this->contents;
806 }
807
808 public function getLastModified()
809 {
810 if ($this->cacheFile === null)
811 {
812 return false;
813 }
814
815 if ($this->lastModified === null)
816 {
817 $this->lastModified = filemtime($this->cacheFile);
818 }
819
820 return $this->lastModified;
821
822 }
823
824 public function getEtag()
825 {
826 if ($this->cacheFile === null)
827 {
828 return false;
829 }
830
831 return md5(
832 $this->cacheFile.filesize($this->cacheFile).$this->getLastModified()
833 );
834 }
835
836 public function getContentType()
837 {
838 $contents = $this->getContents();
839 $head = mb_strpos($contents, "</head>");
840 $meta = "#<meta.*?charset\\s*=\\s*(?:[\"']?)([^\"'>]+)#im";
841
842 if ($head !== false && preg_match($meta, mb_substr($contents, 0, $head), $match))
843 {
844 return "text/html; charset=".$match[1];
845 }
846
847 return false;
848 }
849
850 public function exists()
851 {
852 return $this->cacheFile !== null;
853 }
854
859 public function shouldCountQuota()
860 {
861 return true;
862 }
863
868 public function isGzipped()
869 {
870 return false;
871 }
872}
873
874class_alias("Bitrix\\Main\\Composite\\AbstractResponse", "StaticHtmlCacheResponse");
875class_alias("Bitrix\\Main\\Composite\\MemcachedResponse", "StaticHtmlMemcachedResponse");
876class_alias("Bitrix\\Main\\Composite\\FileResponse", "StaticHtmlFileResponse");
__construct($cacheKey, array $configuration, array $htmlCacheOptions)
__construct($cacheKey, array $configuration, array $htmlCacheOptions)
static removeIgnoredParams($queryString)
Definition helper.php:318
static convertUriToPath($uri, $host=null, $privateKey=null)
Definition helper.php:287
static getHttpHost($host=null)
Definition helper.php:41
static getRealPrivateKey($privateKey=null, $postfix=null)
Definition helper.php:92
static getConnection(array $configuration, array $htmlCacheOptions)
__construct($cacheKey, array $configuration, array $htmlCacheOptions)
static autoLoad($className)
static handleError($code, $message, $file, $line)