1use mime::Mime;
11
12use std::borrow::Cow;
13use std::fs::File;
14use std::io;
15use std::io::prelude::*;
16
17use std::path::Path;
18
19#[cfg(feature = "hyper")]
20pub mod hyper;
21
22pub mod lazy;
23
24mod sized;
25
26pub use self::sized::SizedRequest;
27
28const BOUNDARY_LEN: usize = 16;
29
30macro_rules! map_self {
31 ($selff:expr, $try:expr) => (
32 match $try {
33 Ok(_) => Ok($selff),
34 Err(err) => Err(err.into()),
35 }
36 )
37}
38
39pub struct Multipart<S> {
45 writer: MultipartWriter<'static, S>,
46}
47
48impl Multipart<()> {
49 pub fn from_request<R: HttpRequest>(req: R) -> Result<Multipart<R::Stream>, R::Error> {
54 let (boundary, stream) = open_stream(req, None)?;
55
56 Ok(Multipart {
57 writer: MultipartWriter::new(stream, boundary),
58 })
59 }
60}
61
62impl<S: HttpStream> Multipart<S> {
63 pub fn write_text<N: AsRef<str>, V: AsRef<str>>(&mut self, name: N, val: V) -> Result<&mut Self, S::Error> {
69 map_self!(self, self.writer.write_text(name.as_ref(), val.as_ref()))
70 }
71
72 pub fn write_file<N: AsRef<str>, P: AsRef<Path>>(&mut self, name: N, path: P) -> Result<&mut Self, S::Error> {
84 let name = name.as_ref();
85 let path = path.as_ref();
86
87 map_self!(self, self.writer.write_file(name, path))
88 }
89
90 pub fn write_stream<N: AsRef<str>, St: Read>(
111 &mut self, name: N, stream: &mut St, filename: Option<&str>, content_type: Option<Mime>
112 ) -> Result<&mut Self, S::Error> {
113 let name = name.as_ref();
114
115 map_self!(self, self.writer.write_stream(stream, name, filename, content_type))
116 }
117
118 pub fn send(self) -> Result<S::Response, S::Error> {
120 self.writer.finish().map_err(io::Error::into).and_then(|body| body.finish())
121 }
122}
123
124impl<R: HttpRequest> Multipart<SizedRequest<R>>
125where <R::Stream as HttpStream>::Error: From<R::Error> {
126 pub fn from_request_sized(req: R) -> Result<Self, R::Error> {
128 Multipart::from_request(SizedRequest::from_request(req))
129 }
130}
131
132pub trait HttpRequest {
134 type Stream: HttpStream;
137 type Error: From<io::Error> + Into<<Self::Stream as HttpStream>::Error>;
140
141 fn apply_headers(&mut self, boundary: &str, content_len: Option<u64>) -> bool;
147
148 fn open_stream(self) -> Result<Self::Stream, Self::Error>;
150}
151
152pub trait HttpStream: Write {
154 type Request: HttpRequest;
156 type Response;
158 type Error: From<io::Error> + From<<Self::Request as HttpRequest>::Error>;
161
162 fn finish(self) -> Result<Self::Response, Self::Error>;
164}
165
166impl HttpRequest for () {
167 type Stream = io::Sink;
168 type Error = io::Error;
169
170 fn apply_headers(&mut self, _: &str, _: Option<u64>) -> bool { true }
171 fn open_stream(self) -> Result<Self::Stream, Self::Error> { Ok(io::sink()) }
172}
173
174impl HttpStream for io::Sink {
175 type Request = ();
176 type Response = ();
177 type Error = io::Error;
178
179 fn finish(self) -> Result<Self::Response, Self::Error> { Ok(()) }
180}
181
182fn gen_boundary() -> String {
183 ::random_alphanumeric(BOUNDARY_LEN)
184}
185
186fn open_stream<R: HttpRequest>(mut req: R, content_len: Option<u64>) -> Result<(String, R::Stream), R::Error> {
187 let boundary = gen_boundary();
188 req.apply_headers(&boundary, content_len);
189 req.open_stream().map(|stream| (boundary, stream))
190}
191
192struct MultipartWriter<'a, W> {
193 inner: W,
194 boundary: Cow<'a, str>,
195 data_written: bool,
196}
197
198impl<'a, W: Write> MultipartWriter<'a, W> {
199 fn new<B: Into<Cow<'a, str>>>(inner: W, boundary: B) -> Self {
200 MultipartWriter {
201 inner,
202 boundary: boundary.into(),
203 data_written: false,
204 }
205 }
206
207 fn write_boundary(&mut self) -> io::Result<()> {
208 if self.data_written {
209 self.inner.write_all(b"\r\n")?;
210 }
211
212 write!(self.inner, "--{}\r\n", self.boundary)
213 }
214
215 fn write_text(&mut self, name: &str, text: &str) -> io::Result<()> {
216 chain_result! {
217 self.write_field_headers(name, None, None),
218 self.inner.write_all(text.as_bytes())
219 }
220 }
221
222 fn write_file(&mut self, name: &str, path: &Path) -> io::Result<()> {
223 let (content_type, filename) = mime_filename(path);
224 let mut file = File::open(path)?;
225 self.write_stream(&mut file, name, filename, Some(content_type))
226 }
227
228 fn write_stream<S: Read>(&mut self, stream: &mut S, name: &str, filename: Option<&str>, content_type: Option<Mime>) -> io::Result<()> {
229 let content_type = Some(content_type.unwrap_or(mime::APPLICATION_OCTET_STREAM));
231
232 chain_result! {
233 self.write_field_headers(name, filename, content_type),
234 io::copy(stream, &mut self.inner),
235 Ok(())
236 }
237 }
238
239 fn write_field_headers(&mut self, name: &str, filename: Option<&str>, content_type: Option<Mime>)
240 -> io::Result<()> {
241 chain_result! {
242 self.write_boundary(),
244 { self.data_written = true; Ok(()) },
245 write!(self.inner, "Content-Disposition: form-data; name=\"{}\"", name),
246 filename.map(|filename| write!(self.inner, "; filename=\"{}\"", filename))
247 .unwrap_or(Ok(())),
248 content_type.map(|content_type| write!(self.inner, "\r\nContent-Type: {}", content_type))
249 .unwrap_or(Ok(())),
250 self.inner.write_all(b"\r\n\r\n")
251 }
252 }
253
254 fn finish(mut self) -> io::Result<W> {
255 if self.data_written {
256 self.inner.write_all(b"\r\n")?;
257 }
258
259 write!(self.inner, "--{}--\r\n", self.boundary)?;
263 Ok(self.inner)
264 }
265}
266
267fn mime_filename(path: &Path) -> (Mime, Option<&str>) {
268 let content_type = ::mime_guess::from_path(path).first_or_octet_stream();
269 let filename = opt_filename(path);
270 (content_type, filename)
271}
272
273fn opt_filename(path: &Path) -> Option<&str> {
274 path.file_name().and_then(|filename| filename.to_str())
275}