use base_web_media_type::{MediaType, MediaTypeError};
use std::fmt;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Boundary(String);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BoundaryError {
NotMultipart,
Missing,
Invalid,
}
impl Boundary {
pub const LENGTH_LIMIT: usize = 70;
const RANDOM_BYTES: usize = 24;
pub fn new(text: &str) -> Result<Self, BoundaryError> {
let allowed = |byte: u8| byte.is_ascii_alphanumeric() || b"'()+_,-./:=? ".contains(&byte);
if text.is_empty()
|| text.len() > Self::LENGTH_LIMIT
|| text.ends_with(' ')
|| !text.bytes().all(allowed)
{
return Err(BoundaryError::Invalid);
}
Ok(Self(text.to_owned()))
}
pub fn from_media_type(media_type: &MediaType) -> Result<Self, BoundaryError> {
if media_type.top_level_type() != "multipart" {
return Err(BoundaryError::NotMultipart);
}
Self::new(
media_type
.parameter("boundary")
.ok_or(BoundaryError::Missing)?,
)
}
pub fn generate() -> Self {
let mut bytes = [0; Self::RANDOM_BYTES];
getrandom::fill(&mut bytes).expect("the operating system provides random bytes");
Self(base64url(&bytes))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_media_type(&self, subtype: &str) -> Result<MediaType, MediaTypeError> {
MediaType::new("multipart", subtype)?.with_parameter("boundary", &self.0)
}
pub fn form_data_media_type(&self) -> MediaType {
MediaType::MULTIPART_FORM_DATA
.with_parameter("boundary", &self.0)
.expect("a valid boundary is a valid parameter value")
}
}
impl std::str::FromStr for Boundary {
type Err = BoundaryError;
fn from_str(text: &str) -> Result<Self, Self::Err> {
Self::new(text)
}
}
impl AsRef<str> for Boundary {
fn as_ref(&self) -> &str {
&self.0
}
}
impl fmt::Debug for Boundary {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.debug_tuple("Boundary").field(&self.0).finish()
}
}
impl fmt::Display for Boundary {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(&self.0)
}
}
impl fmt::Display for BoundaryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(match self {
Self::NotMultipart => "media type is not multipart",
Self::Missing => "multipart media type has no boundary",
Self::Invalid => "invalid multipart boundary",
})
}
}
impl std::error::Error for BoundaryError {}
fn base64url(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut text = String::with_capacity(bytes.len() / 3 * 4);
for chunk in bytes.chunks_exact(3) {
let triple = u32::from(chunk[0]) << 16 | u32::from(chunk[1]) << 8 | u32::from(chunk[2]);
for shift in [18, 12, 6, 0] {
text.push(char::from(ALPHABET[(triple >> shift) as usize & 63]));
}
}
text
}