Skip to content

Commit e3d33b8

Browse files
committed
added cookie fallback to Request::isFrom() for browsers without Sec-Fetch (Safari < 16.4)
1 parent c6fd384 commit e3d33b8

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

src/Http/Request.php

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
namespace Nette\Http;
99

1010
use Nette;
11-
use function array_change_key_case, base64_decode, count, explode, func_num_args, in_array, is_array, preg_match, strcasecmp, strlen, strtr;
11+
use function array_change_key_case, array_filter, base64_decode, count, explode, func_num_args, in_array, is_array, preg_match, strcasecmp, strlen, strtr;
1212

1313

1414
/**
@@ -240,6 +240,7 @@ public function isSameSite(): bool
240240

241241
/**
242242
* Checks whether the request matches the given Sec-Fetch-Site, Sec-Fetch-Dest and Sec-Fetch-User values.
243+
* Falls back to the SameSite=Strict cookie in browsers without Sec-Fetch (Safari < 16.4)
243244
* @param FetchSite|list<FetchSite> $site
244245
* @param FetchDest|list<FetchDest>|null $dest
245246
*/
@@ -249,12 +250,20 @@ public function isFrom(
249250
?bool $user = null,
250251
): bool
251252
{
252-
$actualSite = FetchSite::tryFrom($this->headers['sec-fetch-site'] ?? '');
253+
$siteHeader = $this->headers['sec-fetch-site'] ?? null;
253254
$actualDest = FetchDest::tryFrom($this->headers['sec-fetch-dest'] ?? '');
254255
$actualUser = ($this->headers['sec-fetch-user'] ?? null) === '?1';
255256
$site = is_array($site) ? $site : [$site];
256257
$dest = $dest === null || is_array($dest) ? $dest : [$dest];
257258

259+
if ($siteHeader === null) { // fallback for browsers without Sec-Fetch (Safari < 16.4)
260+
return $dest === null
261+
&& $user === null
262+
&& isset($this->cookies[Helpers::StrictCookieName])
263+
&& array_filter($site, fn(FetchSite $s) => $s !== FetchSite::CrossSite) !== [];
264+
}
265+
266+
$actualSite = FetchSite::tryFrom($siteHeader);
258267
return $actualSite !== null
259268
&& in_array($actualSite, $site, strict: true)
260269
&& ($dest === null || ($actualDest !== null && in_array($actualDest, $dest, strict: true)))

tests/Http/Request.isFrom.phpt

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,46 @@ test('unknown header value matches nothing', function () {
8080
});
8181

8282

83-
test('no Sec-Fetch-Site returns false', function () {
83+
test('no header, no cookie returns false', function () {
8484
$request = new Http\Request(new Http\UrlScript);
8585

8686
Assert::false($request->isFrom(FetchSite::SameOrigin));
8787
Assert::false($request->isFrom(FetchSite::CrossSite));
8888
});
89+
90+
91+
test('cookie fallback proves only "not cross-site"', function () {
92+
$request = new Http\Request(new Http\UrlScript, cookies: [
93+
Http\Helpers::StrictCookieName => '1',
94+
]);
95+
96+
Assert::true($request->isFrom(FetchSite::SameOrigin));
97+
Assert::true($request->isFrom(FetchSite::SameSite));
98+
Assert::true($request->isFrom(FetchSite::None));
99+
Assert::true($request->isFrom([FetchSite::SameOrigin, FetchSite::CrossSite]));
100+
Assert::false($request->isFrom(FetchSite::CrossSite));
101+
});
102+
103+
104+
test('cookie fallback fails closed for dest & user', function () {
105+
$request = new Http\Request(new Http\UrlScript, cookies: [
106+
Http\Helpers::StrictCookieName => '1',
107+
]);
108+
109+
// dest/user can't be proven by the cookie alone, so a stricter check must not pass
110+
Assert::false($request->isFrom(FetchSite::SameOrigin, FetchDest::Document));
111+
Assert::false($request->isFrom(FetchSite::SameOrigin, user: true));
112+
Assert::false($request->isFrom(FetchSite::SameOrigin, user: false));
113+
});
114+
115+
116+
test('cookie fallback not used when Sec-Fetch-Site present', function () {
117+
$request = new Http\Request(new Http\UrlScript, cookies: [
118+
Http\Helpers::StrictCookieName => '1',
119+
], headers: [
120+
'Sec-Fetch-Site' => 'cross-site',
121+
]);
122+
123+
Assert::false($request->isFrom(FetchSite::SameOrigin));
124+
Assert::true($request->isFrom(FetchSite::CrossSite));
125+
});

0 commit comments

Comments
 (0)