#include "Globals.h" #include "../TestHelpers.h" #include "HTTP/UrlClient.h" #include "OSSupport/NetworkSingleton.h" namespace { // When under debugger, set timeouts to practically "never" so that there's time to step through the code: #ifdef NDEBUG static const UInt32 TIMEOUT = 10000; // 10 seconds #else static const UInt32 TIMEOUT = 0xffffffff; // ~4.3M seconds / ~49 days #endif /** Track number of cCallbacks instances alive. */ std::atomic g_ActiveCallbacks{ 0 }; /** Simple callbacks that dump the events to the console and signalize a cEvent when the request is finished. */ class cCallbacks: public cUrlClient::cCallbacks { public: cCallbacks(std::shared_ptr a_Event): m_Event(std::move(a_Event)) { ++g_ActiveCallbacks; LOGD("Created a cCallbacks instance at %p", reinterpret_cast(this)); } virtual ~cCallbacks() override { --g_ActiveCallbacks; LOGD("Deleting the cCallbacks instance at %p", reinterpret_cast(this)); } virtual void OnConnected(cTCPLink & a_Link) override { LOG("Link connected to %s:%u", a_Link.GetRemoteIP().c_str(), a_Link.GetRemotePort()); } virtual bool OnCertificateReceived() override { LOG("Server certificate received"); return true; } virtual void OnTlsHandshakeCompleted() override { LOG("TLS handshake has completed."); } virtual void OnRequestSent() override { LOG("Request has been sent"); } virtual void OnHeader(const AString & a_Key, const AString & a_Value) override { LOG("HTTP Header: \"%s\" -> \"%s\"", a_Key.c_str(), a_Value.c_str()); } virtual void OnHeadersFinished() override { LOG("HTTP headers finished."); } virtual void OnBodyData(const void * a_Data, size_t a_Size) override { #if 0 // Output the whole received data blob: AString body(reinterpret_cast(a_Data), a_Size); LOG("Body part:\n%s", body.c_str()); #else // Output only the data size, to keep the log short: LOG("Body part: %u bytes", static_cast(a_Size)); #endif } virtual void OnBodyFinished() override { LOG("Body finished."); m_Event->Set(); } virtual void OnRedirecting(const AString & a_RedirectUrl) override { LOG("Redirecting to \"%s\".", a_RedirectUrl.c_str()); } virtual void OnError(const AString & a_ErrorMsg) override { LOG("Error: %s", a_ErrorMsg.c_str()); m_Event->Set(); } protected: std::shared_ptr m_Event; }; /** The trusted root CAs for individual servers. */ namespace TrustedCAs { // DigiCert Global Root CA (sessionserver.mojang.com, api.mojang.com) // Downloaded from https://www.digicert.com/kb/digicert-root-certificates.htm // DigiCert Global Root CA static const char MojangCom[] = "-----BEGIN CERTIFICATE-----\n" "MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n" "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" "QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n" "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n" "b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n" "9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n" "CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n" "nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n" "43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n" "T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n" "gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n" "BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n" "TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n" "DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n" "hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n" "06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n" "PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n" "YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n" "CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n" "-----END CERTIFICATE-----\n"; // The root cert used by github.com static const char GithubCom[] = "-----BEGIN CERTIFICATE-----\n" "MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL\n" "MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl\n" "eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT\n" "JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx\n" "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT\n" "Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg\n" "VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm\n" "aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo\n" "I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng\n" "o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G\n" "A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD\n" "VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB\n" "zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW\n" "RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg=\n" "-----END CERTIFICATE-----\n"; // The root cert used by github.com in the past (no longer used) static const char GithubComOld[] = "-----BEGIN CERTIFICATE-----\n" "MIIEFzCCAv+gAwIBAgIQB/LzXIeod6967+lHmTUlvTANBgkqhkiG9w0BAQwFADBh\n" "MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n" "d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n" "QTAeFw0yMTA0MTQwMDAwMDBaFw0zMTA0MTMyMzU5NTlaMFYxCzAJBgNVBAYTAlVT\n" "MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxMDAuBgNVBAMTJ0RpZ2lDZXJ0IFRMUyBI\n" "eWJyaWQgRUNDIFNIQTM4NCAyMDIwIENBMTB2MBAGByqGSM49AgEGBSuBBAAiA2IA\n" "BMEbxppbmNmkKaDp1AS12+umsmxVwP/tmMZJLwYnUcu/cMEFesOxnYeJuq20ExfJ\n" "qLSDyLiQ0cx0NTY8g3KwtdD3ImnI8YDEe0CPz2iHJlw5ifFNkU3aiYvkA8ND5b8v\n" "c6OCAYIwggF+MBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFAq8CCkXjKU5\n" "bXoOzjPHLrPt+8N6MB8GA1UdIwQYMBaAFAPeUDVW0Uy7ZvCj4hsbw5eyPdFVMA4G\n" "A1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwdgYI\n" "KwYBBQUHAQEEajBoMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j\n" "b20wQAYIKwYBBQUHMAKGNGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp\n" "Q2VydEdsb2JhbFJvb3RDQS5jcnQwQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2Ny\n" "bDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0R2xvYmFsUm9vdENBLmNybDA9BgNVHSAE\n" "NjA0MAsGCWCGSAGG/WwCATAHBgVngQwBATAIBgZngQwBAgEwCAYGZ4EMAQICMAgG\n" "BmeBDAECAzANBgkqhkiG9w0BAQwFAAOCAQEAR1mBf9QbH7Bx9phdGLqYR5iwfnYr\n" "6v8ai6wms0KNMeZK6BnQ79oU59cUkqGS8qcuLa/7Hfb7U7CKP/zYFgrpsC62pQsY\n" "kDUmotr2qLcy/JUjS8ZFucTP5Hzu5sn4kL1y45nDHQsFfGqXbbKrAjbYwrwsAZI/\n" "BKOLdRHHuSm8EdCGupK8JvllyDfNJvaGEwwEqonleLHBTnm8dqMLUeTF0J5q/hos\n" "Vq4GNiejcxwIfZMy0MJEGdqN9A57HSgDKwmKdsp33Id6rHtSJlWncg+d0ohP/rEh\n" "xRqhqjn1VtvChMQ1H3Dau0bwhr9kAMQ+959GG50jBbl9s08PqUU643QwmA==\n" "-----END CERTIFICATE-----\n"; // The root cert used by cuberite.org static const char CuberiteOrg[] = "-----BEGIN CERTIFICATE-----\n" "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n" "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n" "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n" "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n" "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n" "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n" "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n" "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n" "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n" "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n" "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n" "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n" "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n" "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n" "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n" "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n" "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n" "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n" "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n" "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n" "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n" "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n" "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n" "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n" "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n" "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n" "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n" "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n" "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n" "-----END CERTIFICATE-----\n"; } int TestRequest1() { LOG("Running test 1 - fetch http://github.com without redirects"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["MaxRedirects"] = "0"; auto res = cUrlClient::Get("http://github.com", std::move(callbacks), AStringMap(), AString(), std::move(options)); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest2() { LOG("Running test 2 - default fetch http://github.com"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); auto res = cUrlClient::Get("http://github.com", std::move(callbacks)); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest3() { LOG("Running test 3 - fetch https://github.com without redirects"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["MaxRedirects"] = "0"; auto res = cUrlClient::Get("https://github.com", std::move(callbacks), AStringMap(), AString(), std::move(options)); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest4() { LOG("Running test 4 - fetch https://github.com with GitHub trusted root CA"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["TrustedRootCAs"] = TrustedCAs::GithubCom; auto res = cUrlClient::Get("https://github.com", std::move(callbacks), {}, {}, options); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest5() { LOG("Running test 5 - fetch https://cuberite.org with Cuberite trusted root CA"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["TrustedRootCAs"] = TrustedCAs::CuberiteOrg; auto res = cUrlClient::Get("https://cuberite.org", std::move(callbacks), {}, {}, options); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest6() { LOG("Running test 6 - fetch https://sessionserver.mojang.com with Mojang trusted root CA"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["TrustedRootCAs"] = TrustedCAs::MojangCom; auto res = cUrlClient::Get("https://sessionserver.mojang.com", std::move(callbacks), {}, {}, options); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest7() { LOG("Running test 7 - fetch https://api.mojang.com with Mojang trusted root CA"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["TrustedRootCAs"] = TrustedCAs::MojangCom; auto res = cUrlClient::Get("https://api.mojang.com", std::move(callbacks), {}, {}, options); if (res.first) { if (!evtFinished->Wait(TIMEOUT)) { LOG("Aborting the wait for response; failing the test."); return 1; } } else { LOG("Immediate error: %s", res.second.c_str()); return 1; } return 0; } int TestRequest8() { LOG("Running test 8 - fetch https://api.mojang.com with GitHub trusted root CA (testing CA verification rejection)"); auto evtFinished = std::make_shared(); auto callbacks = std::make_unique(evtFinished); AStringMap options; options["TrustedRootCAs"] = TrustedCAs::GithubCom; auto [isSuccess, response] = cUrlClient::BlockingGet("https://api.mojang.com", {}, {}, options); if (isSuccess) { LOG("CA verification failure, should have rejected the connection due to bad root CA."); return 1; } return 0; } int TestRequests() { using func_t = int(void); func_t * tests[] = { &TestRequest1, &TestRequest2, &TestRequest3, &TestRequest4, &TestRequest5, &TestRequest6, &TestRequest7, &TestRequest8, }; for (auto test: tests) { LOG("%s", AString(60, '-').c_str()); auto res = test(); if (res != 0) { return res; } } return 0; } } // namespace (anonymous) IMPLEMENT_TEST_MAIN("UrlClient", LOG("Initializing cNetwork..."); cNetworkSingleton::Get().Initialise(); LOG("Testing..."); TEST_EQUAL(TestRequests(), 0); LOG("Terminating cNetwork..."); cNetworkSingleton::Get().Terminate(); // No leaked callback instances LOG("cCallback instances still alive: %d", g_ActiveCallbacks.load()); TEST_EQUAL(g_ActiveCallbacks, 0); )