curl/easy/
form.rs

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