'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
# 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 --from=builder /output/${PROJECT} /${PROJECT}

View File

@ -1,7 +1,6 @@
#include "GetbinClient.hpp"
#include <drogon/HttpClient.h>
#include <drogon/HttpAppFramework.h>
#include <drogon/utils/Utilities.h>
#include <trantor/net/EventLoop.h>
#include <fstream>
#include <sstream>
#include <nlohmann/json.hpp>
@ -14,89 +13,108 @@
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <ctime>
using json = nlohmann::json;
static constexpr const char* SERVER_HOST = "getpkg.xyz";
// Global flag to track if event loop is running
static std::atomic<bool> g_eventLoopRunning{false};
static std::thread g_eventLoopThread;
static std::mutex g_eventLoopMutex;
static std::condition_variable g_eventLoopCv;
static std::string find_ca_certificates() {
// Common CA certificate locations across different Linux distributions
const std::vector<std::string> ca_paths = {
"/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Raspbian
"/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL/CentOS
"/etc/ssl/ca-bundle.pem", // OpenSUSE
"/etc/pki/tls/cert.pem", // Fedora/RHEL alternative
"/etc/ssl/certs/ca-bundle.crt", // Some distros
"/etc/ssl/cert.pem", // Alpine Linux
"/usr/local/share/certs/ca-root-nss.crt", // FreeBSD
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7+
"/etc/ca-certificates/extracted/tls-ca-bundle.pem" // Arch Linux
};
static void ensureEventLoop() {
static std::once_flag initFlag;
std::call_once(initFlag, []() {
g_eventLoopThread = std::thread([]() {
// Start the event loop in a separate thread
drogon::app().getLoop()->queueInLoop([]() {
std::unique_lock<std::mutex> lock(g_eventLoopMutex);
g_eventLoopRunning = true;
g_eventLoopCv.notify_all();
});
drogon::app().run();
});
for (const auto& path : ca_paths) {
std::ifstream file(path);
if (file.good()) {
file.close();
return path;
}
}
// Wait for event loop to start
std::unique_lock<std::mutex> lock(g_eventLoopMutex);
g_eventLoopCv.wait(lock, []() { return g_eventLoopRunning.load(); });
});
return "";
}
GetbinClient::GetbinClient() {
ensureEventLoop();
}
GetbinClient::GetbinClient() {}
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));
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);
std::atomic<bool> success(false);
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::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) {
completed = true;
return;
}
if (ofs) {
const auto& body = response->getBody();
ofs.write(body.data(), body.size());
success = ofs.good();
completed = true;
}
} 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 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));
// Wait for completion
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return done; });
}
worker.join();
return success;
}
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
// Read file first
std::ifstream ifs(archivePath, std::ios::binary);
if (!ifs) {
std::cerr << "[GetbinClient::upload] Failed to open archive file: " << archivePath << std::endl;
@ -104,50 +122,73 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
}
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()} };
// Try to extract tool:arch from filename
std::string filename = archivePath.substr(archivePath.find_last_of("/\\") + 1);
size_t dot = filename.find('.');
std::string labeltag = dot != std::string::npos ? filename.substr(0, dot) : filename;
metadata["labeltags"].push_back(labeltag);
// Create request with raw body upload (simplified approach)
auto req = drogon::HttpRequest::newHttpRequest();
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
std::string ca_path = find_ca_certificates();
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);
req->addHeader("Content-Type", "application/gzip");
req->addHeader("X-Metadata", metadata.dump());
req->setBody(file_content);
std::atomic<bool> success(false);
std::atomic<bool> completed(false);
std::string response_body;
int status_code = 0;
// Add metadata as form parameter
req->setParameter("metadata", metadata.dump());
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
if (result != drogon::ReqResult::Ok || !response) {
std::cerr << "[GetbinClient::upload] HTTP /upload request failed (did not get OK response)." << std::endl;
if (response) {
std::cerr << response->getStatusCode() << std::endl;
std::cerr << response->getBody() << std::endl;
}
completed = true;
return;
}
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());
status_code = static_cast<int>(response->getStatusCode());
response_body = response->getBody();
if (status_code != 200 && status_code != 201) {
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
completed = true;
return;
}
// Parse response for URL/hash
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>();
@ -156,79 +197,106 @@ bool GetbinClient::upload(const std::string& archivePath, std::string& outUrl, s
} 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;
}
} else {
std::cerr << "[GetbinClient::upload] HTTP error: status code " << status_code << std::endl;
std::cerr << "[GetbinClient::upload] Response body: " << response_body << std::endl;
}
completed = true;
} else {
std::cerr << "[GetbinClient::upload] HTTP /upload request failed." << std::endl;
}
done = true;
cv.notify_one();
loop.quit();
}, 60.0); // 60 second timeout
loop.loop();
// Clean up temporary file
std::remove(temp_file.c_str());
});
// Wait for the async request to complete with timeout
auto start = std::chrono::steady_clock::now();
const auto timeout = std::chrono::seconds(60); // 60 second timeout for upload
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));
// Wait for completion
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return done; });
}
worker.join();
return success;
}
bool GetbinClient::getHash(const std::string& toolName, const std::string& arch, std::string& outHash) {
auto client = drogon::HttpClient::newHttpClient("https://" + std::string(SERVER_HOST));
std::string exists_path = "/exists/" + toolName + ":" + arch;
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
std::string ca_path = find_ca_certificates();
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");
std::string hash_path = "/hash/" + toolName + ":" + arch;
auto req = drogon::HttpRequest::newHttpRequest();
req->setMethod(drogon::Get);
req->setPath(exists_path);
std::atomic<bool> success(false);
std::atomic<bool> completed(false);
std::string response_body;
req->setPath(hash_path);
client->sendRequest(req, [&](drogon::ReqResult result, const drogon::HttpResponsePtr& response) {
if (result != drogon::ReqResult::Ok || !response || response->getStatusCode() != drogon::k200OK) {
completed = true;
return;
}
std::lock_guard<std::mutex> lock(mtx);
if (result == drogon::ReqResult::Ok && response && response->getStatusCode() == drogon::k200OK) {
std::string response_body(response->getBody());
response_body = response->getBody();
// Try to parse hash from response body (assume plain text or JSON)
// Try to parse hash from response body
try {
// Try JSON
// Try JSON first
auto resp_json = json::parse(response_body);
if (resp_json.contains("hash")) {
outHash = resp_json["hash"].get<std::string>();
success = true;
completed = true;
return;
}
} catch (...) {
// Not JSON, treat as plain text
outHash = response_body;
success = !outHash.empty();
completed = true;
return;
// Remove trailing newline if present
if (!outHash.empty() && outHash.back() == '\n') {
outHash.pop_back();
}
completed = true;
success = !outHash.empty();
}
}
done = true;
cv.notify_one();
loop.quit();
}, 10.0); // 10 second timeout
loop.loop();
});
// Wait for the async request to complete with timeout
auto start = std::chrono::steady_clock::now();
const auto timeout = std::chrono::seconds(10); // 10 second timeout for hash check
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));
// Wait for completion
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [&] { return done; });
}
worker.join();
return success;
}

View File

@ -292,7 +292,10 @@ fi
# Test 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
print_test_result "Invalid tool name rejection" 0
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.'