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 @@
+