Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 // test HTTP/2
3 var Ci = Components.interfaces;
4 var Cc = Components.classes;
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 }
15 var posts = [];
16 posts.push(generateContent(10));
17 posts.push(generateContent(250000));
19 // pre-calculated md5sums (in hex) of the above posts
20 var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
21 '2ef8d3b6c8f329318eb1a119b12622b6'];
23 var bigListenerData = generateContent(128 * 1024);
24 var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836';
26 function checkIsHttp2(request) {
27 try {
28 if (request.getResponseHeader("X-Firefox-Spdy") == "h2-10") {
29 if (request.getResponseHeader("X-Connection-Http2") == "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 }
40 var Http2CheckListener = function() {};
42 Http2CheckListener.prototype = {
43 onStartRequestFired: false,
44 onDataAvailableFired: false,
45 isHttp2Connection: false,
47 onStartRequest: function testOnStartRequest(request, ctx) {
48 this.onStartRequestFired = true;
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");
55 do_check_eq(request.responseStatus, 200);
56 do_check_eq(request.requestSucceeded, true);
57 },
59 onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
60 this.onDataAvailableFired = true;
61 this.isHttp2Connection = checkIsHttp2(request);
63 read_stream(stream, cnt);
64 },
66 onStopRequest: function testOnStopRequest(request, ctx, status) {
67 do_check_true(this.onStartRequestFired);
68 do_check_true(this.onDataAvailableFired);
69 do_check_true(this.isHttp2Connection);
71 run_next_test();
72 do_test_finished();
73 }
74 };
76 /*
77 * Support for testing valid multiplexing of streams
78 */
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 }
91 /* Listener class to control the testing of multiplexing */
92 var Http2MultiplexListener = function() {};
94 Http2MultiplexListener.prototype = new Http2CheckListener();
96 Http2MultiplexListener.prototype.streamID = 0;
97 Http2MultiplexListener.prototype.buffer = "";
99 Http2MultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
100 this.onDataAvailableFired = true;
101 this.isHttp2Connection = checkIsHttp2(request);
102 this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID"));
103 var data = read_stream(stream, cnt);
104 this.buffer = this.buffer.concat(data);
105 };
107 Http2MultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
108 do_check_true(this.onStartRequestFired);
109 do_check_true(this.onDataAvailableFired);
110 do_check_true(this.isHttp2Connection);
111 do_check_true(this.buffer == multiplexContent);
113 // This is what does most of the hard work for us
114 register_completed_channel(this);
115 };
117 // Does the appropriate checks for header gatewaying
118 var Http2HeaderListener = function(name, callback) {
119 this.name = name;
120 this.callback = callback;
121 };
123 Http2HeaderListener.prototype = new Http2CheckListener();
124 Http2HeaderListener.prototype.value = "";
126 Http2HeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
127 this.onDataAvailableFired = true;
128 this.isHttp2Connection = checkIsHttp2(request);
129 var hvalue = request.getResponseHeader(this.name);
130 do_check_neq(hvalue, "");
131 this.callback(hvalue);
132 read_stream(stream, cnt);
133 };
135 var Http2PushListener = function() {};
137 Http2PushListener.prototype = new Http2CheckListener();
139 Http2PushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
140 this.onDataAvailableFired = true;
141 this.isHttp2Connection = checkIsHttp2(request);
142 if (ctx.originalURI.spec == "https://localhost:6944/push.js" ||
143 ctx.originalURI.spec == "https://localhost:6944/push2.js") {
144 do_check_eq(request.getResponseHeader("pushed"), "yes");
145 }
146 read_stream(stream, cnt);
147 };
149 // Does the appropriate checks for a large GET response
150 var Http2BigListener = function() {};
152 Http2BigListener.prototype = new Http2CheckListener();
153 Http2BigListener.prototype.buffer = "";
155 Http2BigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
156 this.onDataAvailableFired = true;
157 this.isHttp2Connection = checkIsHttp2(request);
158 this.buffer = this.buffer.concat(read_stream(stream, cnt));
159 // We know the server should send us the same data as our big post will be,
160 // so the md5 should be the same
161 do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5"));
162 };
164 Http2BigListener.prototype.onStopRequest = function(request, ctx, status) {
165 do_check_true(this.onStartRequestFired);
166 do_check_true(this.onDataAvailableFired);
167 do_check_true(this.isHttp2Connection);
169 // Don't want to flood output, so don't use do_check_eq
170 do_check_true(this.buffer == bigListenerData);
172 run_next_test();
173 do_test_finished();
174 };
176 // Does the appropriate checks for POSTs
177 var Http2PostListener = function(expected_md5) {
178 this.expected_md5 = expected_md5;
179 };
181 Http2PostListener.prototype = new Http2CheckListener();
182 Http2PostListener.prototype.expected_md5 = "";
184 Http2PostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
185 this.onDataAvailableFired = true;
186 this.isHttp2Connection = checkIsHttp2(request);
187 read_stream(stream, cnt);
188 do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
189 };
191 function makeChan(url) {
192 var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
193 var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
195 return chan;
196 }
198 // Make sure we make a HTTP2 connection and both us and the server mark it as such
199 function test_http2_basic() {
200 var chan = makeChan("https://localhost:6944/");
201 var listener = new Http2CheckListener();
202 chan.asyncOpen(listener, null);
203 }
205 // Support for making sure XHR works over SPDY
206 function checkXhr(xhr) {
207 if (xhr.readyState != 4) {
208 return;
209 }
211 do_check_eq(xhr.status, 200);
212 do_check_eq(checkIsHttp2(xhr), true);
213 run_next_test();
214 do_test_finished();
215 }
217 // Fires off an XHR request over SPDY
218 function test_http2_xhr() {
219 var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
220 .createInstance(Ci.nsIXMLHttpRequest);
221 req.open("GET", "https://localhost:6944/", true);
222 req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
223 false);
224 req.send(null);
225 }
227 // Test to make sure we get multiplexing right
228 function test_http2_multiplex() {
229 var chan1 = makeChan("https://localhost:6944/multiplex1");
230 var chan2 = makeChan("https://localhost:6944/multiplex2");
231 var listener1 = new Http2MultiplexListener();
232 var listener2 = new Http2MultiplexListener();
233 chan1.asyncOpen(listener1, null);
234 chan2.asyncOpen(listener2, null);
235 }
237 // Test to make sure we gateway non-standard headers properly
238 function test_http2_header() {
239 var chan = makeChan("https://localhost:6944/header");
240 var hvalue = "Headers are fun";
241 chan.setRequestHeader("X-Test-Header", hvalue, false);
242 var listener = new Http2HeaderListener("X-Received-Test-Header", function(received_hvalue) {
243 do_check_eq(received_hvalue, hvalue);
244 });
245 chan.asyncOpen(listener, null);
246 }
248 // Test to make sure cookies are split into separate fields before compression
249 function test_http2_cookie_crumbling() {
250 var chan = makeChan("https://localhost:6944/cookie_crumbling");
251 var cookiesSent = ['a=b', 'c=d', 'e=f'].sort();
252 chan.setRequestHeader("Cookie", cookiesSent.join('; '), false);
253 var listener = new Http2HeaderListener("X-Received-Header-Pairs", function(pairsReceived) {
254 var cookiesReceived = JSON.parse(pairsReceived).filter(function(pair) {
255 return pair[0] == 'cookie';
256 }).map(function(pair) {
257 return pair[1];
258 }).sort();
259 do_check_eq(cookiesReceived.length, cookiesSent.length);
260 cookiesReceived.forEach(function(cookieReceived, index) {
261 do_check_eq(cookiesSent[index], cookieReceived)
262 });
263 });
264 chan.asyncOpen(listener, null);
265 }
267 function test_http2_push1() {
268 var chan = makeChan("https://localhost:6944/push");
269 chan.loadGroup = loadGroup;
270 var listener = new Http2PushListener();
271 chan.asyncOpen(listener, chan);
272 }
274 function test_http2_push2() {
275 var chan = makeChan("https://localhost:6944/push.js");
276 chan.loadGroup = loadGroup;
277 var listener = new Http2PushListener();
278 chan.asyncOpen(listener, chan);
279 }
281 function test_http2_push3() {
282 var chan = makeChan("https://localhost:6944/push2");
283 chan.loadGroup = loadGroup;
284 var listener = new Http2PushListener();
285 chan.asyncOpen(listener, chan);
286 }
288 function test_http2_push4() {
289 var chan = makeChan("https://localhost:6944/push2.js");
290 chan.loadGroup = loadGroup;
291 var listener = new Http2PushListener();
292 chan.asyncOpen(listener, chan);
293 }
295 // Make sure we handle GETs that cover more than 2 frames properly
296 function test_http2_big() {
297 var chan = makeChan("https://localhost:6944/big");
298 var listener = new Http2BigListener();
299 chan.asyncOpen(listener, null);
300 }
302 // Support for doing a POST
303 function do_post(content, chan, listener) {
304 var stream = Cc["@mozilla.org/io/string-input-stream;1"]
305 .createInstance(Ci.nsIStringInputStream);
306 stream.data = content;
308 var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
309 uchan.setUploadStream(stream, "text/plain", stream.available());
311 chan.requestMethod = "POST";
313 chan.asyncOpen(listener, null);
314 }
316 // Make sure we can do a simple POST
317 function test_http2_post() {
318 var chan = makeChan("https://localhost:6944/post");
319 var listener = new Http2PostListener(md5s[0]);
320 do_post(posts[0], chan, listener);
321 }
323 // Make sure we can do a POST that covers more than 2 frames
324 function test_http2_post_big() {
325 var chan = makeChan("https://localhost:6944/post");
326 var listener = new Http2PostListener(md5s[1]);
327 do_post(posts[1], chan, listener);
328 }
330 // hack - the header test resets the multiplex object on the server,
331 // so make sure header is always run before the multiplex test.
332 //
333 // make sure post_big runs first to test race condition in restarting
334 // a stalled stream when a SETTINGS frame arrives
335 var tests = [ test_http2_post_big
336 , test_http2_basic
337 , test_http2_push1
338 , test_http2_push2
339 , test_http2_push3
340 , test_http2_push4
341 , test_http2_xhr
342 , test_http2_header
343 , test_http2_cookie_crumbling
344 , test_http2_multiplex
345 , test_http2_big
346 , test_http2_post
347 ];
348 var current_test = 0;
350 function run_next_test() {
351 if (current_test < tests.length) {
352 tests[current_test]();
353 current_test++;
354 do_test_pending();
355 }
356 }
358 // Support for making sure we can talk to the invalid cert the server presents
359 var CertOverrideListener = function(host, port, bits) {
360 this.host = host;
361 if (port) {
362 this.port = port;
363 }
364 this.bits = bits;
365 };
367 CertOverrideListener.prototype = {
368 host: null,
369 port: -1,
370 bits: null,
372 getInterface: function(aIID) {
373 return this.QueryInterface(aIID);
374 },
376 QueryInterface: function(aIID) {
377 if (aIID.equals(Ci.nsIBadCertListener2) ||
378 aIID.equals(Ci.nsIInterfaceRequestor) ||
379 aIID.equals(Ci.nsISupports))
380 return this;
381 throw Components.results.NS_ERROR_NO_INTERFACE;
382 },
384 notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
385 var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
386 var cos = Cc["@mozilla.org/security/certoverride;1"].
387 getService(Ci.nsICertOverrideService);
388 cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
389 return true;
390 },
391 };
393 function addCertOverride(host, port, bits) {
394 var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
395 .createInstance(Ci.nsIXMLHttpRequest);
396 try {
397 var url;
398 if (port) {
399 url = "https://" + host + ":" + port + "/";
400 } else {
401 url = "https://" + host + "/";
402 }
403 req.open("GET", url, false);
404 req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
405 req.send(null);
406 } catch (e) {
407 // This will fail since the server is not trusted yet
408 }
409 }
411 var prefs;
412 var spdypref;
413 var spdy3pref;
414 var spdypush;
415 var http2pref;
417 var loadGroup;
419 function resetPrefs() {
420 prefs.setBoolPref("network.http.spdy.enabled", spdypref);
421 prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref);
422 prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
423 prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2pref);
424 }
426 function run_test() {
427 // Set to allow the cert presented by our SPDY server
428 do_get_profile();
429 var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
430 var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit");
431 prefs.setIntPref("network.http.speculative-parallel-limit", 0);
433 addCertOverride("localhost", 6944,
434 Ci.nsICertOverrideService.ERROR_UNTRUSTED |
435 Ci.nsICertOverrideService.ERROR_MISMATCH |
436 Ci.nsICertOverrideService.ERROR_TIME);
438 prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
440 // Enable all versions of spdy to see that we auto negotiate http/2
441 spdypref = prefs.getBoolPref("network.http.spdy.enabled");
442 spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3");
443 spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
444 http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2draft");
445 prefs.setBoolPref("network.http.spdy.enabled", true);
446 prefs.setBoolPref("network.http.spdy.enabled.v3", true);
447 prefs.setBoolPref("network.http.spdy.allow-push", true);
448 prefs.setBoolPref("network.http.spdy.enabled.http2draft", true);
450 loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
452 // And make go!
453 run_next_test();
454 }