Skip to content

Commit c3c842c

Browse files
authored
fix(network): Include subdomains of localhost when including cookies (#35771)
1 parent c396674 commit c3c842c

File tree

3 files changed

+22
-4
lines changed

3 files changed

+22
-4
lines changed

packages/playwright-core/src/server/cookieStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
import { kMaxCookieExpiresDateInSeconds } from './network';
17+
import { isLocalHostname, kMaxCookieExpiresDateInSeconds } from './network';
1818

1919
import type * as channels from '@protocol/channels';
2020

@@ -30,7 +30,7 @@ export class Cookie {
3030

3131
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.4
3232
matches(url: URL): boolean {
33-
if (this._raw.secure && (url.protocol !== 'https:' && url.hostname !== 'localhost'))
33+
if (this._raw.secure && (url.protocol !== 'https:' && !isLocalHostname(url.hostname)))
3434
return false;
3535
if (!domainMatches(url.hostname, this._raw.domain))
3636
return false;

packages/playwright-core/src/server/network.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,18 @@ export function filterCookies(cookies: channels.NetworkCookie[], urls: string[])
4343
continue;
4444
if (!parsedURL.pathname.startsWith(c.path))
4545
continue;
46-
if (parsedURL.protocol !== 'https:' && parsedURL.hostname !== 'localhost' && c.secure)
46+
if (parsedURL.protocol !== 'https:' && !isLocalHostname(parsedURL.hostname) && c.secure)
4747
continue;
4848
return true;
4949
}
5050
return false;
5151
});
5252
}
5353

54+
export function isLocalHostname(hostname: string): boolean {
55+
return hostname === 'localhost' || hostname.endsWith('.localhost');
56+
}
57+
5458
// Rollover to 5-digit year:
5559
// 253402300799 == Fri, 31 Dec 9999 23:59:59 +0000 (UTC)
5660
// 253402300800 == Sat, 1 Jan 1000 00:00:00 +0000 (UTC)

tests/library/global-fetch-cookie.spec.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ type StorageStateType = PromiseArg<ReturnType<APIRequestContext['storageState']>
3737
it.skip(({ mode }) => mode !== 'default');
3838

3939
const __testHookLookup = (hostname: string): LookupAddress[] => {
40-
if (hostname === 'localhost' || hostname.endsWith('one.com') || hostname.endsWith('two.com'))
40+
if (hostname.endsWith('localhost') || hostname.endsWith('one.com') || hostname.endsWith('two.com'))
4141
return [{ address: '127.0.0.1', family: 4 }];
4242
else
4343
throw new Error(`Failed to resolve hostname: ${hostname}`);
@@ -140,6 +140,20 @@ it('should send secure cookie over http for localhost', async ({ request, server
140140
expect(serverRequest.headers.cookie).toBe('a=v; b=v');
141141
});
142142

143+
it('should send secure cookie over http for subdomains of localhost', async ({ request, server }) => {
144+
server.setRoute('/setcookie.html', (req, res) => {
145+
res.setHeader('Set-Cookie', ['a=v; secure', 'b=v']);
146+
res.end();
147+
});
148+
const prefix = `http://a.b.localhost:${server.PORT}`;
149+
await request.get(`${prefix}/setcookie.html`);
150+
const [serverRequest] = await Promise.all([
151+
server.waitForRequest('/empty.html'),
152+
request.get(`${prefix}/empty.html`)
153+
]);
154+
expect(serverRequest.headers.cookie).toBe('a=v; b=v');
155+
});
156+
143157
it('should send not expired cookies', async ({ request, server }) => {
144158
server.setRoute('/setcookie.html', (req, res) => {
145159
const tomorrow = new Date();

0 commit comments

Comments
 (0)