diff --git a/lychee-bin/src/client.rs b/lychee-bin/src/client.rs index 7c9e81270b..7cedc91f9f 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.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 c6e18ee287..cf9f41260c 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,34 @@ 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); +#[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, + } + } +} + /// The format to use for the final status report #[derive(Debug, Deserialize, Default, Clone, Display, EnumIter, VariantNames, PartialEq)] #[non_exhaustive] @@ -240,7 +269,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 +346,11 @@ 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)] + #[serde(default)] + pub(crate) min_tls: Option, + /// 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..158ecb7053 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`]. @@ -352,6 +355,10 @@ impl ClientBuilder { 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), None => builder,