multipart/server/save.rs
1// Copyright 2016 `multipart` Crate Developers
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7//! Utilities for saving request entries to the filesystem.
8
9pub use server::buf_redux::BufReader;
10
11pub use tempfile::TempDir;
12
13use std::collections::HashMap;
14use std::io::prelude::*;
15use std::fs::{self, File, OpenOptions};
16use std::path::{Path, PathBuf};
17use std::sync::Arc;
18use std::{cmp, env, io, mem, str, u32, u64};
19use tempfile;
20
21use server::field::{FieldHeaders, MultipartField, MultipartData, ReadEntry, ReadEntryResult};
22
23use self::SaveResult::*;
24use self::TextPolicy::*;
25use self::PartialReason::*;
26
27const RANDOM_FILENAME_LEN: usize = 12;
28
29fn rand_filename() -> String {
30 ::random_alphanumeric(RANDOM_FILENAME_LEN)
31}
32
33macro_rules! try_start (
34 ($try:expr) => (
35 match $try {
36 Ok(val) => val,
37 Err(e) => return Error(e),
38 }
39 )
40);
41
42macro_rules! try_full (
43 ($try:expr) => {
44 match $try {
45 Full(full) => full,
46 other => return other,
47 }
48 }
49);
50
51macro_rules! try_partial (
52 ($try:expr) => {
53 match $try {
54 Full(full) => return Full(full.into()),
55 Partial(partial, reason) => (partial, reason),
56 Error(e) => return Error(e),
57 }
58 }
59);
60
61#[derive(Clone, Copy, Debug, Eq, PartialEq)]
62enum TextPolicy {
63 /// Attempt to read a text field as text, falling back to binary on error
64 Try,
65 /// Attempt to read a text field as text, returning any errors
66 Force,
67 /// Don't try to read text
68 Ignore
69}
70
71/// A builder for saving a file or files to the local filesystem.
72///
73/// ### `OpenOptions`
74/// This builder holds an instance of `std::fs::OpenOptions` which is used
75/// when creating the new file(s).
76///
77/// By default, the open options are set with `.write(true).create_new(true)`,
78/// so if the file already exists then an error will be thrown. This is to avoid accidentally
79/// overwriting files from other requests.
80///
81/// If you want to modify the options used to open the save file, you can use
82/// `mod_open_opts()`.
83///
84/// ### File Size and Count Limits
85/// You can set a size limit for individual fields with `size_limit()`, which takes either `u64`
86/// or `Option<u64>`.
87///
88/// You can also set the maximum number of fields to process with `count_limit()`, which
89/// takes either `u32` or `Option<u32>`. This only has an effect when using
90/// `SaveBuilder<[&mut] Multipart>`.
91///
92/// By default, these limits are set conservatively to limit the maximum memory and disk space
93/// usage of a single request. You should set `count_limit` specifically for each request endpoint
94/// based on the number of fields you're expecting (exactly to that number if you're not expecting
95/// duplicate fields).
96///
97/// ### Memory Threshold and Text Policy
98/// By default, small fields (a few kilobytes or smaller) will be read directly to memory
99/// without creating a file. This behavior is controlled by the `memory_threshold()` setter. You can
100/// *roughly* tune the maximum memory a single request uses by tuning
101/// `count_limit * memory_threshold`
102///
103/// If a field appears to contain text data (its content-type is `text/*` or it doesn't declare
104/// one), `SaveBuilder` can read it to a string instead of saving the raw bytes as long as it falls
105/// below the set `memory_threshold`.
106///
107/// By default, the behavior is to attempt to validate the data as UTF-8, falling back to saving
108/// just the bytes if the validation fails at any point. You can restore/ensure this behavior
109/// with the `try_text()` modifier.
110///
111/// Alternatively, you can use the `force_text()` modifier to make the save operation return
112/// an error when UTF-8 decoding fails, though this only holds true while the size is below
113/// `memory_threshold`. The `ignore_text()` modifier turns off UTF-8 validation altogether.
114///
115/// UTF-8 validation is performed incrementally (after every `BufRead::fill_buf()` call)
116/// to hopefully maximize throughput, instead of blocking while the field is read to completion
117/// and performing validation over the entire result at the end. (RFC: this could be a lot of
118/// unnecessary work if most fields end up being written to the filesystem, however, but this
119/// can be turned off with `ignore_text()` if it fits the use-case.)
120///
121/// ### Warning: Do **not** trust user input!
122/// It is a serious security risk to create files or directories with paths based on user input.
123/// A malicious user could craft a path which can be used to overwrite important files, such as
124/// web templates, static assets, Javascript files, database files, configuration files, etc.,
125/// if they are writable by the server process.
126///
127/// This can be mitigated somewhat by setting filesystem permissions as
128/// conservatively as possible and running the server under its own user with restricted
129/// permissions, but you should still not use user input directly as filesystem paths.
130/// If it is truly necessary, you should sanitize user input such that it cannot cause a path to be
131/// misinterpreted by the OS. Such functionality is outside the scope of this crate.
132#[must_use = "nothing saved to the filesystem yet"]
133pub struct SaveBuilder<S> {
134 savable: S,
135 open_opts: OpenOptions,
136 size_limit: u64,
137 count_limit: u32,
138 memory_threshold: u64,
139 text_policy: TextPolicy,
140}
141
142/// Common methods for whole requests as well as individual fields.
143impl<S> SaveBuilder<S> {
144 /// Implementation detail but not problematic to have accessible.
145 #[doc(hidden)]
146 pub fn new(savable: S) -> SaveBuilder<S> {
147 let mut open_opts = OpenOptions::new();
148 open_opts.write(true).create_new(true);
149
150 SaveBuilder {
151 savable,
152 open_opts,
153 // 8 MiB, on the conservative end compared to most frameworks
154 size_limit: 8 * 1024 * 1024,
155 // Arbitrary, I have no empirical data for this
156 count_limit: 256,
157 // 10KiB, used by Apache Commons
158 // https://commons.apache.org/proper/commons-fileupload/apidocs/org/apache/commons/fileupload/disk/DiskFileItemFactory.html
159 memory_threshold: 10 * 1024,
160 text_policy: TextPolicy::Try,
161 }
162 }
163
164 /// Set the maximum number of bytes to write out *per file*.
165 ///
166 /// Can be `u64` or `Option<u64>`. If `None` or `u64::MAX`, clears the limit.
167 pub fn size_limit<L: Into<Option<u64>>>(mut self, limit: L) -> Self {
168 self.size_limit = limit.into().unwrap_or(u64::MAX);
169 self
170 }
171
172 /// Modify the `OpenOptions` used to open any files for writing.
173 ///
174 /// The `write` flag will be reset to `true` after the closure returns. (It'd be pretty
175 /// pointless otherwise, right?)
176 pub fn mod_open_opts<F: FnOnce(&mut OpenOptions)>(mut self, opts_fn: F) -> Self {
177 opts_fn(&mut self.open_opts);
178 self.open_opts.write(true);
179 self
180 }
181
182 /// Set the threshold at which to switch from copying a field into memory to copying
183 /// it to disk.
184 ///
185 /// If `0`, forces fields to save directly to the filesystem.
186 /// If `u64::MAX`, effectively forces fields to always save to memory.
187 pub fn memory_threshold(self, memory_threshold: u64) -> Self {
188 Self { memory_threshold, ..self }
189 }
190
191 /// When encountering a field that is apparently text, try to read it to a string or fall
192 /// back to binary otherwise.
193 ///
194 /// If set for an individual field (`SaveBuilder<&mut MultipartData<_>>`), will
195 /// always attempt to decode text regardless of the field's `Content-Type`.
196 ///
197 /// Has no effect once `memory_threshold` has been reached.
198 pub fn try_text(self) -> Self {
199 Self { text_policy: TextPolicy::Try, ..self }
200 }
201
202 /// When encountering a field that is apparently text, read it to a string or return an error.
203 ///
204 /// If set for an individual field (`SaveBuilder<&mut MultipartData<_>>`), will
205 /// always attempt to decode text regardless of the field's `Content-Type`.
206 ///
207 /// (RFC: should this continue to validate UTF-8 when writing to the filesystem?)
208 pub fn force_text(self) -> Self {
209 Self { text_policy: TextPolicy::Force, ..self}
210 }
211
212 /// Don't try to read or validate any field data as UTF-8.
213 pub fn ignore_text(self) -> Self {
214 Self { text_policy: TextPolicy::Ignore, ..self }
215 }
216}
217
218/// Save API for whole multipart requests.
219impl<M> SaveBuilder<M> where M: ReadEntry {
220 /// Set the maximum number of fields to process.
221 ///
222 /// Can be `u32` or `Option<u32>`. If `None` or `u32::MAX`, clears the limit.
223 pub fn count_limit<L: Into<Option<u32>>>(mut self, count_limit: L) -> Self {
224 self.count_limit = count_limit.into().unwrap_or(u32::MAX);
225 self
226 }
227
228 /// Save all fields in the request using a new temporary directory prefixed with
229 /// `multipart-rs` in the OS temporary directory.
230 ///
231 /// For more options, create a `TempDir` yourself and pass it to `with_temp_dir()` instead.
232 ///
233 /// See `with_entries()` for more info.
234 ///
235 /// ### Note: Temporary
236 /// See `SaveDir` for more info (the type of `Entries::save_dir`).
237 pub fn temp(self) -> EntriesSaveResult<M> {
238 self.temp_with_prefix("multipart-rs")
239 }
240
241 /// Save all fields in the request using a new temporary directory with the given string
242 /// as a prefix in the OS temporary directory.
243 ///
244 /// For more options, create a `TempDir` yourself and pass it to `with_temp_dir()` instead.
245 ///
246 /// See `with_entries()` for more info.
247 ///
248 /// ### Note: Temporary
249 /// See `SaveDir` for more info (the type of `Entries::save_dir`).
250 pub fn temp_with_prefix(self, prefix: &str) -> EntriesSaveResult<M> {
251 match tempfile::Builder::new().prefix(prefix).tempdir() {
252 Ok(tempdir) => self.with_temp_dir(tempdir),
253 Err(e) => SaveResult::Error(e),
254 }
255 }
256
257 /// Save all fields in the request using the given `TempDir`.
258 ///
259 /// See `with_entries()` for more info.
260 ///
261 /// The `TempDir` is returned in the result under `Entries::save_dir`.
262 pub fn with_temp_dir(self, tempdir: TempDir) -> EntriesSaveResult<M> {
263 self.with_entries(Entries::new(SaveDir::Temp(tempdir)))
264 }
265
266 /// Save the file fields in the request to a new permanent directory with the given path.
267 ///
268 /// Any nonexistent directories in the path will be created.
269 ///
270 /// See `with_entries()` for more info.
271 pub fn with_dir<P: Into<PathBuf>>(self, dir: P) -> EntriesSaveResult<M> {
272 let dir = dir.into();
273
274 try_start!(create_dir_all(&dir));
275
276 self.with_entries(Entries::new(SaveDir::Perm(dir)))
277 }
278
279 /// Commence the save operation using the existing `Entries` instance.
280 ///
281 /// May be used to resume a saving operation after handling an error.
282 ///
283 /// If `count_limit` is set, only reads that many fields before returning an error.
284 /// If you wish to resume from `PartialReason::CountLimit`, simply remove some entries.
285 ///
286 /// Note that `PartialReason::CountLimit` will still be returned if the number of fields
287 /// reaches `u32::MAX`, but this would be an extremely degenerate case.
288 pub fn with_entries(self, mut entries: Entries) -> EntriesSaveResult<M> {
289 let SaveBuilder {
290 savable, open_opts, count_limit, size_limit,
291 memory_threshold, text_policy
292 } = self;
293
294 let mut res = ReadEntry::read_entry(savable);
295
296 let _ = entries.recount_fields();
297
298 let save_field = |field: &mut MultipartField<M>, entries: &Entries| {
299 let text_policy = if field.is_text() { text_policy } else { Ignore };
300
301 let mut saver = SaveBuilder {
302 savable: &mut field.data, open_opts: open_opts.clone(),
303 count_limit, size_limit, memory_threshold, text_policy
304 };
305
306 saver.with_dir(entries.save_dir.as_path())
307 };
308
309 while entries.fields_count < count_limit {
310 let mut field: MultipartField<M> = match res {
311 ReadEntryResult::Entry(field) => field,
312 ReadEntryResult::End(_) => return Full(entries), // normal exit point
313 ReadEntryResult::Error(_, e) => return Partial (
314 PartialEntries {
315 entries,
316 partial: None,
317 },
318 e.into(),
319 )
320 };
321
322 let (dest, reason) = match save_field(&mut field, &entries) {
323 Full(saved) => {
324 entries.push_field(field.headers, saved);
325 res = ReadEntry::read_entry(field.data.into_inner());
326 continue;
327 },
328 Partial(saved, reason) => (Some(saved), reason),
329 Error(error) => (None, PartialReason::IoError(error)),
330 };
331
332 return Partial(
333 PartialEntries {
334 entries,
335 partial: Some(PartialSavedField {
336 source: field,
337 dest,
338 }),
339 },
340 reason
341 );
342 }
343
344 Partial(
345 PartialEntries {
346 entries,
347 partial: None,
348 },
349 PartialReason::CountLimit
350 )
351 }
352}
353
354/// Save API for individual fields.
355impl<'m, M: 'm> SaveBuilder<&'m mut MultipartData<M>> where MultipartData<M>: BufRead {
356 /// Save the field data, potentially using a file with a random name in the
357 /// OS temporary directory.
358 ///
359 /// See `with_path()` for more details.
360 pub fn temp(&mut self) -> FieldSaveResult {
361 let path = env::temp_dir().join(rand_filename());
362 self.with_path(path)
363 }
364
365 /// Save the field data, potentially using a file with the given name in
366 /// the OS temporary directory.
367 ///
368 /// See `with_path()` for more details.
369 pub fn with_filename(&mut self, filename: &str) -> FieldSaveResult {
370 let mut tempdir = env::temp_dir();
371 tempdir.set_file_name(filename);
372
373 self.with_path(tempdir)
374 }
375
376 /// Save the field data, potentially using a file with a random alphanumeric name
377 /// in the given directory.
378 ///
379 /// See `with_path()` for more details.
380 pub fn with_dir<P: AsRef<Path>>(&mut self, dir: P) -> FieldSaveResult {
381 let path = dir.as_ref().join(rand_filename());
382 self.with_path(path)
383 }
384
385 /// Save the field data, potentially using a file with the given path.
386 ///
387 /// Creates any missing directories in the path (RFC: skip this step?).
388 /// Uses the contained `OpenOptions` to create the file.
389 /// Truncates the file to the given `size_limit`, if set.
390 ///
391 /// The no directories or files will be created until the set `memory_threshold` is reached.
392 /// If `size_limit` is set and less than or equal to `memory_threshold`,
393 /// then the disk will never be touched.
394 pub fn with_path<P: Into<PathBuf>>(&mut self, path: P) -> FieldSaveResult {
395 let bytes = if self.text_policy != Ignore {
396 let (text, reason) = try_partial!(self.save_text());
397 match reason {
398 SizeLimit if !self.cmp_size_limit(text.len()) => text.into_bytes(),
399 Utf8Error(_) if self.text_policy != Force => text.into_bytes(),
400 other => return Partial(text.into(), other),
401 }
402 } else {
403 Vec::new()
404 };
405
406 let (bytes, reason) = try_partial!(self.save_mem(bytes));
407
408 match reason {
409 SizeLimit if !self.cmp_size_limit(bytes.len()) => (),
410 other => return Partial(bytes.into(), other)
411 }
412
413 let path = path.into();
414
415 let mut file = match create_dir_all(&path).and_then(|_| self.open_opts.open(&path)) {
416 Ok(file) => file,
417 Err(e) => return Error(e),
418 };
419
420 let data = try_full!(
421 try_write_all(&bytes, &mut file)
422 .map(move |size| SavedData::File(path, size as u64))
423 );
424
425 self.write_to(file).map(move |written| data.add_size(written))
426 }
427
428
429 /// Write out the field data to `dest`, truncating if a limit was set.
430 ///
431 /// Returns the number of bytes copied, and whether or not the limit was reached
432 /// (tested by `MultipartFile::fill_buf().is_empty()` so no bytes are consumed).
433 ///
434 /// Retries on interrupts.
435 pub fn write_to<W: Write>(&mut self, mut dest: W) -> SaveResult<u64, u64> {
436 if self.size_limit < u64::MAX {
437 try_copy_limited(&mut self.savable, |buf| try_write_all(buf, &mut dest), self.size_limit)
438 } else {
439 try_read_buf(&mut self.savable, |buf| try_write_all(buf, &mut dest))
440 }
441 }
442
443 fn save_mem(&mut self, mut bytes: Vec<u8>) -> SaveResult<Vec<u8>, Vec<u8>> {
444 let pre_read = bytes.len() as u64;
445 match self.read_mem(|buf| { bytes.extend_from_slice(buf); Full(buf.len()) }, pre_read) {
446 Full(_) => Full(bytes),
447 Partial(_, reason) => Partial(bytes, reason),
448 Error(e) => if !bytes.is_empty() { Partial(bytes, e.into()) }
449 else { Error(e) }
450 }
451
452 }
453
454 fn save_text(&mut self) -> SaveResult<String, String> {
455 let mut string = String::new();
456
457 // incrementally validate UTF-8 to do as much work as possible during network activity
458 let res = self.read_mem(|buf| {
459 match str::from_utf8(buf) {
460 Ok(s) => { string.push_str(s); Full(buf.len()) },
461 // buffer should always be bigger
462 Err(e) => if buf.len() < 4 {
463 Partial(0, e.into())
464 } else {
465 string.push_str(str::from_utf8(&buf[..e.valid_up_to()]).unwrap());
466 Full(e.valid_up_to())
467 }
468 }
469 }, 0);
470
471 match res {
472 Full(_) => Full(string),
473 Partial(_, reason) => Partial(string, reason),
474 Error(e) => Error(e),
475 }
476 }
477
478 fn read_mem<Wb: FnMut(&[u8]) -> SaveResult<usize, usize>>(&mut self, with_buf: Wb, pre_read: u64) -> SaveResult<u64, u64> {
479 let limit = cmp::min(self.size_limit, self.memory_threshold)
480 .saturating_sub(pre_read);
481 try_copy_limited(&mut self.savable, with_buf, limit)
482 }
483
484 fn cmp_size_limit(&self, size: usize) -> bool {
485 size as u64 >= self.size_limit
486 }
487}
488
489/// A field that has been saved (to memory or disk) from a multipart request.
490#[derive(Debug)]
491pub struct SavedField {
492 /// The headers of the field that was saved.
493 pub headers: FieldHeaders,
494 /// The data of the field which may reside in memory or on disk.
495 pub data: SavedData,
496}
497
498/// A saved field's data container (in memory or on disk)
499#[derive(Debug)]
500pub enum SavedData {
501 /// Validated UTF-8 text data.
502 Text(String),
503 /// Binary data.
504 Bytes(Vec<u8>),
505 /// A path to a file on the filesystem and its size as written by `multipart`.
506 File(PathBuf, u64),
507}
508
509impl SavedData {
510 /// Get an adapter for this data which implements `Read`.
511 ///
512 /// If the data is in a file, the file is opened in read-only mode.
513 pub fn readable(&self) -> io::Result<DataReader> {
514 use self::SavedData::*;
515
516 match *self {
517 Text(ref text) => Ok(DataReader::Bytes(text.as_ref())),
518 Bytes(ref bytes) => Ok(DataReader::Bytes(bytes)),
519 File(ref path, _) => Ok(DataReader::File(BufReader::new(fs::File::open(path)?))),
520 }
521 }
522
523 /// Get the size of the data, in memory or on disk.
524 ///
525 /// #### Note
526 /// The size on disk may not match the size of the file if it is externally modified.
527 pub fn size(&self) -> u64 {
528 use self::SavedData::*;
529
530 match *self {
531 Text(ref text) => text.len() as u64,
532 Bytes(ref bytes) => bytes.len() as u64,
533 File(_, size) => size,
534 }
535 }
536
537 /// Returns `true` if the data is known to be in memory (`Text | Bytes`)
538 pub fn is_memory(&self) -> bool {
539 use self::SavedData::*;
540
541 match *self {
542 Text(_) | Bytes(_) => true,
543 File(_, _) => false,
544 }
545 }
546
547 fn add_size(self, add: u64) -> Self {
548 use self::SavedData::File;
549
550 match self {
551 File(path, size) => File(path, size.saturating_add(add)),
552 other => other
553 }
554 }
555}
556
557impl From<String> for SavedData {
558 fn from(s: String) -> Self {
559 SavedData::Text(s)
560 }
561}
562
563impl From<Vec<u8>> for SavedData {
564 fn from(b: Vec<u8>) -> Self {
565 SavedData::Bytes(b)
566 }
567}
568
569/// A `Read` (and `BufRead`) adapter for `SavedData`
570pub enum DataReader<'a> {
571 /// In-memory data source (`SavedData::Bytes | Text`)
572 Bytes(&'a [u8]),
573 /// On-disk data source (`SavedData::File`)
574 File(BufReader<File>),
575}
576
577impl<'a> Read for DataReader<'a> {
578 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
579 use self::DataReader::*;
580
581 match *self {
582 Bytes(ref mut bytes) => bytes.read(buf),
583 File(ref mut file) => file.read(buf),
584 }
585 }
586}
587
588impl<'a> BufRead for DataReader<'a> {
589 fn fill_buf(&mut self) -> io::Result<&[u8]> {
590 use self::DataReader::*;
591
592 match *self {
593 Bytes(ref mut bytes) => bytes.fill_buf(),
594 File(ref mut file) => file.fill_buf(),
595 }
596 }
597
598 fn consume(&mut self, amt: usize) {
599 use self::DataReader::*;
600
601 match *self {
602 Bytes(ref mut bytes) => bytes.consume(amt),
603 File(ref mut file) => file.consume(amt),
604 }
605 }
606}
607
608/// A result of `Multipart::save()`.
609#[derive(Debug)]
610pub struct Entries {
611 /// The fields of the multipart request, mapped by field name -> value.
612 ///
613 /// A field name may have multiple actual fields associated with it, but the most
614 /// common case is a single field.
615 ///
616 /// Each vector is guaranteed not to be empty unless externally modified.
617 // Even though individual fields might only have one entry, it's better to limit the
618 // size of a value type in `HashMap` to improve cache efficiency in lookups.
619 pub fields: HashMap<Arc<str>, Vec<SavedField>>,
620 /// The directory that the entries in `fields` were saved into.
621 pub save_dir: SaveDir,
622 fields_count: u32,
623}
624
625impl Entries {
626 /// Create a new `Entries` with the given `SaveDir`
627 pub fn new(save_dir: SaveDir) -> Self {
628 Entries {
629 fields: HashMap::new(),
630 save_dir,
631 fields_count: 0,
632 }
633 }
634
635 /// Returns `true` if `fields` is empty, `false` otherwise.
636 pub fn is_empty(&self) -> bool {
637 self.fields.is_empty()
638 }
639
640 /// The number of actual fields contained within this `Entries`.
641 ///
642 /// Effectively `self.fields.values().map(Vec::len).sum()` but maintained separately.
643 ///
644 /// ## Note
645 /// This will be incorrect if `fields` is modified externally. Call `recount_fields()`
646 /// to get the correct count.
647 pub fn fields_count(&self) -> u32 {
648 self.fields_count
649 }
650
651 /// Sum the number of fields in this `Entries` and then return the updated value.
652 pub fn recount_fields(&mut self) -> u32 {
653 let fields_count = self.fields.values().map(Vec::len).sum();
654 // saturating cast
655 self.fields_count = cmp::min(u32::MAX as usize, fields_count) as u32;
656 self.fields_count
657 }
658
659 fn push_field(&mut self, mut headers: FieldHeaders, data: SavedData) {
660 use std::collections::hash_map::Entry::*;
661
662 match self.fields.entry(headers.name.clone()) {
663 Vacant(vacant) => { vacant.insert(vec![SavedField { headers, data }]); },
664 Occupied(occupied) => {
665 // dedup the field name by reusing the key's `Arc`
666 headers.name = occupied.key().clone();
667 occupied.into_mut().push({ SavedField { headers, data }});
668 },
669 }
670
671 self.fields_count = self.fields_count.saturating_add(1);
672 }
673
674 /// Print all fields and their contents to stdout. Mostly for testing purposes.
675 pub fn print_debug(&self) -> io::Result<()> {
676 let stdout = io::stdout();
677 let stdout_lock = stdout.lock();
678 self.write_debug(stdout_lock)
679 }
680
681 /// Write all fields and their contents to the given output. Mostly for testing purposes.
682 pub fn write_debug<W: Write>(&self, mut writer: W) -> io::Result<()> {
683 for (name, entries) in &self.fields {
684 writeln!(writer, "Field {:?} has {} entries:", name, entries.len())?;
685
686 for (idx, field) in entries.iter().enumerate() {
687 let mut data = field.data.readable()?;
688 let headers = &field.headers;
689 writeln!(writer, "{}: {:?} ({:?}):", idx, headers.filename, headers.content_type)?;
690 io::copy(&mut data, &mut writer)?;
691 }
692 }
693
694 Ok(())
695 }
696}
697
698/// The save directory for `Entries`. May be temporary (delete-on-drop) or permanent.
699#[derive(Debug)]
700pub enum SaveDir {
701 /// This directory is temporary and will be deleted, along with its contents, when this wrapper
702 /// is dropped.
703 Temp(TempDir),
704 /// This directory is permanent and will be left on the filesystem when this wrapper is dropped.
705 ///
706 /// **N.B.** If this directory is in the OS temporary directory then it may still be
707 /// deleted at any time.
708 Perm(PathBuf),
709}
710
711impl SaveDir {
712 /// Get the path of this directory, either temporary or permanent.
713 pub fn as_path(&self) -> &Path {
714 use self::SaveDir::*;
715 match *self {
716 Temp(ref tempdir) => tempdir.path(),
717 Perm(ref pathbuf) => &*pathbuf,
718 }
719 }
720
721 /// Returns `true` if this is a temporary directory which will be deleted on-drop.
722 pub fn is_temporary(&self) -> bool {
723 use self::SaveDir::*;
724 match *self {
725 Temp(_) => true,
726 Perm(_) => false,
727 }
728 }
729
730 /// Unwrap the `PathBuf` from `self`; if this is a temporary directory,
731 /// it will be converted to a permanent one.
732 pub fn into_path(self) -> PathBuf {
733 use self::SaveDir::*;
734
735 match self {
736 Temp(tempdir) => tempdir.into_path(),
737 Perm(pathbuf) => pathbuf,
738 }
739 }
740
741 /// If this `SaveDir` is temporary, convert it to permanent.
742 /// This is a no-op if it already is permanent.
743 ///
744 /// ### Warning: Potential Data Loss
745 /// Even though this will prevent deletion on-drop, the temporary folder on most OSes
746 /// (where this directory is created by default) can be automatically cleared by the OS at any
747 /// time, usually on reboot or when free space is low.
748 ///
749 /// It is recommended that you relocate the files from a request which you want to keep to a
750 /// permanent folder on the filesystem.
751 pub fn keep(&mut self) {
752 use self::SaveDir::*;
753 *self = match mem::replace(self, Perm(PathBuf::new())) {
754 Temp(tempdir) => Perm(tempdir.into_path()),
755 old_self => old_self,
756 };
757 }
758
759 /// Delete this directory and its contents, regardless of its permanence.
760 ///
761 /// ### Warning: Potential Data Loss
762 /// This is very likely irreversible, depending on the OS implementation.
763 ///
764 /// Files deleted programmatically are deleted directly from disk, as compared to most file
765 /// manager applications which use a staging area from which deleted files can be safely
766 /// recovered (i.e. Windows' Recycle Bin, OS X's Trash Can, etc.).
767 pub fn delete(self) -> io::Result<()> {
768 use self::SaveDir::*;
769 match self {
770 Temp(tempdir) => tempdir.close(),
771 Perm(pathbuf) => fs::remove_dir_all(&pathbuf),
772 }
773 }
774}
775
776impl AsRef<Path> for SaveDir {
777 fn as_ref(&self) -> &Path {
778 self.as_path()
779 }
780}
781
782/// The reason the save operation quit partway through.
783#[derive(Debug)]
784pub enum PartialReason {
785 /// The count limit for files in the request was hit.
786 ///
787 /// The associated file has not been saved to the filesystem.
788 CountLimit,
789 /// The size limit for an individual file was hit.
790 ///
791 /// The file was partially written to the filesystem.
792 SizeLimit,
793 /// An error occurred during the operation.
794 IoError(io::Error),
795 /// An error returned from validating a field as UTF-8 due to `SaveBuilder::force_text()`
796 Utf8Error(str::Utf8Error),
797}
798
799impl From<io::Error> for PartialReason {
800 fn from(e: io::Error) -> Self {
801 IoError(e)
802 }
803}
804
805impl From<str::Utf8Error> for PartialReason {
806 fn from(e: str::Utf8Error) -> Self {
807 Utf8Error(e)
808 }
809}
810
811impl PartialReason {
812 /// Return `io::Error` in the `IoError` case or panic otherwise.
813 pub fn unwrap_err(self) -> io::Error {
814 self.expect_err("`PartialReason` was not `IoError`")
815 }
816
817 /// Return `io::Error` in the `IoError` case or panic with the given
818 /// message otherwise.
819 pub fn expect_err(self, msg: &str) -> io::Error {
820 match self {
821 PartialReason::IoError(e) => e,
822 _ => panic!("{}: {:?}", msg, self),
823 }
824 }
825}
826
827/// The field that was being read when the save operation quit.
828///
829/// May be partially saved to the filesystem if `dest` is `Some`.
830#[derive(Debug)]
831pub struct PartialSavedField<M: ReadEntry> {
832 /// The field that was being read.
833 ///
834 /// May be partially read if `dest` is `Some`.
835 pub source: MultipartField<M>,
836 /// The data from the saving operation, if it got that far.
837 pub dest: Option<SavedData>,
838}
839
840/// The partial result type for `Multipart::save*()`.
841///
842/// Contains the successfully saved entries as well as the partially
843/// saved file that was in the process of being read when the error occurred,
844/// if applicable.
845#[derive(Debug)]
846pub struct PartialEntries<M: ReadEntry> {
847 /// The entries that were saved successfully.
848 pub entries: Entries,
849 /// The field that was in the process of being read. `None` if the error
850 /// occurred between entries.
851 pub partial: Option<PartialSavedField<M>>,
852}
853
854/// Discards `partial`
855impl<M: ReadEntry> Into<Entries> for PartialEntries<M> {
856 fn into(self) -> Entries {
857 self.entries
858 }
859}
860
861impl<M: ReadEntry> PartialEntries<M> {
862 /// If `partial` is present and contains a `SavedFile` then just
863 /// add it to the `Entries` instance and return it.
864 ///
865 /// Otherwise, returns `self.entries`
866 pub fn keep_partial(mut self) -> Entries {
867 if let Some(partial) = self.partial {
868 if let Some(saved) = partial.dest {
869 self.entries.push_field(partial.source.headers, saved);
870 }
871 }
872
873 self.entries
874 }
875}
876
877/// The ternary result type used for the `SaveBuilder<_>` API.
878#[derive(Debug)]
879pub enum SaveResult<Success, Partial> {
880 /// The operation was a total success. Contained is the complete result.
881 Full(Success),
882 /// The operation quit partway through. Included is the partial
883 /// result along with the reason.
884 Partial(Partial, PartialReason),
885 /// An error occurred at the start of the operation, before anything was done.
886 Error(io::Error),
887}
888
889/// Shorthand result for methods that return `Entries`
890pub type EntriesSaveResult<M> = SaveResult<Entries, PartialEntries<M>>;
891
892/// Shorthand result for methods that return `FieldData`s.
893///
894/// The `MultipartData` is not provided here because it is not necessary to return
895/// a borrow when the owned version is probably in the same scope. This hopefully
896/// saves some headache with the borrow-checker.
897pub type FieldSaveResult = SaveResult<SavedData, SavedData>;
898
899impl<M: ReadEntry> EntriesSaveResult<M> {
900 /// Take the `Entries` from `self`, if applicable, and discarding
901 /// the error, if any.
902 pub fn into_entries(self) -> Option<Entries> {
903 match self {
904 Full(entries) | Partial(PartialEntries { entries, .. }, _) => Some(entries),
905 Error(_) => None,
906 }
907 }
908}
909
910impl<S, P> SaveResult<S, P> where P: Into<S> {
911 /// Convert `self` to `Option<S>`; there may still have been an error.
912 pub fn okish(self) -> Option<S> {
913 self.into_opt_both().0
914 }
915
916 /// Map the `Full` or `Partial` values to a new type, retaining the reason
917 /// in the `Partial` case.
918 pub fn map<T, Map>(self, map: Map) -> SaveResult<T, T> where Map: FnOnce(S) -> T {
919 match self {
920 Full(full) => Full(map(full)),
921 Partial(partial, reason) => Partial(map(partial.into()), reason),
922 Error(e) => Error(e),
923 }
924 }
925
926 /// Decompose `self` to `(Option<S>, Option<io::Error>)`
927 pub fn into_opt_both(self) -> (Option<S>, Option<io::Error>) {
928 match self {
929 Full(full) => (Some(full), None),
930 Partial(partial, IoError(e)) => (Some(partial.into()), Some(e)),
931 Partial(partial, _) => (Some(partial.into()), None),
932 Error(error) => (None, Some(error)),
933 }
934 }
935
936 /// Map `self` to an `io::Result`, discarding the error in the `Partial` case.
937 pub fn into_result(self) -> io::Result<S> {
938 match self {
939 Full(entries) => Ok(entries),
940 Partial(partial, _) => Ok(partial.into()),
941 Error(error) => Err(error),
942 }
943 }
944
945 /// Pessimistic version of `into_result()` which will return an error even
946 /// for the `Partial` case.
947 ///
948 /// ### Note: Possible Storage Leak
949 /// It's generally not a good idea to ignore the `Partial` case, as there may still be a
950 /// partially written file on-disk. If you're not using a temporary directory
951 /// (OS-managed or via `TempDir`) then partially written files will remain on-disk until
952 /// explicitly removed which could result in excessive disk usage if not monitored closely.
953 pub fn into_result_strict(self) -> io::Result<S> {
954 match self {
955 Full(entries) => Ok(entries),
956 Partial(_, PartialReason::IoError(e)) | Error(e) => Err(e),
957 Partial(partial, _) => Ok(partial.into()),
958 }
959 }
960}
961
962fn create_dir_all(path: &Path) -> io::Result<()> {
963 if let Some(parent) = path.parent() {
964 fs::create_dir_all(parent)
965 } else {
966 // RFC: return an error instead?
967 warn!("Attempting to save file in what looks like a root directory. File path: {:?}", path);
968 Ok(())
969 }
970}
971
972fn try_copy_limited<R: BufRead, Wb: FnMut(&[u8]) -> SaveResult<usize, usize>>(src: R, mut with_buf: Wb, limit: u64) -> SaveResult<u64, u64> {
973 let mut copied = 0u64;
974 try_read_buf(src, |buf| {
975 let new_copied = copied.saturating_add(buf.len() as u64);
976 if new_copied > limit { return Partial(0, PartialReason::SizeLimit) }
977 copied = new_copied;
978
979 with_buf(buf)
980 })
981}
982
983fn try_read_buf<R: BufRead, Wb: FnMut(&[u8]) -> SaveResult<usize, usize>>(mut src: R, mut with_buf: Wb) -> SaveResult<u64, u64> {
984 let mut total_copied = 0u64;
985
986 macro_rules! try_here (
987 ($try:expr) => (
988 match $try {
989 Ok(val) => val,
990 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
991 Err(e) => return if total_copied == 0 { Error(e) }
992 else { Partial(total_copied, e.into()) },
993 }
994 )
995 );
996
997 loop {
998 let res = {
999 let buf = try_here!(src.fill_buf());
1000 if buf.is_empty() { break; }
1001 with_buf(buf)
1002 };
1003
1004 match res {
1005 Full(copied) => { src.consume(copied); total_copied += copied as u64; }
1006 Partial(copied, reason) => {
1007 src.consume(copied); total_copied += copied as u64;
1008 return Partial(total_copied, reason);
1009 },
1010 Error(err) => {
1011 return Partial(total_copied, err.into());
1012 }
1013 }
1014 }
1015
1016 Full(total_copied)
1017}
1018
1019fn try_write_all<W: Write>(mut buf: &[u8], mut dest: W) -> SaveResult<usize, usize> {
1020 let mut total_copied = 0;
1021
1022 macro_rules! try_here (
1023 ($try:expr) => (
1024 match $try {
1025 Ok(val) => val,
1026 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
1027 Err(e) => return if total_copied == 0 { Error(e) }
1028 else { Partial(total_copied, e.into()) },
1029 }
1030 )
1031 );
1032
1033 while !buf.is_empty() {
1034 match try_here!(dest.write(buf)) {
1035 0 => try_here!(Err(io::Error::new(io::ErrorKind::WriteZero,
1036 "failed to write whole buffer"))),
1037 copied => {
1038 buf = &buf[copied..];
1039 total_copied += copied;
1040 },
1041 }
1042 }
1043
1044 Full(total_copied)
1045}