From 052f3b0253f7631cc2f6cf6ecc38b8619ea474be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Kijewski?= Date: Mon, 18 Sep 2023 04:00:00 +0200 Subject: [PATCH] Change `reqwest` dependency against `ureq` This PR removes `reqwest`, and its transitive dependencies like `tokio`, and uses `ureq` instead. The latter library uses `curl` as HTTP client. `libcurl` should be installed by default on all current Windows and MacOS installations, and most likely by all serious Linux distros, too. `libcurl` is only loaded at runtime instead of being a hard dependecy. This way, if `libcurl` is not installed, the program will still run just fine, but the self update will fail. This changes makes the `github` example much smaller: ```text 1808048 github.master 1127536 github.patch -680512 bytes or -38% ``` ```text $ size github.master github.patch text data bss dec filename 1738914 52288 529 1791731 github.master 1078768 31784 465 1111017 github.patch -660146 -20504 -64 -680714 ``` --- Cargo.toml | 8 +-- src/backends/gitea.rs | 104 +++++++-------------------------------- src/backends/github.rs | 105 +++++++-------------------------------- src/backends/gitlab.rs | 108 +++++++++++------------------------------ src/backends/s3.rs | 26 ++++------ src/errors.rs | 42 +++++++--------- src/lib.rs | 102 +++++++++++++++++++++----------------- src/macros.rs | 15 ------ src/update.rs | 33 +++++++------ 9 files changed, 168 insertions(+), 375 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abb6e32..7caf74c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,23 +19,23 @@ tar = { version = "0.4", optional = true } semver = "1.0" zip = { version = "0.6", default-features = false, features = ["time"], optional = true } either = { version = "1", optional = true } -reqwest = { version = "0.11", default-features = false, features = ["blocking", "json"] } -hyper = "0.14" indicatif = "0.17" quick-xml = "0.23" regex = "1" log = "0.4" urlencoding = "2.1" self-replace = "1" +ureq = { version = "2.7", default-features = false, features = ["gzip", "json", "native-tls"] } +native-tls = "0.2" +once_cell = "1" [features] -default = ["reqwest/default-tls"] +default = [] archive-zip = ["zip"] compression-zip-bzip2 = ["zip/bzip2"] # compression-zip-deflate = ["zip/deflate"] # archive-tar = ["tar"] compression-flate2 = ["flate2", "either"] # -rustls = ["reqwest/rustls-tls"] [package.metadata.docs.rs] # Whether to pass `--all-features` to Cargo (default: false) diff --git a/src/backends/gitea.rs b/src/backends/gitea.rs index 3a64fed..c5e0896 100644 --- a/src/backends/gitea.rs +++ b/src/backends/gitea.rs @@ -4,9 +4,8 @@ gitea releases use std::env::{self, consts::EXE_SUFFIX}; use std::path::{Path, PathBuf}; -use reqwest::{self, header}; - use crate::backends::find_rel_next_link; +use crate::update::api_headers; use crate::{ errors::*, get_target, @@ -154,7 +153,6 @@ impl ReleaseList { /// Retrieve a list of `Release`s. /// If specified, filter for those containing a specified `target` pub fn fetch(self) -> Result> { - set_ssl_vars!(); let api_url = format!( "{}/api/v1/repos/{}/{}/releases", self.host, self.repo_owner, self.repo_name @@ -172,21 +170,17 @@ impl ReleaseList { } fn fetch_releases(&self, url: &str) -> Result> { - let resp = reqwest::blocking::Client::new() - .get(url) - .headers(api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - url - ) - } - let headers = resp.headers().clone(); + let resp = crate::get(url, &api_headers(self.auth_token.as_deref())?)?; + + // handle paged responses containing `Link` header: + // `Link: ; rel="next"` + let next_link = resp + .all("link") + .into_iter() + .find_map(|link| find_rel_next_link(link)) + .map(|s| s.to_owned()); - let releases = resp.json::()?; + let releases = resp.into_json::()?; let releases = releases .as_array() .ok_or_else(|| format_err!(Error::Release, "No releases found"))?; @@ -195,25 +189,10 @@ impl ReleaseList { .map(Release::from_release_gitea) .collect::>>()?; - // handle paged responses containing `Link` header: - // `Link: ; rel="next"` - let links = headers.get_all(reqwest::header::LINK); - - let next_link = links - .iter() - .filter_map(|link| { - if let Ok(link) = link.to_str() { - find_rel_next_link(link) - } else { - None - } - }) - .next(); - Ok(match next_link { None => releases, Some(link) => { - releases.extend(self.fetch_releases(link)?); + releases.extend(self.fetch_releases(&link)?); releases } }) @@ -472,46 +451,24 @@ impl Update { impl ReleaseUpdate for Update { fn get_latest_release(&self) -> Result { - set_ssl_vars!(); let api_url = format!( "{}/api/v1/repos/{}/{}/releases", self.host, self.repo_owner, self.repo_name ); - let resp = reqwest::blocking::Client::new() - .get(&api_url) - .headers(self.api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - let json = resp.json::()?; + + let req = crate::get(&api_url, &api_headers(self.auth_token.as_deref())?)?; + let json = req.into_json::()?; Release::from_release_gitea(&json[0]) } fn get_release_version(&self, ver: &str) -> Result { - set_ssl_vars!(); let api_url = format!( "{}/api/v1/repos/{}/{}/releases/{}", self.host, self.repo_owner, self.repo_name, ver ); - let resp = reqwest::blocking::Client::new() - .get(&api_url) - .headers(self.api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - let json = resp.json::()?; + + let req = crate::get(&api_url, &api_headers(self.auth_token.as_deref())?)?; + let json = req.into_json::()?; Release::from_release_gitea(&json) } @@ -562,10 +519,6 @@ impl ReleaseUpdate for Update { fn auth_token(&self) -> Option { self.auth_token.clone() } - - fn api_headers(&self, auth_token: &Option) -> Result { - api_headers(auth_token) - } } impl Default for UpdateBuilder { @@ -589,24 +542,3 @@ impl Default for UpdateBuilder { } } } - -fn api_headers(auth_token: &Option) -> Result { - let mut headers = header::HeaderMap::new(); - headers.insert( - header::USER_AGENT, - "rust-reqwest/self-update" - .parse() - .expect("gitea invalid user-agent"), - ); - - if let Some(token) = auth_token { - headers.insert( - header::AUTHORIZATION, - format!("token {}", token) - .parse() - .map_err(|err| Error::Config(format!("Failed to parse auth token: {}", err)))?, - ); - }; - - Ok(headers) -} diff --git a/src/backends/github.rs b/src/backends/github.rs index e8097d1..d5c625b 100644 --- a/src/backends/github.rs +++ b/src/backends/github.rs @@ -1,13 +1,11 @@ /*! GitHub releases */ -use hyper::HeaderMap; use std::env::{self, consts::EXE_SUFFIX}; use std::path::{Path, PathBuf}; -use reqwest::{self, header}; - use crate::backends::find_rel_next_link; +use crate::update::api_headers; use crate::{ errors::*, get_target, @@ -153,7 +151,6 @@ impl ReleaseList { /// Retrieve a list of `Release`s. /// If specified, filter for those containing a specified `target` pub fn fetch(self) -> Result> { - set_ssl_vars!(); let api_url = format!( "{}/repos/{}/{}/releases", self.custom_url @@ -174,21 +171,17 @@ impl ReleaseList { } fn fetch_releases(&self, url: &str) -> Result> { - let resp = reqwest::blocking::Client::new() - .get(url) - .headers(api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - url - ) - } - let headers = resp.headers().clone(); + let resp = crate::get(url, &api_headers(self.auth_token.as_deref())?)?; + + // handle paged responses containing `Link` header: + // `Link: ; rel="next"` + let next_link = resp + .all("link") + .into_iter() + .find_map(|link| find_rel_next_link(link)) + .map(|s| s.to_owned()); - let releases = resp.json::()?; + let releases = resp.into_json::()?; let releases = releases .as_array() .ok_or_else(|| format_err!(Error::Release, "No releases found"))?; @@ -197,25 +190,10 @@ impl ReleaseList { .map(Release::from_release) .collect::>>()?; - // handle paged responses containing `Link` header: - // `Link: ; rel="next"` - let links = headers.get_all(reqwest::header::LINK); - - let next_link = links - .iter() - .filter_map(|link| { - if let Ok(link) = link.to_str() { - find_rel_next_link(link) - } else { - None - } - }) - .next(); - Ok(match next_link { None => releases, Some(link) => { - releases.extend(self.fetch_releases(link)?); + releases.extend(self.fetch_releases(&link)?); releases } }) @@ -483,7 +461,6 @@ impl Update { impl ReleaseUpdate for Update { fn get_latest_release(&self) -> Result { - set_ssl_vars!(); let api_url = format!( "{}/repos/{}/{}/releases/latest", self.custom_url @@ -492,24 +469,13 @@ impl ReleaseUpdate for Update { self.repo_owner, self.repo_name ); - let resp = reqwest::blocking::Client::new() - .get(&api_url) - .headers(api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - let json = resp.json::()?; + + let resp = crate::get(&api_url, &api_headers(self.auth_token.as_deref())?)?; + let json = resp.into_json::()?; Release::from_release(&json) } fn get_release_version(&self, ver: &str) -> Result { - set_ssl_vars!(); let api_url = format!( "{}/repos/{}/{}/releases/tags/{}", self.custom_url @@ -519,19 +485,9 @@ impl ReleaseUpdate for Update { self.repo_name, ver ); - let resp = reqwest::blocking::Client::new() - .get(&api_url) - .headers(api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - let json = resp.json::()?; + + let resp = crate::get(&api_url, &api_headers(self.auth_token.as_deref())?)?; + let json = resp.into_json::()?; Release::from_release(&json) } @@ -586,10 +542,6 @@ impl ReleaseUpdate for Update { fn auth_token(&self) -> Option { self.auth_token.clone() } - - fn api_headers(&self, auth_token: &Option) -> Result { - api_headers(auth_token) - } } impl Default for UpdateBuilder { @@ -614,24 +566,3 @@ impl Default for UpdateBuilder { } } } - -fn api_headers(auth_token: &Option) -> Result { - let mut headers = header::HeaderMap::new(); - headers.insert( - header::USER_AGENT, - "rust-reqwest/self-update" - .parse() - .expect("github invalid user-agent"), - ); - - if let Some(token) = auth_token { - headers.insert( - header::AUTHORIZATION, - format!("token {}", token) - .parse() - .map_err(|err| Error::Config(format!("Failed to parse auth token: {}", err)))?, - ); - }; - - Ok(headers) -} diff --git a/src/backends/gitlab.rs b/src/backends/gitlab.rs index 92988af..d288d6a 100644 --- a/src/backends/gitlab.rs +++ b/src/backends/gitlab.rs @@ -1,11 +1,10 @@ /*! Gitlab releases */ +use std::borrow::Cow; use std::env::{self, consts::EXE_SUFFIX}; use std::path::{Path, PathBuf}; -use reqwest::{self, header}; - use crate::backends::find_rel_next_link; use crate::{ errors::*, @@ -150,7 +149,6 @@ impl ReleaseList { /// Retrieve a list of `Release`s. /// If specified, filter for those containing a specified `target` pub fn fetch(self) -> Result> { - set_ssl_vars!(); let api_url = format!( "{}/api/v4/projects/{}%2F{}/releases", self.host, @@ -169,21 +167,17 @@ impl ReleaseList { } fn fetch_releases(&self, url: &str) -> Result> { - let resp = reqwest::blocking::Client::new() - .get(url) - .headers(api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - url - ) - } - let headers = resp.headers().clone(); + let resp = crate::get(url, &api_headers(self.auth_token.as_deref())?)?; + + // handle paged responses containing `Link` header: + // `Link: ; rel="next"` + let next_link = resp + .all("link") + .into_iter() + .find_map(|link| find_rel_next_link(link)) + .map(|s| s.to_owned()); - let releases = resp.json::()?; + let releases = resp.into_json::()?; let releases = releases .as_array() .ok_or_else(|| format_err!(Error::Release, "No releases found"))?; @@ -192,25 +186,10 @@ impl ReleaseList { .map(Release::from_release_gitlab) .collect::>>()?; - // handle paged responses containing `Link` header: - // `Link: ; rel="next"` - let links = headers.get_all(reqwest::header::LINK); - - let next_link = links - .iter() - .filter_map(|link| { - if let Ok(link) = link.to_str() { - find_rel_next_link(link) - } else { - None - } - }) - .next(); - Ok(match next_link { None => releases, Some(link) => { - releases.extend(self.fetch_releases(link)?); + releases.extend(self.fetch_releases(&link)?); releases } }) @@ -465,31 +444,19 @@ impl Update { impl ReleaseUpdate for Update { fn get_latest_release(&self) -> Result { - set_ssl_vars!(); let api_url = format!( "{}/api/v4/projects/{}%2F{}/releases", self.host, urlencoding::encode(&self.repo_owner), self.repo_name ); - let resp = reqwest::blocking::Client::new() - .get(&api_url) - .headers(self.api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - let json = resp.json::()?; + + let resp = crate::get(&api_url, &api_headers(self.auth_token.as_deref())?)?; + let json = resp.into_json::()?; Release::from_release_gitlab(&json[0]) } fn get_release_version(&self, ver: &str) -> Result { - set_ssl_vars!(); let api_url = format!( "{}/api/v4/projects/{}%2F{}/releases/{}", self.host, @@ -497,19 +464,9 @@ impl ReleaseUpdate for Update { self.repo_name, ver ); - let resp = reqwest::blocking::Client::new() - .get(&api_url) - .headers(self.api_headers(&self.auth_token)?) - .send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "api request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - let json = resp.json::()?; + + let resp = crate::get(&api_url, &api_headers(self.auth_token.as_deref())?)?; + let json = resp.into_json::()?; Release::from_release_gitlab(&json) } @@ -561,7 +518,10 @@ impl ReleaseUpdate for Update { self.auth_token.clone() } - fn api_headers(&self, auth_token: &Option) -> Result { + fn api_headers( + &self, + auth_token: Option<&str>, + ) -> Result)>> { api_headers(auth_token) } } @@ -588,23 +548,9 @@ impl Default for UpdateBuilder { } } -fn api_headers(auth_token: &Option) -> Result { - let mut headers = header::HeaderMap::new(); - headers.insert( - header::USER_AGENT, - "rust-reqwest/self-update" - .parse() - .expect("gitlab invalid user-agent"), - ); - - if let Some(token) = auth_token { - headers.insert( - header::AUTHORIZATION, - format!("Bearer {}", token) - .parse() - .map_err(|err| Error::Config(format!("Failed to parse auth token: {}", err)))?, - ); - }; - - Ok(headers) +fn api_headers(auth_token: Option<&str>) -> Result)>> { + match auth_token { + Some(token) => Ok(vec![("authorization", format!("token {token}").into())]), + None => Ok(vec![]), + } } diff --git a/src/backends/s3.rs b/src/backends/s3.rs index 70f5592..fd2ee9b 100644 --- a/src/backends/s3.rs +++ b/src/backends/s3.rs @@ -1,6 +1,14 @@ /*! Amazon S3 releases */ +use std::cmp::Ordering; +use std::env::{self, consts::EXE_SUFFIX}; +use std::path::{Path, PathBuf}; + +use quick_xml::events::Event; +use quick_xml::Reader; +use regex::Regex; + use crate::{ errors::*, get_target, @@ -8,12 +16,6 @@ use crate::{ version::bump_is_greater, DEFAULT_PROGRESS_CHARS, DEFAULT_PROGRESS_TEMPLATE, }; -use quick_xml::events::Event; -use quick_xml::Reader; -use regex::Regex; -use std::cmp::Ordering; -use std::env::{self, consts::EXE_SUFFIX}; -use std::path::{Path, PathBuf}; /// Maximum number of items to retrieve from S3 API const MAX_KEYS: u8 = 100; @@ -537,17 +539,7 @@ fn fetch_releases_from_s3( debug!("using api url: {:?}", api_url); - let resp = reqwest::blocking::Client::new().get(&api_url).send()?; - if !resp.status().is_success() { - bail!( - Error::Network, - "S3 API request failed with status: {:?} - for: {:?}", - resp.status(), - api_url - ) - } - - let body = resp.text()?; + let body = crate::get(&api_url, &[])?.into_string()?; let mut reader = Reader::from_str(&body); reader.trim_text(true); diff --git a/src/errors.rs b/src/errors.rs index 1ded8fc..e3d2f8d 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,9 +17,10 @@ pub enum Error { #[cfg(feature = "archive-zip")] Zip(ZipError), Json(serde_json::Error), - Reqwest(reqwest::Error), SemVer(semver::Error), ArchiveNotEnabled(String), + Ureq(ureq::Error), + NativeTls(native_tls::Error), } impl std::fmt::Display for Error { @@ -32,38 +33,25 @@ impl std::fmt::Display for Error { Config(ref s) => write!(f, "ConfigError: {}", s), Io(ref e) => write!(f, "IoError: {}", e), Json(ref e) => write!(f, "JsonError: {}", e), - Reqwest(ref e) => write!(f, "ReqwestError: {}", e), SemVer(ref e) => write!(f, "SemVerError: {}", e), #[cfg(feature = "archive-zip")] Zip(ref e) => write!(f, "ZipError: {}", e), ArchiveNotEnabled(ref s) => write!(f, "ArchiveNotEnabled: Archive extension '{}' not supported, please enable 'archive-{}' feature!", s, s), + Ureq(ref e) => write!(f, "HTTP error: {}", e), + NativeTls(ref e) => write!(f, "Native TLS: {}", e), } } } impl std::error::Error for Error { - fn description(&self) -> &str { - "Self Update Error" - } - - fn cause(&self) -> Option<&dyn std::error::Error> { - use Error::*; - Some(match *self { - Io(ref e) => e, - Json(ref e) => e, - Reqwest(ref e) => e, - SemVer(ref e) => e, - _ => return None, - }) - } - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use Error::*; Some(match *self { Io(ref e) => e, Json(ref e) => e, - Reqwest(ref e) => e, SemVer(ref e) => e, + Ureq(ref e) => e, + NativeTls(ref e) => e, _ => return None, }) } @@ -81,12 +69,6 @@ impl From for Error { } } -impl From for Error { - fn from(e: reqwest::Error) -> Error { - Error::Reqwest(e) - } -} - impl From for Error { fn from(e: semver::Error) -> Error { Error::SemVer(e) @@ -99,3 +81,15 @@ impl From for Error { Error::Zip(e) } } + +impl From for Error { + fn from(value: ureq::Error) -> Self { + Self::Ureq(value) + } +} + +impl From for Error { + fn from(value: native_tls::Error) -> Self { + Self::NativeTls(value) + } +} diff --git a/src/lib.rs b/src/lib.rs index 79cb6b6..667f803 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ fn update() -> Result<(), Box<::std::error::Error>> { let tmp_tarball = ::std::fs::File::open(&tmp_tarball_path)?; self_update::Download::from_url(&asset.download_url) - .set_header(reqwest::header::ACCEPT, "application/octet-stream".parse()?) + .set_header(header::ACCEPT, "application/octet-stream".parse()?) .download_to(&tmp_tarball)?; let bin_name = std::path::PathBuf::from("self_update_bin"); @@ -126,17 +126,19 @@ fn update() -> Result<(), Box<::std::error::Error>> { */ -pub use self_replace; -pub use tempfile::TempDir; - -#[cfg(feature = "compression-flate2")] -use either::Either; -use indicatif::{ProgressBar, ProgressStyle}; -use reqwest::header; +use std::borrow::Cow; use std::cmp::min; use std::fs; use std::io; use std::path; +use std::sync::Arc; + +#[cfg(feature = "compression-flate2")] +use either::Either; +use indicatif::{ProgressBar, ProgressStyle}; +use once_cell::sync::OnceCell; +pub use self_replace; +pub use tempfile::TempDir; #[macro_use] extern crate log; @@ -153,6 +155,7 @@ pub const DEFAULT_PROGRESS_TEMPLATE: &str = pub const DEFAULT_PROGRESS_CHARS: &str = "=>-"; use errors::*; +use ureq::AgentBuilder; /// Get the current target triple. /// @@ -608,17 +611,18 @@ impl<'a> Move<'a> { pub struct Download { show_progress: bool, url: String, - headers: reqwest::header::HeaderMap, + headers: Vec<(&'static str, Cow<'static, str>)>, progress_template: String, progress_chars: String, } + impl Download { /// Specify download url pub fn from_url(url: &str) -> Self { Self { show_progress: false, url: url.to_owned(), - headers: reqwest::header::HeaderMap::new(), + headers: vec![], progress_template: DEFAULT_PROGRESS_TEMPLATE.to_string(), progress_chars: DEFAULT_PROGRESS_CHARS.to_string(), } @@ -642,18 +646,14 @@ impl Download { } /// Set the download request headers, replaces the existing `HeaderMap` - pub fn set_headers(&mut self, headers: reqwest::header::HeaderMap) -> &mut Self { + pub fn set_headers(&mut self, headers: Vec<(&'static str, Cow<'static, str>)>) -> &mut Self { self.headers = headers; self } /// Set a download request header, inserts into the existing `HeaderMap` - pub fn set_header( - &mut self, - name: reqwest::header::HeaderName, - value: reqwest::header::HeaderValue, - ) -> &mut Self { - self.headers.insert(name, value); + pub fn set_header(&mut self, name: &'static str, value: Cow<'static, str>) -> &mut Self { + self.headers.push((name, value)); self } @@ -669,37 +669,14 @@ impl Download { /// * Writing from `BufReader`-buffer to `File` pub fn download_to(&self, mut dest: T) -> Result<()> { use io::BufRead; - let mut headers = self.headers.clone(); - if !headers.contains_key(header::USER_AGENT) { - headers.insert( - header::USER_AGENT, - "rust-reqwest/self-update" - .parse() - .expect("invalid user-agent"), - ); - } - set_ssl_vars!(); - let resp = reqwest::blocking::Client::new() - .get(&self.url) - .headers(headers) - .send()?; + let resp = get(&self.url, &self.headers)?; + let size = resp - .headers() - .get(reqwest::header::CONTENT_LENGTH) - .map(|val| { - val.to_str() - .map(|s| s.parse::().unwrap_or(0)) - .unwrap_or(0) - }) + .header("content-length") + .map(|s| s.parse::().unwrap_or(0)) .unwrap_or(0); - if !resp.status().is_success() { - bail!( - Error::Update, - "Download request failed with status: {:?}", - resp.status() - ) - } + let resp = resp.into_reader(); let show_progress = if size == 0 { false } else { self.show_progress }; let mut src = io::BufReader::new(resp); @@ -740,6 +717,41 @@ impl Download { } } +fn get(url: &str, headers: &[(&str, Cow<'_, str>)]) -> Result { + #[cfg(target_os = "linux")] + { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + if std::env::var_os("SSL_CERT_FILE").is_none() { + std::env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"); + } + if std::env::var_os("SSL_CERT_DIR").is_none() { + std::env::set_var("SSL_CERT_DIR", "/etc/ssl/certs"); + } + }); + } + + static TLS_CONNECTOR: OnceCell> = OnceCell::new(); + let tls_connector = TLS_CONNECTOR + .get_or_try_init::<_, native_tls::Error>(|| { + let tls_connector = native_tls::TlsConnector::builder() + .min_protocol_version(Some(native_tls::Protocol::Tlsv12)) + .build()?; + Ok(Arc::new(tls_connector)) + })? + .clone(); + + let mut req = AgentBuilder::new() + .tls_connector(tls_connector) + .build() + .request("GET", url) + .set("user-agent", "rust-reqwest/self-update"); + for (key, value) in headers { + req = req.set(key, value); + } + Ok(req.call()?) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/macros.rs b/src/macros.rs index 8b74d98..bf0130e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -8,21 +8,6 @@ macro_rules! cargo_crate_version { }; } -/// Set ssl cert env. vars to make sure openssl can find required files -macro_rules! set_ssl_vars { - () => { - #[cfg(target_os = "linux")] - { - if ::std::env::var_os("SSL_CERT_FILE").is_none() { - ::std::env::set_var("SSL_CERT_FILE", "/etc/ssl/certs/ca-certificates.crt"); - } - if ::std::env::var_os("SSL_CERT_DIR").is_none() { - ::std::env::set_var("SSL_CERT_DIR", "/etc/ssl/certs"); - } - } - }; -} - /// Helper to `print!` and immediately `flush` `stdout` macro_rules! print_flush { ($literal:expr) => { diff --git a/src/update.rs b/src/update.rs index 0857311..c5566f4 100644 --- a/src/update.rs +++ b/src/update.rs @@ -1,4 +1,4 @@ -use reqwest::{self, header}; +use std::borrow::Cow; use std::fs; use std::path::PathBuf; @@ -122,19 +122,11 @@ pub trait ReleaseUpdate { fn auth_token(&self) -> Option; /// Construct a header with an authorisation entry if an auth token is provided - fn api_headers(&self, auth_token: &Option) -> Result { - let mut headers = header::HeaderMap::new(); - - if auth_token.is_some() { - headers.insert( - header::AUTHORIZATION, - (String::from("token ") + &auth_token.clone().unwrap()) - .parse() - .unwrap(), - ); - }; - - Ok(headers) + fn api_headers( + &self, + auth_token: Option<&str>, + ) -> Result)>> { + api_headers(auth_token) } /// Display release information and update the current binary to the latest release, pending @@ -220,8 +212,8 @@ pub trait ReleaseUpdate { println(show_output, "Downloading..."); let mut download = Download::from_url(&target_asset.download_url); - let mut headers = self.api_headers(&self.auth_token())?; - headers.insert(header::ACCEPT, "application/octet-stream".parse().unwrap()); + let mut headers = self.api_headers(self.auth_token().as_deref())?; + headers.push(("accept", "application/octet-stream".into())); download.set_headers(headers); download.show_progress(self.show_download_progress()); @@ -259,3 +251,12 @@ fn println(show_output: bool, msg: &str) { println!("{}", msg); } } + +pub(crate) fn api_headers( + auth_token: Option<&str>, +) -> Result)>> { + match auth_token { + Some(token) => Ok(vec![("authorization", format!("token {token}").into())]), + None => Ok(vec![]), + } +}