1 use std::ffi::CString;
2 use std::fmt;
3 use std::path::Path;
4 use std::ptr;
5 
6 use curl_sys;
7 use easy::{list, List};
8 use FormError;
9 
10 /// Multipart/formdata for an HTTP POST request.
11 ///
12 /// This structure is built up and then passed to the `Easy::httppost` method to
13 /// be sent off with a request.
14 pub struct Form {
15     head: *mut curl_sys::curl_httppost,
16     tail: *mut curl_sys::curl_httppost,
17     headers: Vec<List>,
18     buffers: Vec<Vec<u8>>,
19     strings: Vec<CString>,
20 }
21 
22 /// One part in a multipart upload, added to a `Form`.
23 pub struct Part<'form, 'data> {
24     form: &'form mut Form,
25     name: &'data str,
26     array: Vec<curl_sys::curl_forms>,
27     error: Option<FormError>,
28 }
29 
raw(form: &Form) -> *mut curl_sys::curl_httppost30 pub fn raw(form: &Form) -> *mut curl_sys::curl_httppost {
31     form.head
32 }
33 
34 impl Form {
35     /// Creates a new blank form ready for the addition of new data.
new() -> Form36     pub fn new() -> Form {
37         Form {
38             head: ptr::null_mut(),
39             tail: ptr::null_mut(),
40             headers: Vec::new(),
41             buffers: Vec::new(),
42             strings: Vec::new(),
43         }
44     }
45 
46     /// Prepares adding a new part to this `Form`
47     ///
48     /// Note that the part is not actually added to the form until the `add`
49     /// method is called on `Part`, which may or may not fail.
part<'a, 'data>(&'a mut self, name: &'data str) -> Part<'a, 'data>50     pub fn part<'a, 'data>(&'a mut self, name: &'data str) -> Part<'a, 'data> {
51         Part {
52             error: None,
53             form: self,
54             name,
55             array: vec![curl_sys::curl_forms {
56                 option: curl_sys::CURLFORM_END,
57                 value: ptr::null_mut(),
58             }],
59         }
60     }
61 }
62 
63 impl fmt::Debug for Form {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result64     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65         // TODO: fill this out more
66         f.debug_struct("Form").field("fields", &"...").finish()
67     }
68 }
69 
70 impl Drop for Form {
drop(&mut self)71     fn drop(&mut self) {
72         unsafe {
73             curl_sys::curl_formfree(self.head);
74         }
75     }
76 }
77 
78 impl<'form, 'data> Part<'form, 'data> {
79     /// A pointer to the contents of this part, the actual data to send away.
contents(&mut self, contents: &'data [u8]) -> &mut Self80     pub fn contents(&mut self, contents: &'data [u8]) -> &mut Self {
81         let pos = self.array.len() - 1;
82 
83         // curl has an oddity where if the length if 0 it will call strlen
84         // on the value.  This means that if someone wants to add empty form
85         // contents we need to make sure the buffer contains a null byte.
86         let ptr = if contents.is_empty() {
87             b"\x00"
88         } else {
89             contents
90         }
91         .as_ptr();
92 
93         self.array.insert(
94             pos,
95             curl_sys::curl_forms {
96                 option: curl_sys::CURLFORM_COPYCONTENTS,
97                 value: ptr as *mut _,
98             },
99         );
100         self.array.insert(
101             pos + 1,
102             curl_sys::curl_forms {
103                 option: curl_sys::CURLFORM_CONTENTSLENGTH,
104                 value: contents.len() as *mut _,
105             },
106         );
107         self
108     }
109 
110     /// Causes this file to be read and its contents used as data in this part
111     ///
112     /// This part does not automatically become a file upload part simply
113     /// because its data was read from a file.
114     ///
115     /// # Errors
116     ///
117     /// If the filename has any internal nul bytes or if on Windows it does not
118     /// contain a unicode filename then the `add` function will eventually
119     /// return an error.
file_content<P>(&mut self, file: P) -> &mut Self where P: AsRef<Path>,120     pub fn file_content<P>(&mut self, file: P) -> &mut Self
121     where
122         P: AsRef<Path>,
123     {
124         self._file_content(file.as_ref())
125     }
126 
_file_content(&mut self, file: &Path) -> &mut Self127     fn _file_content(&mut self, file: &Path) -> &mut Self {
128         if let Some(bytes) = self.path2cstr(file) {
129             let pos = self.array.len() - 1;
130             self.array.insert(
131                 pos,
132                 curl_sys::curl_forms {
133                     option: curl_sys::CURLFORM_FILECONTENT,
134                     value: bytes.as_ptr() as *mut _,
135                 },
136             );
137             self.form.strings.push(bytes);
138         }
139         self
140     }
141 
142     /// Makes this part a file upload part of the given file.
143     ///
144     /// Sets the filename field to the basename of the provided file name, and
145     /// it reads the contents of the file and passes them as data and sets the
146     /// content type if the given file matches one of the internally known file
147     /// extensions.
148     ///
149     /// The given upload file must exist entirely on the filesystem before the
150     /// upload is started because libcurl needs to read the size of it
151     /// beforehand.
152     ///
153     /// Multiple files can be uploaded by calling this method multiple times and
154     /// content types can also be configured for each file (by calling that
155     /// next).
156     ///
157     /// # Errors
158     ///
159     /// If the filename has any internal nul bytes or if on Windows it does not
160     /// contain a unicode filename then this function will cause `add` to return
161     /// an error when called.
file<P: ?Sized>(&mut self, file: &'data P) -> &mut Self where P: AsRef<Path>,162     pub fn file<P: ?Sized>(&mut self, file: &'data P) -> &mut Self
163     where
164         P: AsRef<Path>,
165     {
166         self._file(file.as_ref())
167     }
168 
_file(&mut self, file: &'data Path) -> &mut Self169     fn _file(&mut self, file: &'data Path) -> &mut Self {
170         if let Some(bytes) = self.path2cstr(file) {
171             let pos = self.array.len() - 1;
172             self.array.insert(
173                 pos,
174                 curl_sys::curl_forms {
175                     option: curl_sys::CURLFORM_FILE,
176                     value: bytes.as_ptr() as *mut _,
177                 },
178             );
179             self.form.strings.push(bytes);
180         }
181         self
182     }
183 
184     /// Used in combination with `Part::file`, provides the content-type for
185     /// this part, possibly instead of choosing an internal one.
186     ///
187     /// # Panics
188     ///
189     /// This function will panic if `content_type` contains an internal nul
190     /// byte.
content_type(&mut self, content_type: &'data str) -> &mut Self191     pub fn content_type(&mut self, content_type: &'data str) -> &mut Self {
192         if let Some(bytes) = self.bytes2cstr(content_type.as_bytes()) {
193             let pos = self.array.len() - 1;
194             self.array.insert(
195                 pos,
196                 curl_sys::curl_forms {
197                     option: curl_sys::CURLFORM_CONTENTTYPE,
198                     value: bytes.as_ptr() as *mut _,
199                 },
200             );
201             self.form.strings.push(bytes);
202         }
203         self
204     }
205 
206     /// Used in combination with `Part::file`, provides the filename for
207     /// this part instead of the actual one.
208     ///
209     /// # Errors
210     ///
211     /// If `name` contains an internal nul byte, or if on Windows the path is
212     /// not valid unicode then this function will return an error when `add` is
213     /// called.
filename<P: ?Sized>(&mut self, name: &'data P) -> &mut Self where P: AsRef<Path>,214     pub fn filename<P: ?Sized>(&mut self, name: &'data P) -> &mut Self
215     where
216         P: AsRef<Path>,
217     {
218         self._filename(name.as_ref())
219     }
220 
_filename(&mut self, name: &'data Path) -> &mut Self221     fn _filename(&mut self, name: &'data Path) -> &mut Self {
222         if let Some(bytes) = self.path2cstr(name) {
223             let pos = self.array.len() - 1;
224             self.array.insert(
225                 pos,
226                 curl_sys::curl_forms {
227                     option: curl_sys::CURLFORM_FILENAME,
228                     value: bytes.as_ptr() as *mut _,
229                 },
230             );
231             self.form.strings.push(bytes);
232         }
233         self
234     }
235 
236     /// This is used to provide a custom file upload part without using the
237     /// `file` method above.
238     ///
239     /// The first parameter is for the filename field and the second is the
240     /// in-memory contents.
241     ///
242     /// # Errors
243     ///
244     /// If `name` contains an internal nul byte, or if on Windows the path is
245     /// not valid unicode then this function will return an error when `add` is
246     /// called.
buffer<P: ?Sized>(&mut self, name: &'data P, data: Vec<u8>) -> &mut Self where P: AsRef<Path>,247     pub fn buffer<P: ?Sized>(&mut self, name: &'data P, data: Vec<u8>) -> &mut Self
248     where
249         P: AsRef<Path>,
250     {
251         self._buffer(name.as_ref(), data)
252     }
253 
_buffer(&mut self, name: &'data Path, mut data: Vec<u8>) -> &mut Self254     fn _buffer(&mut self, name: &'data Path, mut data: Vec<u8>) -> &mut Self {
255         if let Some(bytes) = self.path2cstr(name) {
256             // If `CURLFORM_BUFFERLENGTH` is set to `0`, libcurl will instead do a strlen() on the
257             // contents to figure out the size so we need to make sure the buffer is actually
258             // zero terminated.
259             let length = data.len();
260             if length == 0 {
261                 data.push(0);
262             }
263 
264             let pos = self.array.len() - 1;
265             self.array.insert(
266                 pos,
267                 curl_sys::curl_forms {
268                     option: curl_sys::CURLFORM_BUFFER,
269                     value: bytes.as_ptr() as *mut _,
270                 },
271             );
272             self.form.strings.push(bytes);
273             self.array.insert(
274                 pos + 1,
275                 curl_sys::curl_forms {
276                     option: curl_sys::CURLFORM_BUFFERPTR,
277                     value: data.as_ptr() as *mut _,
278                 },
279             );
280             self.array.insert(
281                 pos + 2,
282                 curl_sys::curl_forms {
283                     option: curl_sys::CURLFORM_BUFFERLENGTH,
284                     value: length as *mut _,
285                 },
286             );
287             self.form.buffers.push(data);
288         }
289         self
290     }
291 
292     /// Specifies extra headers for the form POST section.
293     ///
294     /// Appends the list of headers to those libcurl automatically generates.
content_header(&mut self, headers: List) -> &mut Self295     pub fn content_header(&mut self, headers: List) -> &mut Self {
296         let pos = self.array.len() - 1;
297         self.array.insert(
298             pos,
299             curl_sys::curl_forms {
300                 option: curl_sys::CURLFORM_CONTENTHEADER,
301                 value: list::raw(&headers) as *mut _,
302             },
303         );
304         self.form.headers.push(headers);
305         self
306     }
307 
308     /// Attempts to add this part to the `Form` that it was created from.
309     ///
310     /// If any error happens while adding, that error is returned, otherwise
311     /// `Ok(())` is returned.
add(&mut self) -> Result<(), FormError>312     pub fn add(&mut self) -> Result<(), FormError> {
313         if let Some(err) = self.error.clone() {
314             return Err(err);
315         }
316         let rc = unsafe {
317             curl_sys::curl_formadd(
318                 &mut self.form.head,
319                 &mut self.form.tail,
320                 curl_sys::CURLFORM_COPYNAME,
321                 self.name.as_ptr(),
322                 curl_sys::CURLFORM_NAMELENGTH,
323                 self.name.len(),
324                 curl_sys::CURLFORM_ARRAY,
325                 self.array.as_ptr(),
326                 curl_sys::CURLFORM_END,
327             )
328         };
329         if rc == curl_sys::CURL_FORMADD_OK {
330             Ok(())
331         } else {
332             Err(FormError::new(rc))
333         }
334     }
335 
336     #[cfg(unix)]
path2cstr(&mut self, p: &Path) -> Option<CString>337     fn path2cstr(&mut self, p: &Path) -> Option<CString> {
338         use std::os::unix::prelude::*;
339         self.bytes2cstr(p.as_os_str().as_bytes())
340     }
341 
342     #[cfg(windows)]
path2cstr(&mut self, p: &Path) -> Option<CString>343     fn path2cstr(&mut self, p: &Path) -> Option<CString> {
344         match p.to_str() {
345             Some(bytes) => self.bytes2cstr(bytes.as_bytes()),
346             None if self.error.is_none() => {
347                 // TODO: better error code
348                 self.error = Some(FormError::new(curl_sys::CURL_FORMADD_INCOMPLETE));
349                 None
350             }
351             None => None,
352         }
353     }
354 
bytes2cstr(&mut self, bytes: &[u8]) -> Option<CString>355     fn bytes2cstr(&mut self, bytes: &[u8]) -> Option<CString> {
356         match CString::new(bytes) {
357             Ok(c) => Some(c),
358             Err(..) if self.error.is_none() => {
359                 // TODO: better error code
360                 self.error = Some(FormError::new(curl_sys::CURL_FORMADD_INCOMPLETE));
361                 None
362             }
363             Err(..) => None,
364         }
365     }
366 }
367 
368 impl<'form, 'data> fmt::Debug for Part<'form, 'data> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result369     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
370         // TODO: fill this out more
371         f.debug_struct("Part")
372             .field("name", &self.name)
373             .field("form", &self.form)
374             .finish()
375     }
376 }
377