You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
8.3 KiB

  1. /*******************************************************************************
  2. uMatrix - a browser extension to black/white list requests.
  3. Copyright (C) 2014-2017 Raymond Hill
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see {http://www.gnu.org/licenses/}.
  14. Home: https://github.com/gorhill/uMatrix
  15. */
  16. /* exported startup, shutdown, install, uninstall */
  17. 'use strict';
  18. /******************************************************************************/
  19. const hostName = 'umatrix';
  20. /******************************************************************************/
  21. function startup({ webExtension }) {
  22. webExtension.startup().then(api => {
  23. let { browser } = api,
  24. storageMigrator;
  25. let onMessage = function(message, sender, callback) {
  26. if ( message.what === 'webext:storageMigrateNext' ) {
  27. storageMigrator = storageMigrator || getStorageMigrator();
  28. storageMigrator.getNext((key, value) => {
  29. if ( key === undefined ) {
  30. storageMigrator.markAsDone();
  31. storageMigrator = undefined;
  32. browser.runtime.onMessage.removeListener(onMessage);
  33. }
  34. callback({ key: key, value: JSON.stringify(value) });
  35. });
  36. return true;
  37. }
  38. if ( message.what === 'webext:storageMigrateDone' ) {
  39. browser.runtime.onMessage.removeListener(onMessage);
  40. }
  41. if ( typeof callback === 'function' ) {
  42. callback();
  43. }
  44. };
  45. browser.runtime.onMessage.addListener(onMessage);
  46. });
  47. }
  48. function shutdown() {
  49. }
  50. function install() {
  51. }
  52. function uninstall() {
  53. }
  54. /******************************************************************************/
  55. var getStorageMigrator = function() {
  56. var db = null;
  57. var dbOpenError = '';
  58. var close = function() {
  59. if ( db !== null ) {
  60. db.asyncClose();
  61. }
  62. db = null;
  63. };
  64. var open = function() {
  65. if ( db !== null ) {
  66. return db;
  67. }
  68. // Create path
  69. var { Services } = Components.utils.import('resource://gre/modules/Services.jsm', null),
  70. path = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile);
  71. path.append('extension-data');
  72. path.append(hostName + '.sqlite');
  73. if ( !path.exists() || !path.isFile() ) {
  74. return null;
  75. }
  76. // Open database.
  77. try {
  78. db = Services.storage.openDatabase(path);
  79. if ( db.connectionReady === false ) {
  80. db.asyncClose();
  81. db = null;
  82. }
  83. } catch (ex) {
  84. if ( dbOpenError === '' ) {
  85. dbOpenError = ex.name;
  86. if ( ex.name === 'NS_ERROR_FILE_CORRUPTED' ) {
  87. close();
  88. }
  89. }
  90. }
  91. if ( db === null ) {
  92. return null;
  93. }
  94. // Since database could be opened successfully, reset error flag (its
  95. // purpose is to avoid spamming console with error messages).
  96. dbOpenError = '';
  97. return db;
  98. };
  99. // Execute a query
  100. var runStatement = function(stmt, callback) {
  101. var result = {};
  102. stmt.executeAsync({
  103. handleResult: function(rows) {
  104. if ( !rows || typeof callback !== 'function' ) {
  105. return;
  106. }
  107. var row;
  108. while ( (row = rows.getNextRow()) ) {
  109. // we assume that there will be two columns, since we're
  110. // using it only for preferences
  111. result[row.getResultByIndex(0)] = row.getResultByIndex(1);
  112. }
  113. },
  114. handleCompletion: function(reason) {
  115. if ( typeof callback === 'function' && reason === 0 ) {
  116. callback(result);
  117. }
  118. result = null;
  119. },
  120. handleError: function(error) {
  121. // Caller expects an answer regardless of failure.
  122. if ( typeof callback === 'function' ) {
  123. callback({});
  124. }
  125. result = null;
  126. // https://github.com/gorhill/uBlock/issues/1768
  127. // Error cases which warrant a removal of the SQL file, so far:
  128. // - SQLLite error 11 database disk image is malformed
  129. // Can't find doc on MDN about the type of error.result, so I
  130. // force a string comparison.
  131. if ( error.result.toString() === '11' ) {
  132. close();
  133. }
  134. }
  135. });
  136. };
  137. var bindNames = function(stmt, names) {
  138. if ( Array.isArray(names) === false || names.length === 0 ) {
  139. return;
  140. }
  141. var params = stmt.newBindingParamsArray();
  142. var i = names.length, bp;
  143. while ( i-- ) {
  144. bp = params.newBindingParams();
  145. bp.bindByName('name', names[i]);
  146. params.addParams(bp);
  147. }
  148. stmt.bindParameters(params);
  149. };
  150. var read = function(details, callback) {
  151. if ( typeof callback !== 'function' ) {
  152. return;
  153. }
  154. var prepareResult = function(result) {
  155. var key;
  156. for ( key in result ) {
  157. if ( result.hasOwnProperty(key) === false ) {
  158. continue;
  159. }
  160. result[key] = JSON.parse(result[key]);
  161. }
  162. if ( typeof details === 'object' && details !== null ) {
  163. for ( key in details ) {
  164. if ( result.hasOwnProperty(key) === false ) {
  165. result[key] = details[key];
  166. }
  167. }
  168. }
  169. callback(result);
  170. };
  171. if ( open() === null ) {
  172. prepareResult({});
  173. return;
  174. }
  175. var names = [];
  176. if ( details !== null ) {
  177. if ( Array.isArray(details) ) {
  178. names = details;
  179. } else if ( typeof details === 'object' ) {
  180. names = Object.keys(details);
  181. } else {
  182. names = [details.toString()];
  183. }
  184. }
  185. var stmt;
  186. if ( names.length === 0 ) {
  187. stmt = db.createAsyncStatement('SELECT * FROM "settings"');
  188. } else {
  189. stmt = db.createAsyncStatement('SELECT * FROM "settings" WHERE "name" = :name');
  190. bindNames(stmt, names);
  191. }
  192. runStatement(stmt, prepareResult);
  193. };
  194. let allKeys;
  195. let readNext = function(key, callback) {
  196. if ( key === undefined ) {
  197. callback();
  198. return;
  199. }
  200. read(key, bin => {
  201. if ( bin instanceof Object && bin.hasOwnProperty(key) ) {
  202. callback(key, bin[key]);
  203. } else {
  204. callback(key);
  205. }
  206. });
  207. };
  208. let getNext = function(callback) {
  209. if ( Array.isArray(allKeys) ) {
  210. readNext(allKeys.pop(), callback);
  211. return;
  212. }
  213. if ( open() === null ) {
  214. callback();
  215. return;
  216. }
  217. let stmt = db.createAsyncStatement('SELECT "name",\'dummy\' FROM "settings"');
  218. runStatement(stmt, result => {
  219. allKeys = [];
  220. for ( let key in result ) {
  221. if ( result.hasOwnProperty(key) ) {
  222. allKeys.push(key);
  223. }
  224. }
  225. readNext(allKeys.pop(), callback);
  226. });
  227. };
  228. let markAsDone = function() {
  229. close();
  230. };
  231. return {
  232. getNext: getNext,
  233. markAsDone: markAsDone,
  234. };
  235. };
  236. /******************************************************************************/