|
1 // test spdy/3 |
|
2 |
|
3 var Ci = Components.interfaces; |
|
4 var Cc = Components.classes; |
|
5 |
|
6 // Generate a small and a large post with known pre-calculated md5 sums |
|
7 function generateContent(size) { |
|
8 var content = ""; |
|
9 for (var i = 0; i < size; i++) { |
|
10 content += "0"; |
|
11 } |
|
12 return content; |
|
13 } |
|
14 |
|
15 var posts = []; |
|
16 posts.push(generateContent(10)); |
|
17 posts.push(generateContent(250000)); |
|
18 |
|
19 // pre-calculated md5sums (in hex) of the above posts |
|
20 var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', |
|
21 '2ef8d3b6c8f329318eb1a119b12622b6']; |
|
22 |
|
23 var bigListenerData = generateContent(128 * 1024); |
|
24 var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836'; |
|
25 |
|
26 function checkIsSpdy(request) { |
|
27 try { |
|
28 if (request.getResponseHeader("X-Firefox-Spdy") == "3") { |
|
29 if (request.getResponseHeader("X-Connection-Spdy") == "yes") { |
|
30 return true; |
|
31 } |
|
32 return false; // Weird case, but the server disagrees with us |
|
33 } |
|
34 } catch (e) { |
|
35 // Nothing to do here |
|
36 } |
|
37 return false; |
|
38 } |
|
39 |
|
40 var SpdyCheckListener = function() {}; |
|
41 |
|
42 SpdyCheckListener.prototype = { |
|
43 onStartRequestFired: false, |
|
44 onDataAvailableFired: false, |
|
45 isSpdyConnection: false, |
|
46 |
|
47 onStartRequest: function testOnStartRequest(request, ctx) { |
|
48 this.onStartRequestFired = true; |
|
49 |
|
50 if (!Components.isSuccessCode(request.status)) |
|
51 do_throw("Channel should have a success code! (" + request.status + ")"); |
|
52 if (!(request instanceof Components.interfaces.nsIHttpChannel)) |
|
53 do_throw("Expecting an HTTP channel"); |
|
54 |
|
55 do_check_eq(request.responseStatus, 200); |
|
56 do_check_eq(request.requestSucceeded, true); |
|
57 }, |
|
58 |
|
59 onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) { |
|
60 this.onDataAvailableFired = true; |
|
61 this.isSpdyConnection = checkIsSpdy(request); |
|
62 |
|
63 read_stream(stream, cnt); |
|
64 }, |
|
65 |
|
66 onStopRequest: function testOnStopRequest(request, ctx, status) { |
|
67 do_check_true(this.onStartRequestFired); |
|
68 do_check_true(this.onDataAvailableFired); |
|
69 do_check_true(this.isSpdyConnection); |
|
70 |
|
71 run_next_test(); |
|
72 do_test_finished(); |
|
73 } |
|
74 }; |
|
75 |
|
76 /* |
|
77 * Support for testing valid multiplexing of streams |
|
78 */ |
|
79 |
|
80 var multiplexContent = generateContent(30*1024); |
|
81 var completed_channels = []; |
|
82 function register_completed_channel(listener) { |
|
83 completed_channels.push(listener); |
|
84 if (completed_channels.length == 2) { |
|
85 do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID); |
|
86 run_next_test(); |
|
87 do_test_finished(); |
|
88 } |
|
89 } |
|
90 |
|
91 /* Listener class to control the testing of multiplexing */ |
|
92 var SpdyMultiplexListener = function() {}; |
|
93 |
|
94 SpdyMultiplexListener.prototype = new SpdyCheckListener(); |
|
95 |
|
96 SpdyMultiplexListener.prototype.streamID = 0; |
|
97 SpdyMultiplexListener.prototype.buffer = ""; |
|
98 |
|
99 SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
100 this.onDataAvailableFired = true; |
|
101 this.isSpdyConnection = checkIsSpdy(request); |
|
102 this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID")); |
|
103 var data = read_stream(stream, cnt); |
|
104 this.buffer = this.buffer.concat(data); |
|
105 }; |
|
106 |
|
107 SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) { |
|
108 do_check_true(this.onStartRequestFired); |
|
109 do_check_true(this.onDataAvailableFired); |
|
110 do_check_true(this.isSpdyConnection); |
|
111 do_check_true(this.buffer == multiplexContent); |
|
112 |
|
113 // This is what does most of the hard work for us |
|
114 register_completed_channel(this); |
|
115 }; |
|
116 |
|
117 // Does the appropriate checks for header gatewaying |
|
118 var SpdyHeaderListener = function(value) { |
|
119 this.value = value |
|
120 }; |
|
121 |
|
122 SpdyHeaderListener.prototype = new SpdyCheckListener(); |
|
123 SpdyHeaderListener.prototype.value = ""; |
|
124 |
|
125 SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
126 this.onDataAvailableFired = true; |
|
127 this.isSpdyConnection = checkIsSpdy(request); |
|
128 do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value); |
|
129 read_stream(stream, cnt); |
|
130 }; |
|
131 |
|
132 var SpdyPushListener = function() {}; |
|
133 |
|
134 SpdyPushListener.prototype = new SpdyCheckListener(); |
|
135 |
|
136 SpdyPushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
137 this.onDataAvailableFired = true; |
|
138 this.isSpdyConnection = checkIsSpdy(request); |
|
139 if (ctx.originalURI.spec == "https://localhost:4443/push.js" || |
|
140 ctx.originalURI.spec == "https://localhost:4443/push2.js") { |
|
141 do_check_eq(request.getResponseHeader("pushed"), "yes"); |
|
142 } |
|
143 read_stream(stream, cnt); |
|
144 }; |
|
145 |
|
146 // Does the appropriate checks for a large GET response |
|
147 var SpdyBigListener = function() {}; |
|
148 |
|
149 SpdyBigListener.prototype = new SpdyCheckListener(); |
|
150 SpdyBigListener.prototype.buffer = ""; |
|
151 |
|
152 SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
153 this.onDataAvailableFired = true; |
|
154 this.isSpdyConnection = checkIsSpdy(request); |
|
155 this.buffer = this.buffer.concat(read_stream(stream, cnt)); |
|
156 // We know the server should send us the same data as our big post will be, |
|
157 // so the md5 should be the same |
|
158 do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5")); |
|
159 }; |
|
160 |
|
161 SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) { |
|
162 do_check_true(this.onStartRequestFired); |
|
163 do_check_true(this.onDataAvailableFired); |
|
164 do_check_true(this.isSpdyConnection); |
|
165 |
|
166 // Don't want to flood output, so don't use do_check_eq |
|
167 do_check_true(this.buffer == bigListenerData); |
|
168 |
|
169 run_next_test(); |
|
170 do_test_finished(); |
|
171 }; |
|
172 |
|
173 // Does the appropriate checks for POSTs |
|
174 var SpdyPostListener = function(expected_md5) { |
|
175 this.expected_md5 = expected_md5; |
|
176 }; |
|
177 |
|
178 SpdyPostListener.prototype = new SpdyCheckListener(); |
|
179 SpdyPostListener.prototype.expected_md5 = ""; |
|
180 |
|
181 SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { |
|
182 this.onDataAvailableFired = true; |
|
183 this.isSpdyConnection = checkIsSpdy(request); |
|
184 read_stream(stream, cnt); |
|
185 do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5")); |
|
186 }; |
|
187 |
|
188 function makeChan(url) { |
|
189 var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); |
|
190 var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel); |
|
191 |
|
192 return chan; |
|
193 } |
|
194 |
|
195 // Make sure we make a spdy connection and both us and the server mark it as such |
|
196 function test_spdy_basic() { |
|
197 var chan = makeChan("https://localhost:4443/"); |
|
198 var listener = new SpdyCheckListener(); |
|
199 chan.asyncOpen(listener, null); |
|
200 } |
|
201 |
|
202 // Support for making sure XHR works over SPDY |
|
203 function checkXhr(xhr) { |
|
204 if (xhr.readyState != 4) { |
|
205 return; |
|
206 } |
|
207 |
|
208 do_check_eq(xhr.status, 200); |
|
209 do_check_eq(checkIsSpdy(xhr), true); |
|
210 run_next_test(); |
|
211 do_test_finished(); |
|
212 } |
|
213 |
|
214 // Fires off an XHR request over SPDY |
|
215 function test_spdy_xhr() { |
|
216 var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] |
|
217 .createInstance(Ci.nsIXMLHttpRequest); |
|
218 req.open("GET", "https://localhost:4443/", true); |
|
219 req.addEventListener("readystatechange", function (evt) { checkXhr(req); }, |
|
220 false); |
|
221 req.send(null); |
|
222 } |
|
223 |
|
224 // Test to make sure we get multiplexing right |
|
225 function test_spdy_multiplex() { |
|
226 var chan1 = makeChan("https://localhost:4443/multiplex1"); |
|
227 var chan2 = makeChan("https://localhost:4443/multiplex2"); |
|
228 var listener1 = new SpdyMultiplexListener(); |
|
229 var listener2 = new SpdyMultiplexListener(); |
|
230 chan1.asyncOpen(listener1, null); |
|
231 chan2.asyncOpen(listener2, null); |
|
232 } |
|
233 |
|
234 // Test to make sure we gateway non-standard headers properly |
|
235 function test_spdy_header() { |
|
236 var chan = makeChan("https://localhost:4443/header"); |
|
237 var hvalue = "Headers are fun"; |
|
238 var listener = new SpdyHeaderListener(hvalue); |
|
239 chan.setRequestHeader("X-Test-Header", hvalue, false); |
|
240 chan.asyncOpen(listener, null); |
|
241 } |
|
242 |
|
243 function test_spdy_push1() { |
|
244 var chan = makeChan("https://localhost:4443/push"); |
|
245 chan.loadGroup = loadGroup; |
|
246 var listener = new SpdyPushListener(); |
|
247 chan.asyncOpen(listener, chan); |
|
248 } |
|
249 |
|
250 function test_spdy_push2() { |
|
251 var chan = makeChan("https://localhost:4443/push.js"); |
|
252 chan.loadGroup = loadGroup; |
|
253 var listener = new SpdyPushListener(); |
|
254 chan.asyncOpen(listener, chan); |
|
255 } |
|
256 |
|
257 function test_spdy_push3() { |
|
258 var chan = makeChan("https://localhost:4443/push2"); |
|
259 chan.loadGroup = loadGroup; |
|
260 var listener = new SpdyPushListener(); |
|
261 chan.asyncOpen(listener, chan); |
|
262 } |
|
263 |
|
264 function test_spdy_push4() { |
|
265 var chan = makeChan("https://localhost:4443/push2.js"); |
|
266 chan.loadGroup = loadGroup; |
|
267 var listener = new SpdyPushListener(); |
|
268 chan.asyncOpen(listener, chan); |
|
269 } |
|
270 |
|
271 // Make sure we handle GETs that cover more than 2 frames properly |
|
272 function test_spdy_big() { |
|
273 var chan = makeChan("https://localhost:4443/big"); |
|
274 var listener = new SpdyBigListener(); |
|
275 chan.asyncOpen(listener, null); |
|
276 } |
|
277 |
|
278 // Support for doing a POST |
|
279 function do_post(content, chan, listener) { |
|
280 var stream = Cc["@mozilla.org/io/string-input-stream;1"] |
|
281 .createInstance(Ci.nsIStringInputStream); |
|
282 stream.data = content; |
|
283 |
|
284 var uchan = chan.QueryInterface(Ci.nsIUploadChannel); |
|
285 uchan.setUploadStream(stream, "text/plain", stream.available()); |
|
286 |
|
287 chan.requestMethod = "POST"; |
|
288 |
|
289 chan.asyncOpen(listener, null); |
|
290 } |
|
291 |
|
292 // Make sure we can do a simple POST |
|
293 function test_spdy_post() { |
|
294 var chan = makeChan("https://localhost:4443/post"); |
|
295 var listener = new SpdyPostListener(md5s[0]); |
|
296 do_post(posts[0], chan, listener); |
|
297 } |
|
298 |
|
299 // Make sure we can do a POST that covers more than 2 frames |
|
300 function test_spdy_post_big() { |
|
301 var chan = makeChan("https://localhost:4443/post"); |
|
302 var listener = new SpdyPostListener(md5s[1]); |
|
303 do_post(posts[1], chan, listener); |
|
304 } |
|
305 |
|
306 // hack - the header test resets the multiplex object on the server, |
|
307 // so make sure header is always run before the multiplex test. |
|
308 // |
|
309 // make sure post_big runs first to test race condition in restarting |
|
310 // a stalled stream when a SETTINGS frame arrives |
|
311 var tests = [ test_spdy_post_big |
|
312 , test_spdy_basic |
|
313 , test_spdy_push1 |
|
314 , test_spdy_push2 |
|
315 , test_spdy_push3 |
|
316 , test_spdy_push4 |
|
317 , test_spdy_xhr |
|
318 , test_spdy_header |
|
319 , test_spdy_multiplex |
|
320 , test_spdy_big |
|
321 , test_spdy_post |
|
322 ]; |
|
323 var current_test = 0; |
|
324 |
|
325 function run_next_test() { |
|
326 if (current_test < tests.length) { |
|
327 tests[current_test](); |
|
328 current_test++; |
|
329 do_test_pending(); |
|
330 } |
|
331 } |
|
332 |
|
333 // Support for making sure we can talk to the invalid cert the server presents |
|
334 var CertOverrideListener = function(host, port, bits) { |
|
335 this.host = host; |
|
336 if (port) { |
|
337 this.port = port; |
|
338 } |
|
339 this.bits = bits; |
|
340 }; |
|
341 |
|
342 CertOverrideListener.prototype = { |
|
343 host: null, |
|
344 port: -1, |
|
345 bits: null, |
|
346 |
|
347 getInterface: function(aIID) { |
|
348 return this.QueryInterface(aIID); |
|
349 }, |
|
350 |
|
351 QueryInterface: function(aIID) { |
|
352 if (aIID.equals(Ci.nsIBadCertListener2) || |
|
353 aIID.equals(Ci.nsIInterfaceRequestor) || |
|
354 aIID.equals(Ci.nsISupports)) |
|
355 return this; |
|
356 throw Components.results.NS_ERROR_NO_INTERFACE; |
|
357 }, |
|
358 |
|
359 notifyCertProblem: function(socketInfo, sslStatus, targetHost) { |
|
360 var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert; |
|
361 var cos = Cc["@mozilla.org/security/certoverride;1"]. |
|
362 getService(Ci.nsICertOverrideService); |
|
363 cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false); |
|
364 return true; |
|
365 }, |
|
366 }; |
|
367 |
|
368 function addCertOverride(host, port, bits) { |
|
369 var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] |
|
370 .createInstance(Ci.nsIXMLHttpRequest); |
|
371 try { |
|
372 var url; |
|
373 if (port) { |
|
374 url = "https://" + host + ":" + port + "/"; |
|
375 } else { |
|
376 url = "https://" + host + "/"; |
|
377 } |
|
378 req.open("GET", url, false); |
|
379 req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits); |
|
380 req.send(null); |
|
381 } catch (e) { |
|
382 // This will fail since the server is not trusted yet |
|
383 } |
|
384 } |
|
385 |
|
386 var prefs; |
|
387 var spdypref; |
|
388 var spdy3pref; |
|
389 var spdypush; |
|
390 |
|
391 var loadGroup; |
|
392 |
|
393 function resetPrefs() { |
|
394 prefs.setBoolPref("network.http.spdy.enabled", spdypref); |
|
395 prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref); |
|
396 prefs.setBoolPref("network.http.spdy.allow-push", spdypush); |
|
397 } |
|
398 |
|
399 function run_test() { |
|
400 // Set to allow the cert presented by our SPDY server |
|
401 do_get_profile(); |
|
402 var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); |
|
403 var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit"); |
|
404 prefs.setIntPref("network.http.speculative-parallel-limit", 0); |
|
405 |
|
406 addCertOverride("localhost", 4443, |
|
407 Ci.nsICertOverrideService.ERROR_UNTRUSTED | |
|
408 Ci.nsICertOverrideService.ERROR_MISMATCH | |
|
409 Ci.nsICertOverrideService.ERROR_TIME); |
|
410 |
|
411 prefs.setIntPref("network.http.speculative-parallel-limit", oldPref); |
|
412 |
|
413 // Enable all versions of spdy to see that we auto negotiate spdy/3 |
|
414 spdypref = prefs.getBoolPref("network.http.spdy.enabled"); |
|
415 spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3"); |
|
416 spdypush = prefs.getBoolPref("network.http.spdy.allow-push"); |
|
417 prefs.setBoolPref("network.http.spdy.enabled", true); |
|
418 prefs.setBoolPref("network.http.spdy.enabled.v3", true); |
|
419 prefs.setBoolPref("network.http.spdy.allow-push", true); |
|
420 |
|
421 loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup); |
|
422 |
|
423 // And make go! |
|
424 run_next_test(); |
|
425 } |