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(name:"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&mut DebugStruct<'_, '_>.debug_struct("Part")
371 .field("name", &self.name)
372 .field(name:"form", &self.form)
373 .finish()
374 }
375}
376