refactor: use net::CanonicalCookie::IsDomainMatch() (#46748)

* refactor: use net::CanonicalCookie::IsDomainMatch()

Previously we had been rolling our own impl

* test: add pattern-matching tests for our cookie API
This commit is contained in:
Charles Kerr 2025-04-25 09:08:16 -05:00 committed by GitHub
parent 06a99d6770
commit 74c4ae0b55
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 160 additions and 23 deletions

View file

@ -102,27 +102,6 @@ namespace electron::api {
namespace {
// Returns whether |domain| matches |filter|.
bool MatchesDomain(std::string filter, const std::string& domain) {
// Add a leading '.' character to the filter domain if it doesn't exist.
if (net::cookie_util::DomainIsHostOnly(filter))
filter.insert(0, ".");
std::string sub_domain(domain);
// Strip any leading '.' character from the input cookie domain.
if (!net::cookie_util::DomainIsHostOnly(sub_domain))
sub_domain = sub_domain.substr(1);
// Now check whether the domain argument is a subdomain of the filter domain.
for (sub_domain.insert(0, "."); sub_domain.length() >= filter.length();) {
if (sub_domain == filter)
return true;
const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot.
sub_domain.erase(0, next_dot);
}
return false;
}
// Returns whether |cookie| matches |filter|.
bool MatchesCookie(const base::Value::Dict& filter,
const net::CanonicalCookie& cookie) {
@ -131,8 +110,7 @@ bool MatchesCookie(const base::Value::Dict& filter,
return false;
if ((str = filter.FindString("path")) && *str != cookie.Path())
return false;
if ((str = filter.FindString("domain")) &&
!MatchesDomain(*str, cookie.Domain()))
if ((str = filter.FindString("domain")) && !cookie.IsDomainMatch(*str))
return false;
std::optional<bool> secure_filter = filter.FindBool("secure");
if (secure_filter && *secure_filter != cookie.SecureAttribute())

View file

@ -260,6 +260,165 @@ describe('session module', () => {
});
});
describe('domain matching', () => {
let testSession: Electron.Session;
beforeEach(() => {
testSession = session.fromPartition(`cookies-domain-test-${Date.now()}`);
});
afterEach(async () => {
// Clear cookies after each test
await testSession.clearStorageData({ storages: ['cookies'] });
});
// Helper to set a cookie and then test if it's retrieved with a domain filter
async function testDomainMatching (setCookieOpts: Electron.CookiesSetDetails,
domain: string,
expectMatch: boolean) {
await testSession.cookies.set(setCookieOpts);
const cookies = await testSession.cookies.get({ domain });
if (expectMatch) {
expect(cookies).to.have.lengthOf(1);
expect(cookies[0].name).to.equal(setCookieOpts.name);
expect(cookies[0].value).to.equal(setCookieOpts.value);
} else {
expect(cookies).to.have.lengthOf(0);
}
}
it('should match exact domain', async () => {
await testDomainMatching({
url: 'http://example.com',
name: 'exactMatch',
value: 'value1',
domain: 'example.com'
}, 'example.com', true);
});
it('should match subdomain when filter has leading dot', async () => {
await testDomainMatching({
url: 'http://sub.example.com',
name: 'subdomainMatch',
value: 'value2',
domain: '.example.com'
}, 'sub.example.com', true);
});
it('should match subdomain when filter has no leading dot (host-only normalization)', async () => {
await testDomainMatching({
url: 'http://sub.example.com',
name: 'hostOnlyNormalization',
value: 'value3',
domain: 'example.com'
}, 'sub.example.com', true);
});
it('should not match unrelated domain', async () => {
await testDomainMatching({
url: 'http://example.com',
name: 'noMatch',
value: 'value4',
domain: 'example.com'
}, 'other.com', false);
});
it('should match domain with a leading dot in both cookie and filter', async () => {
await testDomainMatching({
url: 'http://example.com',
name: 'leadingDotBoth',
value: 'value5',
domain: '.example.com'
}, '.example.com', true);
});
it('should handle case insensitivity in domain', async () => {
await testDomainMatching({
url: 'http://example.com',
name: 'caseInsensitive',
value: 'value7',
domain: 'Example.com'
}, 'example.com', true);
});
it('should handle IP address matching', async () => {
await testDomainMatching({
url: 'http://127.0.0.1',
name: 'ipExactMatch',
value: 'value8',
domain: '127.0.0.1'
}, '127.0.0.1', true);
});
it('should not match different IP addresses', async () => {
await testDomainMatching({
url: 'http://127.0.0.1',
name: 'ipMismatch',
value: 'value9',
domain: '127.0.0.1'
}, '127.0.0.2', false);
});
it('should handle complex subdomain matching properly', async () => {
// Set a cookie with domain .example.com
await testSession.cookies.set({
url: 'http://a.b.example.com',
name: 'complexSubdomain',
value: 'value11',
domain: '.example.com'
});
// This should match the cookie
const cookies1 = await testSession.cookies.get({ domain: 'a.b.example.com' });
expect(cookies1).to.have.lengthOf(1);
expect(cookies1[0].name).to.equal('complexSubdomain');
// This should also match
const cookies2 = await testSession.cookies.get({ domain: 'b.example.com' });
expect(cookies2).to.have.lengthOf(1);
// This should also match
const cookies3 = await testSession.cookies.get({ domain: 'example.com' });
expect(cookies3).to.have.lengthOf(1);
// This should not match
const cookies4 = await testSession.cookies.get({ domain: 'otherexample.com' });
expect(cookies4).to.have.lengthOf(0);
});
it('should handle multiple cookies with different domains', async () => {
// Set two cookies with different domains
await testSession.cookies.set({
url: 'http://example.com',
name: 'cookie1',
value: 'domain1',
domain: 'example.com'
});
await testSession.cookies.set({
url: 'http://other.com',
name: 'cookie2',
value: 'domain2',
domain: 'other.com'
});
// Filter for the first domain
const cookies1 = await testSession.cookies.get({ domain: 'example.com' });
expect(cookies1).to.have.lengthOf(1);
expect(cookies1[0].name).to.equal('cookie1');
// Filter for the second domain
const cookies2 = await testSession.cookies.get({ domain: 'other.com' });
expect(cookies2).to.have.lengthOf(1);
expect(cookies2[0].name).to.equal('cookie2');
// Get all cookies
const allCookies = await testSession.cookies.get({});
expect(allCookies).to.have.lengthOf(2);
});
});
describe('ses.clearStorageData(options)', () => {
afterEach(closeAllWindows);
it('clears localstorage data', async () => {