pub(crate) struct ValidationErr {
pub(crate) violation: String,
pub(crate) help: String,
}
#[cfg(feature = "validation")]
pub(crate) trait Validation<'a> {
fn validate(&self, value: &'a str) -> Result<(), ValidationErr>;
}
#[cfg(all(debug_assertions, feature = "debug_warnings"))]
pub(crate) trait Warning<'a> {
fn maybe_warn(&self, value: &'a str);
}
#[derive(Debug)]
pub(crate) struct VendorAttribute<'a>(pub(crate) &'a str);
#[cfg(feature = "validation")]
impl<'a> TryFrom<&'a str> for VendorAttribute<'a> {
type Error = ValidationErr;
fn try_from(vendor_attr: &'a str) -> Result<Self, Self::Error> {
if vendor_attr.is_empty() {
return Err(ValidationErr {
violation: String::from("Invalid component: Missing attribute name."),
help: String::from("The attribute name may not be blank. Refer to the RFC7512 specification for valid attributes."),
});
}
if [
"token",
"manufacturer",
"serial",
"model",
"library-manufacturer",
"library-version",
"library-description",
"object",
"type",
"id",
"slot-description",
"slot-manufacturer",
"slot-id",
]
.contains(&vendor_attr)
{
return Err(ValidationErr {
violation: String::from("Naming collision with standard path component."),
help: String::from("Move this attribute and its value to the PKCS#11 URI path."),
});
}
if ["pin-source", "pin-value", "module-name", "module-path"].contains(&vendor_attr) {
return Err(ValidationErr {
violation: String::from("Naming collision with standard query component."),
help: format!("Move `{vendor_attr}` and its value to the PKCS#11 URI query."),
});
}
if !vendor_attr.chars().all(|v_attr_nm_char| {
v_attr_nm_char.is_alphanumeric() || v_attr_nm_char == '-' || v_attr_nm_char == '_'
}) {
return Err(ValidationErr{
violation: String::from("Invalid vendor-specific component name: expected `1*pk11-v-attr-nm-char`."),
help: format!("`{vendor_attr}` violated vendor-specific attribute name characters consisting solely of alphanumeric, '-', or '_'.")
});
}
#[cfg(debug_assertions)]
if vendor_attr.starts_with("x-") {
println!(
r#"pkcs11 warning: per RFC7512, the previously used convention of starting vendor attributes with an "x-" prefix is now deprecated. Identified: `{vendor_attr}`."#
);
}
Ok(VendorAttribute(vendor_attr))
}
}
#[cfg(not(feature = "validation"))]
impl<'a> From<&'a str> for VendorAttribute<'a> {
fn from(vendor_attr: &'a str) -> Self {
#[cfg(all(debug_assertions, feature = "debug_warnings"))]
if vendor_attr.starts_with("x-") {
println!(
r#"pkcs11 warning: per RFC7512, the previously used convention of starting vendor attributes with an "x-" prefix is now deprecated. Identified: `{vendor_attr}`."#
);
}
VendorAttribute(vendor_attr)
}
}
#[cfg(feature = "validation")]
pub(crate) fn common_validation(value: &str) -> Option<ValidationErr> {
if value.contains(' ') {
return Some(ValidationErr {
violation: String::from("Invalid component value: Appendix A of [RFC3986] specifies component values may not contain empty spaces."),
help: format!("Replace `{value}` with `{fixed}`.", fixed=value.replace(' ', "%20"))
});
}
if value.contains('#') {
return Some(ValidationErr {
violation: String::from(
"Invalid component value: The '#' delimiter must always be percent-encoded.",
),
help: format!(
"Replace `{value}` with `{fixed}`.",
fixed = value.replace('#', "%23")
),
});
}
None
}
#[cfg(all(debug_assertions, feature = "debug_warnings"))]
pub(crate) fn maybe_suggest_percent_encoding<const T: usize>(
attribute: &str,
value: &str,
addl_res_avail: [char; T], ) {
const PK11_RES_AVAIL: [char; 17] = [
'-', '.', '_', '~', ':', '[', ']', '@', '!', '$', '\'', '(', ')', '*', '+', ',', '=',
];
let mut iter = value.chars().enumerate().peekable();
while let Some((offset, value_char)) = iter.next() {
match value_char {
'%' => {
if iter.next_if(|(_offset, c)| c.is_ascii_hexdigit()).is_none()
|| iter.next_if(|(_offset, c)| c.is_ascii_hexdigit()).is_none()
{
println!("pkcs11 warning: identified malformed percent-encoding at offset {offset} in \
`{value}` of component `{attribute}={value}`");
}
}
c if c.is_alphanumeric()
|| PK11_RES_AVAIL.contains(&c)
|| addl_res_avail.contains(&c) => {}
_ => {
println!("pkcs11 warning: the `{value_char}` identified at offset {offset} in `{value}` of \
component `{attribute}={value}` SHOULD be percent-encoded.");
}
}
}
}