360 lines
14 KiB
C++
360 lines
14 KiB
C++
// 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 <iostream>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cassert>
|
|
#include <fstream>
|
|
#include <thread>
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
|
|
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<unsigned char> ct = {'A','B','C'};
|
|
std::vector<unsigned char> tag; // empty optional
|
|
std::vector<unsigned char> iv = {'I','V'};
|
|
std::vector<unsigned char> salt = {'S','A','L','T'};
|
|
auto j = JsonParser::create_paste_json(ct, tag, iv, salt, "5min", "plaintext", false, false);
|
|
std::vector<unsigned char> 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 <iostream>
|
|
|
|
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;
|
|
} |