UTF encoded form

This commit is contained in:
Matthew Ransley
2023-07-05 14:17:53 +01:00
parent d51035a0f2
commit 4ee6bdec9a
4 changed files with 211 additions and 317 deletions

View File

@@ -10,15 +10,15 @@ use serde::Serialize;
use serde_json;
use super::body::Body;
use super::client::{Client, Pending};
use super::client::{ Client, Pending };
#[cfg(feature = "multipart")]
use super::multipart;
use super::response::Response;
#[cfg(feature = "multipart")]
use crate::header::CONTENT_LENGTH;
use crate::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE};
use crate::{Method, Url};
use http::{request::Parts, Request as HttpRequest, Version};
use crate::header::{ HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE };
use crate::{ Method, Url };
use http::{ request::Parts, Request as HttpRequest, Version };
/// A request which can be executed with `Client::execute()`.
pub struct Request {
@@ -142,23 +142,9 @@ impl Request {
}
pub(super) fn pieces(
self,
) -> (
Method,
Url,
HeaderMap,
Option<Body>,
Option<Duration>,
Version,
) {
(
self.method,
self.url,
self.headers,
self.body,
self.timeout,
self.version,
)
self
) -> (Method, Url, HeaderMap, Option<Body>, Option<Duration>, Version) {
(self.method, self.url, self.headers, self.body, self.timeout, self.version)
}
}
@@ -166,8 +152,7 @@ impl RequestBuilder {
pub(super) fn new(client: Client, request: crate::Result<Request>) -> RequestBuilder {
let mut builder = RequestBuilder { client, request };
let auth = builder
.request
let auth = builder.request
.as_mut()
.ok()
.and_then(|req| extract_authority(&mut req.url));
@@ -181,39 +166,44 @@ impl RequestBuilder {
/// Add a `Header` to this Request.
pub fn header<K, V>(self, key: K, value: V) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>
{
self.header_sensitive(key, value, false)
}
/// Add a `Header` to this Request with ability to define if header_value is sensitive.
fn header_sensitive<K, V>(mut self, key: K, value: V, sensitive: bool) -> RequestBuilder
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<http::Error>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<http::Error>
{
let mut error = None;
if let Ok(ref mut req) = self.request {
match <HeaderName as TryFrom<K>>::try_from(key) {
Ok(key) => match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(mut value) => {
// We want to potentially make an unsensitive header
// to be sensitive, not the reverse. So, don't turn off
// a previously sensitive header.
if sensitive {
value.set_sensitive(true);
Ok(key) =>
match <HeaderValue as TryFrom<V>>::try_from(value) {
Ok(mut value) => {
// We want to potentially make an unsensitive header
// to be sensitive, not the reverse. So, don't turn off
// a previously sensitive header.
if sensitive {
value.set_sensitive(true);
}
req.headers_mut().append(key, value);
}
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
req.headers_mut().append(key, value);
}
Err(e) => error = Some(crate::error::builder(e.into())),
},
Err(e) => error = Some(crate::error::builder(e.into())),
Err(e) => {
error = Some(crate::error::builder(e.into()));
}
};
}
if let Some(err) = error {
@@ -247,14 +237,14 @@ impl RequestBuilder {
/// # }
/// ```
pub fn basic_auth<U, P>(self, username: U, password: Option<P>) -> RequestBuilder
where
U: fmt::Display,
P: fmt::Display,
where U: fmt::Display, P: fmt::Display
{
let mut header_value = b"Basic ".to_vec();
{
let mut encoder =
Base64Encoder::from(&mut header_value, &base64::engine::DEFAULT_ENGINE);
let mut encoder = Base64Encoder::from(
&mut header_value,
&base64::engine::DEFAULT_ENGINE
);
// The unwraps here are fine because Vec::write* is infallible.
write!(encoder, "{}:", username).unwrap();
if let Some(password) = password {
@@ -266,10 +256,7 @@ impl RequestBuilder {
}
/// Enable HTTP bearer authentication.
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder
where
T: fmt::Display,
{
pub fn bearer_auth<T>(self, token: T) -> RequestBuilder where T: fmt::Display {
let header_value = format!("Bearer {}", token);
self.header_sensitive(crate::header::AUTHORIZATION, header_value, true)
}
@@ -318,7 +305,7 @@ impl RequestBuilder {
pub fn multipart(self, mut multipart: multipart::Form) -> RequestBuilder {
let mut builder = self.header(
CONTENT_TYPE,
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str(),
format!("multipart/form-data; boundary={}", multipart.boundary()).as_str()
);
builder = match multipart.compute_length() {
@@ -327,7 +314,7 @@ impl RequestBuilder {
};
if let Ok(ref mut req) = builder.request {
*req.body_mut() = Some(multipart.stream())
*req.body_mut() = Some(multipart.stream());
}
builder
}
@@ -414,11 +401,13 @@ impl RequestBuilder {
Ok(body) => {
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
HeaderValue::from_static("application/x-www-form-urlencoded; charset=UTF-8")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -444,11 +433,15 @@ impl RequestBuilder {
if let Ok(ref mut req) = self.request {
match serde_json::to_vec(json) {
Ok(body) => {
req.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
req.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/json")
);
*req.body_mut() = Some(body.into());
}
Err(err) => error = Some(crate::error::builder(err)),
Err(err) => {
error = Some(crate::error::builder(err));
}
}
}
if let Some(err) = error {
@@ -553,11 +546,9 @@ impl fmt::Debug for RequestBuilder {
fn fmt_request_fields<'a, 'b>(
f: &'a mut fmt::DebugStruct<'a, 'b>,
req: &Request,
req: &Request
) -> &'a mut fmt::DebugStruct<'a, 'b> {
f.field("method", &req.method)
.field("url", &req.url)
.field("headers", &req.headers)
f.field("method", &req.method).field("url", &req.url).field("headers", &req.headers)
}
/// Check the request URL for a "username:password" type authority, and if
@@ -566,21 +557,15 @@ pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>
use percent_encoding::percent_decode;
if url.has_authority() {
let username: String = percent_decode(url.username().as_bytes())
.decode_utf8()
.ok()?
.into();
let password = url.password().and_then(|pass| {
percent_decode(pass.as_bytes())
.decode_utf8()
.ok()
.map(String::from)
});
let username: String = percent_decode(url.username().as_bytes()).decode_utf8().ok()?.into();
let password = url
.password()
.and_then(|pass| {
percent_decode(pass.as_bytes()).decode_utf8().ok().map(String::from)
});
if !username.is_empty() || password.is_some() {
url.set_username("")
.expect("has_authority means set_username shouldn't fail");
url.set_password(None)
.expect("has_authority means set_password shouldn't fail");
url.set_username("").expect("has_authority means set_username shouldn't fail");
url.set_password(None).expect("has_authority means set_password shouldn't fail");
return Some((username, password));
}
}
@@ -588,21 +573,12 @@ pub(crate) fn extract_authority(url: &mut Url) -> Option<(String, Option<String>
None
}
impl<T> TryFrom<HttpRequest<T>> for Request
where
T: Into<Body>,
{
impl<T> TryFrom<HttpRequest<T>> for Request where T: Into<Body> {
type Error = crate::Error;
fn try_from(req: HttpRequest<T>) -> crate::Result<Self> {
let (parts, body) = req.into_parts();
let Parts {
method,
uri,
headers,
version,
..
} = parts;
let Parts { method, uri, headers, version, .. } = parts;
let url = Url::parse(&uri.to_string()).map_err(crate::error::builder)?;
Ok(Request {
method,
@@ -619,14 +595,7 @@ impl TryFrom<Request> for HttpRequest<Body> {
type Error = crate::Error;
fn try_from(req: Request) -> crate::Result<Self> {
let Request {
method,
url,
headers,
body,
version,
..
} = req;
let Request { method, url, headers, body, version, .. } = req;
let mut req = HttpRequest::builder()
.version(version)
@@ -642,7 +611,7 @@ impl TryFrom<Request> for HttpRequest<Body> {
#[cfg(test)]
mod tests {
use super::{Client, HttpRequest, Request, Version};
use super::{ Client, HttpRequest, Request, Version };
use crate::Method;
use serde::Serialize;
use std::collections::BTreeMap;
@@ -667,7 +636,12 @@ mod tests {
let some_url = "https://google.com/";
let r = client.get(some_url);
let r = r.query(&[("foo", "a"), ("foo", "b")]);
let r = r.query(
&[
("foo", "a"),
("foo", "b"),
]
);
let req = r.build().expect("request is valid");
assert_eq!(req.url().query(), Some("foo=a&foo=b"));
@@ -743,11 +717,7 @@ mod tests {
let some_url = "https://google.com/";
let empty_query: &[(&str, &str)] = &[];
let req = client
.get(some_url)
.query(empty_query)
.build()
.expect("request build");
let req = client.get(some_url).query(empty_query).build().expect("request build");
assert_eq!(req.url().query(), None);
assert_eq!(req.url().as_str(), "https://google.com/");
@@ -760,11 +730,7 @@ mod tests {
.post("http://httpbin.org/post")
.header("foo", "bar")
.body("from a &str!");
let req = builder
.try_clone()
.expect("clone successful")
.build()
.expect("request is valid");
let req = builder.try_clone().expect("clone successful").build().expect("request is valid");
assert_eq!(req.url().as_str(), "http://httpbin.org/post");
assert_eq!(req.method(), Method::POST);
assert_eq!(req.headers()["foo"], "bar");
@@ -774,11 +740,7 @@ mod tests {
fn try_clone_no_body() {
let client = Client::new();
let builder = client.get("http://httpbin.org/get");
let req = builder
.try_clone()
.expect("clone successful")
.build()
.expect("request is valid");
let req = builder.try_clone().expect("clone successful").build().expect("request is valid");
assert_eq!(req.url().as_str(), "http://httpbin.org/get");
assert_eq!(req.method(), Method::GET);
assert!(req.body().is_none());
@@ -790,9 +752,7 @@ mod tests {
let chunks: Vec<Result<_, ::std::io::Error>> = vec![Ok("hello"), Ok(" "), Ok("world")];
let stream = futures_util::stream::iter(chunks);
let client = Client::new();
let builder = client
.get("http://httpbin.org/get")
.body(super::Body::wrap_stream(stream));
let builder = client.get("http://httpbin.org/get").body(super::Body::wrap_stream(stream));
let clone = builder.try_clone();
assert!(clone.is_none());
}
@@ -805,10 +765,7 @@ mod tests {
let req = client.get(some_url).build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(
req.headers()["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
assert_eq!(req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
}
#[test]
@@ -823,10 +780,7 @@ mod tests {
.expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(
req.headers()["authorization"],
"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="
);
assert_eq!(req.headers()["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==");
assert!(req.headers()["authorization"].is_sensitive());
}
@@ -835,11 +789,7 @@ mod tests {
let client = Client::new();
let some_url = "https://localhost/";
let req = client
.get(some_url)
.bearer_auth("Hold my bear")
.build()
.expect("request build");
let req = client.get(some_url).bearer_auth("Hold my bear").build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(req.headers()["authorization"], "Bearer Hold my bear");
@@ -854,11 +804,7 @@ mod tests {
let mut header = http::HeaderValue::from_static("in plain sight");
header.set_sensitive(true);
let req = client
.get(some_url)
.header("hiding", header)
.build()
.expect("request build");
let req = client.get(some_url).header("hiding", header).build().expect("request build");
assert_eq!(req.url().as_str(), "https://localhost/");
assert_eq!(req.headers()["hiding"], "in plain sight");