// Network integration test hitting a live PrivateBin instance (optional) // Server under test: https://privatebin.medisoftware.org/ // Enable by setting environment variable PRIVATEBIN_IT=1 #include "privatebinapi.h" #include "json_parser.h" #include "http_client.h" #include #include #include #include #include #include #include #include static bool extract_paste_id_and_key(const std::string& full_url, std::string& paste_id, std::string& key) { // Expected format: https://host/path?PASTEID#BASE58_KEY const std::size_t qpos = full_url.find('?'); const std::size_t hpos = full_url.find('#'); if (qpos == std::string::npos || hpos == std::string::npos || hpos <= qpos + 1) { return false; } paste_id = full_url.substr(qpos + 1, hpos - (qpos + 1)); key = full_url.substr(hpos + 1); return !paste_id.empty() && !key.empty(); } int main() { // --------------------- Lightweight unit coverage section --------------------- { std::cout << "[test] unit: JsonParser create/parse roundtrip..." << std::endl; std::vector ct = {'A','B','C'}; std::vector tag; // empty optional std::vector iv = {'I','V'}; std::vector salt = {'S','A','L','T'}; auto j = JsonParser::create_paste_json(ct, tag, iv, salt, "5min", "plaintext", false, false); std::vector out_ct, out_tag, out_iv, out_salt; std::string out_exp; (void)JsonParser::parse_paste_json(j, out_ct, out_tag, out_iv, out_salt, out_exp); int status = 0; std::string msg, pid, url, del; (void)JsonParser::parse_response(std::string("{\"status\":0,\"id\":\"x\",\"url\":\"u\",\"deletetoken\":\"d\"}"), status, msg, pid, url, del); } { std::cout << "[test] unit: HttpClient GET to example.com..." << std::endl; HttpClient c; std::string resp; (void)c.get("https://example.com/", resp); // ignore success, just exercise code path } { std::cout << "[test] unit: privatebinapi create/get/delete error-paths..." << std::endl; const char* server = "https://127.0.0.1:9/"; // closed port to force network error const char* content = "unit-test-content"; char* out_url = nullptr; char* del_token = nullptr; int rc = create_paste(server, content, nullptr, "5min", "plaintext", 0, 0, &out_url, &del_token); (void)rc; // expect non-zero due to network; crypto/JSON path still exercised char* out_content = nullptr; rc = get_paste(server, "deadbeef", "ABCDEFG", &out_content); (void)rc; rc = delete_paste(server, "deadbeef", "tok"); (void)rc; if (out_url) free_string(out_url); if (del_token) free_string(del_token); if (out_content) free_string(out_content); } // --------------------- Optional integration section --------------------- char* it = nullptr; size_t len = 0; _dupenv_s(&it, &len, "PRIVATEBIN_IT"); if (!it || std::string(it) == "0") { std::cout << "[test] PRIVATEBIN_IT not set; skipping integration test." << std::endl; free(it); return 0; // success } // Allow overriding server via env, fallback to public test instance char* srv = nullptr; size_t srvlen = 0; _dupenv_s(&srv, &srvlen, "PRIVATEBIN_SERVER"); const char* server = (srv && *srv) ? srv : "https://privatebin.medisoftware.org/"; const char* password = nullptr; // no password const char* expiration = "5min"; // short-lived const int burn_after_reading = 0; const int open_discussion = 0; std::cout << "[test] Testing different text formats..." << std::endl; // Test 1: Plaintext format std::cout << "[test] 1. Testing plaintext format..." << std::endl; const char* plaintext_content = "Integration test from lib-privatebin - plaintext"; const char* plaintext_format = "plaintext"; char* paste_url = nullptr; char* delete_token = nullptr; int rc = create_paste(server, plaintext_content, password, expiration, plaintext_format, burn_after_reading, open_discussion, &paste_url, &delete_token); if (rc != 0 || paste_url == nullptr || delete_token == nullptr) { std::cerr << "[test][ERROR] plaintext create_paste failed, rc=" << rc << std::endl; if (paste_url) free_string(paste_url); if (delete_token) free_string(delete_token); return 1; } std::string full_url = paste_url; std::string paste_id; std::string key; const bool ok_parse = extract_paste_id_and_key(full_url, paste_id, key); if (!ok_parse) { std::cerr << "[test][ERROR] failed to parse paste id/key from URL: " << full_url << std::endl; free_string(paste_url); free_string(delete_token); return 1; } std::cout << "[test] get_paste... id=" << paste_id << std::endl; char* fetched = nullptr; rc = get_paste(server, paste_id.c_str(), key.c_str(), &fetched); if (rc != 0 || fetched == nullptr) { std::cerr << "[test][ERROR] get_paste failed, rc=" << rc << std::endl; free_string(paste_url); free_string(delete_token); return 1; } std::string fetched_str = fetched; assert(fetched_str == plaintext_content && "fetched plaintext content mismatch"); std::cout << "[test] delete_paste..." << std::endl; rc = delete_paste(server, paste_id.c_str(), delete_token); if (rc != 0) { std::cerr << "[test][ERROR] delete_paste failed, rc=" << rc << std::endl; free_string(paste_url); free_string(delete_token); free_string(fetched); return 1; } // cleanup plaintext test free_string(paste_url); free_string(delete_token); free_string(fetched); // Respect server rate limit between submissions std::this_thread::sleep_for(std::chrono::seconds(12)); // Test 2: Syntax highlighting format std::cout << "[test] 2. Testing syntax highlighting format..." << std::endl; const char* code_content = R"( #include int main() { std::cout << "Hello, World!" << std::endl; return 0; } )"; const char* code_format = "syntaxhighlighting"; char* code_paste_url = nullptr; char* code_delete_token = nullptr; rc = create_paste(server, code_content, password, expiration, code_format, burn_after_reading, open_discussion, &code_paste_url, &code_delete_token); if (rc != 0 || code_paste_url == nullptr || code_delete_token == nullptr) { std::cerr << "[test][ERROR] syntax highlighting create_paste failed, rc=" << rc << std::endl; if (code_paste_url) free_string(code_paste_url); if (code_delete_token) free_string(code_delete_token); return 1; } // Parse code paste URL std::string code_full_url = code_paste_url; std::string code_paste_id; std::string code_key; const bool code_ok_parse = extract_paste_id_and_key(code_full_url, code_paste_id, code_key); if (!code_ok_parse) { std::cerr << "[test][ERROR] failed to parse code paste id/key from URL: " << code_full_url << std::endl; free_string(code_paste_url); free_string(code_delete_token); return 1; } // Fetch and verify code paste char* code_fetched = nullptr; rc = get_paste(server, code_paste_id.c_str(), code_key.c_str(), &code_fetched); if (rc != 0 || code_fetched == nullptr) { std::cerr << "[test][ERROR] code get_paste failed, rc=" << rc << std::endl; free_string(code_paste_url); free_string(code_delete_token); return 1; } std::string code_fetched_str = code_fetched; assert(code_fetched_str == code_content && "fetched code content mismatch"); // Delete code paste rc = delete_paste(server, code_paste_id.c_str(), code_delete_token); if (rc != 0) { std::cerr << "[test][ERROR] code delete_paste failed, rc=" << rc << std::endl; free_string(code_paste_url); free_string(code_delete_token); free_string(code_fetched); return 1; } // cleanup code test free_string(code_paste_url); free_string(code_delete_token); free_string(code_fetched); // Pause again before next submission std::this_thread::sleep_for(std::chrono::seconds(12)); // Test 3: Markdown format std::cout << "[test] 3. Testing markdown format..." << std::endl; const char* markdown_content = R"( # Test Header This is a **bold** and *italic* text. ## Code Block ```cpp int x = 42; std::cout << x << std::endl; ``` > This is a blockquote. )"; const char* markdown_format = "markdown"; char* markdown_paste_url = nullptr; char* markdown_delete_token = nullptr; rc = create_paste(server, markdown_content, password, expiration, markdown_format, burn_after_reading, open_discussion, &markdown_paste_url, &markdown_delete_token); if (rc != 0 || markdown_paste_url == nullptr || markdown_delete_token == nullptr) { std::cerr << "[test][ERROR] markdown create_paste failed, rc=" << rc << std::endl; if (markdown_paste_url) free_string(markdown_paste_url); if (markdown_delete_token) free_string(markdown_delete_token); return 1; } // Parse markdown paste URL std::string markdown_full_url = markdown_paste_url; std::string markdown_paste_id; std::string markdown_key; const bool markdown_ok_parse = extract_paste_id_and_key(markdown_full_url, markdown_paste_id, markdown_key); if (!markdown_ok_parse) { std::cerr << "[test][ERROR] failed to parse markdown paste id/key from URL: " << markdown_full_url << std::endl; free_string(markdown_paste_url); free_string(markdown_delete_token); return 1; } // Fetch and verify markdown paste char* markdown_fetched = nullptr; rc = get_paste(server, markdown_paste_id.c_str(), markdown_key.c_str(), &markdown_fetched); if (rc != 0 || markdown_fetched == nullptr) { std::cerr << "[test][ERROR] markdown get_paste failed, rc=" << rc << std::endl; free_string(markdown_paste_url); free_string(markdown_delete_token); return 1; } std::string markdown_fetched_str = markdown_fetched; assert(markdown_fetched_str == markdown_content && "fetched markdown content mismatch"); // Delete markdown paste rc = delete_paste(server, markdown_paste_id.c_str(), markdown_delete_token); if (rc != 0) { std::cerr << "[test][ERROR] markdown delete_paste failed, rc=" << rc << std::endl; free_string(markdown_paste_url); free_string(markdown_delete_token); free_string(markdown_fetched); return 1; } // cleanup markdown test free_string(markdown_paste_url); free_string(markdown_delete_token); free_string(markdown_fetched); // Pause again before next submission std::this_thread::sleep_for(std::chrono::seconds(12)); // Test 4: File upload via API { std::cout << "[test] 4. Testing file upload..." << std::endl; // Create temporary file const char* tmpName = "test_upload_tmp.txt"; { std::ofstream ofs(tmpName, std::ios::binary); ofs << "File-Upload via privatebinapi test\n0123456789"; } char* fu_url = nullptr; char* fu_del = nullptr; int frc = upload_file(server, tmpName, nullptr, "5min", 0, 0, &fu_url, &fu_del); if (frc != 0 || !fu_url || !fu_del) { std::cerr << "[test][ERROR] upload_file failed, rc=" << frc << std::endl; if (fu_url) free_string(fu_url); if (fu_del) free_string(fu_del); if (srv) free(srv); free(it); return 1; } std::string full_url2 = fu_url; std::string paste_id2; std::string key2; const bool ok2 = extract_paste_id_and_key(full_url2, paste_id2, key2); if (!ok2) { std::cerr << "[test][ERROR] failed to parse file paste id/key from URL: " << full_url2 << std::endl; free_string(fu_url); free_string(fu_del); if (srv) free(srv); free(it); return 1; } char* fetched2 = nullptr; frc = get_paste(server, paste_id2.c_str(), key2.c_str(), &fetched2); if (frc != 0 || !fetched2) { std::cerr << "[test][ERROR] get_paste (file) failed, rc=" << frc << std::endl; free_string(fu_url); free_string(fu_del); if (srv) free(srv); free(it); return 1; } std::string fetched2_str = fetched2; // Basic content check if (fetched2_str.find("File-Upload via privatebinapi test") == std::string::npos) { std::cerr << "[test][ERROR] fetched file content mismatch" << std::endl; free_string(fu_url); free_string(fu_del); free_string(fetched2); if (srv) free(srv); free(it); return 1; } // Cleanup on server frc = delete_paste(server, paste_id2.c_str(), fu_del); if (frc != 0) { std::cerr << "[test][WARN] delete_paste(file) returned rc=" << frc << std::endl; } free_string(fu_url); free_string(fu_del); free_string(fetched2); // Remove local tmp std::remove(tmpName); } std::cout << "[test] All format & file tests passed successfully!" << std::endl; if (srv) free(srv); return 0; }