shell-download 0.22.1

Zero-dependency Rust library for downloading a remote URL to a file, string or bytes using commonly-available shell tools.
Documentation
use std::io::Write as _;
use std::net::TcpStream;
use std::sync::{
    Arc,
    atomic::{AtomicBool, Ordering},
};
use std::thread::JoinHandle;

use crate::{
    ContentEncoding, DownloadResult, DownloadSink, ResponseError, StartError,
    drivers::{Driver, Request},
    url_parser::Url,
    util,
};

use super::http11;

#[derive(Debug, Clone, Copy)]
/// Plain HTTP/1.1 over a TCP socket (no TLS).
pub(crate) struct TcpDriver;

impl Driver for TcpDriver {
    fn start(
        &self,
        req: Request,
        sink: DownloadSink,
        cancel: Arc<AtomicBool>,
    ) -> Result<JoinHandle<Result<DownloadResult, ResponseError>>, StartError> {
        let url = req.url.clone();
        if url.scheme != "http" {
            return Err(StartError::NoDriverFound);
        }

        Ok(util::spawn_download_thread(
            req,
            sink,
            cancel,
            download_http,
        ))
    }
}

fn download_http(
    req: &Request,
    _sink: &DownloadSink,
    cancel: &Arc<AtomicBool>,
    pipe_writer: std::io::PipeWriter,
) -> Result<(u16, Option<ContentEncoding>), ResponseError> {
    http11::redirect_download(
        req.clone(),
        Arc::clone(cancel),
        pipe_writer,
        fetch_http_only,
    )
}

fn fetch_http_only(
    url: &Url,
    req: &Request,
    cancel: &Arc<AtomicBool>,
) -> Result<http11::HttpResponseParts, ResponseError> {
    if url.scheme != "http" {
        return Err(ResponseError::UnsupportedScheme);
    }

    let request = http11::build_get_request(url, req);
    if cancel.load(Ordering::SeqCst) {
        return Err(ResponseError::Cancelled);
    }

    let host = url.host.as_str();
    let port = url.port.unwrap_or(80);
    let mut stream = TcpStream::connect((host, port)).map_err(ResponseError::Io)?;
    stream
        .write_all(request.as_bytes())
        .map_err(ResponseError::Io)?;
    stream.flush().map_err(ResponseError::Io)?;

    let buf = http11::read_to_vec_cancelled(&mut stream, cancel)?;
    http11::parse_http_response(&buf)
}