actori_http/header/
mod.rs

1//! Various http headers
2// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header)
3
4use std::convert::TryFrom;
5use std::{fmt, str::FromStr};
6
7use bytes::{Bytes, BytesMut};
8use http::Error as HttpError;
9use mime::Mime;
10use percent_encoding::{AsciiSet, CONTROLS};
11
12pub use http::header::*;
13
14use crate::error::ParseError;
15use crate::httpmessage::HttpMessage;
16
17mod common;
18pub(crate) mod map;
19mod shared;
20pub use self::common::*;
21#[doc(hidden)]
22pub use self::shared::*;
23
24#[doc(hidden)]
25pub use self::map::GetAll;
26pub use self::map::HeaderMap;
27
28/// A trait for any object that will represent a header field and value.
29pub trait Header
30where
31    Self: IntoHeaderValue,
32{
33    /// Returns the name of the header field
34    fn name() -> HeaderName;
35
36    /// Parse a header
37    fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
38}
39
40/// A trait for any object that can be Converted to a `HeaderValue`
41pub trait IntoHeaderValue: Sized {
42    /// The type returned in the event of a conversion error.
43    type Error: Into<HttpError>;
44
45    /// Try to convert value to a Header value.
46    fn try_into(self) -> Result<HeaderValue, Self::Error>;
47}
48
49impl IntoHeaderValue for HeaderValue {
50    type Error = InvalidHeaderValue;
51
52    #[inline]
53    fn try_into(self) -> Result<HeaderValue, Self::Error> {
54        Ok(self)
55    }
56}
57
58impl<'a> IntoHeaderValue for &'a str {
59    type Error = InvalidHeaderValue;
60
61    #[inline]
62    fn try_into(self) -> Result<HeaderValue, Self::Error> {
63        self.parse()
64    }
65}
66
67impl<'a> IntoHeaderValue for &'a [u8] {
68    type Error = InvalidHeaderValue;
69
70    #[inline]
71    fn try_into(self) -> Result<HeaderValue, Self::Error> {
72        HeaderValue::from_bytes(self)
73    }
74}
75
76impl IntoHeaderValue for Bytes {
77    type Error = InvalidHeaderValue;
78
79    #[inline]
80    fn try_into(self) -> Result<HeaderValue, Self::Error> {
81        HeaderValue::from_maybe_shared(self)
82    }
83}
84
85impl IntoHeaderValue for Vec<u8> {
86    type Error = InvalidHeaderValue;
87
88    #[inline]
89    fn try_into(self) -> Result<HeaderValue, Self::Error> {
90        HeaderValue::try_from(self)
91    }
92}
93
94impl IntoHeaderValue for String {
95    type Error = InvalidHeaderValue;
96
97    #[inline]
98    fn try_into(self) -> Result<HeaderValue, Self::Error> {
99        HeaderValue::try_from(self)
100    }
101}
102
103impl IntoHeaderValue for usize {
104    type Error = InvalidHeaderValue;
105
106    #[inline]
107    fn try_into(self) -> Result<HeaderValue, Self::Error> {
108        let s = format!("{}", self);
109        HeaderValue::try_from(s)
110    }
111}
112
113impl IntoHeaderValue for u64 {
114    type Error = InvalidHeaderValue;
115
116    #[inline]
117    fn try_into(self) -> Result<HeaderValue, Self::Error> {
118        let s = format!("{}", self);
119        HeaderValue::try_from(s)
120    }
121}
122
123impl IntoHeaderValue for Mime {
124    type Error = InvalidHeaderValue;
125
126    #[inline]
127    fn try_into(self) -> Result<HeaderValue, Self::Error> {
128        HeaderValue::try_from(format!("{}", self))
129    }
130}
131
132/// Represents supported types of content encodings
133#[derive(Copy, Clone, PartialEq, Debug)]
134pub enum ContentEncoding {
135    /// Automatically select encoding based on encoding negotiation
136    Auto,
137    /// A format using the Brotli algorithm
138    Br,
139    /// A format using the zlib structure with deflate algorithm
140    Deflate,
141    /// Gzip algorithm
142    Gzip,
143    /// Indicates the identity function (i.e. no compression, nor modification)
144    Identity,
145}
146
147impl ContentEncoding {
148    #[inline]
149    /// Is the content compressed?
150    pub fn is_compression(self) -> bool {
151        match self {
152            ContentEncoding::Identity | ContentEncoding::Auto => false,
153            _ => true,
154        }
155    }
156
157    #[inline]
158    /// Convert content encoding to string
159    pub fn as_str(self) -> &'static str {
160        match self {
161            ContentEncoding::Br => "br",
162            ContentEncoding::Gzip => "gzip",
163            ContentEncoding::Deflate => "deflate",
164            ContentEncoding::Identity | ContentEncoding::Auto => "identity",
165        }
166    }
167
168    #[inline]
169    /// default quality value
170    pub fn quality(self) -> f64 {
171        match self {
172            ContentEncoding::Br => 1.1,
173            ContentEncoding::Gzip => 1.0,
174            ContentEncoding::Deflate => 0.9,
175            ContentEncoding::Identity | ContentEncoding::Auto => 0.1,
176        }
177    }
178}
179
180impl<'a> From<&'a str> for ContentEncoding {
181    fn from(s: &'a str) -> ContentEncoding {
182        let s = s.trim();
183
184        if s.eq_ignore_ascii_case("br") {
185            ContentEncoding::Br
186        } else if s.eq_ignore_ascii_case("gzip") {
187            ContentEncoding::Gzip
188        } else if s.eq_ignore_ascii_case("deflate") {
189            ContentEncoding::Deflate
190        } else {
191            ContentEncoding::Identity
192        }
193    }
194}
195
196#[doc(hidden)]
197pub(crate) struct Writer {
198    buf: BytesMut,
199}
200
201impl Writer {
202    fn new() -> Writer {
203        Writer {
204            buf: BytesMut::new(),
205        }
206    }
207    fn take(&mut self) -> Bytes {
208        self.buf.split().freeze()
209    }
210}
211
212impl fmt::Write for Writer {
213    #[inline]
214    fn write_str(&mut self, s: &str) -> fmt::Result {
215        self.buf.extend_from_slice(s.as_bytes());
216        Ok(())
217    }
218
219    #[inline]
220    fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> fmt::Result {
221        fmt::write(self, args)
222    }
223}
224
225#[inline]
226#[doc(hidden)]
227/// Reads a comma-delimited raw header into a Vec.
228pub fn from_comma_delimited<'a, I: Iterator<Item = &'a HeaderValue> + 'a, T: FromStr>(
229    all: I,
230) -> Result<Vec<T>, ParseError> {
231    let mut result = Vec::new();
232    for h in all {
233        let s = h.to_str().map_err(|_| ParseError::Header)?;
234        result.extend(
235            s.split(',')
236                .filter_map(|x| match x.trim() {
237                    "" => None,
238                    y => Some(y),
239                })
240                .filter_map(|x| x.trim().parse().ok()),
241        )
242    }
243    Ok(result)
244}
245
246#[inline]
247#[doc(hidden)]
248/// Reads a single string when parsing a header.
249pub fn from_one_raw_str<T: FromStr>(val: Option<&HeaderValue>) -> Result<T, ParseError> {
250    if let Some(line) = val {
251        let line = line.to_str().map_err(|_| ParseError::Header)?;
252        if !line.is_empty() {
253            return T::from_str(line).or(Err(ParseError::Header));
254        }
255    }
256    Err(ParseError::Header)
257}
258
259#[inline]
260#[doc(hidden)]
261/// Format an array into a comma-delimited string.
262pub fn fmt_comma_delimited<T>(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result
263where
264    T: fmt::Display,
265{
266    let mut iter = parts.iter();
267    if let Some(part) = iter.next() {
268        fmt::Display::fmt(part, f)?;
269    }
270    for part in iter {
271        f.write_str(", ")?;
272        fmt::Display::fmt(part, f)?;
273    }
274    Ok(())
275}
276
277// From hyper v0.11.27 src/header/parsing.rs
278
279/// The value part of an extended parameter consisting of three parts:
280/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`),
281/// and a character sequence representing the actual value (`value`), separated by single quote
282/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
283#[derive(Clone, Debug, PartialEq)]
284pub struct ExtendedValue {
285    /// The character set that is used to encode the `value` to a string.
286    pub charset: Charset,
287    /// The human language details of the `value`, if available.
288    pub language_tag: Option<LanguageTag>,
289    /// The parameter value, as expressed in octets.
290    pub value: Vec<u8>,
291}
292
293/// Parses extended header parameter values (`ext-value`), as defined in
294/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2).
295///
296/// Extended values are denoted by parameter names that end with `*`.
297///
298/// ## ABNF
299///
300/// ```text
301/// ext-value     = charset  "'" [ language ] "'" value-chars
302///               ; like RFC 2231's <extended-initial-value>
303///               ; (see [RFC2231], Section 7)
304///
305/// charset       = "UTF-8" / "ISO-8859-1" / mime-charset
306///
307/// mime-charset  = 1*mime-charsetc
308/// mime-charsetc = ALPHA / DIGIT
309///               / "!" / "#" / "$" / "%" / "&"
310///               / "+" / "-" / "^" / "_" / "`"
311///               / "{" / "}" / "~"
312///               ; as <mime-charset> in Section 2.3 of [RFC2978]
313///               ; except that the single quote is not included
314///               ; SHOULD be registered in the IANA charset registry
315///
316/// language      = <Language-Tag, defined in [RFC5646], Section 2.1>
317///
318/// value-chars   = *( pct-encoded / attr-char )
319///
320/// pct-encoded   = "%" HEXDIG HEXDIG
321///               ; see [RFC3986], Section 2.1
322///
323/// attr-char     = ALPHA / DIGIT
324///               / "!" / "#" / "$" / "&" / "+" / "-" / "."
325///               / "^" / "_" / "`" / "|" / "~"
326///               ; token except ( "*" / "'" / "%" )
327/// ```
328pub fn parse_extended_value(
329    val: &str,
330) -> Result<ExtendedValue, crate::error::ParseError> {
331    // Break into three pieces separated by the single-quote character
332    let mut parts = val.splitn(3, '\'');
333
334    // Interpret the first piece as a Charset
335    let charset: Charset = match parts.next() {
336        None => return Err(crate::error::ParseError::Header),
337        Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?,
338    };
339
340    // Interpret the second piece as a language tag
341    let language_tag: Option<LanguageTag> = match parts.next() {
342        None => return Err(crate::error::ParseError::Header),
343        Some("") => None,
344        Some(s) => match s.parse() {
345            Ok(lt) => Some(lt),
346            Err(_) => return Err(crate::error::ParseError::Header),
347        },
348    };
349
350    // Interpret the third piece as a sequence of value characters
351    let value: Vec<u8> = match parts.next() {
352        None => return Err(crate::error::ParseError::Header),
353        Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(),
354    };
355
356    Ok(ExtendedValue {
357        value,
358        charset,
359        language_tag,
360    })
361}
362
363impl fmt::Display for ExtendedValue {
364    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
365        let encoded_value =
366            percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
367        if let Some(ref lang) = self.language_tag {
368            write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
369        } else {
370            write!(f, "{}''{}", self.charset, encoded_value)
371        }
372    }
373}
374
375/// Percent encode a sequence of bytes with a character set defined in
376/// [https://tools.ietf.org/html/rfc5987#section-3.2][url]
377///
378/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2
379pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result {
380    let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE);
381    fmt::Display::fmt(&encoded, f)
382}
383
384/// Convert http::HeaderMap to a HeaderMap
385impl From<http::HeaderMap> for HeaderMap {
386    fn from(map: http::HeaderMap) -> HeaderMap {
387        let mut new_map = HeaderMap::with_capacity(map.capacity());
388        for (h, v) in map.iter() {
389            new_map.append(h.clone(), v.clone());
390        }
391        new_map
392    }
393}
394
395// This encode set is used for HTTP header values and is defined at
396// https://tools.ietf.org/html/rfc5987#section-3.2
397pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS
398    .add(b' ')
399    .add(b'"')
400    .add(b'%')
401    .add(b'\'')
402    .add(b'(')
403    .add(b')')
404    .add(b'*')
405    .add(b',')
406    .add(b'/')
407    .add(b':')
408    .add(b';')
409    .add(b'<')
410    .add(b'-')
411    .add(b'>')
412    .add(b'?')
413    .add(b'[')
414    .add(b'\\')
415    .add(b']')
416    .add(b'{')
417    .add(b'}');
418
419#[cfg(test)]
420mod tests {
421    use super::shared::Charset;
422    use super::{parse_extended_value, ExtendedValue};
423    use language_tags::LanguageTag;
424
425    #[test]
426    fn test_parse_extended_value_with_encoding_and_language_tag() {
427        let expected_language_tag = "en".parse::<LanguageTag>().unwrap();
428        // RFC 5987, Section 3.2.2
429        // Extended notation, using the Unicode character U+00A3 (POUND SIGN)
430        let result = parse_extended_value("iso-8859-1'en'%A3%20rates");
431        assert!(result.is_ok());
432        let extended_value = result.unwrap();
433        assert_eq!(Charset::Iso_8859_1, extended_value.charset);
434        assert!(extended_value.language_tag.is_some());
435        assert_eq!(expected_language_tag, extended_value.language_tag.unwrap());
436        assert_eq!(
437            vec![163, b' ', b'r', b'a', b't', b'e', b's'],
438            extended_value.value
439        );
440    }
441
442    #[test]
443    fn test_parse_extended_value_with_encoding() {
444        // RFC 5987, Section 3.2.2
445        // Extended notation, using the Unicode characters U+00A3 (POUND SIGN)
446        // and U+20AC (EURO SIGN)
447        let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates");
448        assert!(result.is_ok());
449        let extended_value = result.unwrap();
450        assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset);
451        assert!(extended_value.language_tag.is_none());
452        assert_eq!(
453            vec![
454                194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
455                b't', b'e', b's',
456            ],
457            extended_value.value
458        );
459    }
460
461    #[test]
462    fn test_parse_extended_value_missing_language_tag_and_encoding() {
463        // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2
464        let result = parse_extended_value("foo%20bar.html");
465        assert!(result.is_err());
466    }
467
468    #[test]
469    fn test_parse_extended_value_partially_formatted() {
470        let result = parse_extended_value("UTF-8'missing third part");
471        assert!(result.is_err());
472    }
473
474    #[test]
475    fn test_parse_extended_value_partially_formatted_blank() {
476        let result = parse_extended_value("blank second part'");
477        assert!(result.is_err());
478    }
479
480    #[test]
481    fn test_fmt_extended_value_with_encoding_and_language_tag() {
482        let extended_value = ExtendedValue {
483            charset: Charset::Iso_8859_1,
484            language_tag: Some("en".parse().expect("Could not parse language tag")),
485            value: vec![163, b' ', b'r', b'a', b't', b'e', b's'],
486        };
487        assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value));
488    }
489
490    #[test]
491    fn test_fmt_extended_value_with_encoding() {
492        let extended_value = ExtendedValue {
493            charset: Charset::Ext("UTF-8".to_string()),
494            language_tag: None,
495            value: vec![
496                194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
497                b't', b'e', b's',
498            ],
499        };
500        assert_eq!(
501            "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates",
502            format!("{}", extended_value)
503        );
504    }
505}