1 /* Tests various aspects of nsIResumableChannel in combination with HTTP */
3 do_import_script("netwerk/test/httpserver/httpd.js");
7 const NS_ERROR_ENTITY_CHANGED
= 0x804b0020;
8 const NS_ERROR_NOT_RESUMABLE
= 0x804b0019;
10 const rangeBody
= "Body of the range request handler.\r\n";
12 function make_channel(url
, callback
, ctx
) {
13 var ios
= Cc
["@mozilla.org/network/io-service;1"].
14 getService(Ci
.nsIIOService
);
15 return ios
.newChannel(url
, "", null);
18 function AuthPrompt2() {
21 AuthPrompt2
.prototype = {
25 QueryInterface
: function authprompt2_qi(iid
) {
26 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
27 iid
.equals(Components
.interfaces
.nsIAuthPrompt2
))
29 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
33 function ap2_promptAuth(channel
, level
, authInfo
)
35 authInfo
.username
= this.user
;
36 authInfo
.password
= this.pass
;
40 asyncPromptAuth
: function ap2_async(chan
, cb
, ctx
, lvl
, info
) {
41 do_throw("not implemented yet")
45 function Requestor() {
48 Requestor
.prototype = {
49 QueryInterface
: function requestor_qi(iid
) {
50 if (iid
.equals(Components
.interfaces
.nsISupports
) ||
51 iid
.equals(Components
.interfaces
.nsIInterfaceRequestor
))
53 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
56 getInterface
: function requestor_gi(iid
) {
57 if (iid
.equals(Components
.interfaces
.nsIAuthPrompt2
)) {
58 // Allow the prompt to store state by caching it here
60 this.prompt2
= new AuthPrompt2();
64 throw Components
.results
.NS_ERROR_NO_INTERFACE
;
71 dump("*** run_test\n");
72 httpserver
= new nsHttpServer();
73 httpserver
.registerPathHandler("/auth", authHandler
);
74 httpserver
.registerPathHandler("/range", rangeHandler
);
75 httpserver
.registerPathHandler("/redir", redirHandler
);
79 function get_entity_id(request
, data
, ctx
) {
80 do_check_true(request
instanceof Ci
.nsIResumableChannel
,
81 "must be a resumable channel");
82 entityID
= request
.entityID
;
83 dump("*** entity id = " + entityID
+ "\n");
85 // Try a non-resumable URL (responds with 200)
86 var chan
= make_channel("http://localhost:4444/");
87 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
88 chan
.asyncOpen(new ChannelListener(try_resume
, null, CL_EXPECT_FAILURE
), null);
91 function try_resume(request
, data
, ctx
) {
92 do_check_eq(request
.status
, NS_ERROR_NOT_RESUMABLE
);
94 // Try a successful resume
95 var chan
= make_channel("http://localhost:4444/range");
96 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
97 chan
.asyncOpen(new ChannelListener(try_resume_zero
, null), null);
100 function try_resume_zero(request
, data
, ctx
) {
101 do_check_true(request
.nsIHttpChannel
.requestSucceeded
);
102 do_check_eq(data
, rangeBody
.substring(1));
104 // Try a successful resume from 0
105 var chan
= make_channel("http://localhost:4444/range");
106 chan
.nsIResumableChannel
.resumeAt(0, entityID
);
107 chan
.asyncOpen(new ChannelListener(success
, null), null);
110 function success(request
, data
, ctx
) {
111 do_check_true(request
.nsIHttpChannel
.requestSucceeded
);
112 do_check_eq(data
, rangeBody
);
114 // Authentication (no password; working resume)
115 // (should not give us any data)
116 var chan
= make_channel("http://localhost:4444/range");
117 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
118 chan
.nsIHttpChannel
.setRequestHeader("X-Need-Auth", "true", false);
119 chan
.asyncOpen(new ChannelListener(test_auth_nopw
, null, CL_EXPECT_FAILURE
), null);
122 function test_auth_nopw(request
, data
, ctx
) {
123 do_check_false(request
.nsIHttpChannel
.requestSucceeded
);
124 do_check_eq(request
.status
, NS_ERROR_ENTITY_CHANGED
);
126 // Authentication + not working resume
127 var chan
= make_channel("http://localhost:4444/auth");
128 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
129 chan
.notificationCallbacks
= new Requestor();
130 chan
.asyncOpen(new ChannelListener(test_auth
, null, CL_EXPECT_FAILURE
), null);
132 function test_auth(request
, data
, ctx
) {
133 do_check_eq(request
.status
, NS_ERROR_NOT_RESUMABLE
);
134 do_check_true(request
.nsIHttpChannel
.responseStatus
< 300);
136 // Authentication + working resume
137 var chan
= make_channel("http://localhost:4444/range");
138 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
139 chan
.notificationCallbacks
= new Requestor();
140 chan
.nsIHttpChannel
.setRequestHeader("X-Need-Auth", "true", false);
141 chan
.asyncOpen(new ChannelListener(test_auth_resume
, null), null);
144 function test_auth_resume(request
, data
, ctx
) {
145 do_check_eq(data
, rangeBody
.substring(1));
146 do_check_true(request
.nsIHttpChannel
.requestSucceeded
);
148 // 404 page (same content length as real content)
149 var chan
= make_channel("http://localhost:4444/range");
150 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
151 chan
.nsIHttpChannel
.setRequestHeader("X-Want-404", "true", false);
152 chan
.asyncOpen(new ChannelListener(test_404
, null, CL_EXPECT_FAILURE
), null);
155 function test_404(request
, data
, ctx
) {
156 do_check_eq(request
.status
, NS_ERROR_ENTITY_CHANGED
);
157 do_check_eq(request
.nsIHttpChannel
.responseStatus
, 404);
159 // 416 Requested Range Not Satisfiable
160 var chan
= make_channel("http://localhost:4444/range");
161 chan
.nsIResumableChannel
.resumeAt(1000, entityID
);
162 chan
.asyncOpen(new ChannelListener(test_416
, null, CL_EXPECT_FAILURE
), null);
165 function test_416(request
, data
, ctx
) {
166 do_check_eq(request
.status
, NS_ERROR_ENTITY_CHANGED
);
167 do_check_eq(request
.nsIHttpChannel
.responseStatus
, 416);
169 // Redirect + successful resume
170 var chan
= make_channel("http://localhost:4444/redir");
171 chan
.nsIHttpChannel
.setRequestHeader("X-Redir-To", "http://localhost:4444/range", false);
172 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
173 chan
.asyncOpen(new ChannelListener(test_redir_resume
, null), null);
176 function test_redir_resume(request
, data
, ctx
) {
177 do_check_true(request
.nsIHttpChannel
.requestSucceeded
);
178 do_check_eq(data
, rangeBody
.substring(1));
179 do_check_eq(request
.nsIHttpChannel
.responseStatus
, 206);
181 // Redirect + failed resume
182 var chan
= make_channel("http://localhost:4444/redir");
183 chan
.nsIHttpChannel
.setRequestHeader("X-Redir-To", "http://localhost:4444/", false);
184 chan
.nsIResumableChannel
.resumeAt(1, entityID
);
185 chan
.asyncOpen(new ChannelListener(test_redir_noresume
, null, CL_EXPECT_FAILURE
), null);
188 function test_redir_noresume(request
, data
, ctx
) {
189 do_check_eq(request
.status
, NS_ERROR_NOT_RESUMABLE
);
195 httpserver
.start(4444);
196 var chan
= make_channel("http://localhost:4444/range");
197 chan
.asyncOpen(new ChannelListener(get_entity_id
, null), null);
203 function handleAuth(metadata
, response
) {
204 // btoa("guest:guest"), but that function is not available here
205 var expectedHeader
= "Basic Z3Vlc3Q6Z3Vlc3Q=";
208 if (metadata
.hasHeader("Authorization") &&
209 metadata
.getHeader("Authorization") == expectedHeader
)
211 response
.setStatusLine(metadata
.httpVersion
, 200, "OK, authorized");
212 response
.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
218 // didn't know guest:guest, failure
219 response
.setStatusLine(metadata
.httpVersion
, 401, "Unauthorized");
220 response
.setHeader("WWW-Authenticate", 'Basic realm="secret"', false);
227 function authHandler(metadata
, response
) {
228 response
.setHeader("Content-Type", "text/html", false);
229 body
= handleAuth(metadata
, response
) ? "success" : "failure";
230 response
.bodyOutputStream
.write(body
, body
.length
);
234 function rangeHandler(metadata
, response
) {
235 response
.setHeader("Content-Type", "text/html", false);
237 if (metadata
.hasHeader("X-Need-Auth")) {
238 if (!handleAuth(metadata
, response
)) {
239 body
= "auth failed";
240 response
.bodyOutputStream
.write(body
, body
.length
);
245 if (metadata
.hasHeader("X-Want-404")) {
246 response
.setStatusLine(metadata
.httpVersion
, 404, "Not Found");
248 response
.bodyOutputStream
.write(body
, body
.length
);
252 var body
= rangeBody
;
254 if (metadata
.hasHeader("Range")) {
255 // Syntax: bytes=[from]-[to] (we don't support multiple ranges)
256 var matches
= metadata
.getHeader("Range").match(/^\s*bytes=(\d+)?-(\d+)?\s*$/);
257 var from = (matches
[1] === undefined) ? 0 : matches
[1];
258 var to
= (matches
[2] === undefined) ? rangeBody
.length
- 1 : matches
[2];
259 if (from >= rangeBody
.length
) {
260 response
.setStatusLine(metadata
.httpVersion
, 416, "Start pos too high");
261 response
.setHeader("Content-Range", "*/" + rangeBody
.length
, false);
264 body
= body
.substring(from, to
+ 1);
265 // always respond to successful range requests with 206
266 response
.setStatusLine(metadata
.httpVersion
, 206, "Partial Content");
267 response
.setHeader("Content-Range", from + "-" + to
+ "/" + rangeBody
.length
, false);
270 response
.bodyOutputStream
.write(body
, body
.length
);
274 function redirHandler(metadata
, response
) {
275 response
.setStatusLine(metadata
.httpVersion
, 302, "Found");
276 response
.setHeader("Content-Type", "text/html", false);
277 response
.setHeader("Location", metadata
.getHeader("X-Redir-To"), false);
278 var body
= "redirect\r\n";
279 response
.bodyOutputStream
.write(body
, body
.length
);