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