summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobin Humble <plaguedbypenguins@gmail.com>2014-06-08 07:18:55 +0200
committerLorDClockaN <davor@losinj.com>2014-06-08 07:19:22 +0200
commite4cb677f406fd7bedd5b3ee7ff6e249c2b6eb68f (patch)
tree415683915b8ac310fc77dd25a234b17916c6875e
parent7fa622f559cfb6a504b281c4cb56077683d96d63 (diff)
reduce cookie tracking (4/4): onResume deletion of cookiesHEADkitkat
Optionally delete unwanted cookies (and localstorage) at every Browser resume. The default for this feature is off - ie. maintain the current Browser "keep every cookie" behaviour. Optionally localstorage files (site databases) are also removed whenever cookies have been deleted. This helps to reduce evercookie/supercookie persistence. A whitelist of sites that are permitted to keep cookies is stored in the standard Browser shared_prefs. The site's cookie preference is set via a menu checkbox when viewing the page. This allows opt-in whitelisting behaviour on a per-site basis, suitable for saving eg. login cookies. The cookie deletion itself is done by using existing API's to delete all cookies and then selectively restore just those from the whitelisted sites. Cookie counting is the only new API needed by this patch, and is used to eliminate unnecessary cookie and localstorage deletes. Although simplistic, onResume cookie filtering seems to work well and in testing hasn't broken any web browsing. The underlying CookieMonster functions operate on cached copies in ram and are asynchronous to disk so there should be little or no measurable performance impact on browsing from cookies. localstorage deletion is not cached by any layer so, if enabled, might have some minor performance impact. Change-Id: I55c69292a5ddc460e0e50b340dc4330c28becc5e
-rw-r--r--res/menu/browser.xml5
-rw-r--r--res/values/arrays_custom.xml28
-rw-r--r--res/values/strings.xml16
-rw-r--r--res/xml/privacy_security_preferences.xml16
-rw-r--r--src/com/android/browser/BrowserSettings.java169
-rw-r--r--src/com/android/browser/Controller.java22
-rw-r--r--src/com/android/browser/PreferenceKeys.java2
-rw-r--r--src/com/android/browser/UiController.java2
-rw-r--r--src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java26
9 files changed, 286 insertions, 0 deletions
diff --git a/res/menu/browser.xml b/res/menu/browser.xml
index 27c0c81d..d019546a 100644
--- a/res/menu/browser.xml
+++ b/res/menu/browser.xml
@@ -63,6 +63,11 @@
android:id="@+id/ua_desktop_menu_id"
android:checkable="true"
android:title="@string/ua_switcher_desktop" />
+ <item
+ android:id="@+id/cookies_whitelisted_menu_id"
+ android:checkable="true"
+ android:title="@string/site_cookies_whitelisted"
+ android:visible="false" />
</group>
<group
android:id="@+id/SNAPSHOT_MENU">
diff --git a/res/values/arrays_custom.xml b/res/values/arrays_custom.xml
new file mode 100644
index 00000000..243a1229
--- /dev/null
+++ b/res/values/arrays_custom.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2012-2014 The CyanogenMod Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string-array name="pref_security_site_whitelist_entries" translatable="false">
+ <item>@string/pref_security_site_whitelist_cookies_never</item>
+ <item>@string/pref_security_site_whitelist_cookies_only</item>
+ <item>@string/pref_security_site_whitelist_cookies_and_data</item>
+ </string-array>
+ <string-array name="pref_security_site_whitelist_values" translatable="false">
+ <item>0</item>
+ <item>1</item>
+ <item>2</item>
+ </string-array>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d71ddac7..4614a89b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1015,4 +1015,20 @@
<!-- Content description for navigating up in the bookmark folder hierarchy [CHAR LIMIT=NONE] -->
<string name="accessibility_button_bookmarks_folder_up">Previous folder</string>
+ <!-- Browser security/privacy anti-tracking label -->
+ <string name="pref_security_site_whitelist_cookies">Reduce tracking. Frequently delete cookies</string>
+ <!-- Browser security/privacy anti-tracking options -->
+ <string name="pref_security_site_whitelist_cookies_never">Never delete</string>
+ <string name="pref_security_site_whitelist_cookies_only">Delete from all sites that aren\'t in my whitelist</string>
+ <string name="pref_security_site_whitelist_cookies_and_data">Also delete all localstorage databases</string>
+ <!-- Browser security/privacy anti-tracking verbose checkbox label -->
+ <string name="pref_security_site_whitelist_cookies_verbose">Cookie count</string>
+ <!-- Browser security/privacy anti-tracking verbose checkbox summary -->
+ <string name="pref_security_site_whitelist_cookies_verbose_summary">Show the number of deleted and remaining cookies</string>
+ <!-- Browser popup menu checkbox that allows the user to whitelist cookies from this site [CHAR LIMIT=50] -->
+ <string name="site_cookies_whitelisted">Keep site\'s cookies</string>
+ <!-- Toast to inform the user of the number of cookies deleted and remaining [CHAR LIMIT=50] -->
+ <string name="cookies_deleted_info">cookies deleted</string>
+ <string name="cookies_remain_info">cookies remain</string>
+
</resources>
diff --git a/res/xml/privacy_security_preferences.xml b/res/xml/privacy_security_preferences.xml
index 26336000..26c19ffc 100644
--- a/res/xml/privacy_security_preferences.xml
+++ b/res/xml/privacy_security_preferences.xml
@@ -44,6 +44,22 @@
android:title="@string/pref_security_accept_cookies"
android:summary="@string/pref_security_accept_cookies_summary" />
+ <ListPreference
+ android:key="site_whitelist_cookies"
+ android:dependency="accept_cookies"
+ android:dialogTitle="@string/pref_security_site_whitelist_cookies"
+ android:title="@string/pref_security_site_whitelist_cookies"
+ android:entries="@array/pref_security_site_whitelist_entries"
+ android:entryValues="@array/pref_security_site_whitelist_values"
+ android:defaultValue="0" />
+
+ <CheckBoxPreference
+ android:key="site_whitelist_cookies_verbose"
+ android:defaultValue="false"
+ android:dependency="accept_cookies"
+ android:title="@string/pref_security_site_whitelist_cookies_verbose"
+ android:summary="@string/pref_security_site_whitelist_cookies_verbose_summary" />
+
<com.android.browser.BrowserYesNoPreference
android:key="privacy_clear_cookies"
android:title="@string/pref_privacy_clear_cookies"
diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java
index 24cc0766..fe916564 100644
--- a/src/com/android/browser/BrowserSettings.java
+++ b/src/com/android/browser/BrowserSettings.java
@@ -51,6 +51,14 @@ import java.lang.ref.WeakReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.WeakHashMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Arrays;
+
+import android.util.Log;
+import android.net.WebAddress;
+import android.widget.Toast;
/**
* Class for managing settings
@@ -58,6 +66,8 @@ import java.util.WeakHashMap;
public class BrowserSettings implements OnSharedPreferenceChangeListener,
PreferenceKeys {
+ private final static String TAG = "BrowserSettings";
+
// TODO: Do something with this UserAgent stuff
private static final String DESKTOP_USERAGENT = "Mozilla/5.0 (X11; " +
"Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) " +
@@ -502,6 +512,151 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener,
}
}
+ private Set<String> getCookieWhitelist() {
+ requireInitialization();
+ return mPrefs.getStringSet("cookies_whitelist", new HashSet<String>());
+ }
+
+ private void setCookieWhitelist(Set<String> wl) {
+ mPrefs.edit().putStringSet("cookies_whitelist", wl).apply();
+ }
+
+ private boolean addToCookieWhitelist(String s) {
+ Set<String> wl = getCookieWhitelist();
+ boolean ok = wl.add(s);
+ setCookieWhitelist(wl);
+ return ok;
+ }
+
+ private boolean removeFromCookieWhitelist(String s) {
+ Set<String> wl = getCookieWhitelist();
+ boolean ok = wl.remove(s);
+ setCookieWhitelist(wl);
+ return ok;
+ }
+
+ public boolean hasCookiesWhitelisted(WebView view) {
+ if (view == null) {
+ return false;
+ }
+ String url = view.getUrl();
+ if (url == null) { // page hasn't finished loading yet
+ return false;
+ }
+ String host = new WebAddress(url).getHost();
+ Set<String> wl = getCookieWhitelist();
+ return wl.contains(host);
+ }
+
+ public void toggleCookiesWhitelisted(WebView view) {
+ if (view == null) {
+ return;
+ }
+ if (!enableDeleteCookies()) {
+ return;
+ }
+
+ String url = view.getUrl();
+ if (url == null) { // page hasn't finished loading yet
+ return;
+ }
+ String host = new WebAddress(url).getHost();
+ if (hasCookiesWhitelisted(view)) {
+ removeFromCookieWhitelist(host);
+ clearCookiesExceptWhitelist();
+ }
+ else {
+ addToCookieWhitelist(host);
+ }
+ }
+
+ public void clearCookiesExceptWhitelist() {
+ if (!enableDeleteCookies()) {
+ return;
+ }
+
+ Set<String> domains = getCookieWhitelist();
+ HashMap cookies = new HashMap();
+
+ // look for both domain and host cookies (domain cookies have a '.' prefix).
+ // query by https as this returns both http and https cookies.
+ String[] prefixes = { "https://.", "https://" };
+
+ // find all cookies in the whitelist
+ int savedCookies = 0;
+ for (String domain : domains) {
+ for (int p = 0; p < 2; p++) {
+ String url = prefixes[p] + domain;
+ String c = CookieManager.getInstance().getCookie(url);
+ if (c != null && !(c.equals("") || c.equals(null))) {
+ String[] cc = c.split(";"); // split into individual cookies
+ for (int i = 0; i < cc.length; i++) // get rid of leading blanks
+ cc[i] = cc[i].trim();
+ Set<String> ccUniq = new HashSet<String>(Arrays.asList(cc));
+ if (p == 1) { // remove domain cookies duplicated to the host
+ String urlDom = prefixes[0] + domain;
+ if (cookies.containsKey(urlDom)) {
+ Set<String> ccDom = (HashSet<String>)cookies.get(urlDom);
+ ccUniq.removeAll(ccDom);
+ }
+ }
+ savedCookies += ccUniq.size();
+ cookies.put(url, ccUniq);
+ }
+ }
+ }
+
+ int cookiesBefore = CookieManager.getInstance().countCookies();
+ if (savedCookies == cookiesBefore) {
+ // all cookies are whitelisted cookies. our job is done.
+ return;
+ }
+
+ // delete all cookies
+ CookieManager.getInstance().removeAllCookie();
+
+ // re-add all the whitelisted cookies
+ for (String domain : domains) {
+ for (String prefix : prefixes) {
+ String url = prefix + domain;
+ if (cookies.containsKey(url)) {
+ Set<String> cc = (HashSet<String>)cookies.get(url);
+ for (String i : cc) {
+ CookieManager.getInstance().setCookie(url, i + ";");
+ }
+ }
+ }
+ }
+
+ int cookiesAfter = CookieManager.getInstance().countCookies();
+ int cookiesDeleted = cookiesBefore - cookiesAfter;
+ boolean munched = cookiesDeleted > 0;
+
+ if (!munched) { // no cookies were deleted
+ return;
+ }
+
+ // optionally clear all localstorage too
+ if (enableDeleteLocaldata()) {
+ clearDatabases();
+ }
+
+ Log.d(TAG, "clearCookiesExceptWhitelist: "
+ + cookiesDeleted + " cookies deleted, "
+ + cookiesAfter + " cookies remain");
+
+ // toast if we have munched cookies
+ if (munchToast()) {
+ String deleted_str = mContext.getResources()
+ .getString(R.string.cookies_deleted_info);
+ String remain_str = mContext.getResources()
+ .getString(R.string.cookies_remain_info);
+ CharSequence text = "" + cookiesDeleted + " " + deleted_str
+ + "\n" + cookiesAfter + " " + remain_str;
+ Toast.makeText(mContext, text, Toast.LENGTH_SHORT).show();
+ }
+ }
+
public static int getAdjustedMinimumFontSize(int rawValue) {
rawValue++; // Preference starts at 0, min font at 1
if (rawValue > 1) {
@@ -809,6 +964,20 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener,
return mPrefs.getBoolean(PREF_ACCEPT_COOKIES, true);
}
+ public boolean enableDeleteCookies() {
+ int value = Integer.valueOf(mPrefs.getString(PREF_SITE_WHITELIST_COOKIES, "0"));
+ return value != 0;
+ }
+
+ public boolean enableDeleteLocaldata() {
+ int value = Integer.valueOf(mPrefs.getString(PREF_SITE_WHITELIST_COOKIES, "0"));
+ return value == 2;
+ }
+
+ public boolean munchToast() {
+ return mPrefs.getBoolean(PREF_SITE_WHITELIST_COOKIES_VERBOSE, false);
+ }
+
public boolean saveFormdata() {
return mPrefs.getBoolean(PREF_SAVE_FORMDATA, true);
}
diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java
index 2d716a8d..f01f58e8 100644
--- a/src/com/android/browser/Controller.java
+++ b/src/com/android/browser/Controller.java
@@ -688,6 +688,11 @@ public class Controller
return;
}
mSettings.setLastRunPaused(false);
+
+ // delete cookies (and localstorage) early in the resume to
+ // avoid potential races with regular cookie reads and writes.
+ mSettings.clearCookiesExceptWhitelist();
+
mActivityPaused = false;
Tab current = mTabControl.getCurrentTab();
if (current != null) {
@@ -1483,12 +1488,14 @@ public class Controller
boolean canGoForward = false;
boolean isHome = false;
boolean isDesktopUa = false;
+ boolean hasCookiesWhitelisted = false;
boolean isLive = false;
if (tab != null) {
canGoBack = tab.canGoBack();
canGoForward = tab.canGoForward();
isHome = mSettings.getHomePage().equals(tab.getUrl());
isDesktopUa = mSettings.hasDesktopUseragent(tab.getWebView());
+ hasCookiesWhitelisted = mSettings.hasCookiesWhitelisted(tab.getWebView());
isLive = !tab.isSnapshot();
}
final MenuItem back = menu.findItem(R.id.back_menu_id);
@@ -1529,6 +1536,11 @@ public class Controller
menu.setGroupVisible(R.id.SNAPSHOT_MENU, !isLive);
menu.setGroupVisible(R.id.COMBO_MENU, false);
+ // individual Visible needs to be after the group setting
+ final MenuItem cwSwitcher = menu.findItem(R.id.cookies_whitelisted_menu_id);
+ cwSwitcher.setChecked(hasCookiesWhitelisted);
+ cwSwitcher.setVisible(isLive && mSettings.enableDeleteCookies());
+
mUi.updateMenuState(tab, menu);
}
@@ -1656,6 +1668,10 @@ public class Controller
toggleUserAgent();
break;
+ case R.id.cookies_whitelisted_menu_id:
+ toggleCookiesWhitelisted();
+ break;
+
case R.id.window_one_menu_id:
case R.id.window_two_menu_id:
case R.id.window_three_menu_id:
@@ -1693,6 +1709,12 @@ public class Controller
}
@Override
+ public void toggleCookiesWhitelisted() {
+ WebView web = getCurrentWebView();
+ mSettings.toggleCookiesWhitelisted(web);
+ }
+
+ @Override
public void findOnPage() {
getCurrentTopWebView().showFindDialog(null, true);
}
diff --git a/src/com/android/browser/PreferenceKeys.java b/src/com/android/browser/PreferenceKeys.java
index 18280327..8b6d95ca 100644
--- a/src/com/android/browser/PreferenceKeys.java
+++ b/src/com/android/browser/PreferenceKeys.java
@@ -88,6 +88,8 @@ public interface PreferenceKeys {
// Keys for privacy_security_preferences.xml
// ----------------------
static final String PREF_ACCEPT_COOKIES = "accept_cookies";
+ static final String PREF_SITE_WHITELIST_COOKIES = "site_whitelist_cookies";
+ static final String PREF_SITE_WHITELIST_COOKIES_VERBOSE = "site_whitelist_cookies_verbose";
static final String PREF_ENABLE_GEOLOCATION = "enable_geolocation";
static final String PREF_PRIVACY_CLEAR_CACHE = "privacy_clear_cache";
static final String PREF_PRIVACY_CLEAR_COOKIES = "privacy_clear_cookies";
diff --git a/src/com/android/browser/UiController.java b/src/com/android/browser/UiController.java
index f00f1a23..53830d5a 100644
--- a/src/com/android/browser/UiController.java
+++ b/src/com/android/browser/UiController.java
@@ -105,6 +105,8 @@ public interface UiController {
void toggleUserAgent();
+ void toggleCookiesWhitelisted();
+
BrowserSettings getSettings();
boolean supportsVoice();
diff --git a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
index 35e6e432..24301cb7 100644
--- a/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
+++ b/src/com/android/browser/preferences/PrivacySecurityPreferencesFragment.java
@@ -23,6 +23,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.preference.Preference;
+import android.preference.ListPreference;
import android.preference.PreferenceFragment;
public class PrivacySecurityPreferencesFragment extends PreferenceFragment
@@ -37,6 +38,24 @@ public class PrivacySecurityPreferencesFragment extends PreferenceFragment
Preference e = findPreference(PreferenceKeys.PREF_PRIVACY_CLEAR_HISTORY);
e.setOnPreferenceChangeListener(this);
+
+ ListPreference lp = (ListPreference) findPreference(PreferenceKeys.PREF_SITE_WHITELIST_COOKIES);
+ lp.setOnPreferenceChangeListener(this);
+ updateListPreferenceSummary(lp);
+ cookiesVerboseEnable(lp.getValue());
+ }
+
+ private void cookiesVerboseEnable(String str) {
+ // if never, disable verbose option
+ int value = Integer.valueOf(str);
+ Preference pref = findPreference(PreferenceKeys.PREF_SITE_WHITELIST_COOKIES_VERBOSE);
+ if (pref != null) {
+ pref.setEnabled(value != 0);
+ }
+ }
+
+ void updateListPreferenceSummary(ListPreference e) {
+ e.setSummary(e.getEntry());
}
@Override
@@ -54,6 +73,13 @@ public class PrivacySecurityPreferencesFragment extends PreferenceFragment
pref.getKey()));
return true;
}
+ else if (pref.getKey().equals(PreferenceKeys.PREF_SITE_WHITELIST_COOKIES)) {
+ ListPreference lp = (ListPreference) pref;
+ lp.setValue((String) objValue);
+ updateListPreferenceSummary(lp);
+ cookiesVerboseEnable((String) objValue);
+ return false;
+ }
return false;
}