1 /* Tests various aspects of nsIResumableChannel in combination with HTTP */
4 const { HttpServer } = ChromeUtils.importESModule(
5 "resource://testing-common/httpd.sys.mjs"
8 ChromeUtils.defineLazyGetter(this, "URL", function () {
9 return "http://localhost:" + httpserver.identity.primaryPort;
12 var httpserver = null;
14 const NS_ERROR_ENTITY_CHANGED = 0x804b0020;
15 const NS_ERROR_NOT_RESUMABLE = 0x804b0019;
17 const rangeBody = "Body of the range request handler.\r\n";
19 function make_channel(url, callback, ctx) {
20 return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true });
23 function AuthPrompt2() {}
25 AuthPrompt2.prototype = {
29 QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
31 promptAuth: function ap2_promptAuth(channel, level, authInfo) {
32 authInfo.username = this.user;
33 authInfo.password = this.pass;
37 asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
38 throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
42 function Requestor() {}
44 Requestor.prototype = {
45 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
47 getInterface: function requestor_gi(iid) {
48 if (iid.equals(Ci.nsIAuthPrompt2)) {
49 // Allow the prompt to store state by caching it here
51 this.prompt2 = new AuthPrompt2();
56 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
63 dump("*** run_test\n");
64 httpserver = new HttpServer();
65 httpserver.registerPathHandler("/auth", authHandler);
66 httpserver.registerPathHandler("/range", rangeHandler);
67 httpserver.registerPathHandler("/acceptranges", acceptRangesHandler);
68 httpserver.registerPathHandler("/redir", redirHandler);
72 function get_entity_id(request, data, ctx) {
73 dump("*** get_entity_id()\n");
75 request instanceof Ci.nsIResumableChannel,
76 "must be a resumable channel"
78 entityID = request.entityID;
79 dump("*** entity id = " + entityID + "\n");
81 // Try a non-resumable URL (responds with 200)
82 var chan = make_channel(URL);
83 chan.nsIResumableChannel.resumeAt(1, entityID);
84 chan.asyncOpen(new ChannelListener(try_resume, null, CL_EXPECT_FAILURE));
87 function try_resume(request, data, ctx) {
88 dump("*** try_resume()\n");
89 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
91 // Try a successful resume
92 var chan = make_channel(URL + "/range");
93 chan.nsIResumableChannel.resumeAt(1, entityID);
94 chan.asyncOpen(new ChannelListener(try_resume_zero, null));
97 function try_resume_zero(request, data, ctx) {
98 dump("*** try_resume_zero()\n");
99 Assert.ok(request.nsIHttpChannel.requestSucceeded);
100 Assert.equal(data, rangeBody.substring(1));
102 // Try a server which doesn't support range requests
103 var chan = make_channel(URL + "/acceptranges");
104 chan.nsIResumableChannel.resumeAt(0, entityID);
105 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "none", false);
106 chan.asyncOpen(new ChannelListener(try_no_range, null, CL_EXPECT_FAILURE));
109 function try_no_range(request, data, ctx) {
110 dump("*** try_no_range()\n");
111 Assert.ok(request.nsIHttpChannel.requestSucceeded);
112 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
114 // Try a server which supports "bytes" range requests
115 var chan = make_channel(URL + "/acceptranges");
116 chan.nsIResumableChannel.resumeAt(0, entityID);
117 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytes", false);
118 chan.asyncOpen(new ChannelListener(try_bytes_range, null));
121 function try_bytes_range(request, data, ctx) {
122 dump("*** try_bytes_range()\n");
123 Assert.ok(request.nsIHttpChannel.requestSucceeded);
124 Assert.equal(data, rangeBody);
126 // Try a server which supports "foo" and "bar" range requests
127 var chan = make_channel(URL + "/acceptranges");
128 chan.nsIResumableChannel.resumeAt(0, entityID);
129 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foo, bar", false);
131 new ChannelListener(try_foo_bar_range, null, CL_EXPECT_FAILURE)
135 function try_foo_bar_range(request, data, ctx) {
136 dump("*** try_foo_bar_range()\n");
137 Assert.ok(request.nsIHttpChannel.requestSucceeded);
138 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
140 // Try a server which supports "foobar" range requests
141 var chan = make_channel(URL + "/acceptranges");
142 chan.nsIResumableChannel.resumeAt(0, entityID);
143 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foobar", false);
145 new ChannelListener(try_foobar_range, null, CL_EXPECT_FAILURE)
149 function try_foobar_range(request, data, ctx) {
150 dump("*** try_foobar_range()\n");
151 Assert.ok(request.nsIHttpChannel.requestSucceeded);
152 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
154 // Try a server which supports "bytes" and "foobar" range requests
155 var chan = make_channel(URL + "/acceptranges");
156 chan.nsIResumableChannel.resumeAt(0, entityID);
157 chan.nsIHttpChannel.setRequestHeader(
162 chan.asyncOpen(new ChannelListener(try_bytes_foobar_range, null));
165 function try_bytes_foobar_range(request, data, ctx) {
166 dump("*** try_bytes_foobar_range()\n");
167 Assert.ok(request.nsIHttpChannel.requestSucceeded);
168 Assert.equal(data, rangeBody);
170 // Try a server which supports "bytesfoo" and "bar" range requests
171 var chan = make_channel(URL + "/acceptranges");
172 chan.nsIResumableChannel.resumeAt(0, entityID);
173 chan.nsIHttpChannel.setRequestHeader(
179 new ChannelListener(try_bytesfoo_bar_range, null, CL_EXPECT_FAILURE)
183 function try_bytesfoo_bar_range(request, data, ctx) {
184 dump("*** try_bytesfoo_bar_range()\n");
185 Assert.ok(request.nsIHttpChannel.requestSucceeded);
186 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
188 // Try a server which doesn't send Accept-Ranges header at all
189 var chan = make_channel(URL + "/acceptranges");
190 chan.nsIResumableChannel.resumeAt(0, entityID);
191 chan.asyncOpen(new ChannelListener(try_no_accept_ranges, null));
194 function try_no_accept_ranges(request, data, ctx) {
195 dump("*** try_no_accept_ranges()\n");
196 Assert.ok(request.nsIHttpChannel.requestSucceeded);
197 Assert.equal(data, rangeBody);
199 // Try a successful suspend/resume from 0
200 var chan = make_channel(URL + "/range");
201 chan.nsIResumableChannel.resumeAt(0, entityID);
206 CL_SUSPEND | CL_EXPECT_3S_DELAY
211 function try_suspend_resume(request, data, ctx) {
212 dump("*** try_suspend_resume()\n");
213 Assert.ok(request.nsIHttpChannel.requestSucceeded);
214 Assert.equal(data, rangeBody);
216 // Try a successful resume from 0
217 var chan = make_channel(URL + "/range");
218 chan.nsIResumableChannel.resumeAt(0, entityID);
219 chan.asyncOpen(new ChannelListener(success, null));
222 function success(request, data, ctx) {
223 dump("*** success()\n");
224 Assert.ok(request.nsIHttpChannel.requestSucceeded);
225 Assert.equal(data, rangeBody);
227 // Authentication (no password; working resume)
228 // (should not give us any data)
229 var chan = make_channel(URL + "/range");
230 chan.nsIResumableChannel.resumeAt(1, entityID);
231 chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
233 new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE)
237 function test_auth_nopw(request, data, ctx) {
238 dump("*** test_auth_nopw()\n");
239 Assert.ok(!request.nsIHttpChannel.requestSucceeded);
240 Assert.equal(request.status, NS_ERROR_ENTITY_CHANGED);
242 // Authentication + not working resume
243 var chan = make_channel(
244 "http://guest:guest@localhost:" +
245 httpserver.identity.primaryPort +
248 chan.nsIResumableChannel.resumeAt(1, entityID);
249 chan.notificationCallbacks = new Requestor();
250 chan.asyncOpen(new ChannelListener(test_auth, null, CL_EXPECT_FAILURE));
252 function test_auth(request, data, ctx) {
253 dump("*** test_auth()\n");
254 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
255 Assert.ok(request.nsIHttpChannel.responseStatus < 300);
257 // Authentication + working resume
258 var chan = make_channel(
259 "http://guest:guest@localhost:" +
260 httpserver.identity.primaryPort +
263 chan.nsIResumableChannel.resumeAt(1, entityID);
264 chan.notificationCallbacks = new Requestor();
265 chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
266 chan.asyncOpen(new ChannelListener(test_auth_resume, null));
269 function test_auth_resume(request, data, ctx) {
270 dump("*** test_auth_resume()\n");
271 Assert.equal(data, rangeBody.substring(1));
272 Assert.ok(request.nsIHttpChannel.requestSucceeded);
274 // 404 page (same content length as real content)
275 var chan = make_channel(URL + "/range");
276 chan.nsIResumableChannel.resumeAt(1, entityID);
277 chan.nsIHttpChannel.setRequestHeader("X-Want-404", "true", false);
278 chan.asyncOpen(new ChannelListener(test_404, null, CL_EXPECT_FAILURE));
281 function test_404(request, data, ctx) {
282 dump("*** test_404()\n");
283 Assert.equal(request.status, NS_ERROR_ENTITY_CHANGED);
284 Assert.equal(request.nsIHttpChannel.responseStatus, 404);
286 // 416 Requested Range Not Satisfiable
287 var chan = make_channel(URL + "/range");
288 chan.nsIResumableChannel.resumeAt(1000, entityID);
289 chan.asyncOpen(new ChannelListener(test_416, null, CL_EXPECT_FAILURE));
292 function test_416(request, data, ctx) {
293 dump("*** test_416()\n");
294 Assert.equal(request.status, NS_ERROR_ENTITY_CHANGED);
295 Assert.equal(request.nsIHttpChannel.responseStatus, 416);
297 // Redirect + successful resume
298 var chan = make_channel(URL + "/redir");
299 chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/range", false);
300 chan.nsIResumableChannel.resumeAt(1, entityID);
301 chan.asyncOpen(new ChannelListener(test_redir_resume, null));
304 function test_redir_resume(request, data, ctx) {
305 dump("*** test_redir_resume()\n");
306 Assert.ok(request.nsIHttpChannel.requestSucceeded);
307 Assert.equal(data, rangeBody.substring(1));
308 Assert.equal(request.nsIHttpChannel.responseStatus, 206);
310 // Redirect + failed resume
311 var chan = make_channel(URL + "/redir");
312 chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/", false);
313 chan.nsIResumableChannel.resumeAt(1, entityID);
315 new ChannelListener(test_redir_noresume, null, CL_EXPECT_FAILURE)
319 function test_redir_noresume(request, data, ctx) {
320 dump("*** test_redir_noresume()\n");
321 Assert.equal(request.status, NS_ERROR_NOT_RESUMABLE);
323 httpserver.stop(do_test_finished);
326 httpserver.start(-1);
327 var chan = make_channel(URL + "/range");
328 chan.asyncOpen(new ChannelListener(get_entity_id, null));
334 function handleAuth(metadata, response) {
335 // btoa("guest:guest"), but that function is not available here
336 var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
339 metadata.hasHeader("Authorization") &&
340 metadata.getHeader("Authorization") == expectedHeader
342 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
343 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
347 // didn't know guest:guest, failure
348 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
349 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
354 function authHandler(metadata, response) {
355 response.setHeader("Content-Type", "text/html", false);
356 var body = handleAuth(metadata, response) ? "success" : "failure";
357 response.bodyOutputStream.write(body, body.length);
361 function rangeHandler(metadata, response) {
362 response.setHeader("Content-Type", "text/html", false);
364 if (metadata.hasHeader("X-Need-Auth")) {
365 if (!handleAuth(metadata, response)) {
366 body = "auth failed";
367 response.bodyOutputStream.write(body, body.length);
372 if (metadata.hasHeader("X-Want-404")) {
373 response.setStatusLine(metadata.httpVersion, 404, "Not Found");
375 response.bodyOutputStream.write(body, body.length);
379 var body = rangeBody;
381 if (metadata.hasHeader("Range")) {
382 // Syntax: bytes=[from]-[to] (we don't support multiple ranges)
383 var matches = metadata
385 .match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
386 var from = matches[1] === undefined ? 0 : matches[1];
387 var to = matches[2] === undefined ? rangeBody.length - 1 : matches[2];
388 if (from >= rangeBody.length) {
389 response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
390 response.setHeader("Content-Range", "*/" + rangeBody.length, false);
393 body = body.substring(from, to + 1);
394 // always respond to successful range requests with 206
395 response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
398 from + "-" + to + "/" + rangeBody.length,
403 response.bodyOutputStream.write(body, body.length);
407 function acceptRangesHandler(metadata, response) {
408 response.setHeader("Content-Type", "text/html", false);
409 if (metadata.hasHeader("X-Range-Type")) {
412 metadata.getHeader("X-Range-Type"),
416 response.bodyOutputStream.write(rangeBody, rangeBody.length);
420 function redirHandler(metadata, response) {
421 response.setStatusLine(metadata.httpVersion, 302, "Found");
422 response.setHeader("Content-Type", "text/html", false);
423 response.setHeader("Location", metadata.getHeader("X-Redir-To"), false);
424 var body = "redirect\r\n";
425 response.bodyOutputStream.write(body, body.length);