actix_web_multipart_file/
lib.rs1extern 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 pub fn headers(&self) -> &HeaderMap {
62 &self.headers
63 }
64
65 pub fn content_type(&self) -> &Mime {
67 &self.content_type
68 }
69
70 pub fn content_disposition(&self) -> Option<ContentDisposition> {
73 Some(self.content_disposition.clone())
74 }
75}
76
77#[derive(Debug)]
78pub enum FormData {
79 Data {
81 name: String,
83 value: Bytes,
85 },
86 File {
88 name: String,
90 filename: String,
92 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 tempfile.write_all(bytes.as_ref()).map_err(|err| err.into())
149 }).collect()
150 .and_then(move |_| {
151 use std::io::{Seek, SeekFrom};
152 file.seek(SeekFrom::Start(0))?;
154 Ok(file)
155 })
156 })
157}
158
159pub 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 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 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
242pub 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}