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