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