Replace uses of co_call/continuation API with uses of spawn/Promise API
[conkeror.git] / modules / opensearch.js
blobcc81ba8c98317eed5186ce877516ecf880859f63
1 /**
2  * (C) Copyright 2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2010 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.");
24 function opensearch_parse_error (msg) {
25     var e = new Error(msg);
26     e.__proto__ = opensearch_parse_error.prototype;
27     return e;
29 opensearch_parse_error.prototype.__proto__ = Error.prototype;
32 function opensearch_description () {
33     this.urls = {};
35 opensearch_description.prototype = {
36     constructor: opensearch_description,
37     name: null,
38     description: null,
39     urls: null,
40     query_charset: "UTF-8",
42     supports_response_type: function (type) {
43         return (type in this.urls);
44     },
46     /**
47      * Returns null if the result mime_type isn't supported.  The string
48      * search_terms will be escaped by this function.
49      */
50     get_query_load_spec: function (search_terms, type) {
51         if (type == null)
52             type = "text/html";
53         var url = this.urls[type];
54         if (!url)
55             return null;
56         search_terms = encodeURIComponent(search_terms);
57         var eng = this;
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
74             return value;
75         }
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)
84                     url_string += "?";
85                 else
86                     url_string += "&";
87                 url_string += data;
88             }
89             return load_spec({uri: url_string});
90         } else {
91             return load_spec({uri: url_string, raw_post_data: data,
92                               request_mime_type: "application/x-www-form-urlencoded"});
93         }
94     },
96     /**
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.
101      */
102     get_homepage: function () {
103         var url = this.urls["text/html"];
104         if (!url)
105             return null;
106         url = url_path_trim(url.template);
107         url = url.replace("//search.", "//");
108         return url;
109     },
111     get completer () {
112         const response_type_json = "application/x-suggestions+json";
113         const response_type_xml = "application/x-suggestions+xml";
114         var eng = this;
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);
120                 try {
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;
124                     let data = [];
125                     if (doc) {
126                         let elems = doc.getElementsByTagName("CompleteSuggestion");
127                         for (let i = 0; i < elems.length; ++i) {
128                             let node = elems[i];
129                             let name = node.firstChild.getAttribute("data");
130                             let desc = node.lastChild.getAttribute("int");
131                             if (name)
132                                 data.push([name,desc]);
133                         }
134                         delete doc;
135                         delete elem;
136                         delete result;
137                         delete lspec;
138                         let c = { count: data.length,
139                                   get_string: function (i) data[i][0],
140                                   get_description: function (i) {
141                                       if (data[i][1])
142                                           return data[i][1] + " results";
143                                       return "";
144                                   },
145                                   get_input_state: function (i) [data[i][0]]
146                                 };
147                         yield co_return(c);
148                     }
149                 } catch (e) {
150                     yield co_return(null);
151                 }
152             };
153         } else if (this.supports_response_type(response_type_json)) {
154             return function (input, pos, conservative) {
155                 if (pos == 0 && conservative)
156                     yield co_return(undefined);
157                 let str = input.substring(0,pos);
158                 try {
159                     let lspec = eng.get_query_load_spec(str, response_type_json);
160                     let result = yield send_http_request(lspec);
161                     let data = JSON.parse(result.responseText);
162                     delete result;
163                     delete lspec;
165                     if (!(array_p(data) &&
166                           data.length >= 2 &&
167                           typeof(data[0]) == "string" &&
168                           data[0] == str &&
169                           array_p(data[1])))
170                         yield co_return(null);
171                     if (data[2] && array_p(data[2]) &&
172                         data[2].length == data[1].length)
173                     {
174                         var descriptions = data[2];
175                     }
176                     let c = { count: data[1].length,
177                               get_string: function (i) String(data[1][i]),
178                               get_description: (descriptions && (function (i) String(descriptions[i]))),
179                               get_input_state: function (i) [String(data[1][i])]
180                             };
181                     yield co_return(c);
182                 } catch (e) {
183                     yield co_return(null);
184                 }
185             };
186         } else {
187             return null;
188         }
189     }
193 function opensearch_url (type, method, template) {
194     if (!method || !type || !template)
195         throw opensearch_parse_error("Missing method, type, or template for search engine URL");
196     method = method.toUpperCase();
197     type = type.toUpperCase();
198     if (method != "GET" && method != "POST")
199         throw opensearch_parse_error("Invalid method");
200     var template_uri = make_uri(template);
201     switch (template_uri.scheme) {
202     case "http":
203     case "https":
204         break;
205     default:
206         throw opensearch_parse_error("URL template has invalid scheme.");
207         break;
208     }
209     this.type = type;
210     this.method = method;
211     this.template = template;
212     this.params =  [];
214 opensearch_url.prototype = {
215     constructor: opensearch_url,
216     add_param: function (name, value) {
217         this.params.push({name: name, value: value});
218     }
222 function opensearch_parse (node) {
223     var eng = new opensearch_description();
224     for each (let child in node.childNodes) {
225         switch (child.localName) {
226         case "ShortName":
227             eng.name = child.textContent;
228             break;
229         case "Description":
230             eng.description = child.textContent;
231             break;
232         case "Url":
233             try {
234                 let type = child.getAttribute("type");
235                 let method = child.getAttribute("method") || "GET";
236                 let template = child.getAttribute("template");
237                 let engine_url = new opensearch_url(type, method, template);
238                 for each (let p in child.childNodes) {
239                     if (p.localName == "Param") {
240                         let name = p.getAttribute("name");
241                         let value = p.getAttribute("value");
242                         if (name && value)
243                             engine_url.add_param(name, value);
244                     }
245                 }
246                 eng.urls[type] = engine_url;
247             } catch (e) {
248                 // Skip this element if parsing fails
249             }
250             break;
251         case "InputEncoding":
252             eng.query_charset = child.textContent.toUpperCase();
253             break;
254         }
255     }
256     return eng;
260 function opensearch_read_file (file) {
261     var file_istream = Cc["@mozilla.org/network/file-input-stream;1"]
262         .createInstance(Ci.nsIFileInputStream);
263     file_istream.init(file, MODE_RDONLY, 0644, false);
264     var dom_parser = Cc["@mozilla.org/xmlextras/domparser;1"]
265         .createInstance(Ci.nsIDOMParser);
266     var doc = dom_parser.parseFromStream(file_istream, "UTF-8",
267                                          file.fileSize, "text/xml");
268     return opensearch_parse(doc.documentElement);
272 define_keywords("$alternative");
273 function define_opensearch_webjump (name, spec) {
274     keywords(arguments);
275     let alternative = arguments.$alternative;
277     var path = null;
278     if (spec instanceof Ci.nsIFile) 
279         path = spec;
280     else {
281         for (i = 0, n = opensearch_load_paths.length; i < n; ++i) {
282             path = make_file(opensearch_load_paths[i]).clone();
283             path.append(spec);
284             if (path.exists())
285                 break;
286         }
287     }
288     if (! path || ! path.exists())
289         throw new Error("Opensearch file not found.");
291     var eng = opensearch_read_file(path);
293     if (alternative == null)
294         alternative = eng.get_homepage();
296     define_webjump(name,
297                    function (arg) {
298                        return eng.get_query_load_spec(arg);
299                    },
300                    $alternative = alternative,
301                    $description = eng.description,
302                    $completer = eng.completer);
305 define_opensearch_webjump("google", "google.xml");
306 define_opensearch_webjump("bugzilla", "mozilla-bugzilla.xml");
307 define_opensearch_webjump("wikipedia", "wikipedia.xml");
308 define_opensearch_webjump("wiktionary", "wiktionary.xml");
309 define_opensearch_webjump("answers", "answers.xml");
310 define_opensearch_webjump("yahoo", "yahoo.xml");
311 define_opensearch_webjump("creativecommons", "creativecommons.xml");
312 define_opensearch_webjump("ebay", "eBay.xml");
313 define_opensearch_webjump("duckduckgo", "duckduckgo.xml");
315 provide("opensearch");