use core::fmt;
use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
use reqwest::header::HeaderValue;
#[cfg(test)]
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::vec::Vec;
use super::sensitive_header_value;
#[cfg(test)]
use super::sensitive_header_value_with_probe;
pub const MAX_BEARER_TOKEN_BYTES: usize = 4096;
const BEARER_PREFIX: &[u8] = b"Bearer ";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum BearerTokenError {
Empty,
TooLong,
InvalidByte,
AllocationFailed,
}
impl_static_error!(BearerTokenError,
Self::Empty => "bearer token is empty",
Self::TooLong => "bearer token exceeds the length limit",
Self::InvalidByte => "bearer token contains an invalid byte",
Self::AllocationFailed => "bearer-token allocation failed",
);
pub struct BearerToken {
authorization: Vec<u8>,
#[cfg(test)]
drop_probe: Option<Arc<AtomicUsize>>,
#[cfg(test)]
header_drop_probe: Option<Arc<AtomicUsize>>,
}
impl BearerToken {
pub fn new(token: &str) -> Result<Self, BearerTokenError> {
Self::from_bytes(token.as_bytes())
}
pub fn from_mut_bytes(token: &mut [u8]) -> Result<Self, BearerTokenError> {
let result = Self::from_bytes(token);
sanitize_bytes(token);
result
}
pub fn from_secret_buffer(token: SecretBuffer<'_>) -> Result<Self, BearerTokenError> {
Self::from_bytes(token.as_slice())
}
fn from_bytes(token: &[u8]) -> Result<Self, BearerTokenError> {
if token.is_empty() {
return Err(BearerTokenError::Empty);
}
if token.len() > MAX_BEARER_TOKEN_BYTES {
return Err(BearerTokenError::TooLong);
}
let mut saw_token_byte = false;
let mut padding = false;
for byte in token.iter().copied() {
match byte {
b'=' if saw_token_byte => padding = true,
b'=' => return Err(BearerTokenError::InvalidByte),
_ if padding || !is_bearer_byte(byte) => {
return Err(BearerTokenError::InvalidByte);
}
_ => saw_token_byte = true,
}
}
let capacity = BEARER_PREFIX
.len()
.checked_add(token.len())
.ok_or(BearerTokenError::TooLong)?;
let mut authorization = Vec::new();
authorization
.try_reserve_exact(capacity)
.map_err(|_| BearerTokenError::AllocationFailed)?;
authorization.extend_from_slice(BEARER_PREFIX);
authorization.extend_from_slice(token);
Ok(Self {
authorization,
#[cfg(test)]
drop_probe: None,
#[cfg(test)]
header_drop_probe: None,
})
}
pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
sensitive_header_value(&self.authorization)
}
#[cfg(test)]
pub(crate) fn header_value_with_drop_probe(&self) -> Result<HeaderValue, ()> {
sensitive_header_value_with_probe(&self.authorization, self.header_drop_probe.clone())
}
#[cfg(test)]
pub(crate) fn owned_bytes(&self) -> &[u8] {
&self.authorization
}
#[cfg(test)]
pub(crate) fn with_drop_probe(
token: &str,
probe: Arc<AtomicUsize>,
) -> Result<Self, BearerTokenError> {
let mut value = Self::new(token)?;
value.drop_probe = Some(probe);
Ok(value)
}
#[cfg(test)]
pub(crate) fn with_header_drop_probe(
token: &str,
probe: Arc<AtomicUsize>,
) -> Result<Self, BearerTokenError> {
let mut value = Self::new(token)?;
value.header_drop_probe = Some(probe);
Ok(value)
}
}
impl Drop for BearerToken {
fn drop(&mut self) {
sanitize_bytes(&mut self.authorization);
#[cfg(test)]
if let Some(probe) = &self.drop_probe {
probe.fetch_add(1, Ordering::SeqCst);
}
}
}
impl fmt::Debug for BearerToken {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("BearerToken([redacted])")
}
}
const fn is_bearer_byte(byte: u8) -> bool {
byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~' | b'+' | b'/')
}