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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Typed HTTP header serialization and deserialization.
//!
//! This crate is still in its early, experimental stages. It currently takes a fairly pedantic view of parsing, and
//! tries to support exactly what's specified in the HTTP RFCs.
//!
//! The `HeaderMapExt` extension trait provides new methods on the `http::HeaderMap` type to insert, retrieve, and
//! remove headers in a typed manner.
#![doc(html_root_url = "https://docs.rs/typed-headers/0.1")]

extern crate base64;
extern crate bytes;
extern crate chrono;

pub extern crate http;
pub extern crate mime;

use http::header::{self, HeaderMap, HeaderName, HeaderValue};
use std::error;
use std::fmt;
use std::mem;

pub use impls::*;

mod impls;
pub mod util;

pub trait Header {
    /// Returns the name of this header.
    ///
    /// The `http` crate provides constants for all standard header names. Implementations for
    /// nonstandard headers can use the `lazy_static` crate to obtain a static reference to a
    /// `HeaderName`.
    fn name() -> &'static HeaderName;

    /// Parses the header from the raw value bytes.
    ///
    /// The iterator may be empty, which indicates that the header was not present, and `Ok(None)`
    /// should be returned.
    ///
    /// If the iterator is not exhausted when this function returns, it will be treated as a parse
    /// error.
    fn from_values<'a>(
        values: &mut header::ValueIter<'a, HeaderValue>,
    ) -> Result<Option<Self>, Error>
    where
        Self: Sized;

    /// Serializes the header to raw values.
    ///
    /// Each call to `values.append` adds a header entry. Almost all headers should only append a
    /// single value. `Set-Cookie` is a rare exception.
    ///
    /// This method is infallible. Header implementations should ensure at construction time that
    /// they will be able to successfully serialize.
    fn to_values(&self, values: &mut ToValues);
}

#[derive(Debug)]
enum ErrorKind {
    InvalidValue,
    TooFewValues,
    TooManyValues,
}

/// An error serializing or deserializing a header.
#[derive(Debug)]
pub struct Error(ErrorKind);

impl Error {
    #[inline]
    pub fn invalid_value() -> Error {
        Error(ErrorKind::InvalidValue)
    }

    #[inline]
    pub fn too_few_values() -> Error {
        Error(ErrorKind::TooFewValues)
    }

    #[inline]
    pub fn too_many_values() -> Error {
        Error(ErrorKind::TooManyValues)
    }
}

impl fmt::Display for Error {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        let s = match self.0 {
            ErrorKind::InvalidValue => "invalid header value",
            ErrorKind::TooFewValues => "too few header values",
            ErrorKind::TooManyValues => "too many header values",
        };
        fmt.write_str(s)
    }
}

impl error::Error for Error {
    fn description(&self) -> &str {
        "header error"
    }
}

enum ToValuesState<'a> {
    First(header::Entry<'a, HeaderValue>),
    Latter(header::OccupiedEntry<'a, HeaderValue>),
    Tmp,
}

pub struct ToValues<'a>(ToValuesState<'a>);

impl<'a> ToValues<'a> {
    pub fn append(&mut self, value: HeaderValue) {
        let entry = match mem::replace(&mut self.0, ToValuesState::Tmp) {
            ToValuesState::First(header::Entry::Occupied(mut e)) => {
                e.insert(value);
                e
            }
            ToValuesState::First(header::Entry::Vacant(e)) => e.insert_entry(value),
            ToValuesState::Latter(mut e) => {
                e.append(value);
                e
            }
            ToValuesState::Tmp => unreachable!(),
        };
        self.0 = ToValuesState::Latter(entry);
    }
}

/// An extension trait adding typed getters and setters to `HeaderMap`.
pub trait HeaderMapExt {
    /// Retrieves the specified header from the map, if present.
    fn typed_get<H>(&self) -> Result<Option<H>, Error>
    where
        H: Header;

    /// Inserts the provided header into the map.
    ///
    /// This overwrites any existing entries for that header.
    fn typed_insert<H>(&mut self, header: &H)
    where
        H: Header;

    /// Removes and returns the specified header from the map, if present.
    ///
    /// The header will be removed even if it doesn't successfully parse.
    fn typed_remove<H>(&mut self) -> Result<Option<H>, Error>
    where
        H: Header;
}

impl HeaderMapExt for HeaderMap {
    fn typed_get<H>(&self) -> Result<Option<H>, Error>
    where
        H: Header,
    {
        let mut values = self.get_all(H::name()).iter();
        match H::from_values(&mut values) {
            Ok(header) => match values.next() {
                Some(_) => Err(Error::too_many_values()),
                None => Ok(header),
            },
            Err(e) => Err(e),
        }
    }

    fn typed_insert<H>(&mut self, header: &H)
    where
        H: Header,
    {
        let entry = self.entry(H::name()).unwrap();
        let mut values = ToValues(ToValuesState::First(entry));
        header.to_values(&mut values);
    }

    fn typed_remove<H>(&mut self) -> Result<Option<H>, Error>
    where
        H: Header,
    {
        match self.entry(H::name()).unwrap() {
            header::Entry::Occupied(entry) => {
                let r = H::from_values(&mut entry.iter());
                entry.remove();
                r
            }
            header::Entry::Vacant(_) => Ok(None),
        }
    }
}