PluginProbe
RabbitLoader / 3.0.3
RabbitLoader v3.0.3
4.0.2 4.0.1 4.0 3.2.0 3.1.1 3.1.0 3.0.5 3.0.3 3.0.4 2.17.1 2.17.2 2.17.3 2.17.4 2.17.5 2.17.6 2.17.7 2.18.0 2.18.1 2.18.2 2.18.3 2.18.4 2.18.5 2.18.6 2.18.7 2.18.8 All 129 releases
rabbit-loader / inc / RabbitLoader / SDK / Request.php

Request.php in RabbitLoader 3.0.3, at inc/RabbitLoader/SDK/Request.php

473 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace RabbitLoader\SDK;
4
5 class Request
6 {
7 private $licenseKey = '';
8 private $requestURL = "";
9 private $requestURI = "";
10 private $cacheFile = null;
11 private $debug = false;
12 private $rootDir = '';
13 private $ignoreRead = false;
14 private $ignoreWrite = false;
15 private $ignoreReason = '';
16 private $isNoOptimization = false;
17 private $isWarmup = false;
18 private $onlyAfter = 0;
19 private $purgeCallback = null;
20 private $meMode = false;
21 private $rlTest = false;
22 private $platform = [];
23
24 const EC429 = ['BQE', 'EC224', 'EC230', 'EC232'];
25
26 const IG_PARAMS = ['_gl', 'epik', 'fbclid', 'gbraid', 'gclid', 'msclkid', 'utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term', 'vgo_ee', 'wbraid', 'zenid', 'rltest', 'rlrand'];
27
28 public function __construct($licenseKey, $rootDir)
29 {
30 $this->licenseKey = $licenseKey;
31 $this->rootDir = $rootDir;
32 $this->parse();
33 $this->ignoreParams(self::IG_PARAMS);
34 $this->cacheFile = new Cache($this->getURL(), $this->rootDir);
35 if (empty($licenseKey)) {
36 $this->ignoreRequest('disconnected');
37 }
38
39 $this->platform = [
40 'plugin_cms' => 'php-sdk',
41 'cms_v' => defined('PHP_VERSION') ? PHP_VERSION : '',
42 'plugin_v' => '1.0.13'
43 ];
44 }
45
46 public function setDebug($debug)
47 {
48 $this->debug = $debug;
49 $this->cacheFile->setDebug($this->debug);
50 }
51
52 public function getURL()
53 {
54 return $this->requestURL;
55 }
56
57 public function ignoreRequest($reason = 'default')
58 {
59 //replace newlines form reason text to avoid issues in appendFooter
60 $reason = str_replace(array("\n", "\r"), '', $reason);
61 $this->ignoreRead = true;
62 $this->ignoreWrite = true;
63 $this->ignoreReason = $reason;
64 Util::sendHeader('x-rl-skip: ' . $this->ignoreReason, true);
65 }
66
67 public function skipForCookies($cookieNames)
68 {
69 if (!empty($cookieNames)) {
70 foreach ($cookieNames as $c) {
71 if (isset($_COOKIE[$c])) {
72 $this->ignoreRequest("cookie-$c");
73 break;
74 }
75 }
76 }
77 }
78
79 public function skipForPaths($patterns)
80 {
81 if (empty($patterns) || empty($this->requestURI)) {
82 return;
83 }
84 #fnmatch(): the maximum allowed length for filename is 4096 characters
85 $uriRaw = substr($this->requestURI, 0, 4096);
86 $uriDecoded = substr(rawurldecode($this->requestURI), 0, 4096);
87
88 // PHP 8 throws ValueError when fnmatch() receives a null byte.
89 if (strpos($uriRaw, "\0") !== false || strpos($uriDecoded, "\0") !== false) {
90 $this->ignoreRequest('malformed-uri');
91 return;
92 }
93
94 foreach ($patterns as $path_pattern) {
95 if (empty($path_pattern)) {
96 continue;
97 }
98
99 $path_pattern = trim($path_pattern);
100 if ($path_pattern === '' || strpos($path_pattern, "\0") !== false) {
101 continue;
102 }
103
104 $patterns_to_match = [$path_pattern];
105 if (!$this->patternContainsWildcard($path_pattern)) {
106 $alternate_pattern = $this->getAlternateTrailingSlashPattern($path_pattern);
107 if ($alternate_pattern !== $path_pattern) {
108 $patterns_to_match[] = $alternate_pattern;
109 }
110 }
111
112 foreach ($patterns_to_match as $candidate_pattern) {
113 if (fnmatch($candidate_pattern, $uriRaw) || fnmatch($candidate_pattern, $uriDecoded)) {
114 $this->ignoreRequest("skip-path-$path_pattern");
115 break 2;
116 }
117 }
118 }
119 }
120
121 private function patternContainsWildcard($pattern)
122 {
123 return strpbrk($pattern, '*?[') !== false;
124 }
125
126 private function getAlternateTrailingSlashPattern($pattern)
127 {
128 if ($pattern === '/') {
129 return $pattern;
130 }
131
132 $query = '';
133 $query_position = strpos($pattern, '?');
134 if ($query_position !== false) {
135 $query = substr($pattern, $query_position);
136 $pattern = substr($pattern, 0, $query_position);
137 }
138
139 if ($pattern === '') {
140 return $query === '' ? $pattern : $pattern . $query;
141 }
142
143 if (substr($pattern, -1) === '/') {
144 $pattern = rtrim($pattern, '/');
145 } else {
146 $pattern .= '/';
147 }
148
149 if ($pattern === '') {
150 $pattern = '/';
151 }
152
153 return $pattern . $query;
154 }
155
156 public function ignoreParams($paramNames)
157 {
158 if (empty($paramNames)) {
159 return;
160 }
161 $parsed_url = parse_url($this->requestURL);
162 $query = empty($parsed_url['query']) ? '' : trim($parsed_url['query']);
163 if (!empty($query)) {
164 try {
165 parse_str($query, $qs_vars);
166
167 if (!empty($paramNames)) {
168 foreach ($paramNames as $p) {
169 unset($qs_vars[trim($p)]);
170 }
171 }
172
173 $query = http_build_query($qs_vars);
174
175 $this->requestURI = trim(@$parsed_url['path']) . (empty($query) ? '' : '?' . $query);;
176 $host = trim(@$parsed_url['host']);
177 $scheme = trim(@$parsed_url['scheme']);
178 $this->requestURL = $scheme . '://' . $host . $this->requestURI;
179 } catch (\Throwable $e) {
180 Exc::catch($e);
181 }
182 }
183 }
184
185 /**
186 * @param array $ignore_params pass array containing the params to be ignored for caching.
187 */
188 private function parse()
189 {
190 $rm = Util::getRequestMethod();
191 if (strcasecmp($rm, 'get') !== 0) {
192 $this->ignoreRequest("request-method-$rm");
193 return;
194 }
195 //process request
196 if (isset($_SERVER['REQUEST_URI'])) {
197 list($urlpart, $qspart) = array_pad(explode('?', $_SERVER['REQUEST_URI']), 2, '');
198 parse_str($qspart, $qsvars);
199
200 $varsLenO = count($qsvars);
201 if (isset($qsvars['rl-no-optimization']) || isset($_SERVER['HTTP_RL_NO_OPTIMIZATION']) || isset($_SERVER['HTTP_RL_CSS'])) {
202 unset($qsvars['rl-no-optimization']);
203 $this->isNoOptimization = true;
204 $this->ignoreRequest("no-optimization");
205 unset($_GET['rl-no-optimization']);
206 }
207
208 if (isset($qsvars['norl'])) {
209 unset($qsvars['norl']);
210 $this->isNoOptimization = true;
211 $this->ignoreRequest("no-optimization");
212 unset($_GET['norl']);
213 }
214
215 if (isset($qsvars['rl-warmup'])) {
216 unset($qsvars['rl-warmup']);
217 $this->ignoreRead = true;
218 $this->isWarmup = true;
219 unset($_GET['rl-warmup']);
220 }
221
222 if (isset($qsvars['rltest'])) {
223 $this->rlTest = true;
224 unset($qsvars['rltest']);
225 unset($_GET['rltest']);
226 }
227
228 if (isset($qsvars['rl-rand'])) {
229 unset($qsvars['rl-rand']);
230 unset($_GET['rl-rand']);
231 }
232
233 if (isset($qsvars['rl-only-after'])) {
234 $this->onlyAfter = ($qsvars['rl-only-after'] / 1000);
235 unset($qsvars['rl-only-after']);
236 unset($_GET['rl-only-after']);
237 }
238
239 $varsLenM = count($qsvars);
240 if ($varsLenO != $varsLenM) {
241 $newqs = http_build_query($qsvars);
242 $_SERVER['REQUEST_URI'] = $urlpart . (empty($newqs) ? '' : '?' . $newqs);
243 }
244 }
245 $request_uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '/';
246
247 $http_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
248 if (empty($http_host)) {
249 $http_host = 'localhost';
250 $this->ignoreRequest("missing-host");
251 }
252 $raw_link = ($this->isHTTPS() ? "https" : "http") . "://$http_host$request_uri";
253
254 $parsed_url = parse_url($raw_link);
255 $query = empty($parsed_url['query']) ? '' : trim($parsed_url['query']);
256
257 if (empty($parsed_url['path'])) {
258 $parsed_url['path'] = '';
259 }
260 if (empty($parsed_url['host'])) {
261 $parsed_url['host'] = 'localhost';
262 }
263 if (empty($parsed_url['scheme'])) {
264 $parsed_url['scheme'] = '';
265 }
266 $this->requestURI = trim($parsed_url['path']) . (empty($query) ? '' : '?' . $query);;
267 $host = trim($parsed_url['host']);
268 $scheme = trim($parsed_url['scheme']);
269 $this->requestURL = $scheme . '://' . $host . $this->requestURI;
270 }
271
272 private function isHTTPS()
273 {
274 return (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443) || (isset($_SERVER['HTTPS']) && strcmp($_SERVER['HTTPS'], "off") !== 0);
275 }
276
277 private function serve()
278 {
279 if ($this->isWarmup) {
280 if ($this->cacheFile->fresh(Cache::TTL_LONG, $this->onlyAfter)) {
281 if (!empty($this->purgeCallback)) {
282 call_user_func_array($this->purgeCallback, [$this->getURL()]);
283 }
284 Util::sendHeader('x-rl-cache: fresh', true);
285 exit;
286 } else {
287 Util::sendHeader('x-rl-cache: stale', true);
288 }
289 } else {
290 if ($this->meMode && !$this->rlTest) {
291 Util::sendHeader('x-rl-skip: me-mode', true);
292 } else if ($this->ignoreRead) {
293 Util::sendHeader('x-rl-skip: ' . $this->ignoreReason, true);
294 } else {
295 if ($this->cacheFile->serve()) {
296 exit;
297 } else {
298 Util::sendHeader('x-rl-cache: miss', true);
299 }
300 }
301 }
302
303 if ($this->isNoOptimization || $this->isWarmup) {
304 Util::sendHeader('Cache-Control: no-store, no-cache, must-revalidate, max-age=0', true);
305 Util::sendHeader('Cache-Control: post-check=0, pre-check=0', false);
306 Util::sendHeader('Pragma: no-cache', true);
307 }
308
309 ob_start([$this, 'addFooter']);
310 }
311
312 /**
313 * Saves buffer and append footer to all requests
314 */
315 public function addFooter($buffer)
316 {
317 $code = http_response_code();
318 if ($code != 200) {
319 $this->ignoreRequest('status-' . $code);
320 }
321
322 $validBuffer = is_string($buffer);
323 if ($this->debug) {
324 $bufferLen = $validBuffer ? strlen($buffer) : 0;
325 $level = ob_get_level();
326 Util::sendHeader("x-rl-buffer: LN:$bufferLen LV:$level", false);
327 }
328
329 if ($validBuffer) {
330 try {
331 $bom = pack('H*', 'EFBBBF');
332 if ($bom !== false) {
333 $buffer = preg_replace("/^($bom)*/", '', $buffer);
334 }
335 $headersList = headers_list();
336 $headers = [];
337 $contentType = NULL;
338 foreach ($headersList as $h) {
339 $p = explode(':', $h, 2);
340 $headers[trim($p[0])] = [trim($p[1])];
341 if (strcasecmp(trim($p[0]), 'content-type') === 0) {
342 $contentType = $p[1];
343 }
344 }
345 $isHtml = ($contentType && stripos($contentType, 'text/html') !== false);
346 $isAmp = preg_match("/<html.*?\s(amp|âš¡)(\s|=|>)/", $buffer);
347
348 if ($isHtml && !$isAmp) {
349 $this->appendFooter($buffer);
350 if ($this->isWarmup && !$this->ignoreWrite) {
351 $this->cacheFile->save(Cache::TTL_SHORT, $buffer, $headers);
352 $this->refresh($this->requestURL, true);
353 }
354 }
355
356 if ($this->debug) {
357 Util::sendHeader("x-rl-page: H:$isHtml A:$isAmp", false);
358 }
359 } catch (\Throwable $e) {
360 if ($this->debug) {
361 $buffer = $e->getMessage();
362 }
363 Exc::catch($e);
364 } catch (\Exception $e) {
365 if ($this->debug) {
366 $buffer = $e->getMessage();
367 }
368 Exc::catch($e);
369 }
370 }
371 return $buffer;
372 }
373
374 private function appendFooter(&$buffer)
375 {
376 $appended = Util::append($buffer, '<script data-rlskip="1" id="rl-sdk-js-0">!function(e,r,a,t){var n="searchParams",l="append",i="getTime",o="Date",d=e.rlPageData||{},f=d.rlCached;r.cookie="rlCached="+(f?"1":"0")+"; path=/;";let c=new e[o];function h(r){if(!r)return;let a=new e[o](r);return a&&a.getFullYear()>1970&&a<c}let u=h(d.exp),p=h(d.rlModified);(!f||u||p)&&!a&&setTimeout(function r(){let a=new e[o](p?d.rlModified:t);if(u){let f=new e[o](d.exp);f>a&&(a=f)}var h=new URL(location.href);h[n][l]("rl-warmup","1"),h[n][l]("rl-rand",c[i]()),h[n][l]("rl-only-after",a[i]()),fetch(h)},1e3)}(this,document,"' . $this->ignoreReason . '","' . date('c') . '");</script>');
377
378 if ($this->debug) {
379 Util::sendHeader("x-rl-footer: $appended", false);
380 }
381 }
382
383 public function process()
384 {
385 try {
386 $this->serve();
387 } catch (\Throwable $e) {
388 Exc::catch($e);
389 } catch (\Exception $e) {
390 Exc::catch($e);
391 }
392 }
393
394 private function refresh($url, $force)
395 {
396 if ($this->cacheFile->get429()) {
397 Util::sendHeader('x-rl-429: 1', true);
398 return;
399 }
400
401 $api = new API($this->licenseKey, $this->platform);
402 $api->setDebug($this->debug);
403
404 //send HB before refresh to unblock previous un-installation if any
405 if ($this->cacheFile->collectGarbage(strtotime('-15 minutes'))) {
406 Util::sendHeader('x-rl-hb-pre: 1', true);
407 $api->heartbeat();
408 Util::sendHeader('x-rl-hb-post: 1', true);
409 }
410
411 $response = $api->refreshV2($this->cacheFile, $url, $force);
412
413 $resJson = json_encode($response);
414 if ($resJson) {
415 //print resJson string if encode success
416 Util::sendHeader('x-rl-debug-refresh1:' . $resJson, true);
417 } else {
418 //print raw response if encode fails
419 Util::sendHeader('x-rl-debug-refresh2:' . $response, true);
420 }
421
422 if (!empty($response['saved'])) {
423 if (!empty($this->purgeCallback)) {
424 call_user_func_array($this->purgeCallback, [$url]);
425 }
426 Util::sendHeader('x-rl-refresh-saved: 1', true);
427 } else {
428 if (!empty($response['message'])) {
429 Util::sendHeader('x-rl-debug-refresh3:' . $resJson, true);
430 if (in_array($response['message'], self::EC429)) {
431 $this->cacheFile->set429();
432 }
433 if ($response['message'] === 'BQE' || $response['message'] === 'EC224') {
434 $this->cacheFile->deleteAll();
435 }
436 } else {
437 Util::sendHeader('x-rl-res-msg-empty: 1', true);
438 }
439 }
440 exit;
441 }
442
443 public function setVariant($variant)
444 {
445 $this->cacheFile->setVariant($variant);
446 }
447
448 public function isWarmUp()
449 {
450 return $this->isWarmup;
451 }
452
453 public function registerPurgeCallback($cb)
454 {
455 $this->purgeCallback = $cb;
456 }
457
458 public function setMeMode()
459 {
460 return $this->meMode = true;
461 }
462
463 public function setPlatform($data)
464 {
465 if (is_array($data)) {
466 foreach ($data as $key => $val) {
467 $this->platform[$key] = $val;
468 }
469 }
470 return;
471 }
472 }
473