headers_content_md5/
lib.rs

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