# ***** 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 (Original author) # Gavin Sharp # Joe Hughes # Pamela Greene # Michael Kaply # # 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 ***** # This code is a subset of nsSearchService.js from the current trunk # It is used to retrieve the name from a Sherlock SRC file const PERMS_FILE = 0644; const MODE_RDONLY = 0x01; const SEARCH_DATA_XML = 1; const SEARCH_DATA_TEXT = 2; const SEARCH_TYPE_MOZSEARCH = 1; const SEARCH_TYPE_SHERLOCK = 2; const SEARCH_TYPE_OPENSEARCH = 3; const NEW_LINES = /(\r\n|\r|\n)/; 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"; // Returns false for whitespace-only or commented out lines in a // Sherlock file, true otherwise. function isUsefulLine(aLine) { return !(/^\s*($|#)/i.test(aLine)); } function getSearchEngineName(searchenginepath) { var data; var type; var dataType = SEARCH_DATA_TEXT; var name; var ext = searchenginepath.substring(searchenginepath.lastIndexOf('.') + 1,searchenginepath.length); if (ext.toLowerCase() == "xml") { dataType = SEARCH_DATA_XML; } var sourcefile = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); sourcefile.initWithPath(searchenginepath); var fileInStream = Components.classes["@mozilla.org/network/file-input-stream;1"] .createInstance(Components.interfaces.nsIFileInputStream); fileInStream.init(sourcefile, MODE_RDONLY, PERMS_FILE, false); switch (dataType) { case SEARCH_DATA_XML: var domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"] .createInstance(Components.interfaces.nsIDOMParser); var doc = domParser.parseFromStream(fileInStream, "UTF-8", sourcefile.fileSize, "text/xml"); data = doc.documentElement; if (checkNameSpace(data, [MOZSEARCH_LOCALNAME], [MOZSEARCH_NS_10])) { type = SEARCH_TYPE_MOZSEARCH; name = parseAsOpenSearch(data); } else if (checkNameSpace(data, [OPENSEARCH_LOCALNAME], OPENSEARCH_NAMESPACES)) { type = SEARCH_TYPE_OPENSEARCH; name = parseAsOpenSearch(data); } break; case SEARCH_DATA_TEXT: var binaryInStream = Components.classes["@mozilla.org/binaryinputstream;1"] .createInstance(Components.interfaces.nsIBinaryInputStream); binaryInStream.setInputStream(fileInStream); data = binaryInStream.readByteArray(binaryInStream.available()); type = SEARCH_TYPE_SHERLOCK; name = parseAsSherlock(data); break; } fileInStream.close(); return name; } 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 = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"] .createInstance(Components.interfaces.nsIScriptableUnicodeConverter); 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); // Split the string into lines, and filter out comments and // whitespace-only lines return dataString.split(NEW_LINES).filter(isUsefulLine); } /** * Extract search engine information from the collected data to initialize * the engine object. */ function parseAsSherlock(data) { /** * 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) { 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++; } } } lines = lines.splice(startLine, numberOfLines); 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; } // 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; try { sherlockLines = sherlockBytesToLines(data); searchSection = getSection(sherlockLines, "search"); sourceTextEncoding = parseInt(searchSection["sourcetextencoding"]); if (sourceTextEncoding) { // Re-convert the bytes using the found sourceTextEncoding sherlockLines = sherlockBytesToLines(data, sourceTextEncoding); searchSection = getSection(sherlockLines, "search"); } } catch (ex) { // The conversion using the default charset failed. Remove any non-ascii // bytes and try to find a sourceTextEncoding. var asciiBytes = 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(data, sourceTextEncoding); searchSection = getSection(sherlockLines, "search"); } else { // ERROR("Couldn't find a working charset", Cr.NS_ERROR_FAILURE); } } return searchSection["name"]; } /** * 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) { return (aElement && (aLocalNameArray.indexOf(aElement.localName) != -1) && (aNameSpaceArray.indexOf(aElement.namespaceURI) != -1)); } /** * Extract search engine information from the collected data to initialize * the engine object. */ function parseAsOpenSearch(data) { var doc = data; for (var i = 0; i < doc.childNodes.length; ++i) { var child = doc.childNodes[i]; switch (child.localName) { case "ShortName": return child.textContent; break; case "Image": // this._parseImage(child); break; } } } function getSearchEngineImage(searchenginepath) { var ext = searchenginepath.substring(searchenginepath.lastIndexOf('.') + 1,searchenginepath.length); if (ext.toLowerCase() != "xml") { return; } // assume we're already open search var data; var sourcefile = Components.classes["@mozilla.org/file/local;1"] .createInstance(Components.interfaces.nsILocalFile); sourcefile.initWithPath(searchenginepath); var fileInStream = Components.classes["@mozilla.org/network/file-input-stream;1"] .createInstance(Components.interfaces.nsIFileInputStream); fileInStream.init(sourcefile, MODE_RDONLY, PERMS_FILE, false); var domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"] .createInstance(Components.interfaces.nsIDOMParser); var doc = domParser.parseFromStream(fileInStream, "UTF-8", sourcefile.fileSize, "text/xml"); data = doc.documentElement; for (var i = 0; i < data.childNodes.length; ++i) { var child = data.childNodes[i]; switch (child.localName) { case "Image": fileInStream.close(); return child.textContent; break; } } fileInStream.close(); return; }