Skip to content

Commit

Permalink
fixup! Allows configuring tls_backend
Browse files Browse the repository at this point in the history
  • Loading branch information
erickguan committed Nov 10, 2024
1 parent c4ba0dd commit 429187f
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 64 deletions.
17 changes: 12 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 13 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ app_dirs = { version = "2", package = "app_dirs2" }
clap = { version = "4", features = ["std", "derive", "help", "usage", "cargo", "error-context", "color", "wrap_help"], default-features = false }
env_logger = { version = "0.11", optional = true }
log = "0.4"
reqwest = { version = "0.12.5", features = ["blocking", "native-tls", "rustls-tls", "rustls-tls-native-roots", "rustls-tls-webpki-roots"], default-features = false }
reqwest = { version = "0.12.9", features = ["blocking"], default-features = false }
serde = "1.0.21"
serde_derive = "1.0.21"
toml = "0.8.19"
Expand All @@ -44,22 +44,25 @@ tempfile = "3.1.0"
filetime = "0.2.10"

[features]
default = ["native-roots"]
default = ["native-tls"]
logging = ["env_logger"]

# Reqwest (the HTTP client library) can handle TLS connections in three
# Reqwest (the HTTP client library) can handle TLS connections in four
# different modes:
#
# - Rustls with native roots
# - Rustls with WebPK roots
# - Native TLS (SChannel on Windows, Secure Transport on macOS and OpenSSL otherwise)
# - Rustls with:
# - native roots
# - WebPK roots
# - Native TLS (SChannel on Windows, Secure Transport on macOS and OpenSSL otherwise) with:
# - native roots
# - WebPK roots
#
# Exactly one of the three variants must be selected. By default, Rustls with
# native roots is enabled.
# Exactly one of the four variants must be selected. By default, uses native TLS and native roots.
# When you compile the crate, you should also set the `tls_backend` config with the default feature.
native-roots = ["reqwest/rustls-tls-native-roots"]
webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
native-tls = ["reqwest/native-tls"]
native-tls-with-webpki-roots = ["reqwest/native-tls", "reqwest/rustls-tls-webpki-roots-no-provider"]
rustls = ["reqwest/rustls-tls-webpki-roots"]
rustls-with-native-roots = ["reqwest/rustls-tls-native-roots"]

[profile.release]
strip = true
Expand Down
60 changes: 40 additions & 20 deletions src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ use reqwest::{blocking::Client, Proxy};
use walkdir::{DirEntry, WalkDir};
use zip::ZipArchive;

use crate::{types::PlatformType, utils::print_warning};
use crate::{config::TlsBackend, types::PlatformType, utils::print_warning};

pub static TLDR_PAGES_DIR: &str = "tldr-pages";
static TLDR_OLD_PAGES_DIR: &str = "tldr-master";

#[derive(Debug)]
pub struct Cache<'a> {
pub struct Cache {
cache_dir: PathBuf,
enable_styles: bool,
tls_backend: &'a str, // for setting up reqwest client
tls_backend: TlsBackend, // for setting up reqwest client
}

#[derive(Debug)]
Expand Down Expand Up @@ -86,8 +86,8 @@ pub enum CacheFreshness {
Missing,
}

impl<'a> Cache<'a> {
pub fn new<P>(cache_dir: P, enable_styles: bool, tls_backend: &'a str) -> Self
impl Cache {
pub fn new<P>(cache_dir: P, enable_styles: bool, tls_backend: TlsBackend) -> Self
where
P: Into<PathBuf>,
{
Expand Down Expand Up @@ -140,13 +140,21 @@ impl<'a> Cache<'a> {
/// Builds HTTPS client based on configuration.
///
/// Note that `Cargo.toml` also defines default feature .
fn build_client(&self) -> Result<Client> {
let mut builder = Client::builder();
builder = match self.tls_backend {
"native-roots" => builder.use_rustls_tls().tls_built_in_native_certs(true),
"webpki-roots" => builder.use_rustls_tls().tls_built_in_webpki_certs(true),
"native-tls" => builder.use_native_tls().tls_built_in_native_certs(true),
_ => builder, // TLS backend defaults to the compiled feature
fn build_client(&self, tls_backend: TlsBackend) -> Result<Client> {
let mut builder: reqwest::blocking::ClientBuilder = Client::builder();
builder = match tls_backend {
#[cfg(feature = "native-tls")]
TlsBackend::NativeTLS => builder.use_native_tls(),
#[cfg(feature = "native-tls-with-webpki-roots")]
TlsBackend::NativeTLSWithWebPKIRoots => {
builder.use_native_tls().tls_built_in_webpki_certs(true)
}
#[cfg(feature = "rustls")]
TlsBackend::Rustls => builder.use_native_tls().tls_built_in_webpki_certs(true),
#[cfg(feature = "rustls-with-native-roots")]
TlsBackend::RustlsWithNativeRoots => {
builder.use_native_tls().tls_built_in_native_certs(true)
}
};
if let Ok(ref host) = env::var("HTTP_PROXY") {
if let Ok(proxy) = Proxy::http(host) {
Expand Down Expand Up @@ -178,7 +186,7 @@ impl<'a> Cache<'a> {
pub fn update(&self, archive_url: &str) -> Result<()> {
self.ensure_cache_dir_exists()?;

let client = self.build_client()?;
let client = self.build_client(self.tls_backend)?;
// First, download the compressed data
let bytes: Vec<u8> = Self::download(&client, archive_url)?;

Expand Down Expand Up @@ -519,8 +527,12 @@ mod tests {
}

macro_rules! https_client_tests {
($($name:ident: $backend:expr),* $(,)?) => {
// Define each test with an optional cfg attribute for conditional compilation
($(
$(#[$cfg:meta])? $name:ident: $backend:expr
),* $(,)?) => {
$(
$( #[$cfg] )?
#[test]
fn $name() {
let dir = tempfile::tempdir().unwrap();
Expand All @@ -529,18 +541,26 @@ mod tests {
cache_dir: dir.into_path(),
enable_styles: false,
tls_backend: $backend,
});
}, $backend);

// intentionally empty, assumes we have built the client.
}
)*
}
};
}

// Use the macro with conditional compilation attributes
https_client_tests! {
tests_https_client_with_native_roots: "native-roots",
tests_https_client_with_wekpki_roots: "wekpki-roots",
tests_https_client_with_native_tls: "native-tls",
tests_https_client_with_default_backend: "default",
#[cfg(feature = "native-tls")]
tests_https_client_with_native_tls: TlsBackend::NativeTLS,

#[cfg(feature = "native-tls-with-webpki-roots")]
tests_https_client_with_webpki_roots: TlsBackend::NativeTLSWithWebPKIRoots,

#[cfg(feature = "rustls")]
tests_https_client_with_rustls: TlsBackend::Rustls,

#[cfg(feature = "rustls-with-native-roots")]
tests_https_client_with_rustls_and_native_roots: TlsBackend::RustlsWithNativeRoots,
}
}
82 changes: 65 additions & 17 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ use crate::types::PathSource;
pub const CONFIG_FILE_NAME: &str = "config.toml";
pub const MAX_CACHE_AGE: Duration = Duration::from_secs(2_592_000); // 30 days
const DEFAULT_UPDATE_INTERVAL_HOURS: u64 = MAX_CACHE_AGE.as_secs() / 3600; // 30 days
// Chooses a default TLS backend.
// `default` will choose a backend based on the compiled default TLS backend feature.
// Read more in `Cargo.toml'.
const DEFAULT_TLS_BACKEND: &str = "default";

fn default_underline() -> bool {
false
Expand Down Expand Up @@ -170,26 +166,24 @@ const fn default_auto_update_interval_hours() -> u64 {
DEFAULT_UPDATE_INTERVAL_HOURS
}

fn default_tls_backend() -> String {
DEFAULT_TLS_BACKEND.to_string()
}
const DEFAULT_TLS_BACKEND: RawTlsBackend = RawTlsBackend::NativeTLS;

#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
struct RawUpdatesConfig {
#[serde(default)]
pub auto_update: bool,
#[serde(default = "default_auto_update_interval_hours")]
pub auto_update_interval_hours: u64,
#[serde(default = "default_tls_backend")]
pub tls_backend: String,
#[serde(default)]
pub tls_backend: RawTlsBackend,
}

impl Default for RawUpdatesConfig {
fn default() -> Self {
Self {
auto_update: false,
auto_update_interval_hours: DEFAULT_UPDATE_INTERVAL_HOURS,
tls_backend: DEFAULT_TLS_BACKEND.to_string(),
tls_backend: DEFAULT_TLS_BACKEND,
}
}
}
Expand All @@ -201,7 +195,7 @@ impl From<RawUpdatesConfig> for UpdatesConfig {
auto_update_interval: Duration::from_secs(
raw_updates_config.auto_update_interval_hours * 3600,
),
tls_backend: DEFAULT_TLS_BACKEND.to_string(),
tls_backend: TlsBackend::from(raw_updates_config.tls_backend),
}
}
}
Expand Down Expand Up @@ -268,12 +262,7 @@ pub struct DisplayConfig {
pub struct UpdatesConfig {
pub auto_update: bool,
pub auto_update_interval: Duration,
/// Allows choosing a TLS backend supported by `reqwest`. Available TLS backends:
/// # - `native-roots`: Rustls with native roots
/// # - `webpki-roots`: Rustls with `WebPK` roots
/// # - `native-tls`: Native TLS (`SChannel` on Windows, Secure Transport on macOS and OpenSSL otherwise)
/// Read more in `Cargo.toml`
pub tls_backend: String,
pub tls_backend: TlsBackend,
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand All @@ -300,6 +289,65 @@ pub struct DirectoriesConfig {
pub custom_pages_dir: Option<PathWithSource>,
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum RawTlsBackend {
/// Native TLS (`SChannel` on Windows, Secure Transport on macOS and OpenSSL otherwise)
#[cfg(feature = "native-tls")]
#[serde(rename = "native-tls")]
NativeTLS,
/// NativeTLS but using WebPKI roots from `tealdeer`.
#[cfg(feature = "native-tls-with-webpki-roots")]
#[serde(rename = "native-tls-with-webpki-roots")]
NativeTLSWithWebPKIRoots,
/// Rustls with WebPKI roots.
#[cfg(feature = "rustls")]
#[serde(rename = "rustls")]
Rustls,
/// Rustls with native roots.
#[cfg(feature = "rustls-with-native-roots")]
#[serde(rename = "rustls-with-native-roots")]
RustlsWithNativeRoots,
}

impl Default for RawTlsBackend {
fn default() -> Self {
return Self::NativeTLS;
}
}

impl From<RawTlsBackend> for TlsBackend {
fn from(raw_tls_backend: RawTlsBackend) -> Self {
match raw_tls_backend {
#[cfg(feature = "native-tls")]
RawTlsBackend::NativeTLS => Self::NativeTLS,
#[cfg(feature = "native-tls-with-webpki-roots")]
RawTlsBackend::NativeTLSWithWebPKIRoots => Self::NativeTLSWithWebPKIRoots,
#[cfg(feature = "rustls")]
RawTlsBackend::Rustls => Self::Rustls,
#[cfg(feature = "rustls-with-native-roots")]
RawTlsBackend::RustlsWithNativeRoots => Self::RustlsWithNativeRoots,
}
}
}

/// Allows choosing a `reqwest`'s TLS backend. Available TLS backends:
/// Read more in `Cargo.toml`
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum TlsBackend {
/// Native TLS (`SChannel` on Windows, Secure Transport on macOS and OpenSSL otherwise)
#[cfg(feature = "native-tls")]
NativeTLS,
/// NativeTLS but using WebPKI roots from `tealdeer`.
#[cfg(feature = "native-tls-with-webpki-roots")]
NativeTLSWithWebPKIRoots,
/// Rustls with WebPKI roots.
#[cfg(feature = "rustls")]
Rustls,
/// Rustls with native roots.
#[cfg(feature = "rustls-with-native-roots")]
RustlsWithNativeRoots,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Config {
pub style: StyleConfig,
Expand Down
20 changes: 8 additions & 12 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::too_many_lines)]

#[cfg(any(
all(feature = "native-roots", feature = "webpki-roots"),
all(feature = "native-roots", feature = "native-tls"),
all(feature = "webpki-roots", feature = "native-tls"),
not(any(
feature = "native-roots",
feature = "webpki-roots",
feature = "native-tls"
)),
))]
#[cfg(not(any(
feature = "native-tls",
feature = "native-tls-with-webpki-roots",
feature = "rustls",
feature = "rustls-with-native-roots",
)))]
compile_error!(
"exactly one of the features \"native-roots\", \"webpki-roots\" or \"native-tls\" must be enabled"
"at least one of the features \"native-tls\", \"native-tls-with-webpki-roots\", \"rustls\" or \"rustls-with-native-roots\" must be enabled"
);

use std::{
Expand Down Expand Up @@ -307,7 +303,7 @@ fn main() {
let cache = Cache::new(
&config.directories.cache_dir.path,
enable_styles,
&config.updates.tls_backend,
config.updates.tls_backend,
);

// Clear cache, pass through
Expand Down

0 comments on commit 429187f

Please sign in to comment.