diff --git a/app/filtering.js b/app/filtering.js index 5d9c6f20ca8..08c4067bafb 100644 --- a/app/filtering.js +++ b/app/filtering.js @@ -33,6 +33,7 @@ const {updateElectronDownloadItem} = require('./browser/electronDownloadItem') const {fullscreenOption} = require('./common/constants/settingsEnums') const isThirdPartyHost = require('./browser/isThirdPartyHost') var extensionState = require('./common/state/extensionState.js') +const {cookieExceptions, refererExceptions} = require('../js/data/siteHacks') let appStore = null @@ -46,9 +47,6 @@ let initializedPartitions = {} const transparent1pxGif = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7' const pdfjsOrigin = `chrome-extension://${config.PDFJSExtensionId}` -// Third party domains that require a valid referer to work -const refererExceptions = ['use.typekit.net', 'cloud.typography.com', 'www.moremorewin.net'] - /** * Maps partition name to the session object */ @@ -220,6 +218,47 @@ function registerForBeforeRedirect (session, partition) { }) } +module.exports.applyCookieSetting = (requestHeaders, url, firstPartyUrl, isPrivate) => { + const cookieSetting = module.exports.isResourceEnabled(appConfig.resourceNames.COOKIEBLOCK, firstPartyUrl, isPrivate) + if (cookieSetting) { + const parsedTargetUrl = urlParse(url || '') + const parsedFirstPartyUrl = urlParse(firstPartyUrl) + + if (cookieSetting === 'blockAllCookies' || + isThirdPartyHost(parsedFirstPartyUrl.hostname, parsedTargetUrl.hostname)) { + let hasCookieException = false + const firstPartyOrigin = getOrigin(firstPartyUrl) + const targetOrigin = getOrigin(url) + if (cookieExceptions.hasOwnProperty(firstPartyOrigin)) { + const subResources = cookieExceptions[firstPartyOrigin] + for (let i = 0; i < subResources.length; ++i) { + if (subResources[i] === targetOrigin) { + hasCookieException = true + break + } else if (subResources[i].includes('*')) { + const regSubResource = new RegExp(subResources[i].replace('//', '\\/\\/').replace('*', '.*'), 'g') + if (targetOrigin.match(regSubResource)) { + hasCookieException = true + break + } + } + } + } + // Clear cookie and referer on third-party requests + if (requestHeaders['Cookie'] && + firstPartyOrigin !== pdfjsOrigin && !hasCookieException) { + requestHeaders['Cookie'] = undefined + } + if (requestHeaders['Referer'] && + !refererExceptions.includes(parsedTargetUrl.hostname)) { + requestHeaders['Referer'] = targetOrigin + } + } + } + + return requestHeaders +} + /** * Register for notifications for webRequest.onBeforeSendHeaders for * a particular session. @@ -266,25 +305,8 @@ function registerForBeforeSendHeaders (session, partition) { } } - const cookieSetting = module.exports.isResourceEnabled(appConfig.resourceNames.COOKIEBLOCK, firstPartyUrl, isPrivate) - if (cookieSetting) { - const parsedTargetUrl = urlParse(details.url || '') - const parsedFirstPartyUrl = urlParse(firstPartyUrl) + requestHeaders = module.exports.applyCookieSetting(requestHeaders, details.url, firstPartyUrl, isPrivate) - if (cookieSetting === 'blockAllCookies' || - isThirdPartyHost(parsedFirstPartyUrl.hostname, parsedTargetUrl.hostname)) { - // Clear cookie and referer on third-party requests - if (requestHeaders['Cookie'] && - getOrigin(firstPartyUrl) !== pdfjsOrigin) { - requestHeaders['Cookie'] = undefined - } - if (cookieSetting !== 'blockAllCookies' && - requestHeaders['Referer'] && - !refererExceptions.includes(parsedTargetUrl.hostname)) { - requestHeaders['Referer'] = getOrigin(details.url) - } - } - } if (sendDNT) { requestHeaders['DNT'] = '1' } diff --git a/js/data/siteHacks.js b/js/data/siteHacks.js index a14e169b40d..f78cfe7e698 100644 --- a/js/data/siteHacks.js +++ b/js/data/siteHacks.js @@ -22,13 +22,17 @@ const emptyDataURI = { } /** - * Holds an array of [Primary URL, subresource URL] to allow 3rd party cookies. + * Holds an map of {Primary URL: subresource URL} to allow 3rd party cookies. * Subresource URL can be '*' or undefined to indicate all. */ -module.exports.cookieExceptions = [ - ['https://inbox.google.com', 'https://hangouts.google.com'], - ['https://mail.google.com', 'https://hangouts.google.com'] -] +module.exports.cookieExceptions = { + 'https://inbox.google.com': ['https://hangouts.google.com'], + 'https://mail.google.com': ['https://hangouts.google.com'], + 'https://drive.google.com': ['https://doc-*-docs.googleusercontent.com'] +} + +// Third party domains that require a valid referer to work +module.exports.refererExceptions = ['use.typekit.net', 'cloud.typography.com', 'www.moremorewin.net'] /** * Holds an array of [Primary URL, subresource URL] to allow 3rd party localstorage. diff --git a/js/state/contentSettings.js b/js/state/contentSettings.js index 21c54f36029..0c94ff98f53 100644 --- a/js/state/contentSettings.js +++ b/js/state/contentSettings.js @@ -263,9 +263,12 @@ const siteSettingsToContentSettings = (currentSiteSettings, defaultContentSettin contentSettings = addContentSettings(contentSettings, 'cookies', primaryPattern, '*', 'block') contentSettings = addContentSettings(contentSettings, 'cookies', primaryPattern, primaryPattern, 'allow') contentSettings = addContentSettings(contentSettings, 'referer', primaryPattern, '*', 'block') - cookieExceptions.forEach((exceptionPair) => { - contentSettings = addContentSettings(contentSettings, 'cookies', exceptionPair[0], exceptionPair[1], 'allow') - }) + for (let key in cookieExceptions) { + const subResources = cookieExceptions[key] + for (let i = 0; i < subResources.length; ++i) { + contentSettings = addContentSettings(contentSettings, 'cookies', key, subResources[i], 'allow') + } + } } else if (siteSetting.get('cookieControl') === 'blockAllCookies') { contentSettings = addContentSettings(contentSettings, 'cookies', primaryPattern, '*', 'block') contentSettings = addContentSettings(contentSettings, 'referer', primaryPattern, '*', 'block') diff --git a/test/unit/app/filteringTest.js b/test/unit/app/filteringTest.js new file mode 100644 index 00000000000..961ff19a174 --- /dev/null +++ b/test/unit/app/filteringTest.js @@ -0,0 +1,120 @@ +/* global describe, before, after, it */ +const mockery = require('mockery') +const assert = require('assert') +const sinon = require('sinon') +const {cookieExceptions, refererExceptions} = require('../../../js/data/siteHacks') + +require('../braveUnit') + +describe('filtering unit tests', function () { + let filtering + const fakeElectron = require('../lib/fakeElectron') + + before(function () { + mockery.enable({ + warnOnReplace: false, + warnOnUnregistered: false, + useCleanCache: true + }) + mockery.registerMock('electron', fakeElectron) + mockery.registerMock('./adBlock', {adBlockResourceName: 'adblock'}) + filtering = require('../../../app/filtering') + }) + + after(function () { + mockery.disable() + }) + + describe('applyCookieSetting', function () { + describe('when cookieSetting === "blockAllCookies"', function () { + let isResourceEnabledStub + before(function () { + isResourceEnabledStub = sinon.stub(filtering, 'isResourceEnabled').returns('blockAllCookies') + }) + after(function () { + isResourceEnabledStub.restore() + }) + + it('clears cookie field', function () { + const url = 'https://cdnp3.stackassets.com/574db2390a12942fcef927356dadc6f9955edea9/store/fe3eb8fc014a20f2d25810b3c4f4b5b0db8695adfd7e8953721a55c51b90/sale_7217_primary_image.jpg' + const firstPartyUrl = 'https://slashdot.org/' + const requestHeaders = { + Cookie: 'optimizelyEndUserId=oeu1491721215718r0.024789086462633003; __ssid=97b17d31-8f1b-4193-8914-df36e7b740f6; optimizelySegments=%7B%22300150879%22%3A%22false%22%2C%22300333436%22%3A%22gc%22%2C%22300387578%22%3A%22campaign%22%7D; optimizelyBuckets=%7B%7D; _pk_id.40.2105=8fca10ea565f58bf.1485982886.187.1499406000.1499405260.; _pk_ses.40.2105=*' + } + const result = filtering.applyCookieSetting(requestHeaders, url, firstPartyUrl, false) + + assert.equal(result.Cookie, undefined) + }) + + describe('when there is a cookie exception', function () { + it('keeps the cookie field', function () { + let cookieException = false + let firstPartyUrl = '' + let url = '' + for (let key in cookieExceptions) { + firstPartyUrl = key + const urls = cookieExceptions[key] + url = urls[0] + cookieException = true + break + } + + assert(cookieException) + + const requestHeaders = { + Cookie: 'optimizelyEndUserId=oeu1491721215718r0.024789086462633003; __ssid=97b17d31-8f1b-4193-8914-df36e7b740f6; optimizelySegments=%7B%22300150879%22%3A%22false%22%2C%22300333436%22%3A%22gc%22%2C%22300387578%22%3A%22campaign%22%7D; optimizelyBuckets=%7B%7D; _pk_id.40.2105=8fca10ea565f58bf.1485982886.187.1499406000.1499405260.; _pk_ses.40.2105=*' + } + const result = filtering.applyCookieSetting(requestHeaders, url, firstPartyUrl, false) + + assert.equal(result.Cookie, requestHeaders.Cookie) + }) + it('wildcard cookie exception', function () { + // Specifically testing drive.google.com + const firstPartyUrl = 'https://drive.google.com' + const url = 'https://doc-0g-3g-docs.googleusercontent.com' + + const requestHeaders = { + Cookie: 'optimizelyEndUserId=oeu1491721215718r0.024789086462633003; __ssid=97b17d31-8f1b-4193-8914-df36e7b740f6; optimizelySegments=%7B%22300150879%22%3A%22false%22%2C%22300333436%22%3A%22gc%22%2C%22300387578%22%3A%22campaign%22%7D; optimizelyBuckets=%7B%7D; _pk_id.40.2105=8fca10ea565f58bf.1485982886.187.1499406000.1499405260.; _pk_ses.40.2105=*' + } + const result = filtering.applyCookieSetting(requestHeaders, url, firstPartyUrl, false) + + assert.equal(result.Cookie, requestHeaders.Cookie) + }) + }) + }) + + describe('when cookieSetting === "block3rdPartyCookie"', function () { + let isResourceEnabledStub + before(function () { + isResourceEnabledStub = sinon.stub(filtering, 'isResourceEnabled').returns('block3rdPartyCookie') + }) + after(function () { + isResourceEnabledStub.restore() + }) + + it('sets the referer to the origin', function () { + const url = 'https://cdnp3.stackassets.com/574db2390a12942fcef927356dadc6f9955edea9/store/fe3eb8fc014a20f2d25810b3c4f4b5b0db8695adfd7e8953721a55c51b90/sale_7217_primary_image.jpg' + const firstPartyUrl = 'https://slashdot.org/' + const requestHeaders = { + Referer: 'https://brave.com' + } + const result = filtering.applyCookieSetting(requestHeaders, url, firstPartyUrl, false) + + assert.equal(result.Referer, 'https://cdnp3.stackassets.com') + }) + + describe('when there is a referer exception', function () { + it('keeps the referer field', function () { + const url = 'https://' + refererExceptions[0] + const firstPartyUrl = 'https://slashdot.org/' + const requestHeaders = { + Referer: 'https://brave.com' + } + const result = filtering.applyCookieSetting(requestHeaders, url, firstPartyUrl, false) + + assert.equal(result.Referer, requestHeaders.Referer) + }) + }) + }) + }) +})