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;
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>>,
}
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 padding = false;
for byte in token.iter().copied() {
if byte == b'=' {
padding = true;
} else if padding || !is_bearer_byte(byte) {
return Err(BearerTokenError::InvalidByte);
}
}
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,
})
}
pub(crate) fn header_value(&self) -> Result<HeaderValue, ()> {
let mut value = HeaderValue::from_bytes(&self.authorization).map_err(|_| ())?;
value.set_sensitive(true);
Ok(value)
}
#[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)
}
}
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'/')
}