netwerk/test/unit/test_resumable_channel.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

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

mercurial