mirror of
https://git.proxmox.com/git/proxmox
synced 2025-08-15 10:31:58 +00:00
switch from curl to ureq
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
parent
fb547f5935
commit
7622380dd7
11
Cargo.toml
11
Cargo.toml
@ -16,11 +16,18 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
openssl = "0.10.29"
|
openssl = "0.10.29"
|
||||||
|
|
||||||
curl = { version = "0.4.33", optional = true }
|
# For the client
|
||||||
|
native-tls = { version = "0.2", optional = true }
|
||||||
|
|
||||||
|
[dependencies.ureq]
|
||||||
|
optional = true
|
||||||
|
version = "2.4"
|
||||||
|
default-features = false
|
||||||
|
features = [ "native-tls", "gzip" ]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
client = ["curl"]
|
client = ["ureq", "native-tls"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
149
src/client.rs
149
src/client.rs
@ -1,8 +1,8 @@
|
|||||||
//! A blocking higher-level ACME client implementation using 'curl'.
|
//! A blocking higher-level ACME client implementation using 'curl'.
|
||||||
|
|
||||||
use std::convert::TryFrom;
|
use std::io::Read;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use curl::easy;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::b64u;
|
use crate::b64u;
|
||||||
@ -73,113 +73,102 @@ pub struct Headers {
|
|||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Headers {
|
|
||||||
fn read_header(&mut self, header: &[u8]) {
|
|
||||||
let (name, value) = match parse_header(header) {
|
|
||||||
Some(h) => h,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
if name.eq_ignore_ascii_case(crate::REPLAY_NONCE) {
|
|
||||||
self.nonce = Some(value.to_owned());
|
|
||||||
} else if name.eq_ignore_ascii_case(crate::LOCATION) {
|
|
||||||
self.location = Some(value.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Inner {
|
struct Inner {
|
||||||
easy: easy::Easy,
|
agent: Option<ureq::Agent>,
|
||||||
nonce: Option<String>,
|
nonce: Option<String>,
|
||||||
proxy: Option<String>,
|
proxy: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inner {
|
impl Inner {
|
||||||
pub fn new() -> Self {
|
fn agent(&mut self) -> Result<&mut ureq::Agent, Error> {
|
||||||
|
if self.agent.is_none() {
|
||||||
|
let connector = Arc::new(
|
||||||
|
native_tls::TlsConnector::new()
|
||||||
|
.map_err(|err| format_err!("failed to create tls connector: {}", err))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut builder = ureq::AgentBuilder::new().tls_connector(connector);
|
||||||
|
|
||||||
|
if let Some(proxy) = self.proxy.as_deref() {
|
||||||
|
builder = builder.proxy(
|
||||||
|
ureq::Proxy::new(proxy)
|
||||||
|
.map_err(|err| format_err!("failed to set proxy: {}", err))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.agent = Some(builder.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.agent.as_mut().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
easy: easy::Easy::new(),
|
agent: None,
|
||||||
nonce: None,
|
nonce: None,
|
||||||
proxy: None,
|
proxy: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(
|
fn execute(
|
||||||
&mut self,
|
&mut self,
|
||||||
method: &[u8],
|
method: &[u8],
|
||||||
url: &str,
|
url: &str,
|
||||||
request_body: Option<&[u8]>,
|
request_body: Option<(&str, &[u8])>, // content-type and body
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
let mut body = Vec::new();
|
let agent = self.agent()?;
|
||||||
let mut headers = Headers::default();
|
let req = match method {
|
||||||
let mut upload;
|
b"POST" => agent.post(url),
|
||||||
|
b"GET" => agent.get(url),
|
||||||
match method {
|
b"HEAD" => agent.head(url),
|
||||||
b"POST" => self.easy.post(true)?,
|
|
||||||
b"GET" => self.easy.get(true)?,
|
|
||||||
b"HEAD" => self.easy.nobody(true)?,
|
|
||||||
other => bail!("invalid http method: {:?}", other),
|
other => bail!("invalid http method: {:?}", other),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = if let Some((content_type, body)) = request_body {
|
||||||
|
req.set("Content-Type", content_type)
|
||||||
|
.set("Content-Length", &body.len().to_string())
|
||||||
|
.send_bytes(body)
|
||||||
|
} else {
|
||||||
|
req.call()
|
||||||
|
}
|
||||||
|
.map_err(|err| format_err!("http request failed: {}", err))?;
|
||||||
|
|
||||||
|
let mut headers = Headers::default();
|
||||||
|
if let Some(value) = response.header(crate::LOCATION) {
|
||||||
|
headers.location = Some(value.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.easy.url(url)?;
|
if let Some(value) = response.header(crate::REPLAY_NONCE) {
|
||||||
|
headers.nonce = Some(value.to_owned());
|
||||||
if let Some(p) = &self.proxy {
|
|
||||||
self.easy.proxy(p)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
let status = response.status();
|
||||||
let mut transfer = self.easy.transfer();
|
|
||||||
|
|
||||||
transfer.write_function(|data| {
|
let mut body = Vec::new();
|
||||||
body.extend(data);
|
response
|
||||||
Ok(data.len())
|
.into_reader()
|
||||||
})?;
|
.take(16 * 1024 * 1024) // arbitrary limit
|
||||||
|
.read_to_end(&mut body)
|
||||||
|
.map_err(|err| format_err!("failed to read response body: {}", err))?;
|
||||||
|
|
||||||
transfer.header_function(|data| {
|
|
||||||
headers.read_header(data);
|
|
||||||
true
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if let Some(body) = request_body {
|
|
||||||
upload = body;
|
|
||||||
transfer.read_function(|dest| {
|
|
||||||
let len = upload.len().min(dest.len());
|
|
||||||
dest[..len].copy_from_slice(&upload[..len]);
|
|
||||||
upload = &upload[len..];
|
|
||||||
Ok(len)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
transfer.perform()?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let status = self.easy.response_code()?;
|
|
||||||
let status =
|
|
||||||
u16::try_from(status).map_err(|_| format_err!("invalid status code: {}", status))?;
|
|
||||||
Ok(HttpResponse {
|
Ok(HttpResponse {
|
||||||
body,
|
|
||||||
status,
|
status,
|
||||||
headers,
|
headers,
|
||||||
|
body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_proxy(&mut self, proxy: String) {
|
pub fn set_proxy(&mut self, proxy: String) {
|
||||||
self.proxy = Some(proxy);
|
self.proxy = Some(proxy);
|
||||||
|
self.agent = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Low-level API to run an n API request. This automatically updates the current nonce!
|
/// Low-level API to run an API request. This automatically updates the current nonce!
|
||||||
fn run_request(&mut self, request: Request) -> Result<HttpResponse, Error> {
|
fn run_request(&mut self, request: Request) -> Result<HttpResponse, Error> {
|
||||||
self.easy.reset();
|
let body = if request.body.is_empty() {
|
||||||
|
|
||||||
let body = if !request.content_type.is_empty() {
|
|
||||||
let mut headers = easy::List::new();
|
|
||||||
headers.append(&format!("Content-Type: {}", request.content_type))?;
|
|
||||||
headers.append(&format!("Content-Length: {}", request.body.len()))?;
|
|
||||||
self.easy
|
|
||||||
.http_headers(headers)
|
|
||||||
.map_err(|err| format_err!("curl error: {}", err))?;
|
|
||||||
Some(request.body.as_bytes())
|
|
||||||
} else {
|
|
||||||
None
|
None
|
||||||
|
} else {
|
||||||
|
Some((request.content_type, request.body.as_bytes()))
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut response = self
|
let mut response = self
|
||||||
@ -603,18 +592,6 @@ impl Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_header(data: &[u8]) -> Option<(&str, &str)> {
|
|
||||||
let colon = data.iter().position(|&b| b == b':')?;
|
|
||||||
|
|
||||||
let name = std::str::from_utf8(&data[..colon]).ok()?;
|
|
||||||
|
|
||||||
let value = &data[(colon + 1)..];
|
|
||||||
let value_start = value.iter().position(|&b| !b.is_ascii_whitespace())?;
|
|
||||||
let value = std::str::from_utf8(&value[value_start..]).ok()?;
|
|
||||||
|
|
||||||
Some((name.trim(), value.trim()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// bad nonce retry count helper
|
/// bad nonce retry count helper
|
||||||
struct Retry(usize);
|
struct Retry(usize);
|
||||||
|
|
||||||
|
13
src/error.rs
13
src/error.rs
@ -63,13 +63,13 @@ pub enum Error {
|
|||||||
/// acme errors.
|
/// acme errors.
|
||||||
Custom(String),
|
Custom(String),
|
||||||
|
|
||||||
/// If built with the `client` feature, this is where general curl/network errors end up.
|
/// If built with the `client` feature, this is where general ureq/network errors end up.
|
||||||
/// This is usually a `curl::Error`, however in order to provide an API which is not
|
/// This is usually a `ureq::Error`, however in order to provide an API which is not
|
||||||
/// feature-dependent, this variant is always present and contains a boxed `dyn Error`.
|
/// feature-dependent, this variant is always present and contains a boxed `dyn Error`.
|
||||||
HttpClient(Box<dyn std::error::Error + Send + Sync + 'static>),
|
HttpClient(Box<dyn std::error::Error + Send + Sync + 'static>),
|
||||||
|
|
||||||
/// If built with the `client` feature, this is where client specific errors which are not from
|
/// If built with the `client` feature, this is where client specific errors which are not from
|
||||||
/// errors forwarded from `curl` end up.
|
/// errors forwarded from `ureq` end up.
|
||||||
Client(String),
|
Client(String),
|
||||||
|
|
||||||
/// A non-openssl error occurred while building data for the CSR.
|
/// A non-openssl error occurred while building data for the CSR.
|
||||||
@ -142,10 +142,3 @@ impl From<crate::request::ErrorResponse> for Error {
|
|||||||
Error::Api(e)
|
Error::Api(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
|
||||||
impl From<curl::Error> for Error {
|
|
||||||
fn from(e: curl::Error) -> Self {
|
|
||||||
Error::HttpClient(Box::new(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user