use std::fmt;
use crate::Error;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Boundary(Box<[u8]>);
impl Boundary {
pub fn new(boundary: &[u8]) -> Result<Self, Error> {
if boundary.is_empty() || boundary.len() > 70 {
return Err(Error::InvalidBoundary);
}
if !boundary.iter().copied().all(is_bchar) {
return Err(Error::InvalidBoundary);
}
if boundary.last().is_some_and(u8::is_ascii_whitespace) {
return Err(Error::InvalidBoundary);
}
Ok(Self(boundary.into()))
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.0
}
#[must_use]
pub fn len(&self) -> usize {
self.0.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl fmt::Display for Boundary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&String::from_utf8_lossy(&self.0))
}
}
impl fmt::Debug for Boundary {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Boundary").field(&String::from_utf8_lossy(&self.0)).finish()
}
}
fn is_bchar(byte: u8) -> bool {
byte.is_ascii_alphanumeric()
|| matches!(
byte,
b'\'' | b'(' | b')' | b'+' | b'_' | b',' | b'-' | b'.' | b'/' | b':' | b'=' | b'?' | b' '
)
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic, clippy::unreachable, clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn boundary_accepts_valid_forms() {
for boundary in [
&b"a"[..],
b"----WebKitFormBoundary7MA4YWxkTrZu0gW",
b"simple boundary",
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-.",
] {
let parsed = Boundary::new(boundary).unwrap();
assert_eq!(parsed.as_bytes(), boundary);
assert_eq!(parsed.len(), boundary.len());
assert!(!parsed.is_empty());
assert_ne!(parsed.to_string(), "");
assert_ne!(format!("{parsed:?}"), "");
}
}
#[test]
fn boundary_rejects_invalid_forms() {
for boundary in [
&b""[..],
b" ",
b"trailing ",
&[b'a'; 71],
b"bad\0byte",
b"bad\nbyte",
b"bad\tbyte",
b"\xe4\xb8\xad",
b"a*b",
b"a;b",
b"a!b",
] {
assert!(matches!(Boundary::new(boundary), Err(Error::InvalidBoundary)), "{boundary:?}");
}
}
#[test]
fn boundary_display_is_lossy_but_readable() {
let boundary = Boundary::new(b"----boundary").unwrap();
assert_eq!(boundary.to_string(), "----boundary");
let debug = format!("{boundary:?}");
assert!(debug.contains("----boundary"));
}
}