dom/settings/SettingsDB.jsm

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 let Cc = Components.classes;
michael@0 8 let Ci = Components.interfaces;
michael@0 9 let Cu = Components.utils;
michael@0 10
michael@0 11 Cu.import("resource://gre/modules/Services.jsm");
michael@0 12 Cu.import("resource://gre/modules/ObjectWrapper.jsm");
michael@0 13
michael@0 14 this.EXPORTED_SYMBOLS = ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"];
michael@0 15
michael@0 16 const DEBUG = false;
michael@0 17 function debug(s) {
michael@0 18 if (DEBUG) dump("-*- SettingsDB: " + s + "\n");
michael@0 19 }
michael@0 20
michael@0 21 this.SETTINGSDB_NAME = "settings";
michael@0 22 this.SETTINGSDB_VERSION = 3;
michael@0 23 this.SETTINGSSTORE_NAME = "settings";
michael@0 24
michael@0 25 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
michael@0 26 Cu.import("resource://gre/modules/FileUtils.jsm");
michael@0 27 Cu.import("resource://gre/modules/NetUtil.jsm");
michael@0 28
michael@0 29 this.SettingsDB = function SettingsDB() {}
michael@0 30
michael@0 31 SettingsDB.prototype = {
michael@0 32
michael@0 33 __proto__: IndexedDBHelper.prototype,
michael@0 34
michael@0 35 upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
michael@0 36 let objectStore;
michael@0 37 if (aOldVersion == 0) {
michael@0 38 objectStore = aDb.createObjectStore(SETTINGSSTORE_NAME, { keyPath: "settingName" });
michael@0 39 if (DEBUG) debug("Created object stores");
michael@0 40 } else if (aOldVersion == 1) {
michael@0 41 if (DEBUG) debug("Get object store for upgrade and remove old index");
michael@0 42 objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
michael@0 43 objectStore.deleteIndex("settingValue");
michael@0 44 } else {
michael@0 45 if (DEBUG) debug("Get object store for upgrade");
michael@0 46 objectStore = aTransaction.objectStore(SETTINGSSTORE_NAME);
michael@0 47 }
michael@0 48
michael@0 49 // Loading resource://app/defaults/settings.json doesn't work because
michael@0 50 // settings.json is not in the omnijar.
michael@0 51 // So we look for the app dir instead and go from here...
michael@0 52 let settingsFile = FileUtils.getFile("DefRt", ["settings.json"], false);
michael@0 53 if (!settingsFile || (settingsFile && !settingsFile.exists())) {
michael@0 54 // On b2g desktop builds the settings.json file is moved in the
michael@0 55 // profile directory by the build system.
michael@0 56 settingsFile = FileUtils.getFile("ProfD", ["settings.json"], false);
michael@0 57 if (!settingsFile || (settingsFile && !settingsFile.exists())) {
michael@0 58 return;
michael@0 59 }
michael@0 60 }
michael@0 61
michael@0 62 let chan = NetUtil.newChannel(settingsFile);
michael@0 63 let stream = chan.open();
michael@0 64 // Obtain a converter to read from a UTF-8 encoded input stream.
michael@0 65 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
michael@0 66 .createInstance(Ci.nsIScriptableUnicodeConverter);
michael@0 67 converter.charset = "UTF-8";
michael@0 68 let rawstr = converter.ConvertToUnicode(NetUtil.readInputStreamToString(
michael@0 69 stream,
michael@0 70 stream.available()) || "");
michael@0 71 let settings;
michael@0 72 try {
michael@0 73 settings = JSON.parse(rawstr);
michael@0 74 } catch(e) {
michael@0 75 if (DEBUG) debug("Error parsing " + settingsFile.path + " : " + e);
michael@0 76 return;
michael@0 77 }
michael@0 78 stream.close();
michael@0 79
michael@0 80 objectStore.openCursor().onsuccess = function(event) {
michael@0 81 let cursor = event.target.result;
michael@0 82 if (cursor) {
michael@0 83 let value = cursor.value;
michael@0 84 if (value.settingName in settings) {
michael@0 85 if (DEBUG) debug("Upgrade " +settings[value.settingName]);
michael@0 86 value.defaultValue = this.prepareValue(settings[value.settingName]);
michael@0 87 delete settings[value.settingName];
michael@0 88 if ("settingValue" in value) {
michael@0 89 value.userValue = this.prepareValue(value.settingValue);
michael@0 90 delete value.settingValue;
michael@0 91 }
michael@0 92 cursor.update(value);
michael@0 93 } else if ("userValue" in value || "settingValue" in value) {
michael@0 94 value.defaultValue = undefined;
michael@0 95 if (aOldVersion == 1 && value.settingValue) {
michael@0 96 value.userValue = this.prepareValue(value.settingValue);
michael@0 97 delete value.settingValue;
michael@0 98 }
michael@0 99 cursor.update(value);
michael@0 100 } else {
michael@0 101 cursor.delete();
michael@0 102 }
michael@0 103 cursor.continue();
michael@0 104 } else {
michael@0 105 for (let name in settings) {
michael@0 106 let value = this.prepareValue(settings[name]);
michael@0 107 if (DEBUG) debug("Set new:" + name +", " + value);
michael@0 108 objectStore.add({ settingName: name, defaultValue: value, userValue: undefined });
michael@0 109 }
michael@0 110 }
michael@0 111 }.bind(this);
michael@0 112 },
michael@0 113
michael@0 114 // If the value is a data: uri, convert it to a Blob.
michael@0 115 convertDataURIToBlob: function(aValue) {
michael@0 116 /* base64 to ArrayBuffer decoding, from
michael@0 117 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Base64_encoding_and_decoding
michael@0 118 */
michael@0 119 function b64ToUint6 (nChr) {
michael@0 120 return nChr > 64 && nChr < 91 ?
michael@0 121 nChr - 65
michael@0 122 : nChr > 96 && nChr < 123 ?
michael@0 123 nChr - 71
michael@0 124 : nChr > 47 && nChr < 58 ?
michael@0 125 nChr + 4
michael@0 126 : nChr === 43 ?
michael@0 127 62
michael@0 128 : nChr === 47 ?
michael@0 129 63
michael@0 130 :
michael@0 131 0;
michael@0 132 }
michael@0 133
michael@0 134 function base64DecToArr(sBase64, nBlocksSize) {
michael@0 135 let sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
michael@0 136 nInLen = sB64Enc.length,
michael@0 137 nOutLen = nBlocksSize ? Math.ceil((nInLen * 3 + 1 >> 2) / nBlocksSize) * nBlocksSize
michael@0 138 : nInLen * 3 + 1 >> 2,
michael@0 139 taBytes = new Uint8Array(nOutLen);
michael@0 140
michael@0 141 for (let nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0; nInIdx < nInLen; nInIdx++) {
michael@0 142 nMod4 = nInIdx & 3;
michael@0 143 nUint24 |= b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << 18 - 6 * nMod4;
michael@0 144 if (nMod4 === 3 || nInLen - nInIdx === 1) {
michael@0 145 for (nMod3 = 0; nMod3 < 3 && nOutIdx < nOutLen; nMod3++, nOutIdx++) {
michael@0 146 taBytes[nOutIdx] = nUint24 >>> (16 >>> nMod3 & 24) & 255;
michael@0 147 }
michael@0 148 nUint24 = 0;
michael@0 149 }
michael@0 150 }
michael@0 151 return taBytes;
michael@0 152 }
michael@0 153
michael@0 154 // Check if we have a data: uri, and if it's base64 encoded.
michael@0 155 // data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEA...
michael@0 156 if (typeof aValue == "string" && aValue.startsWith("data:")) {
michael@0 157 try {
michael@0 158 let uri = Services.io.newURI(aValue, null, null);
michael@0 159 // XXX: that would be nice to reuse the c++ bits of the data:
michael@0 160 // protocol handler instead.
michael@0 161 let mimeType = "application/octet-stream";
michael@0 162 let mimeDelim = aValue.indexOf(";");
michael@0 163 if (mimeDelim !== -1) {
michael@0 164 mimeType = aValue.substring(5, mimeDelim);
michael@0 165 }
michael@0 166 let start = aValue.indexOf(",") + 1;
michael@0 167 let isBase64 = ((aValue.indexOf("base64") + 7) == start);
michael@0 168 let payload = aValue.substring(start);
michael@0 169
michael@0 170 return new Blob([isBase64 ? base64DecToArr(payload) : payload],
michael@0 171 { type: mimeType });
michael@0 172 } catch(e) {
michael@0 173 dump(e);
michael@0 174 }
michael@0 175 }
michael@0 176 return aValue
michael@0 177 },
michael@0 178
michael@0 179 // Makes sure any property that is a data: uri gets converted to a Blob.
michael@0 180 prepareValue: function(aObject) {
michael@0 181 let kind = ObjectWrapper.getObjectKind(aObject);
michael@0 182 if (kind == "array") {
michael@0 183 let res = [];
michael@0 184 aObject.forEach(function(aObj) {
michael@0 185 res.push(this.prepareValue(aObj));
michael@0 186 }, this);
michael@0 187 return res;
michael@0 188 } else if (kind == "file" || kind == "blob" || kind == "date") {
michael@0 189 return aObject;
michael@0 190 } else if (kind == "primitive") {
michael@0 191 return this.convertDataURIToBlob(aObject);
michael@0 192 }
michael@0 193
michael@0 194 // Fall-through, we now have a dictionary object.
michael@0 195 for (let prop in aObject) {
michael@0 196 aObject[prop] = this.prepareValue(aObject[prop]);
michael@0 197 }
michael@0 198 return aObject;
michael@0 199 },
michael@0 200
michael@0 201 init: function init() {
michael@0 202 this.initDBHelper(SETTINGSDB_NAME, SETTINGSDB_VERSION,
michael@0 203 [SETTINGSSTORE_NAME]);
michael@0 204 }
michael@0 205 }

mercurial