http_box/util/field.rs
1// +-----------------------------------------------------------------------------------------------+
2// | Copyright 2016 Sean Kerr |
3// | |
4// | Licensed under the Apache License, Version 2.0 (the "License"); |
5// | you may not use this file except in compliance with the License. |
6// | You may obtain a copy of the License at |
7// | |
8// | http://www.apache.org/licenses/LICENSE-2.0 |
9// | |
10// | Unless required by applicable law or agreed to in writing, software |
11// | distributed under the License is distributed on an "AS IS" BASIS, |
12// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13// | See the License for the specific language governing permissions and |
14// | limitations under the License. |
15// +-----------------------------------------------------------------------------------------------+
16
17use byte::{ is_header_field, is_quoted_header_field, is_token };
18
19use byte_slice::ByteStream;
20use std::fmt;
21
22/// Field errors.
23pub enum FieldError {
24 /// Invalid field name.
25 Name(u8),
26
27 /// Invalid field value.
28 Value(u8)
29}
30
31impl FieldError {
32 /// Format this for debug and display purposes.
33 fn format(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
34 match *self {
35 FieldError::Name(x) => {
36 write!(
37 formatter,
38 "<FieldError::Name: Invalid field name on byte {}>",
39 x
40 )
41 },
42 FieldError::Value(x) => {
43 write!(
44 formatter,
45 "<FieldError::Value: Invalid field value on byte {}>",
46 x
47 )
48 }
49 }
50 }
51}
52
53impl fmt::Debug for FieldError {
54 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
55 self.format(formatter)
56 }
57}
58
59impl fmt::Display for FieldError {
60 fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
61 self.format(formatter)
62 }
63}
64
65// -------------------------------------------------------------------------------------------------
66
67/// Header field iterator.
68///
69/// This allows you to iterate over a header field to retrieve `(name, value)` pairs.
70///
71/// # Errors
72///
73/// - [`FieldError::Name`](enum.FieldError.html#variant.Name)
74/// - [`FieldError::Value`](enum.FieldError.html#variant.Value)
75///
76/// ```rust
77/// extern crate http_box;
78///
79/// use http_box::util::{ FieldError, FieldIterator };
80///
81/// fn main() {
82/// // notice the upper-cased parameter names that are normalized to lower-case thanks
83/// // to the third parameter being `true`
84/// let field = b"COMPRESSION=bzip; BOUNDARY=\"longrandomboundarystring\"";
85///
86/// for (n, (name, value)) in FieldIterator::new(field, b';', true).enumerate() {
87/// if n == 0 {
88/// assert_eq!(
89/// name,
90/// "compression"
91/// );
92///
93/// assert_eq!(
94/// value.unwrap(),
95/// "bzip"
96/// );
97/// } else if n == 1 {
98/// assert_eq!(
99/// name,
100/// "boundary"
101/// );
102///
103/// assert_eq!(
104/// value.unwrap(),
105/// "longrandomboundarystring"
106/// );
107/// }
108/// }
109/// }
110/// ```
111pub struct FieldIterator<'a> {
112 context: ByteStream<'a>,
113 delimiter: u8,
114 name: Vec<u8>,
115 normalize: bool,
116 on_error: Box<FnMut(FieldError) + 'a>,
117 value: Vec<u8>
118}
119
120impl<'a> FieldIterator<'a> {
121 /// Create a new `FieldIterator`.
122 ///
123 /// # Arguments
124 ///
125 /// **`field`**
126 ///
127 /// The header field.
128 ///
129 /// **`delimiter`**
130 ///
131 /// The field delimiter.
132 ///
133 /// **`normalize`**
134 ///
135 /// Indicates that field names should be normalized to lower-case.
136 pub fn new(field: &'a [u8], delimiter: u8, normalize: bool) -> FieldIterator<'a> {
137 FieldIterator{
138 context: ByteStream::new(field),
139 delimiter: delimiter,
140 name: Vec::new(),
141 normalize: normalize,
142 on_error: Box::new(|_|{}),
143 value: Vec::new()
144 }
145 }
146
147 /// Set the on error callback.
148 ///
149 /// # Arguments
150 ///
151 /// **`on_error`**
152 ///
153 /// The callback.
154 pub fn on_error<F>(&mut self, on_error: F) -> &mut Self
155 where F : FnMut(FieldError) + 'a {
156 self.on_error = Box::new(on_error);
157 self
158 }
159}
160
161impl<'a> Iterator for FieldIterator<'a> {
162 type Item = (String, Option<String>);
163
164 fn next(&mut self) -> Option<(String, Option<String>)> {
165 if bs_available!(self.context) == 0 {
166 return None;
167 }
168
169 self.name.clear();
170 self.value.clear();
171
172 loop {
173 // parsing name
174 consume_linear_space!(
175 self.context,
176
177 // on end-of-stream
178 return None
179 );
180
181 bs_replay!(self.context);
182
183 bs_mark!(self.context);
184
185 collect_tokens!(
186 self.context,
187
188 // stop on these bytes
189 self.context.byte == b'='
190 || self.context.byte == self.delimiter
191 || self.context.byte == b'/'
192 || (self.normalize && self.context.byte > 0x40 && self.context.byte < 0x5B),
193
194 // on end-of-stream
195 {
196 // name without a value
197 if bs_slice_length!(self.context) > 0 {
198 self.name.extend_from_slice(bs_slice!(self.context));
199 }
200
201 submit_name!(self);
202 }
203 );
204
205 self.name.extend_from_slice(bs_slice_ignore!(self.context));
206
207 match self.context.byte {
208 b'=' => {
209 consume_linear_space!(
210 self.context,
211
212 // on end-of-stream
213 submit_name!(self)
214 );
215
216 if self.context.byte == b'"' {
217 // quoted value
218 loop {
219 bs_mark!(self.context);
220
221 collect_quoted_field!(
222 self.context,
223
224 // on end-of-stream
225 // didn't find an ending quote
226 submit_error!(self, FieldError::Value)
227 );
228
229 if self.context.byte == b'"' {
230 // found end quote
231 self.value.extend_from_slice(bs_slice_ignore!(self.context));
232
233 consume_linear_space!(
234 self.context,
235
236 // on end-of-stream
237 submit_name_value!(self)
238 );
239
240 if bs_available!(self.context) == 0 {
241 submit_name_value!(self);
242 }
243
244 bs_next!(self.context);
245
246 if self.context.byte == self.delimiter {
247 submit_name_value!(self);
248 }
249
250 // expected a semicolon to end the value
251 submit_error!(self, FieldError::Value);
252 } else if self.context.byte == b'\\' {
253 // found backslash
254 if bs_is_eos!(self.context) {
255 submit_error!(self, FieldError::Name);
256 }
257
258 self.value.extend_from_slice(bs_slice_ignore!(self.context));
259
260 bs_next!(self.context);
261
262 self.value.push(self.context.byte);
263 } else {
264 bs_jump!(self.context, bs_available!(self.context));
265
266 (*self.on_error)(FieldError::Value(self.context.byte));
267
268 return None;
269 }
270 }
271 } else {
272 // unquoted value
273 bs_replay!(self.context);
274 bs_mark!(self.context);
275
276 collect_field!(
277 self.context,
278
279 // stop on these bytes
280 self.context.byte == self.delimiter,
281
282 // on end-of-stream
283 {
284 if bs_slice_length!(self.context) > 0 {
285 self.value.extend_from_slice(bs_slice!(self.context));
286 }
287
288 submit_name_value!(self);
289 }
290 );
291
292 if bs_slice_length!(self.context) == 0 {
293 // name without a value
294 submit_name!(self);
295 }
296
297 if self.context.byte == self.delimiter {
298 submit_name_value!(self.name, bs_slice_ignore!(self.context));
299 } else {
300 bs_jump!(self.context, bs_available!(self.context));
301
302 (*self.on_error)(FieldError::Value(self.context.byte));
303
304 return None;
305 }
306 }
307 },
308 b'/' => {
309 // this isn't allowed as a token, but since it's a name-only field, it's allowed
310 self.name.push(b'/');
311 },
312 byte if byte == self.delimiter => {
313 // name without a value
314 submit_name!(self);
315 },
316 byte if byte > 0x40 && byte < 0x5B => {
317 // upper-cased byte, let's lower-case it
318 self.name.push(self.context.byte + 0x20);
319 },
320 _ => {
321 bs_jump!(self.context, bs_available!(self.context));
322
323 (*self.on_error)(FieldError::Name(self.context.byte));
324
325 return None;
326 }
327 }
328 }
329 }
330}