diff --git a/shell/browser/api/electron_api_cookies.cc b/shell/browser/api/electron_api_cookies.cc index a7296bde7de3..f84f0c49fd89 100644 --- a/shell/browser/api/electron_api_cookies.cc +++ b/shell/browser/api/electron_api_cookies.cc @@ -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 secure_filter = filter.FindBool("secure"); if (secure_filter && *secure_filter != cookie.SecureAttribute()) diff --git a/spec/api-session-spec.ts b/spec/api-session-spec.ts index afa34738ebd3..49b24a78f79e 100644 --- a/spec/api-session-spec.ts +++ b/spec/api-session-spec.ts @@ -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 () => {