Skip to content

Commit 0596ebb

Browse files
committed
Add support for CURLFORM_STREAM
Closes #158
1 parent 2bdc4a5 commit 0596ebb

File tree

3 files changed

+112
-2
lines changed

3 files changed

+112
-2
lines changed

src/easy/form.rs

+57-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ use std::ffi::CString;
22
use std::fmt;
33
use std::path::Path;
44

5+
use libc::c_void;
6+
57
use FormError;
68
use curl_sys;
7-
use easy::{list, List};
9+
use easy::list;
10+
use easy::{List, ReadError};
811

912
/// Multipart/formdata for an HTTP POST request.
1013
///
@@ -16,6 +19,7 @@ pub struct Form {
1619
headers: Vec<List>,
1720
buffers: Vec<Vec<u8>>,
1821
strings: Vec<CString>,
22+
streams: Option<Box<StreamNode>>,
1923
}
2024

2125
/// One part in a multipart upload, added to a `Form`.
@@ -26,10 +30,19 @@ pub struct Part<'form, 'data> {
2630
error: Option<FormError>,
2731
}
2832

33+
struct StreamNode {
34+
stream: Box<FnMut(&mut [u8]) -> Result<usize, ReadError>>,
35+
_next: Option<Box<StreamNode>>,
36+
}
37+
2938
pub fn raw(form: &Form) -> *mut curl_sys::curl_httppost {
3039
form.head
3140
}
3241

42+
pub unsafe fn read(raw: *mut c_void, into: &mut [u8]) -> Result<usize, ReadError> {
43+
((*(raw as *mut StreamNode)).stream)(into)
44+
}
45+
3346
impl Form {
3447
/// Creates a new blank form ready for the addition of new data.
3548
pub fn new() -> Form {
@@ -39,6 +52,7 @@ impl Form {
3952
headers: Vec::new(),
4053
buffers: Vec::new(),
4154
strings: Vec::new(),
55+
streams: None,
4256
}
4357
}
4458

@@ -254,6 +268,48 @@ impl<'form, 'data> Part<'form, 'data> {
254268
self
255269
}
256270

271+
/// Tells libcurl to use the callback provided to get data.
272+
///
273+
/// If you want the part to look like a file upload one, set the `filename`
274+
/// parameter as well. Note that when using `stream` the `len` option must
275+
/// also be set with the total expected length of the part unless the
276+
/// formpost is sent chunked encoded in which case it can be `None`.
277+
///
278+
/// The callback provided must live for the `'static` lifetime and must
279+
/// adhere to the `Send` bound.
280+
pub fn stream<F>(&mut self,
281+
len: Option<usize>,
282+
f: F) -> &mut Self
283+
where F: FnMut(&mut [u8]) -> Result<usize, ReadError> + 'static + Send,
284+
{
285+
self._stream(len, Box::new(f));
286+
self
287+
}
288+
289+
fn _stream(&mut self,
290+
len: Option<usize>,
291+
stream: Box<FnMut(&mut [u8]) -> Result<usize, ReadError> + Send>)
292+
{
293+
let mut node = Box::new(StreamNode {
294+
stream: stream,
295+
_next: self.form.streams.take(),
296+
});
297+
let pos = self.array.len() - 1;
298+
let ptr = &mut *node as *mut StreamNode as usize;
299+
assert!(ptr & 1 == 0);
300+
self.array.insert(pos, curl_sys::curl_forms {
301+
option: curl_sys::CURLFORM_STREAM,
302+
value: (ptr | 1) as *mut _,
303+
});
304+
if let Some(len) = len {
305+
self.array.insert(pos + 1, curl_sys::curl_forms {
306+
option: curl_sys::CURLFORM_CONTENTSLENGTH,
307+
value: len as *mut _,
308+
});
309+
}
310+
self.form.streams = Some(node);
311+
}
312+
257313
/// Attempts to add this part to the `Form` that it was created from.
258314
///
259315
/// If any error happens while adding, that error is returned, otherwise

src/easy/handler.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ impl<H: Handler> Easy2<H> {
613613
let cb: curl_sys::curl_read_callback = read_cb::<H>;
614614
self.setopt_ptr(curl_sys::CURLOPT_READFUNCTION, cb as *const _)
615615
.expect("failed to set read callback");
616+
assert!((ptr as usize) & 1 == 0);
616617
self.setopt_ptr(curl_sys::CURLOPT_READDATA, ptr)
617618
.expect("failed to set read callback");
618619

@@ -2783,7 +2784,18 @@ extern fn read_cb<H: Handler>(ptr: *mut c_char,
27832784
panic::catch(|| unsafe {
27842785
let input = slice::from_raw_parts_mut(ptr as *mut u8,
27852786
size * nmemb);
2786-
match (*(data as *mut Inner<H>)).handler.read(input) {
2787+
2788+
// We use a bit of a "tricky encoding" here to handle CURLFORM_STREAM
2789+
// callbacks. If the lower bit of the data pointer is 1 it's a stream
2790+
// callback so we delegate to the `form` module, otherwise we handle
2791+
// that here as part of `Handler`.
2792+
let ret = if data as usize & 1 == 0 {
2793+
(*(data as *mut Inner<H>)).handler.read(input)
2794+
} else {
2795+
let data = data as usize & !1;
2796+
form::read(data as *mut c_void, input)
2797+
};
2798+
match ret {
27872799
Ok(s) => s,
27882800
Err(ReadError::Pause) => {
27892801
curl_sys::CURL_READFUNC_PAUSE

tests/post.rs

+42
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
extern crate curl;
22

3+
use std::io::Read;
34
use std::str;
45
use std::time::Duration;
56

@@ -106,3 +107,44 @@ hello\n\
106107
t!(handle.httppost(form));
107108
t!(handle.perform());
108109
}
110+
111+
#[test]
112+
fn stream() {
113+
let s = Server::new();
114+
s.receive("\
115+
POST / HTTP/1.1\r\n\
116+
Host: 127.0.0.1:$PORT\r\n\
117+
Accept: */*\r\n\
118+
Content-Length: 238\r\n\
119+
Expect: 100-continue\r\n\
120+
Content-Type: multipart/form-data; boundary=--[..]\r\n\
121+
\r\n\
122+
--[..]\r\n\
123+
Content-Disposition: form-data; name=\"foo\"\r\n\
124+
\r\n\
125+
1234\r\n\
126+
--[..]\r\n\
127+
Content-Disposition: form-data; name=\"bar\"\r\n\
128+
\r\n\
129+
5678\r\n\
130+
--[..]\r\n\
131+
");
132+
s.send("HTTP/1.1 200 OK\r\n\r\n");
133+
134+
let mut handle = handle();
135+
let mut form = Form::new();
136+
137+
let mut contents = "1234".as_bytes();
138+
t!(form.part("foo").stream(Some(contents.len()), move |buf| {
139+
Ok(contents.read(buf).unwrap())
140+
}).add());
141+
142+
let mut contents = "5678".as_bytes();
143+
t!(form.part("bar").stream(Some(contents.len()), move |buf| {
144+
Ok(contents.read(buf).unwrap())
145+
}).add());
146+
147+
t!(handle.url(&s.url("/")));
148+
t!(handle.httppost(form));
149+
t!(handle.perform());
150+
}

0 commit comments

Comments
 (0)