actix_web_multipart_file/
lib.rs

1//! Save multipart files in requests into temporary files
2//!
3//! # Examples
4//! ```rust
5//! # extern crate actix_web;
6//! # extern crate actix_web_multipart_file;
7//! # extern crate futures;
8//! # use actix_web::{FutureResponse, HttpResponse};
9//! # use actix_web_multipart_file::Multiparts;
10//! # use futures::Stream;
11//!
12//! fn handle_multipart(multiparts: Multiparts) -> FutureResponse<HttpResponse> {
13//!     multiparts
14//!         .and_then(|field| {
15//!             // do something with field
16//! #           ::futures::future::ok(())
17//!         })
18//! # ; unimplemented!()
19//!     // ...
20//! }
21//!
22//! ```
23
24extern crate actix_web;
25extern crate bytes;
26extern crate futures;
27#[macro_use]
28extern crate log;
29extern crate tempfile;
30#[macro_use]
31extern crate failure;
32extern crate mime;
33
34use actix_web::dev::AsyncResult;
35use actix_web::error::PayloadError;
36use actix_web::http::header::ContentDisposition;
37use actix_web::http::HeaderMap;
38use actix_web::multipart::Multipart;
39use actix_web::multipart::MultipartItem;
40use actix_web::{FromRequest, HttpMessage, HttpRequest, HttpResponse, ResponseError};
41use bytes::Bytes;
42use futures::future::Either;
43use futures::prelude::*;
44use futures::stream;
45use futures::Poll;
46use mime::Mime;
47use std::fs::File;
48use std::io::Write;
49use tempfile::tempfile;
50
51#[derive(Debug)]
52pub struct Field {
53    pub headers: HeaderMap,
54    pub content_type: Mime,
55    pub content_disposition: ContentDisposition,
56    pub form_data: FormData,
57}
58
59impl Field {
60    /// for compatibility with multipart's Field
61    pub fn headers(&self) -> &HeaderMap {
62        &self.headers
63    }
64
65    /// for compatibility with multipart's Field
66    pub fn content_type(&self) -> &Mime {
67        &self.content_type
68    }
69
70    /// for compatibility with multipart's Field.
71    /// always return Some(data)
72    pub fn content_disposition(&self) -> Option<ContentDisposition> {
73        Some(self.content_disposition.clone())
74    }
75}
76
77#[derive(Debug)]
78pub enum FormData {
79    /// form data that don't have filename
80    Data {
81        /// name field
82        name: String,
83        /// body
84        value: Bytes,
85    },
86    /// form data thathave filename
87    File {
88        /// name field
89        name: String,
90        /// filename field
91        filename: String,
92        /// a temporary file that contains the body
93        file: File,
94    },
95}
96
97#[derive(Debug, Fail)]
98pub enum Error {
99    #[fail(display = "Multipart error: {}", _0)]
100    Multipart(#[cause] ::actix_web::error::MultipartError),
101    #[fail(display = "Given data is not a form data")]
102    NotFormData,
103    #[fail(display = " File handling error: {}", _0)]
104    Io(#[cause] ::std::io::Error),
105}
106
107impl From<::actix_web::error::MultipartError> for Error {
108    fn from(e: ::actix_web::error::MultipartError) -> Self {
109        Error::Multipart(e)
110    }
111}
112
113impl From<::std::io::Error> for Error {
114    fn from(e: ::std::io::Error) -> Self {
115        Error::Io(e)
116    }
117}
118
119impl ResponseError for Error {
120    fn error_response(&self) -> HttpResponse {
121        match self {
122            Error::Multipart(_) => HttpResponse::BadRequest().body("multipart error"),
123            Error::NotFormData => HttpResponse::BadRequest().body("not a form data"),
124            Error::Io(_) => HttpResponse::InternalServerError().body("file error"),
125        }
126    }
127}
128
129pub type BoxStream<'a, T, E> = Box<Stream<Item = T, Error = E> + 'a>;
130
131fn save_into_tempfile<S, I, E>(input: S) -> impl Future<Item = File, Error = Error>
132where
133    S: Stream<Item = I, Error = E>,
134    I: AsRef<[u8]>,
135    Error: From<E>,
136{
137    tempfile()
138        .into_future()
139        .from_err()
140        .and_then(|tempfile| {
141            let file = tempfile.try_clone()?;
142            Ok((tempfile, file))
143        }).and_then(|(mut tempfile, mut file)| {
144            input
145                .from_err()
146                .and_then(move |bytes| {
147                    // FIXME: make it async
148                    tempfile.write_all(bytes.as_ref()).map_err(|err| err.into())
149                }).collect()
150                .and_then(move |_| {
151                    use std::io::{Seek, SeekFrom};
152                    // FIXME: make it async
153                    file.seek(SeekFrom::Start(0))?;
154                    Ok(file)
155                })
156        })
157}
158
159/// save form data with filename into tempfiles
160/// # Examples
161/// ```rust
162/// # extern crate actix_web;
163/// # extern crate actix_web_multipart_file;
164/// # extern crate futures;
165/// # use actix_web::{HttpRequest, HttpMessage, FutureResponse, HttpResponse};
166/// # use actix_web_multipart_file::save_files;
167/// # use futures::Stream;
168///
169/// fn handle_multipart(req: HttpRequest) -> FutureResponse<HttpResponse> {
170///     save_files(req.multipart())
171///         .and_then(|field| {
172///             // do something with field
173/// #           ::futures::future::ok(())
174///         })
175/// # ; unimplemented!()
176///     // ...
177/// }
178///
179/// ```
180pub fn save_files<S>(multipart: Multipart<S>) -> BoxStream<'static, Field, Error>
181where
182    S: Stream<Item = Bytes, Error = PayloadError> + 'static,
183{
184    let st = multipart
185        .map_err(Error::from)
186        .map(|item| -> BoxStream<Field, Error> {
187            match item {
188                MultipartItem::Nested(m) => save_files(m),
189                MultipartItem::Field(field) => {
190                    debug!("multipart field: {:?}", field);
191                    // form data must have content disposition
192                    let content_disposition = match field.content_disposition() {
193                        Some(d) => d,
194                        None => return Box::new(stream::once(Err(Error::NotFormData))),
195                    };
196
197                    if !content_disposition.is_form_data() {
198                        return Box::new(stream::once(Err(Error::NotFormData)));
199                    }
200
201                    // currently fields that don't have names are ignored
202                    let name = match content_disposition.get_name() {
203                        Some(n) => n.to_string(),
204                        None => return Box::new(stream::empty()),
205                    };
206
207                    let content_type = field.content_type().clone();
208                    let headers = field.headers().clone();
209
210                    let form_data = match content_disposition.get_filename() {
211                        None => {
212                            let fut = field
213                                .from_err()
214                                .concat2()
215                                .map(|value| FormData::Data { name, value });
216                            Either::A(fut)
217                        }
218
219                        Some(filename) => {
220                            let filename = filename.to_string();
221                            let fut = save_into_tempfile(field).map(move |file| FormData::File {
222                                name,
223                                filename,
224                                file,
225                            });
226                            Either::B(fut)
227                        }
228                    };
229                    let fut = form_data.map(move |form_data| Field {
230                        content_type,
231                        headers,
232                        content_disposition,
233                        form_data,
234                    });
235                    Box::new(fut.into_stream())
236                }
237            }
238        }).flatten();
239    Box::new(st)
240}
241
242/// An extractor that saves multipart files in requests into temporary files.
243/// See the crate level documentation.
244pub struct Multiparts(BoxStream<'static, Field, Error>);
245
246impl<S> FromRequest<S> for Multiparts {
247    type Config = ();
248    type Result = AsyncResult<Self>;
249    fn from_request(req: &HttpRequest<S>, _: &Self::Config) -> Self::Result {
250        AsyncResult::ok(Multiparts(save_files(req.multipart())))
251    }
252}
253
254impl Stream for Multiparts {
255    type Item = Field;
256    type Error = Error;
257    fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
258        self.0.poll()
259    }
260}