PageRenderTime 64ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 1ms

/browser/components/search/nsSearchService.js

https://bitbucket.org/mkato/mozilla-1.9.0-win64
JavaScript | 3199 lines | 2060 code | 452 blank | 687 comment | 309 complexity | 72265a636c3a10096ba296862d48b7e9 MD5 | raw file
Possible License(s): LGPL-3.0, MIT, BSD-3-Clause, MPL-2.0-no-copyleft-exception, GPL-2.0, LGPL-2.1
  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Browser Search Service.
  15. #
  16. # The Initial Developer of the Original Code is
  17. # Google Inc.
  18. # Portions created by the Initial Developer are Copyright (C) 2005-2006
  19. # the Initial Developer. All Rights Reserved.
  20. #
  21. # Contributor(s):
  22. # Ben Goodger <beng@google.com> (Original author)
  23. # Gavin Sharp <gavin@gavinsharp.com>
  24. # Joe Hughes <joe@retrovirus.com>
  25. # Pamela Greene <pamg.bugs@gmail.com>
  26. #
  27. # Alternatively, the contents of this file may be used under the terms of
  28. # either the GNU General Public License Version 2 or later (the "GPL"), or
  29. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30. # in which case the provisions of the GPL or the LGPL are applicable instead
  31. # of those above. If you wish to allow use of your version of this file only
  32. # under the terms of either the GPL or the LGPL, and not to allow others to
  33. # use your version of this file under the terms of the MPL, indicate your
  34. # decision by deleting the provisions above and replace them with the notice
  35. # and other provisions required by the GPL or the LGPL. If you do not delete
  36. # the provisions above, a recipient may use your version of this file under
  37. # the terms of any one of the MPL, the GPL or the LGPL.
  38. #
  39. # ***** END LICENSE BLOCK *****
  40. const Ci = Components.interfaces;
  41. const Cc = Components.classes;
  42. const Cr = Components.results;
  43. const PERMS_FILE = 0644;
  44. const PERMS_DIRECTORY = 0755;
  45. const MODE_RDONLY = 0x01;
  46. const MODE_WRONLY = 0x02;
  47. const MODE_CREATE = 0x08;
  48. const MODE_APPEND = 0x10;
  49. const MODE_TRUNCATE = 0x20;
  50. // Directory service keys
  51. const NS_APP_SEARCH_DIR_LIST = "SrchPluginsDL";
  52. const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
  53. const NS_APP_SEARCH_DIR = "SrchPlugns";
  54. const NS_APP_USER_PROFILE_50_DIR = "ProfD";
  55. // Search engine "locations". If this list is changed, be sure to update
  56. // the engine's _isDefault function accordingly.
  57. const SEARCH_APP_DIR = 1;
  58. const SEARCH_PROFILE_DIR = 2;
  59. const SEARCH_IN_EXTENSION = 3;
  60. // See documentation in nsIBrowserSearchService.idl.
  61. const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
  62. const QUIT_APPLICATION_TOPIC = "quit-application";
  63. const SEARCH_ENGINE_REMOVED = "engine-removed";
  64. const SEARCH_ENGINE_ADDED = "engine-added";
  65. const SEARCH_ENGINE_CHANGED = "engine-changed";
  66. const SEARCH_ENGINE_LOADED = "engine-loaded";
  67. const SEARCH_ENGINE_CURRENT = "engine-current";
  68. const SEARCH_TYPE_MOZSEARCH = Ci.nsISearchEngine.TYPE_MOZSEARCH;
  69. const SEARCH_TYPE_OPENSEARCH = Ci.nsISearchEngine.TYPE_OPENSEARCH;
  70. const SEARCH_TYPE_SHERLOCK = Ci.nsISearchEngine.TYPE_SHERLOCK;
  71. const SEARCH_DATA_XML = Ci.nsISearchEngine.DATA_XML;
  72. const SEARCH_DATA_TEXT = Ci.nsISearchEngine.DATA_TEXT;
  73. // File extensions for search plugin description files
  74. const XML_FILE_EXT = "xml";
  75. const SHERLOCK_FILE_EXT = "src";
  76. // Delay for lazy serialization (ms)
  77. const LAZY_SERIALIZE_DELAY = 100;
  78. const ICON_DATAURL_PREFIX = "data:image/x-icon;base64,";
  79. // Supported extensions for Sherlock plugin icons
  80. const SHERLOCK_ICON_EXTENSIONS = [".gif", ".png", ".jpg", ".jpeg"];
  81. const NEW_LINES = /(\r\n|\r|\n)/;
  82. // Set an arbitrary cap on the maximum icon size. Without this, large icons can
  83. // cause big delays when loading them at startup.
  84. const MAX_ICON_SIZE = 10000;
  85. // Default charset to use for sending search parameters. ISO-8859-1 is used to
  86. // match previous nsInternetSearchService behavior.
  87. const DEFAULT_QUERY_CHARSET = "ISO-8859-1";
  88. const SEARCH_BUNDLE = "chrome://browser/locale/search.properties";
  89. const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
  90. const OPENSEARCH_NS_10 = "http://a9.com/-/spec/opensearch/1.0/";
  91. const OPENSEARCH_NS_11 = "http://a9.com/-/spec/opensearch/1.1/";
  92. // Although the specification at http://opensearch.a9.com/spec/1.1/description/
  93. // gives the namespace names defined above, many existing OpenSearch engines
  94. // are using the following versions. We therefore allow either.
  95. const OPENSEARCH_NAMESPACES = [
  96. OPENSEARCH_NS_11, OPENSEARCH_NS_10,
  97. "http://a9.com/-/spec/opensearchdescription/1.1/",
  98. "http://a9.com/-/spec/opensearchdescription/1.0/"
  99. ];
  100. const OPENSEARCH_LOCALNAME = "OpenSearchDescription";
  101. const MOZSEARCH_NS_10 = "http://www.mozilla.org/2006/browser/search/";
  102. const MOZSEARCH_LOCALNAME = "SearchPlugin";
  103. const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
  104. const URLTYPE_SEARCH_HTML = "text/html";
  105. // Empty base document used to serialize engines to file.
  106. const EMPTY_DOC = "<?xml version=\"1.0\"?>\n" +
  107. "<" + MOZSEARCH_LOCALNAME +
  108. " xmlns=\"" + MOZSEARCH_NS_10 + "\"" +
  109. " xmlns:os=\"" + OPENSEARCH_NS_11 + "\"" +
  110. "/>";
  111. const BROWSER_SEARCH_PREF = "browser.search.";
  112. const USER_DEFINED = "{searchTerms}";
  113. // Custom search parameters
  114. #ifdef OFFICIAL_BUILD
  115. const MOZ_OFFICIAL = "official";
  116. #else
  117. const MOZ_OFFICIAL = "unofficial";
  118. #endif
  119. #expand const MOZ_DISTRIBUTION_ID = __MOZ_DISTRIBUTION_ID__;
  120. const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
  121. const MOZ_PARAM_DIST_ID = /\{moz:distributionID\}/g;
  122. const MOZ_PARAM_OFFICIAL = /\{moz:official\}/g;
  123. // Supported OpenSearch parameters
  124. // See http://opensearch.a9.com/spec/1.1/querysyntax/#core
  125. const OS_PARAM_USER_DEFINED = /\{searchTerms\??\}/g;
  126. const OS_PARAM_INPUT_ENCODING = /\{inputEncoding\??\}/g;
  127. const OS_PARAM_LANGUAGE = /\{language\??\}/g;
  128. const OS_PARAM_OUTPUT_ENCODING = /\{outputEncoding\??\}/g;
  129. // Default values
  130. const OS_PARAM_LANGUAGE_DEF = "*";
  131. const OS_PARAM_OUTPUT_ENCODING_DEF = "UTF-8";
  132. const OS_PARAM_INPUT_ENCODING_DEF = "UTF-8";
  133. // "Unsupported" OpenSearch parameters. For example, we don't support
  134. // page-based results, so if the engine requires that we send the "page index"
  135. // parameter, we'll always send "1".
  136. const OS_PARAM_COUNT = /\{count\??\}/g;
  137. const OS_PARAM_START_INDEX = /\{startIndex\??\}/g;
  138. const OS_PARAM_START_PAGE = /\{startPage\??\}/g;
  139. // Default values
  140. const OS_PARAM_COUNT_DEF = "20"; // 20 results
  141. const OS_PARAM_START_INDEX_DEF = "1"; // start at 1st result
  142. const OS_PARAM_START_PAGE_DEF = "1"; // 1st page
  143. // Optional parameter
  144. const OS_PARAM_OPTIONAL = /\{(?:\w+:)?\w+\?\}/g;
  145. // A array of arrays containing parameters that we don't fully support, and
  146. // their default values. We will only send values for these parameters if
  147. // required, since our values are just really arbitrary "guesses" that should
  148. // give us the output we want.
  149. var OS_UNSUPPORTED_PARAMS = [
  150. [OS_PARAM_COUNT, OS_PARAM_COUNT_DEF],
  151. [OS_PARAM_START_INDEX, OS_PARAM_START_INDEX_DEF],
  152. [OS_PARAM_START_PAGE, OS_PARAM_START_PAGE_DEF],
  153. ];
  154. // The default engine update interval, in days. This is only used if an engine
  155. // specifies an updateURL, but not an updateInterval.
  156. const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;
  157. // Returns false for whitespace-only or commented out lines in a
  158. // Sherlock file, true otherwise.
  159. function isUsefulLine(aLine) {
  160. return !(/^\s*($|#)/i.test(aLine));
  161. }
  162. /**
  163. * Prefixed to all search debug output.
  164. */
  165. const SEARCH_LOG_PREFIX = "*** Search: ";
  166. /**
  167. * Outputs aText to the JavaScript console as well as to stdout.
  168. */
  169. function DO_LOG(aText) {
  170. dump(SEARCH_LOG_PREFIX + aText + "\n");
  171. var consoleService = Cc["@mozilla.org/consoleservice;1"].
  172. getService(Ci.nsIConsoleService);
  173. consoleService.logStringMessage(aText);
  174. }
  175. #ifdef DEBUG
  176. /**
  177. * In debug builds, use a live, pref-based (browser.search.log) LOG function
  178. * to allow enabling/disabling without a restart.
  179. */
  180. function PREF_LOG(aText) {
  181. var prefB = Cc["@mozilla.org/preferences-service;1"].
  182. getService(Ci.nsIPrefBranch);
  183. var shouldLog = false;
  184. try {
  185. shouldLog = prefB.getBoolPref(BROWSER_SEARCH_PREF + "log");
  186. } catch (ex) {}
  187. if (shouldLog) {
  188. DO_LOG(aText);
  189. }
  190. }
  191. var LOG = PREF_LOG;
  192. #else
  193. /**
  194. * Otherwise, don't log at all by default. This can be overridden at startup
  195. * by the pref, see SearchService's _init method.
  196. */
  197. var LOG = function(){};
  198. #endif
  199. function ERROR(message, resultCode) {
  200. NS_ASSERT(false, SEARCH_LOG_PREFIX + message);
  201. throw resultCode;
  202. }
  203. /**
  204. * Ensures an assertion is met before continuing. Should be used to indicate
  205. * fatal errors.
  206. * @param assertion
  207. * An assertion that must be met
  208. * @param message
  209. * A message to display if the assertion is not met
  210. * @param resultCode
  211. * The NS_ERROR_* value to throw if the assertion is not met
  212. * @throws resultCode
  213. */
  214. function ENSURE_WARN(assertion, message, resultCode) {
  215. NS_ASSERT(assertion, SEARCH_LOG_PREFIX + message);
  216. if (!assertion)
  217. throw resultCode;
  218. }
  219. /**
  220. * Ensures an assertion is met before continuing, but does not warn the user.
  221. * Used to handle normal failure conditions.
  222. * @param assertion
  223. * An assertion that must be met
  224. * @param message
  225. * A message to display if the assertion is not met
  226. * @param resultCode
  227. * The NS_ERROR_* value to throw if the assertion is not met
  228. * @throws resultCode
  229. */
  230. function ENSURE(assertion, message, resultCode) {
  231. if (!assertion) {
  232. LOG(message);
  233. throw resultCode;
  234. }
  235. }
  236. /**
  237. * Ensures an argument assertion is met before continuing.
  238. * @param assertion
  239. * An argument assertion that must be met
  240. * @param message
  241. * A message to display if the assertion is not met
  242. * @throws NS_ERROR_INVALID_ARG for invalid arguments
  243. */
  244. function ENSURE_ARG(assertion, message) {
  245. ENSURE(assertion, message, Cr.NS_ERROR_INVALID_ARG);
  246. }
  247. function loadListener(aChannel, aEngine, aCallback) {
  248. this._channel = aChannel;
  249. this._bytes = [];
  250. this._engine = aEngine;
  251. this._callback = aCallback;
  252. }
  253. loadListener.prototype = {
  254. _callback: null,
  255. _channel: null,
  256. _countRead: 0,
  257. _engine: null,
  258. _stream: null,
  259. QueryInterface: function SRCH_loadQI(aIID) {
  260. if (aIID.equals(Ci.nsISupports) ||
  261. aIID.equals(Ci.nsIRequestObserver) ||
  262. aIID.equals(Ci.nsIStreamListener) ||
  263. aIID.equals(Ci.nsIChannelEventSink) ||
  264. aIID.equals(Ci.nsIInterfaceRequestor) ||
  265. aIID.equals(Ci.nsIBadCertListener2) ||
  266. aIID.equals(Ci.nsISSLErrorListener) ||
  267. // See FIXME comment below
  268. aIID.equals(Ci.nsIHttpEventSink) ||
  269. aIID.equals(Ci.nsIProgressEventSink) ||
  270. false)
  271. return this;
  272. throw Cr.NS_ERROR_NO_INTERFACE;
  273. },
  274. // nsIRequestObserver
  275. onStartRequest: function SRCH_loadStartR(aRequest, aContext) {
  276. LOG("loadListener: Starting request: " + aRequest.name);
  277. this._stream = Cc["@mozilla.org/binaryinputstream;1"].
  278. createInstance(Ci.nsIBinaryInputStream);
  279. },
  280. onStopRequest: function SRCH_loadStopR(aRequest, aContext, aStatusCode) {
  281. LOG("loadListener: Stopping request: " + aRequest.name);
  282. var requestFailed = !Components.isSuccessCode(aStatusCode);
  283. if (!requestFailed && (aRequest instanceof Ci.nsIHttpChannel))
  284. requestFailed = !aRequest.requestSucceeded;
  285. if (requestFailed || this._countRead == 0) {
  286. LOG("loadListener: request failed!");
  287. // send null so the callback can deal with the failure
  288. this._callback(null, this._engine);
  289. } else
  290. this._callback(this._bytes, this._engine);
  291. this._channel = null;
  292. this._engine = null;
  293. },
  294. // nsIStreamListener
  295. onDataAvailable: function SRCH_loadDAvailable(aRequest, aContext,
  296. aInputStream, aOffset,
  297. aCount) {
  298. this._stream.setInputStream(aInputStream);
  299. // Get a byte array of the data
  300. this._bytes = this._bytes.concat(this._stream.readByteArray(aCount));
  301. this._countRead += aCount;
  302. },
  303. // nsIChannelEventSink
  304. onChannelRedirect: function SRCH_loadCRedirect(aOldChannel, aNewChannel,
  305. aFlags) {
  306. this._channel = aNewChannel;
  307. },
  308. // nsIInterfaceRequestor
  309. getInterface: function SRCH_load_GI(aIID) {
  310. return this.QueryInterface(aIID);
  311. },
  312. // nsIBadCertListener2
  313. notifyCertProblem: function SRCH_certProblem(socketInfo, status, targetSite) {
  314. return true;
  315. },
  316. // nsISSLErrorListener
  317. notifySSLError: function SRCH_SSLError(socketInfo, error, targetSite) {
  318. return true;
  319. },
  320. // FIXME: bug 253127
  321. // nsIHttpEventSink
  322. onRedirect: function (aChannel, aNewChannel) {},
  323. // nsIProgressEventSink
  324. onProgress: function (aRequest, aContext, aProgress, aProgressMax) {},
  325. onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
  326. }
  327. /**
  328. * Used to verify a given DOM node's localName and namespaceURI.
  329. * @param aElement
  330. * The element to verify.
  331. * @param aLocalNameArray
  332. * An array of strings to compare against aElement's localName.
  333. * @param aNameSpaceArray
  334. * An array of strings to compare against aElement's namespaceURI.
  335. *
  336. * @returns false if aElement is null, or if its localName or namespaceURI
  337. * does not match one of the elements in the aLocalNameArray or
  338. * aNameSpaceArray arrays, respectively.
  339. * @throws NS_ERROR_INVALID_ARG if aLocalNameArray or aNameSpaceArray are null.
  340. */
  341. function checkNameSpace(aElement, aLocalNameArray, aNameSpaceArray) {
  342. ENSURE_ARG(aLocalNameArray && aNameSpaceArray, "missing aLocalNameArray or \
  343. aNameSpaceArray for checkNameSpace");
  344. return (aElement &&
  345. (aLocalNameArray.indexOf(aElement.localName) != -1) &&
  346. (aNameSpaceArray.indexOf(aElement.namespaceURI) != -1));
  347. }
  348. /**
  349. * Safely close a nsISafeOutputStream.
  350. * @param aFOS
  351. * The file output stream to close.
  352. */
  353. function closeSafeOutputStream(aFOS) {
  354. if (aFOS instanceof Ci.nsISafeOutputStream) {
  355. try {
  356. aFOS.finish();
  357. return;
  358. } catch (e) { }
  359. }
  360. aFOS.close();
  361. }
  362. /**
  363. * Wrapper function for nsIIOService::newURI.
  364. * @param aURLSpec
  365. * The URL string from which to create an nsIURI.
  366. * @returns an nsIURI object, or null if the creation of the URI failed.
  367. */
  368. function makeURI(aURLSpec, aCharset) {
  369. var ios = Cc["@mozilla.org/network/io-service;1"].
  370. getService(Ci.nsIIOService);
  371. try {
  372. return ios.newURI(aURLSpec, aCharset, null);
  373. } catch (ex) { }
  374. return null;
  375. }
  376. /**
  377. * Gets a directory from the directory service.
  378. * @param aKey
  379. * The directory service key indicating the directory to get.
  380. */
  381. function getDir(aKey) {
  382. ENSURE_ARG(aKey, "getDir requires a directory key!");
  383. var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
  384. getService(Ci.nsIProperties);
  385. var dir = fileLocator.get(aKey, Ci.nsIFile);
  386. return dir;
  387. }
  388. /**
  389. * The following two functions are essentially copied from
  390. * nsInternetSearchService. They are required for backwards compatibility.
  391. */
  392. function queryCharsetFromCode(aCode) {
  393. const codes = [];
  394. codes[0] = "x-mac-roman";
  395. codes[6] = "x-mac-greek";
  396. codes[35] = "x-mac-turkish";
  397. codes[513] = "ISO-8859-1";
  398. codes[514] = "ISO-8859-2";
  399. codes[517] = "ISO-8859-5";
  400. codes[518] = "ISO-8859-6";
  401. codes[519] = "ISO-8859-7";
  402. codes[520] = "ISO-8859-8";
  403. codes[521] = "ISO-8859-9";
  404. codes[1049] = "IBM864";
  405. codes[1280] = "windows-1252";
  406. codes[1281] = "windows-1250";
  407. codes[1282] = "windows-1251";
  408. codes[1283] = "windows-1253";
  409. codes[1284] = "windows-1254";
  410. codes[1285] = "windows-1255";
  411. codes[1286] = "windows-1256";
  412. codes[1536] = "us-ascii";
  413. codes[1584] = "GB2312";
  414. codes[1585] = "x-gbk";
  415. codes[1600] = "EUC-KR";
  416. codes[2080] = "ISO-2022-JP";
  417. codes[2096] = "ISO-2022-CN";
  418. codes[2112] = "ISO-2022-KR";
  419. codes[2336] = "EUC-JP";
  420. codes[2352] = "GB2312";
  421. codes[2353] = "x-euc-tw";
  422. codes[2368] = "EUC-KR";
  423. codes[2561] = "Shift_JIS";
  424. codes[2562] = "KOI8-R";
  425. codes[2563] = "Big5";
  426. codes[2565] = "HZ-GB-2312";
  427. if (codes[aCode])
  428. return codes[aCode];
  429. return getLocalizedPref("intl.charset.default", DEFAULT_QUERY_CHARSET);
  430. }
  431. function fileCharsetFromCode(aCode) {
  432. const codes = [
  433. "x-mac-roman", // 0
  434. "Shift_JIS", // 1
  435. "Big5", // 2
  436. "EUC-KR", // 3
  437. "X-MAC-ARABIC", // 4
  438. "X-MAC-HEBREW", // 5
  439. "X-MAC-GREEK", // 6
  440. "X-MAC-CYRILLIC", // 7
  441. "X-MAC-DEVANAGARI" , // 9
  442. "X-MAC-GURMUKHI", // 10
  443. "X-MAC-GUJARATI", // 11
  444. "X-MAC-ORIYA", // 12
  445. "X-MAC-BENGALI", // 13
  446. "X-MAC-TAMIL", // 14
  447. "X-MAC-TELUGU", // 15
  448. "X-MAC-KANNADA", // 16
  449. "X-MAC-MALAYALAM", // 17
  450. "X-MAC-SINHALESE", // 18
  451. "X-MAC-BURMESE", // 19
  452. "X-MAC-KHMER", // 20
  453. "X-MAC-THAI", // 21
  454. "X-MAC-LAOTIAN", // 22
  455. "X-MAC-GEORGIAN", // 23
  456. "X-MAC-ARMENIAN", // 24
  457. "GB2312", // 25
  458. "X-MAC-TIBETAN", // 26
  459. "X-MAC-MONGOLIAN", // 27
  460. "X-MAC-ETHIOPIC", // 28
  461. "X-MAC-CENTRALEURROMAN", // 29
  462. "X-MAC-VIETNAMESE", // 30
  463. "X-MAC-EXTARABIC" // 31
  464. ];
  465. // Sherlock files have always defaulted to x-mac-roman, so do that here too
  466. return codes[aCode] || codes[0];
  467. }
  468. /**
  469. * Returns a string interpretation of aBytes using aCharset, or null on
  470. * failure.
  471. */
  472. function bytesToString(aBytes, aCharset) {
  473. var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  474. createInstance(Ci.nsIScriptableUnicodeConverter);
  475. LOG("bytesToString: converting using charset: " + aCharset);
  476. try {
  477. converter.charset = aCharset;
  478. return converter.convertFromByteArray(aBytes, aBytes.length);
  479. } catch (ex) {}
  480. return null;
  481. }
  482. /**
  483. * Converts an array of bytes representing a Sherlock file into an array of
  484. * lines representing the useful data from the file.
  485. *
  486. * @param aBytes
  487. * The array of bytes representing the Sherlock file.
  488. * @param aCharsetCode
  489. * An integer value representing a character set code to be passed to
  490. * fileCharsetFromCode, or null for the default Sherlock encoding.
  491. */
  492. function sherlockBytesToLines(aBytes, aCharsetCode) {
  493. // fileCharsetFromCode returns the default encoding if aCharsetCode is null
  494. var charset = fileCharsetFromCode(aCharsetCode);
  495. var dataString = bytesToString(aBytes, charset);
  496. ENSURE(dataString, "sherlockBytesToLines: Couldn't convert byte array!",
  497. Cr.NS_ERROR_FAILURE);
  498. // Split the string into lines, and filter out comments and
  499. // whitespace-only lines
  500. return dataString.split(NEW_LINES).filter(isUsefulLine);
  501. }
  502. /**
  503. * Gets the current value of the locale. It's possible for this preference to
  504. * be localized, so we have to do a little extra work here. Similar code
  505. * exists in nsHttpHandler.cpp when building the UA string.
  506. */
  507. function getLocale() {
  508. const localePref = "general.useragent.locale";
  509. var locale = getLocalizedPref(localePref);
  510. if (locale)
  511. return locale;
  512. // Not localized
  513. var prefs = Cc["@mozilla.org/preferences-service;1"].
  514. getService(Ci.nsIPrefBranch);
  515. return prefs.getCharPref(localePref);
  516. }
  517. /**
  518. * Wrapper for nsIPrefBranch::getComplexValue.
  519. * @param aPrefName
  520. * The name of the pref to get.
  521. * @returns aDefault if the requested pref doesn't exist.
  522. */
  523. function getLocalizedPref(aPrefName, aDefault) {
  524. var prefB = Cc["@mozilla.org/preferences-service;1"].
  525. getService(Ci.nsIPrefBranch);
  526. const nsIPLS = Ci.nsIPrefLocalizedString;
  527. try {
  528. return prefB.getComplexValue(aPrefName, nsIPLS).data;
  529. } catch (ex) {}
  530. return aDefault;
  531. }
  532. /**
  533. * Wrapper for nsIPrefBranch::setComplexValue.
  534. * @param aPrefName
  535. * The name of the pref to set.
  536. */
  537. function setLocalizedPref(aPrefName, aValue) {
  538. var prefB = Cc["@mozilla.org/preferences-service;1"].
  539. getService(Ci.nsIPrefBranch);
  540. const nsIPLS = Ci.nsIPrefLocalizedString;
  541. try {
  542. var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
  543. .createInstance(Ci.nsIPrefLocalizedString);
  544. pls.data = aValue;
  545. prefB.setComplexValue(aPrefName, nsIPLS, pls);
  546. } catch (ex) {}
  547. }
  548. /**
  549. * Wrapper for nsIPrefBranch::getBoolPref.
  550. * @param aPrefName
  551. * The name of the pref to get.
  552. * @returns aDefault if the requested pref doesn't exist.
  553. */
  554. function getBoolPref(aName, aDefault) {
  555. var prefB = Cc["@mozilla.org/preferences-service;1"].
  556. getService(Ci.nsIPrefBranch);
  557. try {
  558. return prefB.getBoolPref(aName);
  559. } catch (ex) {
  560. return aDefault;
  561. }
  562. }
  563. /**
  564. * Get a unique nsIFile object with a sanitized name, based on the engine name.
  565. * @param aName
  566. * A name to "sanitize". Can be an empty string, in which case a random
  567. * 8 character filename will be produced.
  568. * @returns A nsIFile object in the user's search engines directory with a
  569. * unique sanitized name.
  570. */
  571. function getSanitizedFile(aName) {
  572. var fileName = sanitizeName(aName) + "." + XML_FILE_EXT;
  573. var file = getDir(NS_APP_USER_SEARCH_DIR);
  574. file.append(fileName);
  575. file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  576. return file;
  577. }
  578. /**
  579. * Removes all characters not in the "chars" string from aName.
  580. *
  581. * @returns a sanitized name to be used as a filename, or a random name
  582. * if a sanitized name cannot be obtained (if aName contains
  583. * no valid characters).
  584. */
  585. function sanitizeName(aName) {
  586. const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
  587. const maxLength = 60;
  588. var name = aName.toLowerCase();
  589. name = name.replace(/ /g, "-");
  590. name = name.split("").filter(function (el) {
  591. return chars.indexOf(el) != -1;
  592. }).join("");
  593. if (!name) {
  594. // Our input had no valid characters - use a random name
  595. var cl = chars.length - 1;
  596. for (var i = 0; i < 8; ++i)
  597. name += chars.charAt(Math.round(Math.random() * cl));
  598. }
  599. if (name.length > maxLength)
  600. name = name.substring(0, maxLength);
  601. return name;
  602. }
  603. /**
  604. * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
  605. * the state of the search service.
  606. *
  607. * @param aEngine
  608. * The nsISearchEngine object to which the change applies.
  609. * @param aVerb
  610. * A verb describing the change.
  611. *
  612. * @see nsIBrowserSearchService.idl
  613. */
  614. function notifyAction(aEngine, aVerb) {
  615. var os = Cc["@mozilla.org/observer-service;1"].
  616. getService(Ci.nsIObserverService);
  617. LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
  618. os.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
  619. }
  620. /**
  621. * Simple object representing a name/value pair.
  622. */
  623. function QueryParameter(aName, aValue) {
  624. ENSURE_ARG(aName && (aValue != null),
  625. "missing name or value for QueryParameter!");
  626. this.name = aName;
  627. this.value = aValue;
  628. }
  629. /**
  630. * Perform OpenSearch parameter substitution on aParamValue.
  631. *
  632. * @param aParamValue
  633. * A string containing OpenSearch search parameters.
  634. * @param aSearchTerms
  635. * The user-provided search terms. This string will inserted into
  636. * aParamValue as the value of the OS_PARAM_USER_DEFINED parameter.
  637. * This value must already be escaped appropriately - it is inserted
  638. * as-is.
  639. * @param aQueryEncoding
  640. * The value to use for the OS_PARAM_INPUT_ENCODING parameter. See
  641. * definition in the OpenSearch spec.
  642. *
  643. * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
  644. */
  645. function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
  646. var value = aParamValue;
  647. var distributionID = MOZ_DISTRIBUTION_ID;
  648. try {
  649. var prefB = Cc["@mozilla.org/preferences-service;1"].
  650. getService(Ci.nsIPrefBranch);
  651. distributionID = prefB.getCharPref(BROWSER_SEARCH_PREF + "distributionID");
  652. }
  653. catch (ex) { }
  654. // Custom search parameters. These are only available to default search
  655. // engines.
  656. if (aEngine._isDefault) {
  657. value = value.replace(MOZ_PARAM_LOCALE, getLocale());
  658. value = value.replace(MOZ_PARAM_DIST_ID, distributionID);
  659. value = value.replace(MOZ_PARAM_OFFICIAL, MOZ_OFFICIAL);
  660. }
  661. // Insert the OpenSearch parameters we're confident about
  662. value = value.replace(OS_PARAM_USER_DEFINED, aSearchTerms);
  663. value = value.replace(OS_PARAM_INPUT_ENCODING, aEngine.queryCharset);
  664. value = value.replace(OS_PARAM_LANGUAGE,
  665. getLocale() || OS_PARAM_LANGUAGE_DEF);
  666. value = value.replace(OS_PARAM_OUTPUT_ENCODING,
  667. OS_PARAM_OUTPUT_ENCODING_DEF);
  668. // Replace any optional parameters
  669. value = value.replace(OS_PARAM_OPTIONAL, "");
  670. // Insert any remaining required params with our default values
  671. for (var i = 0; i < OS_UNSUPPORTED_PARAMS.length; ++i) {
  672. value = value.replace(OS_UNSUPPORTED_PARAMS[i][0],
  673. OS_UNSUPPORTED_PARAMS[i][1]);
  674. }
  675. return value;
  676. }
  677. /**
  678. * Creates a mozStorage statement that can be used to access the database we
  679. * use to hold metadata.
  680. *
  681. * @param dbconn the database that the statement applies to
  682. * @param sql a string specifying the sql statement that should be created
  683. */
  684. function createStatement (dbconn, sql) {
  685. var stmt = dbconn.createStatement(sql);
  686. var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"].
  687. createInstance(Ci.mozIStorageStatementWrapper);
  688. wrapper.initialize(stmt);
  689. return wrapper;
  690. }
  691. /**
  692. * Creates an engineURL object, which holds the query URL and all parameters.
  693. *
  694. * @param aType
  695. * A string containing the name of the MIME type of the search results
  696. * returned by this URL.
  697. * @param aMethod
  698. * The HTTP request method. Must be a case insensitive value of either
  699. * "GET" or "POST".
  700. * @param aTemplate
  701. * The URL to which search queries should be sent. For GET requests,
  702. * must contain the string "{searchTerms}", to indicate where the user
  703. * entered search terms should be inserted.
  704. *
  705. * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
  706. *
  707. * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
  708. */
  709. function EngineURL(aType, aMethod, aTemplate) {
  710. ENSURE_ARG(aType && aMethod && aTemplate,
  711. "missing type, method or template for EngineURL!");
  712. var method = aMethod.toUpperCase();
  713. var type = aType.toLowerCase();
  714. ENSURE_ARG(method == "GET" || method == "POST",
  715. "method passed to EngineURL must be \"GET\" or \"POST\"");
  716. this.type = type;
  717. this.method = method;
  718. this.params = [];
  719. var templateURI = makeURI(aTemplate);
  720. ENSURE(templateURI, "new EngineURL: template is not a valid URI!",
  721. Cr.NS_ERROR_FAILURE);
  722. switch (templateURI.scheme) {
  723. case "http":
  724. case "https":
  725. // Disable these for now, see bug 295018
  726. // case "file":
  727. // case "resource":
  728. this.template = aTemplate;
  729. break;
  730. default:
  731. ENSURE(false, "new EngineURL: template uses invalid scheme!",
  732. Cr.NS_ERROR_FAILURE);
  733. }
  734. }
  735. EngineURL.prototype = {
  736. addParam: function SRCH_EURL_addParam(aName, aValue) {
  737. this.params.push(new QueryParameter(aName, aValue));
  738. },
  739. getSubmission: function SRCH_EURL_getSubmission(aSearchTerms, aEngine) {
  740. var url = ParamSubstitution(this.template, aSearchTerms, aEngine);
  741. // Create an application/x-www-form-urlencoded representation of our params
  742. // (name=value&name=value&name=value)
  743. var dataString = "";
  744. for (var i = 0; i < this.params.length; ++i) {
  745. var param = this.params[i];
  746. var value = ParamSubstitution(param.value, aSearchTerms, aEngine);
  747. dataString += (i > 0 ? "&" : "") + param.name + "=" + value;
  748. }
  749. var postData = null;
  750. if (this.method == "GET") {
  751. // GET method requests have no post data, and append the encoded
  752. // query string to the url...
  753. if (url.indexOf("?") == -1 && dataString)
  754. url += "?";
  755. url += dataString;
  756. } else if (this.method == "POST") {
  757. // POST method requests must wrap the encoded text in a MIME
  758. // stream and supply that as POSTDATA.
  759. var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
  760. createInstance(Ci.nsIStringInputStream);
  761. #ifdef MOZILLA_1_8_BRANCH
  762. # bug 318193
  763. stringStream.setData(dataString, dataString.length);
  764. #else
  765. stringStream.data = dataString;
  766. #endif
  767. postData = Cc["@mozilla.org/network/mime-input-stream;1"].
  768. createInstance(Ci.nsIMIMEInputStream);
  769. postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
  770. postData.addContentLength = true;
  771. postData.setData(stringStream);
  772. }
  773. return new Submission(makeURI(url), postData);
  774. },
  775. /**
  776. * Serializes the engine object to a OpenSearch Url element.
  777. * @param aDoc
  778. * The document to use to create the Url element.
  779. * @param aElement
  780. * The element to which the created Url element is appended.
  781. *
  782. * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
  783. */
  784. _serializeToElement: function SRCH_EURL_serializeToEl(aDoc, aElement) {
  785. var url = aDoc.createElementNS(OPENSEARCH_NS_11, "Url");
  786. url.setAttribute("type", this.type);
  787. url.setAttribute("method", this.method);
  788. url.setAttribute("template", this.template);
  789. for (var i = 0; i < this.params.length; ++i) {
  790. var param = aDoc.createElementNS(OPENSEARCH_NS_11, "Param");
  791. param.setAttribute("name", this.params[i].name);
  792. param.setAttribute("value", this.params[i].value);
  793. url.appendChild(aDoc.createTextNode("\n "));
  794. url.appendChild(param);
  795. }
  796. url.appendChild(aDoc.createTextNode("\n"));
  797. aElement.appendChild(url);
  798. }
  799. };
  800. /**
  801. * nsISearchEngine constructor.
  802. * @param aLocation
  803. * A nsILocalFile or nsIURI object representing the location of the
  804. * search engine data file.
  805. * @param aSourceDataType
  806. * The data type of the file used to describe the engine. Must be either
  807. * DATA_XML or DATA_TEXT.
  808. * @param aIsReadOnly
  809. * Boolean indicating whether the engine should be treated as read-only.
  810. * Read only engines cannot be serialized to file.
  811. */
  812. function Engine(aLocation, aSourceDataType, aIsReadOnly) {
  813. this._dataType = aSourceDataType;
  814. this._readOnly = aIsReadOnly;
  815. this._urls = [];
  816. if (aLocation instanceof Ci.nsILocalFile) {
  817. // we already have a file (e.g. loading engines from disk)
  818. this._file = aLocation;
  819. } else if (aLocation instanceof Ci.nsIURI) {
  820. this._uri = aLocation;
  821. switch (aLocation.scheme) {
  822. case "https":
  823. case "http":
  824. case "ftp":
  825. case "data":
  826. case "file":
  827. case "resource":
  828. this._uri = aLocation;
  829. break;
  830. default:
  831. ERROR("Invalid URI passed to the nsISearchEngine constructor",
  832. Cr.NS_ERROR_INVALID_ARG);
  833. }
  834. } else
  835. ERROR("Engine location is neither a File nor a URI object",
  836. Cr.NS_ERROR_INVALID_ARG);
  837. }
  838. Engine.prototype = {
  839. // The engine's alias.
  840. _alias: null,
  841. // The data describing the engine. Is either an array of bytes, for Sherlock
  842. // files, or an XML document element, for XML plugins.
  843. _data: null,
  844. // The engine's data type. See data types (DATA_) defined above.
  845. _dataType: null,
  846. // Whether or not the engine is readonly.
  847. _readOnly: true,
  848. // The engine's description
  849. _description: "",
  850. // Used to store the engine to replace, if we're an update to an existing
  851. // engine.
  852. _engineToUpdate: null,
  853. // The file from which the plugin was loaded.
  854. _file: null,
  855. // Set to true if the engine has a preferred icon (an icon that should not be
  856. // overridden by a non-preferred icon).
  857. _hasPreferredIcon: null,
  858. // Whether the engine is hidden from the user.
  859. _hidden: null,
  860. // The engine's name.
  861. _name: null,
  862. // The engine type. See engine types (TYPE_) defined above.
  863. _type: null,
  864. // The name of the charset used to submit the search terms.
  865. _queryCharset: null,
  866. // A URL string pointing to the engine's search form.
  867. __searchForm: null,
  868. get _searchForm() {
  869. return this.__searchForm;
  870. },
  871. set _searchForm(aValue) {
  872. if (/^https?:/i.test(aValue))
  873. this.__searchForm = aValue;
  874. else
  875. LOG("_searchForm: Invalid URL dropped for " + this._name ||
  876. "the current engine");
  877. },
  878. // The URI object from which the engine was retrieved.
  879. // This is null for local plugins, and is used for error messages and logging.
  880. _uri: null,
  881. // Whether to obtain user confirmation before adding the engine. This is only
  882. // used when the engine is first added to the list.
  883. _confirm: false,
  884. // Whether to set this as the current engine as soon as it is loaded. This
  885. // is only used when the engine is first added to the list.
  886. _useNow: true,
  887. // Where the engine was loaded from. Can be one of: SEARCH_APP_DIR,
  888. // SEARCH_PROFILE_DIR, SEARCH_IN_EXTENSION.
  889. __installLocation: null,
  890. // The number of days between update checks for new versions
  891. _updateInterval: null,
  892. // The url to check at for a new update
  893. _updateURL: null,
  894. // The url to check for a new icon
  895. _iconUpdateURL: null,
  896. // A reference to the timer used for lazily serializing the engine to file
  897. _serializeTimer: null,
  898. /**
  899. * Retrieves the data from the engine's file. If the engine's dataType is
  900. * XML, the document element is placed in the engine's data field. For text
  901. * engines, the data is just read directly from file and placed as an array
  902. * of lines in the engine's data field.
  903. */
  904. _initFromFile: function SRCH_ENG_initFromFile() {
  905. ENSURE(this._file && this._file.exists(),
  906. "File must exist before calling initFromFile!",
  907. Cr.NS_ERROR_UNEXPECTED);
  908. var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
  909. createInstance(Ci.nsIFileInputStream);
  910. fileInStream.init(this._file, MODE_RDONLY, PERMS_FILE, false);
  911. switch (this._dataType) {
  912. case SEARCH_DATA_XML:
  913. var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
  914. createInstance(Ci.nsIDOMParser);
  915. var doc = domParser.parseFromStream(fileInStream, "UTF-8",
  916. this._file.fileSize,
  917. "text/xml");
  918. this._data = doc.documentElement;
  919. break;
  920. case SEARCH_DATA_TEXT:
  921. var binaryInStream = Cc["@mozilla.org/binaryinputstream;1"].
  922. createInstance(Ci.nsIBinaryInputStream);
  923. binaryInStream.setInputStream(fileInStream);
  924. var bytes = binaryInStream.readByteArray(binaryInStream.available());
  925. this._data = bytes;
  926. break;
  927. default:
  928. ERROR("Bogus engine _dataType: \"" + this._dataType + "\"",
  929. Cr.NS_ERROR_UNEXPECTED);
  930. }
  931. fileInStream.close();
  932. // Now that the data is loaded, initialize the engine object
  933. this._initFromData();
  934. },
  935. /**
  936. * Retrieves the engine data from a URI.
  937. */
  938. _initFromURI: function SRCH_ENG_initFromURI() {
  939. ENSURE_WARN(this._uri instanceof Ci.nsIURI,
  940. "Must have URI when calling _initFromURI!",
  941. Cr.NS_ERROR_UNEXPECTED);
  942. LOG("_initFromURI: Downloading engine from: \"" + this._uri.spec + "\".");
  943. var ios = Cc["@mozilla.org/network/io-service;1"].
  944. getService(Ci.nsIIOService);
  945. var chan = ios.newChannelFromURI(this._uri);
  946. if (this._engineToUpdate && (chan instanceof Ci.nsIHttpChannel)) {
  947. var lastModified = engineMetadataService.getAttr(this._engineToUpdate,
  948. "updatelastmodified");
  949. if (lastModified)
  950. chan.setRequestHeader("If-Modified-Since", lastModified, false);
  951. }
  952. var listener = new loadListener(chan, this, this._onLoad);
  953. chan.notificationCallbacks = listener;
  954. chan.asyncOpen(listener, null);
  955. },
  956. /**
  957. * Attempts to find an EngineURL object in the set of EngineURLs for
  958. * this Engine that has the given type string. (This corresponds to the
  959. * "type" attribute in the "Url" node in the OpenSearch spec.)
  960. * This method will return the first matching URL object found, or null
  961. * if no matching URL is found.
  962. *
  963. * @param aType string to match the EngineURL's type attribute
  964. */
  965. _getURLOfType: function SRCH_ENG__getURLOfType(aType) {
  966. for (var i = 0; i < this._urls.length; ++i) {
  967. if (this._urls[i].type == aType)
  968. return this._urls[i];
  969. }
  970. return null;
  971. },
  972. _confirmAddEngine: function SRCH_SVC_confirmAddEngine() {
  973. var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  974. getService(Ci.nsIStringBundleService);
  975. var stringBundle = sbs.createBundle(SEARCH_BUNDLE);
  976. var titleMessage = stringBundle.GetStringFromName("addEngineConfirmTitle");
  977. // Display only the hostname portion of the URL.
  978. var dialogMessage =
  979. stringBundle.formatStringFromName("addEngineConfirmation",
  980. [this._name, this._uri.host], 2);
  981. var checkboxMessage = stringBundle.GetStringFromName("addEngineUseNowText");
  982. var addButtonLabel =
  983. stringBundle.GetStringFromName("addEngineAddButtonLabel");
  984. var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  985. getService(Ci.nsIPromptService);
  986. var buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
  987. (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1) +
  988. ps.BUTTON_POS_0_DEFAULT;
  989. var checked = {value: false};
  990. // confirmEx returns the index of the button that was pressed. Since "Add"
  991. // is button 0, we want to return the negation of that value.
  992. var confirm = !ps.confirmEx(null,
  993. titleMessage,
  994. dialogMessage,
  995. buttonFlags,
  996. addButtonLabel,
  997. null, null, // button 1 & 2 names not used
  998. checkboxMessage,
  999. checked);
  1000. return {confirmed: confirm, useNow: checked.value};
  1001. },
  1002. /**
  1003. * Handle the successful download of an engine. Initializes the engine and
  1004. * triggers parsing of the data. The engine is then flushed to disk. Notifies
  1005. * the search service once initialization is complete.
  1006. */
  1007. _onLoad: function SRCH_ENG_onLoad(aBytes, aEngine) {
  1008. /**
  1009. * Handle an error during the load of an engine by prompting the user to
  1010. * notify him that the load failed.
  1011. */
  1012. function onError(aErrorString, aTitleString) {
  1013. if (aEngine._engineToUpdate) {
  1014. // We're in an update, so just fail quietly
  1015. LOG("updating " + aEngine._engineToUpdate.name + " failed");
  1016. return;
  1017. }
  1018. var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
  1019. getService(Ci.nsIStringBundleService);
  1020. var brandBundle = sbs.createBundle(BRAND_BUNDLE);
  1021. var brandName = brandBundle.GetStringFromName("brandShortName");
  1022. var searchBundle = sbs.createBundle(SEARCH_BUNDLE);
  1023. var msgStringName = aErrorString || "error_loading_engine_msg2";
  1024. var titleStringName = aTitleString || "error_loading_engine_title";
  1025. var title = searchBundle.GetStringFromName(titleStringName);
  1026. var text = searchBundle.formatStringFromName(msgStringName,
  1027. [brandName, aEngine._location],
  1028. 2);
  1029. var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
  1030. getService(Ci.nsIWindowWatcher);
  1031. ww.getNewPrompter(null).alert(title, text);
  1032. }
  1033. if (!aBytes) {
  1034. onError();
  1035. return;
  1036. }
  1037. var engineToUpdate = null;
  1038. if (aEngine._engineToUpdate) {
  1039. engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
  1040. // Make this new engine use the old engine's file.
  1041. aEngine._file = engineToUpdate._file;
  1042. }
  1043. switch (aEngine._dataType) {
  1044. case SEARCH_DATA_XML:
  1045. var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
  1046. createInstance(Ci.nsIDOMParser);
  1047. var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
  1048. aEngine._data = doc.documentElement;
  1049. break;
  1050. case SEARCH_DATA_TEXT:
  1051. aEngine._data = aBytes;
  1052. break;
  1053. default:
  1054. onError();
  1055. LOG("_onLoad: Bogus engine _dataType: \"" + this._dataType + "\"");
  1056. return;
  1057. }
  1058. try {
  1059. // Initialize the engine from the obtained data
  1060. aEngine._initFromData();
  1061. } catch (ex) {
  1062. LOG("_onLoad: Failed to init engine!\n" + ex);
  1063. // Report an error to the user
  1064. onError();
  1065. return;
  1066. }
  1067. // Check to see if this is a duplicate engine. If we're confirming the
  1068. // engine load, then we display a "this is a duplicate engine" prompt,
  1069. // otherwise we fail silently.
  1070. if (!engineToUpdate) {
  1071. var ss = Cc["@mozilla.org/browser/search-service;1"].
  1072. getService(Ci.nsIBrowserSearchService);
  1073. if (ss.getEngineByName(aEngine.name)) {
  1074. if (aEngine._confirm)
  1075. onError("error_duplicate_engine_msg", "error_invalid_engine_title");
  1076. LOG("_onLoad: duplicate engine found, bailing");
  1077. return;
  1078. }
  1079. }
  1080. // If requested, confirm the addition now that we have the title.
  1081. // This property is only ever true for engines added via
  1082. // nsIBrowserSearchService::addEngine.
  1083. if (aEngine._confirm) {
  1084. var confirmation = aEngine._confirmAddEngine();
  1085. LOG("_onLoad: confirm is " + confirmation.confirmed +
  1086. "; useNow is " + confirmation.useNow);
  1087. if (!confirmation.confirmed)
  1088. return;
  1089. aEngine._useNow = confirmation.useNow;
  1090. }
  1091. // If we don't yet have a file, get one now. The only case where we would
  1092. // already have a file is if this is an update and _file was set above.
  1093. if (!aEngine._file)
  1094. aEngine._file = getSanitizedFile(aEngine.name);
  1095. if (engineToUpdate) {
  1096. // Keep track of the last modified date, so that we can make conditional
  1097. // requests for future updates.
  1098. engineMetadataService.setAttr(aEngine, "updatelastmodified",
  1099. (new Date()).toUTCString());
  1100. // Set the new engine's icon, if it doesn't yet have one.
  1101. if (!aEngine._iconURI && engineToUpdate._iconURI)
  1102. aEngine._iconURI = engineToUpdate._iconURI;
  1103. // Clear the "use now" flag since we don't want to be changing the
  1104. // current engine for an update.
  1105. aEngine._useNow = false;
  1106. }
  1107. // Write the engine to file
  1108. aEngine._serializeToFile();
  1109. // Notify the search service of the sucessful load. It will deal with
  1110. // updates by checking aEngine._engineToUpdate.
  1111. notifyAction(aEngine, SEARCH_ENGINE_LOADED);
  1112. },
  1113. /**
  1114. * Sets the .iconURI property of the engine.
  1115. *
  1116. * @param aIconURL
  1117. * A URI string pointing to the engine's icon. Must have a http[s],
  1118. * ftp, or data scheme. Icons with HTTP[S] or FTP schemes will be
  1119. * downloaded and converted to data URIs for storage in the engine
  1120. * XML files, if the engine is not readonly.
  1121. * @param aIsPreferred
  1122. * Whether or not this icon is to be preferred. Preferred icons can
  1123. * override non-preferred icons.
  1124. */
  1125. _setIcon: function SRCH_ENG_setIcon(aIconURL, aIsPreferred) {
  1126. // If we already have a preferred icon, and this isn't a preferred icon,
  1127. // just ignore it.
  1128. if (this._hasPreferredIcon && !aIsPreferred)
  1129. return;
  1130. var uri = makeURI(aIconURL);
  1131. // Ignore bad URIs
  1132. if (!uri)
  1133. return;
  1134. LOG("_setIcon: Setting icon url \"" + uri.spec + "\" for engine \""
  1135. + this.name + "\".");
  1136. // Only accept remote icons from http[s] or ftp
  1137. switch (uri.scheme) {
  1138. case "data":
  1139. this._iconURI = uri;
  1140. notifyAction(this, SEARCH_ENGINE_CHANGED);
  1141. this._hasPreferredIcon = aIsPreferred;
  1142. break;
  1143. case "http":
  1144. case "https":
  1145. case "ftp":
  1146. // No use downloading the icon if the engine file is read-only
  1147. if (!this._readOnly) {
  1148. LOG("_setIcon: Downloading icon: \"" + uri.spec +
  1149. "\" for engine: \"" + this.name + "\"");
  1150. var ios = Cc["@mozilla.org/network/io-service;1"].
  1151. getService(Ci.nsIIOService);
  1152. var chan = ios.newChannelFromURI(uri);
  1153. function iconLoadCallback(aByteArray, aEngine) {
  1154. // This callback may run after we've already set a preferred icon,
  1155. // so check again.
  1156. if (aEngine._hasPreferredIcon && !aIsPreferred)
  1157. return;
  1158. if (!aByteArray || aByteArray.length > MAX_ICON_SIZE) {
  1159. LOG("iconLoadCallback: load failed, or the icon was too large!");
  1160. return;
  1161. }
  1162. var str = btoa(String.fromCharCode.apply(null, aByteArray));
  1163. aEngine._iconURI = makeURI(ICON_DATAURL_PREFIX + str);
  1164. // The engine might not have a file yet, if it's being downloaded,
  1165. // because the request for the engine file itself (_onLoad) may not
  1166. // yet be complete. In that case, this change will be written to
  1167. // file when _onLoad is called.
  1168. if (aEngine._file)
  1169. aEngine._serializeToFile();
  1170. notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
  1171. aEngine._hasPreferredIcon = aIsPreferred;
  1172. }
  1173. // If we're currently acting as an "update engine", then the callback
  1174. // should set the icon on the engine we're updating and not us, since
  1175. // |this| might be gone by the time the callback runs.
  1176. var engineToSet = this._engineToUpdate || this;
  1177. var listener = new loadListener(chan, engineToSet, iconLoadCallback);
  1178. chan.notificationCallbacks = listener;
  1179. chan.asyncOpen(listener, null);
  1180. }
  1181. break;
  1182. }
  1183. },
  1184. /**
  1185. * Initialize this Engine object from the collected data.
  1186. */
  1187. _initFromData: function SRCH_ENG_initFromData() {
  1188. ENSURE_WARN(this._data, "Can't init an engine with no data!",
  1189. Cr.NS_ERROR_UNEXPECTED);
  1190. // Find out what type of engine we are
  1191. switch (this._dataType) {
  1192. case SEARCH_DATA_XML:
  1193. if (checkNameSpace(this._data, [MOZSEARCH_LOCALNAME],
  1194. [MOZSEARCH_NS_10])) {
  1195. LOG("_init: Initing MozSearch plugin from " + this._location);
  1196. this._type = SEARCH_TYPE_MOZSEARCH;
  1197. this._parseAsMozSearch();
  1198. } else if (checkNameSpace(this._data, [OPENSEARCH_LOCALNAME],
  1199. OPENSEARCH_NAMESPACES)) {
  1200. LOG("_init: Initing OpenSearch plugin from " + this._location);
  1201. this._type = SEARCH_TYPE_OPENSEARCH;
  1202. this._parseAsOpenSearch();
  1203. } else
  1204. ENSURE(false, this._location + " is not a valid search plugin.",
  1205. Cr.NS_ERROR_FAILURE);
  1206. break;
  1207. case SEARCH_DATA_TEXT:
  1208. LOG("_init: Initing Sherlock plugin from " + this._location);
  1209. // the only text-based format we support is Sherlock
  1210. this._type = SEARCH_TYPE_SHERLOCK;
  1211. this._parseAsSherlock();
  1212. }
  1213. // No need to keep a ref to our data (which in some cases can be a document
  1214. // element) past this point
  1215. this._data = null;
  1216. },
  1217. /**
  1218. * Initialize this Engine object from a collection of metadata.
  1219. */
  1220. _initFromMetadata: function SRCH_ENG_initMetaData(aName, aIconURL, aAlias,
  1221. aDescription, aMethod,
  1222. aTemplate) {
  1223. ENSURE_WARN(!this._readOnly,
  1224. "Can't call _initFromMetaData on a readonly engine!",
  1225. Cr.NS_ERROR_FAILURE);
  1226. this._urls.push(new EngineURL("text/html", aMethod, aTemplate));
  1227. this._name = aName;
  1228. this.alias = aAlias;
  1229. this._description = aDescription;
  1230. this._setIcon(aIconURL, true);
  1231. this._serializeToFile();
  1232. },
  1233. /**
  1234. * Extracts data from an OpenSearch URL element and creates an EngineURL
  1235. * object which is then added to the engine's list of URLs.
  1236. *
  1237. * @throws NS_ERROR_FAILURE if a URL object could not be created.
  1238. *
  1239. * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag.
  1240. * @see EngineURL()
  1241. */
  1242. _parseURL: function SRCH_ENG_parseURL(aElement) {
  1243. var type = aElement.getAttribute("type");
  1244. // According to the spec, method is optional, defaulting to "GET" if not
  1245. // specified
  1246. var method = aElement.getAttribute("method") || "GET";
  1247. var template = aElement.getAttribute("template");
  1248. try {
  1249. var url = new EngineURL(type, method, template);
  1250. } catch (ex) {
  1251. LOG("_parseURL: failed to add " + template + " as a URL");
  1252. throw Cr.NS_ERROR_FAILURE;
  1253. }
  1254. for (var i = 0; i < aElement.childNodes.length; ++i) {
  1255. var param = aElement.childNodes[i];
  1256. if (param.localName == "Param") {
  1257. try {
  1258. url.addParam(param.getAttribute("name"), param.getAttribute("value"));
  1259. } catch (ex) {
  1260. // Ignore failure
  1261. LOG("_parseURL: Url element has an invalid param");
  1262. }
  1263. } else if (param.localName == "MozParam" &&
  1264. // We only support MozParams for default search engines
  1265. this._isDefault) {
  1266. var value;
  1267. switch (param.getAttribute("condition")) {
  1268. case "defaultEngine":
  1269. const defPref = BROWSER_SEARCH_PREF + "defaultenginename";
  1270. var defaultPrefB = Cc["@mozilla.org/preferences-service;1"].
  1271. getService(Ci.nsIPrefService).
  1272. getDefaultBranch(null);
  1273. const nsIPLS = Ci.nsIPrefLocalizedString;
  1274. var defaultName;
  1275. try {
  1276. defaultName = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
  1277. } catch (ex) {}
  1278. // If this engine was the default search engine, use the true value
  1279. if (this.name == defaultName)
  1280. value = param.getAttribute("trueValue");
  1281. else
  1282. value = param.getAttribute("falseValue");
  1283. url.addParam(param.getAttribute("name"), value);
  1284. break;
  1285. case "pref":
  1286. try {
  1287. var prefB = Cc["@mozilla.org/preferences-service;1"].
  1288. getService(Ci.nsIPrefBranch);
  1289. value = prefB.getCharPref(BROWSER_SEARCH_PREF + "param." +
  1290. param.getAttribute("pref"));
  1291. url.addParam(param.getAttribute("name"), value);
  1292. } catch (e) { }
  1293. break;
  1294. }
  1295. }
  1296. }
  1297. this._urls.push(url);
  1298. },
  1299. /**
  1300. * Get the icon from an OpenSearch Image element.
  1301. * @see http://opensearch.a9.com/spec/1.1/description/#image
  1302. */
  1303. _parseImage: function SRCH_ENG_parseImage(aElement) {
  1304. LOG("_parseImage: Image textContent: \"" + aElement.textContent + "\"");
  1305. if (aElement.getAttribute("width") == "16" &&
  1306. aElement.getAttribute("height") == "16") {
  1307. this._setIcon(aElement.textContent, true);
  1308. }
  1309. },
  1310. _parseAsMozSearch: function SRCH_ENG_parseAsMoz() {
  1311. //forward to the OpenSearch parser
  1312. this._parseAsOpenSearch();
  1313. },
  1314. /**
  1315. * Extract search engine information from the collected data to initialize
  1316. * the engine object.
  1317. */
  1318. _parseAsOpenSearch: function SRCH_ENG_parseAsOS() {
  1319. var doc = this._data;
  1320. // The OpenSearch spec sets a default value for the input encoding.
  1321. this._queryCharset = OS_PARAM_INPUT_ENCODING_DEF;
  1322. for (var i = 0; i < doc.childNodes.length; ++i) {
  1323. var child = doc.childNodes[i];
  1324. switch (child.localName) {
  1325. case "ShortName":
  1326. this._name = child.textContent;
  1327. break;
  1328. case "Description":
  1329. this._description = child.textContent;
  1330. break;
  1331. case "Url":
  1332. try {
  1333. this._parseURL(child);
  1334. } catch (ex) {
  1335. // Parsing of the element failed, just skip it.
  1336. }
  1337. break;
  1338. case "Image":
  1339. this._parseImage(child);
  1340. break;
  1341. case "InputEncoding":
  1342. this._queryCharset = child.textContent.toUpperCase();
  1343. break;
  1344. // Non-OpenSearch elements
  1345. case "SearchForm":
  1346. this._searchForm = child.textContent;
  1347. break;
  1348. case "UpdateUrl":
  1349. this._updateURL = child.textContent;
  1350. break;
  1351. case "UpdateInterval":
  1352. this._updateInterval = parseInt(child.textContent);
  1353. break;
  1354. case "IconUpdateUrl":
  1355. this._iconUpdateURL = child.textContent;
  1356. break;
  1357. }
  1358. }
  1359. ENSURE(this.name && (this._urls.length > 0),
  1360. "_parseAsOpenSearch: No name, or missing URL!",
  1361. Cr.NS_ERROR_FAILURE);
  1362. ENSURE(this.supportsResponseType(URLTYPE_SEARCH_HTML),
  1363. "_parseAsOpenSearch: No text/html result type!",
  1364. Cr.NS_ERROR_FAILURE);
  1365. },
  1366. /**
  1367. * Extract search engine information from the collected data to initialize
  1368. * the engine object.
  1369. */
  1370. _parseAsSherlock: function SRCH_ENG_parseAsSherlock() {
  1371. /**
  1372. * Trims leading and trailing whitespace from aStr.
  1373. */
  1374. function sTrim(aStr) {
  1375. return aStr.replace(/^\s+/g, "").replace(/\s+$/g, "");
  1376. }
  1377. /**
  1378. * Extracts one Sherlock "section" from aSource. A section is essentially
  1379. * an HTML element with attributes, but each attribute must be on a new
  1380. * line, by definition.
  1381. *
  1382. * @param aLines
  1383. * An array of lines from the sherlock file.
  1384. * @param aSection
  1385. * The name of the section (e.g. "search" or "browser"). This value
  1386. * is not case sensitive.
  1387. * @returns an object whose properties correspond to the section's
  1388. * attributes.
  1389. */
  1390. function getSection(aLines, aSection) {
  1391. LOG("_parseAsSherlock::getSection: Sherlock lines:\n" +
  1392. aLines.join("\n"));
  1393. var lines = aLines;
  1394. var startMark = new RegExp("^\\s*<" + aSection.toLowerCase() + "\\s*",
  1395. "gi");
  1396. var endMark = /\s*>\s*$/gi;
  1397. var foundStart = false;
  1398. var startLine, numberOfLines;
  1399. // Find the beginning and end of the section
  1400. for (var i = 0; i < lines.length; i++) {
  1401. if (foundStart) {
  1402. if (endMark.test(lines[i])) {
  1403. numberOfLines = i - startLine;
  1404. // Remove the end marker
  1405. lines[i] = lines[i].replace(endMark, "");
  1406. // If the endmarker was not the only thing on the line, include
  1407. // this line in the results
  1408. if (lines[i])
  1409. numberOfLines++;
  1410. break;
  1411. }
  1412. } else {
  1413. if (startMark.test(lines[i])) {
  1414. foundStart = true;
  1415. // Remove the start marker
  1416. lines[i] = lines[i].replace(startMark, "");
  1417. startLine = i;
  1418. // If the line is empty, don't include it in the result
  1419. if (!lines[i])
  1420. startLine++;
  1421. }
  1422. }
  1423. }
  1424. LOG("_parseAsSherlock::getSection: Start index: " + startLine +
  1425. "\nNumber of lines: " + numberOfLines);
  1426. lines = lines.splice(startLine, numberOfLines);
  1427. LOG("_parseAsSherlock::getSection: Section lines:\n" +
  1428. lines.join("\n"));
  1429. var section = {};
  1430. for (var i = 0; i < lines.length; i++) {
  1431. var line = sTrim(lines[i]);
  1432. var els = line.split("=");
  1433. var name = sTrim(els.shift().toLowerCase());
  1434. var value = sTrim(els.join("="));
  1435. if (!name || !value)
  1436. continue;
  1437. // Strip leading and trailing whitespace, remove quotes from the
  1438. // value, and remove any trailing slashes or ">" characters
  1439. value = value.replace(/^["']/, "")
  1440. .replace(/["']\s*[\\\/]?>?\s*$/, "") || "";
  1441. value = sTrim(value);
  1442. // Don't clobber existing attributes
  1443. if (!(name in section))
  1444. section[name] = value;
  1445. }
  1446. return section;
  1447. }
  1448. /**
  1449. * Returns an array of name-value pair arrays representing the Sherlock
  1450. * file's input elements. User defined inputs return USER_DEFINED
  1451. * as the value. Elements are returned in the order they appear in the
  1452. * source file.
  1453. *
  1454. * Example:
  1455. * <input name="foo" value="bar">
  1456. * <input name="foopy" user>
  1457. * Returns:
  1458. * [["foo", "bar"], ["foopy", "{searchTerms}"]]
  1459. *
  1460. * @param aLines
  1461. * An array of lines from the source file.
  1462. */
  1463. function getInputs(aLines) {
  1464. /**
  1465. * Extracts an attribute value from a given a line of text.
  1466. * Example: <input value="foo" name="bar">
  1467. * Extracts the string |foo| or |bar| given an input aAttr of
  1468. * |value| or |name|.
  1469. * Attributes may be quoted or unquoted. If unquoted, any whitespace
  1470. * indicates the end of the attribute value.
  1471. * Example: < value=22 33 name=44\334 >
  1472. * Returns |22| for "value" and |44\334| for "name".
  1473. *
  1474. * @param aAttr
  1475. * The name of the attribute for which to obtain the value. This
  1476. * value is not case sensitive.
  1477. * @param aLine
  1478. * The line containing the attribute.
  1479. *
  1480. * @returns the attribute value, or an empty string if the attribute
  1481. * doesn't exist.
  1482. */
  1483. function getAttr(aAttr, aLine) {
  1484. // Used to determine whether an "input" line from a Sherlock file is a
  1485. // "user defined" input.
  1486. const userInput = /(\s|["'=])user(\s|[>="'\/\\+]|$)/i;
  1487. LOG("_parseAsSherlock::getAttr: Getting attr: \"" +
  1488. aAttr + "\" for line: \"" + aLine + "\"");
  1489. // We're not case sensitive, but we want to return the attribute value
  1490. // in its original case, so create a copy of the source
  1491. var lLine = aLine.toLowerCase();
  1492. var attr = aAttr.toLowerCase();
  1493. var attrStart = lLine.search(new RegExp("\\s" + attr, "i"));
  1494. if (attrStart == -1) {
  1495. // If this is the "user defined input" (i.e. contains the empty
  1496. // "user" attribute), return our special keyword
  1497. if (userInput.test(lLine) && attr == "value") {
  1498. LOG("_parseAsSherlock::getAttr: Found user input!\nLine:\"" + lLine
  1499. + "\"");
  1500. return USER_DEFINED;
  1501. }
  1502. // The attribute doesn't exist - ignore
  1503. LOG("_parseAsSherlock::getAttr: Failed to find attribute:\nLine:\""
  1504. + lLine + "\"\nAttr:\"" + attr + "\"");
  1505. return "";
  1506. }
  1507. var valueStart = lLine.indexOf("=", attrStart) + "=".length;
  1508. if (valueStart == -1)
  1509. return "";
  1510. var quoteStart = lLine.indexOf("\"", valueStart);
  1511. if (quoteStart == -1) {
  1512. // Unquoted attribute, get the rest of the line, trimmed at the first
  1513. // sign of whitespace. If the rest of the line is only whitespace,
  1514. // returns a blank string.
  1515. return lLine.substr(valueStart).replace(/\s.*$/, "");
  1516. } else {
  1517. // Make sure that there's only whitespace between the start of the
  1518. // value and the first quote. If there is, end the attribute value at
  1519. // the first sign of whitespace. This prevents us from falling into
  1520. // the next attribute if this is an unquoted attribute followed by a
  1521. // quoted attribute.
  1522. var betweenEqualAndQuote = lLine.substring(valueStart, quoteStart);
  1523. if (/\S/.test(betweenEqualAndQuote))
  1524. return lLine.substr(valueStart).replace(/\s.*$/, "");
  1525. // Adjust the start index to account for the opening quote
  1526. valueStart = quoteStart + "\"".length;
  1527. // Find the closing quote
  1528. valueEnd = lLine.indexOf("\"", valueStart);
  1529. // If there is no closing quote, just go to the end of the line
  1530. if (valueEnd == -1)
  1531. valueEnd = aLine.length;
  1532. }
  1533. return aLine.substring(valueStart, valueEnd);
  1534. }
  1535. var inputs = [];
  1536. LOG("_parseAsSherlock::getInputs: Lines:\n" + aLines);
  1537. // Filter out everything but non-inputs
  1538. lines = aLines.filter(function (line) {
  1539. return /^\s*<input/i.test(line);
  1540. });
  1541. LOG("_parseAsSherlock::getInputs: Filtered lines:\n" + lines);
  1542. lines.forEach(function (line) {
  1543. // Strip leading/trailing whitespace and remove the surrounding markup
  1544. // ("<input" and ">")
  1545. line = sTrim(line).replace(/^<input/i, "").replace(/>$/, "");
  1546. // If this is one of the "directional" inputs (<inputnext>/<inputprev>)
  1547. const directionalInput = /^(prev|next)/i;
  1548. if (directionalInput.test(line)) {
  1549. // Make it look like a normal input by removing "prev" or "next"
  1550. line = line.replace(directionalInput, "");
  1551. // If it has a name, give it a dummy value to match previous
  1552. // nsInternetSearchService behavior
  1553. if (/name\s*=/i.test(line)) {
  1554. line += " value=\"0\"";
  1555. } else
  1556. return; // Line has no name, skip it
  1557. }
  1558. var attrName = getAttr("name", line);
  1559. var attrValue = getAttr("value", line);
  1560. LOG("_parseAsSherlock::getInputs: Got input:\nName:\"" + attrName +
  1561. "\"\nValue:\"" + attrValue + "\"");
  1562. if (attrValue)
  1563. inputs.push([attrName, attrValue]);
  1564. });
  1565. return inputs;
  1566. }
  1567. function err(aErr) {
  1568. LOG("_parseAsSherlock::err: Sherlock param error:\n" + aErr);
  1569. throw Cr.NS_ERROR_FAILURE;
  1570. }
  1571. // First try converting our byte array using the default Sherlock encoding.
  1572. // If this fails, or if we find a sourceTextEncoding attribute, we need to
  1573. // reconvert the byte array using the specified encoding.
  1574. var sherlockLines, searchSection, sourceTextEncoding, browserSection;
  1575. try {
  1576. sherlockLines = sherlockBytesToLines(this._data);
  1577. searchSection = getSection(sherlockLines, "search");
  1578. browserSection = getSection(sherlockLines, "browser");
  1579. sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]);
  1580. if (sourceTextEncoding) {
  1581. // Re-convert the bytes using the found sourceTextEncoding
  1582. sherlockLines = sherlockBytesToLines(this._data, sourceTextEncoding);
  1583. searchSection = getSection(sherlockLines, "search");
  1584. browserSection = getSection(sherlockLines, "browser");
  1585. }
  1586. } catch (ex) {
  1587. // The conversion using the default charset failed. Remove any non-ascii
  1588. // bytes and try to find a sourceTextEncoding.
  1589. var asciiBytes = this._data.filter(function (n) {return !(0x80 & n);});
  1590. var asciiString = String.fromCharCode.apply(null, asciiBytes);
  1591. sherlockLines = asciiString.split(NEW_LINES).filter(isUsefulLine);
  1592. searchSection = getSection(sherlockLines, "search");
  1593. sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]);
  1594. if (sourceTextEncoding) {
  1595. sherlockLines = sherlockBytesToLines(this._data, sourceTextEncoding);
  1596. searchSection = getSection(sherlockLines, "search");
  1597. browserSection = getSection(sherlockLines, "browser");
  1598. } else
  1599. ERROR("Couldn't find a working charset", Cr.NS_ERROR_FAILURE);
  1600. }
  1601. LOG("_parseAsSherlock: Search section:\n" + searchSection.toSource());
  1602. this._name = searchSection["name"] || err("Missing name!");
  1603. this._description = searchSection["description"] || "";
  1604. this._queryCharset = searchSection["querycharset"] ||
  1605. queryCharsetFromCode(searchSection["queryencoding"]);
  1606. this._searchForm = searchSection["searchform"];
  1607. this._updateInterval = parseInt(browserSection["updatecheckdays"]);
  1608. this._updateURL = browserSection["update"];
  1609. this._iconUpdateURL = browserSection["updateicon"];
  1610. var method = (searchSection["method"] || "GET").toUpperCase();
  1611. var template = searchSection["action"] || err("Missing action!");
  1612. var inputs = getInputs(sherlockLines);
  1613. LOG("_parseAsSherlock: Inputs:\n" + inputs.toSource());
  1614. var url = null;
  1615. if (method == "GET") {
  1616. // Here's how we construct the input string:
  1617. // <input> is first: Name Attr: Prefix Data Example:
  1618. // YES EMPTY None <value> TEMPLATE<value>
  1619. // YES NON-EMPTY ? <name>=<value> TEMPLATE?<name>=<value>
  1620. // NO EMPTY ------------- <ignored> --------------
  1621. // NO NON-EMPTY & <name>=<value> TEMPLATE?<n1>=<v1>&<n2>=<v2>
  1622. for (var i = 0; i < inputs.length; i++) {
  1623. var name = inputs[i][0];
  1624. var value = inputs[i][1];
  1625. if (i==0) {
  1626. if (name == "")
  1627. template += USER_DEFINED;
  1628. else
  1629. template += "?" + name + "=" + value;
  1630. } else if (name != "")
  1631. template += "&" + name + "=" + value;
  1632. }
  1633. url = new EngineURL("text/html", method, template);
  1634. } else if (method == "POST") {
  1635. // Create the URL object and just add the parameters directly
  1636. url = new EngineURL("text/html", method, template);
  1637. for (var i = 0; i < inputs.length; i++) {
  1638. var name = inputs[i][0];
  1639. var value = inputs[i][1];
  1640. if (name)
  1641. url.addParam(name, value);
  1642. }
  1643. } else
  1644. err("Invalid method!");
  1645. this._urls.push(url);
  1646. },
  1647. /**
  1648. * Returns an XML document object containing the search plugin information,
  1649. * which can later be used to reload the engine.
  1650. */
  1651. _serializeToElement: function SRCH_ENG_serializeToEl() {
  1652. function appendTextNode(aNameSpace, aLocalName, aValue) {
  1653. if (!aValue)
  1654. return null;
  1655. var node = doc.createElementNS(aNameSpace, aLocalName);
  1656. node.appendChild(doc.createTextNode(aValue));
  1657. docElem.appendChild(node);
  1658. docElem.appendChild(doc.createTextNode("\n"));
  1659. return node;
  1660. }
  1661. var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
  1662. createInstance(Ci.nsIDOMParser);
  1663. var doc = parser.parseFromString(EMPTY_DOC, "text/xml");
  1664. var docElem = doc.documentElement;
  1665. docElem.appendChild(doc.createTextNode("\n"));
  1666. appendTextNode(OPENSEARCH_NS_11, "ShortName", this.name);
  1667. appendTextNode(OPENSEARCH_NS_11, "Description", this._description);
  1668. appendTextNode(OPENSEARCH_NS_11, "InputEncoding", this._queryCharset);
  1669. if (this._iconURI) {
  1670. var imageNode = appendTextNode(OPENSEARCH_NS_11, "Image",
  1671. this._iconURI.spec);
  1672. if (imageNode) {
  1673. imageNode.setAttribute("width", "16");
  1674. imageNode.setAttribute("height", "16");
  1675. }
  1676. }
  1677. appendTextNode(MOZSEARCH_NS_10, "UpdateInterval", this._updateInterval);
  1678. appendTextNode(MOZSEARCH_NS_10, "UpdateUrl", this._updateURL);
  1679. appendTextNode(MOZSEARCH_NS_10, "IconUpdateUrl", this._iconUpdateURL);
  1680. appendTextNode(MOZSEARCH_NS_10, "SearchForm", this._searchForm);
  1681. for (var i = 0; i < this._urls.length; ++i)
  1682. this._urls[i]._serializeToElement(doc, docElem);
  1683. docElem.appendChild(doc.createTextNode("\n"));
  1684. return doc;
  1685. },
  1686. _lazySerializeToFile: function SRCH_ENG_serializeToFile() {
  1687. if (this._serializeTimer) {
  1688. // Reset the timer
  1689. this._serializeTimer.delay = LAZY_SERIALIZE_DELAY;
  1690. } else {
  1691. this._serializeTimer = Cc["@mozilla.org/timer;1"].
  1692. createInstance(Ci.nsITimer);
  1693. var timerCallback = {
  1694. self: this,
  1695. notify: function SRCH_ENG_notify(aTimer) {
  1696. try {
  1697. this.self._serializeToFile();
  1698. } catch (ex) {
  1699. LOG("Serialization from timer callback failed:\n" + ex);
  1700. }
  1701. this.self._serializeTimer = null;
  1702. }
  1703. };
  1704. this._serializeTimer.initWithCallback(timerCallback,
  1705. LAZY_SERIALIZE_DELAY,
  1706. Ci.nsITimer.TYPE_ONE_SHOT);
  1707. }
  1708. },
  1709. /**
  1710. * Serializes the engine object to file.
  1711. */
  1712. _serializeToFile: function SRCH_ENG_serializeToFile() {
  1713. var file = this._file;
  1714. ENSURE_WARN(!this._readOnly, "Can't serialize a read only engine!",
  1715. Cr.NS_ERROR_FAILURE);
  1716. ENSURE_WARN(file && file.exists(), "Can't serialize: file doesn't exist!",
  1717. Cr.NS_ERROR_UNEXPECTED);
  1718. var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  1719. createInstance(Ci.nsIFileOutputStream);
  1720. // Serialize the engine first - we don't want to overwrite a good file
  1721. // if this somehow fails.
  1722. var doc = this._serializeToElement();
  1723. fos.init(file, (MODE_WRONLY | MODE_TRUNCATE), PERMS_FILE, 0);
  1724. try {
  1725. var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
  1726. createInstance(Ci.nsIDOMSerializer);
  1727. serializer.serializeToStream(doc.documentElement, fos, null);
  1728. } catch (e) {
  1729. LOG("_serializeToFile: Error serializing engine:\n" + e);
  1730. }
  1731. closeSafeOutputStream(fos);
  1732. },
  1733. /**
  1734. * Remove the engine's file from disk. The search service calls this once it
  1735. * removes the engine from its internal store. This function will throw if
  1736. * the file cannot be removed.
  1737. */
  1738. _remove: function SRCH_ENG_remove() {
  1739. ENSURE(!this._readOnly, "Can't remove read only engine!",
  1740. Cr.NS_ERROR_FAILURE);
  1741. ENSURE(this._file && this._file.exists(),
  1742. "Can't remove engine: file doesn't exist!",
  1743. Cr.NS_ERROR_FILE_NOT_FOUND);
  1744. this._file.remove(false);
  1745. },
  1746. // nsISearchEngine
  1747. get alias() {
  1748. if (this._alias === null)
  1749. this._alias = engineMetadataService.getAttr(this, "alias");
  1750. return this._alias;
  1751. },
  1752. set alias(val) {
  1753. this._alias = val;
  1754. engineMetadataService.setAttr(this, "alias", val);
  1755. notifyAction(this, SEARCH_ENGINE_CHANGED);
  1756. },
  1757. get description() {
  1758. return this._description;
  1759. },
  1760. get hidden() {
  1761. if (this._hidden === null)
  1762. this._hidden = engineMetadataService.getAttr(this, "hidden");
  1763. return this._hidden;
  1764. },
  1765. set hidden(val) {
  1766. var value = !!val;
  1767. if (value != this._hidden) {
  1768. this._hidden = value;
  1769. engineMetadataService.setAttr(this, "hidden", value);
  1770. notifyAction(this, SEARCH_ENGINE_CHANGED);
  1771. }
  1772. },
  1773. get iconURI() {
  1774. return this._iconURI;
  1775. },
  1776. get _iconURL() {
  1777. if (!this._iconURI)
  1778. return "";
  1779. return this._iconURI.spec;
  1780. },
  1781. // Where the engine is being loaded from: will return the URI's spec if the
  1782. // engine is being downloaded and does not yet have a file. This is only used
  1783. // for logging.
  1784. get _location() {
  1785. if (this._file)
  1786. return this._file.path;
  1787. if (this._uri)
  1788. return this._uri.spec;
  1789. return "";
  1790. },
  1791. // The file that the plugin is loaded from is a unique identifier for it. We
  1792. // use this as the identifier to store data in the sqlite database
  1793. get _id() {
  1794. ENSURE_WARN(this._file, "No _file for id!", Cr.NS_ERROR_FAILURE);
  1795. if (this._isInProfile)
  1796. return "[profile]/" + this._file.leafName;
  1797. if (this._isInAppDir)
  1798. return "[app]/" + this._file.leafName;
  1799. // We're not in the profile or appdir, so this must be an extension-shipped
  1800. // plugin. Use the full path.
  1801. return this._file.path;
  1802. },
  1803. get _installLocation() {
  1804. ENSURE_WARN(this._file && this._file.exists(),
  1805. "_installLocation: engine has no file!",
  1806. Cr.NS_ERROR_FAILURE);
  1807. if (this.__installLocation === null) {
  1808. if (this._file.parent.equals(getDir(NS_APP_SEARCH_DIR)))
  1809. this.__installLocation = SEARCH_APP_DIR;
  1810. else if (this._file.parent.equals(getDir(NS_APP_USER_SEARCH_DIR)))
  1811. this.__installLocation = SEARCH_PROFILE_DIR;
  1812. else
  1813. this.__installLocation = SEARCH_IN_EXTENSION;
  1814. }
  1815. return this.__installLocation;
  1816. },
  1817. get _isInAppDir() {
  1818. return this._installLocation == SEARCH_APP_DIR;
  1819. },
  1820. get _isInProfile() {
  1821. return this._installLocation == SEARCH_PROFILE_DIR;
  1822. },
  1823. get _isDefault() {
  1824. // For now, our concept of a "default engine" is "one that is not in the
  1825. // user's profile directory", which is currently equivalent to "is app- or
  1826. // extension-shipped".
  1827. return !this._isInProfile;
  1828. },
  1829. get _hasUpdates() {
  1830. // Whether or not the engine has an update URL
  1831. return !!(this._updateURL || this._iconUpdateURL);
  1832. },
  1833. get name() {
  1834. return this._name;
  1835. },
  1836. get type() {
  1837. return this._type;
  1838. },
  1839. get searchForm() {
  1840. if (!this._searchForm) {
  1841. // No searchForm specified in the engine definition file, use the prePath
  1842. // (e.g. https://foo.com for https://foo.com/search.php?q=bar).
  1843. var htmlUrl = this._getURLOfType(URLTYPE_SEARCH_HTML);
  1844. ENSURE_WARN(htmlUrl, "Engine has no HTML URL!", Cr.NS_ERROR_UNEXPECTED);
  1845. this._searchForm = makeURI(htmlUrl.template).prePath;
  1846. }
  1847. return this._searchForm;
  1848. },
  1849. get queryCharset() {
  1850. if (this._queryCharset)
  1851. return this._queryCharset;
  1852. return this._queryCharset = queryCharsetFromCode(/* get the default */);
  1853. },
  1854. // from nsISearchEngine
  1855. addParam: function SRCH_ENG_addParam(aName, aValue, aResponseType) {
  1856. ENSURE_ARG(aName && (aValue != null),
  1857. "missing name or value for nsISearchEngine::addParam!");
  1858. ENSURE_WARN(!this._readOnly,
  1859. "called nsISearchEngine::addParam on a read-only engine!",
  1860. Cr.NS_ERROR_FAILURE);
  1861. if (!aResponseType)
  1862. aResponseType = URLTYPE_SEARCH_HTML;
  1863. var url = this._getURLOfType(aResponseType);
  1864. ENSURE(url, "Engine object has no URL for response type " + aResponseType,
  1865. Cr.NS_ERROR_FAILURE);
  1866. url.addParam(aName, aValue);
  1867. // Serialize the changes to file lazily
  1868. this._lazySerializeToFile();
  1869. },
  1870. // from nsISearchEngine
  1871. getSubmission: function SRCH_ENG_getSubmission(aData, aResponseType) {
  1872. if (!aResponseType)
  1873. aResponseType = URLTYPE_SEARCH_HTML;
  1874. var url = this._getURLOfType(aResponseType);
  1875. if (!url)
  1876. return null;
  1877. if (!aData) {
  1878. // Return a dummy submission object with our searchForm attribute
  1879. return new Submission(makeURI(this.searchForm), null);
  1880. }
  1881. LOG("getSubmission: In data: \"" + aData + "\"");
  1882. var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
  1883. getService(Ci.nsITextToSubURI);
  1884. var data = "";
  1885. try {
  1886. data = textToSubURI.ConvertAndEscape(this.queryCharset, aData);
  1887. } catch (ex) {
  1888. LOG("getSubmission: Falling back to default queryCharset!");
  1889. data = textToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
  1890. }
  1891. LOG("getSubmission: Out data: \"" + data + "\"");
  1892. return url.getSubmission(data, this);
  1893. },
  1894. // from nsISearchEngine
  1895. supportsResponseType: function SRCH_ENG_supportsResponseType(type) {
  1896. return (this._getURLOfType(type) != null);
  1897. },
  1898. // nsISupports
  1899. QueryInterface: function SRCH_ENG_QI(aIID) {
  1900. if (aIID.equals(Ci.nsISearchEngine) ||
  1901. aIID.equals(Ci.nsISupports))
  1902. return this;
  1903. throw Cr.NS_ERROR_NO_INTERFACE;
  1904. },
  1905. get wrappedJSObject() {
  1906. return this;
  1907. }
  1908. };
  1909. // nsISearchSubmission
  1910. function Submission(aURI, aPostData) {
  1911. this._uri = aURI;
  1912. this._postData = aPostData;
  1913. }
  1914. Submission.prototype = {
  1915. get uri() {
  1916. return this._uri;
  1917. },
  1918. get postData() {
  1919. return this._postData;
  1920. },
  1921. QueryInterface: function SRCH_SUBM_QI(aIID) {
  1922. if (aIID.equals(Ci.nsISearchSubmission) ||
  1923. aIID.equals(Ci.nsISupports))
  1924. return this;
  1925. throw Cr.NS_ERROR_NO_INTERFACE;
  1926. }
  1927. }
  1928. // nsIBrowserSearchService
  1929. function SearchService() {
  1930. this._init();
  1931. }
  1932. SearchService.prototype = {
  1933. _engines: { },
  1934. _sortedEngines: null,
  1935. // Whether or not we need to write the order of engines on shutdown. This
  1936. // needs to happen anytime _sortedEngines is modified after initial startup.
  1937. _needToSetOrderPrefs: false,
  1938. _init: function() {
  1939. var prefB = Cc["@mozilla.org/preferences-service;1"].
  1940. getService(Ci.nsIPrefBranch);
  1941. var shouldLog = false;
  1942. try {
  1943. shouldLog = prefB.getBoolPref(BROWSER_SEARCH_PREF + "log");
  1944. } catch (ex) {}
  1945. if (shouldLog) {
  1946. // Replace the empty LOG function with the useful one
  1947. LOG = DO_LOG;
  1948. }
  1949. engineMetadataService.init();
  1950. engineUpdateService.init();
  1951. this._addObservers();
  1952. var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
  1953. getService(Ci.nsIProperties);
  1954. var locations = fileLocator.get(NS_APP_SEARCH_DIR_LIST,
  1955. Ci.nsISimpleEnumerator);
  1956. while (locations.hasMoreElements()) {
  1957. var location = locations.getNext().QueryInterface(Ci.nsIFile);
  1958. this._loadEngines(location);
  1959. }
  1960. // Now that all engines are loaded, build the sorted engine list
  1961. this._buildSortedEngineList();
  1962. selectedEngineName = getLocalizedPref(BROWSER_SEARCH_PREF +
  1963. "selectedEngine");
  1964. this._currentEngine = this.getEngineByName(selectedEngineName) ||
  1965. this.defaultEngine;
  1966. },
  1967. _addEngineToStore: function SRCH_SVC_addEngineToStore(aEngine) {
  1968. LOG("_addEngineToStore: Adding engine: \"" + aEngine.name + "\"");
  1969. // See if there is an existing engine with the same name. However, if this
  1970. // engine is updating another engine, it's allowed to have the same name.
  1971. var hasSameNameAsUpdate = (aEngine._engineToUpdate &&
  1972. aEngine.name == aEngine._engineToUpdate.name);
  1973. if (aEngine.name in this._engines && !hasSameNameAsUpdate) {
  1974. LOG("_addEngineToStore: Duplicate engine found, aborting!");
  1975. return;
  1976. }
  1977. if (aEngine._engineToUpdate) {
  1978. // We need to replace engineToUpdate with the engine that just loaded.
  1979. var oldEngine = aEngine._engineToUpdate;
  1980. // Remove the old engine from the hash, since it's keyed by name, and our
  1981. // name might change (the update might have a new name).
  1982. delete this._engines[oldEngine.name];
  1983. // Hack: we want to replace the old engine with the new one, but since
  1984. // people may be holding refs to the nsISearchEngine objects themselves,
  1985. // we'll just copy over all "private" properties (those without a getter
  1986. // or setter) from one object to the other.
  1987. for (var p in aEngine) {
  1988. if (!(aEngine.__lookupGetter__(p) || aEngine.__lookupSetter__(p)))
  1989. oldEngine[p] = aEngine[p];
  1990. }
  1991. aEngine = oldEngine;
  1992. aEngine._engineToUpdate = null;
  1993. // Add the engine back
  1994. this._engines[aEngine.name] = aEngine;
  1995. notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
  1996. } else {
  1997. // Not an update, just add the new engine.
  1998. this._engines[aEngine.name] = aEngine;
  1999. // Only add the engine to the list of sorted engines if the initial list
  2000. // has already been built (i.e. if this._sortedEngines is non-null). If
  2001. // it hasn't, we're still loading engines from disk, and will build the
  2002. // sorted engine list when that initial loading is done.
  2003. if (this._sortedEngines) {
  2004. this._sortedEngines.push(aEngine);
  2005. this._needToSetOrderPrefs = true;
  2006. }
  2007. notifyAction(aEngine, SEARCH_ENGINE_ADDED);
  2008. }
  2009. if (aEngine._hasUpdates) {
  2010. // Schedule the engine's next update, if it isn't already.
  2011. if (!engineMetadataService.getAttr(aEngine, "updateexpir"))
  2012. engineUpdateService.scheduleNextUpdate(aEngine);
  2013. // We need to save the engine's _dataType, if this is the first time the
  2014. // engine is added to the dataStore, since ._dataType isn't persisted
  2015. // and will change on the next startup (since the engine will then be
  2016. // XML). We need this so that we know how to load any future updates from
  2017. // this engine.
  2018. if (!engineMetadataService.getAttr(aEngine, "updatedatatype"))
  2019. engineMetadataService.setAttr(aEngine, "updatedatatype",
  2020. aEngine._dataType);
  2021. }
  2022. },
  2023. _loadEngines: function SRCH_SVC_loadEngines(aDir) {
  2024. LOG("_loadEngines: Searching in " + aDir.path + " for search engines.");
  2025. // Check whether aDir is the user profile dir
  2026. var isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
  2027. var files = aDir.directoryEntries
  2028. .QueryInterface(Ci.nsIDirectoryEnumerator);
  2029. var ios = Cc["@mozilla.org/network/io-service;1"].
  2030. getService(Ci.nsIIOService);
  2031. while (files.hasMoreElements()) {
  2032. var file = files.nextFile;
  2033. // Ignore hidden and empty files, and directories
  2034. if (!file.isFile() || file.fileSize == 0 || file.isHidden())
  2035. continue;
  2036. var fileURL = ios.newFileURI(file).QueryInterface(Ci.nsIURL);
  2037. var fileExtension = fileURL.fileExtension.toLowerCase();
  2038. var isWritable = isInProfile && file.isWritable();
  2039. var dataType;
  2040. switch (fileExtension) {
  2041. case XML_FILE_EXT:
  2042. dataType = SEARCH_DATA_XML;
  2043. break;
  2044. case SHERLOCK_FILE_EXT:
  2045. dataType = SEARCH_DATA_TEXT;
  2046. break;
  2047. default:
  2048. // Not an engine
  2049. continue;
  2050. }
  2051. var addedEngine = null;
  2052. try {
  2053. addedEngine = new Engine(file, dataType, !isWritable);
  2054. addedEngine._initFromFile();
  2055. } catch (ex) {
  2056. LOG("_loadEngines: Failed to load " + file.path + "!\n" + ex);
  2057. continue;
  2058. }
  2059. if (fileExtension == SHERLOCK_FILE_EXT) {
  2060. if (isWritable) {
  2061. try {
  2062. this._convertSherlockFile(addedEngine, fileURL.fileBaseName);
  2063. } catch (ex) {
  2064. LOG("_loadEngines: Failed to convert: " + fileURL.path + "\n" + ex);
  2065. // The engine couldn't be converted, mark it as read-only
  2066. addedEngine._readOnly = true;
  2067. }
  2068. }
  2069. // If the engine still doesn't have an icon, see if we can find one
  2070. if (!addedEngine._iconURI) {
  2071. var icon = this._findSherlockIcon(file, fileURL.fileBaseName);
  2072. if (icon)
  2073. addedEngine._iconURI = ios.newFileURI(icon);
  2074. }
  2075. }
  2076. this._addEngineToStore(addedEngine);
  2077. }
  2078. },
  2079. _saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
  2080. // We only need to write the prefs. if something has changed.
  2081. if (!this._needToSetOrderPrefs)
  2082. return;
  2083. // Set the useDB pref to indicate that from now on we should use the order
  2084. // information stored in the database.
  2085. var prefB = Cc["@mozilla.org/preferences-service;1"].
  2086. getService(Ci.nsIPrefBranch);
  2087. prefB.setBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", true);
  2088. var engines = this._getSortedEngines(true);
  2089. var values = [];
  2090. var names = [];
  2091. for (var i = 0; i < engines.length; ++i) {
  2092. names[i] = "order";
  2093. values[i] = i + 1;
  2094. }
  2095. engineMetadataService.setAttrs(engines, names, values);
  2096. },
  2097. _buildSortedEngineList: function SRCH_SVC_buildSortedEngineList() {
  2098. var addedEngines = { };
  2099. this._sortedEngines = [];
  2100. var engine;
  2101. // If the user has specified a custom engine order, read the order
  2102. // information from the engineMetadataService instead of the default
  2103. // prefs.
  2104. if (getBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", false)) {
  2105. for each (engine in this._engines) {
  2106. var orderNumber = engineMetadataService.getAttr(engine, "order");
  2107. // Since the DB isn't regularly cleared, and engine files may disappear
  2108. // without us knowing, we may already have an engine in this slot. If
  2109. // that happens, we just skip it - it will be added later on as an
  2110. // unsorted engine. This problem will sort itself out when we call
  2111. // _saveSortedEngineList at shutdown.
  2112. if (orderNumber && !this._sortedEngines[orderNumber-1]) {
  2113. this._sortedEngines[orderNumber-1] = engine;
  2114. addedEngines[engine.name] = engine;
  2115. } else {
  2116. // We need to call _saveSortedEngines so this gets sorted out.
  2117. this._needToSetOrderPrefs = true;
  2118. }
  2119. }
  2120. // Filter out any nulls for engines that may have been removed
  2121. var filteredEngines = this._sortedEngines.filter(function(a) { return !!a; });
  2122. if (this._sortedEngines.length != filteredEngines.length)
  2123. this._needToSetOrderPrefs = true;
  2124. this._sortedEngines = filteredEngines;
  2125. } else {
  2126. // The DB isn't being used, so just read the engine order from the prefs
  2127. var i = 0;
  2128. var engineName;
  2129. var prefName;
  2130. try {
  2131. var prefB = Cc["@mozilla.org/preferences-service;1"].
  2132. getService(Ci.nsIPrefBranch);
  2133. var extras =
  2134. prefB.getChildList(BROWSER_SEARCH_PREF + "order.extra.", { });
  2135. for each (prefName in extras) {
  2136. engineName = prefB.getCharPref(prefName);
  2137. engine = this._engines[engineName];
  2138. if (!engine || engine.name in addedEngines)
  2139. continue;
  2140. this._sortedEngines.push(engine);
  2141. addedEngines[engine.name] = engine;
  2142. }
  2143. }
  2144. catch (e) { }
  2145. while (true) {
  2146. engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
  2147. if (!engineName)
  2148. break;
  2149. engine = this._engines[engineName];
  2150. if (!engine || engine.name in addedEngines)
  2151. continue;
  2152. this._sortedEngines.push(engine);
  2153. addedEngines[engine.name] = engine;
  2154. }
  2155. }
  2156. // Array for the remaining engines, alphabetically sorted
  2157. var alphaEngines = [];
  2158. for each (engine in this._engines) {
  2159. if (!(engine.name in addedEngines))
  2160. alphaEngines.push(this._engines[engine.name]);
  2161. }
  2162. alphaEngines = alphaEngines.sort(function (a, b) {
  2163. return a.name.localeCompare(b.name);
  2164. });
  2165. this._sortedEngines = this._sortedEngines.concat(alphaEngines);
  2166. },
  2167. /**
  2168. * Converts a Sherlock file and its icon into the custom XML format used by
  2169. * the Search Service. Saves the engine's icon (if present) into the XML as a
  2170. * data: URI and changes the extension of the source file from ".src" to
  2171. * ".xml". The engine data is then written to the file as XML.
  2172. * @param aEngine
  2173. * The Engine object that needs to be converted.
  2174. * @param aBaseName
  2175. * The basename of the Sherlock file.
  2176. * Example: "foo" for file "foo.src".
  2177. *
  2178. * @throws NS_ERROR_FAILURE if the file could not be converted.
  2179. *
  2180. * @see nsIURL::fileBaseName
  2181. */
  2182. _convertSherlockFile: function SRCH_SVC_convertSherlock(aEngine, aBaseName) {
  2183. var oldSherlockFile = aEngine._file;
  2184. // Back up the old file
  2185. try {
  2186. var backupDir = oldSherlockFile.parent;
  2187. backupDir.append("searchplugins-backup");
  2188. if (!backupDir.exists())
  2189. backupDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
  2190. oldSherlockFile.copyTo(backupDir, null);
  2191. } catch (ex) {
  2192. // Just bail. Engines that can't be backed up won't be converted, but
  2193. // engines that aren't converted are loaded as readonly.
  2194. LOG("_convertSherlockFile: Couldn't back up " + oldSherlockFile.path +
  2195. ":\n" + ex);
  2196. throw Cr.NS_ERROR_FAILURE;
  2197. }
  2198. // Rename the file, but don't clobber existing files
  2199. var newXMLFile = oldSherlockFile.parent.clone();
  2200. newXMLFile.append(aBaseName + "." + XML_FILE_EXT);
  2201. if (newXMLFile.exists()) {
  2202. // There is an existing file with this name, create a unique file
  2203. newXMLFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
  2204. }
  2205. // Rename the .src file to .xml
  2206. oldSherlockFile.moveTo(null, newXMLFile.leafName);
  2207. aEngine._file = newXMLFile;
  2208. // Write the converted engine to disk
  2209. aEngine._serializeToFile();
  2210. // Update the engine's _type.
  2211. aEngine._type = SEARCH_TYPE_MOZSEARCH;
  2212. // See if it has a corresponding icon
  2213. try {
  2214. var icon = this._findSherlockIcon(aEngine._file, aBaseName);
  2215. if (icon && icon.fileSize < MAX_ICON_SIZE) {
  2216. // Use this as the engine's icon
  2217. var bStream = Cc["@mozilla.org/binaryinputstream;1"].
  2218. createInstance(Ci.nsIBinaryInputStream);
  2219. var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
  2220. createInstance(Ci.nsIFileInputStream);
  2221. fileInStream.init(icon, MODE_RDONLY, PERMS_FILE, 0);
  2222. bStream.setInputStream(fileInStream);
  2223. var bytes = [];
  2224. while (bStream.available() != 0)
  2225. bytes = bytes.concat(bStream.readByteArray(bStream.available()));
  2226. bStream.close();
  2227. // Convert the byte array to a base64-encoded string
  2228. var str = btoa(String.fromCharCode.apply(null, bytes));
  2229. aEngine._iconURI = makeURI(ICON_DATAURL_PREFIX + str);
  2230. LOG("_importSherlockEngine: Set sherlock iconURI to: \"" +
  2231. aEngine._iconURL + "\"");
  2232. // Write the engine to disk to save changes
  2233. aEngine._serializeToFile();
  2234. // Delete the icon now that we're sure everything's been saved
  2235. icon.remove(false);
  2236. }
  2237. } catch (ex) { LOG("_convertSherlockFile: Error setting icon:\n" + ex); }
  2238. },
  2239. /**
  2240. * Finds an icon associated to a given Sherlock file. Searches the provided
  2241. * file's parent directory looking for files with the same base name and one
  2242. * of the file extensions in SHERLOCK_ICON_EXTENSIONS.
  2243. * @param aEngineFile
  2244. * The Sherlock plugin file.
  2245. * @param aBaseName
  2246. * The basename of the Sherlock file.
  2247. * Example: "foo" for file "foo.src".
  2248. * @see nsIURL::fileBaseName
  2249. */
  2250. _findSherlockIcon: function SRCH_SVC_findSherlock(aEngineFile, aBaseName) {
  2251. for (var i = 0; i < SHERLOCK_ICON_EXTENSIONS.length; i++) {
  2252. var icon = aEngineFile.parent.clone();
  2253. icon.append(aBaseName + SHERLOCK_ICON_EXTENSIONS[i]);
  2254. if (icon.exists() && icon.isFile())
  2255. return icon;
  2256. }
  2257. return null;
  2258. },
  2259. /**
  2260. * Get a sorted array of engines.
  2261. * @param aWithHidden
  2262. * True if hidden plugins should be included in the result.
  2263. */
  2264. _getSortedEngines: function SRCH_SVC_getSorted(aWithHidden) {
  2265. if (aWithHidden)
  2266. return this._sortedEngines;
  2267. return this._sortedEngines.filter(function (engine) {
  2268. return !engine.hidden;
  2269. });
  2270. },
  2271. // nsIBrowserSearchService
  2272. getEngines: function SRCH_SVC_getEngines(aCount) {
  2273. LOG("getEngines: getting all engines");
  2274. var engines = this._getSortedEngines(true);
  2275. aCount.value = engines.length;
  2276. return engines;
  2277. },
  2278. getVisibleEngines: function SRCH_SVC_getVisible(aCount) {
  2279. LOG("getVisibleEngines: getting all visible engines");
  2280. var engines = this._getSortedEngines(false);
  2281. aCount.value = engines.length;
  2282. return engines;
  2283. },
  2284. getDefaultEngines: function SRCH_SVC_getDefault(aCount) {
  2285. function isDefault(engine) {
  2286. return engine._isDefault;
  2287. };
  2288. var engines = this._sortedEngines.filter(isDefault);
  2289. var engineOrder = {};
  2290. var engineName;
  2291. var i = 1;
  2292. // Build a list of engines which we have ordering information for.
  2293. // We're rebuilding the list here because _sortedEngines contain the
  2294. // current order, but we want the original order.
  2295. // First, look at the "browser.search.order.extra" branch.
  2296. try {
  2297. var prefB = Cc["@mozilla.org/preferences-service;1"].
  2298. getService(Ci.nsIPrefBranch);
  2299. var extras = prefB.getChildList(BROWSER_SEARCH_PREF + "order.extra.",
  2300. {});
  2301. for each (var prefName in extras) {
  2302. engineName = prefB.getCharPref(prefName);
  2303. if (!(engineName in engineOrder))
  2304. engineOrder[engineName] = i++;
  2305. }
  2306. } catch (e) {
  2307. LOG("Getting extra order prefs failed: " + e);
  2308. }
  2309. // Now look through the "browser.search.order" branch.
  2310. for (var j = 1; ; j++) {
  2311. engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + j);
  2312. if (!engineName)
  2313. break;
  2314. if (!(engineName in engineOrder))
  2315. engineOrder[engineName] = i++;
  2316. }
  2317. LOG("getDefaultEngines: engineOrder: " + engineOrder.toSource());
  2318. function compareEngines (a, b) {
  2319. var aIdx = engineOrder[a.name];
  2320. var bIdx = engineOrder[b.name];
  2321. if (aIdx && bIdx)
  2322. return aIdx - bIdx;
  2323. if (aIdx)
  2324. return -1;
  2325. if (bIdx)
  2326. return 1;
  2327. return a.name.localeCompare(b.name);
  2328. }
  2329. engines.sort(compareEngines);
  2330. aCount.value = engines.length;
  2331. return engines;
  2332. },
  2333. getEngineByName: function SRCH_SVC_getEngineByName(aEngineName) {
  2334. return this._engines[aEngineName] || null;
  2335. },
  2336. getEngineByAlias: function SRCH_SVC_getEngineByAlias(aAlias) {
  2337. for (var engineName in this._engines) {
  2338. var engine = this._engines[engineName];
  2339. if (engine && engine.alias == aAlias)
  2340. return engine;
  2341. }
  2342. return null;
  2343. },
  2344. addEngineWithDetails: function SRCH_SVC_addEWD(aName, aIconURL, aAlias,
  2345. aDescription, aMethod,
  2346. aTemplate) {
  2347. ENSURE_ARG(aName, "Invalid name passed to addEngineWithDetails!");
  2348. ENSURE_ARG(aMethod, "Invalid method passed to addEngineWithDetails!");
  2349. ENSURE_ARG(aTemplate, "Invalid template passed to addEngineWithDetails!");
  2350. ENSURE(!this._engines[aName], "An engine with that name already exists!",
  2351. Cr.NS_ERROR_FILE_ALREADY_EXISTS);
  2352. var engine = new Engine(getSanitizedFile(aName), SEARCH_DATA_XML, false);
  2353. engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
  2354. aMethod, aTemplate);
  2355. this._addEngineToStore(engine);
  2356. },
  2357. addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
  2358. aConfirm) {
  2359. LOG("addEngine: Adding \"" + aEngineURL + "\".");
  2360. try {
  2361. var uri = makeURI(aEngineURL);
  2362. var engine = new Engine(uri, aDataType, false);
  2363. engine._initFromURI();
  2364. } catch (ex) {
  2365. LOG("addEngine: Error adding engine:\n" + ex);
  2366. throw Cr.NS_ERROR_FAILURE;
  2367. }
  2368. engine._setIcon(aIconURL, false);
  2369. engine._confirm = aConfirm;
  2370. },
  2371. removeEngine: function SRCH_SVC_removeEngine(aEngine) {
  2372. ENSURE_ARG(aEngine, "no engine passed to removeEngine!");
  2373. var engineToRemove = null;
  2374. for (var e in this._engines)
  2375. if (aEngine.wrappedJSObject == this._engines[e])
  2376. engineToRemove = this._engines[e];
  2377. ENSURE(engineToRemove, "removeEngine: Can't find engine to remove!",
  2378. Cr.NS_ERROR_FILE_NOT_FOUND);
  2379. if (engineToRemove == this.currentEngine)
  2380. this._currentEngine = null;
  2381. if (engineToRemove._readOnly) {
  2382. // Just hide it (the "hidden" setter will notify) and remove its alias to
  2383. // avoid future conflicts with other engines.
  2384. engineToRemove.hidden = true;
  2385. engineToRemove.alias = null;
  2386. } else {
  2387. // Cancel the lazy serialization timer if it's running
  2388. if (engineToRemove._serializeTimer) {
  2389. engineToRemove._serializeTimer.cancel();
  2390. engineToRemove._serializeTimer = null;
  2391. }
  2392. // Remove the engine file from disk (this might throw)
  2393. engineToRemove._remove();
  2394. engineToRemove._file = null;
  2395. // Remove the engine from _sortedEngines
  2396. var index = this._sortedEngines.indexOf(engineToRemove);
  2397. ENSURE(index != -1, "Can't find engine to remove in _sortedEngines!",
  2398. Cr.NS_ERROR_FAILURE);
  2399. this._sortedEngines.splice(index, 1);
  2400. // Remove the engine from the internal store
  2401. delete this._engines[engineToRemove.name];
  2402. notifyAction(engineToRemove, SEARCH_ENGINE_REMOVED);
  2403. // Since we removed an engine, we need to update the preferences.
  2404. this._needToSetOrderPrefs = true;
  2405. }
  2406. },
  2407. moveEngine: function SRCH_SVC_moveEngine(aEngine, aNewIndex) {
  2408. ENSURE_ARG((aNewIndex < this._sortedEngines.length) && (aNewIndex >= 0),
  2409. "SRCH_SVC_moveEngine: Index out of bounds!");
  2410. ENSURE_ARG(aEngine instanceof Ci.nsISearchEngine,
  2411. "SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
  2412. ENSURE(!aEngine.hidden, "moveEngine: Can't move a hidden engine!",
  2413. Cr.NS_ERROR_FAILURE);
  2414. var engine = aEngine.wrappedJSObject;
  2415. var currentIndex = this._sortedEngines.indexOf(engine);
  2416. ENSURE(currentIndex != -1, "moveEngine: Can't find engine to move!",
  2417. Cr.NS_ERROR_UNEXPECTED);
  2418. // Our callers only take into account non-hidden engines when calculating
  2419. // aNewIndex, but we need to move it in the array of all engines, so we
  2420. // need to adjust aNewIndex accordingly. To do this, we count the number
  2421. // of hidden engines in the list before the engine that we're taking the
  2422. // place of. We do this by first finding newIndexEngine (the engine that
  2423. // we were supposed to replace) and then iterating through the complete
  2424. // engine list until we reach it, increasing aNewIndex for each hidden
  2425. // engine we find on our way there.
  2426. //
  2427. // This could be further simplified by having our caller pass in
  2428. // newIndexEngine directly instead of aNewIndex.
  2429. var newIndexEngine = this._getSortedEngines(false)[aNewIndex];
  2430. ENSURE(newIndexEngine, "moveEngine: Can't find engine to replace!",
  2431. Cr.NS_ERROR_UNEXPECTED);
  2432. for (var i = 0; i < this._sortedEngines.length; ++i) {
  2433. if (newIndexEngine == this._sortedEngines[i])
  2434. break;
  2435. if (this._sortedEngines[i].hidden)
  2436. aNewIndex++;
  2437. }
  2438. if (currentIndex == aNewIndex)
  2439. return; // nothing to do!
  2440. // Move the engine
  2441. var movedEngine = this._sortedEngines.splice(currentIndex, 1)[0];
  2442. this._sortedEngines.splice(aNewIndex, 0, movedEngine);
  2443. notifyAction(engine, SEARCH_ENGINE_CHANGED);
  2444. // Since we moved an engine, we need to update the preferences.
  2445. this._needToSetOrderPrefs = true;
  2446. },
  2447. restoreDefaultEngines: function SRCH_SVC_resetDefaultEngines() {
  2448. for each (var e in this._engines) {
  2449. // Unhide all default engines
  2450. if (e.hidden && e._isDefault)
  2451. e.hidden = false;
  2452. }
  2453. },
  2454. get defaultEngine() {
  2455. const defPref = BROWSER_SEARCH_PREF + "defaultenginename";
  2456. // Get the default engine - this pref should always exist, but the engine
  2457. // might be hidden
  2458. this._defaultEngine = this.getEngineByName(getLocalizedPref(defPref, ""));
  2459. if (!this._defaultEngine || this._defaultEngine.hidden)
  2460. this._defaultEngine = this._getSortedEngines(false)[0] || null;
  2461. return this._defaultEngine;
  2462. },
  2463. get currentEngine() {
  2464. if (!this._currentEngine || this._currentEngine.hidden)
  2465. this._currentEngine = this.defaultEngine;
  2466. return this._currentEngine;
  2467. },
  2468. set currentEngine(val) {
  2469. ENSURE_ARG(val instanceof Ci.nsISearchEngine,
  2470. "Invalid argument passed to currentEngine setter");
  2471. var newCurrentEngine = this.getEngineByName(val.name);
  2472. ENSURE(newCurrentEngine, "Can't find engine in store!",
  2473. Cr.NS_ERROR_UNEXPECTED);
  2474. this._currentEngine = newCurrentEngine;
  2475. var currentEnginePref = BROWSER_SEARCH_PREF + "selectedEngine";
  2476. var prefB = Cc["@mozilla.org/preferences-service;1"].
  2477. getService(Ci.nsIPrefService).QueryInterface(Ci.nsIPrefBranch);
  2478. if (this._currentEngine == this.defaultEngine) {
  2479. if (prefB.prefHasUserValue(currentEnginePref))
  2480. prefB.clearUserPref(currentEnginePref);
  2481. }
  2482. else {
  2483. setLocalizedPref(currentEnginePref, this._currentEngine.name);
  2484. }
  2485. notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
  2486. },
  2487. // nsIObserver
  2488. observe: function SRCH_SVC_observe(aEngine, aTopic, aVerb) {
  2489. switch (aTopic) {
  2490. case SEARCH_ENGINE_TOPIC:
  2491. if (aVerb == SEARCH_ENGINE_LOADED) {
  2492. var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
  2493. LOG("nsSearchService::observe: Done installation of " + engine.name
  2494. + ".");
  2495. this._addEngineToStore(engine.wrappedJSObject);
  2496. if (engine.wrappedJSObject._useNow) {
  2497. LOG("nsSearchService::observe: setting current");
  2498. this.currentEngine = aEngine;
  2499. }
  2500. }
  2501. break;
  2502. case QUIT_APPLICATION_TOPIC:
  2503. this._removeObservers();
  2504. this._saveSortedEngineList();
  2505. break;
  2506. }
  2507. },
  2508. _addObservers: function SRCH_SVC_addObservers() {
  2509. var os = Cc["@mozilla.org/observer-service;1"].
  2510. getService(Ci.nsIObserverService);
  2511. os.addObserver(this, SEARCH_ENGINE_TOPIC, false);
  2512. os.addObserver(this, QUIT_APPLICATION_TOPIC, false);
  2513. },
  2514. _removeObservers: function SRCH_SVC_removeObservers() {
  2515. var os = Cc["@mozilla.org/observer-service;1"].
  2516. getService(Ci.nsIObserverService);
  2517. os.removeObserver(this, SEARCH_ENGINE_TOPIC);
  2518. os.removeObserver(this, QUIT_APPLICATION_TOPIC);
  2519. },
  2520. QueryInterface: function SRCH_SVC_QI(aIID) {
  2521. if (aIID.equals(Ci.nsIBrowserSearchService) ||
  2522. aIID.equals(Ci.nsIObserver) ||
  2523. aIID.equals(Ci.nsISupports))
  2524. return this;
  2525. throw Cr.NS_ERROR_NO_INTERFACE;
  2526. }
  2527. };
  2528. var engineMetadataService = {
  2529. init: function epsInit() {
  2530. var engineDataTable = "id INTEGER PRIMARY KEY, engineid STRING, name STRING, value STRING";
  2531. var file = getDir(NS_APP_USER_PROFILE_50_DIR);
  2532. file.append("search.sqlite");
  2533. var dbService = Cc["@mozilla.org/storage/service;1"].
  2534. getService(Ci.mozIStorageService);
  2535. try {
  2536. this.mDB = dbService.openDatabase(file);
  2537. } catch (ex) {
  2538. if (ex.result == 0x8052000b) { /* NS_ERROR_FILE_CORRUPTED */
  2539. // delete and try again
  2540. file.remove(false);
  2541. this.mDB = dbService.openDatabase(file);
  2542. } else {
  2543. throw ex;
  2544. }
  2545. }
  2546. try {
  2547. this.mDB.createTable("engine_data", engineDataTable);
  2548. } catch (ex) {
  2549. // Fails if the table already exists, which is fine
  2550. }
  2551. this.mGetData = createStatement (
  2552. this.mDB,
  2553. "SELECT value FROM engine_data WHERE engineid = :engineid AND name = :name");
  2554. this.mDeleteData = createStatement (
  2555. this.mDB,
  2556. "DELETE FROM engine_data WHERE engineid = :engineid AND name = :name");
  2557. this.mInsertData = createStatement (
  2558. this.mDB,
  2559. "INSERT INTO engine_data (engineid, name, value) " +
  2560. "VALUES (:engineid, :name, :value)");
  2561. },
  2562. getAttr: function epsGetAttr(engine, name) {
  2563. // attr names must be lower case
  2564. name = name.toLowerCase();
  2565. var stmt = this.mGetData;
  2566. stmt.reset();
  2567. var pp = stmt.params;
  2568. pp.engineid = engine._id;
  2569. pp.name = name;
  2570. var value = null;
  2571. if (stmt.step())
  2572. value = stmt.row.value;
  2573. stmt.reset();
  2574. return value;
  2575. },
  2576. setAttr: function epsSetAttr(engine, name, value) {
  2577. // attr names must be lower case
  2578. name = name.toLowerCase();
  2579. this.mDB.beginTransaction();
  2580. var pp = this.mDeleteData.params;
  2581. pp.engineid = engine._id;
  2582. pp.name = name;
  2583. this.mDeleteData.step();
  2584. this.mDeleteData.reset();
  2585. pp = this.mInsertData.params;
  2586. pp.engineid = engine._id;
  2587. pp.name = name;
  2588. pp.value = value;
  2589. this.mInsertData.step();
  2590. this.mInsertData.reset();
  2591. this.mDB.commitTransaction();
  2592. },
  2593. setAttrs: function epsSetAttrs(engines, names, values) {
  2594. this.mDB.beginTransaction();
  2595. for (var i = 0; i < engines.length; i++) {
  2596. // attr names must be lower case
  2597. var name = names[i].toLowerCase();
  2598. var pp = this.mDeleteData.params;
  2599. pp.engineid = engines[i]._id;
  2600. pp.name = names[i];
  2601. this.mDeleteData.step();
  2602. this.mDeleteData.reset();
  2603. pp = this.mInsertData.params;
  2604. pp.engineid = engines[i]._id;
  2605. pp.name = names[i];
  2606. pp.value = values[i];
  2607. this.mInsertData.step();
  2608. this.mInsertData.reset();
  2609. }
  2610. this.mDB.commitTransaction();
  2611. },
  2612. deleteEngineData: function epsDelData(engine, name) {
  2613. // attr names must be lower case
  2614. name = name.toLowerCase();
  2615. var pp = this.mDeleteData.params;
  2616. pp.engineid = engine._id;
  2617. pp.name = name;
  2618. this.mDeleteData.step();
  2619. this.mDeleteData.reset();
  2620. }
  2621. }
  2622. const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
  2623. /**
  2624. * Outputs aText to the JavaScript console as well as to stdout, if the search
  2625. * logging pref (browser.search.update.log) is set to true.
  2626. */
  2627. function ULOG(aText) {
  2628. var prefB = Cc["@mozilla.org/preferences-service;1"].
  2629. getService(Ci.nsIPrefBranch);
  2630. var shouldLog = false;
  2631. try {
  2632. shouldLog = prefB.getBoolPref(BROWSER_SEARCH_PREF + "update.log");
  2633. } catch (ex) {}
  2634. if (shouldLog) {
  2635. dump(SEARCH_UPDATE_LOG_PREFIX + aText + "\n");
  2636. var consoleService = Cc["@mozilla.org/consoleservice;1"].
  2637. getService(Ci.nsIConsoleService);
  2638. consoleService.logStringMessage(aText);
  2639. }
  2640. }
  2641. var engineUpdateService = {
  2642. init: function eus_init() {
  2643. var tm = Cc["@mozilla.org/updates/timer-manager;1"].
  2644. getService(Ci.nsIUpdateTimerManager);
  2645. // figure out how often to check for any expired engines
  2646. var prefB = Cc["@mozilla.org/preferences-service;1"].
  2647. getService(Ci.nsIPrefBranch);
  2648. var interval = prefB.getIntPref(BROWSER_SEARCH_PREF + "updateinterval");
  2649. // Interval is stored in hours
  2650. var seconds = interval * 3600;
  2651. tm.registerTimer("search-engine-update-timer", engineUpdateService,
  2652. seconds);
  2653. },
  2654. scheduleNextUpdate: function eus_scheduleNextUpdate(aEngine) {
  2655. var interval = aEngine._updateInterval || SEARCH_DEFAULT_UPDATE_INTERVAL;
  2656. var milliseconds = interval * 86400000; // |interval| is in days
  2657. engineMetadataService.setAttr(aEngine, "updateexpir",
  2658. Date.now() + milliseconds);
  2659. },
  2660. notify: function eus_Notify(aTimer) {
  2661. ULOG("notify called");
  2662. if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true))
  2663. return;
  2664. // Our timer has expired, but unfortunately, we can't get any data from it.
  2665. // Therefore, we need to walk our engine-list, looking for expired engines
  2666. var searchService = Cc["@mozilla.org/browser/search-service;1"].
  2667. getService(Ci.nsIBrowserSearchService);
  2668. var currentTime = Date.now();
  2669. ULOG("currentTime: " + currentTime);
  2670. for each (engine in searchService.getEngines({})) {
  2671. engine = engine.wrappedJSObject;
  2672. if (!engine._hasUpdates || engine._readOnly)
  2673. continue;
  2674. ULOG("checking " + engine.name);
  2675. var expirTime = engineMetadataService.getAttr(engine, "updateexpir");
  2676. var updateURL = engine._updateURL;
  2677. var iconUpdateURL = engine._iconUpdateURL;
  2678. ULOG("expirTime: " + expirTime + "\nupdateURL: " + updateURL +
  2679. "\niconUpdateURL: " + iconUpdateURL);
  2680. var engineExpired = expirTime <= currentTime;
  2681. if (!expirTime || !engineExpired) {
  2682. ULOG("skipping engine");
  2683. continue;
  2684. }
  2685. ULOG(engine.name + " has expired");
  2686. var testEngine = null;
  2687. var updateURI = makeURI(updateURL);
  2688. if (updateURI) {
  2689. var dataType = engineMetadataService.getAttr(engine, "updatedatatype")
  2690. if (!dataType) {
  2691. ULOG("No loadtype to update engine!");
  2692. continue;
  2693. }
  2694. testEngine = new Engine(updateURI, dataType, false);
  2695. testEngine._engineToUpdate = engine;
  2696. testEngine._initFromURI();
  2697. } else
  2698. ULOG("invalid updateURI");
  2699. if (iconUpdateURL) {
  2700. // If we're updating the engine too, use the new engine object,
  2701. // otherwise use the existing engine object.
  2702. (testEngine || engine)._setIcon(iconUpdateURL, true);
  2703. }
  2704. // Schedule the next update
  2705. this.scheduleNextUpdate(engine);
  2706. } // end engine iteration
  2707. }
  2708. };
  2709. const kClassID = Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}");
  2710. const kClassName = "Browser Search Service";
  2711. const kContractID = "@mozilla.org/browser/search-service;1";
  2712. // nsIFactory
  2713. const kFactory = {
  2714. createInstance: function (outer, iid) {
  2715. if (outer != null)
  2716. throw Cr.NS_ERROR_NO_AGGREGATION;
  2717. return (new SearchService()).QueryInterface(iid);
  2718. }
  2719. };
  2720. // nsIModule
  2721. const gModule = {
  2722. registerSelf: function (componentManager, fileSpec, location, type) {
  2723. componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  2724. componentManager.registerFactoryLocation(kClassID,
  2725. kClassName,
  2726. kContractID,
  2727. fileSpec, location, type);
  2728. },
  2729. unregisterSelf: function(componentManager, fileSpec, location) {
  2730. componentManager.QueryInterface(Ci.nsIComponentRegistrar);
  2731. componentManager.unregisterFactoryLocation(kClassID, fileSpec);
  2732. },
  2733. getClassObject: function (componentManager, cid, iid) {
  2734. if (!cid.equals(kClassID))
  2735. throw Cr.NS_ERROR_NO_INTERFACE;
  2736. if (!iid.equals(Ci.nsIFactory))
  2737. throw Cr.NS_ERROR_NOT_IMPLEMENTED;
  2738. return kFactory;
  2739. },
  2740. canUnload: function (componentManager) {
  2741. return true;
  2742. }
  2743. };
  2744. function NSGetModule(componentManager, fileSpec) {
  2745. return gModule;
  2746. }
  2747. #include ../../../toolkit/content/debug.js