1 | use std::ffi::CString; |
2 | use std::fmt; |
3 | use std::path::Path; |
4 | use std::ptr; |
5 | |
6 | use crate::easy::{list, List}; |
7 | use crate::FormError; |
8 | use 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. |
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 | |
30 | 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. |
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 | |
63 | impl 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 | |
70 | impl Drop for Form { |
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. |
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 | |
368 | impl<'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 | |