#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Encoding {
Base64,
Base64Url,
}
pub const SUPPORTED_ENCODINGS: [&str; 2] = ["base64", "base64url"];
impl Encoding {
pub fn from_name(name: &str) -> Option<Self> {
match name {
"base64" => Some(Self::Base64),
"base64url" => Some(Self::Base64Url),
_ => None,
}
}
pub fn name(self) -> &'static str {
match self {
Self::Base64 => "base64",
Self::Base64Url => "base64url",
}
}
pub fn pattern(self) -> &'static str {
match self {
Self::Base64 => "^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$",
Self::Base64Url => "^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$",
}
}
}
pub enum EncodingClass {
Supported(Encoding),
Unsupported,
}
pub fn classify(name: &str) -> EncodingClass {
match Encoding::from_name(name) {
Some(encoding) => EncodingClass::Supported(encoding),
None => EncodingClass::Unsupported,
}
}
pub fn is_valid(encoding: Encoding, value: &str) -> bool {
regex::Regex::new(encoding.pattern())
.expect("pinned contentEncoding pattern compiles")
.is_match(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pinned_patterns_pass_the_pattern_gate() {
for name in SUPPORTED_ENCODINGS {
let encoding = Encoding::from_name(name).expect("supported");
crate::json_schema::pattern::gate_and_normalize(encoding.pattern()).unwrap_or_else(
|error| panic!("{name} pinned pattern rejected by gate: {error:?}"),
);
}
}
#[test]
fn classify_partitions_names() {
assert!(matches!(
classify("base64"),
EncodingClass::Supported(Encoding::Base64)
));
assert!(matches!(
classify("base64url"),
EncodingClass::Supported(Encoding::Base64Url)
));
for name in [
"base16",
"quoted-printable",
"7bit",
"8bit",
"binary",
"hex",
] {
assert!(
matches!(classify(name), EncodingClass::Unsupported),
"{name}"
);
}
}
#[test]
fn base64_accepts_canonical_padded_rejects_url_and_unpadded() {
assert!(is_valid(Encoding::Base64, "Pj4+"));
assert!(is_valid(Encoding::Base64, "aGk="));
assert!(is_valid(Encoding::Base64, ""));
assert!(!is_valid(Encoding::Base64, "Pj4-"));
assert!(!is_valid(Encoding::Base64, "a-b_"));
assert!(!is_valid(Encoding::Base64, "aGk"));
assert!(!is_valid(Encoding::Base64, "aG k="));
assert!(!is_valid(Encoding::Base64, "aGk=\n"));
assert!(!is_valid(Encoding::Base64, "aGk!"));
}
#[test]
fn base64url_accepts_canonical_unpadded_rejects_std_and_padding() {
assert!(is_valid(Encoding::Base64Url, "Pj4-"));
assert!(is_valid(Encoding::Base64Url, "aGk"));
assert!(is_valid(Encoding::Base64Url, ""));
assert!(!is_valid(Encoding::Base64Url, "Pj4+"));
assert!(!is_valid(Encoding::Base64Url, "aGk="));
assert!(!is_valid(Encoding::Base64Url, "aGk=A"));
assert!(!is_valid(Encoding::Base64Url, "a"));
}
}