1 /* 2 * Copyright 2010, Christophe Huriaux 3 * Copyright 2014-2020, Haiku, inc. 4 * Distributed under the terms of the MIT licence 5 */ 6 7 8 #include "HttpTest.h" 9 10 #include <algorithm> 11 #include <cstdio> 12 #include <cstdlib> 13 #include <cstring> 14 #include <fstream> 15 #include <map> 16 #include <posix/libgen.h> 17 #include <string> 18 19 #include <AutoDeleter.h> 20 #include <HttpRequest.h> 21 #include <NetworkKit.h> 22 #include <UrlProtocolListener.h> 23 #include <UrlProtocolRoster.h> 24 25 #include <tools/cppunit/ThreadedTestCaller.h> 26 27 #include "TestServer.h" 28 29 30 using namespace BPrivate::Network; 31 32 33 namespace { 34 35 typedef std::map<std::string, std::string> HttpHeaderMap; 36 37 38 class TestListener : public BUrlProtocolListener, public BDataIO { 39 public: 40 TestListener(const std::string& expectedResponseBody, 41 const HttpHeaderMap& expectedResponseHeaders) 42 : 43 fExpectedResponseBody(expectedResponseBody), 44 fExpectedResponseHeaders(expectedResponseHeaders) 45 { 46 } 47 48 virtual ssize_t Write( 49 const void *data, 50 size_t size) 51 { 52 std::copy_n( 53 (const char*)data, 54 size, 55 std::back_inserter(fActualResponseBody)); 56 return size; 57 } 58 59 virtual void HeadersReceived( 60 BUrlRequest* caller) 61 { 62 const BHttpResult& http_result 63 = dynamic_cast<const BHttpResult&>(caller->Result()); 64 const BHttpHeaders& headers = http_result.Headers(); 65 66 for (int32 i = 0; i < headers.CountHeaders(); ++i) { 67 const BHttpHeader& header = headers.HeaderAt(i); 68 fActualResponseHeaders[std::string(header.Name())] 69 = std::string(header.Value()); 70 } 71 } 72 73 74 virtual bool CertificateVerificationFailed( 75 BUrlRequest* caller, 76 BCertificate& certificate, 77 const char* message) 78 { 79 // TODO: Add tests that exercize this behavior. 80 // 81 // At the moment there doesn't seem to be any public API for providing 82 // an alternate certificate authority, or for constructing a 83 // BCertificate to be sent to BUrlContext::AddCertificateException(). 84 // Once we have such a public API then it will be useful to create 85 // test scenarios that exercize the validation performed by the 86 // undrelying TLS implementaiton to verify that it is configured 87 // to do so. 88 // 89 // For now we just disable TLS certificate validation entirely because 90 // we are generating a self-signed TLS certificate for these tests. 91 return true; 92 } 93 94 95 void Verify() 96 { 97 CPPUNIT_ASSERT_EQUAL(fExpectedResponseBody, fActualResponseBody); 98 99 for (HttpHeaderMap::iterator iter = fActualResponseHeaders.begin(); 100 iter != fActualResponseHeaders.end(); 101 ++iter) 102 { 103 CPPUNIT_ASSERT_EQUAL_MESSAGE( 104 "(header " + iter->first + ")", 105 fExpectedResponseHeaders[iter->first], 106 iter->second); 107 } 108 CPPUNIT_ASSERT_EQUAL( 109 fExpectedResponseHeaders.size(), 110 fActualResponseHeaders.size()); 111 } 112 113 private: 114 std::string fExpectedResponseBody; 115 std::string fActualResponseBody; 116 117 HttpHeaderMap fExpectedResponseHeaders; 118 HttpHeaderMap fActualResponseHeaders; 119 }; 120 121 122 void SendAuthenticatedRequest( 123 BUrlContext &context, 124 BUrl &testUrl, 125 const std::string& expectedResponseBody, 126 const HttpHeaderMap &expectedResponseHeaders) 127 { 128 TestListener listener(expectedResponseBody, expectedResponseHeaders); 129 130 ObjectDeleter<BUrlRequest> requestDeleter( 131 BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener, 132 &context)); 133 BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get()); 134 CPPUNIT_ASSERT(request != NULL); 135 136 request->SetUserName("walter"); 137 request->SetPassword("secret"); 138 139 CPPUNIT_ASSERT(request->Run()); 140 141 while (request->IsRunning()) 142 snooze(1000); 143 144 CPPUNIT_ASSERT_EQUAL(B_OK, request->Status()); 145 146 const BHttpResult &result = 147 dynamic_cast<const BHttpResult &>(request->Result()); 148 CPPUNIT_ASSERT_EQUAL(200, result.StatusCode()); 149 CPPUNIT_ASSERT_EQUAL(BString("OK"), result.StatusText()); 150 151 listener.Verify(); 152 } 153 154 155 // Return the path of a file path relative to this source file. 156 std::string TestFilePath(const std::string& relativePath) 157 { 158 char *testFileSource = strdup(__FILE__); 159 MemoryDeleter _(testFileSource); 160 161 std::string testSrcDir(::dirname(testFileSource)); 162 163 return testSrcDir + "/" + relativePath; 164 } 165 166 167 template <typename T> 168 void AddCommonTests(BThreadedTestCaller<T>& testCaller) 169 { 170 testCaller.addThread("GetTest", &T::GetTest); 171 testCaller.addThread("HeadTest", &T::HeadTest); 172 testCaller.addThread("NoContentTest", &T::NoContentTest); 173 testCaller.addThread("UploadTest", &T::UploadTest); 174 testCaller.addThread("BasicAuthTest", &T::AuthBasicTest); 175 testCaller.addThread("DigestAuthTest", &T::AuthDigestTest); 176 testCaller.addThread("AutoRedirectTest", &T::AutoRedirectTest); 177 } 178 179 } 180 181 182 HttpTest::HttpTest(TestServerMode mode) 183 : 184 fTestServer(mode) 185 { 186 } 187 188 189 HttpTest::~HttpTest() 190 { 191 } 192 193 194 void 195 HttpTest::setUp() 196 { 197 CPPUNIT_ASSERT_EQUAL_MESSAGE( 198 "Starting up test server", 199 B_OK, 200 fTestServer.Start()); 201 } 202 203 204 void 205 HttpTest::GetTest() 206 { 207 _GetTest("/"); 208 } 209 210 211 void 212 HttpTest::HeadTest() 213 { 214 BUrl testUrl(fTestServer.BaseUrl(), "/"); 215 BUrlContext* context = new BUrlContext(); 216 context->AcquireReference(); 217 218 std::string expectedResponseBody(""); 219 HttpHeaderMap expectedResponseHeaders; 220 expectedResponseHeaders["Content-Encoding"] = "gzip"; 221 expectedResponseHeaders["Content-Length"] = "144"; 222 expectedResponseHeaders["Content-Type"] = "text/plain"; 223 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 224 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 225 226 TestListener listener(expectedResponseBody, expectedResponseHeaders); 227 228 ObjectDeleter<BUrlRequest> requestDeleter( 229 BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener, 230 context)); 231 BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get()); 232 CPPUNIT_ASSERT(request != NULL); 233 234 request->SetAutoReferrer(false); 235 request->SetMethod("HEAD"); 236 237 CPPUNIT_ASSERT(request->Run()); 238 while (request->IsRunning()) 239 snooze(1000); 240 241 CPPUNIT_ASSERT_EQUAL(B_OK, request->Status()); 242 243 const BHttpResult& result 244 = dynamic_cast<const BHttpResult&>(request->Result()); 245 CPPUNIT_ASSERT_EQUAL(200, result.StatusCode()); 246 CPPUNIT_ASSERT_EQUAL(BString("OK"), result.StatusText()); 247 248 CPPUNIT_ASSERT_EQUAL(144, result.Length()); 249 250 listener.Verify(); 251 252 CPPUNIT_ASSERT(!context->GetCookieJar().GetIterator().HasNext()); 253 // This page should not set cookies 254 255 context->ReleaseReference(); 256 } 257 258 259 void 260 HttpTest::NoContentTest() 261 { 262 BUrl testUrl(fTestServer.BaseUrl(), "/204"); 263 BUrlContext* context = new BUrlContext(); 264 context->AcquireReference(); 265 266 std::string expectedResponseBody(""); 267 HttpHeaderMap expectedResponseHeaders; 268 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 269 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 270 271 TestListener listener(expectedResponseBody, expectedResponseHeaders); 272 273 ObjectDeleter<BUrlRequest> requestDeleter( 274 BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener, 275 context)); 276 BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get()); 277 CPPUNIT_ASSERT(request != NULL); 278 279 request->SetAutoReferrer(false); 280 281 CPPUNIT_ASSERT(request->Run()); 282 while (request->IsRunning()) 283 snooze(1000); 284 285 CPPUNIT_ASSERT_EQUAL(B_OK, request->Status()); 286 287 const BHttpResult& result 288 = dynamic_cast<const BHttpResult&>(request->Result()); 289 CPPUNIT_ASSERT_EQUAL(204, result.StatusCode()); 290 CPPUNIT_ASSERT_EQUAL(BString("No Content"), result.StatusText()); 291 292 listener.Verify(); 293 294 CPPUNIT_ASSERT(!context->GetCookieJar().GetIterator().HasNext()); 295 // This page should not set cookies 296 297 context->ReleaseReference(); 298 } 299 300 301 void 302 HttpTest::ProxyTest() 303 { 304 BUrl testUrl(fTestServer.BaseUrl(), "/"); 305 306 TestProxyServer proxy; 307 CPPUNIT_ASSERT_EQUAL_MESSAGE( 308 "Test proxy server startup", 309 B_OK, 310 proxy.Start()); 311 312 BUrlContext* context = new BUrlContext(); 313 context->AcquireReference(); 314 context->SetProxy("127.0.0.1", proxy.Port()); 315 316 std::string expectedResponseBody( 317 "Path: /\r\n" 318 "\r\n" 319 "Headers:\r\n" 320 "--------\r\n" 321 "Host: 127.0.0.1:PORT\r\n" 322 "Content-Length: 0\r\n" 323 "Accept: */*\r\n" 324 "Accept-Encoding: gzip\r\n" 325 "Connection: close\r\n" 326 "User-Agent: Services Kit (Haiku)\r\n" 327 "X-Forwarded-For: 127.0.0.1:PORT\r\n"); 328 HttpHeaderMap expectedResponseHeaders; 329 expectedResponseHeaders["Content-Encoding"] = "gzip"; 330 expectedResponseHeaders["Content-Length"] = "169"; 331 expectedResponseHeaders["Content-Type"] = "text/plain"; 332 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 333 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 334 335 TestListener listener(expectedResponseBody, expectedResponseHeaders); 336 337 ObjectDeleter<BUrlRequest> requestDeleter( 338 BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener, 339 context)); 340 BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get()); 341 CPPUNIT_ASSERT(request != NULL); 342 343 CPPUNIT_ASSERT(request->Run()); 344 345 while (request->IsRunning()) 346 snooze(1000); 347 348 CPPUNIT_ASSERT_EQUAL(B_OK, request->Status()); 349 350 const BHttpResult& response 351 = dynamic_cast<const BHttpResult&>(request->Result()); 352 CPPUNIT_ASSERT_EQUAL(200, response.StatusCode()); 353 CPPUNIT_ASSERT_EQUAL(BString("OK"), response.StatusText()); 354 CPPUNIT_ASSERT_EQUAL(169, response.Length()); 355 // Fixed size as we know the response format. 356 CPPUNIT_ASSERT(!context->GetCookieJar().GetIterator().HasNext()); 357 // This page should not set cookies 358 359 listener.Verify(); 360 361 context->ReleaseReference(); 362 } 363 364 365 void 366 HttpTest::UploadTest() 367 { 368 std::string testFilePath = TestFilePath("testfile.txt"); 369 370 // The test server will echo the POST body back to us in the HTTP response, 371 // so here we load it into memory so that we can compare to make sure that 372 // the server received it. 373 std::string fileContents; 374 { 375 std::ifstream inputStream( 376 testFilePath.c_str(), 377 std::ios::in | std::ios::binary); 378 CPPUNIT_ASSERT(inputStream); 379 380 inputStream.seekg(0, std::ios::end); 381 fileContents.resize(inputStream.tellg()); 382 383 inputStream.seekg(0, std::ios::beg); 384 inputStream.read(&fileContents[0], fileContents.size()); 385 inputStream.close(); 386 387 CPPUNIT_ASSERT(!fileContents.empty()); 388 } 389 390 std::string expectedResponseBody( 391 "Path: /post\r\n" 392 "\r\n" 393 "Headers:\r\n" 394 "--------\r\n" 395 "Host: 127.0.0.1:PORT\r\n" 396 "Accept: */*\r\n" 397 "Accept-Encoding: gzip\r\n" 398 "Connection: close\r\n" 399 "User-Agent: Services Kit (Haiku)\r\n" 400 "Content-Type: multipart/form-data; boundary=<<BOUNDARY-ID>>\r\n" 401 "Content-Length: 1404\r\n" 402 "\r\n" 403 "Request body:\r\n" 404 "-------------\r\n" 405 "--<<BOUNDARY-ID>>\r\n" 406 "Content-Disposition: form-data; name=\"_uploadfile\";" 407 " filename=\"testfile.txt\"\r\n" 408 "Content-Type: application/octet-stream\r\n" 409 "\r\n" 410 + fileContents 411 + "\r\n" 412 "--<<BOUNDARY-ID>>\r\n" 413 "Content-Disposition: form-data; name=\"hello\"\r\n" 414 "\r\n" 415 "world\r\n" 416 "--<<BOUNDARY-ID>>--\r\n" 417 "\r\n"); 418 HttpHeaderMap expectedResponseHeaders; 419 expectedResponseHeaders["Content-Encoding"] = "gzip"; 420 expectedResponseHeaders["Content-Length"] = "913"; 421 expectedResponseHeaders["Content-Type"] = "text/plain"; 422 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 423 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 424 TestListener listener(expectedResponseBody, expectedResponseHeaders); 425 426 BUrl testUrl(fTestServer.BaseUrl(), "/post"); 427 428 BUrlContext context; 429 430 ObjectDeleter<BUrlRequest> requestDeleter( 431 BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener, 432 &context)); 433 BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get()); 434 CPPUNIT_ASSERT(request != NULL); 435 436 BHttpForm form; 437 form.AddString("hello", "world"); 438 CPPUNIT_ASSERT_EQUAL( 439 B_OK, 440 form.AddFile("_uploadfile", BPath(testFilePath.c_str()))); 441 442 request->SetPostFields(form); 443 444 CPPUNIT_ASSERT(request->Run()); 445 446 while (request->IsRunning()) 447 snooze(1000); 448 449 CPPUNIT_ASSERT_EQUAL(B_OK, request->Status()); 450 451 const BHttpResult &result = 452 dynamic_cast<const BHttpResult &>(request->Result()); 453 CPPUNIT_ASSERT_EQUAL(200, result.StatusCode()); 454 CPPUNIT_ASSERT_EQUAL(BString("OK"), result.StatusText()); 455 CPPUNIT_ASSERT_EQUAL(913, result.Length()); 456 457 listener.Verify(); 458 } 459 460 461 void 462 HttpTest::AuthBasicTest() 463 { 464 BUrlContext context; 465 466 BUrl testUrl(fTestServer.BaseUrl(), "/auth/basic/walter/secret"); 467 468 std::string expectedResponseBody( 469 "Path: /auth/basic/walter/secret\r\n" 470 "\r\n" 471 "Headers:\r\n" 472 "--------\r\n" 473 "Host: 127.0.0.1:PORT\r\n" 474 "Accept: */*\r\n" 475 "Accept-Encoding: gzip\r\n" 476 "Connection: close\r\n" 477 "User-Agent: Services Kit (Haiku)\r\n" 478 "Referer: SCHEME://127.0.0.1:PORT/auth/basic/walter/secret\r\n" 479 "Authorization: Basic d2FsdGVyOnNlY3JldA==\r\n"); 480 481 HttpHeaderMap expectedResponseHeaders; 482 expectedResponseHeaders["Content-Encoding"] = "gzip"; 483 expectedResponseHeaders["Content-Length"] = "212"; 484 expectedResponseHeaders["Content-Type"] = "text/plain"; 485 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 486 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 487 expectedResponseHeaders["Www-Authenticate"] = "Basic realm=\"Fake Realm\""; 488 489 SendAuthenticatedRequest(context, testUrl, expectedResponseBody, 490 expectedResponseHeaders); 491 492 CPPUNIT_ASSERT(!context.GetCookieJar().GetIterator().HasNext()); 493 // This page should not set cookies 494 } 495 496 497 void 498 HttpTest::AuthDigestTest() 499 { 500 BUrlContext context; 501 502 BUrl testUrl(fTestServer.BaseUrl(), "/auth/digest/walter/secret"); 503 504 std::string expectedResponseBody( 505 "Path: /auth/digest/walter/secret\r\n" 506 "\r\n" 507 "Headers:\r\n" 508 "--------\r\n" 509 "Host: 127.0.0.1:PORT\r\n" 510 "Accept: */*\r\n" 511 "Accept-Encoding: gzip\r\n" 512 "Connection: close\r\n" 513 "User-Agent: Services Kit (Haiku)\r\n" 514 "Referer: SCHEME://127.0.0.1:PORT/auth/digest/walter/secret\r\n" 515 "Authorization: Digest username=\"walter\"," 516 " realm=\"user@shredder\"," 517 " nonce=\"f3a95f20879dd891a5544bf96a3e5518\"," 518 " algorithm=MD5," 519 " opaque=\"f0bb55f1221a51b6d38117c331611799\"," 520 " uri=\"/auth/digest/walter/secret\"," 521 " qop=auth," 522 " cnonce=\"60a3d95d286a732374f0f35fb6d21e79\"," 523 " nc=00000001," 524 " response=\"f4264de468aa1a91d81ac40fa73445f3\"\r\n" 525 "Cookie: stale_after=never; fake=fake_value\r\n"); 526 527 HttpHeaderMap expectedResponseHeaders; 528 expectedResponseHeaders["Content-Encoding"] = "gzip"; 529 expectedResponseHeaders["Content-Length"] = "403"; 530 expectedResponseHeaders["Content-Type"] = "text/plain"; 531 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 532 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 533 expectedResponseHeaders["Set-Cookie"] = "fake=fake_value; Path=/"; 534 expectedResponseHeaders["Www-Authenticate"] 535 = "Digest realm=\"user@shredder\", " 536 "nonce=\"f3a95f20879dd891a5544bf96a3e5518\", " 537 "qop=\"auth\", " 538 "opaque=f0bb55f1221a51b6d38117c331611799, " 539 "algorithm=MD5, " 540 "stale=FALSE"; 541 542 SendAuthenticatedRequest(context, testUrl, expectedResponseBody, 543 expectedResponseHeaders); 544 545 std::map<BString, BString> cookies; 546 BNetworkCookieJar::Iterator iter 547 = context.GetCookieJar().GetIterator(); 548 while (iter.HasNext()) { 549 const BNetworkCookie* cookie = iter.Next(); 550 cookies[cookie->Name()] = cookie->Value(); 551 } 552 CPPUNIT_ASSERT_EQUAL(2, cookies.size()); 553 CPPUNIT_ASSERT_EQUAL(BString("fake_value"), cookies["fake"]); 554 CPPUNIT_ASSERT_EQUAL(BString("never"), cookies["stale_after"]); 555 } 556 557 558 void 559 HttpTest::AutoRedirectTest() 560 { 561 _GetTest("/302"); 562 } 563 564 565 /* static */ void 566 HttpTest::AddTests(BTestSuite& parent) 567 { 568 { 569 CppUnit::TestSuite& suite = *new CppUnit::TestSuite("HttpTest"); 570 571 HttpTest* httpTest = new HttpTest(); 572 BThreadedTestCaller<HttpTest>* httpTestCaller 573 = new BThreadedTestCaller<HttpTest>("HttpTest::", httpTest); 574 575 // HTTP + HTTPs 576 AddCommonTests<HttpTest>(*httpTestCaller); 577 578 httpTestCaller->addThread("ProxyTest", &HttpTest::ProxyTest); 579 580 suite.addTest(httpTestCaller); 581 parent.addTest("HttpTest", &suite); 582 } 583 584 { 585 CppUnit::TestSuite& suite = *new CppUnit::TestSuite("HttpsTest"); 586 587 HttpsTest* httpsTest = new HttpsTest(); 588 BThreadedTestCaller<HttpsTest>* httpsTestCaller 589 = new BThreadedTestCaller<HttpsTest>("HttpsTest::", httpsTest); 590 591 // HTTP + HTTPs 592 AddCommonTests<HttpsTest>(*httpsTestCaller); 593 594 suite.addTest(httpsTestCaller); 595 parent.addTest("HttpsTest", &suite); 596 } 597 } 598 599 600 void 601 HttpTest::_GetTest(const BString& path) 602 { 603 BUrl testUrl(fTestServer.BaseUrl(), path); 604 BUrlContext* context = new BUrlContext(); 605 context->AcquireReference(); 606 607 std::string expectedResponseBody( 608 "Path: /\r\n" 609 "\r\n" 610 "Headers:\r\n" 611 "--------\r\n" 612 "Host: 127.0.0.1:PORT\r\n" 613 "Accept: */*\r\n" 614 "Accept-Encoding: gzip\r\n" 615 "Connection: close\r\n" 616 "User-Agent: Services Kit (Haiku)\r\n"); 617 HttpHeaderMap expectedResponseHeaders; 618 expectedResponseHeaders["Content-Encoding"] = "gzip"; 619 expectedResponseHeaders["Content-Length"] = "144"; 620 expectedResponseHeaders["Content-Type"] = "text/plain"; 621 expectedResponseHeaders["Date"] = "Sun, 09 Feb 2020 19:32:42 GMT"; 622 expectedResponseHeaders["Server"] = "Test HTTP Server for Haiku"; 623 624 TestListener listener(expectedResponseBody, expectedResponseHeaders); 625 626 ObjectDeleter<BUrlRequest> requestDeleter( 627 BUrlProtocolRoster::MakeRequest(testUrl, &listener, &listener, 628 context)); 629 BHttpRequest* request = dynamic_cast<BHttpRequest*>(requestDeleter.Get()); 630 CPPUNIT_ASSERT(request != NULL); 631 632 request->SetAutoReferrer(false); 633 634 CPPUNIT_ASSERT(request->Run()); 635 while (request->IsRunning()) 636 snooze(1000); 637 638 CPPUNIT_ASSERT_EQUAL(B_OK, request->Status()); 639 640 const BHttpResult& result 641 = dynamic_cast<const BHttpResult&>(request->Result()); 642 CPPUNIT_ASSERT_EQUAL(200, result.StatusCode()); 643 CPPUNIT_ASSERT_EQUAL(BString("OK"), result.StatusText()); 644 645 CPPUNIT_ASSERT_EQUAL(144, result.Length()); 646 647 listener.Verify(); 648 649 CPPUNIT_ASSERT(!context->GetCookieJar().GetIterator().HasNext()); 650 // This page should not set cookies 651 652 context->ReleaseReference(); 653 } 654 655 656 // # pragma mark - HTTPS 657 658 659 HttpsTest::HttpsTest() 660 : 661 HttpTest(TEST_SERVER_MODE_HTTPS) 662 { 663 } 664