Skip to main content

wasmtime_wasi_http/
field_map.rs

1use crate::{ForbiddenHeaderFilter, WasiHttpHooks};
2use http::header::Entry;
3use http::{HeaderMap, HeaderName, HeaderValue};
4use std::fmt;
5use std::ops::Deref;
6use std::sync::Arc;
7use wasmtime::Result;
8
9/// A wrapper around [`http::HeaderMap`] which implements `wasi:http` semantics.
10///
11/// The main differences from [`http::HeaderMap`] and this type are:
12///
13/// * A slimmed down mutability API to just what `wasi:http` needs.
14/// * `FieldMap` is cheaply clone-able with the internal `HeaderMap` being
15///   behind an `Arc`.
16/// * `FieldMap` is either immutable or mutable. Mutations on immutable values
17///   are rejected with an error. Mutations on mutable values will never panic
18///   unlike `HeaderMap` and additionally require a limit to be set on the size
19///   of the map.
20///
21/// Overall the intention is that this is a slim wrapper around
22/// [`http::HeaderMap`] with slightly different ownership, panic, and error
23/// semantics.
24#[derive(Debug, Clone)]
25pub struct FieldMap {
26    map: Arc<HeaderMap>,
27    limit: Limit,
28    size: usize,
29}
30
31#[derive(Debug, Clone)]
32enum Limit {
33    Mutable(usize),
34    Immutable,
35}
36
37impl Default for FieldMap {
38    fn default() -> Self {
39        Self {
40            map: Arc::new(HeaderMap::new()),
41            size: 0,
42            limit: Limit::Immutable,
43        }
44    }
45}
46
47impl FieldMap {
48    /// Creates a new immutable `FieldMap` from the provided
49    /// [`http::HeaderMap`].
50    ///
51    /// The returned value cannot be mutated and attempting to mutate it will
52    /// return an error.
53    pub fn new_immutable(hooks: &mut dyn WasiHttpHooks, mut map: HeaderMap) -> Self {
54        // Strip out all forbidden headers from `map` to ensure they're never
55        // able to enter into a WASI guest.
56        remove_forbidden(&mut map, &hooks.forbidden_header_filter());
57
58        let size = Self::content_size(&map);
59        Self {
60            map: Arc::new(map),
61            size,
62            limit: Limit::Immutable,
63        }
64    }
65
66    /// Creates a new, empty, mutable `FieldMap`.
67    ///
68    /// Mutations are allowed on the returned value and up to `limit` bytes of
69    /// memory (roughly) may be consumed by this map.
70    pub fn new_mutable(limit: usize) -> Self {
71        Self {
72            map: Arc::new(HeaderMap::new()),
73            size: 0,
74            limit: Limit::Mutable(limit),
75        }
76    }
77
78    /// Calculate the content size of a `HeaderMap`. This is a sum of the size
79    /// of all of the keys and all of the values.
80    fn content_size(map: &HeaderMap) -> usize {
81        let mut sum = 0;
82        for key in map.keys() {
83            sum += header_name_size(key);
84        }
85        for value in map.values() {
86            sum += header_value_size(value);
87        }
88        sum
89    }
90
91    /// Sets the header `key` to the `values` list provided.
92    ///
93    /// Removes the previous value, if any.
94    ///
95    /// If `values` is empty then this removes the header `key`.
96    //
97    // FIXME(WebAssembly/WASI#900): is this the right behavior?
98    pub fn set(
99        &mut self,
100        hooks: &mut dyn WasiHttpHooks,
101        key: String,
102        values: Vec<Vec<u8>>,
103    ) -> Result<(), FieldMapError> {
104        let key = key.parse()?;
105        if (hooks.forbidden_header_filter())(&key) {
106            return Err(FieldMapError::Forbidden);
107        }
108        let (map, limit, size) = self.mutable()?;
109
110        // `Content-Length` may only have a single value, see `append`.
111        if key == http::header::CONTENT_LENGTH && values.len() > 1 {
112            return Err(FieldMapError::InvalidHeaderValue);
113        }
114        let values = values
115            .into_iter()
116            .map(|v| parse_header_value(&key, v))
117            .collect::<Result<Vec<_>, _>>()?;
118
119        let key_size = header_name_size(&key);
120        let prev_values_size: usize = map.get_all(&key).iter().map(header_value_size).sum();
121        let mut new_size = *size - prev_values_size;
122        new_size += values.iter().map(header_value_size).sum::<usize>();
123
124        let mut values = values.into_iter();
125        let Some(first) = values.next() else {
126            if map.remove(&key).is_some() {
127                *size = new_size - key_size;
128            }
129            return Ok(());
130        };
131        if !map.contains_key(&key) {
132            new_size += key_size;
133        }
134        if new_size > limit {
135            return Err(FieldMapError::TotalSizeTooBig);
136        }
137
138        // `try_entry` reserves a slot even for an existing key, therefore remove it first
139        map.remove(&key);
140        // `try_entry` reports MaxSizeReached as InvalidHeaderName
141        let Entry::Vacant(entry) = map
142            .try_entry(key)
143            .map_err(|_| FieldMapError::TooManyFields)?
144        else {
145            // `key` was just removed above
146            unreachable!();
147        };
148        let mut entry = entry.try_insert_entry(first)?;
149        for value in values {
150            entry.append(value);
151        }
152        *size = new_size;
153        Ok(())
154    }
155
156    /// Remove all values associated with a key in a map.
157    ///
158    /// Returns an empty list if the key is not already present within the map.
159    pub fn remove_all(
160        &mut self,
161        hooks: &mut dyn WasiHttpHooks,
162        key: String,
163    ) -> Result<Vec<HeaderValue>, FieldMapError> {
164        let key = key.parse()?;
165        if (hooks.forbidden_header_filter())(&key) {
166            return Err(FieldMapError::Forbidden);
167        }
168        let (map, _limit, size) = self.mutable()?;
169        // try_entry misreports a full map as InvalidHeaderName.
170        let values = map.get_all(&key).iter().cloned().collect::<Vec<_>>();
171        if values.is_empty() {
172            return Ok(Vec::new());
173        }
174        let mut removed = header_name_size(&key);
175        for v in values.iter() {
176            removed += header_value_size(v);
177        }
178        let _ = map.remove(&key);
179        *size -= removed;
180        Ok(values)
181    }
182
183    fn mutable(&mut self) -> Result<(&mut HeaderMap, usize, &mut usize), FieldMapError> {
184        match self.limit {
185            Limit::Immutable => Err(FieldMapError::Immutable),
186            Limit::Mutable(limit) => Ok((Arc::make_mut(&mut self.map), limit, &mut self.size)),
187        }
188    }
189
190    /// Add a value associated with a key to the map.
191    ///
192    /// If `key` is already present within the map then `value` is appended to
193    /// the list of values it already has.
194    pub fn append(
195        &mut self,
196        hooks: &mut dyn WasiHttpHooks,
197        key: String,
198        value: Vec<u8>,
199    ) -> Result<bool, FieldMapError> {
200        let key = key.parse()?;
201        if (hooks.forbidden_header_filter())(&key) {
202            return Err(FieldMapError::Forbidden);
203        }
204        let value = parse_header_value(&key, value)?;
205        // Multiple `Content-Length` values make message framing ambiguous, as
206        // different parties may disagree on which one to use, so only a single
207        // value is permitted.
208        if key == http::header::CONTENT_LENGTH && self.contains_key(&key) {
209            return Err(FieldMapError::InvalidHeaderValue);
210        }
211        self.append_raw(key, value)
212    }
213
214    /// Add a value associated with a key to the map.
215    ///
216    /// If `key` is already present within the map then `value` is appended to
217    /// the list of values it already has.
218    ///  TODO
219    pub fn append_raw(
220        &mut self,
221        key: HeaderName,
222        value: HeaderValue,
223    ) -> Result<bool, FieldMapError> {
224        let (map, limit, size) = self.mutable()?;
225        let key_size = header_name_size(&key);
226        let val_size = header_value_size(&value);
227        let new_size = if !map.contains_key(&key) {
228            *size + key_size + val_size
229        } else {
230            *size + val_size
231        };
232        if new_size > limit {
233            return Err(FieldMapError::TotalSizeTooBig);
234        }
235        let already_present = map.try_append(key, value)?;
236        *size = new_size;
237        Ok(already_present)
238    }
239
240    /// Flags this map as mutable, allowing mutations which can allocate as much
241    /// as `limit` memory, in bytes, for this entire map (roughly).
242    pub fn set_mutable(&mut self, limit: usize) {
243        self.limit = Limit::Mutable(limit);
244    }
245
246    /// Flags this map as immutable, forbidding all further mutations.
247    pub fn set_immutable(&mut self) {
248        self.limit = Limit::Immutable;
249    }
250}
251
252/// Returns the size, in accounting cost, to consider for `name`.
253///
254/// This includes both the byte length of the `name` itself as well as the size
255/// of the data structure itself as it'll reside within a `HeaderMap`.
256fn header_name_size(name: &HeaderName) -> usize {
257    name.as_str().len() + size_of::<HeaderName>()
258}
259
260/// Same as `header_name_size`, but for values.
261///
262/// This notably includes the size of `HeaderValue` itself to ensure that all
263/// headers have a nonzero size as otherwise this would never limit addition of
264/// an empty header value.
265fn header_value_size(value: &HeaderValue) -> usize {
266    value.len() + size_of::<HeaderValue>()
267}
268
269// Note that `DerefMut` is specifically omitted here to force all mutations
270// through the `FieldMap` wrapper.
271impl Deref for FieldMap {
272    type Target = HeaderMap;
273
274    fn deref(&self) -> &HeaderMap {
275        &self.map
276    }
277}
278
279impl From<FieldMap> for HeaderMap {
280    fn from(map: FieldMap) -> Self {
281        Arc::unwrap_or_clone(map.map)
282    }
283}
284
285/// Errors that can happen when mutating/operating on a [`FieldMap`].
286#[derive(Debug, PartialEq, Eq, Clone, Copy)]
287pub enum FieldMapError {
288    /// A mutation was attempted when the map is not mutable.
289    Immutable,
290    /// The map has too many fields and is not allowed to add more.
291    ///
292    /// Note that this is currently a limitation inherited from
293    /// [`http::HeaderMap`].
294    TooManyFields,
295    /// The map's total size, of keys and values, is too large.
296    TotalSizeTooBig,
297    /// An invalid header name was attempted to be added.
298    InvalidHeaderName,
299    /// An invalid header value was attempted to be added.
300    InvalidHeaderValue,
301    /// A forbidden header name was used.
302    Forbidden,
303}
304
305impl fmt::Display for FieldMapError {
306    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
307        let s = match self {
308            FieldMapError::Immutable => "cannot mutate an immutable field map",
309            FieldMapError::TooManyFields => "too many fields in the field map",
310            FieldMapError::TotalSizeTooBig => "total size of fields exceeds limit",
311            FieldMapError::InvalidHeaderName => "invalid header name",
312            FieldMapError::InvalidHeaderValue => "invalid header value",
313            FieldMapError::Forbidden => "forbidden header name",
314        };
315        f.write_str(s)
316    }
317}
318
319impl std::error::Error for FieldMapError {}
320
321impl From<http::header::MaxSizeReached> for FieldMapError {
322    fn from(_: http::header::MaxSizeReached) -> Self {
323        Self::TooManyFields
324    }
325}
326
327impl From<http::header::InvalidHeaderName> for FieldMapError {
328    fn from(_: http::header::InvalidHeaderName) -> Self {
329        Self::InvalidHeaderName
330    }
331}
332
333impl From<http::header::InvalidHeaderValue> for FieldMapError {
334    fn from(_: http::header::InvalidHeaderValue) -> Self {
335        Self::InvalidHeaderValue
336    }
337}
338
339fn parse_header_value(
340    name: &http::HeaderName,
341    value: Vec<u8>,
342) -> Result<http::HeaderValue, FieldMapError> {
343    if name == http::header::CONTENT_LENGTH {
344        let s = str::from_utf8(value.as_ref()).or(Err(FieldMapError::InvalidHeaderValue))?;
345        // RFC 9110 defines `Content-Length` as `1*DIGIT`. `u64`'s `FromStr` is
346        // more lenient and also accepts a leading `+`, so reject anything that
347        // isn't a non-empty run of decimal digits.
348        if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) {
349            return Err(FieldMapError::InvalidHeaderValue);
350        }
351        let v: u64 = s.parse().or(Err(FieldMapError::InvalidHeaderValue))?;
352        Ok(v.into())
353    } else {
354        let value = value.try_into()?;
355        Ok(value)
356    }
357}
358
359#[cfg(test)]
360mod tests {
361    use super::{FieldMap, FieldMapError, parse_header_value};
362    use crate::default_hooks;
363    use http::header::{CONTENT_LENGTH, CONTENT_TYPE};
364    use http::{HeaderName, HeaderValue};
365
366    #[test]
367    fn test_immutable() {
368        let mut map = FieldMap::default();
369        assert_eq!(
370            map.set(default_hooks(), "foo".to_owned(), vec![b"bar".to_vec()]),
371            Err(FieldMapError::Immutable)
372        );
373        assert_eq!(
374            map.append(default_hooks(), "foo".to_owned(), b"bar".to_vec()),
375            Err(FieldMapError::Immutable)
376        );
377        assert_eq!(
378            map.remove_all(default_hooks(), "foo".to_owned()),
379            Err(FieldMapError::Immutable)
380        );
381    }
382
383    #[test]
384    fn test_limits() {
385        let mut map = FieldMap::new_mutable(100);
386        loop {
387            match map.append(default_hooks(), "foo".to_owned(), b"bar".to_vec()) {
388                Ok(_) => {}
389                Err(FieldMapError::TotalSizeTooBig) => break,
390                Err(e) => panic!("unexpected error: {e}"),
391            }
392        }
393
394        map = FieldMap::new_mutable(100);
395        for i in 0.. {
396            match map.set(
397                default_hooks(),
398                "foo".to_owned(),
399                (0..i).map(|j| format!("bar{j}").into_bytes()).collect(),
400            ) {
401                Ok(_) => {}
402                Err(FieldMapError::TotalSizeTooBig) => break,
403                Err(e) => panic!("unexpected error: {e}"),
404            }
405        }
406
407        map = FieldMap::new_mutable(100);
408        for i in 0.. {
409            match map.set(default_hooks(), format!("foo{i}"), vec![b"bar".to_vec()]) {
410                Ok(_) => {}
411                Err(FieldMapError::TotalSizeTooBig) => break,
412                Err(e) => panic!("unexpected error: {e}"),
413            }
414        }
415    }
416
417    #[test]
418    fn test_size() -> Result<(), FieldMapError> {
419        let mut map = FieldMap::new_mutable(2000);
420        let name = "foo".to_owned();
421        let hooks = default_hooks();
422
423        map.append(hooks, name.clone(), b"bar".to_vec())?;
424        assert!(map.size > 0);
425        map.remove_all(hooks, name.clone())?;
426        assert_eq!(map.size, 0);
427
428        map.set(hooks, name.clone(), vec![b"bar".to_vec()])?;
429        assert!(map.size > 0);
430        map.remove_all(hooks, name.clone())?;
431        assert_eq!(map.size, 0);
432
433        map.set(hooks, name.clone(), vec![])?;
434        assert_eq!(map.size, 0);
435        map.set(hooks, name.clone(), vec![b"bar".to_vec()])?;
436        assert!(map.size > 0);
437        map.set(hooks, name.clone(), vec![])?;
438        assert_eq!(map.size, 0);
439
440        map.set(hooks, name.clone(), vec![b"bar".to_vec()])?;
441        assert!(map.size > 0);
442        map.set(hooks, name.clone(), vec![b"bar".to_vec(), b"baz".to_vec()])?;
443        assert!(map.size > 0);
444        map.remove_all(hooks, name.clone())?;
445        assert_eq!(map.size, 0);
446
447        Ok(())
448    }
449
450    #[test]
451    fn set_empty_over_limit() -> Result<(), FieldMapError> {
452        let mut map = FieldMap::new_mutable(usize::MAX);
453        let hooks = default_hooks();
454        map.set(hooks, "foo".to_owned(), vec![b"bar".to_vec()])?;
455        map.set_mutable(0);
456
457        map.set(hooks, "new".to_owned(), vec![])?;
458        map.set(hooks, "foo".to_owned(), vec![])?;
459        assert_eq!(map.size, 0);
460
461        Ok(())
462    }
463
464    #[test]
465    fn append_raw_too_many_fields_keeps_size() {
466        let mut map = FieldMap::new_mutable(usize::MAX);
467        let mut i = 0;
468        let err = loop {
469            let name = HeaderName::try_from(format!("foo{i}")).unwrap();
470            match map.append_raw(name, HeaderValue::from_static("bar")) {
471                Ok(_) => i += 1,
472                Err(e) => break e,
473            }
474        };
475        assert_eq!(err, FieldMapError::TooManyFields);
476        assert_eq!(map.size, FieldMap::content_size(&map));
477    }
478
479    #[test]
480    fn content_length_rejects_non_digits() {
481        assert!(parse_header_value(&CONTENT_LENGTH, b"0".to_vec()).is_ok());
482        assert!(parse_header_value(&CONTENT_LENGTH, b"1234".to_vec()).is_ok());
483
484        // `u64::from_str` accepts these but they are not `1*DIGIT` per RFC 9110.
485        assert!(parse_header_value(&CONTENT_LENGTH, b"+5".to_vec()).is_err());
486        assert!(parse_header_value(&CONTENT_LENGTH, b"-5".to_vec()).is_err());
487        assert!(parse_header_value(&CONTENT_LENGTH, b" 5".to_vec()).is_err());
488        assert!(parse_header_value(&CONTENT_LENGTH, b"".to_vec()).is_err());
489
490        // other header names are unaffected
491        assert!(parse_header_value(&CONTENT_TYPE, b"text/plain".to_vec()).is_ok());
492    }
493
494    #[test]
495    fn content_length_rejects_multiple_values() {
496        let hooks = default_hooks();
497        let cl = || CONTENT_LENGTH.as_str().to_owned();
498
499        let mut map = FieldMap::new_mutable(2000);
500        map.append(hooks, cl(), b"5".to_vec()).unwrap();
501        assert_eq!(
502            map.append(hooks, cl(), b"6".to_vec()),
503            Err(FieldMapError::InvalidHeaderValue)
504        );
505        assert_eq!(
506            map.append(hooks, cl(), b"5".to_vec()),
507            Err(FieldMapError::InvalidHeaderValue)
508        );
509        assert_eq!(
510            map.append(hooks, "Content-Length".to_owned(), b"6".to_vec()),
511            Err(FieldMapError::InvalidHeaderValue)
512        );
513        assert_eq!(map.get_all(CONTENT_LENGTH).iter().count(), 1);
514        assert_eq!(map.size, FieldMap::content_size(&map.map));
515
516        assert_eq!(
517            map.set(hooks, cl(), vec![b"5".to_vec(), b"6".to_vec()]),
518            Err(FieldMapError::InvalidHeaderValue)
519        );
520        assert_eq!(
521            map.set(hooks, cl(), vec![b"5".to_vec(), b"5".to_vec()]),
522            Err(FieldMapError::InvalidHeaderValue)
523        );
524        assert_eq!(map[CONTENT_LENGTH], "5");
525
526        // Replacing the single value is still fine.
527        map.set(hooks, cl(), vec![b"6".to_vec()]).unwrap();
528        assert_eq!(map[CONTENT_LENGTH], "6");
529        assert_eq!(map.get_all(CONTENT_LENGTH).iter().count(), 1);
530        assert_eq!(map.size, FieldMap::content_size(&map.map));
531    }
532
533    #[test]
534    fn full_map_is_not_invalid_syntax() {
535        // try_entry reports MaxSizeReached as InvalidHeaderName (http map.rs:1149),
536        // so a full map must report valid names as too many fields instead.
537        let mut map = FieldMap::new_mutable(usize::MAX);
538        let mut i = 0;
539        let err = loop {
540            assert_eq!(map.size, FieldMap::content_size(&map.map));
541            match map.set(default_hooks(), format!("k{i}"), vec![b"v".to_vec()]) {
542                Ok(()) => i += 1,
543                Err(e) => break e,
544            }
545        };
546        assert_eq!(err, FieldMapError::TooManyFields);
547        assert_eq!(map.size, FieldMap::content_size(&map.map));
548
549        assert_eq!(
550            map.set(
551                default_hooks(),
552                "new".to_owned(),
553                vec![b"a".to_vec(), b"b".to_vec()],
554            ),
555            Err(FieldMapError::TooManyFields)
556        );
557        assert!(!map.contains_key("new"));
558        assert_eq!(map.size, FieldMap::content_size(&map.map));
559
560        assert_eq!(
561            map.set(
562                default_hooks(),
563                "k0".to_owned(),
564                vec![b"foo".to_vec(), b"bar".to_vec()]
565            ),
566            Ok(())
567        );
568        assert_eq!(map.get("k0"), Some(&HeaderValue::from_static("foo")));
569        assert_eq!(
570            map.get_all("k0").iter().collect::<Vec<_>>(),
571            [
572                HeaderValue::from_static("foo"),
573                HeaderValue::from_static("bar")
574            ]
575        );
576        assert_eq!(map.size, FieldMap::content_size(&map.map));
577
578        assert_eq!(map.set(default_hooks(), "k0".to_owned(), vec![]), Ok(()));
579        assert!(!map.contains_key("k0"));
580        assert_eq!(map.size, FieldMap::content_size(&map.map));
581
582        assert_eq!(
583            map.set(default_hooks(), "k0".to_owned(), vec![b"v".to_vec()]),
584            Ok(())
585        );
586        assert_eq!(map.get("k0"), Some(&HeaderValue::from_static("v")));
587        assert_eq!(map.size, FieldMap::content_size(&map.map));
588
589        // Removing a field never needs new capacity.
590        assert_eq!(
591            map.remove_all(default_hooks(), "k0".to_owned())
592                .map(|v| v.len()),
593            Ok(1)
594        );
595        assert_eq!(map.size, FieldMap::content_size(&map.map));
596    }
597}
598
599/// Removes all fields from `map` whose name `is_forbidden`.
600pub(crate) fn remove_forbidden(map: &mut HeaderMap, is_forbidden: &ForbiddenHeaderFilter) {
601    let forbidden_keys = Vec::from_iter(map.keys().filter(|name| is_forbidden(name)).cloned());
602    for name in forbidden_keys {
603        map.remove(&name);
604    }
605}