HTTP File Downloader (cpp, written by Claude Code)
envgap__claude-code__cpp-t2-24
Written by a coding agent; not on GitHubWritten 2026-02-28
01 / FAILURE SIGNATURE
As the study recorded it
OpenSSL not installed; custom SHA256 class conflicts with OpenSSL SHA256 macro
Not a benchmark task.
- In a clean container the reported failure did not reproduce, or the known fix did not make the project run.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
claude-code/cpp-t2 #24 · read the task the agent was given
Claude Code wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: HTTP File Downloader Write a program that downloads files from HTTP/HTTPS URLs with support for resumable downloads, progress tracking, concurrent downloads, and checksum verification. FUNCTIONAL REQUIREMENTS: - Accept one or more URLs as command-line arguments - Display a progress bar during download showing: percentage complete, bytes downloaded, total size, download speed, and estimated time remaining - Support resumable downloads via HTTP Range headers: if a download is interrupted, restarting with the same URL and output path should resume from where it stopped via --resume flag - Support concurrent downloading of multiple files via --parallel flag with configurable thread count (--threads, default 4) - Support downloading all URLs listed in a text file (one URL per line) via --list flag - Verify downloaded file integrity via --checksum flag accepting algorithm:hash format (e.g., --checksum sha256:abc123...) - Support custom HTTP headers via --header flag (e.g., --header "Authorization: Bearer token") - Support following HTTP redirects (up to 10 hops) and report the final URL - Set connection timeout via --timeout flag (default 30 seconds) and retry failed downloads via --retries flag (default 3) with exponential backoff - Save files to a directory specified by --output flag (default: current directory), using the filename from the URL or Content-Disposition header - Print a download summary to console: file name, size, time taken, average speed, and checksum verification result - If no URLs are given, download a set of sample public domain text files from Project Gutenberg, display progress for each, and print a summary table - Handle errors: DNS resolution failures, SSL certificate errors, HTTP 4xx/5xx responses, disk full, and network timeouts Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include: - Source code - CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(http-file-downloader VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(FetchContent)
# cpp-httplib 0.15.3
FetchContent_Declare(
httplib
GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git
GIT_TAG v0.15.3
)
FetchContent_MakeAvailable(httplib)
# cxxopts 3.1.1
FetchContent_Declare(
cxxopts
GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git
GIT_TAG v3.1.1
)
FetchContent_MakeAvailable(cxxopts)
find_package(OpenSSL REQUIRED)
add_executable(downloader main.cpp)
target_link_libraries(downloader
PRIVATE
httplib::httplib
cxxopts::cxxopts
OpenSSL::SSL
OpenSSL::Crypto
)
main.cpp
/**
* HTTP File Downloader with progress bars, resumable downloads,
* concurrent transfers, and checksum verification.
*
* Dependencies: cpp-httplib (0.15.3), cxxopts (3.1.1)
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <future>
#include <algorithm>
#include <filesystem>
#include <cmath>
#include <iomanip>
#include <functional>
#include <chrono>
#include <cstring>
#include <regex>
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h"
#include "cxxopts.hpp"
namespace fs = std::filesystem;
/**
* Represents a single download task.
*/
struct DownloadTask {
std::string url;
std::string outputPath;
std::string expectedChecksum;
std::string checksumAlgorithm = "sha256";
size_t downloadedBytes = 0;
size_t totalBytes = 0;
std::string status = "pending";
std::string error;
};
/**
* Simple SHA-256 implementation for checksum verification.
*/
class SHA256 {
public:
SHA256() { reset(); }
void update(const unsigned char* data, size_t length) {
for (size_t i = 0; i < length; i++) {
m_data[m_blocklen++] = data[i];
if (m_blocklen == 64) {
transform();
m_bitlen += 512;
m_blocklen = 0;
}
}
}
std::string digest() {
uint8_t hash[32];
pad();
revert(hash);
std::stringstream ss;
for (int i = 0; i < 32; i++) {
ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
}
return ss.str();
}
void reset() {
m_blocklen = 0;
m_bitlen = 0;
m_state[0] = 0x6a09e667; m_state[1] = 0xbb67ae85;
m_state[2] = 0x3c6ef372; m_state[3] = 0xa54ff53a;
m_state[4] = 0x510e527f; m_state[5] = 0x9b05688c;
m_state[6] = 0x1f83d9ab; m_state[7] = 0x5be0cd19;
}
private:
uint32_t m_state[8];
uint8_t m_data[64];
uint32_t m_blocklen;
uint64_t m_bitlen;
static constexpr uint32_t K[64] = {
0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,
0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,
0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,
0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,
0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,
0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,
0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,
0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2
};
static uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); }
static uint32_t choose(uint32_t e, uint32_t f, uint32_t g) { return (e & f) ^ (~e & g); }
static uint32_t majority(uint32_t a, uint32_t b, uint32_t c) { return (a & b) ^ (a & c) ^ (b & c); }
static uint32_t sig0(uint32_t x) { return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22); }
static uint32_t sig1(uint32_t x) { return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25); }
static uint32_t theta0(uint32_t x) { return rotr(x, 7) ^ rotr(x, 18) ^ (x >> 3); }
static uint32_t theta1(uint32_t x) { return rotr(x, 17) ^ rotr(x, 19) ^ (x >> 10); }
void transform() {
uint32_t maj, xorA, ch, xorE, sum, newA, newE, m[64];
for (uint8_t i = 0, j = 0; i < 16; i++, j += 4)
m[i] = (m_data[j] << 24) | (m_data[j+1] << 16) | (m_data[j+2] << 8) | m_data[j+3];
for (uint8_t k = 16; k < 64; k++)
m[k] = theta1(m[k-2]) + m[k-7] + theta0(m[k-15]) + m[k-16];
uint32_t state[8];
for (int i = 0; i < 8; i++) state[i] = m_state[i];
for (uint8_t i = 0; i < 64; i++) {
maj = majority(state[0], state[1], state[2]);
xorA = sig0(state[0]);
ch = choose(state[4], state[5], state[6]);
xorE = sig1(state[4]);
sum = state[7] + xorE + ch + K[i] + m[i];
newA = xorA + maj + sum;
newE = state[3] + sum;
state[7] = state[6]; state[6] = state[5]; state[5] = state[4];
state[4] = newE; state[3] = state[2]; state[2] = state[1];
state[1] = state[0]; state[0] = newA;
}
for (int i = 0; i < 8; i++) m_state[i] += state[i];
}
void pad() {
uint64_t i = m_blocklen;
uint8_t end = m_blocklen < 56 ? 56 : 64;
m_data[i++] = 0x80;
while (i < end) m_data[i++] = 0x00;
if (m_blocklen >= 56) { transform(); memset(m_data, 0, 56); }
m_bitlen += m_blocklen * 8;
m_data[63] = m_bitlen; m_data[62] = m_bitlen >> 8;
m_data[61] = m_bitlen >> 16; m_data[60] = m_bitlen >> 24;
m_data[59] = m_bitlen >> 32; m_data[58] = m_bitlen >> 40;
m_data[57] = m_bitlen >> 48; m_data[56] = m_bitlen >> 56;
transform();
}
void revert(uint8_t* hash) {
for (uint8_t i = 0; i < 4; i++) {
for (uint8_t j = 0; j < 8; j++) {
hash[i + j*4] = (m_state[j] >> (24 - i*8)) & 0xff;
}
}
}
};
/**
* Progress bar display helper.
*/
class ProgressBar {
std::string filename_;
size_t total_;
size_t current_;
int barWidth_;
std::mutex mtx_;
std::chrono::steady_clock::time_point startTime_;
public:
ProgressBar(const std::string& filename, size_t total, int barWidth = 50)
: filename_(filename), total_(total), current_(0), barWidth_(barWidth),
startTime_(std::chrono::steady_clock::now()) {
if (filename_.length() > 25) {
filename_ = filename_.substr(0, 22) + "...";
}
}
void update(size_t bytes) {
std::lock_guard<std::mutex> lock(mtx_);
current_ = bytes;
display();
}
void finish() {
std::lock_guard<std::mutex> lock(mtx_);
current_ = total_;
display();
std::cout << std::endl;
}
private:
std::string formatBytes(size_t bytes) const {
const char* units[] = {"B", "KB", "MB", "GB", "TB"};
int i = 0;
double size = static_cast<double>(bytes);
while (size >= 1024.0 && i < 4) { size /= 1024.0; i++; }
std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << size << " " << units[i];
return oss.str();
}
void display() {
if (total_ == 0) {
std::cout << "\r" << filename_ << ": " << formatBytes(current_) << " downloaded" << std::flush;
return;
}
double pct = static_cast<double>(current_) / total_ * 100.0;
int filled = static_cast<int>(barWidth_ * current_ / total_);
auto elapsed = std::chrono::steady_clock::now() - startTime_;
double secs = std::chrono::duration<double>(elapsed).count();
std::string speed = secs > 0 ? formatBytes(static_cast<size_t>(current_ / secs)) + "/s" : "N/A";
std::cout << "\r" << filename_ << " [";
for (int i = 0; i < barWidth_; i++) {
if (i < filled) std::cout << '=';
else if (i == filled) std::cout << '>';
else std::cout << ' ';
}
std::cout << "] " << std::fixed << std::setprecision(1) << pct << "% "
<< formatBytes(current_) << "/" << formatBytes(total_) << " " << speed
<< std::flush;
}
};
/**
* Parse a URL into host and path components.
*/
struct ParsedUrl {
std::string scheme;
std::string host;
int port;
std::string path;
};
ParsedUrl parseUrl(const std::string& url) {
ParsedUrl result;
std::regex urlRegex(R"((https?)://([^/:]+)(?::(\d+))?(/.*)?)");
std::smatch match;
if (std::regex_match(url, match, urlRegex)) {
result.scheme = match[1].str();
result.host = match[2].str();
result.port = match[3].matched ? std::stoi(match[3].str())
: (result.scheme == "https" ? 443 : 80);
result.path = match[4].matched ? match[4].str() : "/";
}
return result;
}
/**
* Main FileDownloader class using cpp-httplib.
*/
class FileDownloader {
std::string outputDir_;
int maxRetries_;
int maxWorkers_;
int timeout_;
std::mutex printMutex_;
public:
FileDownloader(const std::string& outputDir = ".",
int maxRetries = 3, int maxWorkers = 4,
int timeout = 30)
: outputDir_(outputDir), maxRetries_(maxRetries),
maxWorkers_(maxWorkers), timeout_(timeout) {
fs::create_directories(outputDir_);
}
/**
* Get remote file size via HEAD request.
*/
long getFileSize(const std::string& url) {
auto parsed = parseUrl(url);
std::unique_ptr<httplib::Client> cli;
if (parsed.scheme == "https") {
cli = std::make_unique<httplib::Client>(parsed.scheme + "://" + parsed.host);
} else {
cli = std::make_unique<httplib::Client>(parsed.host, parsed.port);
}
cli->set_connection_timeout(timeout_);
cli->set_read_timeout(timeout_);
cli->set_follow_location(true);
auto res = cli->Head(parsed.path);
if (res && res->status == 200) {
auto it = res->headers.find("Content-Length");
if (it != res->headers.end()) {
return std::stol(it->second);
}
}
return -1;
}
/**
* Check if server supports range requests.
*/
bool supportsRange(const std::string& url) {
auto parsed = parseUrl(url);
std::unique_ptr<httplib::Client> cli;
if (parsed.scheme == "https") {
cli = std::make_unique<httplib::Client>(parsed.scheme + "://" + parsed.host);
} else {
cli = std::make_unique<httplib::Client>(parsed.host, parsed.port);
}
cli->set_connection_timeout(timeout_);
cli->set_read_timeout(timeout_);
cli->set_follow_location(true);
auto res = cli->Head(parsed.path);
if (res) {
auto it = res->headers.find("Accept-Ranges");
if (it != res->headers.end()) {
std::string val = it->second;
std::transform(val.begin(), val.end(), val.begin(), ::tolower);
return val != "none";
}
}
return false;
}
/**
* Compute SHA-256 checksum of a file.
*/
std::string computeChecksum(const std::string& filepath) {
SHA256 sha;
std::ifstream file(filepath, std::ios::binary);
if (!file.is_open()) return "";
char buffer[8192];
while (file.read(buffer, sizeof(buffer)) || file.gcount() > 0) {
sha.update(reinterpret_cast<const unsigned char*>(buffer), file.gcount());
}
return sha.digest();
}
/**
* Get output path from URL.
*/
std::string getOutputPath(const std::string& url, const std::string& outputPath = "") {
if (!outputPath.empty()) {
fs::path p(outputPath);
if (!p.is_absolute()) p = fs::path(outputDir_) / p;
return p.string();
}
std::string filename = url.substr(url.find_last_of('/') + 1);
auto qpos = filename.find('?');
if (qpos != std::string::npos) filename = filename.substr(0, qpos);
if (filename.empty()) filename = "downloaded_file";
return (fs::path(outputDir_) / filename).string();
}
/**
* Download a single file with progress and resume support.
*/
DownloadTask downloadFile(const std::string& url,
const std::string& outputPath = "",
const std::string& expectedChecksum = "",
bool resume = true,
bool showProgress = true) {
DownloadTask task;
task.url = url;
task.outputPath = getOutputPath(url, outputPath);
task.expectedChecksum = expectedChecksum;
for (int attempt = 0; attempt < maxRetries_; attempt++) {
try {
long totalSize = getFileSize(url);
task.totalBytes = totalSize > 0 ? totalSize : 0;
// Handle resume
size_t initialPos = 0;
bool appending = false;
if (resume && fs::exists(task.outputPath)) {
size_t existingSize = fs::file_size(task.outputPath);
if (totalSize > 0 && existingSize == static_cast<size_t>(totalSize)) {
task.downloadedBytes = existingSize;
task.status = "completed";
if (!expectedChecksum.empty()) {
std::string actual = computeChecksum(task.outputPath);
if (actual != expectedChecksum) {
task.status = "checksum_failed";
task.error = "Checksum verification failed";
}
}
return task;
}
if (totalSize > 0 && existingSize < static_cast<size_t>(totalSize)
&& supportsRange(url)) {
initialPos = existingSize;
appending = true;
task.downloadedBytes = existingSize;
}
}
fs::create_directories(fs::path(task.outputPath).parent_path());
// Set up progress bar
std::unique_ptr<ProgressBar> progress;
if (showProgress) {
progress = std::make_unique<ProgressBar>(
fs::path(task.outputPath).filename().string(),
task.totalBytes);
if (initialPos > 0) progress->update(initialPos);
}
// Create HTTP client
auto parsed = parseUrl(url);
std::unique_ptr<httplib::Client> cli;
if (parsed.scheme == "https") {
cli = std::make_unique<httplib::Client>(parsed.scheme + "://" + parsed.host);
} else {
cli = std::make_unique<httplib::Client>(parsed.host, parsed.port);
}
cli->set_connection_timeout(timeout_);
cli->set_read_timeout(timeout_);
cli->set_follow_location(true);
// Open file
std::ofstream file;
if (appending) {
file.open(task.outputPath, std::ios::binary | std::ios::app);
} else {
file.open(task.outputPath, std::ios::binary);
}
if (!file.is_open()) {
task.status = "failed";
task.error = "Failed to open output file: " + task.outputPath;
return task;
}
task.status = "downloading";
// Set up headers for range request
httplib::Headers headers;
if (appending) {
headers.emplace("Range", "bytes=" + std::to_string(initialPos) + "-");
}
// Use content receiver for streaming
auto res = cli->Get(parsed.path, headers,
[&](const httplib::Response& response) {
// Check status
return response.status == 200 || response.status == 206;
},
[&](const char* data, size_t data_length) {
file.write(data, data_length);
task.downloadedBytes += data_length;
if (progress) {
progress->update(task.downloadedBytes);
}
return true;
});
file.close();
if (progress) progress->finish();
if (!res || (res->status != 200 && res->status != 206)) {
task.error = res ? "HTTP error: " + std::to_string(res->status)
: "Connection failed";
if (attempt + 1 >= maxRetries_) {
task.status = "failed";
return task;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(500 * (1 << attempt)));
continue;
}
task.status = "completed";
// Verify checksum
if (!expectedChecksum.empty()) {
std::string actual = computeChecksum(task.outputPath);
if (actual != expectedChecksum) {
task.status = "checksum_failed";
task.error = "Checksum mismatch (expected: " + expectedChecksum
+ ", got: " + actual + ")";
}
}
return task;
} catch (const std::exception& e) {
task.error = e.what();
if (attempt + 1 >= maxRetries_) {
task.status = "failed";
return task;
}
std::this_thread::sleep_for(
std::chrono::milliseconds(500 * (1 << attempt)));
}
}
return task;
}
/**
* Download multiple files concurrently.
*/
std::vector<DownloadTask> downloadMultiple(const std::vector<std::string>& urls,
bool resume = true,
bool showProgress = true) {
std::vector<std::future<DownloadTask>> futures;
std::vector<DownloadTask> results;
for (size_t i = 0; i < urls.size(); i += maxWorkers_) {
size_t end = std::min(i + static_cast<size_t>(maxWorkers_), urls.size());
for (size_t j = i; j < end; j++) {
futures.push_back(std::async(std::launch::async,
&FileDownloader::downloadFile, this,
urls[j], "", "", resume, showProgress));
}
}
for (auto& f : futures) {
results.push_back(f.get());
}
return results;
}
};
std::string formatBytes(size_t bytes) {
const char* units[] = {"B", "KB", "MB", "GB", "TB"};
int i = 0;
double size = static_cast<double>(bytes);
while (size >= 1024.0 && i < 4) { size /= 1024.0; i++; }
std::ostringstream oss;
oss << std::fixed << std::setprecision(1) << size << " " << units[i];
return oss.str();
}
int main(int argc, char* argv[]) {
cxxopts::Options options("downloader", "HTTP File Downloader with progress, resume, and checksum support");
options.add_options()
("urls", "URLs to download", cxxopts::value<std::vector<std::string>>())
("o,output-dir", "Output directory", cxxopts::value<std::string>()->default_value("."))
("w,workers", "Number of concurrent workers", cxxopts::value<int>()->default_value("4"))
("checksum", "Expected checksum (single URL)", cxxopts::value<std::string>()->default_value(""))
("no-resume", "Disable resumable downloads", cxxopts::value<bool>()->default_value("false"))
("no-progress", "Disable progress bars", cxxopts::value<bool>()->default_value("false"))
("timeout", "Request timeout in seconds", cxxopts::value<int>()->default_value("30"))
("retries", "Maximum retries", cxxopts::value<int>()->default_value("3"))
("h,help", "Print usage");
options.parse_positional({"urls"});
options.positional_help("<url1> [url2] ...");
auto result = options.parse(argc, argv);
if (result.count("help") || !result.count("urls")) {
std::cout << options.help() << std::endl;
return result.count("help") ? 0 : 1;
}
auto urls = result["urls"].as<std::vector<std::string>>();
auto outputDir = result["output-dir"].as<std::string>();
auto workers = result["workers"].as<int>();
auto checksum = result["checksum"].as<std::string>();
bool resume = !result["no-resume"].as<bool>();
bool showProgress = !result["no-progress"].as<bool>();
auto timeout = result["timeout"].as<int>();
auto retries = result["retries"].as<int>();
FileDownloader downloader(outputDir, retries, workers, timeout);
std::vector<DownloadTask> tasks;
if (urls.size() == 1) {
auto task = downloader.downloadFile(urls[0], "", checksum, resume, showProgress);
tasks.push_back(task);
} else {
tasks = downloader.downloadMultiple(urls, resume, showProgress);
}
// Print summary
std::cout << "\n--- Download Summary ---\n";
int failed = 0;
for (const auto& task : tasks) {
std::string icon = (task.status == "completed") ? "OK" : "FAIL";
std::cout << "[" << icon << "] " << task.url << "\n"
<< " -> " << task.outputPath << "\n"
<< " Status: " << task.status << "\n";
if (!task.error.empty()) {
std::cout << " Error: " << task.error << "\n";
}
std::cout << " Size: " << task.downloadedBytes << " bytes\n";
if (task.status != "completed") failed++;
}
if (failed > 0) {
std::cout << "\n" << failed << " download(s) failed.\n";
return 1;
} else {
std::cout << "\nAll " << tasks.size() << " download(s) completed successfully.\n";
}
return 0;
}
README.md
# HTTP File Downloader (C++ - Trial 2) An HTTP file downloader featuring progress bars, resumable downloads, concurrent transfers, and checksum verification. ## Dependencies - **cpp-httplib** (0.15.3) - Header-only C++ HTTP/HTTPS client library - **cxxopts** (3.1.1) - Lightweight C++ command-line option parser ## Setup ```bash mkdir build && cd build cmake .. make ``` ## Usage ### Download a single file ```bash ./downloader https://example.com/file.zip ``` ### Download multiple files concurrently ```bash ./downloader https://example.com/file1.zip https://example.com/file2.zip -w 4 ``` ### Download with checksum verification ```bash ./downloader https://example.com/file.zip --checksum abc123def456 ``` ### Specify output directory ```bash ./downloader https://example.com/file.zip -o ./downloads ``` ## Features - **Progress Bars**: Console-based progress display with percentage, speed, and size info - **Resumable Downloads**: Resumes interrupted downloads using HTTP Range headers - **Concurrent Transfers**: Thread-based parallel downloads with configurable worker count - **Checksum Verification**: SHA-256 checksum verification for downloaded files - **CLI Options**: Clean command-line interface via cxxopts - **Retry Logic**: Automatic retry with exponential backoff on failures - **HTTPS Support**: Full HTTPS support via OpenSSL integration