use global JSON object instead of nsIJSON
[conkeror.git] / modules / opensearch.js
blob29f0af910f846f20729a9c577062fd9f598a272a
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 in_module(null);
14 require("webjump.js");
17 define_variable("opensearch_load_paths",
18     [file_locator_service.get("ProfD", Ci.nsIFile),
19      file_locator_service.get("CurProcD", Ci.nsIFile)]
20     .map(function (x) x.append("search-engines") || x),
21     "Paths to search for opensearch description files.  Default list "+
22     "includes the subdirectory called 'search-engines' in your profile "+
23     "directory and the Conkeror installation directory.");
26 function opensearch_parse_error (msg) {
27     var e = new Error(msg);
28     e.__proto__ = opensearch_parse_error.prototype;
29     return e;
31 opensearch_parse_error.prototype.__proto__ = Error.prototype;
34 function opensearch_description () {
35     this.urls = {};
37 opensearch_description.prototype = {
38     constructor: opensearch_description,
39     name: null,
40     description: null,
41     urls: null,
42     query_charset: "UTF-8",
44     supports_response_type: function (type) {
45         return (type in this.urls);
46     },
48     /**
49      * Returns null if the result mime_type isn't supported.  The string
50      * search_terms will be escaped by this function.
51      */
52     get_query_load_spec: function (search_terms, type) {
53         if (type == null)
54             type = "text/html";
55         var url = this.urls[type];
56         if (!url)
57             return null;
58         search_terms = encodeURIComponent(search_terms);
59         var eng = this;
61         function substitute (value) {
62             // Insert the OpenSearch parameters we're confident about
63             value = value.replace(/\{searchTerms\??\}/g, search_terms);
64             value = value.replace(/\{inputEncoding\??\}/g, eng.query_charset);
65             value = value.replace(/\{language\??\}/g, get_locale() || "*");
66             value = value.replace(/\{outputEncoding\??\}/g, "UTF-8");
68             // Remove any optional parameters
69             value = value.replace(/\{(?:\w+:)?\w+\?\}/g, "");
71             // Insert any remaining required params with our default values
72             value = value.replace(/\{count\??\}/g, "20");     // 20 results
73             value = value.replace(/\{startIndex\??\}/g, "1"); // start at 1st result
74             value = value.replace(/\{startPage\??\}/g, "1");  // 1st page
76             return value;
77         }
79         var url_string = substitute(url.template);
81         var data = url.params.map(function (p) (p.name + "=" + substitute(p.value))).join("&");
83         if (url.method == "GET") {
84             if (data.length > 0) {
85                 if (url_string.indexOf("?") == -1)
86                     url_string += "?";
87                 else
88                     url_string += "&";
89                 url_string += data;
90             }
91             return load_spec({uri: url_string});
92         } else {
93             return load_spec({uri: url_string, raw_post_data: data,
94                               request_mime_type: "application/x-www-form-urlencoded"});
95         }
96     },
98     /**
99      * Guess the url of a home page to correspond with the search engine.
100      * Take the text/html url for the search engine, trim off the path and
101      * any "search." prefix on the domain.
102      * This works for all the provided search engines.
103      */
104     get_homepage: function () {
105         var url = this.urls["text/html"];
106         if (!url)
107             return null;
108         url = url_path_trim(url.template);
109         url = url.replace("//search.", "//");
110         return url;
111     },
113     get completer () {
114         const response_type_json = "application/x-suggestions+json";
115         const response_type_xml = "application/x-suggestions+xml";
116         var eng = this;
117         if (this.supports_response_type(response_type_xml)) {
118             return function (input, pos, conservative) {
119                 if (pos == 0 && conservative)
120                     yield co_return(undefined);
121                 let str = input.substring(0,pos);
122                 try {
123                     let lspec = eng.get_query_load_spec(str, response_type_xml);
124                     let result = yield send_http_request(lspec);
125                     let doc = result.responseXML;
126                     let data = [];
127                     if (doc) {
128                         let elems = doc.getElementsByTagName("CompleteSuggestion");
129                         for (let i = 0; i < elems.length; ++i) {
130                             let node = elems[i];
131                             let name = node.firstChild.getAttribute("data");
132                             let desc = node.lastChild.getAttribute("int");
133                             if (name && desc)
134                                 data.push([name,desc]);
135                         }
136                         delete doc;
137                         delete elem;
138                         delete result;
139                         delete lspec;
140                         let c = { count: data.length,
141                                   get_string: function (i) data[i][0],
142                                   get_description: function (i) data[i][1] + " results",
143                                   get_input_state: function (i) [data[i][0]]
144                                 };
145                         yield co_return(c);
146                     }
147                 } catch (e) {
148                     yield co_return(null);
149                 }
150             };
151         } else if (this.supports_response_type(response_type_json)) {
152             return function (input, pos, conservative) {
153                 if (pos == 0 && conservative)
154                     yield co_return(undefined);
155                 let str = input.substring(0,pos);
156                 try {
157                     let lspec = eng.get_query_load_spec(str, response_type_json);
158                     let result = yield send_http_request(lspec);
159                     let data = JSON.parse(result.responseText);
160                     delete result;
161                     delete lspec;
163                     if (!(data instanceof Array &&
164                           data.length >= 2 &&
165                           typeof(data[0]) == "string" &&
166                           data[0] == str &&
167                           data[1] instanceof Array))
168                         yield co_return(null);
169                     if (data[2] && data[2] instanceof Array &&
170                         data[2].length == data[1].length)
171                     {
172                         var descriptions = data[2];
173                     }
174                     let c = { count: data[1].length,
175                               get_string: function (i) String(data[1][i]),
176                               get_description: (descriptions && (function (i) String(descriptions[i]))),
177                               get_input_state: function (i) [String(data[1][i])]
178                             };
179                     yield co_return(c);
180                 } catch (e) {
181                     yield co_return(null);
182                 }
183             };
184         } else {
185             return null;
186         }
187     }
191 function opensearch_url (type, method, template) {
192     if (!method || !type || !template)
193         throw opensearch_parse_error("Missing method, type, or template for search engine URL");
194     method = method.toUpperCase();
195     type = type.toUpperCase();
196     if (method != "GET" && method != "POST")
197         throw opensearch_parse_error("Invalid method");
198     var template_uri = make_uri(template);
199     switch (template_uri.scheme) {
200     case "http":
201     case "https":
202         break;
203     default:
204         throw opensearch_parse_error("URL template has invalid scheme.");
205         break;
206     }
207     this.type = type;
208     this.method = method;
209     this.template = template;
210     this.params =  [];
212 opensearch_url.prototype = {
213     constructor: opensearch_url,
214     add_param: function (name, value) {
215         this.params.push({name: name, value: value});
216     }
220 function opensearch_parse (node) {
221     var eng = new opensearch_description();
222     for each (let child in node.childNodes) {
223         switch (child.localName) {
224         case "ShortName":
225             eng.name = child.textContent;
226             break;
227         case "Description":
228             eng.description = child.textContent;
229             break;
230         case "Url":
231             try {
232                 let type = child.getAttribute("type");
233                 let method = child.getAttribute("method") || "GET";
234                 let template = child.getAttribute("template");
235                 let engine_url = new opensearch_url(type, method, template);
236                 for each (let p in child.childNodes) {
237                     if (p.localName == "Param") {
238                         let name = p.getAttribute("name");
239                         let value = p.getAttribute("value");
240                         if (name && value)
241                             engine_url.add_param(name, value);
242                     }
243                 }
244                 eng.urls[type] = engine_url;
245             } catch (e) {
246                 // Skip this element if parsing fails
247             }
248             break;
249         case "InputEncoding":
250             eng.query_charset = child.textContent.toUpperCase();
251             break;
252         }
253     }
254     return eng;
258 function opensearch_read_file (file) {
259     var file_istream = Cc["@mozilla.org/network/file-input-stream;1"]
260         .createInstance(Ci.nsIFileInputStream);
261     file_istream.init(file, MODE_RDONLY, 0644, false);
262     var dom_parser = Cc["@mozilla.org/xmlextras/domparser;1"]
263         .createInstance(Ci.nsIDOMParser);
264     var doc = dom_parser.parseFromStream(file_istream, "UTF-8",
265                                          file.fileSize, "text/xml");
266     return opensearch_parse(doc.documentElement);
270 define_keywords("$alternative");
271 function define_opensearch_webjump (name, spec) {
272     keywords(arguments);
273     let alternative = arguments.$alternative;
275     var path = null;
276     if (spec instanceof Ci.nsIFile) 
277         path = spec;
278     else {
279         for (i = 0, n = opensearch_load_paths.length; i < n; ++i) {
280             path = make_file(opensearch_load_paths[i]).clone();
281             path.append(spec);
282             if (path.exists())
283                 break;
284         }
285     }
286     if (! path || ! path.exists())
287         throw new Error("Opensearch file not found.");
289     var eng = opensearch_read_file(path);
291     if (alternative == null)
292         alternative = eng.get_homepage();
294     define_webjump(name,
295                    function (arg) {
296                        return eng.get_query_load_spec(arg);
297                    },
298                    $alternative = alternative,
299                    $description = eng.description,
300                    $completer = eng.completer);
303 define_opensearch_webjump("google", "google.xml");
304 define_opensearch_webjump("bugzilla", "mozilla-bugzilla.xml");
305 define_opensearch_webjump("wikipedia", "wikipedia.xml");
306 define_opensearch_webjump("wiktionary", "wiktionary.xml");
307 define_opensearch_webjump("answers", "answers.xml");
308 define_opensearch_webjump("yahoo", "yahoo.xml");
309 define_opensearch_webjump("creativecommons", "creativecommons.xml");
310 define_opensearch_webjump("ebay", "eBay.xml");
312 provide("opensearch");