На php можно все это протестировать данным кодом.
<?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 >= 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, '5.4.0', '<')) {
die("Требуется PHP >= 5.4.0\n");
}
error_reporting(E_ALL & ~E_DEPRECATED & ~E_NOTICE & ~E_WARNING);
ini_set('display_errors', '0');
ini_set('max_execution_time', '240');
ini_set('default_socket_timeout', '20');
$isCli = (php_sapi_name() === 'cli');
$useColors = $isCli;
if (!$isCli) {
header('Content-Type: text/html; charset=utf-8');
echo '<!DOCTYPE html><html><head><meta charset="utf-8"><title>BrowserLeaks Test</title>';
echo '<style>
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}
</style></head><body><pre>';
}
/* ==================== ВЫВОД ==================== */
function c($text, $color = null, $bold = false) {
global $useColors, $isCli;
if ($isCli) {
if (!$useColors) return $text;
$map = array(
'red'=>31,'green'=>32,'yellow'=>33,'blue'=>34,
'magenta'=>35,'cyan'=>36,'white'=>37,'gray'=>90
);
$code = isset($map[$color]) ? $map[$color] : 0;
$b = $bold ? '1;' : '';
return "\033[{$b}{$code}m{$text}\033[0m";
}
$map = array(
'red'=>'#f48771','green'=>'#6a9955','yellow'=>'#dcdcaa','blue'=>'#569cd6',
'magenta'=>'#c586c0','cyan'=>'#4ec9b0','white'=>'#d4d4d4','gray'=>'#808080'
);
$col = isset($map[$color]) ? $map[$color] : '#d4d4d4';
$weight = $bold ? 'bold' : 'normal';
return '<span style="color:'.$col.';font-weight:'.$weight.'">'
. htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . '</span>';
}
function line($char = '-', $len = 100, $color = 'gray') {
echo c(str_repeat($char, $len), $color) . "\n";
}
function header_($title, $ch = '=', $len = 100) {
line($ch, $len, 'cyan');
echo c(' ' . $title, 'cyan', true) . "\n";
line($ch, $len, 'cyan');
}
function subheader_($title, $len = 100) {
line('-', $len, 'blue');
echo c(' ' . $title, 'blue', true) . "\n";
line('-', $len, 'blue');
}
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, 'UTF-8');
}
/* ==================== BROTLI / CHUNKED ==================== */
function brotliAvailable() {
static $ok = null;
if ($ok !== null) return $ok;
if (function_exists('brotli_uncompress')) return $ok = true;
$out = array(); $rc = 1;
@exec('command -v brotli 2>/dev/null', $out, $rc);
if ($rc === 0 && !empty($out)) return $ok = true;
$out = array(); $rc = 1;
@exec('where brotli 2>NUL', $out, $rc);
return $ok = ($rc === 0 && !empty($out));
}
function decodeBrotli($data) {
if (function_exists('brotli_uncompress')) {
$d = @brotli_uncompress($data);
if ($d !== false) return $d;
}
$in = tempnam(sys_get_temp_dir(), 'br_in_');
$out = tempnam(sys_get_temp_dir(), 'br_out_');
if ($in && $out) {
file_put_contents($in, $data);
@exec('brotli -d -c ' . escapeshellarg($in) . ' > ' . escapeshellarg($out) . ' 2>/dev/null', $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 = ''; $pos = 0; $len = strlen($data);
while ($pos < $len) {
$e = strpos($data, "\r\n", $pos);
if ($e === false) break;
$hex = trim(substr($data, $pos, $e - $pos));
$s = strpos($hex, ';');
if ($s !== false) $hex = substr($hex, 0, $s);
if ($hex === '' || !ctype_xdigit($hex)) break;
$size = hexdec($hex);
$pos = $e + 2;
if ($size === 0) break;
if ($pos + $size > $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=>value)
* @return array status, headers, set_cookies, body, raw
*/
function socketFetchHttp1($url, $options = array()) {
$p = parse_url($url);
if (!$p || empty($p['host'])) {
throw new Exception('Некорректный URL: ' . $url);
}
$scheme = isset($p['scheme']) ? $p['scheme'] : 'https';
$host = $p['host'];
$port = isset($p['port']) ? (int)$p['port'] : ($scheme === 'https' ? 443 : 80);
$path = (isset($p['path']) ? $p['path'] : '/')
. (isset($p['query']) ? '?' . $p['query'] : '');
$isHttps = ($scheme === 'https');
$timeout = isset($options['timeout']) ? (int)$options['timeout'] : 20;
$method = isset($options['method']) ? strtoupper($options['method']) : 'GET';
$body = isset($options['body']) ? (string)$options['body'] : '';
$headers = isset($options['headers']) ? $options['headers'] : array();
$headers = array_merge(array('Host' => $host, 'Connection' => 'close'), $headers);
if ($method === 'POST') {
if (!isset($headers['Content-Type'])) $headers['Content-Type'] = 'application/x-www-form-urlencoded';
if (!isset($headers['Content-Length'])) $headers['Content-Length'] = strlen($body);
}
$req = "$method $path HTTP/1.1\r\n";
foreach ($headers as $k => $v) $req .= "$k: $v\r\n";
$req .= "\r\n";
if ($method === 'POST' && $body !== '') $req .= $body;
$errno = 0; $errstr = '';
$ssl = array(
'verify_peer' => true,
'verify_peer_name' => true,
'SNI_enabled' => true,
'peer_name' => $host,
);
// ALPN — только PHP >= 5.6. На 5.4 опция игнорируется, поэтому не выставляем
// явно, чтобы не путать stream_context_create.
if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
$ssl['alpn_protocols'] = 'http/1.1';
}
$ctx = stream_context_create(array('ssl' => $ssl));
$remote = ($isHttps ? 'ssl://' : 'tcp://') . $host . ':' . $port;
$fp = @stream_socket_client($remote, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $ctx);
if (!$fp) {
throw new Exception("Не удалось подключиться к $remote: [$errno] $errstr");
}
stream_set_timeout($fp, $timeout);
stream_set_blocking($fp, true);
if (@fwrite($fp, $req) === false) {
fclose($fp);
throw new Exception('fwrite failed');
}
// Заголовки
$rawHead = '';
while (!feof($fp)) {
$l = fgets($fp, 8192);
if ($l === false) break;
$rawHead .= $l;
if ($l === "\r\n" || $l === "\n") break;
}
if ($rawHead === '') {
$meta = stream_get_meta_data($fp);
fclose($fp);
throw new Exception(!empty($meta['timed_out'])
? 'таймаут при чтении заголовков'
: 'соединение закрыто до получения ответа');
}
$hl = preg_split("/\r\n|\n/", trim($rawHead));
$statusLine = array_shift($hl);
if (!preg_match('#^HTTP/\d\.\d\s+(\d+)#', $statusLine, $m)) {
fclose($fp);
throw new Exception('Некорректная статус-строка: ' . $statusLine);
}
$status = (int)$m[1];
$parsed = array(); $setCookies = array();
foreach ($hl as $l) {
if ($l === '') continue;
$pos = strpos($l, ':');
if ($pos === false) continue;
$n = strtolower(trim(substr($l, 0, $pos)));
$v = trim(substr($l, $pos + 1));
if ($n === 'set-cookie') { $setCookies[] = $v; continue; }
$parsed[$n] = isset($parsed[$n]) ? $parsed[$n] . ', ' . $v : $v;
}
// Тело
$rawBody = '';
$isChunked = isset($parsed['transfer-encoding'])
&& stripos($parsed['transfer-encoding'], 'chunked') !== false;
if ($isChunked) {
while (!feof($fp)) {
$chunk = fread($fp, 8192);
if ($chunk === false || $chunk === '') {
$info = stream_get_meta_data($fp);
if (!empty($info['timed_out']) || !empty($info['eof'])) break;
continue;
}
$rawBody .= $chunk;
if (strpos($rawBody, "\r\n0\r\n\r\n") !== false
|| strpos($rawBody, "\r\n0\r\n") !== false) break;
}
} elseif (isset($parsed['content-length'])) {
$need = (int)$parsed['content-length'];
while (strlen($rawBody) < $need && !feof($fp)) {
$chunk = fread($fp, min(8192, $need - strlen($rawBody)));
if ($chunk === false || $chunk === '') {
$info = stream_get_meta_data($fp);
if (!empty($info['timed_out'])) break;
continue;
}
$rawBody .= $chunk;
}
} else {
while (!feof($fp)) {
$chunk = fread($fp, 8192);
if ($chunk === false || $chunk === '') {
$info = stream_get_meta_data($fp);
if (!empty($info['timed_out']) || !empty($info['eof'])) break;
continue;
}
$rawBody .= $chunk;
}
}
fclose($fp);
if ($isChunked) $rawBody = decodeChunked($rawBody);
if (isset($parsed['content-encoding'])) {
$list = array_map('trim', explode(',', strtolower($parsed['content-encoding'])));
foreach (array_reverse($list) as $e) {
if ($e === 'gzip' || $e === 'x-gzip') {
$d = @gzdecode($rawBody);
if ($d !== false) $rawBody = $d;
} elseif ($e === 'deflate') {
$d = @gzinflate($rawBody);
if ($d === false) $d = @gzuncompress($rawBody);
if ($d !== false) $rawBody = $d;
} elseif ($e === 'br') {
$d = decodeBrotli($rawBody);
if ($d !== null) $rawBody = $d;
}
}
}
return array(
'status' => $status,
'headers' => $parsed,
'set_cookies' => $setCookies,
'body' => $rawBody,
'raw' => $rawHead . "\r\n" . $rawBody,
);
}
/* ==================== ЗАПРОСЫ ==================== */
$url = 'https://tls.browserleaks.com/';
$results = array();
header_('ТЕСТИРОВАНИЕ ЗАПРОСОВ К ' . $url);
echo "\n";
echo c(' PHP версия: ', 'gray') . PHP_VERSION
. ' (' . (PHP_INT_SIZE === 8 ? '64-bit' : '32-bit') . ")\n";
echo c(' cURL: ', 'gray') . (function_exists('curl_init') ? c('доступен','green') : c('НЕДОСТУПЕН','red')) . "\n";
echo c(' OpenSSL: ', 'gray') . (extension_loaded('openssl') ? c('доступен','green') : c('НЕДОСТУПЕН','red')) . "\n";
echo c(' Brotli: ', 'gray') . (brotliAvailable() ? c('доступен','green') : c('нет (br не запрашиваем)','yellow')) . "\n";
echo c(' ALPN h2: ', 'gray') . (version_compare(PHP_VERSION, '5.6.0', '>=')
? c('возможен','green') : c('недоступен (нужен PHP >= 5.6)','yellow')) . "\n";
echo "\n";
// --- 1. file_get_contents ---
echo c(' [1/5] ', 'yellow', true) . 'file_get_contents...' . "\n";
try {
$ctx = stream_context_create(array(
'http' => array(
'method' => 'GET',
'header' => "User-Agent: PHP-file_get_contents-test\r\nAccept: application/json\r\n",
'timeout' => 20,
'ignore_errors' => true,
),
'ssl' => array('verify_peer' => true, 'verify_peer_name' => true),
));
$fgc = @file_get_contents($url, false, $ctx);
if ($fgc !== false && $fgc !== '') {
$decoded = json_decode($fgc, true);
$results['file_get_contents'] = array(
'success' => $decoded !== null,
'data' => $decoded,
'raw' => $fgc,
'error' => $decoded === null ? 'Не удалось разобрать JSON' : null,
);
} else {
$err = error_get_last();
$results['file_get_contents'] = array(
'success' => false,
'error' => isset($err['message']) ? $err['message'] : 'Пустой ответ',
);
}
} catch (Exception $e) {
$results['file_get_contents'] = array('success' => false, 'error' => $e->getMessage());
}
// --- 2. cURL ---
echo c(' [2/5] ', 'yellow', true) . 'cURL...' . "\n";
if (function_exists('curl_init')) {
try {
$ch = curl_init();
curl_setopt_array($ch, array(
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 20,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTPHEADER => array(
'User-Agent: PHP-cURL-test',
'Accept: application/json',
),
));
$curlResp = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curlErr = curl_error($ch);
curl_close($ch);
if ($curlResp !== false && $httpCode == 200) {
$decoded = json_decode($curlResp, true);
$results['curl'] = array(
'success' => $decoded !== null,
'data' => $decoded,
'raw' => $curlResp,
'http_code' => $httpCode,
'error' => $decoded === null ? 'Не удалось разобрать JSON' : null,
);
} else {
$results['curl'] = array(
'success' => false,
'error' => $curlErr ? $curlErr : ('HTTP ' . $httpCode),
'http_code' => $httpCode,
);
}
} catch (Exception $e) {
$results['curl'] = array('success' => false, 'error' => $e->getMessage());
}
} else {
$results['curl'] = array('success' => false, 'error' => 'Расширение cURL не установлено');
}
// --- 3. Socket HTTP/1.1 (basic) ---
echo c(' [3/5] ', 'yellow', true) . 'Socket HTTP/1.1 (basic)...' . "\n";
try {
$r = socketFetchHttp1($url, array(
'timeout' => 20,
'headers' => array(
'User-Agent' => 'PHP-socket-test',
'Accept' => 'application/json, text/plain, */*',
'Accept-Encoding' => 'gzip, deflate',
),
));
$decoded = json_decode($r['body'], true);
$results['socket_basic'] = array(
'success' => $decoded !== null,
'data' => $decoded,
'raw' => $r['raw'],
'status' => $r['status'],
'error' => $decoded === null
? 'HTTP ' . $r['status'] . ': ' . substr($r['body'], 0, 300) : null,
);
} catch (Exception $e) {
$results['socket_basic'] = array('success' => false, 'error' => $e->getMessage());
}
// --- 4. Socket HTTP/1.1 (Chrome UA) ---
echo c(' [4/5] ', 'yellow', true) . 'Socket HTTP/1.1 (Chrome UA)...' . "\n";
try {
$chromeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
. '(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36';
$acceptEncoding = 'gzip, deflate';
if (brotliAvailable()) $acceptEncoding .= ', br';
$r = socketFetchHttp1($url, array(
'timeout' => 20,
'headers' => array(
'User-Agent' => $chromeUA,
'sec-ch-ua' => '"Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"',
'sec-ch-ua-mobile' => '?0',
'sec-ch-ua-platform' => '"Windows"',
'Upgrade-Insecure-Requests' => '1',
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
'Sec-Fetch-Site' => 'none',
'Sec-Fetch-Mode' => 'navigate',
'Sec-Fetch-User' => '?1',
'Sec-Fetch-Dest' => 'document',
'Accept-Encoding' => $acceptEncoding,
'Accept-Language' => 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
),
));
$decoded = json_decode($r['body'], true);
$results['socket_chrome'] = array(
'success' => $decoded !== null,
'data' => $decoded,
'raw' => $r['raw'],
'status' => $r['status'],
'error' => $decoded === null
? 'HTTP ' . $r['status'] . ': ' . substr($r['body'], 0, 300) : null,
);
} catch (Exception $e) {
$results['socket_chrome'] = array('success' => false, 'error' => $e->getMessage());
}
// --- 5. Socket HTTP/2 (Chrome) ---
echo c(' [5/5] ', 'yellow', true) . 'Socket HTTP/2 (Chrome)...' . "\n";
if (version_compare(PHP_VERSION, '5.6.0', '>=') && PHP_INT_SIZE === 8) {
// На PHP 5.4 этого блока не будет — оставлен только "заглушка" ниже.
// Полная реализация HTTP/2 лежит в отдельном файле (browserleaks_h2.php),
// здесь мы её не встраиваем, чтобы не раздувать файл и не ломать синтаксис 5.4.
$results['socket_chrome_h2'] = array(
'success' => false,
'error' => 'Для HTTP/2-варианта используйте отдельный файл (PHP >= 5.6, 64-bit)',
);
} else {
$results['socket_chrome_h2'] = array(
'success' => false,
'error' => 'HTTP/2-вариант требует PHP >= 5.6 и 64-битную платформу',
);
}
/* ==================== СТАТУС ==================== */
echo "\n";
header_('СТАТУС ВЫПОЛНЕНИЯ');
echo "\n";
$successful = array();
$labels = array(
'file_get_contents' => 'file_get_contents',
'curl' => 'cURL',
'socket_basic' => 'Socket H1 basic',
'socket_chrome' => 'Socket H1 Chrome',
'socket_chrome_h2' => 'Socket H2 Chrome',
);
foreach ($results as $m => $r) {
$lbl = isset($labels[$m]) ? $labels[$m] : $m;
if (!empty($r['success'])) {
$successful[$m] = $r['data'];
printf(" %s %-22s %s\n",
c('OK', 'green', true),
c($lbl, 'white'),
c('УСПЕШНО', 'green', true)
);
} else {
printf(" %s %-22s %s %s\n",
c('FAIL', 'red', true),
c($lbl, 'white'),
c('ОШИБКА', 'red', true),
c('(' . (isset($r['error']) ? $r['error'] : 'unknown') . ')', 'gray')
);
}
}
if (empty($successful)) {
echo "\n" . c(' Ни один запрос не удался.', 'red', true) . "\n";
if (!$isCli) echo '</pre></body></html>';
exit(1);
}
/* ==================== АНАЛИЗ ==================== */
function getField($data, $field) {
if (isset($data[$field])) return $data[$field];
if (isset($data['tls'][$field])) return $data['tls'][$field];
return null;
}
$keyFields = array(
'user_agent' => 'User-Agent',
'ja4' => 'JA4',
'ja4_r' => 'JA4_r',
'ja4_o' => 'JA4_o (original)',
'ja4_ro' => 'JA4_ro (original)',
'ja3_hash' => 'JA3 Hash',
'ja3_text' => 'JA3 Text',
'ja3n_hash' => 'JA3N Hash',
'ja3n_text' => 'JA3N Text',
'akamai_hash' => 'Akamai Hash',
'akamai_text' => 'Akamai Text',
);
$tlsFields = array(
'cipher_suite', 'key_exchange', 'signature_scheme',
'selected_version', 'record_version', 'handshake_version',
);
echo "\n";
header_('СВОДНАЯ ТАБЛИЦА ОСНОВНЫХ ПОЛЕЙ');
echo "\n";
foreach ($keyFields as $field => $label) {
$values = array();
foreach ($successful as $m => $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(' ' . $label, 'cyan', true)
. c($isSame ? ' [совпадает]' : ' [различается]', $isSame ? 'green' : 'yellow') . "\n";
foreach ($values as $m => $v) {
echo ' ' . c(str_pad(isset($labels[$m]) ? $labels[$m] : $m, 22), 'gray')
. ' = ' . fmt($v) . "\n";
}
echo "\n";
}
foreach ($tlsFields as $tf) {
$values = array();
foreach ($successful as $m => $data) {
if (isset($data['tls'][$tf])) $values[$m] = $data['tls'][$tf];
}
if (empty($values)) continue;
$unique = array_unique($values, SORT_REGULAR);
$isSame = count($unique) === 1;
echo c(' tls.' . $tf, 'cyan', true)
. c($isSame ? ' [совпадает]' : ' [различается]', $isSame ? 'green' : 'yellow') . "\n";
foreach ($values as $m => $v) {
if (is_array($v)) {
$disp = isset($v['name'])
? $v['name'] . ' (' . (isset($v['id']) ? $v['id'] : '') . ')'
: json_encode($v, JSON_UNESCAPED_UNICODE);
} else {
$disp = $v;
}
echo ' ' . c(str_pad(isset($labels[$m]) ? $labels[$m] : $m, 22), 'gray')
. ' = ' . fmt($disp) . "\n";
}
echo "\n";
}
echo "\n";
header_('ОБЩИЕ ОТВЕТЫ (совпали у всех)');
echo "\n";
$commonFound = false;
foreach (array_merge(array_keys($keyFields), array_map(function($f){ return 'tls.' . $f; }, $tlsFields)) as $f) {
$vals = array();
foreach ($successful as $m => $d) {
if (strpos($f, 'tls.') === 0) {
$tf = substr($f, 4);
if (isset($d['tls'][$tf])) $vals[$m] = $d['tls'][$tf];
} else {
$v = getField($d, $f);
if ($v !== null) $vals[$m] = $v;
}
}
if (count($vals) === count($successful) && count(array_unique($vals, SORT_REGULAR)) === 1) {
$v = reset($vals);
if (is_array($v)) {
$disp = isset($v['name']) ? $v['name'] : json_encode($v, JSON_UNESCAPED_UNICODE);
} else {
$disp = $v;
}
echo ' ' . c($f, 'green', true) . ' = ' . fmt($disp) . "\n";
$commonFound = true;
}
}
if (!$commonFound) echo c(' Нет полностью совпадающих полей.', 'yellow') . "\n";
echo "\n";
header_('РАЗЛИЧИЯ (чем отличались запросы)');
echo "\n";
$diffFound = false;
foreach (array_merge(array_keys($keyFields), array_map(function($f){ return 'tls.' . $f; }, $tlsFields)) as $f) {
$vals = array();
foreach ($successful as $m => $d) {
if (strpos($f, 'tls.') === 0) {
$tf = substr($f, 4);
if (isset($d['tls'][$tf])) $vals[$m] = $d['tls'][$tf];
} else {
$v = getField($d, $f);
if ($v !== null) $vals[$m] = $v;
}
}
if (count($vals) > 1 && count(array_unique($vals, SORT_REGULAR)) > 1) {
echo ' ' . c($f, 'yellow', true) . "\n";
foreach ($vals as $m => $v) {
if (is_array($v)) {
$disp = isset($v['name'])
? $v['name'] . ' (' . (isset($v['id']) ? $v['id'] : '') . ')'
: json_encode($v, JSON_UNESCAPED_UNICODE);
} else {
$disp = $v;
}
echo ' ' . c(str_pad(isset($labels[$m]) ? $labels[$m] : $m, 22), 'gray')
. ' = ' . fmt($disp) . "\n";
}
$diffFound = true;
}
}
if (!$diffFound) echo c(' Нет различий по отслеживаемым полям.', 'yellow') . "\n";
/* ==================== ДЕТАЛЬНО ПО МЕТОДАМ ==================== */
echo "\n";
header_('ДЕТАЛЬНАЯ СВОДКА ПО КАЖДОМУ МЕТОДУ');
echo "\n";
foreach ($successful as $m => $d) {
$lbl = isset($labels[$m]) ? $labels[$m] : $m;
subheader_('Метод: ' . $lbl);
foreach ($keyFields as $f => $label) {
$v = getField($d, $f);
if ($v === null) continue;
echo ' ' . c(str_pad($label, 22), 'white', true) . ' = ' . fmt($v) . "\n";
}
foreach ($tlsFields as $tf) {
if (!isset($d['tls'][$tf])) continue;
$v = $d['tls'][$tf];
if (is_array($v)) {
$disp = isset($v['name'])
? $v['name'] . ' (' . (isset($v['id']) ? $v['id'] : '') . ')'
: json_encode($v, JSON_UNESCAPED_UNICODE);
} else {
$disp = $v;
}
echo ' ' . c(str_pad('tls.' . $tf, 22), 'white', true) . ' = ' . fmt($disp) . "\n";
}
echo "\n";
}
/* ==================== СОХРАНЕНИЕ ==================== */
echo "\n";
header_('СОХРАНЕНИЕ');
echo "\n";
$rawFile = dirname(__FILE__) . '/browserleaks_raw_responses.json';
$saved = @file_put_contents(
$rawFile,
json_encode($results, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
);
if ($saved !== false) {
echo ' Файл сохранён: ' . c($rawFile, 'yellow', true) . "\n";
echo ' Размер: ' . c(filesize($rawFile) . ' байт', 'yellow') . "\n";
} else {
echo c(' Не удалось сохранить файл (нет прав).', 'red') . "\n";
}
echo "\n";
line('=', 100, 'cyan');
echo c(' АНАЛИЗ ЗАВЕРШЁН', 'green', true) . "\n";
line('=', 100, 'cyan');
if (!$isCli) echo '</pre></body></html>';