Repository: typst/typst
File: crates/typst-cli/src/download.rs (Rust)

Code before:

/// Keep track of this many download speed samples.
const SPEED_SAMPLES: usize = 5;

/// Load a certificate from the file system if the `--cert` argument or
/// `TYPST_CERT` environment variable is present. The certificate is cached for
/// efficiency.
///
/// - Returns `None` if `--cert` and `TYPST_CERT` are not set.
/// - Returns `Some(Ok(cert))` if the certificate was loaded successfully.
/// - Returns `Some(Err(err))` if an error occurred while loading the certificate.
fn cert() -> Option<Result<&'static Certificate, EcoString>> {
    static CERT: OnceCell<Certificate> = OnceCell::new();
    crate::ARGS.cert.as_ref().map(|path| {
        CERT.get_or_try_init(|| {
            let pem = std::fs::read(path)
                .map_err(|err| eco_format!("cannot read certificate: {err}"))?;
            Certificate::from_pem(&pem)
                .map_err(|err| eco_format!("cannot parse certificate: {err}"))
        })
    })
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum DownloadError {
    IoError(io::Error),
    UreqError(ureq::Error),
    Message(EcoString),
}

impl Display for DownloadError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::IoError(err) => write!(f, "{err}"),
            Self::UreqError(err) => write!(f, "{err}"),
            Self::Message(msg) => write!(f, "{msg}"),
        }
    }
}

impl Error for DownloadError {}

impl From<io::Error> for DownloadError {
    fn from(err: io::Error) -> Self {
        Self::IoError(err)
    }
}

impl From<ureq::Error> for DownloadError {
    fn from(err: ureq::Error) -> Self {

Review comment (bug):
`ureq::Error` already implements `From<io::Error>` so perhaps we can just use a custom `io::Error` to pass through the certificate failures and skip adding a new type.

Code after:

/// Load a certificate from the file system if the `--cert` argument or
/// `TYPST_CERT` environment variable is present. The certificate is cached for
/// efficiency.
///
/// - Returns `None` if `--cert` and `TYPST_CERT` are not set.
/// - Returns `Some(Ok(cert))` if the certificate was loaded successfully.
/// - Returns `Some(Err(err))` if an error occurred while loading the certificate.
fn cert() -> Option<Result<&'static Certificate, io::Error>> {
    static CERT: OnceCell<Certificate> = OnceCell::new();
    crate::ARGS.cert.as_ref().map(|path| {
        CERT.get_or_try_init(|| {
            let pem = std::fs::read(path)?;
            Certificate::from_pem(&pem).map_err(io::Error::other)
        })
    })
}

/// Download binary data and display its progress.
#[allow(clippy::result_large_err)]
pub fn download_with_progress(url: &str) -> Result<Vec<u8>, ureq::Error> {
    let response = download(url)?;
    Ok(RemoteReader::from_response(response).download()?)
}

/// Download from a URL.
#[allow(clippy::result_large_err)]
pub fn download(url: &str) -> Result<ureq::Response, ureq::Error> {
    let mut builder = ureq::AgentBuilder::new();
    let mut tls = TlsConnector::builder();

    // Set user agent.
    builder = builder.user_agent(concat!("typst/", env!("CARGO_PKG_VERSION")));

    // Get the network proxy config from the environment and apply it.
    if let Some(proxy) = env_proxy::for_url_str(url)
        .to_url()
        .and_then(|url| ureq::Proxy::new(url).ok())
    {
        builder = builder.proxy(proxy);
    }

    // Apply a custom CA certificate if present.
    if let Some(cert) = cert() {
        tls.add_root_certificate(cert?.clone());
    }

    // Configure native TLS.
    let connector =
        tls.build().map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
    builder = builder.tls_connector(Arc::new(connector));