use core::{fmt, str::FromStr};
use crate::std::string::String;
use crate::user::authority::StaticAuthorizer;
use rama_core::error::BoxErrorExt as _;
use rama_core::error::{BoxError, ErrorContext as _, ErrorExt};
use rama_utils::bytes::ct::ct_eq_bytes;
use rama_utils::str::{NonEmptyStr, arcstr::ArcStr};
#[derive(Clone, Eq)]
pub struct RawToken(NonEmptyStr);
#[macro_export]
#[doc(hidden)]
macro_rules! __raw_token {
($text:expr $(,)?) => {{
const __RAW_TOKEN_TEXT: &str = $text;
if __RAW_TOKEN_TEXT.is_empty() {
panic!("empty str cannot be used as RawToken");
}
let mut i = 0;
let bytes = __RAW_TOKEN_TEXT.as_bytes();
while i < bytes.len() {
let b = bytes[i];
if !(b == b'\t' || (b' ' <= b && b <= b'~')) {
panic!("RawToken contains a forbidden byte (visible ASCII + SP/HTAB required)");
}
i += 1;
}
unsafe {
$crate::user::credentials::RawToken::new_unchecked(
$crate::__private::utils::str::non_empty_str!(__RAW_TOKEN_TEXT),
)
}
}};
}
#[doc(inline)]
pub use crate::__raw_token as raw_token;
impl fmt::Debug for RawToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("RawToken").field(&"***").finish()
}
}
impl PartialEq for RawToken {
fn eq(&self, other: &Self) -> bool {
ct_eq_bytes(self.0.as_bytes(), other.0.as_bytes())
}
}
impl RawToken {
#[doc(hidden)]
#[must_use]
pub const unsafe fn new_unchecked(s: NonEmptyStr) -> Self {
Self(s)
}
pub fn try_new(s: NonEmptyStr) -> Result<Self, BoxError> {
if let Some(idx) = s
.as_bytes()
.iter()
.position(|b| !(*b == b'\t' || (b' '..=b'~').contains(b)))
{
return Err(
BoxError::from_static_str("RawToken contains forbidden byte")
.context_field("byte_index", idx),
);
}
Ok(Self(s))
}
#[must_use]
pub fn token(&self) -> &str {
&self.0
}
#[must_use]
pub fn into_authorizer(self) -> StaticAuthorizer<Self> {
StaticAuthorizer::new(self)
}
}
impl TryFrom<&str> for RawToken {
type Error = BoxError;
#[inline(always)]
fn try_from(value: &str) -> Result<Self, Self::Error> {
Self::try_new(
value
.try_into()
.context("turn str slice into non-empty str")?,
)
}
}
impl TryFrom<String> for RawToken {
type Error = BoxError;
#[inline(always)]
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_new(value.try_into().context("turn string into non-empty str")?)
}
}
impl TryFrom<ArcStr> for RawToken {
type Error = BoxError;
#[inline(always)]
fn try_from(value: ArcStr) -> Result<Self, Self::Error> {
Self::try_new(
value
.try_into()
.context("turn arc str into non-empty str")?,
)
}
}
impl TryFrom<NonEmptyStr> for RawToken {
type Error = BoxError;
#[inline(always)]
fn try_from(value: NonEmptyStr) -> Result<Self, Self::Error> {
Self::try_new(value)
}
}
impl FromStr for RawToken {
type Err = BoxError;
#[inline]
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.try_into()
}
}
impl fmt::Display for RawToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn regression_raw_token_constant_time_eq() {
let a = RawToken::try_from("abcdefghij").unwrap();
let b = RawToken::try_from("abcdefghij").unwrap();
let last_diff = RawToken::try_from("abcdefghiX").unwrap();
let first_diff = RawToken::try_from("Xbcdefghij").unwrap();
let diff_len = RawToken::try_from("abcdefghi").unwrap();
assert_eq!(a, b);
assert_ne!(a, last_diff);
assert_ne!(a, first_diff);
assert_ne!(a, diff_len);
}
#[test]
fn regression_raw_token_rejects_crlf_nul() {
RawToken::try_from("foo\rbar").unwrap_err();
RawToken::try_from("foo\nbar").unwrap_err();
RawToken::try_from("foo\0bar").unwrap_err();
RawToken::try_from("foo\x7fbar").unwrap_err();
RawToken::try_from("foo bar").unwrap();
RawToken::try_from("foo,bar").unwrap();
RawToken::try_from("key=value:scope").unwrap();
RawToken::try_from("foo\tbar").unwrap();
RawToken::try_from("eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ4In0.sig").unwrap();
}
}