← All tasks
cppclaude-code/cpp-t2 #24Not a task: not reproduced

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