2 * (C) Copyright 2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2010 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
9 // Supported OpenSearch parameters
10 // http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax
12 require("webjump.js");
15 define_variable("opensearch_load_paths",
16 [file_locator_service.get("ProfD", Ci.nsIFile),
17 file_locator_service.get("CurProcD", Ci.nsIFile)]
18 .map(function (x) x.append("search-engines") || x),
19 "Paths to search for opensearch description files. Default list "+
20 "includes the subdirectory called 'search-engines' in your profile "+
21 "directory and the Conkeror installation directory.");
24 function opensearch_parse_error (msg) {
25 var e = new Error(msg);
26 e.__proto__ = opensearch_parse_error.prototype;
29 opensearch_parse_error.prototype.__proto__ = Error.prototype;
32 function opensearch_description () {
35 opensearch_description.prototype = {
36 constructor: opensearch_description,
40 query_charset: "UTF-8",
42 supports_response_type: function (type) {
43 return (type in this.urls);
47 * Returns null if the result mime_type isn't supported. The string
48 * search_terms will be escaped by this function.
50 get_query_load_spec: function (search_terms, type) {
53 var url = this.urls[type];
56 search_terms = encodeURIComponent(search_terms);
59 function substitute (value) {
60 // Insert the OpenSearch parameters we're confident about
61 value = value.replace(/\{searchTerms\??\}/g, search_terms);
62 value = value.replace(/\{inputEncoding\??\}/g, eng.query_charset);
63 value = value.replace(/\{language\??\}/g, get_locale() || "*");
64 value = value.replace(/\{outputEncoding\??\}/g, "UTF-8");
66 // Remove any optional parameters
67 value = value.replace(/\{(?:\w+:)?\w+\?\}/g, "");
69 // Insert any remaining required params with our default values
70 value = value.replace(/\{count\??\}/g, "20"); // 20 results
71 value = value.replace(/\{startIndex\??\}/g, "1"); // start at 1st result
72 value = value.replace(/\{startPage\??\}/g, "1"); // 1st page
77 var url_string = substitute(url.template);
79 var data = url.params.map(function (p) (p.name + "=" + substitute(p.value))).join("&");
81 if (url.method == "GET") {
82 if (data.length > 0) {
83 if (url_string.indexOf("?") == -1)
89 return load_spec({uri: url_string});
91 return load_spec({uri: url_string, raw_post_data: data,
92 request_mime_type: "application/x-www-form-urlencoded"});
97 * Guess the url of a home page to correspond with the search engine.
98 * Take the text/html url for the search engine, trim off the path and
99 * any "search." prefix on the domain.
100 * This works for all the provided search engines.
102 get_homepage: function () {
103 var url = this.urls["text/html"];
106 url = url_path_trim(url.template);
107 url = url.replace("//search.", "//");
112 const response_type_json = "application/x-suggestions+json";
113 const response_type_xml = "application/x-suggestions+xml";
115 if (this.supports_response_type(response_type_xml)) {
116 return function (input, pos, conservative) {
117 if (pos == 0 && conservative)
118 yield co_return(undefined);
119 let str = input.substring(0,pos);
121 let lspec = eng.get_query_load_spec(str, response_type_xml);
122 let result = yield send_http_request(lspec);
123 let doc = result.responseXML;
126 let elems = doc.getElementsByTagName("CompleteSuggestion");
127 for (let i = 0; i < elems.length; ++i) {
129 let name = node.firstChild.getAttribute("data");
130 let desc = node.lastChild.getAttribute("int");
132 data.push([name,desc]);
138 let c = { count: data.length,
139 get_string: function (i) data[i][0],
140 get_description: function (i) data[i][1] + " results",
141 get_input_state: function (i) [data[i][0]]
146 yield co_return(null);
149 } else if (this.supports_response_type(response_type_json)) {
150 return function (input, pos, conservative) {
151 if (pos == 0 && conservative)
152 yield co_return(undefined);
153 let str = input.substring(0,pos);
155 let lspec = eng.get_query_load_spec(str, response_type_json);
156 let result = yield send_http_request(lspec);
157 let data = JSON.parse(result.responseText);
161 if (!(data instanceof Array &&
163 typeof(data[0]) == "string" &&
165 data[1] instanceof Array))
166 yield co_return(null);
167 if (data[2] && data[2] instanceof Array &&
168 data[2].length == data[1].length)
170 var descriptions = data[2];
172 let c = { count: data[1].length,
173 get_string: function (i) String(data[1][i]),
174 get_description: (descriptions && (function (i) String(descriptions[i]))),
175 get_input_state: function (i) [String(data[1][i])]
179 yield co_return(null);
189 function opensearch_url (type, method, template) {
190 if (!method || !type || !template)
191 throw opensearch_parse_error("Missing method, type, or template for search engine URL");
192 method = method.toUpperCase();
193 type = type.toUpperCase();
194 if (method != "GET" && method != "POST")
195 throw opensearch_parse_error("Invalid method");
196 var template_uri = make_uri(template);
197 switch (template_uri.scheme) {
202 throw opensearch_parse_error("URL template has invalid scheme.");
206 this.method = method;
207 this.template = template;
210 opensearch_url.prototype = {
211 constructor: opensearch_url,
212 add_param: function (name, value) {
213 this.params.push({name: name, value: value});
218 function opensearch_parse (node) {
219 var eng = new opensearch_description();
220 for each (let child in node.childNodes) {
221 switch (child.localName) {
223 eng.name = child.textContent;
226 eng.description = child.textContent;
230 let type = child.getAttribute("type");
231 let method = child.getAttribute("method") || "GET";
232 let template = child.getAttribute("template");
233 let engine_url = new opensearch_url(type, method, template);
234 for each (let p in child.childNodes) {
235 if (p.localName == "Param") {
236 let name = p.getAttribute("name");
237 let value = p.getAttribute("value");
239 engine_url.add_param(name, value);
242 eng.urls[type] = engine_url;
244 // Skip this element if parsing fails
247 case "InputEncoding":
248 eng.query_charset = child.textContent.toUpperCase();
256 function opensearch_read_file (file) {
257 var file_istream = Cc["@mozilla.org/network/file-input-stream;1"]
258 .createInstance(Ci.nsIFileInputStream);
259 file_istream.init(file, MODE_RDONLY, 0644, false);
260 var dom_parser = Cc["@mozilla.org/xmlextras/domparser;1"]
261 .createInstance(Ci.nsIDOMParser);
262 var doc = dom_parser.parseFromStream(file_istream, "UTF-8",
263 file.fileSize, "text/xml");
264 return opensearch_parse(doc.documentElement);
268 define_keywords("$alternative");
269 function define_opensearch_webjump (name, spec) {
271 let alternative = arguments.$alternative;
274 if (spec instanceof Ci.nsIFile)
277 for (i = 0, n = opensearch_load_paths.length; i < n; ++i) {
278 path = make_file(opensearch_load_paths[i]).clone();
284 if (! path || ! path.exists())
285 throw new Error("Opensearch file not found.");
287 var eng = opensearch_read_file(path);
289 if (alternative == null)
290 alternative = eng.get_homepage();
294 return eng.get_query_load_spec(arg);
296 $alternative = alternative,
297 $description = eng.description,
298 $completer = eng.completer);
301 define_opensearch_webjump("google", "google.xml");
302 define_opensearch_webjump("bugzilla", "mozilla-bugzilla.xml");
303 define_opensearch_webjump("wikipedia", "wikipedia.xml");
304 define_opensearch_webjump("wiktionary", "wiktionary.xml");
305 define_opensearch_webjump("answers", "answers.xml");
306 define_opensearch_webjump("yahoo", "yahoo.xml");
307 define_opensearch_webjump("creativecommons", "creativecommons.xml");
308 define_opensearch_webjump("ebay", "eBay.xml");
309 define_opensearch_webjump("duckduckgo", "duckduckgo.xml");
311 provide("opensearch");