From 09fc47580332b7646850899b13f50f2e016e3248 Mon Sep 17 00:00:00 2001 From: Hugo McNally Date: Mon, 4 Sep 2023 08:44:47 +0100 Subject: [PATCH 1/2] WIP implementation of TLS min version see #1232 --- lychee-bin/src/client.rs | 1 + lychee-bin/src/options.rs | 30 +++++++++++++++++++++++++++++- lychee-lib/src/client.rs | 8 +++++++- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/lychee-bin/src/client.rs b/lychee-bin/src/client.rs index 7c9e81270b..aa655579a0 100644 --- a/lychee-bin/src/client.rs +++ b/lychee-bin/src/client.rs @@ -76,6 +76,7 @@ pub(crate) fn create(cfg: &Config, cookie_jar: Option<&Arc>) - .accepted(accepted) .require_https(cfg.require_https) .cookie_jar(cookie_jar.cloned()) + .min_tls_version(cfg.min_tls) .include_fragments(cfg.include_fragments) .fallback_extensions(cfg.fallback_extensions.clone()) .build() diff --git a/lychee-bin/src/options.rs b/lychee-bin/src/options.rs index c6e18ee287..336da11c33 100644 --- a/lychee-bin/src/options.rs +++ b/lychee-bin/src/options.rs @@ -10,6 +10,7 @@ use lychee_lib::{ StatusCodeSelector, DEFAULT_MAX_REDIRECTS, DEFAULT_MAX_RETRIES, DEFAULT_RETRY_WAIT_TIME_SECS, DEFAULT_TIMEOUT_SECS, DEFAULT_USER_AGENT, }; +use reqwest::tls; use secrecy::{ExposeSecret, SecretString}; use serde::Deserialize; use std::path::Path; @@ -46,6 +47,22 @@ const HELP_MSG_CONFIG_FILE: &str = formatcp!( const TIMEOUT_STR: &str = concatcp!(DEFAULT_TIMEOUT_SECS); const RETRY_WAIT_TIME_STR: &str = concatcp!(DEFAULT_RETRY_WAIT_TIME_SECS); +const TLS_VERSIONS: [&'static str; 4] = [ + "TLSv1_0", + "TLSv1_1", + "TLSv1_2", + "TLSv1_3", +]; +fn tls_from_str(ver: impl AsRef) -> Option { + match ver.as_ref() { + "TLSv1_0" => Some(tls::Version::TLS_1_0), + "TLSv1_1" => Some(tls::Version::TLS_1_1), + "TLSv1_2" => Some(tls::Version::TLS_1_2), + "TLSv1_3" => Some(tls::Version::TLS_1_3), + _ => None, + } +} + /// The format to use for the final status report #[derive(Debug, Deserialize, Default, Clone, Display, EnumIter, VariantNames, PartialEq)] #[non_exhaustive] @@ -240,7 +257,7 @@ pub(crate) struct Config { long, default_value_t = FileExtensions::default(), long_help = "Test the specified file extensions for URIs when checking files locally. - + Multiple extensions can be separated by commas. Note that if you want to check filetypes, which have multiple extensions, e.g. HTML files with both .html and .htm extensions, you need to specify both extensions explicitly." @@ -317,6 +334,17 @@ list of excluded status codes. This example will not cache results with a status #[serde(default = "max_retries")] pub(crate) max_retries: u64, + // Minimum TLS Version + #[arg( + long, + default_value = "TLSv1_0", + value_parser=PossibleValuesParser::new(TLS_VERSIONS).map(tls_from_str), + )] + #[serde( + skip, + )] + pub(crate) min_tls: tls::Version, + /// Maximum number of concurrent network requests #[arg(long, default_value = &MAX_CONCURRENCY_STR)] #[serde(default = "max_concurrency")] diff --git a/lychee-lib/src/client.rs b/lychee-lib/src/client.rs index f9566f06e4..01c53f5461 100644 --- a/lychee-lib/src/client.rs +++ b/lychee-lib/src/client.rs @@ -22,7 +22,7 @@ use http::{ use log::{debug, warn}; use octocrab::Octocrab; use regex::RegexSet; -use reqwest::{header, redirect}; +use reqwest::{header, redirect, tls}; use reqwest_cookie_store::CookieStoreMutex; use secrecy::{ExposeSecret, SecretString}; use typed_builder::TypedBuilder; @@ -192,6 +192,9 @@ pub struct ClientBuilder { #[builder(default = DEFAULT_MAX_RETRIES)] max_retries: u64, + /// Minimum accepted TLS version. + min_tls_version: Option, + /// User-agent used for checking links. /// /// Defaults to [`DEFAULT_USER_AGENT`]. @@ -351,6 +354,9 @@ impl ClientBuilder { if let Some(cookie_jar) = self.cookie_jar { builder = builder.cookie_provider(cookie_jar); } + if let Some(min_tls) = self.min_tls_version { + builder = builder.min_tls_version(min_tls); + } let reqwest_client = match self.timeout { Some(t) => builder.timeout(t), From 520d533f494b22c82c02f9547dfea68bb81a3e1a Mon Sep 17 00:00:00 2001 From: Hugo McNally Date: Sun, 16 Mar 2025 18:46:07 +0000 Subject: [PATCH 2/2] Fix build --- lychee-bin/src/client.rs | 2 +- lychee-bin/src/options.rs | 50 ++++++++++++++++++++++----------------- lychee-lib/src/client.rs | 1 + 3 files changed, 30 insertions(+), 23 deletions(-) diff --git a/lychee-bin/src/client.rs b/lychee-bin/src/client.rs index aa655579a0..7cedc91f9f 100644 --- a/lychee-bin/src/client.rs +++ b/lychee-bin/src/client.rs @@ -76,7 +76,7 @@ pub(crate) fn create(cfg: &Config, cookie_jar: Option<&Arc>) - .accepted(accepted) .require_https(cfg.require_https) .cookie_jar(cookie_jar.cloned()) - .min_tls_version(cfg.min_tls) + .min_tls_version(cfg.min_tls.clone().map(Into::into)) .include_fragments(cfg.include_fragments) .fallback_extensions(cfg.fallback_extensions.clone()) .build() diff --git a/lychee-bin/src/options.rs b/lychee-bin/src/options.rs index 336da11c33..cf9f41260c 100644 --- a/lychee-bin/src/options.rs +++ b/lychee-bin/src/options.rs @@ -47,19 +47,31 @@ const HELP_MSG_CONFIG_FILE: &str = formatcp!( const TIMEOUT_STR: &str = concatcp!(DEFAULT_TIMEOUT_SECS); const RETRY_WAIT_TIME_STR: &str = concatcp!(DEFAULT_RETRY_WAIT_TIME_SECS); -const TLS_VERSIONS: [&'static str; 4] = [ - "TLSv1_0", - "TLSv1_1", - "TLSv1_2", - "TLSv1_3", -]; -fn tls_from_str(ver: impl AsRef) -> Option { - match ver.as_ref() { - "TLSv1_0" => Some(tls::Version::TLS_1_0), - "TLSv1_1" => Some(tls::Version::TLS_1_1), - "TLSv1_2" => Some(tls::Version::TLS_1_2), - "TLSv1_3" => Some(tls::Version::TLS_1_3), - _ => None, +#[derive(Debug, Display, Deserialize, Default, Clone, EnumString)] +#[non_exhaustive] +pub(crate) enum TlsVersion { + #[serde(rename = "TLSv1_0")] + #[strum(serialize = "TLSv1_0")] + V1_0, + #[serde(rename = "TLSv1_1")] + #[strum(serialize = "TLSv1_1")] + V1_1, + #[serde(rename = "TLSv1_2")] + #[strum(serialize = "TLSv1_2")] + #[default] + V1_2, + #[serde(rename = "TLSv1_3")] + #[strum(serialize = "TLSv1_3")] + V1_3, +} +impl From for tls::Version { + fn from(ver: TlsVersion) -> Self { + match ver { + TlsVersion::V1_0 => tls::Version::TLS_1_0, + TlsVersion::V1_1 => tls::Version::TLS_1_1, + TlsVersion::V1_2 => tls::Version::TLS_1_2, + TlsVersion::V1_3 => tls::Version::TLS_1_3, + } } } @@ -335,15 +347,9 @@ list of excluded status codes. This example will not cache results with a status pub(crate) max_retries: u64, // Minimum TLS Version - #[arg( - long, - default_value = "TLSv1_0", - value_parser=PossibleValuesParser::new(TLS_VERSIONS).map(tls_from_str), - )] - #[serde( - skip, - )] - pub(crate) min_tls: tls::Version, + #[arg(long)] + #[serde(default)] + pub(crate) min_tls: Option, /// Maximum number of concurrent network requests #[arg(long, default_value = &MAX_CONCURRENCY_STR)] diff --git a/lychee-lib/src/client.rs b/lychee-lib/src/client.rs index 01c53f5461..158ecb7053 100644 --- a/lychee-lib/src/client.rs +++ b/lychee-lib/src/client.rs @@ -354,6 +354,7 @@ impl ClientBuilder { if let Some(cookie_jar) = self.cookie_jar { builder = builder.cookie_provider(cookie_jar); } + if let Some(min_tls) = self.min_tls_version { builder = builder.min_tls_version(min_tls); }