home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / firefox / components / nsSearchService.js < prev    next >
Encoding:
Text File  |  2006-08-18  |  88.4 KB  |  2,656 lines

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