Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support navigator.doNotTrack #1861

Merged
merged 6 commits into from
Mar 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions src/js/contentscripts/dnt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* This file is part of Privacy Badger <https://www.eff.org/privacybadger>
* Copyright (C) 2018 Electronic Frontier Foundation
*
* Privacy Badger is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* Privacy Badger is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Privacy Badger. If not, see <http://www.gnu.org/licenses/>.
*/

function getPageScript() {

// code below is not a content script: no chrome.* APIs /////////////////////

// return a string
return "(" + function (NAVIGATOR, OBJECT) {

OBJECT.defineProperty(OBJECT.getPrototypeOf(NAVIGATOR), "doNotTrack", {
get: () => {
return "1";
}
});

// save locally to keep from getting overwritten by site code
} + "(window.navigator, Object));";

// code above is not a content script: no chrome.* APIs /////////////////////

}

/**
* Executes a script in the page DOM context
*/
function insertPageScript(text) {
var parent = document.documentElement,
script = document.createElement('script');

script.text = text;
script.async = false;

parent.insertBefore(script, parent.firstChild);
parent.removeChild(script);
}

// TODO race condition; fix waiting on https://crbug.com/478183
chrome.runtime.sendMessage({
checkEnabled: true
}, function (enabled) {
if (enabled) {
insertPageScript(getPageScript());
}
});
27 changes: 15 additions & 12 deletions src/js/webrequest.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ function onBeforeRequest(details) {
*/
function onBeforeSendHeaders(details) {
let frame_id = details.frameId,
headers = details.requestHeaders,
tab_id = details.tabId,
type = details.type,
url = details.url;
Expand All @@ -153,9 +152,10 @@ function onBeforeSendHeaders(details) {
if (type == "xmlhttprequest" && url.endsWith("/.well-known/dnt-policy.txt")) {
// remove Cookie headers
let newHeaders = [];
for (let i = 0, count = headers.length; i < count; i++) {
if (headers[i].name.toLowerCase() != "cookie") {
newHeaders.push(headers[i]);
for (let i = 0, count = details.requestHeaders.length; i < count; i++) {
let header = details.requestHeaders[i];
if (header.name.toLowerCase() != "cookie") {
newHeaders.push(header);
}
}
return {
Expand All @@ -170,9 +170,13 @@ function onBeforeSendHeaders(details) {
var requestDomain = window.extractHostFromURL(url);

if (!isThirdPartyDomain(requestDomain, tabDomain)) {
// Still sending Do Not Track even if HTTP and cookie blocking are disabled
headers.push({name: "DNT", value: "1"});
return {requestHeaders: headers};
if (badger.isPrivacyBadgerEnabled(tabDomain)) {
// Still sending Do Not Track even if HTTP and cookie blocking are disabled
details.requestHeaders.push({name: "DNT", value: "1"});
return {requestHeaders: details.requestHeaders};
} else {
return {};
}
}

var requestAction = checkAction(tab_id, requestDomain, frame_id);
Expand All @@ -195,8 +199,7 @@ function onBeforeSendHeaders(details) {
}

if (!badger.isPrivacyBadgerEnabled(tabDomain)) {
headers.push({name: "DNT", value: "1"});
return {requestHeaders: headers};
return {};
}

// This will only happen if the above code sets the action for the request
Expand Down Expand Up @@ -231,7 +234,7 @@ function onBeforeSendHeaders(details) {

// This is the typical codepath
if (requestAction == constants.COOKIEBLOCK || requestAction == constants.USER_COOKIE_BLOCK) {
var newHeaders = headers.filter(function(header) {
let newHeaders = details.requestHeaders.filter(function (header) {
return (header.name.toLowerCase() != "cookie" && header.name.toLowerCase() != "referer");
});
newHeaders.push({name: "DNT", value: "1"});
Expand All @@ -240,8 +243,8 @@ function onBeforeSendHeaders(details) {

// if we are here, we're looking at a third party
// that's not yet blocked or cookieblocked
headers.push({name: "DNT", value: "1"});
return {requestHeaders: headers};
details.requestHeaders.push({name: "DNT", value: "1"});
return {requestHeaders: details.requestHeaders};
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"js": [
"js/contentscripts/clobbercookie.js",
"js/contentscripts/clobberlocalstorage.js",
"js/contentscripts/dnt.js",
"js/contentscripts/fingerprinting.js"
],
"matches": [
Expand Down
66 changes: 66 additions & 0 deletions tests/selenium/dnt_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import json
import unittest

import pbtest
Expand All @@ -19,6 +20,24 @@
class DNTTest(pbtest.PBSeleniumTest):
"""Tests to make sure DNT policy checking works as expected."""

# TODO switch to non-delayed version (see below)
# once race condition (https://crbug.com/478183) is fixed
NAVIGATOR_DNT_TEST_URL = (
"https://cdn.rawgit.com/ghostwords/"
"1c50869a0469e38d5dabd53f1204d3de/raw/35732fef159fe3cbee66b6fdb4a91388039cda27/"
"privacy-badger-navigator-donottrack-delayed-fixture.html"
# non-delayed version:
#"9fc6900566a2f93edd8e4a1e48bbaa28/raw/741627c60ca53be69bc11bf21d6d1d0b42edb52a/"
#"privacy-badger-navigator-donottrack-fixture.html"
)

def disable_badger_on_site(self, url):
self.load_url(self.options_url)
self.driver.find_element_by_css_selector(
'a[href="#tab-whitelisted-domains"]').click()
self.driver.find_element_by_id('newWhitelistDomain').send_keys(url)
self.driver.find_element_by_css_selector('button.addButton').click()

def domain_was_recorded(self, domain):
return self.js("""return (
Object.keys(badger.storage.action_map.getItemClones()).indexOf('{}') != -1
Expand Down Expand Up @@ -188,6 +207,53 @@ def test_should_not_record_nontracking_domains(self):
"Non-tracking domain should not have gotten recorded"
)

def test_first_party_dnt_header(self):
TEST_URL = "https://httpbin.org/get"

self.load_url(TEST_URL)

headers = json.loads(
self.driver.find_element_by_tag_name('body').text
)['headers']

self.assertIn('Dnt', headers, "DNT header should have been present")
self.assertEqual(headers['Dnt'], "1",
'DNT header should have been set to "1"')

def test_no_dnt_header_when_disabled(self):
TEST_URL = "https://httpbin.org/get"

self.disable_badger_on_site(TEST_URL)

self.load_url(TEST_URL)

headers = json.loads(
self.driver.find_element_by_tag_name('body').text
)['headers']

self.assertNotIn('Dnt', headers, "DNT header should have been missing")

def test_navigator_object(self):
self.load_url(DNTTest.NAVIGATOR_DNT_TEST_URL, wait_for_body_text=True)

self.assertEqual(
self.driver.find_element_by_tag_name('body').text,
'no tracking (navigator.doNotTrack="1")',
"navigator.DoNotTrack should have been set to \"1\""
)

def test_navigator_left_alone_when_disabled(self):
self.disable_badger_on_site(DNTTest.NAVIGATOR_DNT_TEST_URL)

self.load_url(DNTTest.NAVIGATOR_DNT_TEST_URL, wait_for_body_text=True)

# navigator.doNotTrack defaults to null in Chrome, "unspecified" in Firefox
self.assertEqual(
self.driver.find_element_by_tag_name('body').text[0:5],
'unset',
"navigator.DoNotTrack should have been left unset"
)


if __name__ == "__main__":
unittest.main()
9 changes: 8 additions & 1 deletion tests/selenium/pbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def open_window(self):
self.js('window.open()')
self.driver.switch_to.window(self.driver.window_handles[-1])

def load_url(self, url, wait_on_site=0, retries=5):
def load_url(self, url, wait_on_site=0, wait_for_body_text=False, retries=5):
"""Load a URL and wait before returning."""
for i in range(retries):
try:
Expand All @@ -305,6 +305,13 @@ def load_url(self, url, wait_on_site=0, retries=5):
continue
raise e
self.driver.switch_to.window(self.driver.current_window_handle)

if wait_for_body_text:
retry_until(
lambda: bool(self.driver.find_element_by_tag_name('body').text),
msg="Waiting for document.body.textContent to get populated ..."
)

time.sleep(wait_on_site)

def txt_by_css(self, css_selector, timeout=SEL_DEFAULT_WAIT_TIMEOUT):
Expand Down