/******************************************************************************* uMatrix - a browser extension to black/white list requests. Copyright (C) 2014-2017 Raymond Hill This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see {http://www.gnu.org/licenses/}. Home: https://github.com/gorhill/uMatrix */ /* exported startup, shutdown, install, uninstall */ 'use strict'; /******************************************************************************/ const hostName = 'umatrix'; /******************************************************************************/ function startup({ webExtension }) { webExtension.startup().then(api => { let { browser } = api, storageMigrator; let onMessage = function(message, sender, callback) { if ( message.what === 'webext:storageMigrateNext' ) { storageMigrator = storageMigrator || getStorageMigrator(); storageMigrator.getNext((key, value) => { if ( key === undefined ) { storageMigrator.markAsDone(); storageMigrator = undefined; browser.runtime.onMessage.removeListener(onMessage); } callback({ key: key, value: JSON.stringify(value) }); }); return true; } if ( message.what === 'webext:storageMigrateDone' ) { browser.runtime.onMessage.removeListener(onMessage); } if ( typeof callback === 'function' ) { callback(); } }; browser.runtime.onMessage.addListener(onMessage); }); } function shutdown() { } function install() { } function uninstall() { } /******************************************************************************/ var getStorageMigrator = function() { var db = null; var dbOpenError = ''; var close = function() { if ( db !== null ) { db.asyncClose(); } db = null; }; var open = function() { if ( db !== null ) { return db; } // Create path var { Services } = Components.utils.import('resource://gre/modules/Services.jsm', null), path = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile); path.append('extension-data'); path.append(hostName + '.sqlite'); if ( !path.exists() || !path.isFile() ) { return null; } // Open database. try { db = Services.storage.openDatabase(path); if ( db.connectionReady === false ) { db.asyncClose(); db = null; } } catch (ex) { if ( dbOpenError === '' ) { dbOpenError = ex.name; if ( ex.name === 'NS_ERROR_FILE_CORRUPTED' ) { close(); } } } if ( db === null ) { return null; } // Since database could be opened successfully, reset error flag (its // purpose is to avoid spamming console with error messages). dbOpenError = ''; return db; }; // Execute a query var runStatement = function(stmt, callback) { var result = {}; stmt.executeAsync({ handleResult: function(rows) { if ( !rows || typeof callback !== 'function' ) { return; } var row; while ( (row = rows.getNextRow()) ) { // we assume that there will be two columns, since we're // using it only for preferences result[row.getResultByIndex(0)] = row.getResultByIndex(1); } }, handleCompletion: function(reason) { if ( typeof callback === 'function' && reason === 0 ) { callback(result); } result = null; }, handleError: function(error) { // Caller expects an answer regardless of failure. if ( typeof callback === 'function' ) { callback({}); } result = null; // https://github.com/gorhill/uBlock/issues/1768 // Error cases which warrant a removal of the SQL file, so far: // - SQLLite error 11 database disk image is malformed // Can't find doc on MDN about the type of error.result, so I // force a string comparison. if ( error.result.toString() === '11' ) { close(); } } }); }; var bindNames = function(stmt, names) { if ( Array.isArray(names) === false || names.length === 0 ) { return; } var params = stmt.newBindingParamsArray(); var i = names.length, bp; while ( i-- ) { bp = params.newBindingParams(); bp.bindByName('name', names[i]); params.addParams(bp); } stmt.bindParameters(params); }; var read = function(details, callback) { if ( typeof callback !== 'function' ) { return; } var prepareResult = function(result) { var key; for ( key in result ) { if ( result.hasOwnProperty(key) === false ) { continue; } result[key] = JSON.parse(result[key]); } if ( typeof details === 'object' && details !== null ) { for ( key in details ) { if ( result.hasOwnProperty(key) === false ) { result[key] = details[key]; } } } callback(result); }; if ( open() === null ) { prepareResult({}); return; } var names = []; if ( details !== null ) { if ( Array.isArray(details) ) { names = details; } else if ( typeof details === 'object' ) { names = Object.keys(details); } else { names = [details.toString()]; } } var stmt; if ( names.length === 0 ) { stmt = db.createAsyncStatement('SELECT * FROM "settings"'); } else { stmt = db.createAsyncStatement('SELECT * FROM "settings" WHERE "name" = :name'); bindNames(stmt, names); } runStatement(stmt, prepareResult); }; let allKeys; let readNext = function(key, callback) { if ( key === undefined ) { callback(); return; } read(key, bin => { if ( bin instanceof Object && bin.hasOwnProperty(key) ) { callback(key, bin[key]); } else { callback(key); } }); }; let getNext = function(callback) { if ( Array.isArray(allKeys) ) { readNext(allKeys.pop(), callback); return; } if ( open() === null ) { callback(); return; } let stmt = db.createAsyncStatement('SELECT "name",\'dummy\' FROM "settings"'); runStatement(stmt, result => { allKeys = []; for ( let key in result ) { if ( result.hasOwnProperty(key) ) { allKeys.push(key); } } readNext(allKeys.pop(), callback); }); }; let markAsDone = function() { close(); }; return { getNext: getNext, markAsDone: markAsDone, }; }; /******************************************************************************/