multipart2/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::buffer_redux::BufReader;
10
11pub use tempfile::TempDir;
12
13use std::collections::HashMap;
14use std::fs::{self, File, OpenOptions};
15use std::io::prelude::*;
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, MultipartData, MultipartField, ReadEntry, ReadEntryResult};
22
23use self::PartialReason::*;
24use self::SaveResult::*;
25use self::TextPolicy::*;
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 {
189 memory_threshold,
190 ..self
191 }
192 }
193
194 /// When encountering a field that is apparently text, try to read it to a string or fall
195 /// back to binary otherwise.
196 ///
197 /// If set for an individual field (`SaveBuilder<&mut MultipartData<_>>`), will
198 /// always attempt to decode text regardless of the field's `Content-Type`.
199 ///
200 /// Has no effect once `memory_threshold` has been reached.
201 pub fn try_text(self) -> Self {
202 Self {
203 text_policy: TextPolicy::Try,
204 ..self
205 }
206 }
207
208 /// When encountering a field that is apparently text, read it to a string or return an error.
209 ///
210 /// If set for an individual field (`SaveBuilder<&mut MultipartData<_>>`), will
211 /// always attempt to decode text regardless of the field's `Content-Type`.
212 ///
213 /// (RFC: should this continue to validate UTF-8 when writing to the filesystem?)
214 pub fn force_text(self) -> Self {
215 Self {
216 text_policy: TextPolicy::Force,
217 ..self
218 }
219 }
220
221 /// Don't try to read or validate any field data as UTF-8.
222 pub fn ignore_text(self) -> Self {
223 Self {
224 text_policy: TextPolicy::Ignore,
225 ..self
226 }
227 }
228}
229
230/// Save API for whole multipart requests.
231impl<M> SaveBuilder<M>
232where
233 M: ReadEntry,
234{
235 /// Set the maximum number of fields to process.
236 ///
237 /// Can be `u32` or `Option<u32>`. If `None` or `u32::MAX`, clears the limit.
238 pub fn count_limit<L: Into<Option<u32>>>(mut self, count_limit: L) -> Self {
239 self.count_limit = count_limit.into().unwrap_or(u32::MAX);
240 self
241 }
242
243 /// Save all fields in the request using a new temporary directory prefixed with
244 /// `multipart-rs` in the OS temporary directory.
245 ///
246 /// For more options, create a `TempDir` yourself and pass it to `with_temp_dir()` instead.
247 ///
248 /// See `with_entries()` for more info.
249 ///
250 /// ### Note: Temporary
251 /// See `SaveDir` for more info (the type of `Entries::save_dir`).
252 pub fn temp(self) -> EntriesSaveResult<M> {
253 self.temp_with_prefix("multipart-rs")
254 }
255
256 /// Save all fields in the request using a new temporary directory with the given string
257 /// as a prefix in the OS temporary directory.
258 ///
259 /// For more options, create a `TempDir` yourself and pass it to `with_temp_dir()` instead.
260 ///
261 /// See `with_entries()` for more info.
262 ///
263 /// ### Note: Temporary
264 /// See `SaveDir` for more info (the type of `Entries::save_dir`).
265 pub fn temp_with_prefix(self, prefix: &str) -> EntriesSaveResult<M> {
266 match tempfile::Builder::new().prefix(prefix).tempdir() {
267 Ok(tempdir) => self.with_temp_dir(tempdir),
268 Err(e) => SaveResult::Error(e),
269 }
270 }
271
272 /// Save all fields in the request using the given `TempDir`.
273 ///
274 /// See `with_entries()` for more info.
275 ///
276 /// The `TempDir` is returned in the result under `Entries::save_dir`.
277 pub fn with_temp_dir(self, tempdir: TempDir) -> EntriesSaveResult<M> {
278 self.with_entries(Entries::new(SaveDir::Temp(tempdir)))
279 }
280
281 /// Save the file fields in the request to a new permanent directory with the given path.
282 ///
283 /// Any nonexistent directories in the path will be created.
284 ///
285 /// See `with_entries()` for more info.
286 pub fn with_dir<P: Into<PathBuf>>(self, dir: P) -> EntriesSaveResult<M> {
287 let dir = dir.into();
288
289 try_start!(create_dir_all(&dir));
290
291 self.with_entries(Entries::new(SaveDir::Perm(dir)))
292 }
293
294 /// Commence the save operation using the existing `Entries` instance.
295 ///
296 /// May be used to resume a saving operation after handling an error.
297 ///
298 /// If `count_limit` is set, only reads that many fields before returning an error.
299 /// If you wish to resume from `PartialReason::CountLimit`, simply remove some entries.
300 ///
301 /// Note that `PartialReason::CountLimit` will still be returned if the number of fields
302 /// reaches `u32::MAX`, but this would be an extremely degenerate case.
303 pub fn with_entries(self, mut entries: Entries) -> EntriesSaveResult<M> {
304 let SaveBuilder {
305 savable,
306 open_opts,
307 count_limit,
308 size_limit,
309 memory_threshold,
310 text_policy,
311 } = self;
312
313 let mut res = ReadEntry::read_entry(savable);
314
315 let _ = entries.recount_fields();
316
317 let save_field = |field: &mut MultipartField<M>, entries: &Entries| {
318 let text_policy = if field.is_text() { text_policy } else { Ignore };
319
320 let mut saver = SaveBuilder {
321 savable: &mut field.data,
322 open_opts: open_opts.clone(),
323 count_limit,
324 size_limit,
325 memory_threshold,
326 text_policy,
327 };
328
329 saver.with_dir(entries.save_dir.as_path())
330 };
331
332 while entries.fields_count < count_limit {
333 let mut field: MultipartField<M> = match res {
334 ReadEntryResult::Entry(field) => field,
335 ReadEntryResult::End(_) => return Full(entries), // normal exit point
336 ReadEntryResult::Error(_, e) => {
337 return Partial(
338 PartialEntries {
339 entries,
340 partial: None,
341 },
342 e.into(),
343 )
344 }
345 };
346
347 let (dest, reason) = match save_field(&mut field, &entries) {
348 Full(saved) => {
349 entries.push_field(field.headers, saved);
350 res = ReadEntry::read_entry(field.data.into_inner());
351 continue;
352 }
353 Partial(saved, reason) => (Some(saved), reason),
354 Error(error) => (None, PartialReason::IoError(error)),
355 };
356
357 return Partial(
358 PartialEntries {
359 entries,
360 partial: Some(PartialSavedField {
361 source: field,
362 dest,
363 }),
364 },
365 reason,
366 );
367 }
368
369 Partial(
370 PartialEntries {
371 entries,
372 partial: None,
373 },
374 PartialReason::CountLimit,
375 )
376 }
377}
378
379/// Save API for individual fields.
380impl<'m, M: 'm> SaveBuilder<&'m mut MultipartData<M>>
381where
382 MultipartData<M>: BufRead,
383{
384 /// Save the field data, potentially using a file with a random name in the
385 /// OS temporary directory.
386 ///
387 /// See `with_path()` for more details.
388 pub fn temp(&mut self) -> FieldSaveResult {
389 let path = env::temp_dir().join(rand_filename());
390 self.with_path(path)
391 }
392
393 /// Save the field data, potentially using a file with the given name in
394 /// the OS temporary directory.
395 ///
396 /// See `with_path()` for more details.
397 pub fn with_filename(&mut self, filename: &str) -> FieldSaveResult {
398 let mut tempdir = env::temp_dir();
399 tempdir.set_file_name(filename);
400
401 self.with_path(tempdir)
402 }
403
404 /// Save the field data, potentially using a file with a random alphanumeric name
405 /// in the given directory.
406 ///
407 /// See `with_path()` for more details.
408 pub fn with_dir<P: AsRef<Path>>(&mut self, dir: P) -> FieldSaveResult {
409 let path = dir.as_ref().join(rand_filename());
410 self.with_path(path)
411 }
412
413 /// Save the field data, potentially using a file with the given path.
414 ///
415 /// Creates any missing directories in the path (RFC: skip this step?).
416 /// Uses the contained `OpenOptions` to create the file.
417 /// Truncates the file to the given `size_limit`, if set.
418 ///
419 /// The no directories or files will be created until the set `memory_threshold` is reached.
420 /// If `size_limit` is set and less than or equal to `memory_threshold`,
421 /// then the disk will never be touched.
422 pub fn with_path<P: Into<PathBuf>>(&mut self, path: P) -> FieldSaveResult {
423 let bytes = if self.text_policy != Ignore {
424 let (text, reason) = try_partial!(self.save_text());
425 match reason {
426 SizeLimit if !self.cmp_size_limit(text.len()) => text.into_bytes(),
427 Utf8Error(_) if self.text_policy != Force => text.into_bytes(),
428 other => return Partial(text.into(), other),
429 }
430 } else {
431 Vec::new()
432 };
433
434 let (bytes, reason) = try_partial!(self.save_mem(bytes));
435
436 match reason {
437 SizeLimit if !self.cmp_size_limit(bytes.len()) => (),
438 other => return Partial(bytes.into(), other),
439 }
440
441 let path = path.into();
442
443 let mut file = match create_dir_all(&path).and_then(|_| self.open_opts.open(&path)) {
444 Ok(file) => file,
445 Err(e) => return Error(e),
446 };
447
448 let data =
449 try_full!(try_write_all(&bytes, &mut file)
450 .map(move |size| SavedData::File(path, size as u64)));
451
452 self.write_to(file)
453 .map(move |written| data.add_size(written))
454 }
455
456 /// Write out the field data to `dest`, truncating if a limit was set.
457 ///
458 /// Returns the number of bytes copied, and whether or not the limit was reached
459 /// (tested by `MultipartFile::fill_buf().is_empty()` so no bytes are consumed).
460 ///
461 /// Retries on interrupts.
462 pub fn write_to<W: Write>(&mut self, mut dest: W) -> SaveResult<u64, u64> {
463 if self.size_limit < u64::MAX {
464 try_copy_limited(
465 &mut self.savable,
466 |buf| try_write_all(buf, &mut dest),
467 self.size_limit,
468 )
469 } else {
470 try_read_buf(&mut self.savable, |buf| try_write_all(buf, &mut dest))
471 }
472 }
473
474 fn save_mem(&mut self, mut bytes: Vec<u8>) -> SaveResult<Vec<u8>, Vec<u8>> {
475 let pre_read = bytes.len() as u64;
476 match self.read_mem(
477 |buf| {
478 bytes.extend_from_slice(buf);
479 Full(buf.len())
480 },
481 pre_read,
482 ) {
483 Full(_) => Full(bytes),
484 Partial(_, reason) => Partial(bytes, reason),
485 Error(e) => {
486 if !bytes.is_empty() {
487 Partial(bytes, e.into())
488 } else {
489 Error(e)
490 }
491 }
492 }
493 }
494
495 fn save_text(&mut self) -> SaveResult<String, String> {
496 let mut string = String::new();
497
498 // incrementally validate UTF-8 to do as much work as possible during network activity
499 let res = self.read_mem(
500 |buf| {
501 match str::from_utf8(buf) {
502 Ok(s) => {
503 string.push_str(s);
504 Full(buf.len())
505 }
506 // buffer should always be bigger
507 Err(e) => {
508 if buf.len() < 4 {
509 Partial(0, e.into())
510 } else {
511 string.push_str(str::from_utf8(&buf[..e.valid_up_to()]).unwrap());
512 Full(e.valid_up_to())
513 }
514 }
515 }
516 },
517 0,
518 );
519
520 match res {
521 Full(_) => Full(string),
522 Partial(_, reason) => Partial(string, reason),
523 Error(e) => Error(e),
524 }
525 }
526
527 fn read_mem<Wb: FnMut(&[u8]) -> SaveResult<usize, usize>>(
528 &mut self,
529 with_buf: Wb,
530 pre_read: u64,
531 ) -> SaveResult<u64, u64> {
532 let limit = cmp::min(self.size_limit, self.memory_threshold).saturating_sub(pre_read);
533 try_copy_limited(&mut self.savable, with_buf, limit)
534 }
535
536 fn cmp_size_limit(&self, size: usize) -> bool {
537 size as u64 >= self.size_limit
538 }
539}
540
541/// A field that has been saved (to memory or disk) from a multipart request.
542#[derive(Debug)]
543pub struct SavedField {
544 /// The headers of the field that was saved.
545 pub headers: FieldHeaders,
546 /// The data of the field which may reside in memory or on disk.
547 pub data: SavedData,
548}
549
550/// A saved field's data container (in memory or on disk)
551#[derive(Debug)]
552pub enum SavedData {
553 /// Validated UTF-8 text data.
554 Text(String),
555 /// Binary data.
556 Bytes(Vec<u8>),
557 /// A path to a file on the filesystem and its size as written by `multipart`.
558 File(PathBuf, u64),
559}
560
561impl SavedData {
562 /// Get an adapter for this data which implements `Read`.
563 ///
564 /// If the data is in a file, the file is opened in read-only mode.
565 pub fn readable(&self) -> io::Result<DataReader> {
566 use self::SavedData::*;
567
568 match *self {
569 Text(ref text) => Ok(DataReader::Bytes(text.as_ref())),
570 Bytes(ref bytes) => Ok(DataReader::Bytes(bytes)),
571 File(ref path, _) => Ok(DataReader::File(BufReader::new(fs::File::open(path)?))),
572 }
573 }
574
575 /// Get the size of the data, in memory or on disk.
576 ///
577 /// #### Note
578 /// The size on disk may not match the size of the file if it is externally modified.
579 pub fn size(&self) -> u64 {
580 use self::SavedData::*;
581
582 match *self {
583 Text(ref text) => text.len() as u64,
584 Bytes(ref bytes) => bytes.len() as u64,
585 File(_, size) => size,
586 }
587 }
588
589 /// Returns `true` if the data is known to be in memory (`Text | Bytes`)
590 pub fn is_memory(&self) -> bool {
591 use self::SavedData::*;
592
593 match *self {
594 Text(_) | Bytes(_) => true,
595 File(_, _) => false,
596 }
597 }
598
599 fn add_size(self, add: u64) -> Self {
600 use self::SavedData::File;
601
602 match self {
603 File(path, size) => File(path, size.saturating_add(add)),
604 other => other,
605 }
606 }
607}
608
609impl From<String> for SavedData {
610 fn from(s: String) -> Self {
611 SavedData::Text(s)
612 }
613}
614
615impl From<Vec<u8>> for SavedData {
616 fn from(b: Vec<u8>) -> Self {
617 SavedData::Bytes(b)
618 }
619}
620
621/// A `Read` (and `BufRead`) adapter for `SavedData`
622pub enum DataReader<'a> {
623 /// In-memory data source (`SavedData::Bytes | Text`)
624 Bytes(&'a [u8]),
625 /// On-disk data source (`SavedData::File`)
626 File(BufReader<File>),
627}
628
629impl<'a> Read for DataReader<'a> {
630 fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
631 use self::DataReader::*;
632
633 match *self {
634 Bytes(ref mut bytes) => bytes.read(buf),
635 File(ref mut file) => file.read(buf),
636 }
637 }
638}
639
640impl<'a> BufRead for DataReader<'a> {
641 fn fill_buf(&mut self) -> io::Result<&[u8]> {
642 use self::DataReader::*;
643
644 match *self {
645 Bytes(ref mut bytes) => bytes.fill_buf(),
646 File(ref mut file) => file.fill_buf(),
647 }
648 }
649
650 fn consume(&mut self, amt: usize) {
651 use self::DataReader::*;
652
653 match *self {
654 Bytes(ref mut bytes) => bytes.consume(amt),
655 File(ref mut file) => file.consume(amt),
656 }
657 }
658}
659
660/// A result of `Multipart::save()`.
661#[derive(Debug)]
662pub struct Entries {
663 /// The fields of the multipart request, mapped by field name -> value.
664 ///
665 /// A field name may have multiple actual fields associated with it, but the most
666 /// common case is a single field.
667 ///
668 /// Each vector is guaranteed not to be empty unless externally modified.
669 // Even though individual fields might only have one entry, it's better to limit the
670 // size of a value type in `HashMap` to improve cache efficiency in lookups.
671 pub fields: HashMap<Arc<str>, Vec<SavedField>>,
672 /// The directory that the entries in `fields` were saved into.
673 pub save_dir: SaveDir,
674 fields_count: u32,
675}
676
677impl Entries {
678 /// Create a new `Entries` with the given `SaveDir`
679 pub fn new(save_dir: SaveDir) -> Self {
680 Entries {
681 fields: HashMap::new(),
682 save_dir,
683 fields_count: 0,
684 }
685 }
686
687 /// Returns `true` if `fields` is empty, `false` otherwise.
688 pub fn is_empty(&self) -> bool {
689 self.fields.is_empty()
690 }
691
692 /// The number of actual fields contained within this `Entries`.
693 ///
694 /// Effectively `self.fields.values().map(Vec::len).sum()` but maintained separately.
695 ///
696 /// ## Note
697 /// This will be incorrect if `fields` is modified externally. Call `recount_fields()`
698 /// to get the correct count.
699 pub fn fields_count(&self) -> u32 {
700 self.fields_count
701 }
702
703 /// Sum the number of fields in this `Entries` and then return the updated value.
704 pub fn recount_fields(&mut self) -> u32 {
705 let fields_count = self.fields.values().map(Vec::len).sum();
706 // saturating cast
707 self.fields_count = cmp::min(u32::MAX as usize, fields_count) as u32;
708 self.fields_count
709 }
710
711 fn push_field(&mut self, mut headers: FieldHeaders, data: SavedData) {
712 use std::collections::hash_map::Entry::*;
713
714 match self.fields.entry(headers.name.clone()) {
715 Vacant(vacant) => {
716 vacant.insert(vec![SavedField { headers, data }]);
717 }
718 Occupied(occupied) => {
719 // dedup the field name by reusing the key's `Arc`
720 headers.name = occupied.key().clone();
721 occupied.into_mut().push(SavedField { headers, data });
722 }
723 }
724
725 self.fields_count = self.fields_count.saturating_add(1);
726 }
727
728 /// Print all fields and their contents to stdout. Mostly for testing purposes.
729 pub fn print_debug(&self) -> io::Result<()> {
730 let stdout = io::stdout();
731 let stdout_lock = stdout.lock();
732 self.write_debug(stdout_lock)
733 }
734
735 /// Write all fields and their contents to the given output. Mostly for testing purposes.
736 pub fn write_debug<W: Write>(&self, mut writer: W) -> io::Result<()> {
737 for (name, entries) in &self.fields {
738 writeln!(writer, "Field {:?} has {} entries:", name, entries.len())?;
739
740 for (idx, field) in entries.iter().enumerate() {
741 let mut data = field.data.readable()?;
742 let headers = &field.headers;
743 writeln!(
744 writer,
745 "{}: {:?} ({:?}):",
746 idx, headers.filename, headers.content_type
747 )?;
748 io::copy(&mut data, &mut writer)?;
749 }
750 }
751
752 Ok(())
753 }
754}
755
756/// The save directory for `Entries`. May be temporary (delete-on-drop) or permanent.
757#[derive(Debug)]
758pub enum SaveDir {
759 /// This directory is temporary and will be deleted, along with its contents, when this wrapper
760 /// is dropped.
761 Temp(TempDir),
762 /// This directory is permanent and will be left on the filesystem when this wrapper is dropped.
763 ///
764 /// **N.B.** If this directory is in the OS temporary directory then it may still be
765 /// deleted at any time.
766 Perm(PathBuf),
767}
768
769impl SaveDir {
770 /// Get the path of this directory, either temporary or permanent.
771 pub fn as_path(&self) -> &Path {
772 use self::SaveDir::*;
773 match *self {
774 Temp(ref tempdir) => tempdir.path(),
775 Perm(ref pathbuf) => &*pathbuf,
776 }
777 }
778
779 /// Returns `true` if this is a temporary directory which will be deleted on-drop.
780 pub fn is_temporary(&self) -> bool {
781 use self::SaveDir::*;
782 match *self {
783 Temp(_) => true,
784 Perm(_) => false,
785 }
786 }
787
788 /// Unwrap the `PathBuf` from `self`; if this is a temporary directory,
789 /// it will be converted to a permanent one.
790 pub fn into_path(self) -> PathBuf {
791 use self::SaveDir::*;
792
793 match self {
794 Temp(tempdir) => tempdir.into_path(),
795 Perm(pathbuf) => pathbuf,
796 }
797 }
798
799 /// If this `SaveDir` is temporary, convert it to permanent.
800 /// This is a no-op if it already is permanent.
801 ///
802 /// ### Warning: Potential Data Loss
803 /// Even though this will prevent deletion on-drop, the temporary folder on most OSes
804 /// (where this directory is created by default) can be automatically cleared by the OS at any
805 /// time, usually on reboot or when free space is low.
806 ///
807 /// It is recommended that you relocate the files from a request which you want to keep to a
808 /// permanent folder on the filesystem.
809 pub fn keep(&mut self) {
810 use self::SaveDir::*;
811 *self = match mem::replace(self, Perm(PathBuf::new())) {
812 Temp(tempdir) => Perm(tempdir.into_path()),
813 old_self => old_self,
814 };
815 }
816
817 /// Delete this directory and its contents, regardless of its permanence.
818 ///
819 /// ### Warning: Potential Data Loss
820 /// This is very likely irreversible, depending on the OS implementation.
821 ///
822 /// Files deleted programmatically are deleted directly from disk, as compared to most file
823 /// manager applications which use a staging area from which deleted files can be safely
824 /// recovered (i.e. Windows' Recycle Bin, OS X's Trash Can, etc.).
825 pub fn delete(self) -> io::Result<()> {
826 use self::SaveDir::*;
827 match self {
828 Temp(tempdir) => tempdir.close(),
829 Perm(pathbuf) => fs::remove_dir_all(&pathbuf),
830 }
831 }
832}
833
834impl AsRef<Path> for SaveDir {
835 fn as_ref(&self) -> &Path {
836 self.as_path()
837 }
838}
839
840/// The reason the save operation quit partway through.
841#[derive(Debug)]
842pub enum PartialReason {
843 /// The count limit for files in the request was hit.
844 ///
845 /// The associated file has not been saved to the filesystem.
846 CountLimit,
847 /// The size limit for an individual file was hit.
848 ///
849 /// The file was partially written to the filesystem.
850 SizeLimit,
851 /// An error occurred during the operation.
852 IoError(io::Error),
853 /// An error returned from validating a field as UTF-8 due to `SaveBuilder::force_text()`
854 Utf8Error(str::Utf8Error),
855}
856
857impl From<io::Error> for PartialReason {
858 fn from(e: io::Error) -> Self {
859 IoError(e)
860 }
861}
862
863impl From<str::Utf8Error> for PartialReason {
864 fn from(e: str::Utf8Error) -> Self {
865 Utf8Error(e)
866 }
867}
868
869impl PartialReason {
870 /// Return `io::Error` in the `IoError` case or panic otherwise.
871 pub fn unwrap_err(self) -> io::Error {
872 self.expect_err("`PartialReason` was not `IoError`")
873 }
874
875 /// Return `io::Error` in the `IoError` case or panic with the given
876 /// message otherwise.
877 pub fn expect_err(self, msg: &str) -> io::Error {
878 match self {
879 PartialReason::IoError(e) => e,
880 _ => panic!("{}: {:?}", msg, self),
881 }
882 }
883}
884
885/// The field that was being read when the save operation quit.
886///
887/// May be partially saved to the filesystem if `dest` is `Some`.
888#[derive(Debug)]
889pub struct PartialSavedField<M: ReadEntry> {
890 /// The field that was being read.
891 ///
892 /// May be partially read if `dest` is `Some`.
893 pub source: MultipartField<M>,
894 /// The data from the saving operation, if it got that far.
895 pub dest: Option<SavedData>,
896}
897
898/// The partial result type for `Multipart::save*()`.
899///
900/// Contains the successfully saved entries as well as the partially
901/// saved file that was in the process of being read when the error occurred,
902/// if applicable.
903#[derive(Debug)]
904pub struct PartialEntries<M: ReadEntry> {
905 /// The entries that were saved successfully.
906 pub entries: Entries,
907 /// The field that was in the process of being read. `None` if the error
908 /// occurred between entries.
909 pub partial: Option<PartialSavedField<M>>,
910}
911
912/// Discards `partial`
913impl<M: ReadEntry> Into<Entries> for PartialEntries<M> {
914 fn into(self) -> Entries {
915 self.entries
916 }
917}
918
919impl<M: ReadEntry> PartialEntries<M> {
920 /// If `partial` is present and contains a `SavedFile` then just
921 /// add it to the `Entries` instance and return it.
922 ///
923 /// Otherwise, returns `self.entries`
924 pub fn keep_partial(mut self) -> Entries {
925 if let Some(partial) = self.partial {
926 if let Some(saved) = partial.dest {
927 self.entries.push_field(partial.source.headers, saved);
928 }
929 }
930
931 self.entries
932 }
933}
934
935/// The ternary result type used for the `SaveBuilder<_>` API.
936#[derive(Debug)]
937pub enum SaveResult<Success, Partial> {
938 /// The operation was a total success. Contained is the complete result.
939 Full(Success),
940 /// The operation quit partway through. Included is the partial
941 /// result along with the reason.
942 Partial(Partial, PartialReason),
943 /// An error occurred at the start of the operation, before anything was done.
944 Error(io::Error),
945}
946
947/// Shorthand result for methods that return `Entries`
948pub type EntriesSaveResult<M> = SaveResult<Entries, PartialEntries<M>>;
949
950/// Shorthand result for methods that return `FieldData`s.
951///
952/// The `MultipartData` is not provided here because it is not necessary to return
953/// a borrow when the owned version is probably in the same scope. This hopefully
954/// saves some headache with the borrow-checker.
955pub type FieldSaveResult = SaveResult<SavedData, SavedData>;
956
957impl<M: ReadEntry> EntriesSaveResult<M> {
958 /// Take the `Entries` from `self`, if applicable, and discarding
959 /// the error, if any.
960 pub fn into_entries(self) -> Option<Entries> {
961 match self {
962 Full(entries) | Partial(PartialEntries { entries, .. }, _) => Some(entries),
963 Error(_) => None,
964 }
965 }
966}
967
968impl<S, P> SaveResult<S, P>
969where
970 P: Into<S>,
971{
972 /// Convert `self` to `Option<S>`; there may still have been an error.
973 pub fn okish(self) -> Option<S> {
974 self.into_opt_both().0
975 }
976
977 /// Map the `Full` or `Partial` values to a new type, retaining the reason
978 /// in the `Partial` case.
979 pub fn map<T, Map>(self, map: Map) -> SaveResult<T, T>
980 where
981 Map: FnOnce(S) -> T,
982 {
983 match self {
984 Full(full) => Full(map(full)),
985 Partial(partial, reason) => Partial(map(partial.into()), reason),
986 Error(e) => Error(e),
987 }
988 }
989
990 /// Decompose `self` to `(Option<S>, Option<io::Error>)`
991 pub fn into_opt_both(self) -> (Option<S>, Option<io::Error>) {
992 match self {
993 Full(full) => (Some(full), None),
994 Partial(partial, IoError(e)) => (Some(partial.into()), Some(e)),
995 Partial(partial, _) => (Some(partial.into()), None),
996 Error(error) => (None, Some(error)),
997 }
998 }
999
1000 /// Map `self` to an `io::Result`, discarding the error in the `Partial` case.
1001 pub fn into_result(self) -> io::Result<S> {
1002 match self {
1003 Full(entries) => Ok(entries),
1004 Partial(partial, _) => Ok(partial.into()),
1005 Error(error) => Err(error),
1006 }
1007 }
1008
1009 /// Pessimistic version of `into_result()` which will return an error even
1010 /// for the `Partial` case.
1011 ///
1012 /// ### Note: Possible Storage Leak
1013 /// It's generally not a good idea to ignore the `Partial` case, as there may still be a
1014 /// partially written file on-disk. If you're not using a temporary directory
1015 /// (OS-managed or via `TempDir`) then partially written files will remain on-disk until
1016 /// explicitly removed which could result in excessive disk usage if not monitored closely.
1017 pub fn into_result_strict(self) -> io::Result<S> {
1018 match self {
1019 Full(entries) => Ok(entries),
1020 Partial(_, PartialReason::IoError(e)) | Error(e) => Err(e),
1021 Partial(partial, _) => Ok(partial.into()),
1022 }
1023 }
1024}
1025
1026fn create_dir_all(path: &Path) -> io::Result<()> {
1027 if let Some(parent) = path.parent() {
1028 fs::create_dir_all(parent)
1029 } else {
1030 // RFC: return an error instead?
1031 warn!(
1032 "Attempting to save file in what looks like a root directory. File path: {:?}",
1033 path
1034 );
1035 Ok(())
1036 }
1037}
1038
1039fn try_copy_limited<R: BufRead, Wb: FnMut(&[u8]) -> SaveResult<usize, usize>>(
1040 src: R,
1041 mut with_buf: Wb,
1042 limit: u64,
1043) -> SaveResult<u64, u64> {
1044 let mut copied = 0u64;
1045 try_read_buf(src, |buf| {
1046 let new_copied = copied.saturating_add(buf.len() as u64);
1047 if new_copied > limit {
1048 return Partial(0, PartialReason::SizeLimit);
1049 }
1050 copied = new_copied;
1051
1052 with_buf(buf)
1053 })
1054}
1055
1056fn try_read_buf<R: BufRead, Wb: FnMut(&[u8]) -> SaveResult<usize, usize>>(
1057 mut src: R,
1058 mut with_buf: Wb,
1059) -> SaveResult<u64, u64> {
1060 let mut total_copied = 0u64;
1061
1062 macro_rules! try_here (
1063 ($try:expr) => (
1064 match $try {
1065 Ok(val) => val,
1066 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
1067 Err(e) => return if total_copied == 0 { Error(e) }
1068 else { Partial(total_copied, e.into()) },
1069 }
1070 )
1071 );
1072
1073 loop {
1074 let res = {
1075 let buf = try_here!(src.fill_buf());
1076 if buf.is_empty() {
1077 break;
1078 }
1079 with_buf(buf)
1080 };
1081
1082 match res {
1083 Full(copied) => {
1084 src.consume(copied);
1085 total_copied += copied as u64;
1086 }
1087 Partial(copied, reason) => {
1088 src.consume(copied);
1089 total_copied += copied as u64;
1090 return Partial(total_copied, reason);
1091 }
1092 Error(err) => {
1093 return Partial(total_copied, err.into());
1094 }
1095 }
1096 }
1097
1098 Full(total_copied)
1099}
1100
1101fn try_write_all<W: Write>(mut buf: &[u8], mut dest: W) -> SaveResult<usize, usize> {
1102 let mut total_copied = 0;
1103
1104 macro_rules! try_here (
1105 ($try:expr) => (
1106 match $try {
1107 Ok(val) => val,
1108 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => continue,
1109 Err(e) => return if total_copied == 0 { Error(e) }
1110 else { Partial(total_copied, e.into()) },
1111 }
1112 )
1113 );
1114
1115 while !buf.is_empty() {
1116 match try_here!(dest.write(buf)) {
1117 0 => try_here!(Err(io::Error::new(
1118 io::ErrorKind::WriteZero,
1119 "failed to write whole buffer"
1120 ))),
1121 copied => {
1122 buf = &buf[copied..];
1123 total_copied += copied;
1124 }
1125 }
1126 }
1127
1128 Full(total_copied)
1129}