netwerk/test/unit/test_resumable_channel.js

changeset 1
ca08bd8f51b2
equal deleted inserted replaced
-1:000000000000 0:acf3b7e28fd3
1 /* Tests various aspects of nsIResumableChannel in combination with HTTP */
2
3 Cu.import("resource://testing-common/httpd.js");
4
5 XPCOMUtils.defineLazyGetter(this, "URL", function() {
6 return "http://localhost:" + httpserver.identity.primaryPort;
7 });
8
9 var httpserver = null;
10
11 const NS_ERROR_ENTITY_CHANGED = 0x804b0020;
12 const NS_ERROR_NOT_RESUMABLE = 0x804b0019;
13
14 const rangeBody = "Body of the range request handler.\r\n";
15
16 function make_channel(url, callback, ctx) {
17 var ios = Cc["@mozilla.org/network/io-service;1"].
18 getService(Ci.nsIIOService);
19 return ios.newChannel(url, "", null);
20 }
21
22 function AuthPrompt2() {
23 }
24
25 AuthPrompt2.prototype = {
26 user: "guest",
27 pass: "guest",
28
29 QueryInterface: function authprompt2_qi(iid) {
30 if (iid.equals(Components.interfaces.nsISupports) ||
31 iid.equals(Components.interfaces.nsIAuthPrompt2))
32 return this;
33 throw Components.results.NS_ERROR_NO_INTERFACE;
34 },
35
36 promptAuth:
37 function ap2_promptAuth(channel, level, authInfo)
38 {
39 authInfo.username = this.user;
40 authInfo.password = this.pass;
41 return true;
42 },
43
44 asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
45 throw 0x80004001;
46 }
47 };
48
49 function Requestor() {
50 }
51
52 Requestor.prototype = {
53 QueryInterface: function requestor_qi(iid) {
54 if (iid.equals(Components.interfaces.nsISupports) ||
55 iid.equals(Components.interfaces.nsIInterfaceRequestor))
56 return this;
57 throw Components.results.NS_ERROR_NO_INTERFACE;
58 },
59
60 getInterface: function requestor_gi(iid) {
61 if (iid.equals(Components.interfaces.nsIAuthPrompt2)) {
62 // Allow the prompt to store state by caching it here
63 if (!this.prompt2)
64 this.prompt2 = new AuthPrompt2();
65 return this.prompt2;
66 }
67
68 throw Components.results.NS_ERROR_NO_INTERFACE;
69 },
70
71 prompt2: null
72 };
73
74 function run_test() {
75 dump("*** run_test\n");
76 httpserver = new HttpServer();
77 httpserver.registerPathHandler("/auth", authHandler);
78 httpserver.registerPathHandler("/range", rangeHandler);
79 httpserver.registerPathHandler("/acceptranges", acceptRangesHandler);
80 httpserver.registerPathHandler("/redir", redirHandler);
81
82 var entityID;
83
84 function get_entity_id(request, data, ctx) {
85 dump("*** get_entity_id()\n");
86 do_check_true(request instanceof Ci.nsIResumableChannel,
87 "must be a resumable channel");
88 entityID = request.entityID;
89 dump("*** entity id = " + entityID + "\n");
90
91 // Try a non-resumable URL (responds with 200)
92 var chan = make_channel(URL);
93 chan.nsIResumableChannel.resumeAt(1, entityID);
94 chan.asyncOpen(new ChannelListener(try_resume, null, CL_EXPECT_FAILURE), null);
95 }
96
97 function try_resume(request, data, ctx) {
98 dump("*** try_resume()\n");
99 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
100
101 // Try a successful resume
102 var chan = make_channel(URL + "/range");
103 chan.nsIResumableChannel.resumeAt(1, entityID);
104 chan.asyncOpen(new ChannelListener(try_resume_zero, null), null);
105 }
106
107 function try_resume_zero(request, data, ctx) {
108 dump("*** try_resume_zero()\n");
109 do_check_true(request.nsIHttpChannel.requestSucceeded);
110 do_check_eq(data, rangeBody.substring(1));
111
112 // Try a server which doesn't support range requests
113 var chan = make_channel(URL + "/acceptranges");
114 chan.nsIResumableChannel.resumeAt(0, entityID);
115 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "none", false);
116 chan.asyncOpen(new ChannelListener(try_no_range, null, CL_EXPECT_FAILURE), null);
117 }
118
119 function try_no_range(request, data, ctx) {
120 dump("*** try_no_range()\n");
121 do_check_true(request.nsIHttpChannel.requestSucceeded);
122 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
123
124 // Try a server which supports "bytes" range requests
125 var chan = make_channel(URL + "/acceptranges");
126 chan.nsIResumableChannel.resumeAt(0, entityID);
127 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytes", false);
128 chan.asyncOpen(new ChannelListener(try_bytes_range, null), null);
129 }
130
131 function try_bytes_range(request, data, ctx) {
132 dump("*** try_bytes_range()\n");
133 do_check_true(request.nsIHttpChannel.requestSucceeded);
134 do_check_eq(data, rangeBody);
135
136 // Try a server which supports "foo" and "bar" range requests
137 var chan = make_channel(URL + "/acceptranges");
138 chan.nsIResumableChannel.resumeAt(0, entityID);
139 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foo, bar", false);
140 chan.asyncOpen(new ChannelListener(try_foo_bar_range, null, CL_EXPECT_FAILURE), null);
141 }
142
143 function try_foo_bar_range(request, data, ctx) {
144 dump("*** try_foo_bar_range()\n");
145 do_check_true(request.nsIHttpChannel.requestSucceeded);
146 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
147
148 // Try a server which supports "foobar" range requests
149 var chan = make_channel(URL + "/acceptranges");
150 chan.nsIResumableChannel.resumeAt(0, entityID);
151 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "foobar", false);
152 chan.asyncOpen(new ChannelListener(try_foobar_range, null, CL_EXPECT_FAILURE), null);
153 }
154
155 function try_foobar_range(request, data, ctx) {
156 dump("*** try_foobar_range()\n");
157 do_check_true(request.nsIHttpChannel.requestSucceeded);
158 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
159
160 // Try a server which supports "bytes" and "foobar" range requests
161 var chan = make_channel(URL + "/acceptranges");
162 chan.nsIResumableChannel.resumeAt(0, entityID);
163 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytes, foobar", false);
164 chan.asyncOpen(new ChannelListener(try_bytes_foobar_range, null), null);
165 }
166
167 function try_bytes_foobar_range(request, data, ctx) {
168 dump("*** try_bytes_foobar_range()\n");
169 do_check_true(request.nsIHttpChannel.requestSucceeded);
170 do_check_eq(data, rangeBody);
171
172 // Try a server which supports "bytesfoo" and "bar" range requests
173 var chan = make_channel(URL + "/acceptranges");
174 chan.nsIResumableChannel.resumeAt(0, entityID);
175 chan.nsIHttpChannel.setRequestHeader("X-Range-Type", "bytesfoo, bar", false);
176 chan.asyncOpen(new ChannelListener(try_bytesfoo_bar_range, null, CL_EXPECT_FAILURE), null);
177 }
178
179 function try_bytesfoo_bar_range(request, data, ctx) {
180 dump("*** try_bytesfoo_bar_range()\n");
181 do_check_true(request.nsIHttpChannel.requestSucceeded);
182 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
183
184 // Try a server which doesn't send Accept-Ranges header at all
185 var chan = make_channel(URL + "/acceptranges");
186 chan.nsIResumableChannel.resumeAt(0, entityID);
187 chan.asyncOpen(new ChannelListener(try_no_accept_ranges, null), null);
188 }
189
190 function try_no_accept_ranges(request, data, ctx) {
191 dump("*** try_no_accept_ranges()\n");
192 do_check_true(request.nsIHttpChannel.requestSucceeded);
193 do_check_eq(data, rangeBody);
194
195 // Try a successful suspend/resume from 0
196 var chan = make_channel(URL + "/range");
197 chan.nsIResumableChannel.resumeAt(0, entityID);
198 chan.asyncOpen(new ChannelListener(try_suspend_resume, null,
199 CL_SUSPEND | CL_EXPECT_3S_DELAY), null);
200 }
201
202 function try_suspend_resume(request, data, ctx) {
203 dump("*** try_suspend_resume()\n");
204 do_check_true(request.nsIHttpChannel.requestSucceeded);
205 do_check_eq(data, rangeBody);
206
207 // Try a successful resume from 0
208 var chan = make_channel(URL + "/range");
209 chan.nsIResumableChannel.resumeAt(0, entityID);
210 chan.asyncOpen(new ChannelListener(success, null), null);
211 }
212
213 function success(request, data, ctx) {
214 dump("*** success()\n");
215 do_check_true(request.nsIHttpChannel.requestSucceeded);
216 do_check_eq(data, rangeBody);
217
218
219 // Authentication (no password; working resume)
220 // (should not give us any data)
221 var chan = make_channel(URL + "/range");
222 chan.nsIResumableChannel.resumeAt(1, entityID);
223 chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
224 chan.asyncOpen(new ChannelListener(test_auth_nopw, null, CL_EXPECT_FAILURE), null);
225 }
226
227 function test_auth_nopw(request, data, ctx) {
228 dump("*** test_auth_nopw()\n");
229 do_check_false(request.nsIHttpChannel.requestSucceeded);
230 do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
231
232 // Authentication + not working resume
233 var chan = make_channel("http://guest:guest@localhost:" +
234 httpserver.identity.primaryPort + "/auth");
235 chan.nsIResumableChannel.resumeAt(1, entityID);
236 chan.notificationCallbacks = new Requestor();
237 chan.asyncOpen(new ChannelListener(test_auth, null, CL_EXPECT_FAILURE), null);
238 }
239 function test_auth(request, data, ctx) {
240 dump("*** test_auth()\n");
241 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
242 do_check_true(request.nsIHttpChannel.responseStatus < 300);
243
244 // Authentication + working resume
245 var chan = make_channel("http://guest:guest@localhost:" +
246 httpserver.identity.primaryPort + "/range");
247 chan.nsIResumableChannel.resumeAt(1, entityID);
248 chan.notificationCallbacks = new Requestor();
249 chan.nsIHttpChannel.setRequestHeader("X-Need-Auth", "true", false);
250 chan.asyncOpen(new ChannelListener(test_auth_resume, null), null);
251 }
252
253 function test_auth_resume(request, data, ctx) {
254 dump("*** test_auth_resume()\n");
255 do_check_eq(data, rangeBody.substring(1));
256 do_check_true(request.nsIHttpChannel.requestSucceeded);
257
258 // 404 page (same content length as real content)
259 var chan = make_channel(URL + "/range");
260 chan.nsIResumableChannel.resumeAt(1, entityID);
261 chan.nsIHttpChannel.setRequestHeader("X-Want-404", "true", false);
262 chan.asyncOpen(new ChannelListener(test_404, null, CL_EXPECT_FAILURE), null);
263 }
264
265 function test_404(request, data, ctx) {
266 dump("*** test_404()\n");
267 do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
268 do_check_eq(request.nsIHttpChannel.responseStatus, 404);
269
270 // 416 Requested Range Not Satisfiable
271 var chan = make_channel(URL + "/range");
272 chan.nsIResumableChannel.resumeAt(1000, entityID);
273 chan.asyncOpen(new ChannelListener(test_416, null, CL_EXPECT_FAILURE), null);
274 }
275
276 function test_416(request, data, ctx) {
277 dump("*** test_416()\n");
278 do_check_eq(request.status, NS_ERROR_ENTITY_CHANGED);
279 do_check_eq(request.nsIHttpChannel.responseStatus, 416);
280
281 // Redirect + successful resume
282 var chan = make_channel(URL + "/redir");
283 chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/range", false);
284 chan.nsIResumableChannel.resumeAt(1, entityID);
285 chan.asyncOpen(new ChannelListener(test_redir_resume, null), null);
286 }
287
288 function test_redir_resume(request, data, ctx) {
289 dump("*** test_redir_resume()\n");
290 do_check_true(request.nsIHttpChannel.requestSucceeded);
291 do_check_eq(data, rangeBody.substring(1));
292 do_check_eq(request.nsIHttpChannel.responseStatus, 206);
293
294 // Redirect + failed resume
295 var chan = make_channel(URL + "/redir");
296 chan.nsIHttpChannel.setRequestHeader("X-Redir-To", URL + "/", false);
297 chan.nsIResumableChannel.resumeAt(1, entityID);
298 chan.asyncOpen(new ChannelListener(test_redir_noresume, null, CL_EXPECT_FAILURE), null);
299 }
300
301 function test_redir_noresume(request, data, ctx) {
302 dump("*** test_redir_noresume()\n");
303 do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
304
305 httpserver.stop(do_test_finished);
306 }
307
308 httpserver.start(-1);
309 var chan = make_channel(URL + "/range");
310 chan.asyncOpen(new ChannelListener(get_entity_id, null), null);
311 do_test_pending();
312 }
313
314 // HANDLERS
315
316 function handleAuth(metadata, response) {
317 // btoa("guest:guest"), but that function is not available here
318 var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
319
320 var body;
321 if (metadata.hasHeader("Authorization") &&
322 metadata.getHeader("Authorization") == expectedHeader)
323 {
324 response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
325 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
326
327 return true;
328 }
329 else
330 {
331 // didn't know guest:guest, failure
332 response.setStatusLine(metadata.httpVersion, 401, "Unauthorized");
333 response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
334
335 return false;
336 }
337 }
338
339 // /auth
340 function authHandler(metadata, response) {
341 response.setHeader("Content-Type", "text/html", false);
342 var body = handleAuth(metadata, response) ? "success" : "failure";
343 response.bodyOutputStream.write(body, body.length);
344 }
345
346 // /range
347 function rangeHandler(metadata, response) {
348 response.setHeader("Content-Type", "text/html", false);
349
350 if (metadata.hasHeader("X-Need-Auth")) {
351 if (!handleAuth(metadata, response)) {
352 body = "auth failed";
353 response.bodyOutputStream.write(body, body.length);
354 return;
355 }
356 }
357
358 if (metadata.hasHeader("X-Want-404")) {
359 response.setStatusLine(metadata.httpVersion, 404, "Not Found");
360 body = rangeBody;
361 response.bodyOutputStream.write(body, body.length);
362 return;
363 }
364
365 var body = rangeBody;
366
367 if (metadata.hasHeader("Range")) {
368 // Syntax: bytes=[from]-[to] (we don't support multiple ranges)
369 var matches = metadata.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
370 var from = (matches[1] === undefined) ? 0 : matches[1];
371 var to = (matches[2] === undefined) ? rangeBody.length - 1 : matches[2];
372 if (from >= rangeBody.length) {
373 response.setStatusLine(metadata.httpVersion, 416, "Start pos too high");
374 response.setHeader("Content-Range", "*/" + rangeBody.length, false);
375 return;
376 }
377 body = body.substring(from, to + 1);
378 // always respond to successful range requests with 206
379 response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
380 response.setHeader("Content-Range", from + "-" + to + "/" + rangeBody.length, false);
381 }
382
383 response.bodyOutputStream.write(body, body.length);
384 }
385
386 // /acceptranges
387 function acceptRangesHandler(metadata, response) {
388 response.setHeader("Content-Type", "text/html", false);
389 if (metadata.hasHeader("X-Range-Type"))
390 response.setHeader("Accept-Ranges", metadata.getHeader("X-Range-Type"), false);
391 response.bodyOutputStream.write(rangeBody, rangeBody.length);
392 }
393
394 // /redir
395 function redirHandler(metadata, response) {
396 response.setStatusLine(metadata.httpVersion, 302, "Found");
397 response.setHeader("Content-Type", "text/html", false);
398 response.setHeader("Location", metadata.getHeader("X-Redir-To"), false);
399 var body = "redirect\r\n";
400 response.bodyOutputStream.write(body, body.length);
401 }
402
403

mercurial