search-engines: Remove broken Answers search engine
[conkeror.git] / modules / opensearch.js
blobaa2cc52dc17039406fa157ae5f3fc365a5bdbb15
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2010,2012 John J. Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
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.");
23 const opensearch_response_type_json = "application/x-suggestions+json";
24 const opensearch_response_type_xml = "application/x-suggestions+xml";
27 function opensearch_parse_error (msg) {
28     var e = new Error(msg);
29     e.__proto__ = opensearch_parse_error.prototype;
30     return e;
32 opensearch_parse_error.prototype.__proto__ = Error.prototype;
35 function opensearch_xml_completions (completer, data) {
36     completions.call(this, completer, data);
38 opensearch_xml_completions.prototype = {
39     constructor: opensearch_xml_completions,
40     __proto__: completions.prototype,
41     toString: function () "#<opensearch_xml_completions>",
42     get_string: function (i) this.data[i][0],
43     get_description: function (i) {
44         if (this.data[i][1])
45             return this.data[i][1] + " results";
46         return "";
47     }
51 function opensearch_json_completions (completer, data, descriptions) {
52     completions.call(this, completer, data);
53     this.descriptions = descriptions;
55 opensearch_json_completions.prototype = {
56     constructor: opensearch_json_completions,
57     __proto__: completions.prototype,
58     toString: function () "#<opensearch_json_completions>",
59     descriptions: null,
60     get_string: function (i) String(this.data[i]),
61     get_description: function (i) {
62         if (this.descriptions)
63             return String(this.descriptions[i]);
64         return null;
65     }
69 function opensearch_xml_completer (eng) {
70     this.eng = eng;
72 opensearch_xml_completer.prototype = {
73     constructor: opensearch_xml_completer,
74     __proto__: completer.prototype,
75     toString: function () "#<opensearch_xml_completer>",
76     eng: null,
77     complete: function (input, pos) {
78         let str = input.substring(0, pos);
79         try {
80             let lspec = this.eng.get_query_load_spec(str, opensearch_response_type_xml);
81             let result = yield send_http_request(lspec);
82             let doc = result.responseXML;
83             var narrowed = [];
84             if (doc) {
85                 let elems = doc.getElementsByTagName("CompleteSuggestion");
86                 for (let i = 0; i < elems.length; ++i) {
87                     let node = elems[i];
88                     let name = node.firstChild.getAttribute("data");
89                     let desc = node.lastChild.getAttribute("int");
90                     if (name)
91                         narrowed.push([name,desc]);
92                 }
93                 delete this.doc;
94                 delete this.elem;
95                 delete this.result;
96                 delete this.lspec;
97                 yield co_return(new opensearch_xml_completions(this, narrowed));
98             }
99         } catch (e) {
100             yield co_return(null);
101         }
102     }
106 function opensearch_json_completer (eng) {
107     this.eng = eng;
109 opensearch_json_completer.prototype = {
110     constructor: opensearch_json_completer,
111     __proto__: completer.prototype,
112     toString: function () "#<opensearch_json_completer>",
113     eng: null,
114     complete: function (input, pos) {
115         let str = input.substring(0,pos);
116         try {
117             let lspec = this.eng.get_query_load_spec(str, opensearch_response_type_json);
118             let result = yield send_http_request(lspec);
119             let data = JSON.parse(result.responseText);
120             delete this.result;
121             delete this.lspec;
122             if (!(array_p(data) &&
123                   data.length >= 2 &&
124                   typeof data[0] == "string" &&
125                   data[0] == str &&
126                   array_p(data[1])))
127                 yield co_return(null);
128             if (data[2] && array_p(data[2]) &&
129                 data[2].length == data[1].length)
130             {
131                 var descriptions = data[2];
132             }
133             yield co_return(new opensearch_json_completions(this, data[1], descriptions));
134         } catch (e) {
135             yield co_return(null);
136         }
137     }
141 function opensearch_description () {
142     this.urls = {};
144 opensearch_description.prototype = {
145     constructor: opensearch_description,
146     name: null,
147     description: null,
148     urls: null,
149     query_charset: "UTF-8",
151     supports_response_type: function (type) {
152         return (type in this.urls);
153     },
155     /**
156      * Returns null if the result mime_type isn't supported.  The string
157      * search_terms will be escaped by this function.
158      */
159     get_query_load_spec: function (search_terms, type) {
160         if (type == null)
161             type = "text/html";
162         var url = this.urls[type];
163         if (!url)
164             return null;
165         search_terms = encodeURIComponent(search_terms);
166         var eng = this;
168         function substitute (value) {
169             // Insert the OpenSearch parameters we're confident about
170             value = value.replace(/\{searchTerms\??\}/g, search_terms);
171             value = value.replace(/\{inputEncoding\??\}/g, eng.query_charset);
172             value = value.replace(/\{language\??\}/g, get_locale() || "*");
173             value = value.replace(/\{outputEncoding\??\}/g, "UTF-8");
175             // Remove any optional parameters
176             value = value.replace(/\{(?:\w+:)?\w+\?\}/g, "");
178             // Insert any remaining required params with our default values
179             value = value.replace(/\{count\??\}/g, "20");     // 20 results
180             value = value.replace(/\{startIndex\??\}/g, "1"); // start at 1st result
181             value = value.replace(/\{startPage\??\}/g, "1");  // 1st page
183             return value;
184         }
186         var url_string = substitute(url.template);
188         var data = url.params.map(function (p) (p.name + "=" + substitute(p.value))).join("&");
190         if (url.method == "GET") {
191             if (data.length > 0) {
192                 if (url_string.indexOf("?") == -1)
193                     url_string += "?";
194                 else
195                     url_string += "&";
196                 url_string += data;
197             }
198             return load_spec({uri: url_string});
199         } else {
200             return load_spec({uri: url_string, raw_post_data: data,
201                               request_mime_type: "application/x-www-form-urlencoded"});
202         }
203     },
205     /**
206      * Guess the url of a home page to correspond with the search engine.
207      * Take the text/html url for the search engine, trim off the path and
208      * any "search." prefix on the domain.
209      * This works for all the provided search engines.
210      */
211     get_homepage: function () {
212         var url = this.urls["text/html"];
213         if (!url)
214             return null;
215         url = url_path_trim(url.template);
216         url = url.replace("//search.", "//");
217         return url;
218     },
220     get completer () {
221         if (this.supports_response_type(opensearch_response_type_xml))
222             return new opensearch_xml_completer(this);
223         if (this.supports_response_type(opensearch_response_type_json))
224             return new opensearch_json_completer(this);
225         return null;
226     }
230 function opensearch_url (type, method, template) {
231     if (!method || !type || !template)
232         throw opensearch_parse_error("Missing method, type, or template for search engine URL");
233     method = method.toUpperCase();
234     type = type.toUpperCase();
235     if (method != "GET" && method != "POST")
236         throw opensearch_parse_error("Invalid method");
237     var template_uri = make_uri(template);
238     switch (template_uri.scheme) {
239     case "http":
240     case "https":
241         break;
242     default:
243         throw opensearch_parse_error("URL template has invalid scheme.");
244         break;
245     }
246     this.type = type;
247     this.method = method;
248     this.template = template;
249     this.params =  [];
251 opensearch_url.prototype = {
252     constructor: opensearch_url,
253     add_param: function (name, value) {
254         this.params.push({name: name, value: value});
255     }
259 function opensearch_parse (node) {
260     var eng = new opensearch_description();
261     for each (let child in node.childNodes) {
262         switch (child.localName) {
263         case "ShortName":
264             eng.name = child.textContent;
265             break;
266         case "Description":
267             eng.description = child.textContent;
268             break;
269         case "Url":
270             try {
271                 let type = child.getAttribute("type");
272                 let method = child.getAttribute("method") || "GET";
273                 let template = child.getAttribute("template");
274                 let engine_url = new opensearch_url(type, method, template);
275                 for each (let p in child.childNodes) {
276                     if (p.localName == "Param") {
277                         let name = p.getAttribute("name");
278                         let value = p.getAttribute("value");
279                         if (name && value)
280                             engine_url.add_param(name, value);
281                     }
282                 }
283                 eng.urls[type] = engine_url;
284             } catch (e) {
285                 // Skip this element if parsing fails
286             }
287             break;
288         case "InputEncoding":
289             eng.query_charset = child.textContent.toUpperCase();
290             break;
291         }
292     }
293     return eng;
297 function opensearch_read_file (file) {
298     var file_istream = Cc["@mozilla.org/network/file-input-stream;1"]
299         .createInstance(Ci.nsIFileInputStream);
300     file_istream.init(file, MODE_RDONLY, parseInt("0644", 8), false);
301     var dom_parser = Cc["@mozilla.org/xmlextras/domparser;1"]
302         .createInstance(Ci.nsIDOMParser);
303     var doc = dom_parser.parseFromStream(file_istream, "UTF-8",
304                                          file.fileSize, "text/xml");
305     return opensearch_parse(doc.documentElement);
309 define_keywords("$alternative");
310 function define_opensearch_webjump (name, spec) {
311     keywords(arguments);
312     let alternative = arguments.$alternative;
314     var path = null;
315     if (spec instanceof Ci.nsIFile)
316         path = spec;
317     else {
318         for (let i = 0, n = opensearch_load_paths.length; i < n; ++i) {
319             path = make_file(opensearch_load_paths[i]).clone();
320             path.append(spec);
321             if (path.exists())
322                 break;
323         }
324     }
325     if (! path || ! path.exists())
326         throw new Error("Opensearch file not found.");
328     var eng = opensearch_read_file(path);
330     if (alternative == null)
331         alternative = eng.get_homepage();
333     define_webjump(name,
334                    function (arg) {
335                        return eng.get_query_load_spec(arg);
336                    },
337                    $alternative = alternative,
338                    $doc = eng.description,
339                    $completer = eng.completer);
342 define_opensearch_webjump("google", "google.xml");
343 define_opensearch_webjump("bugzilla", "mozilla-bugzilla.xml");
344 define_opensearch_webjump("wikipedia", "wikipedia.xml");
345 define_opensearch_webjump("wiktionary", "wiktionary.xml");
346 define_opensearch_webjump("yahoo", "yahoo.xml");
347 define_opensearch_webjump("creativecommons", "creativecommons.xml");
348 define_opensearch_webjump("ebay", "eBay.xml");
349 define_opensearch_webjump("duckduckgo", "duckduckgo.xml");
351 provide("opensearch");