1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
use miette::IntoDiagnostic;
use serde::{Deserialize, Serialize};
use starbase_utils::fs;
use std::path::PathBuf;
use tracing::{debug, trace, warn};

#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(default, rename_all = "kebab-case")]
#[cfg_attr(feature = "schematic", derive(schematic::Schematic))]
pub struct HttpOptions {
    pub allow_invalid_certs: bool,
    pub proxies: Vec<String>,
    pub root_cert: Option<PathBuf>,
}

/// Create an HTTP/HTTPS client that'll be used for downloading files.
pub fn create_http_client() -> miette::Result<reqwest::Client> {
    create_http_client_with_options(&HttpOptions::default())
}

/// Create an HTTP/HTTPS client with the provided options, that'll be
/// used for downloading files.
pub fn create_http_client_with_options(options: &HttpOptions) -> miette::Result<reqwest::Client> {
    debug!("Creating HTTP client");

    let mut client = reqwest::Client::builder()
        .user_agent(format!("warpgate@{}", env!("CARGO_PKG_VERSION")))
        .use_rustls_tls();

    if options.allow_invalid_certs {
        trace!("Allowing invalid certificates (I hope you know what you're doing!)");

        client = client.danger_accept_invalid_certs(true);
    }

    if let Some(root_cert) = &options.root_cert {
        trace!(root_cert = ?root_cert, "Adding user provided root certificate");

        match root_cert.extension().map(|e| e.to_str().unwrap()) {
            Some("der") => {
                client = client.add_root_certificate(
                    reqwest::Certificate::from_der(&fs::read_file_bytes(root_cert)?)
                        .into_diagnostic()?,
                )
            }
            Some("pem") => {
                client = client.add_root_certificate(
                    reqwest::Certificate::from_pem(&fs::read_file_bytes(root_cert)?)
                        .into_diagnostic()?,
                )
            }
            _ => {
                warn!(
                    root_cert = ?root_cert,
                    "Invalid root certificate type, must be a DER or PEM file",
                );
            }
        };
    }

    for proxy in &options.proxies {
        trace!(proxy = &proxy, "Adding proxy to http client");

        if proxy.starts_with("http:") {
            client = client.proxy(reqwest::Proxy::http(proxy).into_diagnostic()?);
        } else if proxy.starts_with("https:") {
            client = client.proxy(reqwest::Proxy::https(proxy).into_diagnostic()?);
        } else {
            warn!(proxy, "Invalid proxy, only http or https URLs allowed");
        };
    }

    let client = client.build().into_diagnostic()?;

    debug!("Created HTTP client");

    Ok(client)
}