// Tests for oatpp-authkit/mail/SmtpTransport.hpp. // // Covers the pure, network-free surface: // - base64Encode against RFC 4648 vectors // - hasHeaderInjectionChars // - send() rejects CR/LF/NUL in recipient / from address BEFORE touching // libcurl (the SMTP header-injection guard) — no live mail server needed, // the validation short-circuits ahead of curl_easy_init / perform. #include "oatpp-authkit/mail/SmtpTransport.hpp" #include #include namespace { int g_failures = 0; #define REQUIRE(expr) do { \ if (!(expr)) { \ std::fprintf(stderr, "FAIL %s:%d %s\n", __FILE__, __LINE__, #expr); \ ++g_failures; \ } \ } while (0) using namespace oatpp_authkit::mail; void test_base64_rfc4648_vectors() { REQUIRE(base64Encode("") == ""); REQUIRE(base64Encode("f") == "Zg=="); REQUIRE(base64Encode("fo") == "Zm8="); REQUIRE(base64Encode("foo") == "Zm9v"); REQUIRE(base64Encode("foob") == "Zm9vYg=="); REQUIRE(base64Encode("fooba") == "Zm9vYmE="); REQUIRE(base64Encode("foobar") == "Zm9vYmFy"); } void test_header_injection_detector() { REQUIRE(!hasHeaderInjectionChars("a@b.com")); REQUIRE( hasHeaderInjectionChars("a@b.com\r\nBcc: evil@x.com")); REQUIRE( hasHeaderInjectionChars("a@b.com\n")); REQUIRE( hasHeaderInjectionChars("a@b.com\r")); REQUIRE( hasHeaderInjectionChars(std::string("a@b.com\0x", 9))); // embedded NUL } void test_send_rejects_crlf_in_addresses() { SmtpConfig cfg; cfg.host = "localhost"; cfg.fromAddress = "noreply@example.com"; // CRLF in recipient → rejected with no network call. std::string r1 = send("victim@example.com\r\nBcc: evil@x.com", "subject", "

hi

", {}, cfg); REQUIRE(r1.find("invalid recipient") != std::string::npos); // CRLF in from address → rejected. SmtpConfig cfg2 = cfg; cfg2.fromAddress = "noreply@example.com\r\nSubject: spoofed"; std::string r2 = send("victim@example.com", "subject", "

hi

", {}, cfg2); REQUIRE(r2.find("invalid from") != std::string::npos); // Empty-config guards still fire (and come before the address checks). SmtpConfig empty; REQUIRE(send("a@b.com", "s", "b", {}, empty).find("no host") != std::string::npos); } } // namespace int main() { test_base64_rfc4648_vectors(); test_header_injection_detector(); test_send_rejects_crlf_in_addresses(); std::printf("%s (%d failures)\n", g_failures ? "FAIL" : "OK", g_failures); return g_failures ? 1 : 0; }