EV-WebKit
view release on metacpan or search on metacpan
t/98-curl-target.t view on Meta::CPAN
use v5.10; use strict; use warnings;
use Test::More;
use EV::WebKit::Fingerprint;
is(EV::WebKit::Fingerprint::curl_target('windows-chrome'), 'chrome150', 'windows-chrome -> chrome150');
is(EV::WebKit::Fingerprint::curl_target('windows-firefox'), 'firefox147', 'windows-firefox -> firefox147');
is(EV::WebKit::Fingerprint::curl_target('macos-safari'), 'safari26_0', 'macos-safari -> safari26_0');
is(EV::WebKit::Fingerprint::curl_target('iphone-safari'), 'safari26_0_ios', 'iphone-safari -> safari26_0_ios');
is(EV::WebKit::Fingerprint::curl_target('pixel-chrome'), 'chrome131_android','pixel-chrome -> chrome131_android');
is(EV::WebKit::Fingerprint::curl_target('nope'), undef, 'unknown profile -> undef');
is(EV::WebKit::Fingerprint::curl_target(undef), undef, 'undef profile -> undef');
# every preset has a curl target (coherence)
ok(EV::WebKit::Fingerprint::curl_target($_), "preset $_ maps to a target")
for EV::WebKit::Fingerprint::profiles();
# refreshed versions
my $wc = EV::WebKit::Fingerprint::resolve('windows-chrome');
like($wc->{user_agent}, qr{Chrome/150\.}, 'windows-chrome UA is Chrome 150');
like($wc->{user_agent}, qr{Windows NT}, 'windows-chrome stays Windows');
like($wc->{ua_data}{uaFullVersion}, qr{^150\.}, 'windows-chrome uaFullVersion is 150');
my $ms = EV::WebKit::Fingerprint::resolve('macos-safari');
like($ms->{user_agent}, qr{Version/26\.}, 'macos-safari UA is Safari 26');
my $is = EV::WebKit::Fingerprint::resolve('iphone-safari');
like($is->{user_agent}, qr{Version/26\.}, 'iphone-safari UA is Safari 26');
like($is->{user_agent}, qr{iPhone OS 26}, 'iphone-safari is iOS 26');
my $pc = EV::WebKit::Fingerprint::resolve('pixel-chrome');
like($pc->{user_agent}, qr{Chrome/131\.}, 'pixel-chrome UA is Chrome 131');
like($pc->{ua_data}{uaFullVersion}, qr{^131\.}, 'pixel-chrome uaFullVersion is 131');
like($pc->{user_agent}, qr{Android 10; K\)}, 'pixel-chrome UA is the REDUCED Chrome 131 Android form (Android 10; K)');
unlike($pc->{user_agent}, qr{Pixel 8|Android 14}, 'the raw device/OS version is NOT in the UA string (UA reduction)');
is($pc->{ua_data}{model}, 'Pixel 8', 'the real device rides in ua_data.model -> Sec-CH-UA-Model');
# --- identity headers: UA + Chrome-format Accept-Language (+ low-entropy hints) ---
{
my $id = EV::WebKit::Fingerprint::identity_headers($wc);
like($id->{'user-agent'}, qr{Chrome/150}, 'identity_headers carries the UA');
is($id->{'accept-language'}, 'en-US,en;q=0.9',
'identity Accept-Language is Chrome format (en-US,en;q=0.9), not libsoup-flavored');
# Both the order and the GREASE brand itself are per-release, taken from
# curl-impersonate's chrome150 template rather than assumed: 131 put
# "Not_A Brand";v="24" last, 150 puts "Not;A=Brand";v="8" first. What must
# hold is that this header and navigator.userAgentData come from one list.
is($id->{'sec-ch-ua'}, '"Not;A=Brand";v="8", "Chromium";v="150", "Google Chrome";v="150"',
'sec-ch-ua is the chrome150 template brand list, in its order');
is($id->{'sec-ch-ua-mobile'}, '?0', 'sec-ch-ua-mobile ?0 for a desktop profile');
# a Safari profile has no ua_data: UA + Accept-Language, but no sec-ch-ua
my $sid = EV::WebKit::Fingerprint::identity_headers($ms);
is($sid->{'accept-language'}, 'en-US,en;q=0.9', 'Safari identity still carries Chrome-format Accept-Language');
ok(!exists $sid->{'sec-ch-ua'}, 'Safari identity has no sec-ch-ua (no ua_data)');
# the q-weight sequence for 3+ languages: 0.9, 0.8, ...
my $three = EV::WebKit::Fingerprint::resolve({ profile => 'windows-chrome', languages => ['de-DE','de','en'] });
is(EV::WebKit::Fingerprint::identity_headers($three)->{'accept-language'},
'de-DE,de;q=0.9,en;q=0.8', 'Accept-Language q-weights decrement (0.9, 0.8) for three languages');
}
# --- high-entropy hints must match what JS getHighEntropyValues() reports, or an
# Accept-CH'ing origin that cross-checks the two channels sees a contradiction ---
{
my $he = EV::WebKit::Fingerprint::high_entropy_headers($wc);
is($he->{'sec-ch-ua-wow64'}, '?0',
'high-entropy carries Sec-CH-UA-WoW64: ?0 (matches JS wow64:false)');
is($he->{'sec-ch-ua-full-version'}, '"150.0.7871.189"',
'high-entropy carries the deprecated singular Sec-CH-UA-Full-Version (JS exposes uaFullVersion)');
is($he->{'sec-ch-ua-full-version-list'},
'"Not;A=Brand";v="8.0.0.0", "Chromium";v="150.0.7871.189", "Google Chrome";v="150.0.7871.189"',
'Sec-CH-UA-Full-Version-List uses the same brand list and order');
is($he->{'sec-ch-ua-platform-version'}, '"10.0.0"', 'and Sec-CH-UA-Platform-Version');
# Safari profiles (no ua_data) expose no client hints on either layer
is_deeply(EV::WebKit::Fingerprint::high_entropy_headers($ms), {},
'a Safari profile has no high-entropy hints (no ua_data)');
}
done_testing;
( run in 1.040 second using v1.01-cache-2.11-cpan-b16cb0d3907 )