1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
//! Provides the [`ContentMd5`] typed header.
//!
//! # Example
//!
//! ```
//! use headers::Header;
//! use http::HeaderValue;
//! use headers_content_md5::ContentMd5;
//!
//! let value = HeaderValue::from_static("Q2hlY2sgSW50ZWdyaXR5IQ==");
//! let md5 = ContentMd5::decode(&mut [&value].into_iter()).unwrap();
//! assert_eq!(md5.0, "Check Integrity!".as_bytes())
//! ```
#![deny(unsafe_code)]
#![deny(unused_must_use)]
use base64::{engine::general_purpose::STANDARD as base64, Engine};
use headers::{Header, HeaderValue};
/// `Content-MD5` header, defined in
/// [RFC1864](https://datatracker.ietf.org/doc/html/rfc1864)
///
/// ## Example values
///
/// * `Q2hlY2sgSW50ZWdyaXR5IQ==`
///
/// # Example
///
/// Decoding:
///
/// ```
/// use headers::Header;
/// use http::HeaderValue;
/// use headers_content_md5::ContentMd5;
///
/// let value = HeaderValue::from_static("Q2hlY2sgSW50ZWdyaXR5IQ==");
/// let mut values = [&value].into_iter();
///
/// let md5 = ContentMd5::decode(&mut values).unwrap();
/// assert_eq!(md5.0, "Check Integrity!".as_bytes())
/// ```
///
/// Encoding:
///
/// ```
/// use headers::Header;
/// use http::HeaderValue;
/// use headers_content_md5::ContentMd5;
///
/// let value = HeaderValue::from_static("Q2hlY2sgSW50ZWdyaXR5IQ==");
/// let md5 = ContentMd5("Check Integrity!".as_bytes().try_into().unwrap());
///
/// let mut header = Vec::default();
/// md5.encode(&mut header);
/// assert_eq!(header[0], "Q2hlY2sgSW50ZWdyaXR5IQ==");
/// ```
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ContentMd5(pub [u8; 16]);
static CONTENT_MD5: http::header::HeaderName = http::header::HeaderName::from_static("content-md5");
impl Header for ContentMd5 {
fn name() -> &'static http::header::HeaderName {
&CONTENT_MD5
}
fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(
values: &mut I,
) -> Result<Self, headers::Error> {
let value = values.next().ok_or_else(headers::Error::invalid)?;
// Ensure base64 encoded length fits the expected MD5 digest length.
if value.len() < 22 || value.len() > 24 {
return Err(headers::Error::invalid());
}
let value = value.to_str().map_err(|_| headers::Error::invalid())?;
let mut buffer = [0; 18];
base64
.decode_slice(value, &mut buffer)
.map_err(|_| headers::Error::invalid())?;
let mut slice = [0; 16];
slice.copy_from_slice(&buffer[..16]);
Ok(Self(slice))
}
fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
let encoded = base64.encode(self.0);
if let Ok(value) = HeaderValue::from_str(&encoded) {
values.extend(std::iter::once(value));
}
}
}
#[cfg(test)]
mod tests {
use crate::ContentMd5;
use headers::Header;
use http::HeaderValue;
#[test]
fn decode_works() {
let value = HeaderValue::from_static("Q2hlY2sgSW50ZWdyaXR5IQ==");
let md5 = ContentMd5::decode(&mut [&value].into_iter()).unwrap();
assert_eq!(md5.0, "Check Integrity!".as_bytes())
}
#[test]
fn encode_works() {
let md5 = ContentMd5("Check Integrity!".as_bytes().try_into().unwrap());
let mut header = Vec::default();
md5.encode(&mut header);
assert_eq!(header[0], "Q2hlY2sgSW50ZWdyaXR5IQ==");
}
}