1use std::ffi::CString;
2use std::fmt;
3use std::path::Path;
4use std::ptr;
5
6use crate::easy::{list, List};
7use crate::FormError;
8use curl_sys;
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.
14pub 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`.
23pub 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
30pub fn raw(form: &Form) -> *mut curl_sys::curl_httppost {
31 form.head
32}
33
34impl Form {
35 /// Creates a new blank form ready for the addition of new data.
36 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.
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
63impl fmt::Debug for Form {
64 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65 // TODO: fill this out more
66 f.debug_struct("Form").field(name:"fields", &"...").finish()
67 }
68}
69
70impl Drop for Form {
71 fn drop(&mut self) {
72 unsafe {
73 curl_sys::curl_formfree(self.head);
74 }
75 }
76}
77
78impl<'form, 'data> Part<'form, 'data> {
79 /// A pointer to the contents of this part, the actual data to send away.
80 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.
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
127 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.
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
169 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.
191 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.
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
221 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.
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
254 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.
295 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.
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)]
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)]
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
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
368impl<'form, 'data> fmt::Debug for Part<'form, 'data> {
369 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
370 // TODO: fill this out more
371 f&mut DebugStruct<'_, '_>.debug_struct("Part")
372 .field("name", &self.name)
373 .field(name:"form", &self.form)
374 .finish()
375 }
376}
377