rocket/data/
limits.rs

1use serde::{Serialize, Deserialize};
2use crate::request::{Request, FromRequest, Outcome};
3
4use crate::data::ByteUnit;
5use crate::http::uncased::Uncased;
6
7/// Mapping from (hierarchical) data types to size limits.
8///
9/// A `Limits` structure contains a mapping from a given hierarchical data type
10/// ("form", "data-form", "ext/pdf", and so on) to the maximum size in bytes
11/// that should be accepted by Rocket for said data type. For instance, if the
12/// limit for "form" is set to `256`, only 256 bytes from an incoming non-data
13/// form (that is, url-encoded) will be accepted.
14///
15/// To help in preventing DoS attacks, all incoming data reads must capped by a
16/// limit. As such, all data guards impose a limit. The _name_ of the limit is
17/// dictated by the data guard or type itself. For instance, [`Form`] imposes
18/// the `form` limit for value-based forms and `data-form` limit for data-based
19/// forms.
20///
21/// If a limit is exceeded, a guard will typically fail. The [`Capped`] type
22/// allows retrieving some data types even when the limit is exceeded.
23///
24/// [`Capped`]: crate::data::Capped
25/// [`Form`]: crate::form::Form
26///
27/// # Hierarchy
28///
29/// Data limits are hierarchical. The `/` (forward slash) character delimits the
30/// levels, or layers, of a given limit. To obtain a limit value for a given
31/// name, layers are peeled from right to left until a match is found, if any.
32/// For example, fetching the limit named `pet/dog/bingo` will return the first
33/// of `pet/dog/bingo`, `pet/dog` or `pet`:
34///
35/// ```rust
36/// use rocket::data::{Limits, ToByteUnit};
37///
38/// let limits = Limits::default()
39///     .limit("pet", 64.kibibytes())
40///     .limit("pet/dog", 128.kibibytes())
41///     .limit("pet/dog/bingo", 96.kibibytes());
42///
43/// assert_eq!(limits.get("pet/dog/bingo"), Some(96.kibibytes()));
44/// assert_eq!(limits.get("pet/dog/ralph"), Some(128.kibibytes()));
45/// assert_eq!(limits.get("pet/cat/bingo"), Some(64.kibibytes()));
46///
47/// assert_eq!(limits.get("pet/dog/bingo/hat"), Some(96.kibibytes()));
48/// ```
49///
50/// # Built-in Limits
51///
52/// The following table details recognized built-in limits used by Rocket.
53///
54/// | Limit Name        | Default | Type         | Description                           |
55/// |-------------------|---------|--------------|---------------------------------------|
56/// | `form`            | 32KiB   | [`Form`]     | entire non-data-based form            |
57/// | `data-form`       | 2MiB    | [`Form`]     | entire data-based form                |
58/// | `file`            | 1MiB    | [`TempFile`] | [`TempFile`] data guard or form field |
59/// | `file/$ext`       | _N/A_   | [`TempFile`] | file form field with extension `$ext` |
60/// | `string`          | 8KiB    | [`String`]   | data guard or form field              |
61/// | `string`          | 8KiB    | [`&str`]     | data guard or form field              |
62/// | `bytes`           | 8KiB    | [`Vec<u8>`]  | data guard                            |
63/// | `bytes`           | 8KiB    | [`&[u8]`]    | data guard or form field              |
64/// | `json`            | 1MiB    | [`Json`]     | JSON data and form payloads           |
65/// | `msgpack`         | 1MiB    | [`MsgPack`]  | MessagePack data and form payloads    |
66///
67/// [`TempFile`]: crate::fs::TempFile
68/// [`Json`]: crate::serde::json::Json
69/// [`MsgPack`]: crate::serde::msgpack::MsgPack
70///
71/// # Usage
72///
73/// A `Limits` structure is created following the builder pattern:
74///
75/// ```rust
76/// use rocket::data::{Limits, ToByteUnit};
77///
78/// // Set a limit of 64KiB for forms, 3MiB for PDFs, and 1MiB for JSON.
79/// let limits = Limits::default()
80///     .limit("form", 64.kibibytes())
81///     .limit("file/pdf", 3.mebibytes())
82///     .limit("json", 2.mebibytes());
83/// ```
84///
85/// The [`Limits::default()`](#impl-Default) method populates the `Limits`
86/// structure with default limits in the [table above](#built-in-limits). A
87/// configured limit can be retrieved via the `&Limits` request guard:
88///
89/// ```rust
90/// # #[macro_use] extern crate rocket;
91/// use std::io;
92///
93/// use rocket::data::{Data, Limits, ToByteUnit};
94///
95/// #[post("/echo", data = "<data>")]
96/// async fn echo(data: Data<'_>, limits: &Limits) -> io::Result<String> {
97///     let limit = limits.get("data").unwrap_or(1.mebibytes());
98///     Ok(data.open(limit).into_string().await?.value)
99/// }
100/// ```
101///
102/// ...or via the [`Request::limits()`] method:
103///
104/// ```
105/// # #[macro_use] extern crate rocket;
106/// use rocket::request::Request;
107/// use rocket::data::{self, Data, FromData};
108///
109/// # struct MyType;
110/// # type MyError = ();
111/// #[rocket::async_trait]
112/// impl<'r> FromData<'r> for MyType {
113///     type Error = MyError;
114///
115///     async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> {
116///         let limit = req.limits().get("my-data-type");
117///         /* .. */
118///         # unimplemented!()
119///     }
120/// }
121/// ```
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
123#[serde(transparent)]
124pub struct Limits {
125    #[serde(deserialize_with = "Limits::deserialize")]
126    #[serde(serialize_with = "figment::util::vec_tuple_map::serialize")]
127    pub(crate) limits: Vec<(Uncased<'static>, ByteUnit)>,
128}
129
130impl Default for Limits {
131    fn default() -> Limits {
132        Limits::new()
133            .limit("form", Limits::FORM)
134            .limit("data-form", Limits::DATA_FORM)
135            .limit("file", Limits::FILE)
136            .limit("string", Limits::STRING)
137            .limit("bytes", Limits::BYTES)
138            .limit("json", Limits::JSON)
139            .limit("msgpack", Limits::MESSAGE_PACK)
140    }
141}
142
143impl Limits {
144    /// Default limit for value-based forms.
145    pub const FORM: ByteUnit = ByteUnit::Kibibyte(32);
146
147    /// Default limit for data-based forms.
148    pub const DATA_FORM: ByteUnit = ByteUnit::Mebibyte(2);
149
150    /// Default limit for temporary files.
151    pub const FILE: ByteUnit = ByteUnit::Mebibyte(1);
152
153    /// Default limit for strings.
154    pub const STRING: ByteUnit = ByteUnit::Kibibyte(8);
155
156    /// Default limit for bytes.
157    pub const BYTES: ByteUnit = ByteUnit::Kibibyte(8);
158
159    /// Default limit for JSON payloads.
160    pub const JSON: ByteUnit = ByteUnit::Mebibyte(1);
161
162    /// Default limit for MessagePack payloads.
163    pub const MESSAGE_PACK: ByteUnit = ByteUnit::Mebibyte(1);
164
165    /// Construct a new `Limits` structure with no limits set.
166    ///
167    /// # Example
168    ///
169    /// ```rust
170    /// use rocket::data::{Limits, ToByteUnit};
171    ///
172    /// let limits = Limits::default();
173    /// assert_eq!(limits.get("form"), Some(32.kibibytes()));
174    ///
175    /// let limits = Limits::new();
176    /// assert_eq!(limits.get("form"), None);
177    /// ```
178    #[inline]
179    pub fn new() -> Self {
180        Limits { limits: vec![] }
181    }
182
183    /// Adds or replaces a limit in `self`, consuming `self` and returning a new
184    /// `Limits` structure with the added or replaced limit.
185    ///
186    /// # Example
187    ///
188    /// ```rust
189    /// use rocket::data::{Limits, ToByteUnit};
190    ///
191    /// let limits = Limits::default();
192    /// assert_eq!(limits.get("form"), Some(32.kibibytes()));
193    /// assert_eq!(limits.get("json"), Some(1.mebibytes()));
194    /// assert_eq!(limits.get("cat"), None);
195    ///
196    /// let limits = limits.limit("cat", 1.mebibytes());
197    /// assert_eq!(limits.get("form"), Some(32.kibibytes()));
198    /// assert_eq!(limits.get("cat"), Some(1.mebibytes()));
199    ///
200    /// let limits = limits.limit("json", 64.mebibytes());
201    /// assert_eq!(limits.get("json"), Some(64.mebibytes()));
202    /// ```
203    pub fn limit<S: Into<Uncased<'static>>>(mut self, name: S, limit: ByteUnit) -> Self {
204        let name = name.into();
205        match self.limits.binary_search_by(|(k, _)| k.cmp(&name)) {
206            Ok(i) => self.limits[i].1 = limit,
207            Err(i) => self.limits.insert(i, (name, limit))
208        }
209
210        self
211    }
212
213    /// Returns the limit named `name`, proceeding hierarchically from right
214    /// to left until one is found, or returning `None` if none is found.
215    ///
216    /// # Example
217    ///
218    /// ```rust
219    /// use rocket::data::{Limits, ToByteUnit};
220    ///
221    /// let limits = Limits::default()
222    ///     .limit("json", 2.mebibytes())
223    ///     .limit("file/jpeg", 4.mebibytes())
224    ///     .limit("file/jpeg/special", 8.mebibytes());
225    ///
226    /// assert_eq!(limits.get("form"), Some(32.kibibytes()));
227    /// assert_eq!(limits.get("json"), Some(2.mebibytes()));
228    /// assert_eq!(limits.get("data-form"), Some(Limits::DATA_FORM));
229    ///
230    /// assert_eq!(limits.get("file"), Some(1.mebibytes()));
231    /// assert_eq!(limits.get("file/png"), Some(1.mebibytes()));
232    /// assert_eq!(limits.get("file/jpeg"), Some(4.mebibytes()));
233    /// assert_eq!(limits.get("file/jpeg/inner"), Some(4.mebibytes()));
234    /// assert_eq!(limits.get("file/jpeg/special"), Some(8.mebibytes()));
235    ///
236    /// assert!(limits.get("cats").is_none());
237    /// ```
238    pub fn get<S: AsRef<str>>(&self, name: S) -> Option<ByteUnit> {
239        let mut name = name.as_ref();
240        let mut indices = name.rmatch_indices('/');
241        loop {
242            let exact_limit = self.limits
243                .binary_search_by(|(k, _)| k.as_uncased_str().cmp(name.into()))
244                .map(|i| self.limits[i].1);
245
246            if let Ok(exact) = exact_limit {
247                return Some(exact);
248            }
249
250            let (i, _) = indices.next()?;
251            name = &name[..i];
252        }
253    }
254
255    /// Returns the limit for the name created by joining the strings in
256    /// `layers` with `/` as a separator, then proceeding like
257    /// [`Limits::get()`], hierarchically from right to left until one is found,
258    /// or returning `None` if none is found.
259    ///
260    /// This methods exists to allow finding hierarchical limits without
261    /// constructing a string to call `get()` with but otherwise returns the
262    /// same results.
263    ///
264    /// # Example
265    ///
266    /// ```rust
267    /// use rocket::data::{Limits, ToByteUnit};
268    ///
269    /// let limits = Limits::default()
270    ///     .limit("json", 2.mebibytes())
271    ///     .limit("file/jpeg", 4.mebibytes())
272    ///     .limit("file/jpeg/special", 8.mebibytes());
273    ///
274    /// assert_eq!(limits.find(["json"]), Some(2.mebibytes()));
275    /// assert_eq!(limits.find(["json", "person"]), Some(2.mebibytes()));
276    ///
277    /// assert_eq!(limits.find(["file"]), Some(1.mebibytes()));
278    /// assert_eq!(limits.find(["file", "png"]), Some(1.mebibytes()));
279    /// assert_eq!(limits.find(["file", "jpeg"]), Some(4.mebibytes()));
280    /// assert_eq!(limits.find(["file", "jpeg", "inner"]), Some(4.mebibytes()));
281    /// assert_eq!(limits.find(["file", "jpeg", "special"]), Some(8.mebibytes()));
282    ///
283    /// # let s: &[&str] = &[]; assert_eq!(limits.find(s), None);
284    /// ```
285    pub fn find<S: AsRef<str>, L: AsRef<[S]>>(&self, layers: L) -> Option<ByteUnit> {
286        let layers = layers.as_ref();
287        for j in (1..=layers.len()).rev() {
288            let layers = &layers[..j];
289            let opt = self.limits
290                .binary_search_by(|(k, _)| {
291                    let k_layers = k.as_str().split('/');
292                    k_layers.cmp(layers.iter().map(|s| s.as_ref()))
293                })
294                .map(|i| self.limits[i].1);
295
296            if let Ok(byte_unit) = opt {
297                return Some(byte_unit);
298            }
299        }
300
301        None
302    }
303
304    /// Deserialize a `Limits` vector from a map. Ensures that the resulting
305    /// vector is properly sorted for futures lookups via binary search.
306    fn deserialize<'de, D>(de: D) -> Result<Vec<(Uncased<'static>, ByteUnit)>, D::Error>
307        where D: serde::Deserializer<'de>
308    {
309        let mut limits = figment::util::vec_tuple_map::deserialize(de)?;
310        limits.sort();
311        Ok(limits)
312    }
313}
314
315#[crate::async_trait]
316impl<'r> FromRequest<'r> for &'r Limits {
317    type Error = std::convert::Infallible;
318
319    async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
320        Outcome::Success(req.limits())
321    }
322}