qiniu_multipart/server/
iron.rs

1//! Integration with the [Iron](https://ironframework.io) framework, enabled with the `iron` feature (optional). Includes a `BeforeMiddleware` implementation.
2//!
3//! Not shown here: `impl `[`HttpRequest`](../trait.HttpRequest.html#implementors)` for
4//! iron::Request`.
5
6use iron::headers::ContentType;
7use iron::mime::{Mime, SubLevel, TopLevel};
8use iron::request::{Body as IronBody, Request as IronRequest};
9use iron::typemap::Key;
10use iron::{BeforeMiddleware, IronError, IronResult};
11
12use std::path::PathBuf;
13use std::{error, fmt, io};
14use tempfile;
15
16use super::save::SaveResult::*;
17use super::save::{Entries, PartialReason, TempDir};
18use super::{FieldHeaders, HttpRequest, Multipart};
19
20impl<'r, 'a, 'b> HttpRequest for &'r mut IronRequest<'a, 'b> {
21    type Body = &'r mut IronBody<'a, 'b>;
22
23    fn multipart_boundary(&self) -> Option<&str> {
24        let content_type = try_opt!(self.headers.get::<ContentType>());
25        if let Mime(TopLevel::Multipart, SubLevel::FormData, _) = **content_type {
26            content_type.get_param("boundary").map(|b| b.as_str())
27        } else {
28            None
29        }
30    }
31
32    fn body(self) -> &'r mut IronBody<'a, 'b> {
33        &mut self.body
34    }
35}
36
37/// The default file size limit for [`Intercept`](struct.Intercept.html), in bytes.
38pub const DEFAULT_FILE_SIZE_LIMIT: u64 = 2 * 1024 * 1024;
39
40/// The default file count limit for [`Intercept`](struct.Intercept.html).
41pub const DEFAULT_FILE_COUNT_LIMIT: u32 = 16;
42
43/// A `BeforeMiddleware` for Iron which will intercept and read-out multipart requests and store
44/// the result in the request.
45///
46/// Successful reads will be placed in the `extensions: TypeMap` field of `iron::Request` as an
47/// [`Entries`](../struct.Entries.html) instance (as both key-type and value):
48///
49/// ```no_run
50/// extern crate iron;
51/// extern crate qiniu_multipart;
52///
53/// use iron::prelude::*;
54///
55/// use qiniu_multipart::server::Entries;
56/// use qiniu_multipart::server::iron::Intercept;
57///
58/// fn main() {
59///     let mut chain = Chain::new(|req: &mut Request| if let Some(entries) =
60///         req.extensions.get::<Entries>() {
61///
62///         Ok(Response::with(format!("{:?}", entries)))
63///     } else {
64///         Ok(Response::with("Not a multipart request"))
65///     });
66///
67///     chain.link_before(Intercept::default());
68///
69///     Iron::new(chain).http("localhost:80").unwrap();
70/// }
71/// ```
72///
73/// Any errors during which occur during reading will be passed on as `IronError`.
74#[derive(Debug)]
75pub struct Intercept {
76    /// The parent directory for all temporary directories created by this middleware.
77    /// Will be created if it doesn't exist (lazy).
78    ///
79    /// If omitted, uses the OS temporary directory.
80    ///
81    /// Default value: `None`.
82    pub temp_dir_path: Option<PathBuf>,
83    /// The size limit of uploaded files, in bytes.
84    ///
85    /// Files which exceed this size will be rejected.
86    /// See the `limit_behavior` field for more info.
87    ///
88    /// Default value: [`DEFAULT_FILE_SIZE_LIMIT`](constant.default_file_size_limit.html)
89    pub file_size_limit: u64,
90    /// The limit on the number of files which will be saved from
91    /// the request. Requests which exceed this count will be rejected.
92    ///
93    /// Default value: [`DEFAULT_FILE_COUNT_LIMT`](constant.default_file_count_limit.html)
94    pub file_count_limit: u32,
95    /// What to do when a file count or size limit has been exceeded.
96    ///
97    /// See [`LimitBehavior`](enum.limitbehavior.html) for more info.
98    pub limit_behavior: LimitBehavior,
99}
100
101impl Intercept {
102    /// Set the `temp_dir_path` for this middleware.
103    pub fn temp_dir_path<P: Into<PathBuf>>(self, path: P) -> Self {
104        Intercept {
105            temp_dir_path: Some(path.into()),
106            ..self
107        }
108    }
109
110    /// Set the `file_size_limit` for this middleware.
111    pub fn file_size_limit(self, limit: u64) -> Self {
112        Intercept {
113            file_size_limit: limit,
114            ..self
115        }
116    }
117
118    /// Set the `file_count_limit` for this middleware.
119    pub fn file_count_limit(self, limit: u32) -> Self {
120        Intercept {
121            file_count_limit: limit,
122            ..self
123        }
124    }
125
126    /// Set the `limit_behavior` for this middleware.
127    pub fn limit_behavior(self, behavior: LimitBehavior) -> Self {
128        Intercept {
129            limit_behavior: behavior,
130            ..self
131        }
132    }
133
134    fn read_request(&self, req: &mut IronRequest) -> IronResult<Option<Entries>> {
135        let multipart = match Multipart::from_request(req) {
136            Ok(multipart) => multipart,
137            Err(_) => return Ok(None),
138        };
139
140        let tempdir = self
141            .temp_dir_path
142            .as_ref()
143            .map_or_else(
144                || tempfile::Builder::new().prefix("multipart-iron").tempdir(),
145                |path| {
146                    tempfile::Builder::new()
147                        .prefix("multipart-iron")
148                        .tempdir_in(path)
149                },
150            )
151            .map_err(|e| io_to_iron(e, "Error opening temporary directory for request."))?;
152
153        match self.limit_behavior {
154            LimitBehavior::ThrowError => self.read_request_strict(multipart, tempdir),
155            LimitBehavior::Continue => self.read_request_lenient(multipart, tempdir),
156        }
157    }
158
159    fn read_request_strict(
160        &self,
161        mut multipart: IronMultipart,
162        tempdir: TempDir,
163    ) -> IronResult<Option<Entries>> {
164        match multipart
165            .save()
166            .size_limit(self.file_size_limit)
167            .count_limit(self.file_count_limit)
168            .with_temp_dir(tempdir)
169        {
170            Full(entries) => Ok(Some(entries)),
171            Partial(_, PartialReason::Utf8Error(_)) => unreachable!(),
172            Partial(_, PartialReason::IoError(err)) => {
173                Err(io_to_iron(err, "Error midway through request"))
174            }
175            Partial(_, PartialReason::CountLimit) => {
176                Err(FileCountLimitError(self.file_count_limit).into())
177            }
178            Partial(partial, PartialReason::SizeLimit) => {
179                let partial = partial.partial.expect(EXPECT_PARTIAL_FILE);
180                Err(FileSizeLimitError {
181                    field: partial.source.headers,
182                }
183                .into())
184            }
185            Error(err) => Err(io_to_iron(err, "Error at start of request")),
186        }
187    }
188
189    fn read_request_lenient(
190        &self,
191        mut multipart: IronMultipart,
192        tempdir: TempDir,
193    ) -> IronResult<Option<Entries>> {
194        let mut entries = match multipart
195            .save()
196            .size_limit(self.file_size_limit)
197            .count_limit(self.file_count_limit)
198            .with_temp_dir(tempdir)
199        {
200            Full(entries) => return Ok(Some(entries)),
201            Partial(_, PartialReason::IoError(err)) => {
202                return Err(io_to_iron(err, "Error midway through request"))
203            }
204            Partial(partial, _) => partial.keep_partial(),
205            Error(err) => return Err(io_to_iron(err, "Error at start of request")),
206        };
207
208        loop {
209            entries = match multipart
210                .save()
211                .size_limit(self.file_size_limit)
212                .count_limit(self.file_count_limit)
213                .with_entries(entries)
214            {
215                Full(entries) => return Ok(Some(entries)),
216                Partial(_, PartialReason::IoError(err)) => {
217                    return Err(io_to_iron(err, "Error midway through request"))
218                }
219                Partial(partial, _) => partial.keep_partial(),
220                Error(err) => return Err(io_to_iron(err, "Error at start of request")),
221            };
222        }
223    }
224}
225
226type IronMultipart<'r, 'a, 'b> = Multipart<&'r mut IronBody<'a, 'b>>;
227
228const EXPECT_PARTIAL_FILE: &str = "File size limit hit but the offending \
229                                   file was not available; this is a bug.";
230
231impl Default for Intercept {
232    fn default() -> Self {
233        Intercept {
234            temp_dir_path: None,
235            file_size_limit: DEFAULT_FILE_SIZE_LIMIT,
236            file_count_limit: DEFAULT_FILE_COUNT_LIMIT,
237            limit_behavior: LimitBehavior::ThrowError,
238        }
239    }
240}
241
242impl BeforeMiddleware for Intercept {
243    fn before(&self, req: &mut IronRequest) -> IronResult<()> {
244        self.read_request(req)?
245            .map(|entries| req.extensions.insert::<Entries>(entries));
246
247        Ok(())
248    }
249}
250
251impl Key for Entries {
252    type Value = Self;
253}
254
255/// The behavior of `Intercept` when a file size or count limit is exceeded.
256#[derive(Clone, Copy, Debug)]
257#[repr(u32)]
258pub enum LimitBehavior {
259    /// Return an error from the middleware describing the issue.
260    ThrowError,
261    /// Ignore the limit.
262    ///
263    /// In the case of file size limits, the offending file will be truncated
264    /// in the result.
265    ///
266    /// In the case of file count limits, the request will be completed.
267    Continue,
268}
269
270/// An error returned from `Intercept` when the size limit
271/// for an individual file is exceeded.
272#[derive(Debug)]
273pub struct FileSizeLimitError {
274    /// The field where the error occurred.
275    pub field: FieldHeaders,
276}
277
278impl error::Error for FileSizeLimitError {
279    fn description(&self) -> &str {
280        "file size limit reached"
281    }
282}
283
284impl fmt::Display for FileSizeLimitError {
285    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
286        match self.field.filename {
287            Some(ref filename) => write!(
288                f,
289                "File size limit reached for field \"{}\" (filename: \"{}\")",
290                self.field.name, filename
291            ),
292            None => write!(
293                f,
294                "File size limit reached for field \"{}\" (no filename)",
295                self.field.name
296            ),
297        }
298    }
299}
300
301impl Into<IronError> for FileSizeLimitError {
302    fn into(self) -> IronError {
303        let desc_str = self.to_string();
304        IronError::new(self, desc_str)
305    }
306}
307
308/// An error returned from `Intercept` when the file count limit
309/// for a single request was exceeded.
310#[derive(Debug)]
311pub struct FileCountLimitError(u32);
312
313impl error::Error for FileCountLimitError {
314    fn description(&self) -> &str {
315        "file count limit reached"
316    }
317}
318
319impl fmt::Display for FileCountLimitError {
320    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
321        write!(f, "File count limit reached for request. Limit: {}", self.0)
322    }
323}
324
325impl Into<IronError> for FileCountLimitError {
326    fn into(self) -> IronError {
327        let desc_string = self.to_string();
328        IronError::new(self, desc_string)
329    }
330}
331
332fn io_to_iron<M: Into<String>>(err: io::Error, msg: M) -> IronError {
333    IronError::new(err, msg.into())
334}