PageRenderTime 64ms CodeModel.GetById 21ms 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

Large files files are truncated, but you can click here to view the full file

  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 (par

Large files files are truncated, but you can click here to view the full file