Tue, 06 Jan 2015 21:39:09 +0100
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 | } |