1use 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
37pub const DEFAULT_FILE_SIZE_LIMIT: u64 = 2 * 1024 * 1024;
39
40pub const DEFAULT_FILE_COUNT_LIMIT: u32 = 16;
42
43#[derive(Debug)]
75pub struct Intercept {
76 pub temp_dir_path: Option<PathBuf>,
83 pub file_size_limit: u64,
90 pub file_count_limit: u32,
95 pub limit_behavior: LimitBehavior,
99}
100
101impl Intercept {
102 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 pub fn file_size_limit(self, limit: u64) -> Self {
112 Intercept {
113 file_size_limit: limit,
114 ..self
115 }
116 }
117
118 pub fn file_count_limit(self, limit: u32) -> Self {
120 Intercept {
121 file_count_limit: limit,
122 ..self
123 }
124 }
125
126 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#[derive(Clone, Copy, Debug)]
257#[repr(u32)]
258pub enum LimitBehavior {
259 ThrowError,
261 Continue,
268}
269
270#[derive(Debug)]
273pub struct FileSizeLimitError {
274 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#[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}