]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/protocol.jsm
Import 1.0 supporting Firefox up to 14.*
[dactyl.git] / common / modules / protocol.jsm
1 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
2 //
3 // This work is licensed for reuse under an MIT license. Details are
4 // given in the LICENSE.txt file included with this file.
5 /* use strict */
6
7 Components.utils.import("resource://dactyl/bootstrap.jsm");
8 defineModule("protocol", {
9     exports: ["LocaleChannel", "Protocol", "RedirectChannel", "StringChannel", "XMLChannel"],
10     require: ["services", "util"]
11 }, this);
12
13 var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].getService(Ci.nsIPrincipal);
14
15 function Channel(url, orig, noErrorChannel, unprivileged) {
16     try {
17         if (url == null)
18             return noErrorChannel ? null : NetError(orig);
19
20         if (url instanceof Ci.nsIChannel)
21             return url;
22
23         if (typeof url === "function")
24             return let ([type, data] = url(orig)) StringChannel(data, type, orig);
25
26         if (isArray(url))
27             return let ([type, data] = url) StringChannel(data, type, orig);
28
29         let uri = services.io.newURI(url, null, null);
30         return (new XMLChannel(uri, null, noErrorChannel)).channel;
31     }
32     catch (e) {
33         util.reportError(e);
34         util.dump(url);
35         throw e;
36     }
37 }
38 function NetError(orig, error) {
39     return services.InterfacePointer({
40         QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannel]),
41
42         name: orig.spec,
43
44         URI: orig,
45
46         originalURI: orig,
47
48         asyncOpen: function () { throw error || Cr.NS_ERROR_FILE_NOT_FOUND },
49
50         open: function () { throw error || Cr.NS_ERROR_FILE_NOT_FOUND }
51     }).data.QueryInterface(Ci.nsIChannel);
52 }
53 function RedirectChannel(to, orig, time, message) {
54     let html = <html><head><meta http-equiv="Refresh" content={(time || 0) + ";" + to}/></head>
55                      <body><h2 style="text-align: center">{message || ""}</h2></body></html>.toXMLString();
56     return StringChannel(html, "text/html", services.io.newURI(to, null, null));
57 }
58
59 function Protocol(scheme, classID, contentBase) {
60     function Protocol() {
61         ProtocolBase.call(this);
62     }
63     Protocol.prototype = {
64         __proto__: ProtocolBase.prototype,
65
66         classID: Components.ID(classID),
67
68         scheme: scheme,
69
70         contentBase: contentBase,
71
72         _xpcom_factory: JSMLoader.Factory(Protocol),
73     };
74     return Protocol;
75 }
76
77 function ProtocolBase() {
78     this.wrappedJSObject = this;
79
80     this.pages = {};
81     this.providers = {
82         "content": function (uri, path) this.pages[path] || this.contentBase + path,
83
84         "data": function (uri) {
85             var channel = services.io.newChannel(uri.path.replace(/^\/(.*)(?:#.*)?/, "data:$1"),
86                                                  null, null);
87
88             channel.contentCharset = "UTF-8";
89             channel.owner = systemPrincipal;
90             channel.originalURI = uri;
91             return channel;
92         }
93     };
94 }
95 ProtocolBase.prototype = {
96     get contractID()        services.PROTOCOL + this.scheme,
97     get classDescription()  this.scheme + " utility protocol",
98     QueryInterface:         XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
99
100     purge: function purge() {
101         for (let doc in util.iterDocuments())
102             try {
103                 if (doc.documentURIObject.scheme == this.scheme)
104                     doc.defaultView.close();
105             }
106             catch (e) {
107                 util.reportError(e);
108             }
109     },
110
111     defaultPort: -1,
112     allowPort: function (port, scheme) false,
113     protocolFlags: 0
114          | Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE
115          | Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE,
116
117     newURI: function newURI(spec, charset, baseURI) {
118         if (baseURI && (!(baseURI instanceof Ci.nsIURL) || baseURI.host === "data"))
119             baseURI = null;
120         return services.URL(services.URL.URLTYPE_AUTHORITY,
121                             this.defaultPort, spec, charset, baseURI);
122     },
123
124     newChannel: function newChannel(uri) {
125         try {
126             uri.QueryInterface(Ci.nsIURL);
127
128             let path = decodeURIComponent(uri.filePath.substr(1));
129             if (uri.host in this.providers)
130                 return Channel(this.providers[uri.host].call(this, uri, path),
131                                uri);
132
133             return NetError(uri);
134         }
135         catch (e) {
136             util.reportError(e);
137             throw e;
138         }
139     }
140 };
141
142 function LocaleChannel(pkg, locale, path, orig) {
143     for each (let locale in [locale, "en-US"])
144         for each (let sep in "-/") {
145             var channel = Channel(["resource:/", pkg + sep + locale, path].join("/"), orig, true, true);
146             if (channel)
147                 return channel;
148         }
149
150     return NetError(orig);
151 }
152
153 function StringChannel(data, contentType, uri) {
154     let channel = services.StreamChannel(uri);
155     channel.contentStream = services.CharsetConv("UTF-8").convertToInputStream(data);
156     if (contentType)
157         channel.contentType = contentType;
158     channel.contentCharset = "UTF-8";
159     channel.owner = systemPrincipal;
160     if (uri)
161         channel.originalURI = uri;
162     return channel;
163 }
164
165 function XMLChannel(uri, contentType, noErrorChannel, unprivileged) {
166     try {
167         var channel = services.io.newChannelFromURI(uri);
168         var channelStream = channel.open();
169     }
170     catch (e) {
171         this.channel = noErrorChannel ? null : NetError(uri);
172         return;
173     }
174
175     this.uri = uri;
176     this.sourceChannel = services.io.newChannelFromURI(uri);
177     this.pipe = services.Pipe(true, true, 0, 0, null);
178     this.writes = [];
179
180     this.channel = services.StreamChannel(uri);
181     this.channel.contentStream = this.pipe.inputStream;
182     this.channel.contentType = contentType || channel.contentType;
183     this.channel.contentCharset = "UTF-8";
184     if (!unprivileged)
185         this.channel.owner = systemPrincipal;
186
187     let type = this.channel.contentType;
188     if (/^text\/|[\/+]xml$/.test(type)) {
189         let stream = services.InputStream(channelStream);
190         let [, pre, doctype, url, extra, open, post] = util.regexp(<![CDATA[
191                 ^ ([^]*?)
192                 (?:
193                     (<!DOCTYPE \s+ \S+ \s+) (?:SYSTEM \s+ "([^"]*)" | ((?:[^[>\s]|\s[^[])*))
194                     (\s+ \[)?
195                     ([^]*)
196                 )?
197                 $
198             ]]>, "x").exec(stream.read(4096));
199         this.writes.push(pre);
200         if (doctype) {
201             this.writes.push(doctype + (extra || "") + " [\n");
202             if (url)
203                 this.addChannel(url);
204
205             if (!open)
206                 this.writes.push("\n]");
207
208             for (let [, pre, url] in util.regexp.iterate(/([^]*?)(?:%include\s+"([^"]*)";|$)/gy, post)) {
209                 this.writes.push(pre);
210                 if (url)
211                     this.addChannel(url);
212             }
213         }
214     }
215     this.writes.push(channelStream);
216
217     this.writeNext();
218 }
219 XMLChannel.prototype = {
220     QueryInterface:   XPCOMUtils.generateQI([Ci.nsIRequestObserver]),
221
222     addChannel: function addChannel(url) {
223         try {
224             this.writes.push(services.io.newChannel(url, null, this.uri).open());
225         }
226         catch (e) {
227             util.reportError(e);
228         }
229     },
230
231     writeNext: function () {
232         try {
233             if (!this.writes.length)
234                 this.pipe.outputStream.close();
235             else {
236                 let stream = this.writes.shift();
237                 if (isString(stream))
238                     stream = services.StringStream(stream);
239
240                 services.StreamCopier(stream, this.pipe.outputStream, null,
241                                       false, true, 4096, true, false)
242                         .asyncCopy(this, null);
243             }
244         }
245         catch (e) {
246             util.reportError(e);
247         }
248     },
249
250     onStartRequest: function (request, context) {},
251     onStopRequest: function (request, context, statusCode) {
252         this.writeNext();
253     }
254 };
255
256 endModule();
257
258 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: