diff --git a/src/js/reasons/headers.js b/src/js/reasons/headers.js index b565c94..0c2b3ef 100644 --- a/src/js/reasons/headers.js +++ b/src/js/reasons/headers.js @@ -4,18 +4,20 @@ const {Action} = require('../schemes'), {URL} = require('../shim'), - {LruMap, hasAction} = require('../utils'), + {hasAction} = require('../utils'), {newEtagHeaderFunc} = require('./etag'), + {Referer} = require('./referer'), {HEADER_DEACTIVATE_ON_HOST, header_methods, NO_ACTION, TAB_DEACTIVATE_HEADERS} = require('../constants'); const alwaysTrue = () => true; class HeaderHandler { constructor(store) { + this.referer = new Referer(); this.badHeaders = new Map([ ['cookie', alwaysTrue], ['set-cookie', alwaysTrue], - ['referer', alwaysTrue], + ['referer', this.referer.shouldRemoveHeader.bind(this.referer)], ['etag', newEtagHeaderFunc(store)], ['if-none-match', alwaysTrue] ]); diff --git a/src/js/reasons/referer.js b/src/js/reasons/referer.js new file mode 100644 index 0000000..a33616c --- /dev/null +++ b/src/js/reasons/referer.js @@ -0,0 +1,49 @@ +"use strict"; + +[(function(exports) { + +const {LruMap, log} = require('../utils'); + +function is4xx(statusCode) { + return (400 <= statusCode) && (statusCode < 500); +} + +class Referer { + constructor() { + this.requestIdCache = new LruMap(1000); + this.badRedirects = new LruMap(1000); + } + + removeRefererFailedOnce({statusCode, requestId}) { + return (is4xx(statusCode) && this.requestIdCache.has(requestId)) && !this.badRedirects.has(requestId); + } + + failedAlready({requestId}) { + return this.badRedirects.has(requestId); + } + + shouldRemoveHeader(details, header) { + if (!this.requestIdCache.has(details.requestId)) { + this.requestIdCache.set(details.requestId, header.value); + } + + if (this.failedAlready(details)) { + log('failed referer already'); + return false; + } + return true; + } + + onHeadersReceived(details) { + if (this.removeRefererFailedOnce(details)) { + this.badRedirects.set(details.requestId); + log(`failed referer removal, redirecting ${details.url}`); + details.shortCircuit = true; + return details.response = {redirectUrl: details.url}; + } + } +} + +Object.assign(exports, {Referer}); + +})].map(func => typeof exports == 'undefined' ? define('/reasons/referer', func) : func(exports)); diff --git a/src/js/test/reasons/referer_test.js b/src/js/test/reasons/referer_test.js new file mode 100644 index 0000000..076f406 --- /dev/null +++ b/src/js/test/reasons/referer_test.js @@ -0,0 +1,44 @@ +"use strict"; + +const {assert} = require('chai'), + {Referer} = require('../../reasons/referer'); + +describe('referer.js', function() { + let requestId = 1, + url = 'https://whatever.com/nope'; + beforeEach(function() { + this.details = {requestId, url}; + this.header = {name: 'Referer', value: 'https://foo.com/stuff'}; + this.referer = new Referer(); + }); + describe('#shouldRemoveHeader', function() { + it('removes first', function() { + assert.isTrue(this.referer.shouldRemoveHeader(this.details, this.header)); + }); + it('does not remove when failedAlready', function() { + this.referer.badRedirects.set(requestId); + assert.isFalse(this.referer.shouldRemoveHeader(this.details, this.header)); + }); + }); + + describe('#onHeadersReceived', function() { + it('no action for non-400 responses', function() { + assert.isUndefined(this.referer.onHeadersReceived({statusCode: 200})); + }); + describe('sent', function() { + beforeEach(function() { + this.referer.shouldRemoveHeader(this.details, this.header); + }); + it('no actionn for already failed but 400 again responses', function() { + this.referer.badRedirects.set(requestId); + assert.isUndefined(this.referer.onHeadersReceived({requestId, statusCode: 403})); + }); + it('redirects on first 400', function() { + let {details} = this, + statusCode = 403; + Object.assign(details, {statusCode}); + assert.deepEqual(this.referer.onHeadersReceived(details), {redirectUrl: details.url}); + }); + }); + }); +}); diff --git a/src/js/webrequest.js b/src/js/webrequest.js index 3ddce3b..3fd7931 100644 --- a/src/js/webrequest.js +++ b/src/js/webrequest.js @@ -5,6 +5,7 @@ const shim = require('./shim'), {URL} = shim, constants = require('./constants'), {header_methods, request_methods} = constants, + {ON_BEFORE_REQUEST, ON_BEFORE_SEND_HEADERS, ON_HEADERS_RECEIVED} = request_methods, {Handler} = require('./reasons/handlers'); function annotateDetails(details, requestType) { @@ -85,31 +86,40 @@ class WebRequest { } onBeforeRequest(details) { - annotateDetails(details, request_methods.ON_BEFORE_REQUEST); + annotateDetails(details, ON_BEFORE_REQUEST); this.recordRequest(details); return this.commitRequest(details); } onBeforeSendHeaders(details) { - annotateDetails(details, request_methods.ON_BEFORE_SEND_HEADERS); + annotateDetails(details, ON_BEFORE_SEND_HEADERS); this.headerHandler(details); this.markAction(details); return details.response; } onHeadersReceived(details) { - annotateDetails(details, request_methods.ON_HEADERS_RECEIVED); + annotateDetails(details, ON_HEADERS_RECEIVED); this.headerHandler(details); this.markAction(details); return details.response; } + requestOrResponseAction(details) { + if (!details.shortCircuit) { + if (details.requestType == ON_HEADERS_RECEIVED) { + return this.handler.headerHandler.referer.onHeadersReceived(details); + } + } + } + headerHandler(details) { if (this.isThirdParty(details)) { let headers = details[details.headerPropName], removed = this.removeHeaders(details, headers); this.checkAllRequestActions(details); - if (!details.shortCircuit && removed.length) { + this.requestOrResponseAction(details); + if (!details.shortCircuit && (removed.length || headers.mutated)) { details.response = {[details.headerPropName]: headers}; this.markHeaders(removed, details); } diff --git a/src/manifest.json b/src/manifest.json index d2967c3..9b3be13 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -55,6 +55,7 @@ "js/reasons/reasons.js", "js/reasons/fingerprinting.js", "js/reasons/user_url_deactivate.js", + "js/reasons/referer.js", "js/reasons/headers.js", "js/reasons/etag.js", "js/reasons/utils.js", diff --git a/src/skin/popup.html b/src/skin/popup.html index b7a81b5..167aa34 100644 --- a/src/skin/popup.html +++ b/src/skin/popup.html @@ -14,6 +14,7 @@ +