blob: 2265cfea48c8b23eff105fa72115beaf68851202 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
use std::borrow::Cow;
use std::mem;
use std::time::Duration;
use crate::{Error, Result};
use http::HeaderMap;
use http::HeaderValue;
use http::Method;
use http::Uri;
use http::header::HeaderName;
use http::uri::Authority;
use http::uri::PathAndQuery;
use http::uri::Scheme;
use std::str::FromStr;
/// Signing context for request.
#[derive(Debug)]
pub struct SigningRequest {
/// HTTP method.
pub method: Method,
/// HTTP scheme.
pub scheme: Scheme,
/// HTTP authority.
pub authority: Authority,
/// HTTP path.
pub path: String,
/// HTTP query parameters.
pub query: Vec<(String, String)>,
/// HTTP headers.
pub headers: HeaderMap,
}
impl SigningRequest {
/// Build a signing context from http::request::Parts.
pub fn build(parts: &mut http::request::Parts) -> Result<Self> {
let uri = mem::take(&mut parts.uri).into_parts();
let paq = uri
.path_and_query
.unwrap_or_else(|| PathAndQuery::from_static("/"));
Ok(SigningRequest {
method: parts.method.clone(),
scheme: uri.scheme.unwrap_or(Scheme::HTTP),
authority: uri.authority.ok_or_else(|| {
Error::request_invalid("request without authority is invalid for signing")
})?,
path: paq.path().to_string(),
query: paq
.query()
.map(|v| {
form_urlencoded::parse(v.as_bytes())
.map(|(k, v)| (k.into_owned(), v.into_owned()))
.collect()
})
.unwrap_or_default(),
// Take the headers out of the request to avoid copy.
// We will return it back when apply the context.
headers: mem::take(&mut parts.headers),
})
}
/// Apply the signing context back to http::request::Parts.
pub fn apply(mut self, parts: &mut http::request::Parts) -> Result<()> {
let query_size = self.query_size();
// Return headers back.
mem::swap(&mut parts.headers, &mut self.headers);
parts.method = self.method;
parts.uri = {
let mut uri_parts = mem::take(&mut parts.uri).into_parts();
// Return scheme bakc.
uri_parts.scheme = Some(self.scheme);
// Return authority back.
uri_parts.authority = Some(self.authority);
// Build path and query.
uri_parts.path_and_query =
{
let paq = if query_size == 0 {
self.path
} else {
let mut s = self.path;
s.reserve(query_size + 1);
s.push('?');
for (i, (k, v)) in self.query.iter().enumerate() {
if i > 0 {
s.push('&');
}
s.push_str(k);
if !v.is_empty() {
s.push('=');
s.push_str(v);
}
}
s
};
Some(PathAndQuery::from_str(&paq).map_err(|e| {
Error::request_invalid("invalid path and query").with_source(e)
})?)
};
Uri::from_parts(uri_parts)
.map_err(|e| Error::request_invalid("failed to build URI").with_source(e))?
};
Ok(())
}
/// Get the path percent decoded.
pub fn path_percent_decoded(&self) -> Cow<'_, str> {
percent_encoding::percent_decode_str(&self.path).decode_utf8_lossy()
}
/// Get query size.
#[inline]
pub fn query_size(&self) -> usize {
self.query
.iter()
.map(|(k, v)| k.len() + v.len())
.sum::<usize>()
}
/// Push a new query pair into query list.
#[inline]
pub fn query_push(&mut self, key: impl Into<String>, value: impl Into<String>) {
self.query.push((key.into(), value.into()));
}
/// Push a query string into query list.
#[inline]
pub fn query_append(&mut self, query: &str) {
self.query.push((query.to_string(), "".to_string()));
}
/// Get query value by filter.
pub fn query_to_vec_with_filter(&self, filter: impl Fn(&str) -> bool) -> Vec<(String, String)> {
self.query
.iter()
// Filter all queries
.filter(|(k, _)| filter(k))
// Clone all queries
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect()
}
/// Convert sorted query to string.
///
/// ```shell
/// [(a, b), (c, d)] => "a:b\nc:d"
/// ```
pub fn query_to_string(mut query: Vec<(String, String)>, sep: &str, join: &str) -> String {
let mut s = String::with_capacity(16);
// Sort via header name.
query.sort();
for (idx, (k, v)) in query.into_iter().enumerate() {
if idx != 0 {
s.push_str(join);
}
s.push_str(&k);
if !v.is_empty() {
s.push_str(sep);
s.push_str(&v);
}
}
s
}
/// Convert sorted query to percent decoded string.
///
/// ```shell
/// [(a, b), (c, d)] => "a:b\nc:d"
/// ```
pub fn query_to_percent_decoded_string(
mut query: Vec<(String, String)>,
sep: &str,
join: &str,
) -> String {
let mut s = String::with_capacity(16);
// Sort via header name.
query.sort();
for (idx, (k, v)) in query.into_iter().enumerate() {
if idx != 0 {
s.push_str(join);
}
s.push_str(&k);
if !v.is_empty() {
s.push_str(sep);
s.push_str(&percent_encoding::percent_decode_str(&v).decode_utf8_lossy());
}
}
s
}
/// Get header value by name.
///
/// Returns empty string if header not found.
#[inline]
pub fn header_get_or_default(&self, key: &HeaderName) -> Result<&str> {
match self.headers.get(key) {
Some(v) => v
.to_str()
.map_err(|e| Error::request_invalid("invalid header value").with_source(e)),
None => Ok(""),
}
}
/// Normalize header value.
pub fn header_value_normalize(v: &mut HeaderValue) {
let bs = v.as_bytes();
let starting_index = bs.iter().position(|b| *b != b' ').unwrap_or(0);
let ending_offset = bs.iter().rev().position(|b| *b != b' ').unwrap_or(0);
let ending_index = bs.len() - ending_offset;
// This can't fail because we started with a valid HeaderValue and then only trimmed spaces
*v = HeaderValue::from_bytes(&bs[starting_index..ending_index])
.expect("invalid header value")
}
/// Get header names as sorted vector.
pub fn header_name_to_vec_sorted(&self) -> Vec<&str> {
let mut h = self
.headers
.keys()
.map(|k| k.as_str())
.collect::<Vec<&str>>();
h.sort_unstable();
h
}
/// Get header names with given prefix.
pub fn header_to_vec_with_prefix(&self, prefix: &str) -> Vec<(String, String)> {
self.headers
.iter()
// Filter all header that starts with prefix
.filter(|(k, _)| k.as_str().starts_with(prefix))
// Convert all header name to lowercase
.map(|(k, v)| {
(
k.as_str().to_lowercase(),
v.to_str().expect("must be valid header").to_string(),
)
})
.collect()
}
/// Convert sorted headers to string.
///
/// ```shell
/// [(a, b), (c, d)] => "a:b\nc:d"
/// ```
pub fn header_to_string(mut headers: Vec<(String, String)>, sep: &str, join: &str) -> String {
let mut s = String::with_capacity(16);
// Sort via header name.
headers.sort();
for (idx, (k, v)) in headers.into_iter().enumerate() {
if idx != 0 {
s.push_str(join);
}
s.push_str(&k);
s.push_str(sep);
s.push_str(&v);
}
s
}
}
/// SigningMethod is the method that used in signing.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum SigningMethod {
/// Signing with header.
Header,
/// Signing with query.
Query(Duration),
}