http2_test_common.js (33490B)
1 // test HTTP/2 2 3 "use strict"; 4 5 /* import-globals-from head_channels.js */ 6 7 // Generate a small and a large post with known pre-calculated md5 sums 8 function generateContent(size) { 9 var content = ""; 10 for (var i = 0; i < size; i++) { 11 content += "0"; 12 } 13 return content; 14 } 15 16 var posts = []; 17 posts.push(generateContent(10)); 18 posts.push(generateContent(250000)); 19 posts.push(generateContent(128000)); 20 21 // pre-calculated md5sums (in hex) of the above posts 22 var md5s = [ 23 "f1b708bba17f1ce948dc979f4d7092bc", 24 "2ef8d3b6c8f329318eb1a119b12622b6", 25 ]; 26 27 var bigListenerData = generateContent(128 * 1024); 28 var bigListenerMD5 = "8f607cfdd2c87d6a7eedb657dafbd836"; 29 30 function checkIsHttp2(request) { 31 try { 32 if (request.getResponseHeader("X-Firefox-Spdy") == "h2") { 33 if (request.getResponseHeader("X-Connection-Http2") == "yes") { 34 return true; 35 } 36 return false; // Weird case, but the server disagrees with us 37 } 38 } catch (e) { 39 // Nothing to do here 40 } 41 return false; 42 } 43 44 var Http2CheckListener = function () {}; 45 46 Http2CheckListener.prototype = { 47 onStartRequestFired: false, 48 onDataAvailableFired: false, 49 isHttp2Connection: false, 50 shouldBeHttp2: true, 51 accum: 0, 52 expected: -1, 53 shouldSucceed: true, 54 55 onStartRequest: function testOnStartRequest(request) { 56 this.onStartRequestFired = true; 57 if (this.shouldSucceed && !Components.isSuccessCode(request.status)) { 58 do_throw("Channel should have a success code! (" + request.status + ")"); 59 } else if ( 60 !this.shouldSucceed && 61 Components.isSuccessCode(request.status) 62 ) { 63 do_throw("Channel succeeded unexpectedly!"); 64 } 65 66 Assert.ok(request instanceof Ci.nsIHttpChannel); 67 if (this.noResponseStatus) { 68 Assert.throws( 69 () => request.responseStatus, 70 /NS_ERROR_NOT_AVAILABLE/, 71 "getting response status should throw" 72 ); 73 return; 74 } 75 Assert.equal(request.requestSucceeded, this.shouldSucceed); 76 if (this.shouldSucceed) { 77 Assert.equal(request.responseStatus, 200); 78 } 79 }, 80 81 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { 82 this.onDataAvailableFired = true; 83 this.isHttp2Connection = checkIsHttp2(request); 84 this.accum += cnt; 85 read_stream(stream, cnt); 86 }, 87 88 onStopRequest: function testOnStopRequest(request, status) { 89 Assert.ok(this.onStartRequestFired); 90 if (this.expected != -1) { 91 Assert.equal(this.accum, this.expected); 92 } 93 94 if (this.shouldSucceed) { 95 Assert.ok(Components.isSuccessCode(status)); 96 Assert.ok(this.onDataAvailableFired); 97 Assert.equal(this.isHttp2Connection, this.shouldBeHttp2); 98 } else { 99 Assert.ok(!Components.isSuccessCode(status)); 100 } 101 request.QueryInterface(Ci.nsIProxiedChannel); 102 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 103 this.finish({ httpProxyConnectResponseCode }); 104 }, 105 }; 106 107 /* 108 * Support for testing valid multiplexing of streams 109 */ 110 111 var multiplexContent = generateContent(30 * 1024); 112 113 /* Listener class to control the testing of multiplexing */ 114 var Http2MultiplexListener = function () {}; 115 116 Http2MultiplexListener.prototype = new Http2CheckListener(); 117 118 Http2MultiplexListener.prototype.streamID = 0; 119 Http2MultiplexListener.prototype.buffer = ""; 120 121 Http2MultiplexListener.prototype.onDataAvailable = function ( 122 request, 123 stream, 124 off, 125 cnt 126 ) { 127 this.onDataAvailableFired = true; 128 this.isHttp2Connection = checkIsHttp2(request); 129 this.streamID = parseInt(request.getResponseHeader("X-Http2-StreamID")); 130 var data = read_stream(stream, cnt); 131 this.buffer = this.buffer.concat(data); 132 }; 133 134 Http2MultiplexListener.prototype.onStopRequest = function (request) { 135 Assert.ok(this.onStartRequestFired); 136 Assert.ok(this.onDataAvailableFired); 137 Assert.ok(this.isHttp2Connection); 138 Assert.equal(this.buffer, multiplexContent); 139 140 request.QueryInterface(Ci.nsIProxiedChannel); 141 // This is what does most of the hard work for us 142 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 143 var streamID = this.streamID; 144 this.finish({ httpProxyConnectResponseCode, streamID }); 145 }; 146 147 // Does the appropriate checks for header gatewaying 148 var Http2HeaderListener = function (name, callback) { 149 this.name = name; 150 this.callback = callback; 151 }; 152 153 Http2HeaderListener.prototype = new Http2CheckListener(); 154 Http2HeaderListener.prototype.value = ""; 155 156 Http2HeaderListener.prototype.onDataAvailable = function ( 157 request, 158 stream, 159 off, 160 cnt 161 ) { 162 this.onDataAvailableFired = true; 163 this.isHttp2Connection = checkIsHttp2(request); 164 var hvalue = request.getResponseHeader(this.name); 165 Assert.notEqual(hvalue, ""); 166 this.callback(hvalue); 167 read_stream(stream, cnt); 168 }; 169 170 const pushHdrTxt = 171 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 172 const pullHdrTxt = pushHdrTxt.split("").reverse().join(""); 173 174 function checkContinuedHeaders(getHeader, headerPrefix, headerText) { 175 for (var i = 0; i < 265; i++) { 176 Assert.equal(getHeader(headerPrefix + 1), headerText); 177 } 178 } 179 180 // Does the appropriate checks for a large GET response 181 var Http2BigListener = function () {}; 182 183 Http2BigListener.prototype = new Http2CheckListener(); 184 Http2BigListener.prototype.buffer = ""; 185 186 Http2BigListener.prototype.onDataAvailable = function ( 187 request, 188 stream, 189 off, 190 cnt 191 ) { 192 this.onDataAvailableFired = true; 193 this.isHttp2Connection = checkIsHttp2(request); 194 this.buffer = this.buffer.concat(read_stream(stream, cnt)); 195 // We know the server should send us the same data as our big post will be, 196 // so the md5 should be the same 197 Assert.equal(bigListenerMD5, request.getResponseHeader("X-Expected-MD5")); 198 }; 199 200 Http2BigListener.prototype.onStopRequest = function (request) { 201 Assert.ok(this.onStartRequestFired); 202 Assert.ok(this.onDataAvailableFired); 203 Assert.ok(this.isHttp2Connection); 204 205 // Don't want to flood output, so don't use do_check_eq 206 Assert.equal(this.buffer, bigListenerData); 207 208 request.QueryInterface(Ci.nsIProxiedChannel); 209 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 210 this.finish({ httpProxyConnectResponseCode }); 211 }; 212 213 var Http2HugeSuspendedListener = function () {}; 214 215 Http2HugeSuspendedListener.prototype = new Http2CheckListener(); 216 Http2HugeSuspendedListener.prototype.count = 0; 217 218 Http2HugeSuspendedListener.prototype.onDataAvailable = function ( 219 request, 220 stream, 221 off, 222 cnt 223 ) { 224 this.onDataAvailableFired = true; 225 this.isHttp2Connection = checkIsHttp2(request); 226 this.count += cnt; 227 read_stream(stream, cnt); 228 }; 229 230 Http2HugeSuspendedListener.prototype.onStopRequest = function (request) { 231 Assert.ok(this.onStartRequestFired); 232 Assert.ok(this.onDataAvailableFired); 233 Assert.ok(this.isHttp2Connection); 234 Assert.equal(this.count, 1024 * 1024 * 1); // 1mb of data expected 235 request.QueryInterface(Ci.nsIProxiedChannel); 236 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 237 this.finish({ httpProxyConnectResponseCode }); 238 }; 239 240 // Does the appropriate checks for POSTs 241 var Http2PostListener = function (expected_md5) { 242 this.expected_md5 = expected_md5; 243 }; 244 245 Http2PostListener.prototype = new Http2CheckListener(); 246 Http2PostListener.prototype.expected_md5 = ""; 247 248 Http2PostListener.prototype.onDataAvailable = function ( 249 request, 250 stream, 251 off, 252 cnt 253 ) { 254 this.onDataAvailableFired = true; 255 this.isHttp2Connection = checkIsHttp2(request); 256 read_stream(stream, cnt); 257 Assert.equal( 258 this.expected_md5, 259 request.getResponseHeader("X-Calculated-MD5") 260 ); 261 }; 262 263 var ResumeStalledChannelListener = function () {}; 264 265 ResumeStalledChannelListener.prototype = { 266 onStartRequestFired: false, 267 onDataAvailableFired: false, 268 isHttp2Connection: false, 269 shouldBeHttp2: true, 270 resumable: null, 271 272 onStartRequest: function testOnStartRequest(request) { 273 this.onStartRequestFired = true; 274 if (!Components.isSuccessCode(request.status)) { 275 do_throw("Channel should have a success code! (" + request.status + ")"); 276 } 277 278 Assert.ok(request instanceof Ci.nsIHttpChannel); 279 Assert.equal(request.responseStatus, 200); 280 Assert.equal(request.requestSucceeded, true); 281 }, 282 283 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { 284 this.onDataAvailableFired = true; 285 this.isHttp2Connection = checkIsHttp2(request); 286 read_stream(stream, cnt); 287 }, 288 289 onStopRequest: function testOnStopRequest(request, status) { 290 Assert.ok(this.onStartRequestFired); 291 Assert.ok(Components.isSuccessCode(status)); 292 Assert.ok(this.onDataAvailableFired); 293 Assert.equal(this.isHttp2Connection, this.shouldBeHttp2); 294 this.resumable.resume(); 295 }, 296 }; 297 298 // test a large download that creates stream flow control and 299 // confirm we can do another independent stream while the download 300 // stream is stuck 301 async function test_http2_blocking_download(serverPort) { 302 var chan = makeHTTPChannel(`https://localhost:${serverPort}/bigdownload`); 303 var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal); 304 internalChannel.initialRwin = 500000; // make the stream.suspend push back in h2 305 var p = new Promise(resolve => { 306 var listener = new Http2CheckListener(); 307 listener.finish = resolve; 308 listener.expected = 3 * 1024 * 1024; 309 chan.asyncOpen(listener); 310 chan.suspend(); 311 }); 312 // wait 5 seconds so that stream flow control kicks in and then see if we 313 // can do a basic transaction (i.e. session not blocked). afterwards resume 314 // channel 315 do_timeout(5000, function () { 316 var simpleChannel = makeHTTPChannel(`https://localhost:${serverPort}/`); 317 var sl = new ResumeStalledChannelListener(); 318 sl.resumable = chan; 319 simpleChannel.asyncOpen(sl); 320 }); 321 return p; 322 } 323 324 // Make sure we make a HTTP2 connection and both us and the server mark it as such 325 async function test_http2_basic(serverPort, origin = "localhost") { 326 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`); 327 var p = new Promise(resolve => { 328 var listener = new Http2CheckListener(); 329 listener.finish = resolve; 330 chan.asyncOpen(listener); 331 }); 332 return p; 333 } 334 335 async function test_http2_basic_unblocked_dep( 336 serverPort, 337 origin = "localhost" 338 ) { 339 var chan = makeHTTPChannel( 340 `https://${origin}:${serverPort}/basic_unblocked_dep` 341 ); 342 var cos = chan.QueryInterface(Ci.nsIClassOfService); 343 cos.addClassFlags(Ci.nsIClassOfService.Unblocked); 344 return new Promise(resolve => { 345 var listener = new Http2CheckListener(); 346 listener.finish = resolve; 347 chan.asyncOpen(listener); 348 }); 349 } 350 351 // make sure we don't use h2 when disallowed 352 async function test_http2_nospdy(serverPort, origin = "localhost") { 353 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/`); 354 return new Promise(resolve => { 355 var listener = new Http2CheckListener(); 356 listener.finish = resolve; 357 var internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal); 358 internalChannel.allowSpdy = false; 359 listener.shouldBeHttp2 = false; 360 chan.asyncOpen(listener); 361 }); 362 } 363 364 // Support for making sure XHR works over SPDY 365 function checkXhr(xhr, finish) { 366 if (xhr.readyState != 4) { 367 return; 368 } 369 370 Assert.equal(xhr.status, 200); 371 Assert.equal(checkIsHttp2(xhr), true); 372 finish(); 373 } 374 375 // Fires off an XHR request over h2 376 async function test_http2_xhr(serverPort, origin = "localhost") { 377 return new Promise(resolve => { 378 var req = new XMLHttpRequest(); 379 req.open("GET", `https://${origin}:${serverPort}/`, true); 380 req.addEventListener("readystatechange", function () { 381 checkXhr(req, resolve); 382 }); 383 req.send(null); 384 }); 385 } 386 387 var Http2ConcurrentListener = function () {}; 388 389 Http2ConcurrentListener.prototype = new Http2CheckListener(); 390 Http2ConcurrentListener.prototype.count = 0; 391 Http2ConcurrentListener.prototype.target = 0; 392 Http2ConcurrentListener.prototype.reset = 0; 393 Http2ConcurrentListener.prototype.recvdHdr = 0; 394 395 Http2ConcurrentListener.prototype.onStopRequest = function (request) { 396 this.count++; 397 Assert.ok(this.isHttp2Connection); 398 if (this.recvdHdr > 0) { 399 Assert.equal(request.getResponseHeader("X-Recvd"), this.recvdHdr); 400 } 401 402 if (this.count == this.target) { 403 if (this.reset > 0) { 404 Services.prefs.setIntPref( 405 "network.http.http2.default-concurrent", 406 this.reset 407 ); 408 } 409 request.QueryInterface(Ci.nsIProxiedChannel); 410 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 411 this.finish({ httpProxyConnectResponseCode }); 412 } 413 }; 414 415 async function test_http2_concurrent( 416 concurrent_channels, 417 serverPort, 418 origin = "localhost" 419 ) { 420 var p = new Promise(resolve => { 421 var concurrent_listener = new Http2ConcurrentListener(); 422 concurrent_listener.finish = resolve; 423 concurrent_listener.target = 201; 424 concurrent_listener.reset = Services.prefs.getIntPref( 425 "network.http.http2.default-concurrent" 426 ); 427 Services.prefs.setIntPref("network.http.http2.default-concurrent", 100); 428 429 for (var i = 0; i < concurrent_listener.target; i++) { 430 concurrent_channels[i] = makeHTTPChannel( 431 `https://${origin}:${serverPort}/750ms` 432 ); 433 concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; 434 concurrent_channels[i].asyncOpen(concurrent_listener); 435 } 436 }); 437 return p; 438 } 439 440 async function test_http2_concurrent_post( 441 concurrent_channels, 442 serverPort, 443 origin = "localhost" 444 ) { 445 return new Promise(resolve => { 446 var concurrent_listener = new Http2ConcurrentListener(); 447 concurrent_listener.finish = resolve; 448 concurrent_listener.target = 8; 449 concurrent_listener.recvdHdr = posts[2].length; 450 concurrent_listener.reset = Services.prefs.getIntPref( 451 "network.http.http2.default-concurrent" 452 ); 453 Services.prefs.setIntPref("network.http.http2.default-concurrent", 3); 454 455 for (var i = 0; i < concurrent_listener.target; i++) { 456 concurrent_channels[i] = makeHTTPChannel( 457 `https://${origin}:${serverPort}/750msPost` 458 ); 459 concurrent_channels[i].loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; 460 var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( 461 Ci.nsIStringInputStream 462 ); 463 stream.setByteStringData(posts[2]); 464 var uchan = concurrent_channels[i].QueryInterface(Ci.nsIUploadChannel); 465 uchan.setUploadStream(stream, "text/plain", stream.available()); 466 concurrent_channels[i].requestMethod = "POST"; 467 concurrent_channels[i].asyncOpen(concurrent_listener); 468 } 469 }); 470 } 471 472 // Test to make sure we get multiplexing right 473 async function test_http2_multiplex(serverPort) { 474 let chan1 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex1`); 475 let chan2 = makeHTTPChannel(`https://localhost:${serverPort}/multiplex2`); 476 let listener1 = new Http2MultiplexListener(); 477 let listener2 = new Http2MultiplexListener(); 478 479 let promises = []; 480 let p1 = new Promise(resolve => { 481 listener1.finish = resolve; 482 }); 483 promises.push(p1); 484 let p2 = new Promise(resolve => { 485 listener2.finish = resolve; 486 }); 487 promises.push(p2); 488 489 chan1.asyncOpen(listener1); 490 chan2.asyncOpen(listener2); 491 return Promise.all(promises); 492 } 493 494 // Test to make sure we gateway non-standard headers properly 495 async function test_http2_header(serverPort, origin = "localhost") { 496 let chan = makeHTTPChannel(`https://${origin}:${serverPort}/header`); 497 let hvalue = "Headers are fun"; 498 chan.setRequestHeader("X-Test-Header", hvalue, false); 499 return new Promise(resolve => { 500 let listener = new Http2HeaderListener("X-Received-Test-Header", function ( 501 received_hvalue 502 ) { 503 Assert.equal(received_hvalue, hvalue); 504 }); 505 listener.finish = resolve; 506 chan.asyncOpen(listener); 507 }); 508 } 509 510 // Test to make sure headers with invalid characters in the name are rejected 511 async function test_http2_invalid_response_header( 512 serverPort, 513 invalid_kind, 514 origin = "localhost" 515 ) { 516 return new Promise(resolve => { 517 var listener = new Http2CheckListener(); 518 listener.finish = resolve; 519 listener.shouldSucceed = false; 520 var chan = makeHTTPChannel( 521 `https://${origin}:${serverPort}/invalid_response_header/${invalid_kind}` 522 ); 523 chan.asyncOpen(listener); 524 }); 525 } 526 527 // Test to make sure cookies are split into separate fields before compression 528 async function test_http2_cookie_crumbling(serverPort, origin = "localhost") { 529 var chan = makeHTTPChannel( 530 `https://${origin}:${serverPort}/cookie_crumbling` 531 ); 532 var cookiesSent = ["a=b", "c=d01234567890123456789", "e=f"].sort(); 533 chan.setRequestHeader("Cookie", cookiesSent.join("; "), false); 534 return new Promise(resolve => { 535 var listener = new Http2HeaderListener("X-Received-Header-Pairs", function ( 536 pairsReceived 537 ) { 538 var cookiesReceived = JSON.parse(pairsReceived) 539 .filter(function (pair) { 540 return pair[0] == "cookie"; 541 }) 542 .map(function (pair) { 543 return pair[1]; 544 }) 545 .sort(); 546 Assert.equal(cookiesReceived.length, cookiesSent.length); 547 cookiesReceived.forEach(function (cookieReceived, index) { 548 Assert.equal(cookiesSent[index], cookieReceived); 549 }); 550 }); 551 listener.finish = resolve; 552 chan.asyncOpen(listener); 553 }); 554 } 555 556 // this is a basic test where the server sends a simple document with 2 header 557 // blocks. bug 1027364 558 async function test_http2_doubleheader(serverPort, origin = "localhost") { 559 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/doubleheader`); 560 return new Promise(resolve => { 561 var listener = new Http2CheckListener(); 562 listener.finish = resolve; 563 chan.asyncOpen(listener); 564 }); 565 } 566 567 // Make sure we handle GETs that cover more than 2 frames properly 568 async function test_http2_big(serverPort, origin = "localhost") { 569 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/big`); 570 return new Promise(resolve => { 571 var listener = new Http2BigListener(); 572 listener.finish = resolve; 573 chan.asyncOpen(listener); 574 }); 575 } 576 577 async function test_http2_huge_suspended(serverPort, origin = "localhost") { 578 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/huge`); 579 return new Promise(resolve => { 580 var listener = new Http2HugeSuspendedListener(); 581 listener.finish = resolve; 582 chan.asyncOpen(listener); 583 chan.suspend(); 584 do_timeout(500, chan.resume); 585 }); 586 } 587 588 // Support for doing a POST 589 function do_post(content, chan, listener, method) { 590 var stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( 591 Ci.nsIStringInputStream 592 ); 593 stream.setByteStringData(content); 594 595 var uchan = chan.QueryInterface(Ci.nsIUploadChannel); 596 uchan.setUploadStream(stream, "text/plain", stream.available()); 597 598 chan.requestMethod = method; 599 600 chan.asyncOpen(listener); 601 } 602 603 // Make sure we can do a simple POST 604 async function test_http2_post(serverPort, origin = "localhost") { 605 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`); 606 var p = new Promise(resolve => { 607 var listener = new Http2PostListener(md5s[0]); 608 listener.finish = resolve; 609 do_post(posts[0], chan, listener, "POST"); 610 }); 611 return p; 612 } 613 614 async function test_http2_empty_post(serverPort, origin = "localhost") { 615 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`); 616 var p = new Promise(resolve => { 617 var listener = new Http2PostListener("0"); 618 listener.finish = resolve; 619 do_post("", chan, listener, "POST"); 620 }); 621 return p; 622 } 623 624 // Make sure we can do a simple PATCH 625 async function test_http2_patch(serverPort, origin = "localhost") { 626 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/patch`); 627 return new Promise(resolve => { 628 var listener = new Http2PostListener(md5s[0]); 629 listener.finish = resolve; 630 do_post(posts[0], chan, listener, "PATCH"); 631 }); 632 } 633 634 // Make sure we can do a POST that covers more than 2 frames 635 async function test_http2_post_big(serverPort, origin = "localhost") { 636 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/post`); 637 return new Promise(resolve => { 638 var listener = new Http2PostListener(md5s[1]); 639 listener.finish = resolve; 640 do_post(posts[1], chan, listener, "POST"); 641 }); 642 } 643 644 // When a http proxy is used alt-svc is disable. Therefore if withProxy is true, 645 // try numberOfTries times to connect and make sure that alt-svc is not use and we never 646 // connect to the HTTP/2 server. 647 var altsvcClientListener = function ( 648 finish, 649 httpserv, 650 httpserv2, 651 withProxy, 652 numberOfTries 653 ) { 654 this.finish = finish; 655 this.httpserv = httpserv; 656 this.httpserv2 = httpserv2; 657 this.withProxy = withProxy; 658 this.numberOfTries = numberOfTries; 659 }; 660 661 altsvcClientListener.prototype = { 662 onStartRequest: function test_onStartR(request) { 663 Assert.equal(request.status, Cr.NS_OK); 664 }, 665 666 onDataAvailable: function test_ODA(request, stream, offset, cnt) { 667 read_stream(stream, cnt); 668 }, 669 670 onStopRequest: function test_onStopR(request) { 671 var isHttp2Connection = checkIsHttp2( 672 request.QueryInterface(Ci.nsIHttpChannel) 673 ); 674 if (!isHttp2Connection) { 675 dump("/altsvc1 not over h2 yet - retry\n"); 676 if (this.withProxy && this.numberOfTries == 0) { 677 request.QueryInterface(Ci.nsIProxiedChannel); 678 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 679 this.finish({ httpProxyConnectResponseCode }); 680 return; 681 } 682 let chan = makeHTTPChannel( 683 `http://foo.example.com:${this.httpserv}/altsvc1`, 684 this.withProxy 685 ).QueryInterface(Ci.nsIHttpChannel); 686 // we use this header to tell the server to issue a altsvc frame for the 687 // speficied origin we will use in the next part of the test 688 chan.setRequestHeader( 689 "x-redirect-origin", 690 `http://foo.example.com:${this.httpserv2}`, 691 false 692 ); 693 chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; 694 chan.asyncOpen( 695 new altsvcClientListener( 696 this.finish, 697 this.httpserv, 698 this.httpserv2, 699 this.withProxy, 700 this.numberOfTries - 1 701 ) 702 ); 703 } else { 704 Assert.ok(isHttp2Connection); 705 let chan = makeHTTPChannel( 706 `http://foo.example.com:${this.httpserv2}/altsvc2` 707 ).QueryInterface(Ci.nsIHttpChannel); 708 chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; 709 chan.asyncOpen( 710 new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2) 711 ); 712 } 713 }, 714 }; 715 716 var altsvcClientListener2 = function (finish, httpserv, httpserv2) { 717 this.finish = finish; 718 this.httpserv = httpserv; 719 this.httpserv2 = httpserv2; 720 }; 721 722 altsvcClientListener2.prototype = { 723 onStartRequest: function test_onStartR(request) { 724 Assert.equal(request.status, Cr.NS_OK); 725 }, 726 727 onDataAvailable: function test_ODA(request, stream, offset, cnt) { 728 read_stream(stream, cnt); 729 }, 730 731 onStopRequest: function test_onStopR(request) { 732 var isHttp2Connection = checkIsHttp2( 733 request.QueryInterface(Ci.nsIHttpChannel) 734 ); 735 if (!isHttp2Connection) { 736 dump("/altsvc2 not over h2 yet - retry\n"); 737 var chan = makeHTTPChannel( 738 `http://foo.example.com:${this.httpserv2}/altsvc2` 739 ).QueryInterface(Ci.nsIHttpChannel); 740 chan.loadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE; 741 chan.asyncOpen( 742 new altsvcClientListener2(this.finish, this.httpserv, this.httpserv2) 743 ); 744 } else { 745 Assert.ok(isHttp2Connection); 746 request.QueryInterface(Ci.nsIProxiedChannel); 747 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 748 this.finish({ httpProxyConnectResponseCode }); 749 } 750 }, 751 }; 752 753 async function test_http2_altsvc(httpserv, httpserv2, withProxy) { 754 var chan = makeHTTPChannel( 755 `http://foo.example.com:${httpserv}/altsvc1`, 756 withProxy 757 ).QueryInterface(Ci.nsIHttpChannel); 758 return new Promise(resolve => { 759 var numberOfTries = 0; 760 if (withProxy) { 761 numberOfTries = 20; 762 } 763 chan.asyncOpen( 764 new altsvcClientListener( 765 resolve, 766 httpserv, 767 httpserv2, 768 withProxy, 769 numberOfTries 770 ) 771 ); 772 }); 773 } 774 775 var WrongSuiteListener = function () {}; 776 777 WrongSuiteListener.prototype = new Http2CheckListener(); 778 WrongSuiteListener.prototype.shouldBeHttp2 = false; 779 WrongSuiteListener.prototype.onStopRequest = function (request, status) { 780 Services.prefs.setBoolPref( 781 "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", 782 true 783 ); 784 Services.prefs.clearUserPref("security.tls.version.max"); 785 Http2CheckListener.prototype.onStopRequest.call(this, request, status); 786 }; 787 788 // test that we use h1 without the mandatory cipher suite available when 789 // offering at most tls1.2 790 async function test_http2_wrongsuite_tls12(serverPort, origin = "localhost") { 791 Services.prefs.setBoolPref( 792 "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", 793 false 794 ); 795 Services.prefs.setIntPref("security.tls.version.max", 3); 796 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`); 797 chan.loadFlags = 798 Ci.nsIRequest.LOAD_FRESH_CONNECTION | 799 Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; 800 return new Promise(resolve => { 801 var listener = new WrongSuiteListener(); 802 listener.finish = resolve; 803 chan.asyncOpen(listener); 804 }); 805 } 806 807 // test that we use h2 when offering tls1.3 or higher regardless of if the 808 // mandatory cipher suite is available 809 async function test_http2_wrongsuite_tls13(serverPort, origin = "localhost") { 810 Services.prefs.setBoolPref( 811 "security.ssl3.ecdhe_rsa_aes_128_gcm_sha256", 812 false 813 ); 814 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/wrongsuite`); 815 chan.loadFlags = 816 Ci.nsIRequest.LOAD_FRESH_CONNECTION | 817 Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; 818 return new Promise(resolve => { 819 var listener = new WrongSuiteListener(); 820 listener.finish = resolve; 821 listener.shouldBeHttp2 = true; 822 chan.asyncOpen(listener); 823 }); 824 } 825 826 async function test_http2_h11required_stream(serverPort, origin = "localhost") { 827 var chan = makeHTTPChannel( 828 `https://${origin}:${serverPort}/h11required_stream` 829 ); 830 return new Promise(resolve => { 831 var listener = new Http2CheckListener(); 832 listener.finish = resolve; 833 listener.shouldBeHttp2 = false; 834 chan.asyncOpen(listener); 835 }); 836 } 837 838 function H11RequiredSessionListener() {} 839 840 H11RequiredSessionListener.prototype = new Http2CheckListener(); 841 842 H11RequiredSessionListener.prototype.onStopRequest = function (request) { 843 var streamReused = request.getResponseHeader("X-H11Required-Stream-Ok"); 844 Assert.equal(streamReused, "yes"); 845 846 Assert.ok(this.onStartRequestFired); 847 Assert.ok(this.onDataAvailableFired); 848 Assert.equal(this.isHttp2Connection, this.shouldBeHttp2); 849 850 request.QueryInterface(Ci.nsIProxiedChannel); 851 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 852 this.finish({ httpProxyConnectResponseCode }); 853 }; 854 855 async function test_http2_h11required_session( 856 serverPort, 857 origin = "localhost" 858 ) { 859 var chan = makeHTTPChannel( 860 `https://${origin}:${serverPort}/h11required_session` 861 ); 862 return new Promise(resolve => { 863 var listener = new H11RequiredSessionListener(); 864 listener.finish = resolve; 865 listener.shouldBeHttp2 = false; 866 chan.asyncOpen(listener); 867 }); 868 } 869 870 async function test_http2_retry_rst(serverPort, origin = "localhost") { 871 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/rstonce`); 872 return new Promise(resolve => { 873 var listener = new Http2CheckListener(); 874 listener.finish = resolve; 875 chan.asyncOpen(listener); 876 }); 877 } 878 879 async function test_http2_continuations_over_max_response_limit( 880 loadGroup, 881 serverPort, 882 origin = "localhost" 883 ) { 884 var chan = makeHTTPChannel( 885 `https://${origin}:${serverPort}/hugecontinuedheaders?size=385` 886 ); 887 chan.loadGroup = loadGroup; 888 return new Promise(resolve => { 889 var listener = new Http2CheckListener(); 890 listener.finish = resolve; 891 listener.shouldSucceed = false; 892 listener.noResponseStatus = true; 893 chan.asyncOpen(listener); 894 }); 895 } 896 897 function Http2IllegalHpackValidationListener() {} 898 899 Http2IllegalHpackValidationListener.prototype = new Http2CheckListener(); 900 Http2IllegalHpackValidationListener.prototype.shouldGoAway = false; 901 902 Http2IllegalHpackValidationListener.prototype.onStopRequest = function ( 903 request 904 ) { 905 var wentAway = request.getResponseHeader("X-Did-Goaway") === "yes"; 906 Assert.equal(wentAway, this.shouldGoAway); 907 908 Assert.ok(this.onStartRequestFired); 909 Assert.ok(this.onDataAvailableFired); 910 Assert.equal(this.isHttp2Connection, this.shouldBeHttp2); 911 912 request.QueryInterface(Ci.nsIProxiedChannel); 913 var httpProxyConnectResponseCode = request.httpProxyConnectResponseCode; 914 this.finish({ httpProxyConnectResponseCode }); 915 }; 916 917 function Http2IllegalHpackListener() {} 918 Http2IllegalHpackListener.prototype = new Http2CheckListener(); 919 Http2IllegalHpackListener.prototype.shouldGoAway = false; 920 921 Http2IllegalHpackListener.prototype.onStopRequest = function () { 922 var chan = makeHTTPChannel( 923 `https://${this.origin}:${this.serverPort}/illegalhpack_validate` 924 ); 925 var listener = new Http2IllegalHpackValidationListener(); 926 listener.finish = this.finish; 927 listener.shouldGoAway = this.shouldGoAway; 928 chan.asyncOpen(listener); 929 }; 930 931 async function test_http2_illegalhpacksoft(serverPort, origin = "localhost") { 932 var chan = makeHTTPChannel( 933 `https://${origin}:${serverPort}/illegalhpacksoft` 934 ); 935 return new Promise(resolve => { 936 var listener = new Http2IllegalHpackListener(); 937 listener.finish = resolve; 938 listener.serverPort = serverPort; 939 listener.origin = origin; 940 listener.shouldGoAway = false; 941 listener.shouldSucceed = false; 942 chan.asyncOpen(listener); 943 }); 944 } 945 946 async function test_http2_illegalhpackhard(serverPort, origin = "localhost") { 947 var chan = makeHTTPChannel( 948 `https://${origin}:${serverPort}/illegalhpackhard` 949 ); 950 return new Promise(resolve => { 951 var listener = new Http2IllegalHpackListener(); 952 listener.finish = resolve; 953 listener.serverPort = serverPort; 954 listener.origin = origin; 955 listener.shouldGoAway = true; 956 listener.shouldSucceed = false; 957 chan.asyncOpen(listener); 958 }); 959 } 960 961 async function test_http2_folded_header( 962 loadGroup, 963 serverPort, 964 origin = "localhost" 965 ) { 966 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/foldedheader`); 967 chan.loadGroup = loadGroup; 968 return new Promise(resolve => { 969 var listener = new Http2CheckListener(); 970 listener.finish = resolve; 971 listener.shouldSucceed = false; 972 chan.asyncOpen(listener); 973 }); 974 } 975 976 async function test_http2_empty_data(serverPort, origin = "localhost") { 977 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/emptydata`); 978 return new Promise(resolve => { 979 var listener = new Http2CheckListener(); 980 listener.finish = resolve; 981 chan.asyncOpen(listener); 982 }); 983 } 984 985 async function test_http2_status_phrase(serverPort, origin = "localhost") { 986 var chan = makeHTTPChannel(`https://${origin}:${serverPort}/statusphrase`); 987 return new Promise(resolve => { 988 var listener = new Http2CheckListener(); 989 listener.finish = resolve; 990 listener.shouldSucceed = false; 991 chan.asyncOpen(listener); 992 }); 993 } 994 995 var PulledDiskCacheListener = function () {}; 996 PulledDiskCacheListener.prototype = new Http2CheckListener(); 997 PulledDiskCacheListener.prototype.EXPECTED_DATA = "this was pulled via h2"; 998 PulledDiskCacheListener.prototype.readData = ""; 999 PulledDiskCacheListener.prototype.onDataAvailable = 1000 function testOnDataAvailable(request, stream, off, cnt) { 1001 this.onDataAvailableFired = true; 1002 this.isHttp2Connection = checkIsHttp2(request); 1003 this.accum += cnt; 1004 this.readData += read_stream(stream, cnt); 1005 }; 1006 PulledDiskCacheListener.prototype.onStopRequest = function testOnStopRequest( 1007 request, 1008 status 1009 ) { 1010 Assert.equal(this.EXPECTED_DATA, this.readData); 1011 Http2CheckListener.prorotype.onStopRequest.call(this, request, status); 1012 }; 1013 1014 const DISK_CACHE_DATA = "this is from disk cache"; 1015 1016 var FromDiskCacheListener = function (finish, loadGroup, serverPort) { 1017 this.finish = finish; 1018 this.loadGroup = loadGroup; 1019 this.serverPort = serverPort; 1020 }; 1021 FromDiskCacheListener.prototype = { 1022 onStartRequestFired: false, 1023 onDataAvailableFired: false, 1024 readData: "", 1025 1026 onStartRequest: function testOnStartRequest(request) { 1027 this.onStartRequestFired = true; 1028 if (!Components.isSuccessCode(request.status)) { 1029 do_throw("Channel should have a success code! (" + request.status + ")"); 1030 } 1031 1032 Assert.ok(request instanceof Ci.nsIHttpChannel); 1033 Assert.ok(request.requestSucceeded); 1034 Assert.equal(request.responseStatus, 200); 1035 }, 1036 1037 onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { 1038 this.onDataAvailableFired = true; 1039 this.readData += read_stream(stream, cnt); 1040 }, 1041 1042 onStopRequest: function testOnStopRequest(request, status) { 1043 Assert.ok(this.onStartRequestFired); 1044 Assert.ok(Components.isSuccessCode(status)); 1045 Assert.ok(this.onDataAvailableFired); 1046 Assert.equal(this.readData, DISK_CACHE_DATA); 1047 1048 evict_cache_entries("disk"); 1049 syncWithCacheIOThread(() => { 1050 // Now that we know the entry is out of the disk cache, check to make sure 1051 // we don't have this hiding in the push cache somewhere - if we do, it 1052 // didn't get cancelled, and we have a bug. 1053 var chan = makeHTTPChannel( 1054 `https://localhost:${this.serverPort}/diskcache` 1055 ); 1056 var listener = new PulledDiskCacheListener(); 1057 listener.finish = this.finish; 1058 chan.loadGroup = this.loadGroup; 1059 chan.asyncOpen(listener); 1060 }); 1061 }, 1062 };