-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathprotocol.js
More file actions
217 lines (199 loc) · 7.78 KB
/
protocol.js
File metadata and controls
217 lines (199 loc) · 7.78 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
'use strict'
const { Cc, Ci, Cu, Cm } = require("chrome")
, { MatchPattern } = require('match-pattern')
, { Trait } = require("light-traits")
, xpcom = require("xpcom")
, { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm")
, IOService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService)
, uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].
getService(Ci.nsIUUIDGenerator)
, streamChannel = Cc["@mozilla.org/network/input-stream-channel;1"]
, inputStream = Cc["@mozilla.org/io/string-input-stream;1"]
, SimpleURI = Cc["@mozilla.org/network/simple-uri;1"]
, securityManager = Cc["@mozilla.org/scriptsecuritymanager;1"].
getService(Ci.nsIScriptSecurityManager)
function identity(value) value
const TXPCOM = Trait.compose(
Trait({
interfaces: Trait.required,
contractID: Trait.required,
}),
{
QueryInterface: { get: function QueryInterface() {
Object.defineProperty(this, 'QueryInterface', {
value: XPCOMUtils.generateQI(this.interfaces),
configurable: false
})
return this.QueryInterface
}, configurable: true },
classID: { get: function classID() {
Object.defineProperty(this, 'classID', {
value: uuidGenerator.generateUUID(),
configurable: false
})
return this.classID
}, configurable: true },
classDescription: { get: function classDescription() {
Object.defineProperty(this, 'classDescription', {
value: this.description || "Jetpack generated class",
configurable: false
})
return this.classDescription
}, configurable: true }
}
)
/**
* Function takes `handler` object implementing a protocol, and `uri` from
* that protocol, performs request operation on handler and returns `response`
* object that contains `channel` of the requested `uri`.
* @param {Object} handler
* Protocol handler
* @param {nsIURI|String} uri
* Requested URI
* @param {nsIURI} [baseURI]
* Base URI. Necessary when given `uri` is relative.
*/
function request(handler, uri, baseURI, charset) {
// Creating `request` and `response` objects that are passed to a `handler`'s
// `onRequest` method. Also note that `response` object inherits from
// `request`, this way `response`'s properties will fall back to the
// `request`'s same named properties.
let channel, request = {}, response = Object.create(request)
// If `baseURI` is provided then given `uri` is relative to it, there for
// we set `referer` property on request to allow protocol handler to resolve
// absolute URI.
if (baseURI) request.referer = baseURI.spec
// Stringifying `uri` to a string and setting it as property on `request`.
request.uri = request.originalURI = uri.spec || uri
handler.onRequest(request, response)
// If response contains `content` property it's not a simple redirect. In
// this case we create channel from the given content.
if (response.content) {
// Creating input stream out of the `response.content` and then creating
// `channel` with that content stream.
let stream = inputStream.createInstance(Ci.nsIStringInputStream)
let content = response.content
stream.setData(content, response.contentLength || content.length)
channel = streamChannel.createInstance(Ci.nsIInputStreamChannel)
channel.contentStream = stream
channel.QueryInterface(Ci.nsIChannel)
// If `uri.spec` is different form `response.uri` it means that either
// `request` was just redirected to a different uri from the existing
// protocol or given `uri` was string, in both cases we need to create
// `uri` that is `nsIURI` since it has to be set on the `channel`.
if (uri.spec !== response.uri) {
uri = SimpleURI.createInstance(Ci.nsIURI)
uri.spec = response.uri
}
// Setting response URI on the channel.
channel.setURI(uri)
}
// Otherwise it's a redirect to an URI from the existing protocol, in such
// case we just use `nsIIOService` to create `channel` straight out of
// `response.uri`
else {
if (response.uri !== request.uri)
uri = IOService.newURI(response.uri, null, null)
channel = IOService.newChannel(response.uri, null, null)
}
// Also setting `contentType` and `contentLength` if they were provided to the
// response.
if (response.contentType)
channel.contentType = response.contentType
if (response.contentLength)
channel.contentLength = response.contentLength
return Object.create(response, {
channel: { value: channel },
uri: { value: uri }
})
}
const THandler = Trait({
onRequest: Trait.required,
listen: function listen(options) {
let handler
if (options.about) {
handler = Object.create(this, { about: { value: options.about } })
handler = TAboutHandler.create(handler)
} else {
handler = Object.create(this, { scheme: { value: options.scheme } })
handler = TProtocolHandler.create(handler)
}
xpcom.register({
uuid: handler.classID,
name: handler.classDescription,
contractID: handler.contractID,
create: identity.bind(null, handler)
})
}
})
const TAboutHandler = Trait.compose(
TXPCOM,
Trait({
about: Trait.required,
onRequest: Trait.required,
interfaces: [ Ci.nsIAboutModule ],
get description() {
return 'Protocol handler for "about:' + this.about + '"'
},
get contractID() {
return "@mozilla.org/network/protocol/about;1?what=" + this.about
},
getURIFlags: function(uri) {
return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT
},
newChannel: function(uri) {
return request(this, uri).channel
}
})
)
const TProtocolHandler = Trait.compose(
TXPCOM,
Trait({
scheme: Trait.required,
onRequest: Trait.required,
interfaces: [ Ci.nsIProtocolHandler ],
// For more information on what these flags mean,
// see caps/src/nsScriptSecurityManager.cpp.
protocolFlags: Ci.nsIProtocolHandler.URI_IS_UI_RESOURCE |
Ci.nsIProtocolHandler.URI_STD |
Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,
defaultPort: -1,
allowPort: function allowPort(port, scheme) false,
newURI: function newURI(relativeURI, charset, baseURI) {
let response = request(this, relativeURI, baseURI, charset)
// If handler have not redirected to another protocol we know that
// `newChannel` method will be called later so we save response so that
// it will be able to use it and then delete it.
if (response.uri.scheme == this.scheme)
(this.responses || (this.responses = {}))[response.uri.spec] = response
return response.uri
},
newChannel: function newChannel(uri) {
// Taking response for this request (`newURI` saved it) from the responses
// map and removing it after, since we don't need memory leaks.
let response = this.responses[uri.spec]
let channel = response.channel
delete this.responses[uri.spec]
// If `originalURI` of the response is different from the `uri` either
// it means that handler just maps `originalURI`. In such case we set
// owner of the channel to the same principal as `originalURI` has in
// order to allow access to the other mapped resources.
if (response.originalURI !== response.uri) {
let originalURI = IOService.newURI(response.originalURI, null, null)
channel.originalURI = originalURI
channel.owner = securityManager.getCodebasePrincipal(originalURI)
}
return channel
},
get contractID() {
return "@mozilla.org/network/protocol;1?name=" + this.scheme
},
get description() {
return 'Protocol handler for "' + this.scheme + ':*"'
}
})
)
exports.Handler = function Handler(options) {
return THandler.create(options)
}