1use iron::headers::ContentType;
7use iron::mime::{Mime, TopLevel, SubLevel};
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::{FieldHeaders, HttpRequest, Multipart};
17use super::save::{Entries, PartialReason, TempDir};
18use super::save::SaveResult::*;
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 { temp_dir_path: Some(path.into()), .. self }
105 }
106
107 pub fn file_size_limit(self, limit: u64) -> Self {
109 Intercept { file_size_limit: limit, .. self }
110 }
111
112 pub fn file_count_limit(self, limit: u32) -> Self {
114 Intercept { file_count_limit: limit, .. self }
115 }
116
117 pub fn limit_behavior(self, behavior: LimitBehavior) -> Self {
119 Intercept { limit_behavior: behavior, .. self }
120 }
121
122 fn read_request(&self, req: &mut IronRequest) -> IronResult<Option<Entries>> {
123 let multipart = match Multipart::from_request(req) {
124 Ok(multipart) => multipart,
125 Err(_) => return Ok(None),
126 };
127
128 let tempdir = self.temp_dir_path.as_ref()
129 .map_or_else(
130 || tempfile::Builder::new().prefix("multipart-iron").tempdir(),
131 |path| tempfile::Builder::new().prefix("multipart-iron").tempdir_in(path)
132 )
133 .map_err(|e| io_to_iron(e, "Error opening temporary directory for request."))?;
134
135 match self.limit_behavior {
136 LimitBehavior::ThrowError => self.read_request_strict(multipart, tempdir),
137 LimitBehavior::Continue => self.read_request_lenient(multipart, tempdir),
138 }
139 }
140
141 fn read_request_strict(&self, mut multipart: IronMultipart, tempdir: TempDir) -> IronResult<Option<Entries>> {
142 match multipart.save().size_limit(self.file_size_limit)
143 .count_limit(self.file_count_limit)
144 .with_temp_dir(tempdir) {
145 Full(entries) => Ok(Some(entries)),
146 Partial(_, PartialReason::Utf8Error(_)) => unreachable!(),
147 Partial(_, PartialReason::IoError(err)) => Err(io_to_iron(err, "Error midway through request")),
148 Partial(_, PartialReason::CountLimit) => Err(FileCountLimitError(self.file_count_limit).into()),
149 Partial(partial, PartialReason::SizeLimit) => {
150 let partial = partial.partial.expect(EXPECT_PARTIAL_FILE);
151 Err(
152 FileSizeLimitError {
153 field: partial.source.headers,
154 }.into()
155 )
156 },
157 Error(err) => Err(io_to_iron(err, "Error at start of request")),
158 }
159 }
160
161 fn read_request_lenient(&self, mut multipart: IronMultipart, tempdir: TempDir) -> IronResult<Option<Entries>> {
162 let mut entries = match multipart.save().size_limit(self.file_size_limit)
163 .count_limit(self.file_count_limit)
164 .with_temp_dir(tempdir) {
165 Full(entries) => return Ok(Some(entries)),
166 Partial(_, PartialReason::IoError(err)) => return Err(io_to_iron(err, "Error midway through request")),
167 Partial(partial, _) => partial.keep_partial(),
168 Error(err) => return Err(io_to_iron(err, "Error at start of request")),
169 };
170
171 loop {
172 entries = match multipart.save().size_limit(self.file_size_limit)
173 .count_limit(self.file_count_limit)
174 .with_entries(entries) {
175 Full(entries) => return Ok(Some(entries)),
176 Partial(_, PartialReason::IoError(err)) => return Err(io_to_iron(err, "Error midway through request")),
177 Partial(partial, _) => partial.keep_partial(),
178 Error(err) => return Err(io_to_iron(err, "Error at start of request")),
179 };
180 }
181 }
182}
183
184type IronMultipart<'r, 'a, 'b> = Multipart<&'r mut IronBody<'a, 'b>>;
185
186const EXPECT_PARTIAL_FILE: &str = "File size limit hit but the offending \
187 file was not available; this is a bug.";
188
189impl Default for Intercept {
190 fn default() -> Self {
191 Intercept {
192 temp_dir_path: None,
193 file_size_limit: DEFAULT_FILE_SIZE_LIMIT,
194 file_count_limit: DEFAULT_FILE_COUNT_LIMIT,
195 limit_behavior: LimitBehavior::ThrowError,
196 }
197 }
198}
199
200impl BeforeMiddleware for Intercept {
201 fn before(&self, req: &mut IronRequest) -> IronResult<()> {
202 self.read_request(req)?
203 .map(|entries| req.extensions.insert::<Entries>(entries));
204
205 Ok(())
206 }
207}
208
209impl Key for Entries {
210 type Value = Self;
211}
212
213#[derive(Clone, Copy, Debug)]
215#[repr(u32)]
216pub enum LimitBehavior {
217 ThrowError,
219 Continue,
226}
227
228#[derive(Debug)]
231pub struct FileSizeLimitError {
232 pub field: FieldHeaders,
234}
235
236impl error::Error for FileSizeLimitError {
237 fn description(&self) -> &str {
238 "file size limit reached"
239 }
240}
241
242impl fmt::Display for FileSizeLimitError {
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 match self.field.filename {
245 Some(ref filename) => write!(f, "File size limit reached for field \"{}\" (filename: \"{}\")", self.field.name, filename),
246 None => write!(f, "File size limit reached for field \"{}\" (no filename)", self.field.name),
247 }
248 }
249}
250
251impl Into<IronError> for FileSizeLimitError {
252 fn into(self) -> IronError {
253 let desc_str = self.to_string();
254 IronError::new(self, desc_str)
255 }
256}
257
258#[derive(Debug)]
261pub struct FileCountLimitError(u32);
262
263impl error::Error for FileCountLimitError {
264 fn description(&self) -> &str {
265 "file count limit reached"
266 }
267}
268
269impl fmt::Display for FileCountLimitError {
270 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 write!(f, "File count limit reached for request. Limit: {}", self.0)
272 }
273}
274
275impl Into<IronError> for FileCountLimitError {
276 fn into(self) -> IronError {
277 let desc_string = self.to_string();
278 IronError::new(self, desc_string)
279 }
280}
281
282fn io_to_iron<M: Into<String>>(err: io::Error, msg: M) -> IronError {
283 IronError::new(err, msg.into())
284}