netwerk/test/unit/test_resumable_channel.js

Wed, 31 Dec 2014 06:55:46 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:46 +0100
changeset 1
ca08bd8f51b2
permissions
-rw-r--r--

Added tag TORBROWSER_REPLICA for changeset 6474c204b198

     1 /* Tests various aspects of nsIResumableChannel in combination with HTTP */
     3 Cu.import("resource://testing-common/httpd.js");
     5 XPCOMUtils.defineLazyGetter(this, "URL", function() {
     6   return "http://localhost:" + httpserver.identity.primaryPort;
     7 });
     9 var httpserver = null;
    11 const NS_ERROR_ENTITY_CHANGED = 0x804b0020;
    12 const NS_ERROR_NOT_RESUMABLE = 0x804b0019;
    14 const rangeBody = "Body of the range request handler.\r\n";
    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 }
    22 function AuthPrompt2() {
    23 }
    25 AuthPrompt2.prototype = {
    26   user: "guest",
    27   pass: "guest",
    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   },
    36   promptAuth:
    37     function ap2_promptAuth(channel, level, authInfo)
    38   {
    39     authInfo.username = this.user;
    40     authInfo.password = this.pass;
    41     return true;
    42   },
    44   asyncPromptAuth: function ap2_async(chan, cb, ctx, lvl, info) {
    45     throw 0x80004001;
    46   }
    47 };
    49 function Requestor() {
    50 }
    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   },
    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     }
    68     throw Components.results.NS_ERROR_NO_INTERFACE;
    69   },
    71   prompt2: null
    72 };
    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);
    82   var entityID;
    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");
    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   }
    97   function try_resume(request, data, ctx) {
    98     dump("*** try_resume()\n");
    99     do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
   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   }
   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));
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   213   function success(request, data, ctx) {
   214     dump("*** success()\n");
   215     do_check_true(request.nsIHttpChannel.requestSucceeded);
   216     do_check_eq(data, rangeBody);
   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   }
   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);
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   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);
   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   }
   301   function test_redir_noresume(request, data, ctx) {
   302     dump("*** test_redir_noresume()\n");
   303     do_check_eq(request.status, NS_ERROR_NOT_RESUMABLE);
   305     httpserver.stop(do_test_finished);
   306   }
   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 }
   314 // HANDLERS
   316 function handleAuth(metadata, response) {
   317   // btoa("guest:guest"), but that function is not available here
   318   var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q=";
   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);
   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);
   335     return false;
   336   }
   337 }
   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 }
   346 // /range
   347 function rangeHandler(metadata, response) {
   348   response.setHeader("Content-Type", "text/html", false);
   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   }
   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   }
   365   var body = rangeBody;
   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   }
   383   response.bodyOutputStream.write(body, body.length);
   384 }
   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 }
   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 }

mercurial