]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/protocol.jsm
Import 1.0rc1 supporting Firefox up to 11.*
[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);
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 stream = services.InputStream(channelStream);
188     let [, pre, doctype, url, extra, open, post] = util.regexp(<![CDATA[
189             ^ ([^]*?)
190             (?:
191                 (<!DOCTYPE \s+ \S+ \s+) (?:SYSTEM \s+ "([^"]*)" | ((?:[^[>\s]|\s[^[])*))
192                 (\s+ \[)?
193                 ([^]*)
194             )?
195             $
196         ]]>, "x").exec(stream.read(4096));
197     this.writes.push(pre);
198     if (doctype) {
199         this.writes.push(doctype + (extra || "") + " [\n");
200         if (url)
201             this.addChannel(url);
202
203         if (!open)
204             this.writes.push("\n]");
205
206         for (let [, pre, url] in util.regexp.iterate(/([^]*?)(?:%include\s+"([^"]*)";|$)/gy, post)) {
207             this.writes.push(pre);
208             if (url)
209                 this.addChannel(url);
210         }
211     }
212     this.writes.push(channelStream);
213
214     this.writeNext();
215 }
216 XMLChannel.prototype = {
217     QueryInterface:   XPCOMUtils.generateQI([Ci.nsIRequestObserver]),
218
219     addChannel: function addChannel(url) {
220         try {
221             this.writes.push(services.io.newChannel(url, null, this.uri).open());
222         }
223         catch (e) {
224             util.reportError(e);
225         }
226     },
227
228     writeNext: function () {
229         try {
230             if (!this.writes.length)
231                 this.pipe.outputStream.close();
232             else {
233                 let stream = this.writes.shift();
234                 if (isString(stream))
235                     stream = services.StringStream(stream);
236
237                 services.StreamCopier(stream, this.pipe.outputStream, null,
238                                       false, true, 4096, true, false)
239                         .asyncCopy(this, null);
240             }
241         }
242         catch (e) {
243             util.reportError(e);
244         }
245     },
246
247     onStartRequest: function (request, context) {},
248     onStopRequest: function (request, context, statusCode) {
249         this.writeNext();
250     }
251 };
252
253 endModule();
254
255 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: