home *** CD-ROM | disk | FTP | other *** search
/ Freelog 100 / FreelogNo100-NovembreDecembre2010.iso / Multimedia / Songbird / Songbird_1.8.0-1800_windows-i686-msvc8.exe / components / nsSearchService.js < prev    next >
Text File  |  2010-08-30  |  116KB  |  3,252 lines

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