pub(crate) fn encode_base64(bytes: &[u8]) -> String {
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
encode_base64_impl(bytes, TABLE, true)
}
pub(crate) fn encode_base64url_no_pad(bytes: &[u8]) -> String {
const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
encode_base64_impl(bytes, TABLE, false)
}
fn encode_base64_impl(bytes: &[u8], table: &[u8; 64], pad: bool) -> String {
let mut output = String::with_capacity((bytes.len() + 2) / 3 * 4);
let mut chunks = bytes.chunks_exact(3);
for chunk in &mut chunks {
let n = ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | chunk[2] as u32;
output.push(table[((n >> 18) & 0x3F) as usize] as char);
output.push(table[((n >> 12) & 0x3F) as usize] as char);
output.push(table[((n >> 6) & 0x3F) as usize] as char);
output.push(table[(n & 0x3F) as usize] as char);
}
let rem = chunks.remainder();
if !rem.is_empty() {
let first = rem[0] as u32;
let second = rem.get(1).copied().unwrap_or_default() as u32;
let n = (first << 16) | (second << 8);
output.push(table[((n >> 18) & 0x3F) as usize] as char);
output.push(table[((n >> 12) & 0x3F) as usize] as char);
if rem.len() == 2 {
output.push(table[((n >> 6) & 0x3F) as usize] as char);
if pad {
output.push('=');
}
} else if pad {
output.push('=');
output.push('=');
}
}
output
}
use crate::decode::{CompressionMode, DEFAULT_ACCEPT_ENCODING};
use crate::error::{Error, ErrorKind, Result};
use crate::header::HeaderMap;
use crate::request::Method;
use crate::response::StatusCode;
#[cfg_attr(not(any(feature = "h2", feature = "h3")), allow(dead_code))]
pub(crate) fn build_httpx_regular_headers(
headers: &HeaderMap,
cookies: &[(String, String)],
compression_mode: CompressionMode,
body_len: Option<u64>,
) -> Vec<(String, String)> {
let mut header_list = Vec::new();
let mut cookie_values = Vec::new();
let mut has_content_length = false;
let mut has_accept_encoding = false;
for (name, value) in headers.iter() {
let name = name.as_str().to_ascii_lowercase();
let value = value.as_str().to_owned();
if matches!(
name.as_str(),
"connection" | "keep-alive" | "proxy-connection" | "transfer-encoding" | "upgrade"
) {
continue;
}
if name == "host" {
continue;
}
if name == "te" && !value.eq_ignore_ascii_case("trailers") {
continue;
}
if name == "cookie" {
cookie_values.push(value);
continue;
}
if name == "content-length" {
has_content_length = true;
}
if name == "accept-encoding" {
has_accept_encoding = true;
}
header_list.push((name, value));
}
if !cookies.is_empty() {
let mut formatted = String::new();
for (index, (name, value)) in cookies.iter().enumerate() {
if index > 0 {
formatted.push_str("; ");
}
formatted.push_str(name);
formatted.push('=');
formatted.push_str(value);
}
cookie_values.push(formatted);
}
if !cookie_values.is_empty() {
header_list.push(("cookie".to_owned(), cookie_values.join("; ")));
}
if let Some(length) = body_len {
if !has_content_length {
header_list.push(("content-length".to_owned(), length.to_string()));
}
}
if !has_accept_encoding && compression_mode.should_add_accept_encoding() {
header_list.push((
"accept-encoding".to_owned(),
DEFAULT_ACCEPT_ENCODING.to_owned(),
));
}
header_list
}
pub(crate) fn response_body_allowed(method: Method, status: StatusCode) -> bool {
method != Method::Head && !matches!(status.as_u16(), 204 | 205 | 304)
}
pub(crate) fn parse_content_length(
headers: &HeaderMap,
protocol: &'static str,
) -> Result<Option<usize>> {
let values = headers.get_all("content-length");
if values.is_empty() {
return Ok(None);
}
let mut parsed = None;
for value in values {
let length = value.parse::<usize>().map_err(|err| {
Error::with_source(
ErrorKind::Transport,
format!("invalid {protocol} content-length header value"),
err,
)
})?;
if let Some(existing) = parsed {
if existing != length {
return Err(Error::new(
ErrorKind::Transport,
format!("conflicting {protocol} content-length header values"),
));
}
} else {
parsed = Some(length);
}
}
Ok(parsed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encode_base64_empty() {
assert_eq!(encode_base64(b""), "");
}
#[test]
fn encode_base64_one_byte() {
assert_eq!(encode_base64(b"M"), "TQ==");
}
#[test]
fn encode_base64_two_bytes() {
assert_eq!(encode_base64(b"Ma"), "TWE=");
}
#[test]
fn encode_base64_three_bytes() {
assert_eq!(encode_base64(b"Man"), "TWFu");
}
#[test]
fn encode_base64_rfc_example() {
assert_eq!(encode_base64(b"foobar"), "Zm9vYmFy");
}
#[test]
fn encode_base64url_no_pad_basic() {
assert_eq!(encode_base64url_no_pad(b"Man"), "TWFu");
}
#[test]
fn encode_base64url_no_pad_uses_url_safe_chars() {
assert_eq!(encode_base64url_no_pad(&[0xFF, 0xFF]), "__8");
}
}