1use std::fmt;
2use soft_ascii_string::SoftAsciiStr;
3
4use internals::grammar::is_ftext;
5
6#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
12pub struct HeaderName {
13 name: &'static SoftAsciiStr
14}
15
16impl HeaderName {
17 pub fn new( name: &'static SoftAsciiStr ) -> Result<Self, InvalidHeaderName> {
26 HeaderName::validate_name( name )?;
27 Ok( HeaderName { name } )
28 }
29
30 pub fn from_ascii_unchecked<B: ?Sized>( name: &'static B ) -> HeaderName
31 where B: AsRef<str>
32 {
33 HeaderName { name: SoftAsciiStr::from_unchecked( name.as_ref() ) }
34 }
35
36 #[inline(always)]
37 pub fn as_ascii_str( &self ) -> &'static SoftAsciiStr {
38 self.name
39 }
40 #[inline(always)]
41 pub fn as_str( &self ) -> &'static str {
42 self.name.as_str()
43 }
44}
45
46impl fmt::Display for HeaderName {
47 fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result {
48 write!(fter, "{}", self.as_str())
49 }
50}
51
52impl PartialEq<str> for HeaderName {
53 fn eq(&self, other: &str) -> bool {
54 self.name.as_str() == other
55 }
56}
57
58impl PartialEq<SoftAsciiStr> for HeaderName {
59 fn eq(&self, other: &SoftAsciiStr) -> bool {
60 self.name == other
61 }
62}
63
64impl HeaderName {
65
66 fn validate_name(name: &SoftAsciiStr) -> Result<(), InvalidHeaderName> {
72 let mut begin_of_word = true;
73 if name.len() < 1 {
74 return Err(InvalidHeaderName { invalid_name: name.to_owned().into() });
75 }
76
77 for ch in name.as_str().chars() {
78 if !is_ftext( ch ) {
79 return Err(InvalidHeaderName { invalid_name: name.to_owned().into() });
80 }
81 match ch {
82 'a'...'z' => {
83 if begin_of_word {
84 return Err(InvalidHeaderName { invalid_name: name.to_owned().into() });
85 }
86 },
87 'A'...'Z' => {
88 if begin_of_word {
89 begin_of_word = false;
90 } else {
91 return Err(InvalidHeaderName { invalid_name: name.to_owned().into() });
92 }
93 },
94 '0'...'9' => {
95 begin_of_word = false;
96 },
97 ch => {
98 if ch < '!' || ch > '~' || ch == ':' {
99 return Err(InvalidHeaderName { invalid_name: name.to_owned().into() });
100 }
101 begin_of_word = true;
102 }
103
104 }
105
106 }
107 Ok( () )
108 }
109}
110
111#[derive(Clone, Debug, Fail)]
112#[fail(display = "given name is not a valid header name: {:?}", invalid_name)]
113pub struct InvalidHeaderName {
114 invalid_name: String
115}
116
117
118pub trait HasHeaderName {
121 fn get_name(&self) -> HeaderName;
122}
123
124impl HasHeaderName for HeaderName {
125 fn get_name(&self) -> HeaderName {
126 *self
127 }
128}
129
130#[cfg(test)]
131mod test {
132 use super::*;
133
134
135 #[test]
136 fn valide_header_names() {
137 let valid_cases = &[
138 "Date",
139 "Some-Header",
140 "33",
141 "Some34",
142 "-33-",
147 "---",
148 "<3+Who-Cares&44",
149 "(3*4=12)^[{~}]"
150 ];
151 for case in valid_cases.iter() {
152 assert_ok!(
153 HeaderName::validate_name( SoftAsciiStr::from_str( case ).unwrap() ) );
154 }
155 }
156
157 #[test]
158 fn invalide_header_names() {
159 let invalid_cases = &[
162 "ID",
163 "DaD",
164 "ans",
165 "all-lower-calse",
166 "ALL-UPPER-CASE",
167 "",
168 "a:b",
169 ":",
170 "-:-",
171 "Message Id",
172 " Leading-Ws",
173 "Message\tId",
174 "Null\0Msg"
175 ];
176 for case in invalid_cases.iter() {
177 assert_err!( HeaderName::validate_name( SoftAsciiStr::from_str( case ).unwrap() ), case );
178 }
179 }
180}
181
182