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.

     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 }

mercurial