'Generic Commit'
Some checks failed
Build-Test-Publish / build (push) Failing after 20s

This commit is contained in:
Your Name 2025-06-17 21:51:36 +12:00
parent b4542c24be
commit f20c8ff330
6 changed files with 254 additions and 173 deletions

View File

@ -67,6 +67,10 @@ FROM scratch AS project
ARG PROJECT ARG PROJECT
# Copy CA certificates for SSL validation
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/
# Copy the actual binary from the regular directory # Copy the actual binary from the regular directory
COPY --from=builder /output/${PROJECT} /${PROJECT} COPY --from=builder /output/${PROJECT} /${PROJECT}

View File

@ -1,7 +1,6 @@
#include "GetbinClient.hpp" #include "GetbinClient.hpp"
#include <drogon/HttpClient.h> #include <drogon/HttpClient.h>
#include <drogon/HttpAppFramework.h> #include <trantor/net/EventLoop.h>
#include <drogon/utils/Utilities.h>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -14,89 +13,108 @@
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
#include <vector>
#include <ctime>
using json = nlohmann::json; using json = nlohmann::json;
static constexpr const char* SERVER_HOST = "getpkg.xyz"; static constexpr const char* SERVER_HOST = "getpkg.xyz";
// Global flag to track if event loop is running static std::string find_ca_certificates() {
static std::atomic<bool> g_eventLoopRunning{false}; // Common CA certificate locations across different Linux distributions
static std::thread g_eventLoopThread; const std::vector<std::string> ca_paths = {
static std::mutex g_eventLoopMutex; "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Raspbian
static std::condition_variable g_eventLoopCv; "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL/CentOS
"/etc/ssl/ca-bundle.pem", // OpenSUSE
static void ensureEventLoop() { "/etc/pki/tls/cert.pem", // Fedora/RHEL alternative
static std::once_flag initFlag; "/etc/ssl/certs/ca-bundle.crt", // Some distros
std::call_once(initFlag, []() { "/etc/ssl/cert.pem", // Alpine Linux
g_eventLoopThread = std::thread([]() { "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD
// Start the event loop in a separate thread "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7+
drogon::app().getLoop()->queueInLoop([]() { "/etc/ca-certificates/extracted/tls-ca-bundle.pem" // Arch Linux
std::unique_lock<std::mutex> lock(g_eventLoopMutex); };
g_eventLoopRunning = true;
g_eventLoopCv.notify_all();
});
drogon::app().run();
});
// Wait for event loop to start
std::unique_lock<std::mutex> lock(g_eventLoopMutex);
g_eventLoopCv.wait(lock, []() { return g_eventLoopRunning.load(); });
});
}
GetbinClient::GetbinClient() {
ensureEventLoop();
}
bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) {
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
std::string object_path = "/object/" + toolName + ":" + arch;
auto req = drogon::HttpRequest::newHttpRequest(); for (const auto& path : ca_paths) {
req->setMethod(drogon::Get); std::ifstream file(path);
req->setPath(object_path); if (file.good()) {
file.close();
std::atomic<bool> success(false); return path;
std::atomic<bool> completed(false);
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) {
std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl;
completed = true;
return;
} }
std::ofstream ofs(outPath, std::ios::binary);
if (!ofs) {
completed = true;
return;
}
const auto& body = response->getBody();
ofs.write(body.data(), body.size());
success = ofs.good();
completed = true;
});
// Wait for the async request to complete with timeout
auto start = std::chrono::steady_clock::now();
const auto timeout = std::chrono::seconds(30); // 30 second timeout
while (!completed) {
if (std::chrono::steady_clock::now() - start > timeout) {
std::cerr << "[GetbinClient::download] Request timed out after 30 seconds." << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
return "";
}
GetbinClient::GetbinClient() {}
bool GetbinClient::download(const std::string& toolName, const std::string& arch, const std::string& outPath) {
bool success = false;
bool done = false;
std::mutex mtx;
std::condition_variable cv;
std::thread worker([&]() {
trantor::EventLoop loop;
auto client = drogon::HttpClient::newHttpClient(
"https://" + std::string(SERVER_HOST),
&loop
);
// Configure SSL certificates for HTTPS
std::string ca_path = find_ca_certificates();
if (!ca_path.empty()) {
std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
// Use addSSLConfigs with proper parameter names for OpenSSL
std::vector<std::pair<std::string, std::string>> sslConfigs;
sslConfigs.push_back({"VerifyCAFile", ca_path});
client->addSSLConfigs(sslConfigs);
} else {
// If no CA certificates found, print warning but continue
std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
}
client->enableCookies();
client->setUserAgent("getpkg/1.0");
std::string object_path = "/object/" + toolName + ":" + arch;
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->setPath(object_path);
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
std::lock_guard<std::mutex> lock(mtx);
if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
std::ofstream ofs(outPath, std::ios::binary);
if (ofs) {
const auto& body = response->getBody();
ofs.write(body.data(), body.size());
success = ofs.good();
}
} else {
std::cerr << "[GetbinClient::download] HTTP request failed." << std::endl;
}
done = true;
cv.notify_one();
loop.quit();
}, 30.0); // 30 second timeout
loop.loop();
});
// Wait for completion
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return done; });
}
worker.join();
return success; return success;
} }
bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) { bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, std::string& outHash, const std::string& token) {
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST)); // Read file first
// Read file
std::ifstream ifs(archivePath, std::ios::binary); std::ifstream ifs(archivePath, std::ios::binary);
if (!ifs) { if (!ifs) {
std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl; std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl;
@ -104,131 +122,181 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
} }
std::string file_content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>()); std::string file_content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
// Compose metadata (minimal, can be extended) // Compose metadata
json metadata = { {"labeltags", json::array()} }; json metadata = { {"labeltags", json::array()} };
// Try to extract tool:arch from filename
std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1); std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1);
size_t dot = filename.find('.'); size_t dot = filename.find('.');
std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename; std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename;
metadata["labeltags"].push_back(labeltag); metadata["labeltags"].push_back(labeltag);
// Create request with raw body upload (simplified approach) bool success = false;
auto req = drogon::HttpRequest::newHttpRequest(); bool done = false;
req->setMethod(drogon::Put); std::mutex mtx;
req->setPath("/upload"); std::condition_variable cv;
req->addHeader("Authorization", "Bearer " + token);
req->addHeader("Content-Type", "application/gzip");
req->addHeader("X-Metadata", metadata.dump());
req->setBody(file_content);
std::atomic<bool> success(false); std::thread worker([&]() {
std::atomic<bool> completed(false); trantor::EventLoop loop;
std::string response_body;
int status_code = 0; auto client = drogon::HttpClient::newHttpClient(
"https://" + std::string(SERVER_HOST),
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { &loop
if (result != drogon::ReqResult::Ok || !response) { );
std::cerr << "[GetbinClient::upload] HTTP /upload request failed (did not get OK response)." << std::endl;
if (response) { // Configure SSL certificates
std::cerr << response->getStatusCode() << std::endl; std::string ca_path = find_ca_certificates();
std::cerr << response->getBody() << std::endl; if (!ca_path.empty()) {
std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
std::vector<std::pair<std::string, std::string>> sslConfigs;
sslConfigs.push_back({"VerifyCAFile", ca_path});
client->addSSLConfigs(sslConfigs);
} else {
std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
}
client->enableCookies();
client->setUserAgent("getpkg/1.0");
// Create upload file from memory content
// First save content to a temporary file since UploadFile expects a file path
std::string temp_file = "/tmp/getpkg_upload_" + std::to_string(std::time(nullptr)) + ".tgz";
std::ofstream temp_ofs(temp_file, std::ios::binary);
if (!temp_ofs) {
std::cerr << "[GetbinClient::upload] Failed to create temporary file: " << temp_file << std::endl;
success = false;
done = true;
cv.notify_one();
loop.quit();
return;
}
temp_ofs.write(file_content.data(), file_content.size());
temp_ofs.close();
// Create upload request with file
drogon::UploadFile upload_file(temp_file);
auto req = drogon::HttpRequest::newFileUploadRequest({upload_file});
req->setMethod(drogon::Put);
req->setPath("/upload");
req->addHeader("Authorization", "Bearer " + token);
// Add metadata as form parameter
req->setParameter("metadata", metadata.dump());
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
std::lock_guard<std::mutex> lock(mtx);
if (result == drogon::ReqResult::Ok && response) {
int status_code = static_cast<int>(response->getStatusCode());
std::string response_body(response->getBody());
if (status_code == 200 || status_code == 201) {
try {
auto resp_json = json::parse(response_body);
if (resp_json.contains("url")) outUrl = resp_json["url"].get<std::string>();
if (resp_json.contains("hash")) outHash = resp_json["hash"].get<std::string>();
success = true;
} catch (const std::exception& e) {
std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl;
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
}
} else {
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
}
} else {
std::cerr << "[GetbinClient::upload] HTTP /upload request failed." << std::endl;
} }
completed = true; done = true;
return; cv.notify_one();
} loop.quit();
}, 60.0); // 60 second timeout
status_code = static_cast<int>(response->getStatusCode()); loop.loop();
response_body = response->getBody();
if (status_code != 200 && status_code != 201) { // Clean up temporary file
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl; std::remove(temp_file.c_str());
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
completed = true;
return;
}
// Parse response for URL/hash
try {
auto resp_json = json::parse(response_body);
if (resp_json.contains("url")) outUrl = resp_json["url"].get<std::string>();
if (resp_json.contains("hash")) outHash = resp_json["hash"].get<std::string>();
success = true;
} catch (const std::exception& e) {
std::cerr << "[GetbinClient::upload] Failed to parse JSON response: " << e.what() << std::endl;
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
} catch (...) {
std::cerr << "[GetbinClient::upload] Unknown error while parsing JSON response." << std::endl;
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
}
completed = true;
}); });
// Wait for the async request to complete with timeout // Wait for completion
auto start = std::chrono::steady_clock::now(); {
const auto timeout = std::chrono::seconds(60); // 60 second timeout for upload std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return done; });
while (!completed) {
if (std::chrono::steady_clock::now() - start > timeout) {
std::cerr << "[GetbinClient::upload] Request timed out after 60 seconds." << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
worker.join();
return success; return success;
} }
bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) { bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST)); bool success = false;
std::string exists_path = "/exists/" + toolName + ":" + arch; bool done = false;
std::mutex mtx;
std::condition_variable cv;
auto req = drogon::HttpRequest::newHttpRequest(); std::thread worker([&]() {
req->setMethod(drogon::Get); trantor::EventLoop loop;
req->setPath(exists_path);
auto client = drogon::HttpClient::newHttpClient(
std::atomic<bool> success(false); "https://" + std::string(SERVER_HOST),
std::atomic<bool> completed(false); &loop
std::string response_body; );
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) { // Configure SSL certificates
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) { std::string ca_path = find_ca_certificates();
completed = true; if (!ca_path.empty()) {
return; std::cerr << "[GetbinClient] Found CA certificates at: " << ca_path << std::endl;
std::vector<std::pair<std::string, std::string>> sslConfigs;
sslConfigs.push_back({"VerifyCAFile", ca_path});
client->addSSLConfigs(sslConfigs);
} else {
std::cerr << "[GetbinClient] Warning: No system CA certificates found. SSL verification may fail." << std::endl;
} }
response_body = response->getBody(); client->enableCookies();
client->setUserAgent("getpkg/1.0");
// Try to parse hash from response body (assume plain text or JSON) std::string hash_path = "/hash/" + toolName + ":" + arch;
try {
// Try JSON auto req = drogon::HttpRequest::newHttpRequest();
auto resp_json = json::parse(response_body); req->setMethod(drogon::Get);
if (resp_json.contains("hash")) { req->setPath(hash_path);
outHash = resp_json["hash"].get<std::string>();
success = true; client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
completed = true; std::lock_guard<std::mutex> lock(mtx);
return; if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
std::string response_body(response->getBody());
// Try to parse hash from response body
try {
// Try JSON first
auto resp_json = json::parse(response_body);
if (resp_json.contains("hash")) {
outHash = resp_json["hash"].get<std::string>();
success = true;
}
} catch (...) {
// Not JSON, treat as plain text
outHash = response_body;
// Remove trailing newline if present
if (!outHash.empty() && outHash.back() == '\n') {
outHash.pop_back();
}
success = !outHash.empty();
}
} }
} catch (...) { done = true;
// Not JSON, treat as plain text cv.notify_one();
outHash = response_body; loop.quit();
success = !outHash.empty(); }, 10.0); // 10 second timeout
completed = true;
return; loop.loop();
}
completed = true;
}); });
// Wait for the async request to complete with timeout // Wait for completion
auto start = std::chrono::steady_clock::now(); {
const auto timeout = std::chrono::seconds(10); // 10 second timeout for hash check std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return done; });
while (!completed) {
if (std::chrono::steady_clock::now() - start > timeout) {
std::cerr << "[GetbinClient::getHash] Request timed out after 10 seconds." << std::endl;
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
} }
worker.join();
return success; return success;
} }

View File

@ -292,7 +292,10 @@ fi
# Test 13: Invalid tool name validation # Test 13: Invalid tool name validation
echo -e "\nTest 13: Invalid tool name validation" echo -e "\nTest 13: Invalid tool name validation"
INVALID_OUTPUT=$(timeout 3 "$GETPKG" install "../evil-tool" 2>&1) || INVALID_OUTPUT="" INVALID_OUTPUT=$(timeout 3 "$GETPKG" install "../evil-tool" 2>&1)
INVALID_EXIT_CODE=$?
echo "Invalid tool output: $INVALID_OUTPUT"
echo "Invalid tool exit code: $INVALID_EXIT_CODE"
if [[ "$INVALID_OUTPUT" =~ "Invalid tool name" ]]; then if [[ "$INVALID_OUTPUT" =~ "Invalid tool name" ]]; then
print_test_result "Invalid tool name rejection" 0 print_test_result "Invalid tool name rejection" 0
else else

View File

@ -0,0 +1,2 @@
#!/bin/bash
echo 'Setup complete.'

View File

@ -0,0 +1,2 @@
#!/bin/bash
echo 'Setup complete.'

View File

@ -0,0 +1,2 @@
#!/bin/bash
echo 'Setup complete.'