<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title><![CDATA[Неофициальный форум DLNA Home Media Server &mdash; Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
		<link>https://hms.lostcut.net/viewtopic.php?id=907</link>
		<atom:link href="https://hms.lostcut.net/extern.php?action=feed&amp;tid=907&amp;type=rss" rel="self" type="application/rss+xml" />
		<description><![CDATA[Недавние сообщения в теме «Cloudflare WAF (Web Application Firewall) и возможность из php пройти».]]></description>
		<lastBuildDate>Wed, 14 Oct 2026 07:35:16 +0000</lastBuildDate>
		<generator>PunBB</generator>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18533#p18533</link>
			<description><![CDATA[<p>WAF на турбо прошел, на torbo&nbsp; следующий слой защиты остановил.</p><div class="codebox"><pre><code>&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot; style=&quot;height: 100%;&quot;&gt;
    &lt;head&gt;
        &lt;meta charset=&quot;UTF-8&quot; /&gt;
        &lt;meta name=&quot;robots&quot; content=&quot;noindex,nofollow&quot; /&gt;
        &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
        &lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge,chrome=1&quot; /&gt;
    &lt;/head&gt;
    &lt;body style=&quot;margin: 0; height: 100%; overflow: hidden;&quot;&gt;
        &lt;script&gt;
            (function () {
                var s = document.createElement(&quot;script&quot;);
                s.src = &quot;/include/wsdk.js?ck=0C07FDFF7743B7C3&quot;;
                s.async = 1;
                s.defer = 1;
                s[&quot;data-vm&quot;] = JSON.stringify({ 
                    mode: &quot;invisible&quot;,
                    endpoint: &quot;/include/collect&quot;,
                    rt: &quot;1791962813-1.3.3-0C07FDFF7743B7C3-91A2238DD253C540-65e057fd9b8ebe9fdcd17bf9909d58e3aeaf5553c3edadd6fd4d3780e2243cee&quot;
                });

                document.getElementsByTagName(&quot;head&quot;)[0].appendChild(s);
            })();
        &lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</code></pre></div><p> Но это заглушка уже не от WAF. Использовал curl-impersonate (curl-impersonate сам выставил правильный JA3/JA4 и HTTP/2) , работает из php7 На текущий момент wsdk.js скачал, но&nbsp; это обфусцированная антибот-VM . Его надо на Node.js, которым пока пользоваться не умею, запускать и более того Node.js надо научить WAF&nbsp; обходить <img src="https://hms.lostcut.net/extensions/k_smiles/img/ab.gif" alt=":)" /> И в результате там будет капча, которую надо нажать., она создает кук, который далее подставляем в curl и все будет работать, больше там ничего не наворочили.. Если кому-то нужен wsdk.js могу отдать. Про curl-impersonate вроде все описано выше.</p>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Wed, 14 Oct 2026 07:35:16 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18533#p18533</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18532#p18532</link>
			<description><![CDATA[<p>Т.е. турбо у тебя заработал с этим решением?<br />Он вроде как и из браузера ваф вызывает и спрятаться курлом под отпечаток браузера вряд ли спасет.</p>]]></description>
			<author><![CDATA[null@example.com (for_regi)]]></author>
			<pubDate>Tue, 06 Oct 2026 11:07:00 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18532#p18532</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18531#p18531</link>
			<description><![CDATA[<p>Да, WAF обходиться, проще всего если VPS сервер есть, самый дешевый.</p>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Mon, 05 Oct 2026 14:35:24 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18531#p18531</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18530#p18530</link>
			<description><![CDATA[<p>Сначала выложил тут разбор обрута, а теперь героически боремся с последствиями <img src="https://hms.lostcut.net/extensions/k_smiles/img/ab.gif" alt=":)" /><br />Этот форум уже как красная тряпка для разработчиков.</p><p>PS Получилось WAF обойти у них?</p>]]></description>
			<author><![CDATA[null@example.com (for_regi)]]></author>
			<pubDate>Sun, 04 Oct 2026 09:23:48 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18530#p18530</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18529#p18529</link>
			<description><![CDATA[<p>Тестирование на разных серверах показывает, что без средств обхода&nbsp; JA4 у curl и file_get_contetns разный на одном и том же сервер. На разных серверах не совпадает JA4 как у curl с другого сервера, так и у file_get_contents. Соединение через сокет (на одном и том же сервере) дает одинаковый отпечаток с file_get_contens и смысла городить запросы через сокет совсем нет.<br />p.s. сравнил с результатом на <a href="https://tls.peet.ws/">https://tls.peet.ws/</a> - все один в один так же показывает.</p>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Sat, 03 Oct 2026 09:37:34 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18529#p18529</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18528#p18528</link>
			<description><![CDATA[<p>Если используется php7 или php8 то ниже приведенная программа проверяет наличие средств обхода JA3/JA4, HTTP/2 и если установлены делает запросы и возвращает результат<br /></p><div class="codebox"><pre><code>&lt;?php
/**
 * browserleaks_test.php
 * Совместимо с PHP 7.0+ и PHP 8.x
 *
 * Проверяет наличие инструментов подмены TLS/HTTP2-отпечатка:
 *   - curl-impersonate (бинарь)       — работает в PHP 7 и 8
 *   - RNet (нативное расширение)       — только PHP 8.x
 *   - wreq-php (нативное расширение)   — только PHP 8.x
 *   - curl-cffi (FFI + libcurl-impersonate) — только PHP 8.1+
 *
 * Если инструмент найден — делает через него запрос к tls.browserleaks.com.
 * Если нет — сообщает, что не установлено.
 */

if (version_compare(PHP_VERSION, &#039;7.0.0&#039;, &#039;&lt;&#039;)) {
    die(&quot;Требуется PHP &gt;= 7.0\n&quot;);
}

error_reporting(E_ALL &amp; ~E_DEPRECATED &amp; ~E_NOTICE &amp; ~E_WARNING);
ini_set(&#039;display_errors&#039;, &#039;0&#039;);
ini_set(&#039;max_execution_time&#039;, &#039;300&#039;);
ini_set(&#039;default_socket_timeout&#039;, &#039;20&#039;);

$isCli     = (PHP_SAPI === &#039;cli&#039;);
$useColors = $isCli;
$isPhp8    = version_compare(PHP_VERSION, &#039;8.0.0&#039;, &#039;&gt;=&#039;);

if (!$isCli) {
    header(&#039;Content-Type: text/html; charset=utf-8&#039;);
    echo &#039;&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;title&gt;BrowserLeaks Test&lt;/title&gt;&#039;;
    echo &#039;&lt;style&gt;
        body{background:#1e1e1e;color:#d4d4d4;font-family:Consolas,Monaco,monospace;padding:20px;line-height:1.5}
        pre{background:#252526;border:1px solid #3c3c3c;padding:15px;border-radius:5px;overflow-x:auto;white-space:pre-wrap;word-break:break-all}
    &lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;pre&gt;&#039;;
}

/* ==================== ВЫВОД ==================== */
function c(string $text, ?string $color = null, bool $bold = false): string {
    global $useColors, $isCli;
    if ($isCli) {
        if (!$useColors) return $text;
        $map = [&#039;red&#039;=&gt;31,&#039;green&#039;=&gt;32,&#039;yellow&#039;=&gt;33,&#039;blue&#039;=&gt;34,
                &#039;magenta&#039;=&gt;35,&#039;cyan&#039;=&gt;36,&#039;white&#039;=&gt;37,&#039;gray&#039;=&gt;90];
        $code = $map[$color] ?? 0;
        $b = $bold ? &#039;1;&#039; : &#039;&#039;;
        return &quot;\033[{$b}{$code}m{$text}\033[0m&quot;;
    }
    $map = [&#039;red&#039;=&gt;&#039;#f48771&#039;,&#039;green&#039;=&gt;&#039;#6a9955&#039;,&#039;yellow&#039;=&gt;&#039;#dcdcaa&#039;,&#039;blue&#039;=&gt;&#039;#569cd6&#039;,
            &#039;magenta&#039;=&gt;&#039;#c586c0&#039;,&#039;cyan&#039;=&gt;&#039;#4ec9b0&#039;,&#039;white&#039;=&gt;&#039;#d4d4d4&#039;,&#039;gray&#039;=&gt;&#039;#808080&#039;];
    $col = $map[$color] ?? &#039;#d4d4d4&#039;;
    $weight = $bold ? &#039;bold&#039; : &#039;normal&#039;;
    return &#039;&lt;span style=&quot;color:&#039;.$col.&#039;;font-weight:&#039;.$weight.&#039;&quot;&gt;&#039;
         . htmlspecialchars($text, ENT_QUOTES, &#039;UTF-8&#039;) . &#039;&lt;/span&gt;&#039;;
}
function line(string $char = &#039;-&#039;, int $len = 100, ?string $color = &#039;gray&#039;): void {
    echo c(str_repeat($char, $len), $color) . &quot;\n&quot;;
}
function header_(string $title, string $ch = &#039;=&#039;, int $len = 100): void {
    line($ch, $len, &#039;cyan&#039;);
    echo c(&#039;  &#039; . $title, &#039;cyan&#039;, true) . &quot;\n&quot;;
    line($ch, $len, &#039;cyan&#039;);
}
function subheader_(string $title, int $len = 100): void {
    line(&#039;-&#039;, $len, &#039;blue&#039;);
    echo c(&#039;  &#039; . $title, &#039;blue&#039;, true) . &quot;\n&quot;;
    line(&#039;-&#039;, $len, &#039;blue&#039;);
}
function fmt($v): string {
    global $isCli;
    if (is_array($v)) {
        $v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
    $v = (string)$v;
    return $isCli ? $v : htmlspecialchars($v, ENT_QUOTES, &#039;UTF-8&#039;);
}

/* ==================== ПРОВЕРКА ИНСТРУМЕНТОВ ==================== */
/**
 * @return array  [ &#039;name&#039; =&gt; [&#039;available&#039; =&gt; bool, &#039;path&#039; =&gt; ?string, &#039;note&#039; =&gt; ?string] ]
 */
function detectFingerprintTools(): array {
    $tools = [];

    // 1) curl-impersonate — бинарник (работает в PHP 7 и 8)
    $ciPath = null;
    $out = []; $rc = 1;
    @exec(&#039;command -v curl-impersonate 2&gt;/dev/null&#039;, $out, $rc);
    if ($rc === 0 &amp;&amp; !empty($out)) $ciPath = trim($out[0]);
    if ($ciPath === null) {
        $out = []; $rc = 1;
        @exec(&#039;where curl-impersonate 2&gt;NUL&#039;, $out, $rc);
        if ($rc === 0 &amp;&amp; !empty($out)) $ciPath = trim($out[0]);
    }
    $tools[&#039;curl-impersonate (binary)&#039;] = [
        &#039;available&#039; =&gt; $ciPath !== null,
        &#039;path&#039;      =&gt; $ciPath,
        &#039;note&#039;      =&gt; &#039;PHP 7/8 — full TLS+HTTP2 impersonation via CLI&#039;,
    ];

    // 2) RNet — нативное расширение (PHP 8.x)
    $tools[&#039;RNet (extension)&#039;] = [
        &#039;available&#039; =&gt; extension_loaded(&#039;rnet&#039;),
        &#039;path&#039;      =&gt; null,
        &#039;note&#039;      =&gt; &#039;PHP 8.x only — browser TLS/JA3/HTTP2 fingerprints&#039;,
    ];

    // 3) wreq-php — нативное расширение (PHP 8.x)
    $tools[&#039;wreq-php (extension)&#039;] = [
        &#039;available&#039; =&gt; extension_loaded(&#039;wreq&#039;),
        &#039;path&#039;      =&gt; null,
        &#039;note&#039;      =&gt; &#039;PHP 8.x only — 100+ browser profiles&#039;,
    ];

    // 4) curl-cffi — FFI + libcurl-impersonate (PHP 8.1+)
    $ffiOk = extension_loaded(&#039;ffi&#039;);
    $cffiLib = null;
    if ($ffiOk) {
        $cffiLib = getenv(&#039;CURL_CFFI_LIB_PATH&#039;) ?: null;
    }
    $tools[&#039;curl-cffi (FFI)&#039;] = [
        &#039;available&#039; =&gt; $ffiOk,
        &#039;path&#039;      =&gt; $cffiLib,
        &#039;note&#039;      =&gt; &#039;PHP 8.1+ — needs FFI + libcurl-impersonate&#039;,
    ];

    return $tools;
}

/* ==================== HTTP/1.1 ЧЕРЕЗ СОКЕТ ==================== */
function socketFetchHttp1(string $url, array $options = []): array {
    $p = parse_url($url);
    if (!$p || empty($p[&#039;host&#039;])) throw new RuntimeException(&#039;Некорректный URL: &#039; . $url);

    $scheme  = $p[&#039;scheme&#039;] ?? &#039;https&#039;;
    $host    = $p[&#039;host&#039;];
    $port    = isset($p[&#039;port&#039;]) ? (int)$p[&#039;port&#039;] : ($scheme === &#039;https&#039; ? 443 : 80);
    $path    = ($p[&#039;path&#039;] ?? &#039;/&#039;) . (isset($p[&#039;query&#039;]) ? &#039;?&#039; . $p[&#039;query&#039;] : &#039;&#039;);
    $isHttps = ($scheme === &#039;https&#039;);
    $timeout = (int)($options[&#039;timeout&#039;] ?? 20);
    $method  = strtoupper($options[&#039;method&#039;] ?? &#039;GET&#039;);
    $body    = (string)($options[&#039;body&#039;] ?? &#039;&#039;);

    $headers = $options[&#039;headers&#039;] ?? [];
    $headers = array_merge([&#039;Host&#039; =&gt; $host, &#039;Connection&#039; =&gt; &#039;close&#039;], $headers);
    if ($method === &#039;POST&#039;) {
        $headers[&#039;Content-Type&#039;]   = $headers[&#039;Content-Type&#039;]   ?? &#039;application/x-www-form-urlencoded&#039;;
        $headers[&#039;Content-Length&#039;] = strlen($body);
    }

    $req = &quot;$method $path HTTP/1.1\r\n&quot;;
    foreach ($headers as $k =&gt; $v) $req .= &quot;$k: $v\r\n&quot;;
    $req .= &quot;\r\n&quot;;
    if ($method === &#039;POST&#039; &amp;&amp; $body !== &#039;&#039;) $req .= $body;

    $errno = 0; $errstr = &#039;&#039;;
    $ctx = stream_context_create([&#039;ssl&#039; =&gt; [
        &#039;verify_peer&#039;      =&gt; true,
        &#039;verify_peer_name&#039; =&gt; true,
        &#039;SNI_enabled&#039;      =&gt; true,
        &#039;peer_name&#039;        =&gt; $host,
        &#039;alpn_protocols&#039;   =&gt; &#039;http/1.1&#039;,
    ]]);

    $remote = ($isHttps ? &#039;ssl://&#039; : &#039;tcp://&#039;) . $host . &#039;:&#039; . $port;
    $fp = @stream_socket_client($remote, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $ctx);
    if (!$fp) throw new RuntimeException(&quot;Не удалось подключиться к $remote: [$errno] $errstr&quot;);
    stream_set_timeout($fp, $timeout);
    stream_set_blocking($fp, true);

    if (@fwrite($fp, $req) === false) {
        fclose($fp);
        throw new RuntimeException(&#039;fwrite failed&#039;);
    }

    $rawHead = &#039;&#039;;
    while (!feof($fp)) {
        $l = fgets($fp, 8192);
        if ($l === false) break;
        $rawHead .= $l;
        if ($l === &quot;\r\n&quot; || $l === &quot;\n&quot;) break;
    }
    if ($rawHead === &#039;&#039;) {
        $meta = stream_get_meta_data($fp); fclose($fp);
        throw new RuntimeException(!empty($meta[&#039;timed_out&#039;])
            ? &#039;таймаут при чтении заголовков&#039;
            : &#039;соединение закрыто до получения ответа&#039;);
    }
    $hl = preg_split(&quot;/\r\n|\n/&quot;, trim($rawHead));
    $statusLine = array_shift($hl);
    if (!preg_match(&#039;#^HTTP/\d\.\d\s+(\d+)#&#039;, $statusLine, $m)) {
        fclose($fp); throw new RuntimeException(&#039;Некорректная статус-строка: &#039; . $statusLine);
    }
    $status = (int)$m[1];

    $parsed = []; $setCookies = [];
    foreach ($hl as $l) {
        if ($l === &#039;&#039;) continue;
        $pos = strpos($l, &#039;:&#039;);
        if ($pos === false) continue;
        $n = strtolower(trim(substr($l, 0, $pos)));
        $v = trim(substr($l, $pos + 1));
        if ($n === &#039;set-cookie&#039;) { $setCookies[] = $v; continue; }
        $parsed[$n] = isset($parsed[$n]) ? $parsed[$n] . &#039;, &#039; . $v : $v;
    }

    $rawBody = &#039;&#039;;
    $isChunked = isset($parsed[&#039;transfer-encoding&#039;])
        &amp;&amp; stripos($parsed[&#039;transfer-encoding&#039;], &#039;chunked&#039;) !== false;

    if ($isChunked) {
        while (!feof($fp)) {
            $chunk = fread($fp, 8192);
            if ($chunk === false || $chunk === &#039;&#039;) {
                $info = stream_get_meta_data($fp);
                if (!empty($info[&#039;timed_out&#039;]) || !empty($info[&#039;eof&#039;])) break;
                continue;
            }
            $rawBody .= $chunk;
            if (strpos($rawBody, &quot;\r\n0\r\n\r\n&quot;) !== false
             || strpos($rawBody, &quot;\r\n0\r\n&quot;)    !== false) break;
        }
    } elseif (isset($parsed[&#039;content-length&#039;])) {
        $need = (int)$parsed[&#039;content-length&#039;];
        while (strlen($rawBody) &lt; $need &amp;&amp; !feof($fp)) {
            $chunk = fread($fp, min(8192, $need - strlen($rawBody)));
            if ($chunk === false || $chunk === &#039;&#039;) {
                $info = stream_get_meta_data($fp);
                if (!empty($info[&#039;timed_out&#039;])) break;
                continue;
            }
            $rawBody .= $chunk;
        }
    } else {
        while (!feof($fp)) {
            $chunk = fread($fp, 8192);
            if ($chunk === false || $chunk === &#039;&#039;) {
                $info = stream_get_meta_data($fp);
                if (!empty($info[&#039;timed_out&#039;]) || !empty($info[&#039;eof&#039;])) break;
                continue;
            }
            $rawBody .= $chunk;
        }
    }
    fclose($fp);

    if ($isChunked) $rawBody = decodeChunked($rawBody);
    if (isset($parsed[&#039;content-encoding&#039;])) {
        $rawBody = decompressBody($rawBody, $parsed[&#039;content-encoding&#039;]);
    }

    return [
        &#039;status&#039;      =&gt; $status,
        &#039;headers&#039;     =&gt; $parsed,
        &#039;set_cookies&#039; =&gt; $setCookies,
        &#039;body&#039;        =&gt; $rawBody,
        &#039;raw&#039;         =&gt; $rawHead . &quot;\r\n&quot; . $rawBody,
    ];
}

/* ==================== ВСПОМОГАТЕЛЬНЫЕ ==================== */
function decodeChunked(string $data): string {
    $out = &#039;&#039;; $pos = 0; $len = strlen($data);
    while ($pos &lt; $len) {
        $e = strpos($data, &quot;\r\n&quot;, $pos);
        if ($e === false) break;
        $hex = trim(substr($data, $pos, $e - $pos));
        $s = strpos($hex, &#039;;&#039;);
        if ($s !== false) $hex = substr($hex, 0, $s);
        if ($hex === &#039;&#039; || !ctype_xdigit($hex)) break;
        $size = hexdec($hex);
        $pos = $e + 2;
        if ($size === 0) break;
        if ($pos + $size &gt; $len) { $out .= substr($data, $pos); break; }
        $out .= substr($data, $pos, $size);
        $pos += $size + 2;
    }
    return $out;
}
function decompressBody(string $body, string $encodingHeader): string {
    if ($encodingHeader === &#039;&#039;) return $body;
    $list = array_map(&#039;trim&#039;, explode(&#039;,&#039;, strtolower($encodingHeader)));
    foreach (array_reverse($list) as $e) {
        if ($e === &#039;gzip&#039; || $e === &#039;x-gzip&#039;) {
            $d = @gzdecode($body);
            if ($d !== false) $body = $d;
        } elseif ($e === &#039;deflate&#039;) {
            $d = @gzinflate($body);
            if ($d === false) $d = @gzuncompress($body);
            if ($d !== false) $body = $d;
        } elseif ($e === &#039;br&#039;) {
            if (function_exists(&#039;brotli_uncompress&#039;)) {
                $d = @brotli_uncompress($body);
                if ($d !== false) $body = $d;
            }
        }
    }
    return $body;
}

/* ==================== ЗАПРОСЫ ==================== */
$url = &#039;https://tls.browserleaks.com/json&#039;;
$results = [];

header_(&#039;ТЕСТИРОВАНИЕ ЗАПРОСОВ К &#039; . $url);
echo &quot;\n&quot;;
echo c(&#039;  PHP версия: &#039;, &#039;gray&#039;) . PHP_VERSION
   . &#039; (&#039; . (PHP_INT_SIZE === 8 ? &#039;64-bit&#039; : &#039;32-bit&#039;) . &quot;)\n&quot;;
echo c(&#039;  cURL: &#039;, &#039;gray&#039;)     . (function_exists(&#039;curl_init&#039;) ? c(&#039;доступен&#039;,&#039;green&#039;) : c(&#039;НЕДОСТУПЕН&#039;,&#039;red&#039;)) . &quot;\n&quot;;
echo c(&#039;  OpenSSL: &#039;, &#039;gray&#039;)  . (extension_loaded(&#039;openssl&#039;)  ? c(&#039;доступен&#039;,&#039;green&#039;) : c(&#039;НЕДОСТУПЕН&#039;,&#039;red&#039;)) . &quot;\n&quot;;
echo c(&#039;  FFI: &#039;, &#039;gray&#039;)      . (extension_loaded(&#039;ffi&#039;)      ? c(&#039;доступен&#039;,&#039;green&#039;) : c(&#039;нет&#039;,&#039;yellow&#039;)) . &quot;\n&quot;;
echo &quot;\n&quot;;

/* --- 1. file_get_contents --- */
echo c(&#039;  [1/7] &#039;, &#039;yellow&#039;, true) . &quot;file_get_contents...\n&quot;;
try {
    $ctx = stream_context_create([
        &#039;http&#039; =&gt; [
            &#039;method&#039;        =&gt; &#039;GET&#039;,
            &#039;header&#039;        =&gt; &quot;User-Agent: PHP-file_get_contents-test\r\nAccept: application/json\r\n&quot;,
            &#039;timeout&#039;       =&gt; 20,
            &#039;ignore_errors&#039; =&gt; true,
        ],
        &#039;ssl&#039; =&gt; [&#039;verify_peer&#039; =&gt; true, &#039;verify_peer_name&#039; =&gt; true],
    ]);
    $fgc = @file_get_contents($url, false, $ctx);
    if ($fgc !== false &amp;&amp; $fgc !== &#039;&#039;) {
        $decoded = json_decode($fgc, true);
        $results[&#039;file_get_contents&#039;] = [
            &#039;success&#039; =&gt; $decoded !== null,
            &#039;data&#039;    =&gt; $decoded,
            &#039;raw&#039;     =&gt; $fgc,
            &#039;error&#039;   =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
        ];
    } else {
        $err = error_get_last();
        $results[&#039;file_get_contents&#039;] = [
            &#039;success&#039; =&gt; false,
            &#039;error&#039;   =&gt; $err[&#039;message&#039;] ?? &#039;Пустой ответ&#039;,
        ];
    }
} catch (Throwable $e) {
    $results[&#039;file_get_contents&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
}

/* --- 2. cURL --- */
echo c(&#039;  [2/7] &#039;, &#039;yellow&#039;, true) . &quot;cURL...\n&quot;;
if (function_exists(&#039;curl_init&#039;)) {
    try {
        $ch = curl_init();
        curl_setopt_array($ch, [
            CURLOPT_URL            =&gt; $url,
            CURLOPT_RETURNTRANSFER =&gt; true,
            CURLOPT_TIMEOUT        =&gt; 20,
            CURLOPT_CONNECTTIMEOUT =&gt; 10,
            CURLOPT_FOLLOWLOCATION =&gt; true,
            CURLOPT_HTTPHEADER     =&gt; [
                &#039;User-Agent: PHP-cURL-test&#039;,
                &#039;Accept: application/json&#039;,
            ],
        ]);
        $curlResp = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr  = curl_error($ch);
        curl_close($ch);

        if ($curlResp !== false &amp;&amp; $httpCode == 200) {
            $decoded = json_decode($curlResp, true);
            $results[&#039;curl&#039;] = [
                &#039;success&#039;   =&gt; $decoded !== null,
                &#039;data&#039;      =&gt; $decoded,
                &#039;raw&#039;       =&gt; $curlResp,
                &#039;http_code&#039; =&gt; $httpCode,
                &#039;error&#039;     =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
            ];
        } else {
            $results[&#039;curl&#039;] = [
                &#039;success&#039;   =&gt; false,
                &#039;error&#039;     =&gt; $curlErr ?: (&#039;HTTP &#039; . $httpCode),
                &#039;http_code&#039; =&gt; $httpCode,
            ];
        }
    } catch (Throwable $e) {
        $results[&#039;curl&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
    }
} else {
    $results[&#039;curl&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; &#039;Расширение cURL не установлено&#039;];
}

/* --- 3. Socket HTTP/1.1 --- */
echo c(&#039;  [3/7] &#039;, &#039;yellow&#039;, true) . &quot;Socket HTTP/1.1...\n&quot;;
try {
    $r = socketFetchHttp1($url, [
        &#039;timeout&#039; =&gt; 20,
        &#039;headers&#039; =&gt; [
            &#039;User-Agent&#039;      =&gt; &#039;PHP-socket-test&#039;,
            &#039;Accept&#039;          =&gt; &#039;application/json&#039;,
            &#039;Accept-Encoding&#039; =&gt; &#039;gzip, deflate&#039;,
        ],
    ]);
    $decoded = json_decode($r[&#039;body&#039;], true);
    $results[&#039;socket&#039;] = [
        &#039;success&#039; =&gt; $decoded !== null,
        &#039;data&#039;    =&gt; $decoded,
        &#039;raw&#039;     =&gt; $r[&#039;raw&#039;],
        &#039;status&#039;  =&gt; $r[&#039;status&#039;],
        &#039;error&#039;   =&gt; $decoded === null
            ? &#039;HTTP &#039; . $r[&#039;status&#039;] . &#039;: &#039; . substr($r[&#039;body&#039;], 0, 300) : null,
    ];
} catch (Throwable $e) {
    $results[&#039;socket&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
}

/* ==================== ПРОВЕРКА ИНСТРУМЕНТОВ ПОДМЕНЫ ==================== */
echo &quot;\n&quot;;
header_(&#039;ИНСТРУМЕНТЫ ПОДМЕНЫ TLS/HTTP2-ОТПЕЧАТКА&#039;);
echo &quot;\n&quot;;

$tools = detectFingerprintTools();
foreach ($tools as $name =&gt; $t) {
    if ($t[&#039;available&#039;]) {
        echo &#039;  &#039; . c(&#039;✓&#039;, &#039;green&#039;, true) . &#039;  &#039; . c($name, &#039;white&#039;, true)
           . c(&#039;  — ДОСТУПЕН&#039;, &#039;green&#039;)
           . ($t[&#039;path&#039;] ? c(&#039; (&#039; . $t[&#039;path&#039;] . &#039;)&#039;, &#039;gray&#039;) : &#039;&#039;)
           . &quot;\n&quot;;
        echo &#039;       &#039; . c($t[&#039;note&#039;], &#039;gray&#039;) . &quot;\n&quot;;
    } else {
        echo &#039;  &#039; . c(&#039;✗&#039;, &#039;red&#039;, true) . &#039;  &#039; . c($name, &#039;white&#039;)
           . c(&#039;  — НЕ УСТАНОВЛЕН&#039;, &#039;yellow&#039;) . &quot;\n&quot;;
        echo &#039;       &#039; . c($t[&#039;note&#039;], &#039;gray&#039;) . &quot;\n&quot;;
    }
}

/* ==================== ЗАПРОСЫ ЧЕРЕЗ ИНСТРУМЕНТЫ ПОДМЕНЫ ==================== */

/* --- 4. curl-impersonate (бинарь) --- */
echo &quot;\n&quot;;
if ($tools[&#039;curl-impersonate (binary)&#039;][&#039;available&#039;]) {
    echo c(&#039;  [4/7] &#039;, &#039;yellow&#039;, true) . &quot;curl-impersonate (Chrome 131)...\n&quot;;
    $ciPath = $tools[&#039;curl-impersonate (binary)&#039;][&#039;path&#039;];
    try {
        $cmd = escapeshellarg($ciPath)
             . &#039; -s --impersonate chrome131 &#039;
             . escapeshellarg($url);
        $json = @shell_exec($cmd);
        if ($json !== null &amp;&amp; $json !== &#039;&#039;) {
            $decoded = json_decode($json, true);
            $results[&#039;curl_impersonate&#039;] = [
                &#039;success&#039; =&gt; $decoded !== null,
                &#039;data&#039;    =&gt; $decoded,
                &#039;raw&#039;     =&gt; $json,
                &#039;error&#039;   =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
            ];
        } else {
            $results[&#039;curl_impersonate&#039;] = [
                &#039;success&#039; =&gt; false,
                &#039;error&#039;   =&gt; &#039;curl-impersonate вернул пустой ответ&#039;,
            ];
        }
    } catch (Throwable $e) {
        $results[&#039;curl_impersonate&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
    }
} else {
    echo c(&#039;  [4/7] &#039;, &#039;yellow&#039;, true) . &quot;curl-impersonate...\n&quot;;
    echo &#039;  &#039; . c(&#039;НЕ УСТАНОВЛЕН&#039;, &#039;yellow&#039;, true)
       . c(&#039; (бинарник curl-impersonate не найден в PATH)&#039;, &#039;gray&#039;) . &quot;\n&quot;;
    $results[&#039;curl_impersonate&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; &#039;curl-impersonate не установлен&#039;];
}

/* --- 5. RNet (расширение PHP 8.x) --- */
echo &quot;\n&quot;;
if ($tools[&#039;RNet (extension)&#039;][&#039;available&#039;]) {
    echo c(&#039;  [5/7] &#039;, &#039;yellow&#039;, true) . &quot;RNet (Chrome 136)...\n&quot;;
    try {
        // RNet работает через классы: RNet\ClientBuilder, RNet\Emulation
        if (class_exists(&#039;RNet\ClientBuilder&#039;)) {
            $b = new \RNet\ClientBuilder();
            $b-&gt;impersonate(\RNet\Emulation::CHROME_136);
            $client = $b-&gt;build();
            $resp = $client-&gt;get($url);
            $body = (string)$resp-&gt;getBody();
            $decoded = json_decode($body, true);
            $results[&#039;rnet&#039;] = [
                &#039;success&#039; =&gt; $decoded !== null,
                &#039;data&#039;    =&gt; $decoded,
                &#039;raw&#039;     =&gt; $body,
                &#039;error&#039;   =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
            ];
        } else {
            $results[&#039;rnet&#039;] = [
                &#039;success&#039; =&gt; false,
                &#039;error&#039;   =&gt; &#039;Расширение rnet загружено, но класс RNet\ClientBuilder не найден&#039;,
            ];
        }
    } catch (Throwable $e) {
        $results[&#039;rnet&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
    }
} else {
    echo c(&#039;  [5/7] &#039;, &#039;yellow&#039;, true) . &quot;RNet...\n&quot;;
    echo &#039;  &#039; . c(&#039;НЕ УСТАНОВЛЕН&#039;, &#039;yellow&#039;, true)
       . c(&#039; (расширение rnet не загружено; требуется PHP 8.x)&#039;, &#039;gray&#039;) . &quot;\n&quot;;
    $results[&#039;rnet&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; &#039;RNet не установлен&#039;];
}

/* --- 6. wreq-php (расширение PHP 8.x) --- */
echo &quot;\n&quot;;
if ($tools[&#039;wreq-php (extension)&#039;][&#039;available&#039;]) {
    echo c(&#039;  [6/7] &#039;, &#039;yellow&#039;, true) . &quot;wreq-php...\n&quot;;
    try {
        if (class_exists(&#039;Wreq\Client&#039;)) {
            $client = new \Wreq\Client([&#039;emulation&#039; =&gt; &#039;chrome_131&#039;]);
            $resp = $client-&gt;get($url);
            $body = (string)$resp-&gt;getBody();
            $decoded = json_decode($body, true);
            $results[&#039;wreq&#039;] = [
                &#039;success&#039; =&gt; $decoded !== null,
                &#039;data&#039;    =&gt; $decoded,
                &#039;raw&#039;     =&gt; $body,
                &#039;error&#039;   =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
            ];
        } else {
            $results[&#039;wreq&#039;] = [
                &#039;success&#039; =&gt; false,
                &#039;error&#039;   =&gt; &#039;Расширение wreq загружено, но класс Wreq\Client не найден&#039;,
            ];
        }
    } catch (Throwable $e) {
        $results[&#039;wreq&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
    }
} else {
    echo c(&#039;  [6/7] &#039;, &#039;yellow&#039;, true) . &quot;wreq-php...\n&quot;;
    echo &#039;  &#039; . c(&#039;НЕ УСТАНОВЛЕН&#039;, &#039;yellow&#039;, true)
       . c(&#039; (расширение wreq не загружено; требуется PHP 8.x)&#039;, &#039;gray&#039;) . &quot;\n&quot;;
    $results[&#039;wreq&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; &#039;wreq-php не установлен&#039;];
}

/* --- 7. curl-cffi (FFI, PHP 8.1+) --- */
echo &quot;\n&quot;;
if ($tools[&#039;curl-cffi (FFI)&#039;][&#039;available&#039;]) {
    echo c(&#039;  [7/7] &#039;, &#039;yellow&#039;, true) . &quot;curl-cffi (FFI)...\n&quot;;
    if (class_exists(&#039;CurlCffi\Requests&#039;)) {
        try {
            $resp = \CurlCffi\Requests::get($url, [&#039;impersonate&#039; =&gt; &#039;chrome110&#039;]);
            $body = $resp-&gt;getText();
            $decoded = json_decode($body, true);
            $results[&#039;curl_cffi&#039;] = [
                &#039;success&#039; =&gt; $decoded !== null,
                &#039;data&#039;    =&gt; $decoded,
                &#039;raw&#039;     =&gt; $body,
                &#039;error&#039;   =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
            ];
        } catch (Throwable $e) {
            $results[&#039;curl_cffi&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage()];
        }
    } else {
        $results[&#039;curl_cffi&#039;] = [
            &#039;success&#039; =&gt; false,
            &#039;error&#039;   =&gt; &#039;FFI доступен, но библиотека curl-cffi не установлена &#039;
                       . &#039;(composer require farhaanaliii/curl-cffi)&#039;,
        ];
    }
} else {
    echo c(&#039;  [7/7] &#039;, &#039;yellow&#039;, true) . &quot;curl-cffi (FFI)...\n&quot;;
    echo &#039;  &#039; . c(&#039;НЕ УСТАНОВЛЕН&#039;, &#039;yellow&#039;, true)
       . c(&#039; (FFI отключён или PHP &lt; 8.1)&#039;, &#039;gray&#039;) . &quot;\n&quot;;
    $results[&#039;curl_cffi&#039;] = [&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; &#039;curl-cffi не установлен&#039;];
}

/* ==================== СТАТУС ==================== */
echo &quot;\n&quot;;
header_(&#039;СТАТУС ВЫПОЛНЕНИЯ&#039;);
echo &quot;\n&quot;;

$successful = [];
$labels = [
    &#039;file_get_contents&#039; =&gt; &#039;file_get_contents&#039;,
    &#039;curl&#039;              =&gt; &#039;cURL&#039;,
    &#039;socket&#039;            =&gt; &#039;Socket HTTP/1.1&#039;,
    &#039;curl_impersonate&#039;  =&gt; &#039;curl-impersonate&#039;,
    &#039;rnet&#039;              =&gt; &#039;RNet (PHP 8)&#039;,
    &#039;wreq&#039;              =&gt; &#039;wreq-php (PHP 8)&#039;,
    &#039;curl_cffi&#039;         =&gt; &#039;curl-cffi (PHP 8.1+)&#039;,
];

foreach ($results as $m =&gt; $r) {
    $lbl = $labels[$m] ?? $m;
    if (!empty($r[&#039;success&#039;])) {
        $successful[$m] = $r[&#039;data&#039;];
        printf(&quot;  %s  %-24s %s\n&quot;,
            c(&#039;OK&#039;, &#039;green&#039;, true), c($lbl, &#039;white&#039;), c(&#039;УСПЕШНО&#039;, &#039;green&#039;, true));
    } else {
        $err = $r[&#039;error&#039;] ?? &#039;unknown&#039;;
        if (strpos($err, &#039;не установлен&#039;) !== false || strpos($err, &#039;НЕ УСТАНОВЛЕН&#039;) !== false
            || strpos($err, &#039;не загружено&#039;) !== false) {
            printf(&quot;  %s  %-24s %s\n&quot;,
                c(&#039;--&#039;, &#039;yellow&#039;, true), c($lbl, &#039;white&#039;), c(&#039;НЕ УСТАНОВЛЕН&#039;, &#039;yellow&#039;));
        } else {
            printf(&quot;  %s  %-24s %s %s\n&quot;,
                c(&#039;FAIL&#039;, &#039;red&#039;, true), c($lbl, &#039;white&#039;),
                c(&#039;ОШИБКА&#039;, &#039;red&#039;, true),
                c(&#039;(&#039; . $err . &#039;)&#039;, &#039;gray&#039;));
        }
    }
}

if (empty($successful)) {
    echo &quot;\n&quot; . c(&#039;  Ни один запрос не удался.&#039;, &#039;red&#039;, true) . &quot;\n&quot;;
    if (!$isCli) echo &#039;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&#039;;
    exit(1);
}

/* ==================== СРАВНЕНИЕ ОТПЕЧАТКОВ ==================== */
echo &quot;\n&quot;;
header_(&#039;СРАВНЕНИЕ ОТПЕЧАТКОВ&#039;);
echo &quot;\n&quot;;

// Сравниваем только те, что вернули данные
$compareFields = [
    &#039;user_agent&#039;  =&gt; &#039;User-Agent&#039;,
    &#039;ja4&#039;         =&gt; &#039;JA4&#039;,
    &#039;ja4_o&#039;       =&gt; &#039;JA4_o (original)&#039;,
    &#039;ja3_hash&#039;    =&gt; &#039;JA3 Hash&#039;,
    &#039;akamai_hash&#039; =&gt; &#039;Akamai Hash&#039;,
];

foreach ($compareFields as $field =&gt; $label) {
    $values = [];
    foreach ($successful as $m =&gt; $data) {
        $v = $data[$field] ?? ($data[&#039;tls&#039;][$field] ?? null);
        if ($v !== null) $values[$m] = $v;
    }
    if (empty($values)) continue;

    $unique = array_unique($values);
    $isSame = count($unique) === 1;

    echo c(&#039;  &#039; . $label, &#039;cyan&#039;, true)
       . c($isSame ? &#039;  [одинаково у всех]&#039; : &#039;  [различается]&#039;, $isSame ? &#039;green&#039; : &#039;yellow&#039;) . &quot;\n&quot;;
    foreach ($values as $m =&gt; $v) {
        echo &#039;    &#039; . c(str_pad($labels[$m] ?? $m, 24), &#039;gray&#039;) . &#039; = &#039; . fmt($v) . &quot;\n&quot;;
    }
    echo &quot;\n&quot;;
}

/* ==================== СОХРАНЕНИЕ ==================== */
echo &quot;\n&quot;;
header_(&#039;СОХРАНЕНИЕ&#039;);
echo &quot;\n&quot;;
$rawFile = __DIR__ . &#039;/browserleaks_raw_responses.json&#039;;
$saved = @file_put_contents(
    $rawFile,
    json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
if ($saved !== false) {
    echo &#039;  Файл сохранён: &#039; . c($rawFile, &#039;yellow&#039;, true) . &quot;\n&quot;;
    echo &#039;  Размер: &#039; . c(filesize($rawFile) . &#039; байт&#039;, &#039;yellow&#039;) . &quot;\n&quot;;
} else {
    echo c(&#039;  Не удалось сохранить файл (нет прав).&#039;, &#039;red&#039;) . &quot;\n&quot;;
}

echo &quot;\n&quot;;
line(&#039;=&#039;, 100, &#039;cyan&#039;);
echo c(&#039;  АНАЛИЗ ЗАВЕРШЁН&#039;, &#039;green&#039;, true) . &quot;\n&quot;;
line(&#039;=&#039;, 100, &#039;cyan&#039;);

if (!$isCli) echo &#039;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&#039;;</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Sat, 03 Oct 2026 09:27:27 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18528#p18528</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18527#p18527</link>
			<description><![CDATA[<p>На php можно все это протестировать данным кодом.<br /></p><div class="codebox"><pre><code>&lt;?php
/**
 * browserleaks_test.php
 *
 * Совместимо с PHP 5.4+ (проверено синтаксически). Работает также на 5.5, 5.6, 7.x, 8.x.
 *
 * Что делает:
 *   - 5 способов запроса к https://tls.browserleaks.com/:
 *       1) file_get_contents
 *       2) cURL
 *       3) Socket HTTP/1.1 (UA: PHP-socket-test)
 *       4) Socket HTTP/1.1 (UA Chrome + все Chrome-заголовки)
 *       5) Socket HTTP/2  (UA Chrome + HPACK + ALPN h2)   — только PHP &gt;= 5.6, 64-bit
 *   - Красивый вывод (CLI + HTML)
 *   - Без обрезки значений
 *
 * Ограничения PHP 5.4:
 *   - Нет ALPN → HTTP/2 недоступен (вариант 5 отключится сам)
 *   - Нет brotli_uncompress → br не запрашиваем, если нет CLI-бинаря brotli
 *   - TLS 1.2 в stream_socket_client может быть недоступен → HTTPS-сокет может не подключиться
 */

if (version_compare(PHP_VERSION, &#039;5.4.0&#039;, &#039;&lt;&#039;)) {
    die(&quot;Требуется PHP &gt;= 5.4.0\n&quot;);
}

error_reporting(E_ALL &amp; ~E_DEPRECATED &amp; ~E_NOTICE &amp; ~E_WARNING);
ini_set(&#039;display_errors&#039;, &#039;0&#039;);
ini_set(&#039;max_execution_time&#039;, &#039;240&#039;);
ini_set(&#039;default_socket_timeout&#039;, &#039;20&#039;);

$isCli     = (php_sapi_name() === &#039;cli&#039;);
$useColors = $isCli;

if (!$isCli) {
    header(&#039;Content-Type: text/html; charset=utf-8&#039;);
    echo &#039;&lt;!DOCTYPE html&gt;&lt;html&gt;&lt;head&gt;&lt;meta charset=&quot;utf-8&quot;&gt;&lt;title&gt;BrowserLeaks Test&lt;/title&gt;&#039;;
    echo &#039;&lt;style&gt;
        body{background:#1e1e1e;color:#d4d4d4;font-family:Consolas,Monaco,monospace;padding:20px;line-height:1.5}
        pre{background:#252526;border:1px solid #3c3c3c;padding:15px;border-radius:5px;overflow-x:auto;white-space:pre-wrap;word-break:break-all}
        .h{color:#4ec9b0;font-weight:bold}.g{color:#6a9955}.y{color:#dcdcaa}.r{color:#f48771}.b{color:#569cd6}
    &lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;pre&gt;&#039;;
}

/* ==================== ВЫВОД ==================== */
function c($text, $color = null, $bold = false) {
    global $useColors, $isCli;
    if ($isCli) {
        if (!$useColors) return $text;
        $map = array(
            &#039;red&#039;=&gt;31,&#039;green&#039;=&gt;32,&#039;yellow&#039;=&gt;33,&#039;blue&#039;=&gt;34,
            &#039;magenta&#039;=&gt;35,&#039;cyan&#039;=&gt;36,&#039;white&#039;=&gt;37,&#039;gray&#039;=&gt;90
        );
        $code = isset($map[$color]) ? $map[$color] : 0;
        $b = $bold ? &#039;1;&#039; : &#039;&#039;;
        return &quot;\033[{$b}{$code}m{$text}\033[0m&quot;;
    }
    $map = array(
        &#039;red&#039;=&gt;&#039;#f48771&#039;,&#039;green&#039;=&gt;&#039;#6a9955&#039;,&#039;yellow&#039;=&gt;&#039;#dcdcaa&#039;,&#039;blue&#039;=&gt;&#039;#569cd6&#039;,
        &#039;magenta&#039;=&gt;&#039;#c586c0&#039;,&#039;cyan&#039;=&gt;&#039;#4ec9b0&#039;,&#039;white&#039;=&gt;&#039;#d4d4d4&#039;,&#039;gray&#039;=&gt;&#039;#808080&#039;
    );
    $col = isset($map[$color]) ? $map[$color] : &#039;#d4d4d4&#039;;
    $weight = $bold ? &#039;bold&#039; : &#039;normal&#039;;
    return &#039;&lt;span style=&quot;color:&#039;.$col.&#039;;font-weight:&#039;.$weight.&#039;&quot;&gt;&#039;
         . htmlspecialchars($text, ENT_QUOTES, &#039;UTF-8&#039;) . &#039;&lt;/span&gt;&#039;;
}
function line($char = &#039;-&#039;, $len = 100, $color = &#039;gray&#039;) {
    echo c(str_repeat($char, $len), $color) . &quot;\n&quot;;
}
function header_($title, $ch = &#039;=&#039;, $len = 100) {
    line($ch, $len, &#039;cyan&#039;);
    echo c(&#039;  &#039; . $title, &#039;cyan&#039;, true) . &quot;\n&quot;;
    line($ch, $len, &#039;cyan&#039;);
}
function subheader_($title, $len = 100) {
    line(&#039;-&#039;, $len, &#039;blue&#039;);
    echo c(&#039;  &#039; . $title, &#039;blue&#039;, true) . &quot;\n&quot;;
    line(&#039;-&#039;, $len, &#039;blue&#039;);
}
function fmt($v) {
    global $isCli;
    if (is_array($v)) {
        $v = json_encode($v, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    }
    $v = (string)$v;
    return $isCli ? $v : htmlspecialchars($v, ENT_QUOTES, &#039;UTF-8&#039;);
}

/* ==================== BROTLI / CHUNKED ==================== */
function brotliAvailable() {
    static $ok = null;
    if ($ok !== null) return $ok;
    if (function_exists(&#039;brotli_uncompress&#039;)) return $ok = true;
    $out = array(); $rc = 1;
    @exec(&#039;command -v brotli 2&gt;/dev/null&#039;, $out, $rc);
    if ($rc === 0 &amp;&amp; !empty($out)) return $ok = true;
    $out = array(); $rc = 1;
    @exec(&#039;where brotli 2&gt;NUL&#039;, $out, $rc);
    return $ok = ($rc === 0 &amp;&amp; !empty($out));
}
function decodeBrotli($data) {
    if (function_exists(&#039;brotli_uncompress&#039;)) {
        $d = @brotli_uncompress($data);
        if ($d !== false) return $d;
    }
    $in  = tempnam(sys_get_temp_dir(), &#039;br_in_&#039;);
    $out = tempnam(sys_get_temp_dir(), &#039;br_out_&#039;);
    if ($in &amp;&amp; $out) {
        file_put_contents($in, $data);
        @exec(&#039;brotli -d -c &#039; . escapeshellarg($in) . &#039; &gt; &#039; . escapeshellarg($out) . &#039; 2&gt;/dev/null&#039;, $o, $rc);
        if ($rc === 0) {
            $d = file_get_contents($out);
            @unlink($in); @unlink($out);
            return $d !== false ? $d : null;
        }
        @unlink($in); @unlink($out);
    }
    return null;
}
function decodeChunked($data) {
    $out = &#039;&#039;; $pos = 0; $len = strlen($data);
    while ($pos &lt; $len) {
        $e = strpos($data, &quot;\r\n&quot;, $pos);
        if ($e === false) break;
        $hex = trim(substr($data, $pos, $e - $pos));
        $s = strpos($hex, &#039;;&#039;);
        if ($s !== false) $hex = substr($hex, 0, $s);
        if ($hex === &#039;&#039; || !ctype_xdigit($hex)) break;
        $size = hexdec($hex);
        $pos = $e + 2;
        if ($size === 0) break;
        if ($pos + $size &gt; $len) { $out .= substr($data, $pos); break; }
        $out .= substr($data, $pos, $size);
        $pos += $size + 2;
    }
    return $out;
}

/* ==================== HTTP/1.1 ЧЕРЕЗ СОКЕТ ==================== */
/**
 * @param string $url
 * @param array  $options  timeout, method, body, headers (name=&gt;value)
 * @return array  status, headers, set_cookies, body, raw
 */
function socketFetchHttp1($url, $options = array()) {
    $p = parse_url($url);
    if (!$p || empty($p[&#039;host&#039;])) {
        throw new Exception(&#039;Некорректный URL: &#039; . $url);
    }

    $scheme  = isset($p[&#039;scheme&#039;]) ? $p[&#039;scheme&#039;] : &#039;https&#039;;
    $host    = $p[&#039;host&#039;];
    $port    = isset($p[&#039;port&#039;]) ? (int)$p[&#039;port&#039;] : ($scheme === &#039;https&#039; ? 443 : 80);
    $path    = (isset($p[&#039;path&#039;]) ? $p[&#039;path&#039;] : &#039;/&#039;)
             . (isset($p[&#039;query&#039;]) ? &#039;?&#039; . $p[&#039;query&#039;] : &#039;&#039;);
    $isHttps = ($scheme === &#039;https&#039;);
    $timeout = isset($options[&#039;timeout&#039;]) ? (int)$options[&#039;timeout&#039;] : 20;
    $method  = isset($options[&#039;method&#039;]) ? strtoupper($options[&#039;method&#039;]) : &#039;GET&#039;;
    $body    = isset($options[&#039;body&#039;]) ? (string)$options[&#039;body&#039;] : &#039;&#039;;

    $headers = isset($options[&#039;headers&#039;]) ? $options[&#039;headers&#039;] : array();
    $headers = array_merge(array(&#039;Host&#039; =&gt; $host, &#039;Connection&#039; =&gt; &#039;close&#039;), $headers);
    if ($method === &#039;POST&#039;) {
        if (!isset($headers[&#039;Content-Type&#039;]))   $headers[&#039;Content-Type&#039;]   = &#039;application/x-www-form-urlencoded&#039;;
        if (!isset($headers[&#039;Content-Length&#039;])) $headers[&#039;Content-Length&#039;] = strlen($body);
    }

    $req = &quot;$method $path HTTP/1.1\r\n&quot;;
    foreach ($headers as $k =&gt; $v) $req .= &quot;$k: $v\r\n&quot;;
    $req .= &quot;\r\n&quot;;
    if ($method === &#039;POST&#039; &amp;&amp; $body !== &#039;&#039;) $req .= $body;

    $errno = 0; $errstr = &#039;&#039;;
    $ssl = array(
        &#039;verify_peer&#039;      =&gt; true,
        &#039;verify_peer_name&#039; =&gt; true,
        &#039;SNI_enabled&#039;      =&gt; true,
        &#039;peer_name&#039;        =&gt; $host,
    );

    // ALPN — только PHP &gt;= 5.6. На 5.4 опция игнорируется, поэтому не выставляем
    // явно, чтобы не путать stream_context_create.
    if (version_compare(PHP_VERSION, &#039;5.6.0&#039;, &#039;&gt;=&#039;)) {
        $ssl[&#039;alpn_protocols&#039;] = &#039;http/1.1&#039;;
    }

    $ctx = stream_context_create(array(&#039;ssl&#039; =&gt; $ssl));

    $remote = ($isHttps ? &#039;ssl://&#039; : &#039;tcp://&#039;) . $host . &#039;:&#039; . $port;
    $fp = @stream_socket_client($remote, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $ctx);
    if (!$fp) {
        throw new Exception(&quot;Не удалось подключиться к $remote: [$errno] $errstr&quot;);
    }
    stream_set_timeout($fp, $timeout);
    stream_set_blocking($fp, true);

    if (@fwrite($fp, $req) === false) {
        fclose($fp);
        throw new Exception(&#039;fwrite failed&#039;);
    }

    // Заголовки
    $rawHead = &#039;&#039;;
    while (!feof($fp)) {
        $l = fgets($fp, 8192);
        if ($l === false) break;
        $rawHead .= $l;
        if ($l === &quot;\r\n&quot; || $l === &quot;\n&quot;) break;
    }
    if ($rawHead === &#039;&#039;) {
        $meta = stream_get_meta_data($fp);
        fclose($fp);
        throw new Exception(!empty($meta[&#039;timed_out&#039;])
            ? &#039;таймаут при чтении заголовков&#039;
            : &#039;соединение закрыто до получения ответа&#039;);
    }
    $hl = preg_split(&quot;/\r\n|\n/&quot;, trim($rawHead));
    $statusLine = array_shift($hl);
    if (!preg_match(&#039;#^HTTP/\d\.\d\s+(\d+)#&#039;, $statusLine, $m)) {
        fclose($fp);
        throw new Exception(&#039;Некорректная статус-строка: &#039; . $statusLine);
    }
    $status = (int)$m[1];

    $parsed = array(); $setCookies = array();
    foreach ($hl as $l) {
        if ($l === &#039;&#039;) continue;
        $pos = strpos($l, &#039;:&#039;);
        if ($pos === false) continue;
        $n = strtolower(trim(substr($l, 0, $pos)));
        $v = trim(substr($l, $pos + 1));
        if ($n === &#039;set-cookie&#039;) { $setCookies[] = $v; continue; }
        $parsed[$n] = isset($parsed[$n]) ? $parsed[$n] . &#039;, &#039; . $v : $v;
    }

    // Тело
    $rawBody = &#039;&#039;;
    $isChunked = isset($parsed[&#039;transfer-encoding&#039;])
        &amp;&amp; stripos($parsed[&#039;transfer-encoding&#039;], &#039;chunked&#039;) !== false;

    if ($isChunked) {
        while (!feof($fp)) {
            $chunk = fread($fp, 8192);
            if ($chunk === false || $chunk === &#039;&#039;) {
                $info = stream_get_meta_data($fp);
                if (!empty($info[&#039;timed_out&#039;]) || !empty($info[&#039;eof&#039;])) break;
                continue;
            }
            $rawBody .= $chunk;
            if (strpos($rawBody, &quot;\r\n0\r\n\r\n&quot;) !== false
             || strpos($rawBody, &quot;\r\n0\r\n&quot;)    !== false) break;
        }
    } elseif (isset($parsed[&#039;content-length&#039;])) {
        $need = (int)$parsed[&#039;content-length&#039;];
        while (strlen($rawBody) &lt; $need &amp;&amp; !feof($fp)) {
            $chunk = fread($fp, min(8192, $need - strlen($rawBody)));
            if ($chunk === false || $chunk === &#039;&#039;) {
                $info = stream_get_meta_data($fp);
                if (!empty($info[&#039;timed_out&#039;])) break;
                continue;
            }
            $rawBody .= $chunk;
        }
    } else {
        while (!feof($fp)) {
            $chunk = fread($fp, 8192);
            if ($chunk === false || $chunk === &#039;&#039;) {
                $info = stream_get_meta_data($fp);
                if (!empty($info[&#039;timed_out&#039;]) || !empty($info[&#039;eof&#039;])) break;
                continue;
            }
            $rawBody .= $chunk;
        }
    }
    fclose($fp);

    if ($isChunked) $rawBody = decodeChunked($rawBody);

    if (isset($parsed[&#039;content-encoding&#039;])) {
        $list = array_map(&#039;trim&#039;, explode(&#039;,&#039;, strtolower($parsed[&#039;content-encoding&#039;])));
        foreach (array_reverse($list) as $e) {
            if ($e === &#039;gzip&#039; || $e === &#039;x-gzip&#039;) {
                $d = @gzdecode($rawBody);
                if ($d !== false) $rawBody = $d;
            } elseif ($e === &#039;deflate&#039;) {
                $d = @gzinflate($rawBody);
                if ($d === false) $d = @gzuncompress($rawBody);
                if ($d !== false) $rawBody = $d;
            } elseif ($e === &#039;br&#039;) {
                $d = decodeBrotli($rawBody);
                if ($d !== null) $rawBody = $d;
            }
        }
    }

    return array(
        &#039;status&#039;      =&gt; $status,
        &#039;headers&#039;     =&gt; $parsed,
        &#039;set_cookies&#039; =&gt; $setCookies,
        &#039;body&#039;        =&gt; $rawBody,
        &#039;raw&#039;         =&gt; $rawHead . &quot;\r\n&quot; . $rawBody,
    );
}

/* ==================== ЗАПРОСЫ ==================== */

$url = &#039;https://tls.browserleaks.com/&#039;;
$results = array();

header_(&#039;ТЕСТИРОВАНИЕ ЗАПРОСОВ К &#039; . $url);
echo &quot;\n&quot;;
echo c(&#039;  PHP версия: &#039;, &#039;gray&#039;) . PHP_VERSION
   . &#039; (&#039; . (PHP_INT_SIZE === 8 ? &#039;64-bit&#039; : &#039;32-bit&#039;) . &quot;)\n&quot;;
echo c(&#039;  cURL: &#039;, &#039;gray&#039;)     . (function_exists(&#039;curl_init&#039;) ? c(&#039;доступен&#039;,&#039;green&#039;) : c(&#039;НЕДОСТУПЕН&#039;,&#039;red&#039;)) . &quot;\n&quot;;
echo c(&#039;  OpenSSL: &#039;, &#039;gray&#039;)  . (extension_loaded(&#039;openssl&#039;)  ? c(&#039;доступен&#039;,&#039;green&#039;) : c(&#039;НЕДОСТУПЕН&#039;,&#039;red&#039;)) . &quot;\n&quot;;
echo c(&#039;  Brotli: &#039;, &#039;gray&#039;)   . (brotliAvailable()             ? c(&#039;доступен&#039;,&#039;green&#039;) : c(&#039;нет (br не запрашиваем)&#039;,&#039;yellow&#039;)) . &quot;\n&quot;;
echo c(&#039;  ALPN h2: &#039;, &#039;gray&#039;)  . (version_compare(PHP_VERSION, &#039;5.6.0&#039;, &#039;&gt;=&#039;)
                                    ? c(&#039;возможен&#039;,&#039;green&#039;) : c(&#039;недоступен (нужен PHP &gt;= 5.6)&#039;,&#039;yellow&#039;)) . &quot;\n&quot;;
echo &quot;\n&quot;;

// --- 1. file_get_contents ---
echo c(&#039;  [1/5] &#039;, &#039;yellow&#039;, true) . &#039;file_get_contents...&#039; . &quot;\n&quot;;
try {
    $ctx = stream_context_create(array(
        &#039;http&#039; =&gt; array(
            &#039;method&#039;        =&gt; &#039;GET&#039;,
            &#039;header&#039;        =&gt; &quot;User-Agent: PHP-file_get_contents-test\r\nAccept: application/json\r\n&quot;,
            &#039;timeout&#039;       =&gt; 20,
            &#039;ignore_errors&#039; =&gt; true,
        ),
        &#039;ssl&#039; =&gt; array(&#039;verify_peer&#039; =&gt; true, &#039;verify_peer_name&#039; =&gt; true),
    ));
    $fgc = @file_get_contents($url, false, $ctx);
    if ($fgc !== false &amp;&amp; $fgc !== &#039;&#039;) {
        $decoded = json_decode($fgc, true);
        $results[&#039;file_get_contents&#039;] = array(
            &#039;success&#039; =&gt; $decoded !== null,
            &#039;data&#039;    =&gt; $decoded,
            &#039;raw&#039;     =&gt; $fgc,
            &#039;error&#039;   =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
        );
    } else {
        $err = error_get_last();
        $results[&#039;file_get_contents&#039;] = array(
            &#039;success&#039; =&gt; false,
            &#039;error&#039;   =&gt; isset($err[&#039;message&#039;]) ? $err[&#039;message&#039;] : &#039;Пустой ответ&#039;,
        );
    }
} catch (Exception $e) {
    $results[&#039;file_get_contents&#039;] = array(&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage());
}

// --- 2. cURL ---
echo c(&#039;  [2/5] &#039;, &#039;yellow&#039;, true) . &#039;cURL...&#039; . &quot;\n&quot;;
if (function_exists(&#039;curl_init&#039;)) {
    try {
        $ch = curl_init();
        curl_setopt_array($ch, array(
            CURLOPT_URL            =&gt; $url,
            CURLOPT_RETURNTRANSFER =&gt; true,
            CURLOPT_TIMEOUT        =&gt; 20,
            CURLOPT_CONNECTTIMEOUT =&gt; 10,
            CURLOPT_FOLLOWLOCATION =&gt; true,
            CURLOPT_HTTPHEADER     =&gt; array(
                &#039;User-Agent: PHP-cURL-test&#039;,
                &#039;Accept: application/json&#039;,
            ),
        ));
        $curlResp = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlErr  = curl_error($ch);
        curl_close($ch);

        if ($curlResp !== false &amp;&amp; $httpCode == 200) {
            $decoded = json_decode($curlResp, true);
            $results[&#039;curl&#039;] = array(
                &#039;success&#039;   =&gt; $decoded !== null,
                &#039;data&#039;      =&gt; $decoded,
                &#039;raw&#039;       =&gt; $curlResp,
                &#039;http_code&#039; =&gt; $httpCode,
                &#039;error&#039;     =&gt; $decoded === null ? &#039;Не удалось разобрать JSON&#039; : null,
            );
        } else {
            $results[&#039;curl&#039;] = array(
                &#039;success&#039;   =&gt; false,
                &#039;error&#039;     =&gt; $curlErr ? $curlErr : (&#039;HTTP &#039; . $httpCode),
                &#039;http_code&#039; =&gt; $httpCode,
            );
        }
    } catch (Exception $e) {
        $results[&#039;curl&#039;] = array(&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage());
    }
} else {
    $results[&#039;curl&#039;] = array(&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; &#039;Расширение cURL не установлено&#039;);
}

// --- 3. Socket HTTP/1.1 (basic) ---
echo c(&#039;  [3/5] &#039;, &#039;yellow&#039;, true) . &#039;Socket HTTP/1.1 (basic)...&#039; . &quot;\n&quot;;
try {
    $r = socketFetchHttp1($url, array(
        &#039;timeout&#039; =&gt; 20,
        &#039;headers&#039; =&gt; array(
            &#039;User-Agent&#039;      =&gt; &#039;PHP-socket-test&#039;,
            &#039;Accept&#039;          =&gt; &#039;application/json, text/plain, */*&#039;,
            &#039;Accept-Encoding&#039; =&gt; &#039;gzip, deflate&#039;,
        ),
    ));
    $decoded = json_decode($r[&#039;body&#039;], true);
    $results[&#039;socket_basic&#039;] = array(
        &#039;success&#039; =&gt; $decoded !== null,
        &#039;data&#039;    =&gt; $decoded,
        &#039;raw&#039;     =&gt; $r[&#039;raw&#039;],
        &#039;status&#039;  =&gt; $r[&#039;status&#039;],
        &#039;error&#039;   =&gt; $decoded === null
            ? &#039;HTTP &#039; . $r[&#039;status&#039;] . &#039;: &#039; . substr($r[&#039;body&#039;], 0, 300) : null,
    );
} catch (Exception $e) {
    $results[&#039;socket_basic&#039;] = array(&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage());
}

// --- 4. Socket HTTP/1.1 (Chrome UA) ---
echo c(&#039;  [4/5] &#039;, &#039;yellow&#039;, true) . &#039;Socket HTTP/1.1 (Chrome UA)...&#039; . &quot;\n&quot;;
try {
    $chromeUA = &#039;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 &#039;
              . &#039;(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36&#039;;
    $acceptEncoding = &#039;gzip, deflate&#039;;
    if (brotliAvailable()) $acceptEncoding .= &#039;, br&#039;;

    $r = socketFetchHttp1($url, array(
        &#039;timeout&#039; =&gt; 20,
        &#039;headers&#039; =&gt; array(
            &#039;User-Agent&#039;                =&gt; $chromeUA,
            &#039;sec-ch-ua&#039;                 =&gt; &#039;&quot;Not/A)Brand&quot;;v=&quot;8&quot;, &quot;Chromium&quot;;v=&quot;126&quot;, &quot;Google Chrome&quot;;v=&quot;126&quot;&#039;,
            &#039;sec-ch-ua-mobile&#039;          =&gt; &#039;?0&#039;,
            &#039;sec-ch-ua-platform&#039;        =&gt; &#039;&quot;Windows&quot;&#039;,
            &#039;Upgrade-Insecure-Requests&#039; =&gt; &#039;1&#039;,
            &#039;Accept&#039;                    =&gt; &#039;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8&#039;,
            &#039;Sec-Fetch-Site&#039;            =&gt; &#039;none&#039;,
            &#039;Sec-Fetch-Mode&#039;            =&gt; &#039;navigate&#039;,
            &#039;Sec-Fetch-User&#039;            =&gt; &#039;?1&#039;,
            &#039;Sec-Fetch-Dest&#039;            =&gt; &#039;document&#039;,
            &#039;Accept-Encoding&#039;           =&gt; $acceptEncoding,
            &#039;Accept-Language&#039;           =&gt; &#039;ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7&#039;,
        ),
    ));
    $decoded = json_decode($r[&#039;body&#039;], true);
    $results[&#039;socket_chrome&#039;] = array(
        &#039;success&#039; =&gt; $decoded !== null,
        &#039;data&#039;    =&gt; $decoded,
        &#039;raw&#039;     =&gt; $r[&#039;raw&#039;],
        &#039;status&#039;  =&gt; $r[&#039;status&#039;],
        &#039;error&#039;   =&gt; $decoded === null
            ? &#039;HTTP &#039; . $r[&#039;status&#039;] . &#039;: &#039; . substr($r[&#039;body&#039;], 0, 300) : null,
    );
} catch (Exception $e) {
    $results[&#039;socket_chrome&#039;] = array(&#039;success&#039; =&gt; false, &#039;error&#039; =&gt; $e-&gt;getMessage());
}

// --- 5. Socket HTTP/2 (Chrome) ---
echo c(&#039;  [5/5] &#039;, &#039;yellow&#039;, true) . &#039;Socket HTTP/2 (Chrome)...&#039; . &quot;\n&quot;;
if (version_compare(PHP_VERSION, &#039;5.6.0&#039;, &#039;&gt;=&#039;) &amp;&amp; PHP_INT_SIZE === 8) {
    // На PHP 5.4 этого блока не будет — оставлен только &quot;заглушка&quot; ниже.
    // Полная реализация HTTP/2 лежит в отдельном файле (browserleaks_h2.php),
    // здесь мы её не встраиваем, чтобы не раздувать файл и не ломать синтаксис 5.4.
    $results[&#039;socket_chrome_h2&#039;] = array(
        &#039;success&#039; =&gt; false,
        &#039;error&#039;   =&gt; &#039;Для HTTP/2-варианта используйте отдельный файл (PHP &gt;= 5.6, 64-bit)&#039;,
    );
} else {
    $results[&#039;socket_chrome_h2&#039;] = array(
        &#039;success&#039; =&gt; false,
        &#039;error&#039;   =&gt; &#039;HTTP/2-вариант требует PHP &gt;= 5.6 и 64-битную платформу&#039;,
    );
}

/* ==================== СТАТУС ==================== */
echo &quot;\n&quot;;
header_(&#039;СТАТУС ВЫПОЛНЕНИЯ&#039;);
echo &quot;\n&quot;;

$successful = array();
$labels = array(
    &#039;file_get_contents&#039; =&gt; &#039;file_get_contents&#039;,
    &#039;curl&#039;              =&gt; &#039;cURL&#039;,
    &#039;socket_basic&#039;      =&gt; &#039;Socket H1 basic&#039;,
    &#039;socket_chrome&#039;     =&gt; &#039;Socket H1 Chrome&#039;,
    &#039;socket_chrome_h2&#039;  =&gt; &#039;Socket H2 Chrome&#039;,
);

foreach ($results as $m =&gt; $r) {
    $lbl = isset($labels[$m]) ? $labels[$m] : $m;
    if (!empty($r[&#039;success&#039;])) {
        $successful[$m] = $r[&#039;data&#039;];
        printf(&quot;  %s  %-22s %s\n&quot;,
            c(&#039;OK&#039;, &#039;green&#039;, true),
            c($lbl, &#039;white&#039;),
            c(&#039;УСПЕШНО&#039;, &#039;green&#039;, true)
        );
    } else {
        printf(&quot;  %s  %-22s %s %s\n&quot;,
            c(&#039;FAIL&#039;, &#039;red&#039;, true),
            c($lbl, &#039;white&#039;),
            c(&#039;ОШИБКА&#039;, &#039;red&#039;, true),
            c(&#039;(&#039; . (isset($r[&#039;error&#039;]) ? $r[&#039;error&#039;] : &#039;unknown&#039;) . &#039;)&#039;, &#039;gray&#039;)
        );
    }
}

if (empty($successful)) {
    echo &quot;\n&quot; . c(&#039;  Ни один запрос не удался.&#039;, &#039;red&#039;, true) . &quot;\n&quot;;
    if (!$isCli) echo &#039;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&#039;;
    exit(1);
}

/* ==================== АНАЛИЗ ==================== */
function getField($data, $field) {
    if (isset($data[$field])) return $data[$field];
    if (isset($data[&#039;tls&#039;][$field])) return $data[&#039;tls&#039;][$field];
    return null;
}

$keyFields = array(
    &#039;user_agent&#039;  =&gt; &#039;User-Agent&#039;,
    &#039;ja4&#039;         =&gt; &#039;JA4&#039;,
    &#039;ja4_r&#039;       =&gt; &#039;JA4_r&#039;,
    &#039;ja4_o&#039;       =&gt; &#039;JA4_o (original)&#039;,
    &#039;ja4_ro&#039;      =&gt; &#039;JA4_ro (original)&#039;,
    &#039;ja3_hash&#039;    =&gt; &#039;JA3 Hash&#039;,
    &#039;ja3_text&#039;    =&gt; &#039;JA3 Text&#039;,
    &#039;ja3n_hash&#039;   =&gt; &#039;JA3N Hash&#039;,
    &#039;ja3n_text&#039;   =&gt; &#039;JA3N Text&#039;,
    &#039;akamai_hash&#039; =&gt; &#039;Akamai Hash&#039;,
    &#039;akamai_text&#039; =&gt; &#039;Akamai Text&#039;,
);

$tlsFields = array(
    &#039;cipher_suite&#039;, &#039;key_exchange&#039;, &#039;signature_scheme&#039;,
    &#039;selected_version&#039;, &#039;record_version&#039;, &#039;handshake_version&#039;,
);

echo &quot;\n&quot;;
header_(&#039;СВОДНАЯ ТАБЛИЦА ОСНОВНЫХ ПОЛЕЙ&#039;);
echo &quot;\n&quot;;

foreach ($keyFields as $field =&gt; $label) {
    $values = array();
    foreach ($successful as $m =&gt; $data) {
        $v = getField($data, $field);
        if ($v !== null) $values[$m] = $v;
    }
    if (empty($values)) continue;

    $unique = array_unique($values);
    $isSame = count($unique) === 1;

    echo c(&#039;  &#039; . $label, &#039;cyan&#039;, true)
       . c($isSame ? &#039;  [совпадает]&#039; : &#039;  [различается]&#039;, $isSame ? &#039;green&#039; : &#039;yellow&#039;) . &quot;\n&quot;;
    foreach ($values as $m =&gt; $v) {
        echo &#039;    &#039; . c(str_pad(isset($labels[$m]) ? $labels[$m] : $m, 22), &#039;gray&#039;)
           . &#039; = &#039; . fmt($v) . &quot;\n&quot;;
    }
    echo &quot;\n&quot;;
}

foreach ($tlsFields as $tf) {
    $values = array();
    foreach ($successful as $m =&gt; $data) {
        if (isset($data[&#039;tls&#039;][$tf])) $values[$m] = $data[&#039;tls&#039;][$tf];
    }
    if (empty($values)) continue;

    $unique = array_unique($values, SORT_REGULAR);
    $isSame = count($unique) === 1;

    echo c(&#039;  tls.&#039; . $tf, &#039;cyan&#039;, true)
       . c($isSame ? &#039;  [совпадает]&#039; : &#039;  [различается]&#039;, $isSame ? &#039;green&#039; : &#039;yellow&#039;) . &quot;\n&quot;;
    foreach ($values as $m =&gt; $v) {
        if (is_array($v)) {
            $disp = isset($v[&#039;name&#039;])
                ? $v[&#039;name&#039;] . &#039; (&#039; . (isset($v[&#039;id&#039;]) ? $v[&#039;id&#039;] : &#039;&#039;) . &#039;)&#039;
                : json_encode($v, JSON_UNESCAPED_UNICODE);
        } else {
            $disp = $v;
        }
        echo &#039;    &#039; . c(str_pad(isset($labels[$m]) ? $labels[$m] : $m, 22), &#039;gray&#039;)
           . &#039; = &#039; . fmt($disp) . &quot;\n&quot;;
    }
    echo &quot;\n&quot;;
}

echo &quot;\n&quot;;
header_(&#039;ОБЩИЕ ОТВЕТЫ (совпали у всех)&#039;);
echo &quot;\n&quot;;
$commonFound = false;
foreach (array_merge(array_keys($keyFields), array_map(function($f){ return &#039;tls.&#039; . $f; }, $tlsFields)) as $f) {
    $vals = array();
    foreach ($successful as $m =&gt; $d) {
        if (strpos($f, &#039;tls.&#039;) === 0) {
            $tf = substr($f, 4);
            if (isset($d[&#039;tls&#039;][$tf])) $vals[$m] = $d[&#039;tls&#039;][$tf];
        } else {
            $v = getField($d, $f);
            if ($v !== null) $vals[$m] = $v;
        }
    }
    if (count($vals) === count($successful) &amp;&amp; count(array_unique($vals, SORT_REGULAR)) === 1) {
        $v = reset($vals);
        if (is_array($v)) {
            $disp = isset($v[&#039;name&#039;]) ? $v[&#039;name&#039;] : json_encode($v, JSON_UNESCAPED_UNICODE);
        } else {
            $disp = $v;
        }
        echo &#039;  &#039; . c($f, &#039;green&#039;, true) . &#039; = &#039; . fmt($disp) . &quot;\n&quot;;
        $commonFound = true;
    }
}
if (!$commonFound) echo c(&#039;  Нет полностью совпадающих полей.&#039;, &#039;yellow&#039;) . &quot;\n&quot;;

echo &quot;\n&quot;;
header_(&#039;РАЗЛИЧИЯ (чем отличались запросы)&#039;);
echo &quot;\n&quot;;
$diffFound = false;
foreach (array_merge(array_keys($keyFields), array_map(function($f){ return &#039;tls.&#039; . $f; }, $tlsFields)) as $f) {
    $vals = array();
    foreach ($successful as $m =&gt; $d) {
        if (strpos($f, &#039;tls.&#039;) === 0) {
            $tf = substr($f, 4);
            if (isset($d[&#039;tls&#039;][$tf])) $vals[$m] = $d[&#039;tls&#039;][$tf];
        } else {
            $v = getField($d, $f);
            if ($v !== null) $vals[$m] = $v;
        }
    }
    if (count($vals) &gt; 1 &amp;&amp; count(array_unique($vals, SORT_REGULAR)) &gt; 1) {
        echo &#039;  &#039; . c($f, &#039;yellow&#039;, true) . &quot;\n&quot;;
        foreach ($vals as $m =&gt; $v) {
            if (is_array($v)) {
                $disp = isset($v[&#039;name&#039;])
                    ? $v[&#039;name&#039;] . &#039; (&#039; . (isset($v[&#039;id&#039;]) ? $v[&#039;id&#039;] : &#039;&#039;) . &#039;)&#039;
                    : json_encode($v, JSON_UNESCAPED_UNICODE);
            } else {
                $disp = $v;
            }
            echo &#039;    &#039; . c(str_pad(isset($labels[$m]) ? $labels[$m] : $m, 22), &#039;gray&#039;)
               . &#039; = &#039; . fmt($disp) . &quot;\n&quot;;
        }
        $diffFound = true;
    }
}
if (!$diffFound) echo c(&#039;  Нет различий по отслеживаемым полям.&#039;, &#039;yellow&#039;) . &quot;\n&quot;;

/* ==================== ДЕТАЛЬНО ПО МЕТОДАМ ==================== */
echo &quot;\n&quot;;
header_(&#039;ДЕТАЛЬНАЯ СВОДКА ПО КАЖДОМУ МЕТОДУ&#039;);
echo &quot;\n&quot;;

foreach ($successful as $m =&gt; $d) {
    $lbl = isset($labels[$m]) ? $labels[$m] : $m;
    subheader_(&#039;Метод: &#039; . $lbl);

    foreach ($keyFields as $f =&gt; $label) {
        $v = getField($d, $f);
        if ($v === null) continue;
        echo &#039;  &#039; . c(str_pad($label, 22), &#039;white&#039;, true) . &#039; = &#039; . fmt($v) . &quot;\n&quot;;
    }
    foreach ($tlsFields as $tf) {
        if (!isset($d[&#039;tls&#039;][$tf])) continue;
        $v = $d[&#039;tls&#039;][$tf];
        if (is_array($v)) {
            $disp = isset($v[&#039;name&#039;])
                ? $v[&#039;name&#039;] . &#039; (&#039; . (isset($v[&#039;id&#039;]) ? $v[&#039;id&#039;] : &#039;&#039;) . &#039;)&#039;
                : json_encode($v, JSON_UNESCAPED_UNICODE);
        } else {
            $disp = $v;
        }
        echo &#039;  &#039; . c(str_pad(&#039;tls.&#039; . $tf, 22), &#039;white&#039;, true) . &#039; = &#039; . fmt($disp) . &quot;\n&quot;;
    }
    echo &quot;\n&quot;;
}

/* ==================== СОХРАНЕНИЕ ==================== */
echo &quot;\n&quot;;
header_(&#039;СОХРАНЕНИЕ&#039;);
echo &quot;\n&quot;;
$rawFile = dirname(__FILE__) . &#039;/browserleaks_raw_responses.json&#039;;
$saved = @file_put_contents(
    $rawFile,
    json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
if ($saved !== false) {
    echo &#039;  Файл сохранён: &#039; . c($rawFile, &#039;yellow&#039;, true) . &quot;\n&quot;;
    echo &#039;  Размер: &#039; . c(filesize($rawFile) . &#039; байт&#039;, &#039;yellow&#039;) . &quot;\n&quot;;
} else {
    echo c(&#039;  Не удалось сохранить файл (нет прав).&#039;, &#039;red&#039;) . &quot;\n&quot;;
}

echo &quot;\n&quot;;
line(&#039;=&#039;, 100, &#039;cyan&#039;);
echo c(&#039;  АНАЛИЗ ЗАВЕРШЁН&#039;, &#039;green&#039;, true) . &quot;\n&quot;;
line(&#039;=&#039;, 100, &#039;cyan&#039;);

if (!$isCli) echo &#039;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&#039;;</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Sat, 03 Oct 2026 08:46:34 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18527#p18527</guid>
		</item>
		<item>
			<title><![CDATA[Re: Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18526#p18526</link>
			<description><![CDATA[<p><strong>JA3-fingerprinting</strong> — это отпечаток TLS-клиента, который вычисляется из полей пакета ClientHello (версия TLS, шифры, расширения, кривые) и сворачивается в 32-символьный MD5-хеш.<span class="postimg"><img src="https://e3c609db-2a15-40f5-8967-5c0f0d03b4d9.selstorage.ru/blog/fb0f84cb-80bb-4950-807a-38fea94774fa.png" alt="https://e3c609db-2a15-40f5-8967-5c0f0d03b4d9.selstorage.ru/blog/fb0f84cb-80bb-4950-807a-38fea94774fa.png" /></span><br />TLS-рукопожатие проходит до HTTP, поэтому подмена User-Agent и заголовков не помогает: JA3 выдаёт бота раньше, чем сервер получит первый запрос.<br />У библиотек Python requests, curl и Go TLS-стек отличается от браузерного, поэтому их JA3 не совпадает ни с одним реальным браузером и легко детектится.<br /><strong>Порядок установки соединения выглядит так:</strong></p><p>Клиент открывает TCP-соединение с сервером.<br />TLS handshake: клиент шлёт ClientHello в plaintext — здесь и снимается JA3.<br />Стороны согласуют ключи и поднимают шифрованный канал.<br />Только теперь уходит HTTP-запрос с User-Agent, cookie и заголовками.</p><p>Отсюда три следствия. Первое: подмена User-Agent не помогает — заголовки идут после рукопожатия, а JA3 снят на шаге 2. Второе: спуфинг HTTP-заголовков вообще не касается TLS-слоя. Третье: бот выдаёт себя раньше, чем сервер потратит ресурсы на разбор запроса, — а значит, отсечь его можно без нагрузки на бэкенд.</p><p><strong>Почему у ботов «неправильный» JA3:</strong></p><p>Python requests / urllib3 используют системный OpenSSL — их отпечаток не совпадает ни с одним браузером.<br />Go-боты (net/http) имеют характерный TLS-стек, давно известный всем антибот-движкам.<br />curl, wget, стоковый Selenium/headless дают узнаваемые сигнатуры из публичных баз JA3.<br />Автоматизация вроде ZennoPoster и BAS часто работает поверх стандартных HTTP-движков, чей TLS-стек не совпадает с реальным браузером.<br /><span class="postimg"><img src="https://e3c609db-2a15-40f5-8967-5c0f0d03b4d9.selstorage.ru/blog/7986a6c7-2227-4640-8141-e1404a7133d8.png" alt="https://e3c609db-2a15-40f5-8967-5c0f0d03b4d9.selstorage.ru/blog/7986a6c7-2227-4640-8141-e1404a7133d8.png" /></span></p><p><strong>С января 2023 года Chrome рандомизирует порядок TLS-расширений,</strong> из-за чего классический JA3 стал нестабильным для браузеров — индустрия перешла на <strong>JA4</strong> (FoxIO, 2023). Раньше порядок расширений был фиксированным, и один браузер давал один JA3. Теперь порядок меняется от соединения к соединению.Масштаб: рандомизация даёт свыше 20 триллионов возможных JA3-хешей для одного и того же Chrome.<br />И это теперь тоже стало признаком, который отслеживает.<strong> Если поступает один и тот же JA3 - детектируется бот.</strong> Описанный в первом сообщении метод это учитывает и эмулирует работу Chrome 110 или более новых версий.<br />JA4 — новый стандарт TLS-отпечатка&nbsp; Он сортирует расширения и шифры перед хешированием, поэтому даёт стабильный отпечаток даже для современного Chrome, который перемешивает порядок. Также JA4 использует SHA-256 вместо MD5, поддерживает QUIC/HTTP3 и имеет читаемый трёхчастный формат a_b_c. По сути это преемник JA3, который к 2026 году стал отраслевым стандартом.<br /><span class="postimg"><img src="https://e3c609db-2a15-40f5-8967-5c0f0d03b4d9.selstorage.ru/blog/288559e6-70e7-44c7-8b0d-9f7bab2cd0ef.png" alt="https://e3c609db-2a15-40f5-8967-5c0f0d03b4d9.selstorage.ru/blog/288559e6-70e7-44c7-8b0d-9f7bab2cd0ef.png" /></span> <br />К 2026 году JA4 — основной TLS-сигнал у Cloudflare, Akamai и AWS WAF. </p><p><strong><br />HTTP/2-отпечаток: второй сетевой слой до контента</strong><br />Помимо TLS, бота выдаёт способ, которым клиент открывает HTTP/2-соединение — так называемый Akamai h2 fingerprint, снимаемый до передачи контента. Это второй сетевой отпечаток, дополняющий JA3/JA4. Когда клиент поднимает HTTP/2, он отправляет SETTINGS-фрейм, WINDOW_UPDATE, задаёт приоритеты потоков и порядок псевдо-заголовков (:method, :authority, :scheme, :path). У браузеров это устроено характерно; у ботов на Go, Python или Node — иначе. Комбинация «JA3/JA4 + HTTP/2-отпечаток» ловит клиентов, которые подделали одно, но забыли про другое: например, TLS выглядит как Chrome, а порядок псевдо-заголовков — как у Python-клиента. </p><p><strong>Ограничения TLS-fingerprinting</strong><br />Общий отпечаток. Миллионы людей с одинаковым Chrome дают одинаковый JA4. По нему нельзя выделить конкретного пользователя — только класс клиента.</p><p>CG-NAT. За одним IP через Carrier-Grade NAT сидят тысячи мобильных пользователей — блокировать по связке IP+JA3 опасно, поймаете живых людей.</p><p>Обход. Инструменты curl-impersonate (пересобирает TLS на BoringSSL), utls в Go и curl_cffi с профилем impersonate=&quot;chrome&quot; копируют браузерный ClientHello почти точно — JA3/JA4 такого бота совпадёт с настоящим Chrome.</p><p>Легитимные не-браузерные клиенты. Мобильные приложения, API-интеграции, платёжные вебхуки и поисковые боты имеют свои TLS-стеки — рубить их огулом нельзя.</p><p>Вывод: TLS-fingerprinting мгновенно и без нагрузки отсеивает дешёвых ботов (Python, curl, стоковый Selenium), но продвинутых с подделанным ClientHello — нет.</p><p><strong>Как обходить такую защиту?</strong><br />Инструменты вроде curl-impersonate, utls и curl_cffi пересобирают TLS-стек так, чтобы ClientHello совпадал с браузерным. <br /><strong>Как проверить свой JA3/JA4-отпечаток?</strong><br />tls.browserleaks.com и аналоги. Откройте такой сервис в разных браузерах и в curl — увидите, что отпечатки различаются. Это наглядно демонстрирует, почему по TLS-рукопожатию бот отличается от настоящего браузера.</p><p>Если научились обходить JA3/JA4, как могут забанить бота?<br />1) Использование статических User-Agent. Если вы посылаете 10 000 запросов с одной и той же строкой Chrome 114, вас забанят через 30 секунд. <br />2) Игнорирование HTTP/2 и HTTP/3. Браузеры давно перешли на новые протоколы, а многие библиотеки по умолчанию используют HTTP/1.1, что является явным признаком бота.<br />3) Отсутствие обработки JavaScript (загрузка только чистого HTML).<br />4) Линейные интервалы между запросами (антифрод видит четкую периодичность).<br />5) Пренебрежение обработкой капчи (рекомендуется использовать сервисы типа 2Captcha или собственные нейросети).<br />6) Слишком высокая скорость парсинга с одного IP (превышение Rate Limit).<br />Ну и конечно, описанные выше&nbsp; Неправильный порядок заголовков в запросе (Header Order). Использование headless-браузеров без патчинга отпечатков.<br /><strong><br />Все перечисленные ниже варианты работают в PHP и меняют TLS ClientHello (JA3/JA4) и/или HTTP/2 SETTINGS</strong><br />Способ&nbsp; &nbsp; Что меняет&nbsp; &nbsp; Требования&nbsp; &nbsp; Точность отпечатка<br />Reqxide&nbsp; &nbsp; TLS + HTTP/2 + заголовки&nbsp; &nbsp; PHP FFI + libcurl-impersonate&nbsp; &nbsp; Полная (JA3/JA4 как у Chrome) <br />wreq-php&nbsp; &nbsp; TLS + HTTP/2&nbsp; &nbsp; PHP 8.x + Rust-расширение&nbsp; &nbsp; Полная (100+ профилей браузеров) <br />PulsarX&nbsp; &nbsp; TLS (cipher order) + HTTP-заголовки&nbsp; &nbsp; PHP 8.1+, ext-curl&nbsp; &nbsp; Частичная (JA3 не байт-в-байт, но HTTP-слой как браузер) <br />curl-impersonate&nbsp; &nbsp; TLS + HTTP/2&nbsp; &nbsp; Внешний бинарник + exec()&nbsp; &nbsp; Полная <br />Ручной stream context&nbsp; &nbsp; Cipher suites, curves&nbsp; &nbsp; ext-openssl&nbsp; &nbsp; Слабый (только cipher list, без GREASE/ECH/permutation)</p>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Sat, 03 Oct 2026 07:05:26 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18526#p18526</guid>
		</item>
		<item>
			<title><![CDATA[Cloudflare WAF (Web Application Firewall) и возможность из php пройти]]></title>
			<link>https://hms.lostcut.net/viewtopic.php?pid=18525#p18525</link>
			<description><![CDATA[<p>Как работает WAF&nbsp; Cloudflare ?&nbsp; WAF проверяет TLS-отпечаток на этапе рукопожатия. Стандартный cURL в PHP имеет отпечаток, который не совпадает ни с одним браузером. Поэтому сервер, даже не посмотрев на ваши заголовки и cookie, отбрасывает соединение с ошибкой 403 Forbidden.&nbsp; TLS-отпечаток (так называемый JA3 fingerprint). Браузер и cURL устанавливают TLS-соединение по-разному (разный набор шифров, расширений, версия TLS). Защита видит, что «отпечаток» не совпадает с заявленным User-Agent, и блокирует запрос. Кстати mail.ru теперь тоже так работает.</p><p>Решить проблему можно за деньги = Proxy-провайдеры: Некоторые сервисы (например, NikaProxy) продают &quot;TLS-Pro&quot; тарифы, где они подменяют ваш TLS-отпечаток на браузерный, чтобы запросы не блокировались .<br />Инструменты проверки: Существуют утилиты (например, ja3-fingerprint-checker), которые показывают, как ваш HTTP-клиент выглядит со стороны защитных систем .</p><p>Решить можно бесплатно, обновлением софта на своем сервере.<br />1. Использование curl-impersonate. Это специальная модификация cURL, которая может полностью имитировать рукопожатие Chrome, Firefox, Edge и Safari. Нужно&nbsp; установить curl-impersonate на сервер или использовать альтернативную библиотеку для PHP.</p><p>Скачать сборку curl-impersonate можно <a href="https://github.com/lexiforest/curl-impersonate/releases">https://github.com/lexiforest/curl-impersonate/releases</a> Например для windows ищем curl-impersonate-v2.2.3.x86_64-win32.tar.gz В этом архиве curl-impersonate.exe и куча батников, которые дадут разные, реальные браузеры например curl_chrome120.bat или curl_chrome146.bat . Все это размещаем в папке C:\curl-impersonate\ <br />Далее ставим&nbsp; &nbsp;Composer (нужен для установки PHP-обёртки). Скачайте Composer-Setup.exe с официального сайта getcomposer.org. Запустите установщик, она найдет сам php. После установки проверить composer -V, должна отобразиться версия Composer и путь к PHP. Обёртка kelvinzer0/curl-impersonate-php — это простая библиотека, которая запускает скачанный .exe через exec() и возвращает ответ . Когда все настроено, используйте обёртку для чтения сайта например такой функцией<br /></p><div class="codebox"><pre><code>use CurlImpersonate\CurlImpersonate;

function fetch_embed_via_impersonate($url, $referer, $cookies = &#039;&#039;) {
    $curl = new CurlImpersonate();

    $curl-&gt;setopt(CurlImpersonate::OPT_ENGINE, &#039;C:\\curl-impersonate\\curl_chrome116.exe&#039;);
    $curl-&gt;setopt(CurlImpersonate::OPT_URL, $url);
    $curl-&gt;setopt(CurlImpersonate::OPT_METHOD, &#039;GET&#039;);
    $curl-&gt;setopt(CurlImpersonate::OPT_TIMEOUT, 25);

    // Заголовки — как в вашем текущем коде
    $headers = [
        &#039;accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8&#039;,
        &#039;accept-language: ru,ru-RU;q=0.9,en-US;q=0.8,en;q=0.7&#039;,
        &#039;referer: &#039; . $referer,
        &#039;sec-ch-ua: &quot;Chromium&quot;;v=&quot;116&quot;, &quot;Not A(Brand&quot;;v=&quot;99&quot;&#039;,
        &#039;sec-ch-ua-mobile: ?0&#039;,
        &#039;sec-ch-ua-platform: &quot;Windows&quot;&#039;,
        &#039;sec-fetch-dest: iframe&#039;,
        &#039;sec-fetch-mode: navigate&#039;,
        &#039;sec-fetch-site: cross-site&#039;,
        &#039;upgrade-insecure-requests: 1&#039;,
        &#039;user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36&#039;,
    ];

    foreach ($headers as $h) {
        $curl-&gt;setopt(CurlImpersonate::OPT_HEADER, $h); // или через массив, см. документацию
    }

    $html = $curl-&gt;execStandard();

    // Извлеките cookie из ответа, если нужно, или используйте опцию COOKIEJAR
    return $html;
}</code></pre></div><p>.&nbsp; <br />обёртка не ведёт cookie-jar автоматически.&nbsp; Вам нужно либо передавать cookie вручную (как заголовок Cookie:), либо использовать опции OPT_COOKIEJAR/OPT_COOKIEFILE, если они поддерживаются версией обёртки </p><p>Есть более современная обёртка farhaanaliii/curl-cffi, которая использует PHP FFI и загружает libcurl-impersonate.dll напрямую, без запуска .exe . Она требует PHP 8.1+ и включённого расширения ffi. Этот пакет распространяется через Composer, как и любой другой PHP-пакет. На странице Packagist указаны его требования и версия <a href="https://packagist.org/packages/farhaanaliii/curl-cffi">https://packagist.org/packages/farhaanaliii/curl-cffi</a> В папке вашего проекта выполните команду:bash<br />composer require farhaanaliii/curl-cffi</p><p>Следует ожидать, что в очередной версии php этот механизм будет встроен и защита сайтов WAF&nbsp; полностью потеряет актуальность.</p>]]></description>
			<author><![CDATA[null@example.com (smsbox3)]]></author>
			<pubDate>Fri, 02 Oct 2026 10:04:48 +0000</pubDate>
			<guid>https://hms.lostcut.net/viewtopic.php?pid=18525#p18525</guid>
		</item>
	</channel>
</rss>
