home *** CD-ROM | disk | FTP | other *** search
Text File | 2007-10-27 | 109.5 KB | 3,261 lines |
- /*
- # ***** BEGIN LICENSE BLOCK *****
- # Version: MPL 1.1/GPL 2.0/LGPL 2.1
- #
- # The contents of this file are subject to the Mozilla Public License Version
- # 1.1 (the "License"); you may not use this file except in compliance with
- # the License. You may obtain a copy of the License at
- # http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS IS" basis,
- # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- # for the specific language governing rights and limitations under the
- # License.
- #
- # The Original Code is the Browser Search Service.
- #
- # The Initial Developer of the Original Code is
- # Google Inc.
- # Portions created by the Initial Developer are Copyright (C) 2005-2006
- # the Initial Developer. All Rights Reserved.
- #
- # Contributor(s):
- # Ben Goodger <beng@google.com> (Original author)
- # Gavin Sharp <gavin@gavinsharp.com>
- # Joe Hughes <joe@retrovirus.com>
- # Pamela Greene <pamg.bugs@gmail.com>
- #
- # Alternatively, the contents of this file may be used under the terms of
- # either the GNU General Public License Version 2 or later (the "GPL"), or
- # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- # in which case the provisions of the GPL or the LGPL are applicable instead
- # of those above. If you wish to allow use of your version of this file only
- # under the terms of either the GPL or the LGPL, and not to allow others to
- # use your version of this file under the terms of the MPL, indicate your
- # decision by deleting the provisions above and replace them with the notice
- # and other provisions required by the GPL or the LGPL. If you do not delete
- # the provisions above, a recipient may use your version of this file under
- # the terms of any one of the MPL, the GPL or the LGPL.
- #
- # ***** END LICENSE BLOCK *****
- */
- const Ci = Components.interfaces;
- const Cc = Components.classes;
- const Cr = Components.results;
-
- const PERMS_FILE = 0644;
- const PERMS_DIRECTORY = 0755;
-
- const MODE_RDONLY = 0x01;
- const MODE_WRONLY = 0x02;
- const MODE_CREATE = 0x08;
- const MODE_APPEND = 0x10;
- const MODE_TRUNCATE = 0x20;
-
- // Directory service keys
- const NS_APP_SEARCH_DIR_LIST = "SrchPluginsDL";
- const NS_APP_USER_SEARCH_DIR = "UsrSrchPlugns";
- const NS_APP_SEARCH_DIR = "SrchPlugns";
- const NS_APP_USER_PROFILE_50_DIR = "ProfD";
-
- // Search engine "locations". If this list is changed, be sure to update
- // the engine's _isDefault function accordingly.
- const SEARCH_APP_DIR = 1;
- const SEARCH_PROFILE_DIR = 2;
- const SEARCH_IN_EXTENSION = 3;
-
- // See documentation in nsIBrowserSearchService.idl.
- const SEARCH_ENGINE_TOPIC = "browser-search-engine-modified";
- const QUIT_APPLICATION_TOPIC = "quit-application";
-
- const SEARCH_ENGINE_REMOVED = "engine-removed";
- const SEARCH_ENGINE_ADDED = "engine-added";
- const SEARCH_ENGINE_CHANGED = "engine-changed";
- const SEARCH_ENGINE_LOADED = "engine-loaded";
- const SEARCH_ENGINE_CURRENT = "engine-current";
-
- const SEARCH_TYPE_MOZSEARCH = Ci.nsISearchEngine.TYPE_MOZSEARCH;
- const SEARCH_TYPE_OPENSEARCH = Ci.nsISearchEngine.TYPE_OPENSEARCH;
- const SEARCH_TYPE_SHERLOCK = Ci.nsISearchEngine.TYPE_SHERLOCK;
-
- const SEARCH_DATA_XML = Ci.nsISearchEngine.DATA_XML;
- const SEARCH_DATA_TEXT = Ci.nsISearchEngine.DATA_TEXT;
-
- // File extensions for search plugin description files
- const XML_FILE_EXT = "xml";
- const SHERLOCK_FILE_EXT = "src";
-
- // Delay for lazy serialization (ms)
- const LAZY_SERIALIZE_DELAY = 100;
-
- const ICON_DATAURL_PREFIX = "data:image/x-icon;base64,";
-
- // Supported extensions for Sherlock plugin icons
- const SHERLOCK_ICON_EXTENSIONS = [".gif", ".png", ".jpg", ".jpeg"];
-
- const NEW_LINES = /(\r\n|\r|\n)/;
-
- // Set an arbitrary cap on the maximum icon size. Without this, large icons can
- // cause big delays when loading them at startup.
- const MAX_ICON_SIZE = 10000;
-
- // Default charset to use for sending search parameters. ISO-8859-1 is used to
- // match previous nsInternetSearchService behavior.
- const DEFAULT_QUERY_CHARSET = "ISO-8859-1";
-
- const SEARCH_BUNDLE = "chrome://browser/locale/search.properties";
- const BRAND_BUNDLE = "chrome://branding/locale/brand.properties";
-
- const OPENSEARCH_NS_10 = "http://a9.com/-/spec/opensearch/1.0/";
- const OPENSEARCH_NS_11 = "http://a9.com/-/spec/opensearch/1.1/";
-
- // Although the specification at http://opensearch.a9.com/spec/1.1/description/
- // gives the namespace names defined above, many existing OpenSearch engines
- // are using the following versions. We therefore allow either.
- const OPENSEARCH_NAMESPACES = [
- OPENSEARCH_NS_11, OPENSEARCH_NS_10,
- "http://a9.com/-/spec/opensearchdescription/1.1/",
- "http://a9.com/-/spec/opensearchdescription/1.0/"
- ];
-
- const OPENSEARCH_LOCALNAME = "OpenSearchDescription";
-
- const MOZSEARCH_NS_10 = "http://www.mozilla.org/2006/browser/search/";
- const MOZSEARCH_LOCALNAME = "SearchPlugin";
-
- const URLTYPE_SUGGEST_JSON = "application/x-suggestions+json";
- const URLTYPE_SEARCH_HTML = "text/html";
-
- // Empty base document used to serialize engines to file.
- const EMPTY_DOC = "<?xml version=\"1.0\"?>\n" +
- "<" + MOZSEARCH_LOCALNAME +
- " xmlns=\"" + MOZSEARCH_NS_10 + "\"" +
- " xmlns:os=\"" + OPENSEARCH_NS_11 + "\"" +
- "/>";
-
- const BROWSER_SEARCH_PREF = "browser.search.";
-
- const USER_DEFINED = "{searchTerms}";
-
- // Custom search parameters
- /*
- #ifdef OFFICIAL_BUILD
- const MOZ_OFFICIAL = "official";
- #else
- */
- const MOZ_OFFICIAL = "unofficial";
- /*
- #endif
- #expand const MOZ_DISTRIBUTION_ID = __MOZ_DISTRIBUTION_ID__;
- */
-
- /**************************
- XXX - What should this be?
- **************************/
- const MOZ_DISTRIBUTION_ID = "Songbird";
-
- const MOZ_PARAM_LOCALE = /\{moz:locale\}/g;
- const MOZ_PARAM_DIST_ID = /\{moz:distributionID\}/g;
- const MOZ_PARAM_OFFICIAL = /\{moz:official\}/g;
-
- // Supported OpenSearch parameters
- // See http://opensearch.a9.com/spec/1.1/querysyntax/#core
- const OS_PARAM_USER_DEFINED = /\{searchTerms\??\}/g;
- const OS_PARAM_INPUT_ENCODING = /\{inputEncoding\??\}/g;
- const OS_PARAM_LANGUAGE = /\{language\??\}/g;
- const OS_PARAM_OUTPUT_ENCODING = /\{outputEncoding\??\}/g;
-
- // Default values
- const OS_PARAM_LANGUAGE_DEF = "*";
- const OS_PARAM_OUTPUT_ENCODING_DEF = "UTF-8";
- const OS_PARAM_INPUT_ENCODING_DEF = "UTF-8";
-
- // "Unsupported" OpenSearch parameters. For example, we don't support
- // page-based results, so if the engine requires that we send the "page index"
- // parameter, we'll always send "1".
- const OS_PARAM_COUNT = /\{count\??\}/g;
- const OS_PARAM_START_INDEX = /\{startIndex\??\}/g;
- const OS_PARAM_START_PAGE = /\{startPage\??\}/g;
-
- // Default values
- const OS_PARAM_COUNT_DEF = "20"; // 20 results
- const OS_PARAM_START_INDEX_DEF = "1"; // start at 1st result
- const OS_PARAM_START_PAGE_DEF = "1"; // 1st page
-
- // Optional parameter
- const OS_PARAM_OPTIONAL = /\{(?:\w+:)?\w+\?\}/g;
-
- // A array of arrays containing parameters that we don't fully support, and
- // their default values. We will only send values for these parameters if
- // required, since our values are just really arbitrary "guesses" that should
- // give us the output we want.
- var OS_UNSUPPORTED_PARAMS = [
- [OS_PARAM_COUNT, OS_PARAM_COUNT_DEF],
- [OS_PARAM_START_INDEX, OS_PARAM_START_INDEX_DEF],
- [OS_PARAM_START_PAGE, OS_PARAM_START_PAGE_DEF],
- ];
-
- // The default engine update interval, in days. This is only used if an engine
- // specifies an updateURL, but not an updateInterval.
- const SEARCH_DEFAULT_UPDATE_INTERVAL = 7;
-
- // Returns false for whitespace-only or commented out lines in a
- // Sherlock file, true otherwise.
- function isUsefulLine(aLine) {
- return !(/^\s*($|#)/i.test(aLine));
- }
-
- /**
- * Prefixed to all search debug output.
- */
- const SEARCH_LOG_PREFIX = "*** Search: ";
-
- /**
- * Outputs aText to the JavaScript console as well as to stdout, if the search
- * logging pref (browser.search.log) is set to true.
- */
- function LOG(aText) {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- var shouldLog = false;
- try {
- shouldLog = prefB.getBoolPref(BROWSER_SEARCH_PREF + "log");
- } catch (ex) {}
-
- if (shouldLog) {
- dump(SEARCH_LOG_PREFIX + aText + "\n");
- var consoleService = Cc["@mozilla.org/consoleservice;1"].
- getService(Ci.nsIConsoleService);
- consoleService.logStringMessage(aText);
- }
- }
-
- function ERROR(message, resultCode) {
- NS_ASSERT(false, SEARCH_LOG_PREFIX + message);
- throw resultCode;
- }
-
- /**
- * Ensures an assertion is met before continuing. Should be used to indicate
- * fatal errors.
- * @param assertion
- * An assertion that must be met
- * @param message
- * A message to display if the assertion is not met
- * @param resultCode
- * The NS_ERROR_* value to throw if the assertion is not met
- * @throws resultCode
- */
- function ENSURE_WARN(assertion, message, resultCode) {
- NS_ASSERT(assertion, SEARCH_LOG_PREFIX + message);
- if (!assertion)
- throw resultCode;
- }
-
- /**
- * Ensures an assertion is met before continuing, but does not warn the user.
- * Used to handle normal failure conditions.
- * @param assertion
- * An assertion that must be met
- * @param message
- * A message to display if the assertion is not met
- * @param resultCode
- * The NS_ERROR_* value to throw if the assertion is not met
- * @throws resultCode
- */
- function ENSURE(assertion, message, resultCode) {
- if (!assertion) {
- LOG(message);
- throw resultCode;
- }
- }
-
- /**
- * Ensures an argument assertion is met before continuing.
- * @param assertion
- * An argument assertion that must be met
- * @param message
- * A message to display if the assertion is not met
- * @throws NS_ERROR_INVALID_ARG for invalid arguments
- */
- function ENSURE_ARG(assertion, message) {
- ENSURE(assertion, message, Cr.NS_ERROR_INVALID_ARG);
- }
-
- function loadListener(aChannel, aEngine, aCallback) {
- this._channel = aChannel;
- this._bytes = [];
- this._engine = aEngine;
- this._callback = aCallback;
- }
- loadListener.prototype = {
- _callback: null,
- _channel: null,
- _countRead: 0,
- _engine: null,
- _stream: null,
-
- QueryInterface: function SRCH_loadQI(aIID) {
- if (aIID.equals(Ci.nsISupports) ||
- aIID.equals(Ci.nsIRequestObserver) ||
- aIID.equals(Ci.nsIStreamListener) ||
- aIID.equals(Ci.nsIChannelEventSink) ||
- aIID.equals(Ci.nsIInterfaceRequestor) ||
- aIID.equals(Ci.nsIBadCertListener) ||
- // See FIXME comment below
- aIID.equals(Ci.nsIHttpEventSink) ||
- aIID.equals(Ci.nsIProgressEventSink) ||
- false)
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
- // nsIRequestObserver
- onStartRequest: function SRCH_loadStartR(aRequest, aContext) {
- LOG("loadListener: Starting request: " + aRequest.name);
- this._stream = Cc["@mozilla.org/binaryinputstream;1"].
- createInstance(Ci.nsIBinaryInputStream);
- },
-
- onStopRequest: function SRCH_loadStopR(aRequest, aContext, aStatusCode) {
- LOG("loadListener: Stopping request: " + aRequest.name);
-
- var requestFailed = !Components.isSuccessCode(aStatusCode);
- if (!requestFailed && (aRequest instanceof Ci.nsIHttpChannel))
- requestFailed = !aRequest.requestSucceeded;
-
- if (requestFailed || this._countRead == 0) {
- LOG("loadListener: request failed!");
- // send null so the callback can deal with the failure
- this._callback(null, this._engine);
- } else
- this._callback(this._bytes, this._engine);
- this._channel = null;
- this._engine = null;
- },
-
- // nsIStreamListener
- onDataAvailable: function SRCH_loadDAvailable(aRequest, aContext,
- aInputStream, aOffset,
- aCount) {
- this._stream.setInputStream(aInputStream);
-
- // Get a byte array of the data
- this._bytes = this._bytes.concat(this._stream.readByteArray(aCount));
- this._countRead += aCount;
- },
-
- // nsIChannelEventSink
- onChannelRedirect: function SRCH_loadCRedirect(aOldChannel, aNewChannel,
- aFlags) {
- this._channel = aNewChannel;
- },
-
- // nsIInterfaceRequestor
- getInterface: function SRCH_load_GI(aIID) {
- return this.QueryInterface(aIID);
- },
-
- // nsIBadCertListener
- confirmUnknownIssuer: function SRCH_load_CUI(aSocketInfo, aCert,
- aCertAddType) {
- return false;
- },
-
- confirmMismatchDomain: function SRCH_load_CMD(aSocketInfo, aTargetURL,
- aCert) {
- return false;
- },
-
- confirmCertExpired: function SRCH_load_CCE(aSocketInfo, aCert) {
- return false;
- },
-
- notifyCrlNextupdate: function SRCH_load_NCN(aSocketInfo, aTargetURL, aCert) {
- },
-
- // FIXME: bug 253127
- // nsIHttpEventSink
- onRedirect: function (aChannel, aNewChannel) {},
- // nsIProgressEventSink
- onProgress: function (aRequest, aContext, aProgress, aProgressMax) {},
- onStatus: function (aRequest, aContext, aStatus, aStatusArg) {}
- }
-
-
- /**
- * Used to verify a given DOM node's localName and namespaceURI.
- * @param aElement
- * The element to verify.
- * @param aLocalNameArray
- * An array of strings to compare against aElement's localName.
- * @param aNameSpaceArray
- * An array of strings to compare against aElement's namespaceURI.
- *
- * @returns false if aElement is null, or if its localName or namespaceURI
- * does not match one of the elements in the aLocalNameArray or
- * aNameSpaceArray arrays, respectively.
- * @throws NS_ERROR_INVALID_ARG if aLocalNameArray or aNameSpaceArray are null.
- */
- function checkNameSpace(aElement, aLocalNameArray, aNameSpaceArray) {
- ENSURE_ARG(aLocalNameArray && aNameSpaceArray, "missing aLocalNameArray or \
- aNameSpaceArray for checkNameSpace");
- return (aElement &&
- (aLocalNameArray.indexOf(aElement.localName) != -1) &&
- (aNameSpaceArray.indexOf(aElement.namespaceURI) != -1));
- }
-
- /**
- * Safely close a nsISafeOutputStream.
- * @param aFOS
- * The file output stream to close.
- */
- function closeSafeOutputStream(aFOS) {
- if (aFOS instanceof Ci.nsISafeOutputStream) {
- try {
- aFOS.finish();
- return;
- } catch (e) { }
- }
- aFOS.close();
- }
-
- /**
- * Wrapper function for nsIIOService::newURI.
- * @param aURLSpec
- * The URL string from which to create an nsIURI.
- * @returns an nsIURI object, or null if the creation of the URI failed.
- */
- function makeURI(aURLSpec, aCharset) {
- var ios = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService);
- try {
- return ios.newURI(aURLSpec, aCharset, null);
- } catch (ex) { }
-
- return null;
- }
-
- /**
- * Gets a directory from the directory service.
- * @param aKey
- * The directory service key indicating the directory to get.
- */
- function getDir(aKey) {
- ENSURE_ARG(aKey, "getDir requires a directory key!");
-
- var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
- getService(Ci.nsIProperties);
- var dir = fileLocator.get(aKey, Ci.nsIFile);
- return dir;
- }
-
- /**
- * The following two functions are essentially copied from
- * nsInternetSearchService. They are required for backwards compatibility.
- */
- function queryCharsetFromCode(aCode) {
- const codes = [];
- codes[0] = "x-mac-roman";
- codes[6] = "x-mac-greek";
- codes[35] = "x-mac-turkish";
- codes[513] = "ISO-8859-1";
- codes[514] = "ISO-8859-2";
- codes[517] = "ISO-8859-5";
- codes[518] = "ISO-8859-6";
- codes[519] = "ISO-8859-7";
- codes[520] = "ISO-8859-8";
- codes[521] = "ISO-8859-9";
- codes[1049] = "IBM864";
- codes[1280] = "windows-1252";
- codes[1281] = "windows-1250";
- codes[1282] = "windows-1251";
- codes[1283] = "windows-1253";
- codes[1284] = "windows-1254";
- codes[1285] = "windows-1255";
- codes[1286] = "windows-1256";
- codes[1536] = "us-ascii";
- codes[1584] = "GB2312";
- codes[1585] = "x-gbk";
- codes[1600] = "EUC-KR";
- codes[2080] = "ISO-2022-JP";
- codes[2096] = "ISO-2022-CN";
- codes[2112] = "ISO-2022-KR";
- codes[2336] = "EUC-JP";
- codes[2352] = "GB2312";
- codes[2353] = "x-euc-tw";
- codes[2368] = "EUC-KR";
- codes[2561] = "Shift_JIS";
- codes[2562] = "KOI8-R";
- codes[2563] = "Big5";
- codes[2565] = "HZ-GB-2312";
-
- if (codes[aCode])
- return codes[aCode];
-
- return getLocalizedPref("intl.charset.default", DEFAULT_QUERY_CHARSET);
- }
- function fileCharsetFromCode(aCode) {
- const codes = [
- "x-mac-roman", // 0
- "Shift_JIS", // 1
- "Big5", // 2
- "EUC-KR", // 3
- "X-MAC-ARABIC", // 4
- "X-MAC-HEBREW", // 5
- "X-MAC-GREEK", // 6
- "X-MAC-CYRILLIC", // 7
- "X-MAC-DEVANAGARI" , // 9
- "X-MAC-GURMUKHI", // 10
- "X-MAC-GUJARATI", // 11
- "X-MAC-ORIYA", // 12
- "X-MAC-BENGALI", // 13
- "X-MAC-TAMIL", // 14
- "X-MAC-TELUGU", // 15
- "X-MAC-KANNADA", // 16
- "X-MAC-MALAYALAM", // 17
- "X-MAC-SINHALESE", // 18
- "X-MAC-BURMESE", // 19
- "X-MAC-KHMER", // 20
- "X-MAC-THAI", // 21
- "X-MAC-LAOTIAN", // 22
- "X-MAC-GEORGIAN", // 23
- "X-MAC-ARMENIAN", // 24
- "GB2312", // 25
- "X-MAC-TIBETAN", // 26
- "X-MAC-MONGOLIAN", // 27
- "X-MAC-ETHIOPIC", // 28
- "X-MAC-CENTRALEURROMAN", // 29
- "X-MAC-VIETNAMESE", // 30
- "X-MAC-EXTARABIC" // 31
- ];
- // Sherlock files have always defaulted to x-mac-roman, so do that here too
- return codes[aCode] || codes[0];
- }
-
- /**
- * Returns a string interpretation of aBytes using aCharset, or null on
- * failure.
- */
- function bytesToString(aBytes, aCharset) {
- var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
- createInstance(Ci.nsIScriptableUnicodeConverter);
- LOG("bytesToString: converting using charset: " + aCharset);
-
- try {
- converter.charset = aCharset;
- return converter.convertFromByteArray(aBytes, aBytes.length);
- } catch (ex) {}
-
- return null;
- }
-
- /**
- * Converts an array of bytes representing a Sherlock file into an array of
- * lines representing the useful data from the file.
- *
- * @param aBytes
- * The array of bytes representing the Sherlock file.
- * @param aCharsetCode
- * An integer value representing a character set code to be passed to
- * fileCharsetFromCode, or null for the default Sherlock encoding.
- */
- function sherlockBytesToLines(aBytes, aCharsetCode) {
- // fileCharsetFromCode returns the default encoding if aCharsetCode is null
- var charset = fileCharsetFromCode(aCharsetCode);
-
- var dataString = bytesToString(aBytes, charset);
- ENSURE(dataString, "sherlockBytesToLines: Couldn't convert byte array!",
- Cr.NS_ERROR_FAILURE);
-
- // Split the string into lines, and filter out comments and
- // whitespace-only lines
- return dataString.split(NEW_LINES).filter(isUsefulLine);
- }
-
- /**
- * Gets the current value of the locale. It's possible for this preference to
- * be localized, so we have to do a little extra work here. Similar code
- * exists in nsHttpHandler.cpp when building the UA string.
- */
- function getLocale() {
- const localePref = "general.useragent.locale";
- var locale = getLocalizedPref(localePref);
- if (locale)
- return locale;
-
- // Not localized
- var prefs = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- return prefs.getCharPref(localePref);
- }
-
- /**
- * Wrapper for nsIPrefBranch::getComplexValue.
- * @param aPrefName
- * The name of the pref to get.
- * @returns aDefault if the requested pref doesn't exist.
- */
- function getLocalizedPref(aPrefName, aDefault) {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- const nsIPLS = Ci.nsIPrefLocalizedString;
- try {
- return prefB.getComplexValue(aPrefName, nsIPLS).data;
- } catch (ex) {}
-
- return aDefault;
- }
-
- /**
- * Wrapper for nsIPrefBranch::setComplexValue.
- * @param aPrefName
- * The name of the pref to set.
- */
- function setLocalizedPref(aPrefName, aValue) {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- const nsIPLS = Ci.nsIPrefLocalizedString;
- try {
- var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
- .createInstance(Ci.nsIPrefLocalizedString);
- pls.data = aValue;
- prefB.setComplexValue(aPrefName, nsIPLS, pls);
- } catch (ex) {}
- }
-
- /**
- * Wrapper for nsIPrefBranch::getBoolPref.
- * @param aPrefName
- * The name of the pref to get.
- * @returns aDefault if the requested pref doesn't exist.
- */
- function getBoolPref(aName, aDefault) {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- try {
- return prefB.getBoolPref(aName);
- } catch (ex) {
- return aDefault;
- }
- }
-
- /**
- * Get a unique nsIFile object with a sanitized name, based on the engine name.
- * @param aName
- * A name to "sanitize". Can be an empty string, in which case a random
- * 8 character filename will be produced.
- * @returns A nsIFile object in the user's search engines directory with a
- * unique sanitized name.
- */
- function getSanitizedFile(aName) {
- var fileName = sanitizeName(aName) + "." + XML_FILE_EXT;
- var file = getDir(NS_APP_USER_SEARCH_DIR);
- file.append(fileName);
- file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
- return file;
- }
-
- /**
- * Removes all characters not in the "chars" string from aName.
- *
- * @returns a sanitized name to be used as a filename, or a random name
- * if a sanitized name cannot be obtained (if aName contains
- * no valid characters).
- */
- function sanitizeName(aName) {
- const chars = "-abcdefghijklmnopqrstuvwxyz0123456789";
- const maxLength = 60;
-
- var name = aName.toLowerCase();
- name = name.replace(/ /g, "-");
- name = name.split("").filter(function (el) {
- return chars.indexOf(el) != -1;
- }).join("");
-
- if (!name) {
- // Our input had no valid characters - use a random name
- var cl = chars.length - 1;
- for (var i = 0; i < 8; ++i)
- name += chars.charAt(Math.round(Math.random() * cl));
- }
-
- if (name.length > maxLength)
- name = name.substring(0, maxLength);
-
- return name;
- }
-
- /**
- * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
- * the state of the search service.
- *
- * @param aEngine
- * The nsISearchEngine object to which the change applies.
- * @param aVerb
- * A verb describing the change.
- *
- * @see nsIBrowserSearchService.idl
- */
- function notifyAction(aEngine, aVerb) {
- var os = Cc["@mozilla.org/observer-service;1"].
- getService(Ci.nsIObserverService);
- LOG("NOTIFY: Engine: \"" + aEngine.name + "\"; Verb: \"" + aVerb + "\"");
- os.notifyObservers(aEngine, SEARCH_ENGINE_TOPIC, aVerb);
- }
-
- /**
- * Simple object representing a name/value pair.
- */
- function QueryParameter(aName, aValue) {
- ENSURE_ARG(aName && (aValue != null),
- "missing name or value for QueryParameter!");
-
- this.name = aName;
- this.value = aValue;
- }
-
- /**
- * Perform OpenSearch parameter substitution on aParamValue.
- *
- * @param aParamValue
- * A string containing OpenSearch search parameters.
- * @param aSearchTerms
- * The user-provided search terms. This string will inserted into
- * aParamValue as the value of the OS_PARAM_USER_DEFINED parameter.
- * This value must already be escaped appropriately - it is inserted
- * as-is.
- * @param aQueryEncoding
- * The value to use for the OS_PARAM_INPUT_ENCODING parameter. See
- * definition in the OpenSearch spec.
- *
- * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
- */
- function ParamSubstitution(aParamValue, aSearchTerms, aEngine) {
- var value = aParamValue;
-
- var distributionID = MOZ_DISTRIBUTION_ID;
- try {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- distributionID = prefB.getCharPref(BROWSER_SEARCH_PREF + "distributionID");
- }
- catch (ex) { }
-
- // Custom search parameters. These are only available to default search
- // engines.
- if (aEngine._isDefault) {
- value = value.replace(MOZ_PARAM_LOCALE, getLocale());
- value = value.replace(MOZ_PARAM_DIST_ID, distributionID);
- value = value.replace(MOZ_PARAM_OFFICIAL, MOZ_OFFICIAL);
- }
-
- // Insert the OpenSearch parameters we're confident about
- value = value.replace(OS_PARAM_USER_DEFINED, aSearchTerms);
- value = value.replace(OS_PARAM_INPUT_ENCODING, aEngine.queryCharset);
- value = value.replace(OS_PARAM_LANGUAGE,
- getLocale() || OS_PARAM_LANGUAGE_DEF);
- value = value.replace(OS_PARAM_OUTPUT_ENCODING,
- OS_PARAM_OUTPUT_ENCODING_DEF);
-
- // Replace any optional parameters
- value = value.replace(OS_PARAM_OPTIONAL, "");
-
- // Insert any remaining required params with our default values
- for (var i = 0; i < OS_UNSUPPORTED_PARAMS.length; ++i) {
- value = value.replace(OS_UNSUPPORTED_PARAMS[i][0],
- OS_UNSUPPORTED_PARAMS[i][1]);
- }
-
- return value;
- }
-
- /**
- * Creates a mozStorage statement that can be used to access the database we
- * use to hold metadata.
- *
- * @param dbconn the database that the statement applies to
- * @param sql a string specifying the sql statement that should be created
- */
- function createStatement (dbconn, sql) {
- var stmt = dbconn.createStatement(sql);
- var wrapper = Cc["@mozilla.org/storage/statement-wrapper;1"].
- createInstance(Ci.mozIStorageStatementWrapper);
-
- wrapper.initialize(stmt);
- return wrapper;
- }
-
- /**
- * Creates an engineURL object, which holds the query URL and all parameters.
- *
- * @param aType
- * A string containing the name of the MIME type of the search results
- * returned by this URL.
- * @param aMethod
- * The HTTP request method. Must be a case insensitive value of either
- * "GET" or "POST".
- * @param aTemplate
- * The URL to which search queries should be sent. For GET requests,
- * must contain the string "{searchTerms}", to indicate where the user
- * entered search terms should be inserted.
- *
- * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
- *
- * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
- */
- function EngineURL(aType, aMethod, aTemplate) {
- ENSURE_ARG(aType && aMethod && aTemplate,
- "missing type, method or template for EngineURL!");
-
- var method = aMethod.toUpperCase();
- var type = aType.toLowerCase();
-
- ENSURE_ARG(method == "GET" || method == "POST",
- "method passed to EngineURL must be \"GET\" or \"POST\"");
-
- this.type = type;
- this.method = method;
- this.params = [];
-
- var templateURI = makeURI(aTemplate);
- ENSURE(templateURI, "new EngineURL: template is not a valid URI!",
- Cr.NS_ERROR_FAILURE);
-
- switch (templateURI.scheme) {
- case "http":
- case "https":
- // Disable these for now, see bug 295018
- // case "file":
- // case "resource":
- this.template = aTemplate;
- break;
- default:
- ENSURE(false, "new EngineURL: template uses invalid scheme!",
- Cr.NS_ERROR_FAILURE);
- }
- }
- EngineURL.prototype = {
-
- addParam: function SRCH_EURL_addParam(aName, aValue) {
- this.params.push(new QueryParameter(aName, aValue));
- },
-
- getSubmission: function SRCH_EURL_getSubmission(aSearchTerms, aEngine) {
- var url = ParamSubstitution(this.template, aSearchTerms, aEngine);
-
- // Create an application/x-www-form-urlencoded representation of our params
- // (name=value&name=value&name=value)
- var dataString = "";
- for (var i = 0; i < this.params.length; ++i) {
- var param = this.params[i];
- var value = ParamSubstitution(param.value, aSearchTerms, aEngine);
-
- dataString += (i > 0 ? "&" : "") + param.name + "=" + value;
- }
-
- var postData = null;
- if (this.method == "GET") {
- // GET method requests have no post data, and append the encoded
- // query string to the url...
- if (url.indexOf("?") == -1 && dataString)
- url += "?";
- url += dataString;
- } else if (this.method == "POST") {
- // POST method requests must wrap the encoded text in a MIME
- // stream and supply that as POSTDATA.
- var stringStream = Cc["@mozilla.org/io/string-input-stream;1"].
- createInstance(Ci.nsIStringInputStream);
- /*
- #ifdef MOZILLA_1_8_BRANCH
- # bug 318193
- stringStream.setData(dataString, dataString.length);
- #else
- */
- stringStream.data = dataString;
- /*
- #endif
- */
- postData = Cc["@mozilla.org/network/mime-input-stream;1"].
- createInstance(Ci.nsIMIMEInputStream);
- postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
- postData.addContentLength = true;
- postData.setData(stringStream);
- }
-
- return new Submission(makeURI(url), postData);
- },
-
- /**
- * Serializes the engine object to a OpenSearch Url element.
- * @param aDoc
- * The document to use to create the Url element.
- * @param aElement
- * The element to which the created Url element is appended.
- *
- * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
- */
- _serializeToElement: function SRCH_EURL_serializeToEl(aDoc, aElement) {
- var url = aDoc.createElementNS(OPENSEARCH_NS_11, "Url");
- url.setAttribute("type", this.type);
- url.setAttribute("method", this.method);
- url.setAttribute("template", this.template);
-
- for (var i = 0; i < this.params.length; ++i) {
- var param = aDoc.createElementNS(OPENSEARCH_NS_11, "Param");
- param.setAttribute("name", this.params[i].name);
- param.setAttribute("value", this.params[i].value);
- url.appendChild(aDoc.createTextNode("\n "));
- url.appendChild(param);
- }
- url.appendChild(aDoc.createTextNode("\n"));
- aElement.appendChild(url);
- }
- };
-
- /**
- * nsISearchEngine constructor.
- * @param aLocation
- * A nsILocalFile or nsIURI object representing the location of the
- * search engine data file.
- * @param aSourceDataType
- * The data type of the file used to describe the engine. Must be either
- * DATA_XML or DATA_TEXT.
- * @param aIsReadOnly
- * Boolean indicating whether the engine should be treated as read-only.
- * Read only engines cannot be serialized to file.
- */
- function Engine(aLocation, aSourceDataType, aIsReadOnly) {
- this._dataType = aSourceDataType;
- this._readOnly = aIsReadOnly;
- this._urls = [];
-
- if (aLocation instanceof Ci.nsILocalFile) {
- // we already have a file (e.g. loading engines from disk)
- this._file = aLocation;
- } else if (aLocation instanceof Ci.nsIURI) {
- this._uri = aLocation;
- switch (aLocation.scheme) {
- case "https":
- case "http":
- case "ftp":
- case "data":
- case "file":
- case "resource":
- case "chrome":
- this._uri = aLocation;
- break;
- default:
- ERROR("Invalid URI passed to the nsISearchEngine constructor",
- Cr.NS_ERROR_INVALID_ARG);
- }
- } else
- ERROR("Engine location is neither a File nor a URI object",
- Cr.NS_ERROR_INVALID_ARG);
- }
-
- Engine.prototype = {
- // The engine's alias.
- _alias: null,
- // The data describing the engine. Is either an array of bytes, for Sherlock
- // files, or an XML document element, for XML plugins.
- _data: null,
- // The engine's data type. See data types (DATA_) defined above.
- _dataType: null,
- // Whether or not the engine is readonly.
- _readOnly: true,
- // The engine's description
- _description: "",
- // Used to store the engine to replace, if we're an update to an existing
- // engine.
- _engineToUpdate: null,
- // The file from which the plugin was loaded.
- _file: null,
- // Set to true if the engine has a preferred icon (an icon that should not be
- // overridden by a non-preferred icon).
- _hasPreferredIcon: null,
- // Whether the engine is hidden from the user.
- _hidden: null,
- // The engine's name.
- _name: null,
- // The engine type. See engine types (TYPE_) defined above.
- _type: null,
- // Space delimited keyword string
- _tags: "",
- // The name of the charset used to submit the search terms.
- _queryCharset: null,
- // A URL string pointing to the engine's search form.
- _searchForm: null,
- // The URI object from which the engine was retrieved.
- // This is null for local plugins, and is used for error messages and logging.
- _uri: null,
- // Whether to obtain user confirmation before adding the engine. This is only
- // used when the engine is first added to the list.
- _confirm: false,
- // Whether to set this as the current engine as soon as it is loaded. This
- // is only used when the engine is first added to the list.
- _useNow: true,
- // Where the engine was loaded from. Can be one of: SEARCH_APP_DIR,
- // SEARCH_PROFILE_DIR, SEARCH_IN_EXTENSION.
- __installLocation: null,
- // The number of days between update checks for new versions
- _updateInterval: null,
- // The url to check at for a new update
- _updateURL: null,
- // The url to check for a new icon
- _iconUpdateURL: null,
- // A reference to the timer used for lazily serializing the engine to file
- _serializeTimer: null,
-
- /**
- * Retrieves the data from the engine's file. If the engine's dataType is
- * XML, the document element is placed in the engine's data field. For text
- * engines, the data is just read directly from file and placed as an array
- * of lines in the engine's data field.
- */
- _initFromFile: function SRCH_ENG_initFromFile() {
- ENSURE(this._file && this._file.exists(),
- "File must exist before calling initFromFile!",
- Cr.NS_ERROR_UNEXPECTED);
-
- var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
- createInstance(Ci.nsIFileInputStream);
-
- fileInStream.init(this._file, MODE_RDONLY, PERMS_FILE, false);
-
- switch (this._dataType) {
- case SEARCH_DATA_XML:
- var domParser = Cc["@mozilla.org/xmlextras/domparser;1"].
- createInstance(Ci.nsIDOMParser);
- var doc = domParser.parseFromStream(fileInStream, "UTF-8",
- this._file.fileSize,
- "text/xml");
-
- this._data = doc.documentElement;
- break;
- case SEARCH_DATA_TEXT:
- var binaryInStream = Cc["@mozilla.org/binaryinputstream;1"].
- createInstance(Ci.nsIBinaryInputStream);
- binaryInStream.setInputStream(fileInStream);
-
- var bytes = binaryInStream.readByteArray(binaryInStream.available());
- this._data = bytes;
-
- break;
- default:
- ERROR("Bogus engine _dataType: \"" + this._dataType + "\"",
- Cr.NS_ERROR_UNEXPECTED);
- }
- fileInStream.close();
-
- // Now that the data is loaded, initialize the engine object
- this._initFromData();
- },
-
- /**
- * Retrieves the engine data from a URI.
- */
- _initFromURI: function SRCH_ENG_initFromURI() {
- ENSURE_WARN(this._uri instanceof Ci.nsIURI,
- "Must have URI when calling _initFromURI!",
- Cr.NS_ERROR_UNEXPECTED);
-
- LOG("_initFromURI: Downloading engine from: \"" + this._uri.spec + "\".");
-
- var ios = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService);
- var chan = ios.newChannelFromURI(this._uri);
-
- if (this._engineToUpdate && (chan instanceof Ci.nsIHttpChannel)) {
- var lastModified = engineMetadataService.getAttr(this._engineToUpdate,
- "updatelastmodified");
- if (lastModified)
- chan.setRequestHeader("If-Modified-Since", lastModified, false);
- }
- var listener = new loadListener(chan, this, this._onLoad);
- chan.notificationCallbacks = listener;
- chan.asyncOpen(listener, null);
- },
-
- /**
- * Attempts to find an EngineURL object in the set of EngineURLs for
- * this Engine that has the given type string. (This corresponds to the
- * "type" attribute in the "Url" node in the OpenSearch spec.)
- * This method will return the first matching URL object found, or null
- * if no matching URL is found.
- *
- * @param aType string to match the EngineURL's type attribute
- */
- _getURLOfType: function SRCH_ENG__getURLOfType(aType) {
- for (var i = 0; i < this._urls.length; ++i) {
- if (this._urls[i].type == aType)
- return this._urls[i];
- }
-
- return null;
- },
-
- _confirmAddEngine: function SRCH_SVC_confirmAddEngine() {
- var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
- getService(Ci.nsIStringBundleService);
- var stringBundle = sbs.createBundle(SEARCH_BUNDLE);
- var titleMessage = stringBundle.GetStringFromName("addEngineConfirmTitle");
-
- // Display only the hostname portion of the URL.
- var dialogMessage =
- stringBundle.formatStringFromName("addEngineConfirmation",
- [this._name, this._uri.host], 2);
- var checkboxMessage = stringBundle.GetStringFromName("addEngineUseNowText");
- var addButtonLabel =
- stringBundle.GetStringFromName("addEngineAddButtonLabel");
-
- var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
- getService(Ci.nsIPromptService);
- var buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
- (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1) +
- ps.BUTTON_POS_0_DEFAULT;
-
- var checked = {value: false};
- // confirmEx returns the index of the button that was pressed. Since "Add"
- // is button 0, we want to return the negation of that value.
- var confirm = !ps.confirmEx(null,
- titleMessage,
- dialogMessage,
- buttonFlags,
- addButtonLabel,
- null, null, // button 1 & 2 names not used
- checkboxMessage,
- checked);
-
- return {confirmed: confirm, useNow: checked.value};
- },
-
- /**
- * Handle the successful download of an engine. Initializes the engine and
- * triggers parsing of the data. The engine is then flushed to disk. Notifies
- * the search service once initialization is complete.
- */
- _onLoad: function SRCH_ENG_onLoad(aBytes, aEngine) {
- /**
- * Handle an error during the load of an engine by prompting the user to
- * notify him that the load failed.
- */
- function onError(aErrorString, aTitleString) {
- if (aEngine._engineToUpdate) {
- // We're in an update, so just fail quietly
- LOG("updating " + aEngine._engineToUpdate.name + " failed");
- return;
- }
- var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
- getService(Ci.nsIStringBundleService);
-
- var brandBundle = sbs.createBundle(BRAND_BUNDLE);
- var brandName = brandBundle.GetStringFromName("brandShortName");
-
- var searchBundle = sbs.createBundle(SEARCH_BUNDLE);
- var msgStringName = aErrorString || "error_loading_engine_msg2";
- var titleStringName = aTitleString || "error_loading_engine_title";
- var title = searchBundle.GetStringFromName(titleStringName);
- var text = searchBundle.formatStringFromName(msgStringName,
- [brandName, aEngine._location],
- 2);
-
- var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
- getService(Ci.nsIWindowWatcher);
- ww.getNewPrompter(null).alert(title, text);
- }
-
- if (!aBytes) {
- onError();
- return;
- }
-
- var engineToUpdate = null;
- if (aEngine._engineToUpdate) {
- engineToUpdate = aEngine._engineToUpdate.wrappedJSObject;
-
- // Make this new engine use the old engine's file.
- aEngine._file = engineToUpdate._file;
- }
-
- switch (aEngine._dataType) {
- case SEARCH_DATA_XML:
- var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
- createInstance(Ci.nsIDOMParser);
- var doc = parser.parseFromBuffer(aBytes, aBytes.length, "text/xml");
- aEngine._data = doc.documentElement;
- break;
- case SEARCH_DATA_TEXT:
- aEngine._data = aBytes;
- break;
- default:
- onError();
- LOG("_onLoad: Bogus engine _dataType: \"" + this._dataType + "\"");
- return;
- }
-
- try {
- // Initialize the engine from the obtained data
- aEngine._initFromData();
- } catch (ex) {
- LOG("_onLoad: Failed to init engine!\n" + ex);
- // Report an error to the user
- onError();
- return;
- }
-
- // Check to see if this is a duplicate engine. If we're confirming the
- // engine load, then we display a "this is a duplicate engine" prompt,
- // otherwise we fail silently.
- if (!engineToUpdate) {
- var ss = Cc["@mozilla.org/browser/search-service;1"].
- getService(Ci.nsIBrowserSearchService);
- if (ss.getEngineByName(aEngine.name)) {
- if (aEngine._confirm)
- onError("error_duplicate_engine_msg", "error_invalid_engine_title");
-
- LOG("_onLoad: duplicate engine found, bailing");
- return;
- }
- }
-
- // If requested, confirm the addition now that we have the title.
- // This property is only ever true for engines added via
- // nsIBrowserSearchService::addEngine.
- if (aEngine._confirm) {
- var confirmation = aEngine._confirmAddEngine();
- LOG("_onLoad: confirm is " + confirmation.confirmed +
- "; useNow is " + confirmation.useNow);
- if (!confirmation.confirmed)
- return;
- aEngine._useNow = confirmation.useNow;
- }
-
- // If we don't yet have a file, get one now. The only case where we would
- // already have a file is if this is an update and _file was set above.
- if (!aEngine._file)
- aEngine._file = getSanitizedFile(aEngine.name);
-
- if (engineToUpdate) {
- // Keep track of the last modified date, so that we can make conditional
- // requests for future updates.
- engineMetadataService.setAttr(aEngine, "updatelastmodified",
- (new Date()).toUTCString());
-
- // Set the new engine's icon, if it doesn't yet have one.
- if (!aEngine._iconURI && engineToUpdate._iconURI)
- aEngine._iconURI = engineToUpdate._iconURI;
-
- // Clear the "use now" flag since we don't want to be changing the
- // current engine for an update.
- aEngine._useNow = false;
- }
-
- // Write the engine to file
- aEngine._serializeToFile();
-
- // Notify the search service of the sucessful load. It will deal with
- // updates by checking aEngine._engineToUpdate.
- notifyAction(aEngine, SEARCH_ENGINE_LOADED);
- },
-
- /**
- * Sets the .iconURI property of the engine.
- *
- * @param aIconURL
- * A URI string pointing to the engine's icon. Must have a http[s],
- * ftp, or data scheme. Icons with HTTP[S] or FTP schemes will be
- * downloaded and converted to data URIs for storage in the engine
- * XML files, if the engine is not readonly.
- * @param aIsPreferred
- * Whether or not this icon is to be preferred. Preferred icons can
- * override non-preferred icons.
- */
- _setIcon: function SRCH_ENG_setIcon(aIconURL, aIsPreferred) {
- // If we already have a preferred icon, and this isn't a preferred icon,
- // just ignore it.
- if (this._hasPreferredIcon && !aIsPreferred)
- return;
-
- var uri = makeURI(aIconURL);
-
- // Ignore bad URIs
- if (!uri)
- return;
-
- LOG("_setIcon: Setting icon url \"" + uri.spec + "\" for engine \""
- + this.name + "\".");
- // Only accept remote icons from http[s] or ftp
- switch (uri.scheme) {
- // Songbird: Accept chrome icons. Needed for library stub.
- case "chrome":
- case "data":
- this._iconURI = uri;
- notifyAction(this, SEARCH_ENGINE_CHANGED);
- this._hasPreferredIcon = aIsPreferred;
- break;
- case "http":
- case "https":
- case "ftp":
- // No use downloading the icon if the engine file is read-only
- if (!this._readOnly) {
- LOG("_setIcon: Downloading icon: \"" + uri.spec +
- "\" for engine: \"" + this.name + "\"");
- var ios = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService);
- var chan = ios.newChannelFromURI(uri);
-
- function iconLoadCallback(aByteArray, aEngine) {
- // This callback may run after we've already set a preferred icon,
- // so check again.
- if (aEngine._hasPreferredIcon && !aIsPreferred)
- return;
-
- if (!aByteArray || aByteArray.length > MAX_ICON_SIZE) {
- LOG("iconLoadCallback: load failed, or the icon was too large!");
- return;
- }
-
- var str = btoa(String.fromCharCode.apply(null, aByteArray));
- aEngine._iconURI = makeURI(ICON_DATAURL_PREFIX + str);
-
- // The engine might not have a file yet, if it's being downloaded,
- // because the request for the engine file itself (_onLoad) may not
- // yet be complete. In that case, this change will be written to
- // file when _onLoad is called.
- if (aEngine._file)
- aEngine._serializeToFile();
-
- notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
- aEngine._hasPreferredIcon = aIsPreferred;
- }
-
- // If we're currently acting as an "update engine", then the callback
- // should set the icon on the engine we're updating and not us, since
- // |this| might be gone by the time the callback runs.
- var engineToSet = this._engineToUpdate || this;
-
- var listener = new loadListener(chan, engineToSet, iconLoadCallback);
- chan.notificationCallbacks = listener;
- chan.asyncOpen(listener, null);
- }
- break;
- }
- },
-
- /**
- * Initialize this Engine object from the collected data.
- */
- _initFromData: function SRCH_ENG_initFromData() {
-
- ENSURE_WARN(this._data, "Can't init an engine with no data!",
- Cr.NS_ERROR_UNEXPECTED);
-
- // Find out what type of engine we are
- switch (this._dataType) {
- case SEARCH_DATA_XML:
- if (checkNameSpace(this._data, [MOZSEARCH_LOCALNAME],
- [MOZSEARCH_NS_10])) {
-
- LOG("_init: Initing MozSearch plugin from " + this._location);
-
- this._type = SEARCH_TYPE_MOZSEARCH;
- this._parseAsMozSearch();
-
- } else if (checkNameSpace(this._data, [OPENSEARCH_LOCALNAME],
- OPENSEARCH_NAMESPACES)) {
-
- LOG("_init: Initing OpenSearch plugin from " + this._location);
-
- this._type = SEARCH_TYPE_OPENSEARCH;
- this._parseAsOpenSearch();
-
- } else
- ENSURE(false, this._location + " is not a valid search plugin.",
- Cr.NS_ERROR_FAILURE);
-
- break;
- case SEARCH_DATA_TEXT:
- LOG("_init: Initing Sherlock plugin from " + this._location);
-
- // the only text-based format we support is Sherlock
- this._type = SEARCH_TYPE_SHERLOCK;
- this._parseAsSherlock();
- }
-
- // No need to keep a ref to our data (which in some cases can be a document
- // element) past this point
- this._data = null;
- },
-
- /**
- * Initialize this Engine object from a collection of metadata.
- */
- _initFromMetadata: function SRCH_ENG_initMetaData(aName, aIconURL, aAlias,
- aDescription, aMethod,
- aTemplate) {
- ENSURE_WARN(!this._readOnly,
- "Can't call _initFromMetaData on a readonly engine!",
- Cr.NS_ERROR_FAILURE);
-
- this._urls.push(new EngineURL("text/html", aMethod, aTemplate));
-
- this._name = aName;
- this._alias = aAlias;
- this._description = aDescription;
- this._setIcon(aIconURL, true);
-
- this._serializeToFile();
- },
-
- /**
- * Extracts data from an OpenSearch URL element and creates an EngineURL
- * object which is then added to the engine's list of URLs.
- *
- * @throws NS_ERROR_FAILURE if a URL object could not be created.
- *
- * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag.
- * @see EngineURL()
- */
- _parseURL: function SRCH_ENG_parseURL(aElement) {
- var type = aElement.getAttribute("type");
- // According to the spec, method is optional, defaulting to "GET" if not
- // specified
- var method = aElement.getAttribute("method") || "GET";
- var template = aElement.getAttribute("template");
-
- try {
- var url = new EngineURL(type, method, template);
- } catch (ex) {
- LOG("_parseURL: failed to add " + template + " as a URL");
- throw Cr.NS_ERROR_FAILURE;
- }
-
- for (var i = 0; i < aElement.childNodes.length; ++i) {
- var param = aElement.childNodes[i];
- if (param.localName == "Param") {
- try {
- url.addParam(param.getAttribute("name"), param.getAttribute("value"));
- } catch (ex) {
- // Ignore failure
- LOG("_parseURL: Url element has an invalid param");
- }
- } else if (param.localName == "MozParam" &&
- // We only support MozParams for default search engines
- this._isDefault) {
- var value;
- switch (param.getAttribute("condition")) {
- case "defaultEngine":
- const defPref = BROWSER_SEARCH_PREF + "defaultenginename";
- var defaultPrefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefService).
- getDefaultBranch(null);
- const nsIPLS = Ci.nsIPrefLocalizedString;
- var defaultName;
- try {
- defaultName = defaultPrefB.getComplexValue(defPref, nsIPLS).data;
- } catch (ex) {}
-
- // If this engine was the default search engine, use the true value
- if (this.name == defaultName)
- value = param.getAttribute("trueValue");
- else
- value = param.getAttribute("falseValue");
- url.addParam(param.getAttribute("name"), value);
- break;
-
- case "pref":
- try {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- value = prefB.getCharPref(BROWSER_SEARCH_PREF + "param." +
- param.getAttribute("pref"));
- url.addParam(param.getAttribute("name"), value);
- } catch (e) { }
- break;
- }
- }
- }
-
- this._urls.push(url);
- },
-
- /**
- * Get the icon from an OpenSearch Image element.
- * @see http://opensearch.a9.com/spec/1.1/description/#image
- */
- _parseImage: function SRCH_ENG_parseImage(aElement) {
- LOG("_parseImage: Image textContent: \"" + aElement.textContent + "\"");
- if (aElement.getAttribute("width") == "16" &&
- aElement.getAttribute("height") == "16") {
- this._setIcon(aElement.textContent, true);
- }
- },
-
- _parseAsMozSearch: function SRCH_ENG_parseAsMoz() {
- //forward to the OpenSearch parser
- this._parseAsOpenSearch();
- },
-
- /**
- * Extract search engine information from the collected data to initialize
- * the engine object.
- */
- _parseAsOpenSearch: function SRCH_ENG_parseAsOS() {
- var doc = this._data;
-
- // The OpenSearch spec sets a default value for the input encoding.
- this._queryCharset = OS_PARAM_INPUT_ENCODING_DEF;
-
- for (var i = 0; i < doc.childNodes.length; ++i) {
- var child = doc.childNodes[i];
- switch (child.localName) {
- case "ShortName":
- this._name = child.textContent;
- break;
- case "Description":
- this._description = child.textContent;
- break;
- case "Url":
- try {
- this._parseURL(child);
- } catch (ex) {
- // Parsing of the element failed, just skip it.
- }
- break;
- case "Image":
- this._parseImage(child);
- break;
- case "InputEncoding":
- this._queryCharset = child.textContent.toUpperCase();
- break;
- case "Tags":
- this._tags = child.textContent;
- break;
-
- // Non-OpenSearch elements
- case "Alias":
- this._alias = child.textContent;
- break;
- case "SearchForm":
- this._searchForm = child.textContent;
- break;
- case "UpdateUrl":
- this._updateURL = child.textContent;
- break;
- case "UpdateInterval":
- this._updateInterval = parseInt(child.textContent);
- break;
- case "IconUpdateUrl":
- this._iconUpdateURL = child.textContent;
- break;
- }
- }
- ENSURE(this.name && (this._urls.length > 0),
- "_parseAsOpenSearch: No name, or missing URL!",
- Cr.NS_ERROR_FAILURE);
- ENSURE(this.supportsResponseType(URLTYPE_SEARCH_HTML),
- "_parseAsOpenSearch: No text/html result type!",
- Cr.NS_ERROR_FAILURE);
- },
-
- /**
- * Extract search engine information from the collected data to initialize
- * the engine object.
- */
- _parseAsSherlock: function SRCH_ENG_parseAsSherlock() {
- /**
- * Trims leading and trailing whitespace from aStr.
- */
- function sTrim(aStr) {
- return aStr.replace(/^\s+/g, "").replace(/\s+$/g, "");
- }
-
- /**
- * Extracts one Sherlock "section" from aSource. A section is essentially
- * an HTML element with attributes, but each attribute must be on a new
- * line, by definition.
- *
- * @param aLines
- * An array of lines from the sherlock file.
- * @param aSection
- * The name of the section (e.g. "search" or "browser"). This value
- * is not case sensitive.
- * @returns an object whose properties correspond to the section's
- * attributes.
- */
- function getSection(aLines, aSection) {
- LOG("_parseAsSherlock::getSection: Sherlock lines:\n" +
- aLines.join("\n"));
- var lines = aLines;
- var startMark = new RegExp("^\\s*<" + aSection.toLowerCase() + "\\s*",
- "gi");
- var endMark = /\s*>\s*$/gi;
-
- var foundStart = false;
- var startLine, numberOfLines;
- // Find the beginning and end of the section
- for (var i = 0; i < lines.length; i++) {
- if (foundStart) {
- if (endMark.test(lines[i])) {
- numberOfLines = i - startLine;
- // Remove the end marker
- lines[i] = lines[i].replace(endMark, "");
- // If the endmarker was not the only thing on the line, include
- // this line in the results
- if (lines[i])
- numberOfLines++;
- break;
- }
- } else {
- if (startMark.test(lines[i])) {
- foundStart = true;
- // Remove the start marker
- lines[i] = lines[i].replace(startMark, "");
- startLine = i;
- // If the line is empty, don't include it in the result
- if (!lines[i])
- startLine++;
- }
- }
- }
- LOG("_parseAsSherlock::getSection: Start index: " + startLine +
- "\nNumber of lines: " + numberOfLines);
- lines = lines.splice(startLine, numberOfLines);
- LOG("_parseAsSherlock::getSection: Section lines:\n" +
- lines.join("\n"));
-
- var section = {};
- for (var i = 0; i < lines.length; i++) {
- var line = sTrim(lines[i]);
-
- var els = line.split("=");
- var name = sTrim(els.shift().toLowerCase());
- var value = sTrim(els.join("="));
-
- if (!name || !value)
- continue;
-
- // Strip leading and trailing whitespace, remove quotes from the
- // value, and remove any trailing slashes or ">" characters
- value = value.replace(/^["']/, "")
- .replace(/["']\s*[\\\/]?>?\s*$/, "") || "";
- value = sTrim(value);
-
- // Don't clobber existing attributes
- if (!(name in section))
- section[name] = value;
- }
- return section;
- }
-
- /**
- * Returns an array of name-value pair arrays representing the Sherlock
- * file's input elements. User defined inputs return USER_DEFINED
- * as the value. Elements are returned in the order they appear in the
- * source file.
- *
- * Example:
- * <input name="foo" value="bar">
- * <input name="foopy" user>
- * Returns:
- * [["foo", "bar"], ["foopy", "{searchTerms}"]]
- *
- * @param aLines
- * An array of lines from the source file.
- */
- function getInputs(aLines) {
-
- /**
- * Extracts an attribute value from a given a line of text.
- * Example: <input value="foo" name="bar">
- * Extracts the string |foo| or |bar| given an input aAttr of
- * |value| or |name|.
- * Attributes may be quoted or unquoted. If unquoted, any whitespace
- * indicates the end of the attribute value.
- * Example: < value=22 33 name=44\334 >
- * Returns |22| for "value" and |44\334| for "name".
- *
- * @param aAttr
- * The name of the attribute for which to obtain the value. This
- * value is not case sensitive.
- * @param aLine
- * The line containing the attribute.
- *
- * @returns the attribute value, or an empty string if the attribute
- * doesn't exist.
- */
- function getAttr(aAttr, aLine) {
- // Used to determine whether an "input" line from a Sherlock file is a
- // "user defined" input.
- const userInput = /(\s|["'=])user(\s|[>="'\/\\+]|$)/i;
-
- LOG("_parseAsSherlock::getAttr: Getting attr: \"" +
- aAttr + "\" for line: \"" + aLine + "\"");
- // We're not case sensitive, but we want to return the attribute value
- // in its original case, so create a copy of the source
- var lLine = aLine.toLowerCase();
- var attr = aAttr.toLowerCase();
-
- var attrStart = lLine.search(new RegExp("\\s" + attr, "i"));
- if (attrStart == -1) {
-
- // If this is the "user defined input" (i.e. contains the empty
- // "user" attribute), return our special keyword
- if (userInput.test(lLine) && attr == "value") {
- LOG("_parseAsSherlock::getAttr: Found user input!\nLine:\"" + lLine
- + "\"");
- return USER_DEFINED;
- }
- // The attribute doesn't exist - ignore
- LOG("_parseAsSherlock::getAttr: Failed to find attribute:\nLine:\""
- + lLine + "\"\nAttr:\"" + attr + "\"");
- return "";
- }
-
- var valueStart = lLine.indexOf("=", attrStart) + "=".length;
- if (valueStart == -1)
- return "";
-
- var quoteStart = lLine.indexOf("\"", valueStart);
- if (quoteStart == -1) {
-
- // Unquoted attribute, get the rest of the line, trimmed at the first
- // sign of whitespace. If the rest of the line is only whitespace,
- // returns a blank string.
- return lLine.substr(valueStart).replace(/\s.*$/, "");
-
- } else {
- // Make sure that there's only whitespace between the start of the
- // value and the first quote. If there is, end the attribute value at
- // the first sign of whitespace. This prevents us from falling into
- // the next attribute if this is an unquoted attribute followed by a
- // quoted attribute.
- var betweenEqualAndQuote = lLine.substring(valueStart, quoteStart);
- if (/\S/.test(betweenEqualAndQuote))
- return lLine.substr(valueStart).replace(/\s.*$/, "");
-
- // Adjust the start index to account for the opening quote
- valueStart = quoteStart + "\"".length;
- // Find the closing quote
- valueEnd = lLine.indexOf("\"", valueStart);
- // If there is no closing quote, just go to the end of the line
- if (valueEnd == -1)
- valueEnd = aLine.length;
- }
- return aLine.substring(valueStart, valueEnd);
- }
-
- var inputs = [];
-
- LOG("_parseAsSherlock::getInputs: Lines:\n" + aLines);
- // Filter out everything but non-inputs
- lines = aLines.filter(function (line) {
- return /^\s*<input/i.test(line);
- });
- LOG("_parseAsSherlock::getInputs: Filtered lines:\n" + lines);
-
- lines.forEach(function (line) {
- // Strip leading/trailing whitespace and remove the surrounding markup
- // ("<input" and ">")
- line = sTrim(line).replace(/^<input/i, "").replace(/>$/, "");
-
- // If this is one of the "directional" inputs (<inputnext>/<inputprev>)
- const directionalInput = /^(prev|next)/i;
- if (directionalInput.test(line)) {
-
- // Make it look like a normal input by removing "prev" or "next"
- line = line.replace(directionalInput, "");
-
- // If it has a name, give it a dummy value to match previous
- // nsInternetSearchService behavior
- if (/name\s*=/i.test(line)) {
- line += " value=\"0\"";
- } else
- return; // Line has no name, skip it
- }
-
- var attrName = getAttr("name", line);
- var attrValue = getAttr("value", line);
- LOG("_parseAsSherlock::getInputs: Got input:\nName:\"" + attrName +
- "\"\nValue:\"" + attrValue + "\"");
- if (attrValue)
- inputs.push([attrName, attrValue]);
- });
- return inputs;
- }
-
- function err(aErr) {
- LOG("_parseAsSherlock::err: Sherlock param error:\n" + aErr);
- throw Cr.NS_ERROR_FAILURE;
- }
-
- // First try converting our byte array using the default Sherlock encoding.
- // If this fails, or if we find a sourceTextEncoding attribute, we need to
- // reconvert the byte array using the specified encoding.
- var sherlockLines, searchSection, sourceTextEncoding, browserSection;
- try {
- sherlockLines = sherlockBytesToLines(this._data);
- searchSection = getSection(sherlockLines, "search");
- browserSection = getSection(sherlockLines, "browser");
- sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]);
- if (sourceTextEncoding) {
- // Re-convert the bytes using the found sourceTextEncoding
- sherlockLines = sherlockBytesToLines(this._data, sourceTextEncoding);
- searchSection = getSection(sherlockLines, "search");
- browserSection = getSection(sherlockLines, "browser");
- }
- } catch (ex) {
- // The conversion using the default charset failed. Remove any non-ascii
- // bytes and try to find a sourceTextEncoding.
- var asciiBytes = this._data.filter(function (n) {return !(0x80 & n);});
- var asciiString = String.fromCharCode.apply(null, asciiBytes);
- sherlockLines = asciiString.split(NEW_LINES).filter(isUsefulLine);
- searchSection = getSection(sherlockLines, "search");
- sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]);
- if (sourceTextEncoding) {
- sherlockLines = sherlockBytesToLines(this._data, sourceTextEncoding);
- searchSection = getSection(sherlockLines, "search");
- browserSection = getSection(sherlockLines, "browser");
- } else
- ERROR("Couldn't find a working charset", Cr.NS_ERROR_FAILURE);
- }
-
- LOG("_parseAsSherlock: Search section:\n" + searchSection.toSource());
-
- this._name = searchSection["name"] || err("Missing name!");
- this._description = searchSection["description"] || "";
- this._queryCharset = searchSection["querycharset"] ||
- queryCharsetFromCode(searchSection["queryencoding"]);
- this._searchForm = searchSection["searchform"];
-
- this._updateInterval = parseInt(browserSection["updatecheckdays"]);
-
- this._updateURL = browserSection["update"];
- this._iconUpdateURL = browserSection["updateicon"];
-
- var method = (searchSection["method"] || "GET").toUpperCase();
- var template = searchSection["action"] || err("Missing action!");
-
- var inputs = getInputs(sherlockLines);
- LOG("_parseAsSherlock: Inputs:\n" + inputs.toSource());
-
- var url = null;
-
- if (method == "GET") {
- // Here's how we construct the input string:
- // <input> is first: Name Attr: Prefix Data Example:
- // YES EMPTY None <value> TEMPLATE<value>
- // YES NON-EMPTY ? <name>=<value> TEMPLATE?<name>=<value>
- // NO EMPTY ------------- <ignored> --------------
- // NO NON-EMPTY & <name>=<value> TEMPLATE?<n1>=<v1>&<n2>=<v2>
- for (var i = 0; i < inputs.length; i++) {
- var name = inputs[i][0];
- var value = inputs[i][1];
- if (i==0) {
- if (name == "")
- template += USER_DEFINED;
- else
- template += "?" + name + "=" + value;
- } else if (name != "")
- template += "&" + name + "=" + value;
- }
- url = new EngineURL("text/html", method, template);
-
- } else if (method == "POST") {
- // Create the URL object and just add the parameters directly
- url = new EngineURL("text/html", method, template);
- for (var i = 0; i < inputs.length; i++) {
- var name = inputs[i][0];
- var value = inputs[i][1];
- if (name)
- url.addParam(name, value);
- }
- } else
- err("Invalid method!");
-
- this._urls.push(url);
- },
-
- /**
- * Returns an XML document object containing the search plugin information,
- * which can later be used to reload the engine.
- */
- _serializeToElement: function SRCH_ENG_serializeToEl() {
- function appendTextNode(aNameSpace, aLocalName, aValue) {
- if (!aValue)
- return null;
- var node = doc.createElementNS(aNameSpace, aLocalName);
- node.appendChild(doc.createTextNode(aValue));
- docElem.appendChild(node);
- docElem.appendChild(doc.createTextNode("\n"));
- return node;
- }
-
- var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
- createInstance(Ci.nsIDOMParser);
-
- var doc = parser.parseFromString(EMPTY_DOC, "text/xml");
- docElem = doc.documentElement;
-
- docElem.appendChild(doc.createTextNode("\n"));
-
- appendTextNode(OPENSEARCH_NS_11, "ShortName", this.name);
- appendTextNode(OPENSEARCH_NS_11, "Description", this._description);
- appendTextNode(OPENSEARCH_NS_11, "InputEncoding", this._queryCharset);
-
- if (this._iconURI) {
- var imageNode = appendTextNode(OPENSEARCH_NS_11, "Image",
- this._iconURI.spec);
- if (imageNode) {
- imageNode.setAttribute("width", "16");
- imageNode.setAttribute("height", "16");
- }
- }
-
- appendTextNode(MOZSEARCH_NS_10, "Alias", this.alias);
- appendTextNode(MOZSEARCH_NS_10, "UpdateInterval", this._updateInterval);
- appendTextNode(MOZSEARCH_NS_10, "UpdateUrl", this._updateURL);
- appendTextNode(MOZSEARCH_NS_10, "IconUpdateUrl", this._iconUpdateURL);
- appendTextNode(MOZSEARCH_NS_10, "SearchForm", this._searchForm);
-
- for (var i = 0; i < this._urls.length; ++i)
- this._urls[i]._serializeToElement(doc, docElem);
- docElem.appendChild(doc.createTextNode("\n"));
-
- return doc;
- },
-
- _lazySerializeToFile: function SRCH_ENG_serializeToFile() {
- if (this._serializeTimer) {
- // Reset the timer
- this._serializeTimer.delay = LAZY_SERIALIZE_DELAY;
- } else {
- this._serializeTimer = Cc["@mozilla.org/timer;1"].
- createInstance(Ci.nsITimer);
- var timerCallback = {
- self: this,
- notify: function SRCH_ENG_notify(aTimer) {
- try {
- this.self._serializeToFile();
- } catch (ex) {
- LOG("Serialization from timer callback failed:\n" + ex);
- }
- this.self._serializeTimer = null;
- }
- };
- this._serializeTimer.initWithCallback(timerCallback,
- LAZY_SERIALIZE_DELAY,
- Ci.nsITimer.TYPE_ONE_SHOT);
- }
- },
-
- /**
- * Serializes the engine object to file.
- */
- _serializeToFile: function SRCH_ENG_serializeToFile() {
- var file = this._file;
- ENSURE_WARN(!this._readOnly, "Can't serialize a read only engine!",
- Cr.NS_ERROR_FAILURE);
- ENSURE_WARN(file && file.exists(), "Can't serialize: file doesn't exist!",
- Cr.NS_ERROR_UNEXPECTED);
-
- var fos = Cc["@mozilla.org/network/safe-file-output-stream;1"].
- createInstance(Ci.nsIFileOutputStream);
-
- // Serialize the engine first - we don't want to overwrite a good file
- // if this somehow fails.
- doc = this._serializeToElement();
-
- fos.init(file, (MODE_WRONLY | MODE_TRUNCATE), PERMS_FILE, 0);
-
- try {
- var serializer = Cc["@mozilla.org/xmlextras/xmlserializer;1"].
- createInstance(Ci.nsIDOMSerializer);
- serializer.serializeToStream(doc.documentElement, fos, null);
- } catch (e) {
- LOG("_serializeToFile: Error serializing engine:\n" + e);
- }
-
- closeSafeOutputStream(fos);
- },
-
- /**
- * Remove the engine's file from disk. The search service calls this once it
- * removes the engine from its internal store. This function will throw if
- * the file cannot be removed.
- */
- _remove: function SRCH_ENG_remove() {
- ENSURE(!this._readOnly, "Can't remove read only engine!",
- Cr.NS_ERROR_FAILURE);
- ENSURE(this._file && this._file.exists(),
- "Can't remove engine: file doesn't exist!",
- Cr.NS_ERROR_FILE_NOT_FOUND);
-
- this._file.remove(false);
- },
-
- // nsISearchEngine
- get alias() {
- if (this._alias === null)
- this._alias = engineMetadataService.getAttr(this, "alias");
-
- return this._alias;
- },
- set alias(val) {
- this._alias = val;
- engineMetadataService.setAttr(this, "alias", val);
- notifyAction(this, SEARCH_ENGINE_CHANGED);
- },
-
- get description() {
- return this._description;
- },
-
- get hidden() {
- if (this._hidden === null)
- this._hidden = engineMetadataService.getAttr(this, "hidden");
- return this._hidden;
- },
- set hidden(val) {
- var value = !!val;
- if (value != this._hidden) {
- this._hidden = value;
- engineMetadataService.setAttr(this, "hidden", value);
- notifyAction(this, SEARCH_ENGINE_CHANGED);
- }
- },
-
- get iconURI() {
- return this._iconURI;
- },
-
- get _iconURL() {
- if (!this._iconURI)
- return "";
- return this._iconURI.spec;
- },
-
- // Where the engine is being loaded from: will return the URI's spec if the
- // engine is being downloaded and does not yet have a file. This is only used
- // for logging.
- get _location() {
- if (this._file)
- return this._file.path;
-
- if (this._uri)
- return this._uri.spec;
-
- return "";
- },
-
- // The file that the plugin is loaded from is a unique identifier for it. We
- // use this as the identifier to store data in the sqlite database
- get _id() {
- ENSURE_WARN(this._file, "No _file for id!", Cr.NS_ERROR_FAILURE);
-
- if (this._isInProfile)
- return "[profile]/" + this._file.leafName;
-
- if (this._isInAppDir)
- return "[app]/" + this._file.leafName;
-
- // We're not in the profile or appdir, so this must be an extension-shipped
- // plugin. Use the full path.
- return this._file.path;
- },
-
- get _installLocation() {
- ENSURE_WARN(this._file && this._file.exists(),
- "_installLocation: engine has no file!",
- Cr.NS_ERROR_FAILURE);
-
- if (this.__installLocation === null) {
- if (this._file.parent.equals(getDir(NS_APP_SEARCH_DIR)))
- this.__installLocation = SEARCH_APP_DIR;
- else if (this._file.parent.equals(getDir(NS_APP_USER_SEARCH_DIR)))
- this.__installLocation = SEARCH_PROFILE_DIR;
- else
- this.__installLocation = SEARCH_IN_EXTENSION;
- }
-
- return this.__installLocation;
- },
-
- get _isInAppDir() {
- return this._installLocation == SEARCH_APP_DIR;
- },
- get _isInProfile() {
- return this._installLocation == SEARCH_PROFILE_DIR;
- },
-
- get _isDefault() {
- // For now, our concept of a "default engine" is "one that is not in the
- // user's profile directory", which is currently equivalent to "is app- or
- // extension-shipped".
- return !this._isInProfile;
- },
-
- get _hasUpdates() {
- // Whether or not the engine has an update URL
- return !!(this._updateURL || this._iconUpdateURL);
- },
-
- get name() {
- return this._name;
- },
-
- get type() {
- return this._type;
- },
-
- get tags() {
- return this._tags;
- },
-
- get searchForm() {
- if (!this._searchForm) {
- // No searchForm specified in the engine definition file, use the prePath
- // (e.g. https://foo.com for https://foo.com/search.php?q=bar).
- var htmlUrl = this._getURLOfType(URLTYPE_SEARCH_HTML);
- ENSURE_WARN(htmlUrl, "Engine has no HTML URL!", Cr.NS_ERROR_UNEXPECTED);
- this._searchForm = makeURI(htmlUrl.template).prePath;
- }
-
- return this._searchForm;
- },
-
- get queryCharset() {
- if (this._queryCharset)
- return this._queryCharset;
- return this._queryCharset = queryCharsetFromCode(/* get the default */);
- },
-
- // from nsISearchEngine
- addParam: function SRCH_ENG_addParam(aName, aValue, aResponseType) {
- ENSURE_ARG(aName && (aValue != null),
- "missing name or value for nsISearchEngine::addParam!");
- ENSURE_WARN(!this._readOnly,
- "called nsISearchEngine::addParam on a read-only engine!",
- Cr.NS_ERROR_FAILURE);
- if (!aResponseType)
- aResponseType = URLTYPE_SEARCH_HTML;
-
- var url = this._getURLOfType(aResponseType);
-
- ENSURE(url, "Engine object has no URL for response type " + aResponseType,
- Cr.NS_ERROR_FAILURE);
-
- url.addParam(aName, aValue);
-
- // Serialize the changes to file lazily
- this._lazySerializeToFile();
- },
-
- // from nsISearchEngine
- getSubmission: function SRCH_ENG_getSubmission(aData, aResponseType) {
- if (!aResponseType)
- aResponseType = URLTYPE_SEARCH_HTML;
-
- var url = this._getURLOfType(aResponseType);
-
- if (!url)
- return null;
-
- if (!aData) {
- // Return a dummy submission object with our searchForm attribute
- return new Submission(makeURI(this.searchForm), null);
- }
-
- LOG("getSubmission: In data: \"" + aData + "\"");
- var textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
- getService(Ci.nsITextToSubURI);
- var data = "";
- try {
- data = textToSubURI.ConvertAndEscape(this.queryCharset, aData);
- } catch (ex) {
- LOG("getSubmission: Falling back to default queryCharset!");
- data = textToSubURI.ConvertAndEscape(DEFAULT_QUERY_CHARSET, aData);
- }
- LOG("getSubmission: Out data: \"" + data + "\"");
- return url.getSubmission(data, this);
- },
-
- // from nsISearchEngine
- supportsResponseType: function SRCH_ENG_supportsResponseType(type) {
- return (this._getURLOfType(type) != null);
- },
-
- // nsISupports
- QueryInterface: function SRCH_ENG_QI(aIID) {
- if (aIID.equals(Ci.nsISearchEngine) ||
- aIID.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
- get wrappedJSObject() {
- return this;
- }
-
- };
-
- // nsISearchSubmission
- function Submission(aURI, aPostData) {
- this._uri = aURI;
- this._postData = aPostData;
- }
- Submission.prototype = {
- get uri() {
- return this._uri;
- },
- get postData() {
- return this._postData;
- },
- QueryInterface: function SRCH_SUBM_QI(aIID) {
- if (aIID.equals(Ci.nsISearchSubmission) ||
- aIID.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
-
- // nsIBrowserSearchService
- function SearchService() {
- this._init();
- }
- SearchService.prototype = {
- _engines: { },
- _sortedEngines: null,
- // Whether or not we need to write the order of engines on shutdown. This
- // needs to happen anytime _sortedEngines is modified after initial startup.
- _needToSetOrderPrefs: false,
-
- _init: function() {
- engineMetadataService.init();
- engineUpdateService.init();
-
- this._addObservers();
-
- var fileLocator = Cc["@mozilla.org/file/directory_service;1"].
- getService(Ci.nsIProperties);
- var locations = fileLocator.get(NS_APP_SEARCH_DIR_LIST,
- Ci.nsISimpleEnumerator);
-
- while (locations.hasMoreElements()) {
- var location = locations.getNext().QueryInterface(Ci.nsIFile);
- this._loadEngines(location);
- }
-
- // Now that all engines are loaded, build the sorted engine list
- this._buildSortedEngineList();
-
- selectedEngineName = getLocalizedPref(BROWSER_SEARCH_PREF +
- "selectedEngine");
- this._currentEngine = this.getEngineByName(selectedEngineName) ||
- this.defaultEngine;
- },
-
- _addEngineToStore: function SRCH_SVC_addEngineToStore(aEngine) {
- LOG("_addEngineToStore: Adding engine: \"" + aEngine.name + "\"");
-
- // Songbird HACK
- // For now any engines with special songbird tags should be hidden by default
- // This is to ensure that if an extension adds a programmatic search engine
- // the search engine will not show up after the extension is uninstalled
- if (aEngine.tags.indexOf("songbird") > -1) {
- aEngine.hidden = true;
- }
-
-
- // See if there is an existing engine with the same name. However, if this
- // engine is updating another engine, it's allowed to have the same name.
- var hasSameNameAsUpdate = (aEngine._engineToUpdate &&
- aEngine.name == aEngine._engineToUpdate.name);
- if (aEngine.name in this._engines && !hasSameNameAsUpdate) {
- LOG("_addEngineToStore: Duplicate engine found, aborting!");
- return;
- }
-
- if (aEngine._engineToUpdate) {
- // We need to replace engineToUpdate with the engine that just loaded.
- var oldEngine = aEngine._engineToUpdate;
-
- // Remove the old engine from the hash, since it's keyed by name, and our
- // name might change (the update might have a new name).
- delete this._engines[oldEngine.name];
-
- // Hack: we want to replace the old engine with the new one, but since
- // people may be holding refs to the nsISearchEngine objects themselves,
- // we'll just copy over all "private" properties (those without a getter
- // or setter) from one object to the other.
- for (var p in aEngine) {
- if (!(aEngine.__lookupGetter__(p) || aEngine.__lookupSetter__(p)))
- oldEngine[p] = aEngine[p];
- }
- aEngine = oldEngine;
- aEngine._engineToUpdate = null;
-
- // Add the engine back
- this._engines[aEngine.name] = aEngine;
- notifyAction(aEngine, SEARCH_ENGINE_CHANGED);
- } else {
- // Not an update, just add the new engine.
- this._engines[aEngine.name] = aEngine;
- // Only add the engine to the list of sorted engines if the initial list
- // has already been built (i.e. if this._sortedEngines is non-null). If
- // it hasn't, we're still loading engines from disk, and will build the
- // sorted engine list when that initial loading is done.
- if (this._sortedEngines) {
- this._sortedEngines.push(aEngine);
- this._needToSetOrderPrefs = true;
- }
- notifyAction(aEngine, SEARCH_ENGINE_ADDED);
- }
-
- if (aEngine._hasUpdates) {
- // Schedule the engine's next update, if it isn't already.
- if (!engineMetadataService.getAttr(aEngine, "updateexpir"))
- engineUpdateService.scheduleNextUpdate(aEngine);
-
- // We need to save the engine's _dataType, if this is the first time the
- // engine is added to the dataStore, since ._dataType isn't persisted
- // and will change on the next startup (since the engine will then be
- // XML). We need this so that we know how to load any future updates from
- // this engine.
- if (!engineMetadataService.getAttr(aEngine, "updatedatatype"))
- engineMetadataService.setAttr(aEngine, "updatedatatype",
- aEngine._dataType);
- }
- },
-
- _loadEngines: function SRCH_SVC_loadEngines(aDir) {
- LOG("_loadEngines: Searching in " + aDir.path + " for search engines.");
-
- // Check whether aDir is the user profile dir
- var isInProfile = aDir.equals(getDir(NS_APP_USER_SEARCH_DIR));
-
- var files = aDir.directoryEntries
- .QueryInterface(Ci.nsIDirectoryEnumerator);
- var ios = Cc["@mozilla.org/network/io-service;1"].
- getService(Ci.nsIIOService);
-
- while (files.hasMoreElements()) {
- var file = files.nextFile;
-
- // Ignore hidden and empty files, and directories
- if (!file.isFile() || file.fileSize == 0 || file.isHidden())
- continue;
-
- var fileURL = ios.newFileURI(file).QueryInterface(Ci.nsIURL);
- var fileExtension = fileURL.fileExtension.toLowerCase();
- var isWritable = isInProfile && file.isWritable();
-
- var dataType;
- switch (fileExtension) {
- case XML_FILE_EXT:
- dataType = SEARCH_DATA_XML;
- break;
- case SHERLOCK_FILE_EXT:
- dataType = SEARCH_DATA_TEXT;
- break;
- default:
- // Not an engine
- continue;
- }
-
- var addedEngine = null;
- try {
- addedEngine = new Engine(file, dataType, !isWritable);
- addedEngine._initFromFile();
- } catch (ex) {
- LOG("_loadEngines: Failed to load " + file.path + "!\n" + ex);
- continue;
- }
-
- if (fileExtension == SHERLOCK_FILE_EXT) {
- if (isWritable) {
- try {
- this._convertSherlockFile(addedEngine, fileURL.fileBaseName);
- } catch (ex) {
- LOG("_loadEngines: Failed to convert: " + fileURL.path + "\n" + ex);
- // The engine couldn't be converted, mark it as read-only
- addedEngine._readOnly = true;
- }
- }
-
- // If the engine still doesn't have an icon, see if we can find one
- if (!addedEngine._iconURI) {
- var icon = this._findSherlockIcon(file, fileURL.fileBaseName);
- if (icon)
- addedEngine._iconURI = ios.newFileURI(icon);
- }
- }
-
- this._addEngineToStore(addedEngine);
- }
- },
-
- _saveSortedEngineList: function SRCH_SVC_saveSortedEngineList() {
- // We only need to write the prefs. if something has changed.
- if (!this._needToSetOrderPrefs)
- return;
-
- // Set the useDB pref to indicate that from now on we should use the order
- // information stored in the database.
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- prefB.setBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", true);
-
- var engines = this._getSortedEngines(true);
- var values = [];
- var names = [];
-
- for (var i = 0; i < engines.length; ++i) {
- names[i] = "order";
- values[i] = i + 1;
- }
-
- engineMetadataService.setAttrs(engines, names, values);
- },
-
- _buildSortedEngineList: function SRCH_SVC_buildSortedEngineList() {
- var addedEngines = { };
- this._sortedEngines = [];
- var engine;
-
- // If the user has specified a custom engine order, read the order
- // information from the engineMetadataService instead of the default
- // prefs.
- if (getBoolPref(BROWSER_SEARCH_PREF + "useDBForOrder", false)) {
- for each (engine in this._engines) {
- var orderNumber = engineMetadataService.getAttr(engine, "order");
-
- // Since the DB isn't regularly cleared, and engine files may disappear
- // without us knowing, we may already have an engine in this slot. If
- // that happens, we just skip it - it will be added later on as an
- // unsorted engine. This problem will sort itself out when we call
- // _saveSortedEngineList at shutdown.
- if (orderNumber && !this._sortedEngines[orderNumber-1]) {
- this._sortedEngines[orderNumber-1] = engine;
- addedEngines[engine.name] = engine;
- } else {
- // We need to call _saveSortedEngines so this gets sorted out.
- this._needToSetOrderPrefs = true;
- }
- }
-
- // Filter out any nulls for engines that may have been removed
- var filteredEngines = this._sortedEngines.filter(function(a) { return !!a; });
- if (this._sortedEngines.length != filteredEngines.length)
- this._needToSetOrderPrefs = true;
- this._sortedEngines = filteredEngines;
-
- } else {
- // The DB isn't being used, so just read the engine order from the prefs
- var i = 0;
- var engineName;
- var prefName;
-
- try {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- var extras =
- prefB.getChildList(BROWSER_SEARCH_PREF + "order.extra.", { });
-
- for each (prefName in extras) {
- engineName = prefB.getCharPref(prefName);
-
- engine = this._engines[engineName];
- if (!engine || engine.name in addedEngines)
- continue;
-
- this._sortedEngines.push(engine);
- addedEngines[engine.name] = engine;
- }
- }
- catch (e) { }
-
- while (true) {
- engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + (++i));
- if (!engineName)
- break;
-
- engine = this._engines[engineName];
- if (!engine || engine.name in addedEngines)
- continue;
-
- this._sortedEngines.push(engine);
- addedEngines[engine.name] = engine;
- }
- }
-
- // Array for the remaining engines, alphabetically sorted
- var alphaEngines = [];
-
- for each (engine in this._engines) {
- if (!(engine.name in addedEngines))
- alphaEngines.push(this._engines[engine.name]);
- }
- alphaEngines = alphaEngines.sort(function (a, b) {
- return a.name.localeCompare(b.name);
- });
- this._sortedEngines = this._sortedEngines.concat(alphaEngines);
- },
-
- /**
- * Converts a Sherlock file and its icon into the custom XML format used by
- * the Search Service. Saves the engine's icon (if present) into the XML as a
- * data: URI and changes the extension of the source file from ".src" to
- * ".xml". The engine data is then written to the file as XML.
- * @param aEngine
- * The Engine object that needs to be converted.
- * @param aBaseName
- * The basename of the Sherlock file.
- * Example: "foo" for file "foo.src".
- *
- * @throws NS_ERROR_FAILURE if the file could not be converted.
- *
- * @see nsIURL::fileBaseName
- */
- _convertSherlockFile: function SRCH_SVC_convertSherlock(aEngine, aBaseName) {
- var oldSherlockFile = aEngine._file;
-
- // Back up the old file
- try {
- var backupDir = oldSherlockFile.parent;
- backupDir.append("searchplugins-backup");
-
- if (!backupDir.exists())
- backupDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
-
- oldSherlockFile.copyTo(backupDir, null);
- } catch (ex) {
- // Just bail. Engines that can't be backed up won't be converted, but
- // engines that aren't converted are loaded as readonly.
- LOG("_convertSherlockFile: Couldn't back up " + oldSherlockFile.path +
- ":\n" + ex);
- throw Cr.NS_ERROR_FAILURE;
- }
-
- // Rename the file, but don't clobber existing files
- var newXMLFile = oldSherlockFile.parent.clone();
- newXMLFile.append(aBaseName + "." + XML_FILE_EXT);
-
- if (newXMLFile.exists()) {
- // There is an existing file with this name, create a unique file
- newXMLFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
- }
-
- // Rename the .src file to .xml
- oldSherlockFile.moveTo(null, newXMLFile.leafName);
-
- aEngine._file = newXMLFile;
-
- // Write the converted engine to disk
- aEngine._serializeToFile();
-
- // Update the engine's _type.
- aEngine._type = SEARCH_TYPE_MOZSEARCH;
-
- // See if it has a corresponding icon
- try {
- var icon = this._findSherlockIcon(aEngine._file, aBaseName);
- if (icon && icon.fileSize < MAX_ICON_SIZE) {
- // Use this as the engine's icon
- var bStream = Cc["@mozilla.org/binaryinputstream;1"].
- createInstance(Ci.nsIBinaryInputStream);
- var fileInStream = Cc["@mozilla.org/network/file-input-stream;1"].
- createInstance(Ci.nsIFileInputStream);
-
- fileInStream.init(icon, MODE_RDONLY, PERMS_FILE, 0);
- bStream.setInputStream(fileInStream);
-
- var bytes = [];
- while (bStream.available() != 0)
- bytes = bytes.concat(bStream.readByteArray(bStream.available()));
- bStream.close();
-
- // Convert the byte array to a base64-encoded string
- var str = b64(bytes);
-
- aEngine._iconURI = makeURI(ICON_DATAURL_PREFIX + str);
- LOG("_importSherlockEngine: Set sherlock iconURI to: \"" +
- aEngine._iconURL + "\"");
-
- // Write the engine to disk to save changes
- aEngine._serializeToFile();
-
- // Delete the icon now that we're sure everything's been saved
- icon.remove(false);
- }
- } catch (ex) { LOG("_convertSherlockFile: Error setting icon:\n" + ex); }
- },
-
- /**
- * Finds an icon associated to a given Sherlock file. Searches the provided
- * file's parent directory looking for files with the same base name and one
- * of the file extensions in SHERLOCK_ICON_EXTENSIONS.
- * @param aEngineFile
- * The Sherlock plugin file.
- * @param aBaseName
- * The basename of the Sherlock file.
- * Example: "foo" for file "foo.src".
- * @see nsIURL::fileBaseName
- */
- _findSherlockIcon: function SRCH_SVC_findSherlock(aEngineFile, aBaseName) {
- for (var i = 0; i < SHERLOCK_ICON_EXTENSIONS.length; i++) {
- var icon = aEngineFile.parent.clone();
- icon.append(aBaseName + SHERLOCK_ICON_EXTENSIONS[i]);
- if (icon.exists() && icon.isFile())
- return icon;
- }
- return null;
- },
-
- /**
- * Get a sorted array of engines.
- * @param aWithHidden
- * True if hidden plugins should be included in the result.
- */
- _getSortedEngines: function SRCH_SVC_getSorted(aWithHidden) {
- if (aWithHidden)
- return this._sortedEngines;
-
- return this._sortedEngines.filter(function (engine) {
- return !engine.hidden;
- });
- },
-
- // nsIBrowserSearchService
- getEngines: function SRCH_SVC_getEngines(aCount) {
- LOG("getEngines: getting all engines");
- var engines = this._getSortedEngines(true);
- aCount.value = engines.length;
- return engines;
- },
-
- getVisibleEngines: function SRCH_SVC_getVisible(aCount) {
- LOG("getVisibleEngines: getting all visible engines");
- var engines = this._getSortedEngines(false);
- aCount.value = engines.length;
- return engines;
- },
-
- getDefaultEngines: function SRCH_SVC_getDefault(aCount) {
- function isDefault(engine) {
- return engine._isDefault;
- };
- var engines = this._sortedEngines.filter(isDefault);
- var engineOrder = {};
- var engineName;
- var i = 1;
-
- // Build a list of engines which we have ordering information for.
- // We're rebuilding the list here because _sortedEngines contain the
- // current order, but we want the original order.
-
- // First, look at the "browser.search.order.extra" branch.
- try {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- var extras = prefB.getChildList(BROWSER_SEARCH_PREF + "order.extra.",
- {});
-
- for each (var prefName in extras) {
- engineName = prefB.getCharPref(prefName);
-
- if (!(engineName in engineOrder))
- engineOrder[engineName] = i++;
- }
- } catch (e) {
- LOG("Getting extra order prefs failed: " + e);
- }
-
- // Now look through the "browser.search.order" branch.
- for (var j = 1; ; j++) {
- engineName = getLocalizedPref(BROWSER_SEARCH_PREF + "order." + j);
- if (!engineName)
- break;
-
- if (!(engineName in engineOrder))
- engineOrder[engineName] = i++;
- }
-
- LOG("getDefaultEngines: engineOrder: " + engineOrder.toSource());
-
- function compareEngines (a, b) {
- var aIdx = engineOrder[a.name];
- var bIdx = engineOrder[b.name];
-
- if (aIdx && bIdx)
- return aIdx - bIdx;
- if (aIdx)
- return -1;
- if (bIdx)
- return 1;
-
- return a.name.localeCompare(b.name);
- }
- engines.sort(compareEngines);
-
- aCount.value = engines.length;
- return engines;
- },
-
- getEngineByName: function SRCH_SVC_getEngineByName(aEngineName) {
- return this._engines[aEngineName] || null;
- },
-
- getEngineByAlias: function SRCH_SVC_getEngineByAlias(aAlias) {
- for (var engineName in this._engines) {
- var engine = this._engines[engineName];
- if (engine && engine.alias == aAlias)
- return engine;
- }
- return null;
- },
-
- addEngineWithDetails: function SRCH_SVC_addEWD(aName, aIconURL, aAlias,
- aDescription, aMethod,
- aTemplate) {
- ENSURE_ARG(aName, "Invalid name passed to addEngineWithDetails!");
- ENSURE_ARG(aMethod, "Invalid method passed to addEngineWithDetails!");
- ENSURE_ARG(aTemplate, "Invalid template passed to addEngineWithDetails!");
-
- ENSURE(!this._engines[aName], "An engine with that name already exists!",
- Cr.NS_ERROR_FILE_ALREADY_EXISTS);
-
- var engine = new Engine(getSanitizedFile(aName), SEARCH_DATA_XML, false);
- engine._initFromMetadata(aName, aIconURL, aAlias, aDescription,
- aMethod, aTemplate);
- this._addEngineToStore(engine);
- },
-
- addEngine: function SRCH_SVC_addEngine(aEngineURL, aDataType, aIconURL,
- aConfirm) {
- LOG("addEngine: Adding \"" + aEngineURL + "\".");
- try {
- var uri = makeURI(aEngineURL);
- var engine = new Engine(uri, aDataType, false);
- engine._initFromURI();
- } catch (ex) {
- LOG("addEngine: Error adding engine:\n" + ex);
- throw Cr.NS_ERROR_FAILURE;
- }
- engine._setIcon(aIconURL, false);
- engine._confirm = aConfirm;
- },
-
- removeEngine: function SRCH_SVC_removeEngine(aEngine) {
- ENSURE_ARG(aEngine, "no engine passed to removeEngine!");
-
- var engineToRemove = null;
- for (var e in this._engines)
- if (aEngine.wrappedJSObject == this._engines[e])
- engineToRemove = this._engines[e];
-
- ENSURE(engineToRemove, "removeEngine: Can't find engine to remove!",
- Cr.NS_ERROR_FILE_NOT_FOUND);
-
- if (engineToRemove == this.currentEngine)
- this._currentEngine = null;
-
- if (engineToRemove._readOnly) {
- // Just hide it (the "hidden" setter will notify) and remove its alias to
- // avoid future conflicts with other engines.
- engineToRemove.hidden = true;
- engineToRemove.alias = null;
- } else {
- // Remove the engine file from disk (this might throw)
- engineToRemove._remove();
- engineToRemove._file = null;
-
- // Remove the engine from _sortedEngines
- var index = this._sortedEngines.indexOf(engineToRemove);
- ENSURE(index != -1, "Can't find engine to remove in _sortedEngines!",
- Cr.NS_ERROR_FAILURE);
- this._sortedEngines.splice(index, 1);
-
- // Remove the engine from the internal store
- delete this._engines[engineToRemove.name];
-
- notifyAction(engineToRemove, SEARCH_ENGINE_REMOVED);
-
- // Since we removed an engine, we need to update the preferences.
- this._needToSetOrderPrefs = true;
- }
- },
-
- moveEngine: function SRCH_SVC_moveEngine(aEngine, aNewIndex) {
- ENSURE_ARG((aNewIndex < this._sortedEngines.length) && (aNewIndex >= 0),
- "SRCH_SVC_moveEngine: Index out of bounds!");
- ENSURE_ARG(aEngine instanceof Ci.nsISearchEngine,
- "SRCH_SVC_moveEngine: Invalid engine passed to moveEngine!");
- ENSURE(!aEngine.hidden, "moveEngine: Can't move a hidden engine!",
- Cr.NS_ERROR_FAILURE);
-
- var engine = aEngine.wrappedJSObject;
-
- var currentIndex = this._sortedEngines.indexOf(engine);
- ENSURE(currentIndex != -1, "moveEngine: Can't find engine to move!",
- Cr.NS_ERROR_UNEXPECTED);
-
- // Our callers only take into account non-hidden engines when calculating
- // aNewIndex, but we need to move it in the array of all engines, so we
- // need to adjust aNewIndex accordingly. To do this, we count the number
- // of hidden engines in the list before the engine that we're taking the
- // place of. We do this by first finding newIndexEngine (the engine that
- // we were supposed to replace) and then iterating through the complete
- // engine list until we reach it, increasing aNewIndex for each hidden
- // engine we find on our way there.
- //
- // This could be further simplified by having our caller pass in
- // newIndexEngine directly instead of aNewIndex.
- var newIndexEngine = this._getSortedEngines(false)[aNewIndex];
- ENSURE(newIndexEngine, "moveEngine: Can't find engine to replace!",
- Cr.NS_ERROR_UNEXPECTED);
-
- for (var i = 0; i < this._sortedEngines.length; ++i) {
- if (newIndexEngine == this._sortedEngines[i])
- break;
- if (this._sortedEngines[i].hidden)
- aNewIndex++;
- }
-
- if (currentIndex == aNewIndex)
- return; // nothing to do!
-
- // Move the engine
- var movedEngine = this._sortedEngines.splice(currentIndex, 1)[0];
- this._sortedEngines.splice(aNewIndex, 0, movedEngine);
-
- notifyAction(engine, SEARCH_ENGINE_CHANGED);
-
- // Since we moved an engine, we need to update the preferences.
- this._needToSetOrderPrefs = true;
- },
-
- restoreDefaultEngines: function SRCH_SVC_resetDefaultEngines() {
- for each (var e in this._engines) {
- // Unhide all default engines
- if (e.hidden && e._isDefault)
- e.hidden = false;
- }
- },
-
- get defaultEngine() {
- const defPref = BROWSER_SEARCH_PREF + "defaultenginename";
- // Get the default engine - this pref should always exist, but the engine
- // might be hidden
- this._defaultEngine = this.getEngineByName(getLocalizedPref(defPref, ""));
- if (!this._defaultEngine || this._defaultEngine.hidden)
- this._defaultEngine = this._getSortedEngines(false)[0] || null;
- return this._defaultEngine;
- },
-
- get currentEngine() {
- if (!this._currentEngine || this._currentEngine.hidden)
- this._currentEngine = this.defaultEngine;
- return this._currentEngine;
- },
- set currentEngine(val) {
- ENSURE_ARG(val instanceof Ci.nsISearchEngine,
- "Invalid argument passed to currentEngine setter");
-
- var newCurrentEngine = this.getEngineByName(val.name);
- ENSURE(newCurrentEngine, "Can't find engine in store!",
- Cr.NS_ERROR_UNEXPECTED);
-
- this._currentEngine = newCurrentEngine;
-
- var currentEnginePref = BROWSER_SEARCH_PREF + "selectedEngine";
-
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefService).QueryInterface(Ci.nsIPrefBranch);
-
- if (this._currentEngine == this.defaultEngine) {
- if (prefB.prefHasUserValue(currentEnginePref))
- prefB.clearUserPref(currentEnginePref);
- }
- else {
- setLocalizedPref(currentEnginePref, this._currentEngine.name);
- }
-
- notifyAction(this._currentEngine, SEARCH_ENGINE_CURRENT);
- },
-
- // nsIObserver
- observe: function SRCH_SVC_observe(aEngine, aTopic, aVerb) {
- switch (aTopic) {
- case SEARCH_ENGINE_TOPIC:
- if (aVerb == SEARCH_ENGINE_LOADED) {
- var engine = aEngine.QueryInterface(Ci.nsISearchEngine);
- LOG("nsSearchService::observe: Done installation of " + engine.name
- + ".");
- this._addEngineToStore(engine.wrappedJSObject);
- if (engine.wrappedJSObject._useNow) {
- LOG("nsSearchService::observe: setting current");
- this.currentEngine = aEngine;
- }
- }
- break;
- case QUIT_APPLICATION_TOPIC:
- this._removeObservers();
- this._saveSortedEngineList();
- break;
- }
- },
-
- _addObservers: function SRCH_SVC_addObservers() {
- var os = Cc["@mozilla.org/observer-service;1"].
- getService(Ci.nsIObserverService);
- os.addObserver(this, SEARCH_ENGINE_TOPIC, false);
- os.addObserver(this, QUIT_APPLICATION_TOPIC, false);
- },
-
- _removeObservers: function SRCH_SVC_removeObservers() {
- var os = Cc["@mozilla.org/observer-service;1"].
- getService(Ci.nsIObserverService);
- os.removeObserver(this, SEARCH_ENGINE_TOPIC);
- os.removeObserver(this, QUIT_APPLICATION_TOPIC);
- },
-
- QueryInterface: function SRCH_SVC_QI(aIID) {
- if (aIID.equals(Ci.nsIBrowserSearchService) ||
- aIID.equals(Ci.nsIObserver) ||
- aIID.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- };
-
- var engineMetadataService = {
- init: function epsInit() {
- var engineDataTable = "id INTEGER PRIMARY KEY, engineid STRING, name STRING, value STRING";
- var file = getDir(NS_APP_USER_PROFILE_50_DIR);
- file.append("search.sqlite");
- var dbService = Cc["@mozilla.org/storage/service;1"].
- getService(Ci.mozIStorageService);
- try {
- this.mDB = dbService.openDatabase(file);
- } catch (ex) {
- if (ex.result == 0x8052000b) { /* NS_ERROR_FILE_CORRUPTED */
- // delete and try again
- file.remove(false);
- this.mDB = dbService.openDatabase(file);
- } else {
- throw ex;
- }
- }
-
- try {
- this.mDB.createTable("engine_data", engineDataTable);
- } catch (ex) {
- // Fails if the table already exists, which is fine
- }
-
- this.mGetData = createStatement (
- this.mDB,
- "SELECT value FROM engine_data WHERE engineid = :engineid AND name = :name");
- this.mDeleteData = createStatement (
- this.mDB,
- "DELETE FROM engine_data WHERE engineid = :engineid AND name = :name");
- this.mInsertData = createStatement (
- this.mDB,
- "INSERT INTO engine_data (engineid, name, value) " +
- "VALUES (:engineid, :name, :value)");
- },
- getAttr: function epsGetAttr(engine, name) {
- // attr names must be lower case
- name = name.toLowerCase();
-
- var stmt = this.mGetData;
- stmt.reset();
- var pp = stmt.params;
- pp.engineid = engine._id;
- pp.name = name;
-
- var value = null;
- if (stmt.step())
- value = stmt.row.value;
- stmt.reset();
- return value;
- },
-
- setAttr: function epsSetAttr(engine, name, value) {
- // attr names must be lower case
- name = name.toLowerCase();
-
- this.mDB.beginTransaction();
-
- var pp = this.mDeleteData.params;
- pp.engineid = engine._id;
- pp.name = name;
- this.mDeleteData.step();
- this.mDeleteData.reset();
-
- pp = this.mInsertData.params;
- pp.engineid = engine._id;
- pp.name = name;
- pp.value = value;
- this.mInsertData.step();
- this.mInsertData.reset();
-
- this.mDB.commitTransaction();
- },
-
- setAttrs: function epsSetAttrs(engines, names, values) {
- this.mDB.beginTransaction();
-
- for (var i = 0; i < engines.length; i++) {
- // attr names must be lower case
- var name = names[i].toLowerCase();
-
- var pp = this.mDeleteData.params;
- pp.engineid = engines[i]._id;
- pp.name = names[i];
- this.mDeleteData.step();
- this.mDeleteData.reset();
-
- pp = this.mInsertData.params;
- pp.engineid = engines[i]._id;
- pp.name = names[i];
- pp.value = values[i];
- this.mInsertData.step();
- this.mInsertData.reset();
- }
-
- this.mDB.commitTransaction();
- },
-
- deleteEngineData: function epsDelData(engine, name) {
- // attr names must be lower case
- name = name.toLowerCase();
-
- var pp = this.mDeleteData.params;
- pp.engineid = engine._id;
- pp.name = name;
- this.mDeleteData.step();
- this.mDeleteData.reset();
- }
- }
-
- const SEARCH_UPDATE_LOG_PREFIX = "*** Search update: ";
-
- /**
- * Outputs aText to the JavaScript console as well as to stdout, if the search
- * logging pref (browser.search.update.log) is set to true.
- */
- function ULOG(aText) {
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- var shouldLog = false;
- try {
- shouldLog = prefB.getBoolPref(BROWSER_SEARCH_PREF + "update.log");
- } catch (ex) {}
-
- if (shouldLog) {
- dump(SEARCH_UPDATE_LOG_PREFIX + aText + "\n");
- var consoleService = Cc["@mozilla.org/consoleservice;1"].
- getService(Ci.nsIConsoleService);
- consoleService.logStringMessage(aText);
- }
- }
-
- var engineUpdateService = {
- init: function eus_init() {
- var tm = Cc["@mozilla.org/updates/timer-manager;1"].
- getService(Ci.nsIUpdateTimerManager);
- // figure out how often to check for any expired engines
- var prefB = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- var interval = prefB.getIntPref(BROWSER_SEARCH_PREF + "updateinterval");
-
- // Interval is stored in hours
- var seconds = interval * 3600;
- tm.registerTimer("search-engine-update-timer", engineUpdateService,
- seconds);
- },
-
- scheduleNextUpdate: function eus_scheduleNextUpdate(aEngine) {
- var interval = aEngine._updateInterval || SEARCH_DEFAULT_UPDATE_INTERVAL;
- var milliseconds = interval * 86400000; // |interval| is in days
- engineMetadataService.setAttr(aEngine, "updateexpir",
- Date.now() + milliseconds);
- },
-
- notify: function eus_Notify(aTimer) {
- ULOG("notify called");
-
- if (!getBoolPref(BROWSER_SEARCH_PREF + "update", true))
- return;
-
- // Our timer has expired, but unfortunately, we can't get any data from it.
- // Therefore, we need to walk our engine-list, looking for expired engines
- var searchService = Cc["@mozilla.org/browser/search-service;1"].
- getService(Ci.nsIBrowserSearchService);
- var currentTime = Date.now();
- ULOG("currentTime: " + currentTime);
- for each (engine in searchService.getEngines({})) {
- engine = engine.wrappedJSObject;
- if (!engine._hasUpdates || engine._readOnly)
- continue;
-
- ULOG("checking " + engine.name);
-
- var expirTime = engineMetadataService.getAttr(engine, "updateexpir");
- var updateURL = engine._updateURL;
- var iconUpdateURL = engine._iconUpdateURL;
- ULOG("expirTime: " + expirTime + "\nupdateURL: " + updateURL +
- "\niconUpdateURL: " + iconUpdateURL);
-
- var engineExpired = expirTime <= currentTime;
-
- if (!expirTime || !engineExpired) {
- ULOG("skipping engine");
- continue;
- }
-
- ULOG(engine.name + " has expired");
-
- var testEngine = null;
-
- var updateURI = makeURI(updateURL);
- if (updateURI) {
- var dataType = engineMetadataService.getAttr(engine, "updatedatatype")
- if (!dataType) {
- ULOG("No loadtype to update engine!");
- continue;
- }
-
- testEngine = new Engine(updateURI, dataType, false);
- testEngine._engineToUpdate = engine;
- testEngine._initFromURI();
- } else
- ULOG("invalid updateURI");
-
- if (iconUpdateURL) {
- // If we're updating the engine too, use the new engine object,
- // otherwise use the existing engine object.
- (testEngine || engine)._setIcon(iconUpdateURL, true);
- }
-
- // Schedule the next update
- this.scheduleNextUpdate(engine);
-
- } // end engine iteration
- }
- };
-
- const kClassID = Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}");
- const kClassName = "Browser Search Service";
- const kContractID = "@mozilla.org/browser/search-service;1";
-
- // nsIFactory
- const kFactory = {
- createInstance: function (outer, iid) {
- if (outer != null)
- throw Cr.NS_ERROR_NO_AGGREGATION;
- return (new SearchService()).QueryInterface(iid);
- }
- };
-
- // nsIModule
- const gModule = {
- registerSelf: function (componentManager, fileSpec, location, type) {
- componentManager.QueryInterface(Ci.nsIComponentRegistrar);
- componentManager.registerFactoryLocation(kClassID,
- kClassName,
- kContractID,
- fileSpec, location, type);
- },
-
- unregisterSelf: function(componentManager, fileSpec, location) {
- componentManager.QueryInterface(Ci.nsIComponentRegistrar);
- componentManager.unregisterFactoryLocation(kClassID, fileSpec);
- },
-
- getClassObject: function (componentManager, cid, iid) {
- if (!cid.equals(kClassID))
- throw Cr.NS_ERROR_NO_INTERFACE;
- if (!iid.equals(Ci.nsIFactory))
- throw Cr.NS_ERROR_NOT_IMPLEMENTED;
- return kFactory;
- },
-
- canUnload: function (componentManager) {
- return true;
- }
- };
-
- function NSGetModule(componentManager, fileSpec) {
- return gModule;
- }
-
- /*
- #include ../../../toolkit/content/debug.js
- */
-
- // Direct copy of debug.js, as apparently we aren't allowed
- // to use the preprocessor on JS components.
-
- var gTraceOnAssert = true;
-
- /**
- * This function provides a simple assertion function for JavaScript.
- * If the condition is true, this function will do nothing. If the
- * condition is false, then the message will be printed to the console
- * and an alert will appear showing a stack trace, so that the (alpha
- * or nightly) user can file a bug containing it. For future enhancements,
- * see bugs 330077 and 330078.
- *
- * To suppress the dialogs, you can run with the environment variable
- * XUL_ASSERT_PROMPT set to 0 (if unset, this defaults to 1).
- *
- * @param condition represents the condition that we're asserting to be
- * true when we call this function--should be
- * something that can be evaluated as a boolean.
- * @param message a string to be displayed upon failure of the assertion
- */
-
- function NS_ASSERT(condition, message) {
- if (condition)
- return;
-
- var caller = arguments.callee.caller;
- var assertionText = "ASSERT: " + message + "\n";
- dump(assertionText);
-
- var stackText = "";
- if (gTraceOnAssert) {
- stackText = "Stack Trace: \n";
- var count = 0;
- while (caller) {
- stackText += count++ + ":" + caller.name + "(";
- for (var i = 0; i < caller.arguments.length; ++i) {
- var arg = caller.arguments[i];
- stackText += arg;
- if (i < caller.arguments.length - 1)
- stackText += ",";
- }
- stackText += ")\n";
- caller = caller.arguments.callee.caller;
- }
- }
-
- var environment = Components.classes["@mozilla.org/process/environment;1"].
- getService(Components.interfaces.nsIEnvironment);
- if (environment.exists("XUL_ASSERT_PROMPT") &&
- !parseInt(environment.get("XUL_ASSERT_PROMPT")))
- return;
-
- var source = null;
- if (this.window)
- source = window;
- var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].
- getService(Components.interfaces.nsIPromptService);
- ps.alert(source, "Assertion Failed", assertionText + stackText);
- }
-