Skip to content

Mime support reworked #602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ zlib-ng-compat = ["curl-sys/zlib-ng-compat", "static-curl"]
upkeep_7_62_0 = ["curl-sys/upkeep_7_62_0"]
poll_7_68_0 = ["curl-sys/poll_7_68_0"]
ntlm = ["curl-sys/ntlm"]
mime = ["curl-sys/mime"]

[[test]]
name = "atexit"
Expand Down
1 change: 1 addition & 0 deletions ci/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ if [ -z "$NO_RUN" ]; then
cargo test --target $TARGET $features
cargo test --target $TARGET --features static-curl $features
cargo test --target $TARGET --features static-curl,protocol-ftp $features
cargo test --target $TARGET --features static-curl,mime $features
cargo test --target $TARGET --features static-curl,http2 $features

# Note that `-Clink-dead-code` is passed here to suppress `--gc-sections` to
Expand Down
1 change: 1 addition & 0 deletions curl-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,4 @@ zlib-ng-compat = ["libz-sys/zlib-ng", "static-curl"]
upkeep_7_62_0 = []
poll_7_68_0 = []
ntlm = []
mime = []
40 changes: 40 additions & 0 deletions curl-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,46 @@ extern "C" {
) -> CURLMcode;
}

#[cfg(feature = "mime")]
mod mime {
use super::*;

pub const CURLOPT_MIMEPOST: CURLoption = CURLOPTTYPE_OBJECTPOINT + 269;

pub enum curl_mime {}
pub enum curl_mimepart {}

extern "C" {
pub fn curl_mime_init(easy_handle: *mut CURL) -> *mut curl_mime;
pub fn curl_mime_free(mime_handle: *mut curl_mime);
pub fn curl_mime_addpart(mime_handle: *mut curl_mime) -> *mut curl_mimepart;
pub fn curl_mime_data(
part: *mut curl_mimepart,
data: *const c_char,
datasize: size_t,
) -> CURLcode;
pub fn curl_mime_name(part: *mut curl_mimepart, name: *const c_char) -> CURLcode;
pub fn curl_mime_filename(part: *mut curl_mimepart, filename: *const c_char) -> CURLcode;
pub fn curl_mime_type(part: *mut curl_mimepart, mimetype: *const c_char) -> CURLcode;
pub fn curl_mime_data_cb(
part: *mut curl_mimepart,
datasize: curl_off_t,
readfunc: Option<curl_read_callback>,
seekfunc: Option<curl_seek_callback>,
freefunc: Option<curl_free_callback>,
arg: *mut c_void,
) -> CURLcode;
pub fn curl_mime_subparts(part: *mut curl_mimepart, subparts: *mut curl_mime) -> CURLcode;
pub fn curl_mime_headers(
part: *mut curl_mimepart,
headers: *mut curl_slist,
take_ownership: c_int,
) -> CURLcode;
}
}
#[cfg(feature = "mime")]
pub use mime::*;

pub fn rust_crate_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
Expand Down
8 changes: 8 additions & 0 deletions src/easy/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ use crate::easy::handler::{Auth, NetRc, PostRedirections, ProxyType, SslOpt};
use crate::easy::handler::{HttpVersion, IpResolve, SslVersion, TimeCondition};
use crate::easy::{Easy2, Handler};
use crate::easy::{Form, List};
#[cfg(feature = "mime")]
use crate::mime::Mime;
use crate::Error;

/// Raw bindings to a libcurl "easy session".
Expand Down Expand Up @@ -1460,6 +1462,12 @@ impl Easy {
self.inner.send(data)
}

/// Same as [`Easy2::new_mime`](struct.Easy2.html#method.mime)
#[cfg(feature = "mime")]
pub fn new_mime(&mut self) -> Mime<EasyData> {
self.inner.new_mime()
}

/// Same as [`Easy2::raw`](struct.Easy2.html#method.raw)
pub fn raw(&self) -> *mut curl_sys::CURL {
self.inner.raw()
Expand Down
47 changes: 46 additions & 1 deletion src/easy/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ use crate::easy::form;
use crate::easy::list;
use crate::easy::windows;
use crate::easy::{Form, List};
#[cfg(feature = "mime")]
use crate::mime::{Mime, MimeHandle};
use crate::panic;
use crate::Error;

Expand Down Expand Up @@ -385,6 +387,8 @@ struct Inner<H> {
resolve_list: Option<List>,
connect_to_list: Option<List>,
form: Option<Form>,
#[cfg(feature = "mime")]
mime: Option<MimeHandle>,
error_buf: RefCell<Vec<u8>>,
handler: H,
}
Expand Down Expand Up @@ -595,6 +599,8 @@ impl<H: Handler> Easy2<H> {
resolve_list: None,
connect_to_list: None,
form: None,
#[cfg(feature = "mime")]
mime: None,
error_buf: RefCell::new(vec![0; curl_sys::CURL_ERROR_SIZE]),
handler,
}),
Expand Down Expand Up @@ -3366,6 +3372,45 @@ impl<H> Easy2<H> {
}
}

/// Create a new MIME handle from this easy handle.
///
/// With the returned [`Mime`] handle, you can add some parts and attach it to the
/// easy handle using [`Mime::post`].
///
/// # Examples
///
/// ```no_run
/// use curl::easy::{Easy, List};
///
/// let mut handle = Easy::new();
/// let mime = handle.new_mime();
///
/// let mut part = mime.add_part();
/// part.name("key1").unwrap();
/// part.data(b"value1").unwrap();
/// let mut part = mime.add_part();
/// part.name("key2").unwrap();
/// part.data(b"value2").unwrap();
///
/// mime.post().unwrap();
///
/// handle.url("https://httpbin.dev/post").unwrap();
/// handle.perform().unwrap();
/// ```
#[cfg(feature = "mime")]
pub fn new_mime(&mut self) -> Mime<H> {
Mime::new(self)
}

#[cfg(feature = "mime")]
pub(crate) fn set_mime(&mut self, mime: MimeHandle) -> Result<(), Error> {
let code =
unsafe { curl_sys::curl_easy_setopt(self.raw(), curl_sys::CURLOPT_MIMEPOST, mime.0) };
self.cvt(code)?;
self.inner.mime = Some(mime);
Ok(())
}

/// Get a pointer to the raw underlying CURL handle.
pub fn raw(&self) -> *mut curl_sys::CURL {
self.inner.handle
Expand Down Expand Up @@ -3493,7 +3538,7 @@ impl<H> Easy2<H> {
Some(msg)
}

fn cvt(&self, rc: curl_sys::CURLcode) -> Result<(), Error> {
pub(crate) fn cvt(&self, rc: curl_sys::CURLcode) -> Result<(), Error> {
if rc == curl_sys::CURLE_OK {
return Ok(());
}
Expand Down
2 changes: 1 addition & 1 deletion src/easy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
mod form;
mod handle;
mod handler;
mod list;
pub(crate) mod list;
mod windows;

pub use self::form::{Form, Part};
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ pub use crate::version::{Protocols, Version};
mod version;

pub mod easy;
#[cfg(feature = "mime")]
pub mod mime;
pub mod multi;
mod panic;

Expand Down
187 changes: 187 additions & 0 deletions src/mime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
//! MIME handling in libcurl.

use std::{
ffi::{c_void, CString},
io::SeekFrom,
slice,
};

use crate::{
easy::{list::raw as list_raw, Easy2, List, ReadError},
panic, Error,
};

mod handler;

pub use handler::PartDataHandler;
use libc::{c_char, c_int, size_t};

#[derive(Debug)]
pub(crate) struct MimeHandle(pub(crate) *mut curl_sys::curl_mime);

/// A MIME handle that holds MIME parts.
#[must_use = "Mime is not attached to the Easy handle until you call `post()` on it"]
#[derive(Debug)]
pub struct Mime<'h, H> {
handle: MimeHandle,
easy: &'h mut Easy2<H>,
}

/// A MIME part associated with a MIME handle.
#[derive(Debug)]
pub struct MimePart<'m, 'h, H> {
raw: *mut curl_sys::curl_mimepart,
mime: &'m Mime<'h, H>,
}

unsafe impl<H> Send for Mime<'_, H> {}

impl<'h, H> Mime<'h, H> {
pub(crate) fn new(easy: &'h mut Easy2<H>) -> Self {
let raw = unsafe { curl_sys::curl_mime_init(easy.raw()) };
assert!(!raw.is_null());
Self {
handle: MimeHandle(raw),
easy,
}
}

/// Creates a new MIME part associated to this MIME handle.
pub fn add_part<'m>(&'m self) -> MimePart<'m, 'h, H> {
MimePart::new(self)
}

/// Returns the raw MIME handle pointer.
pub fn raw(&self) -> *mut curl_sys::curl_mime {
self.handle.0
}

/// Pass the MIME handle to the originating Easy handle to post an HTTP form.
///
/// This option corresponds to `CURLOPT_MIMEPOST`.
pub fn post(self) -> Result<(), Error> {
self.easy.set_mime(self.handle)?;
Ok(())
}
}

impl<'m, 'h, H> MimePart<'m, 'h, H> {
fn new(mime: &'m Mime<'h, H>) -> Self {
let raw = unsafe { curl_sys::curl_mime_addpart(mime.handle.0) };
assert!(!raw.is_null());
Self { raw, mime }
}

/// Returns the raw MIME part pointer.
pub fn raw(&self) -> *mut curl_sys::curl_mimepart {
self.raw
}

/// Sets the data of the content of this MIME part.
pub fn data(&mut self, data: &[u8]) -> Result<(), Error> {
let code =
unsafe { curl_sys::curl_mime_data(self.raw, data.as_ptr() as *const _, data.len()) };
self.mime.easy.cvt(code)?;
Ok(())
}

/// Sets the name of this MIME part.
pub fn name(&mut self, name: &str) -> Result<(), Error> {
let name = CString::new(name)?;
let code = unsafe { curl_sys::curl_mime_name(self.raw, name.as_ptr()) };
self.mime.easy.cvt(code)?;
Ok(())
}

/// Sets the filename of this MIME part.
pub fn filename(&mut self, filename: &str) -> Result<(), Error> {
let filename = CString::new(filename)?;
let code = unsafe { curl_sys::curl_mime_filename(self.raw, filename.as_ptr()) };
self.mime.easy.cvt(code)?;
Ok(())
}

/// Sets the content type of this MIME part.
pub fn content_type(&mut self, content_type: &str) -> Result<(), Error> {
let content_type = CString::new(content_type)?;
let code = unsafe { curl_sys::curl_mime_type(self.raw, content_type.as_ptr()) };
self.mime.easy.cvt(code)?;
Ok(())
}

/// Sets the list of headers of this MIME part.
pub fn headers(&mut self, header_list: List) -> Result<(), Error> {
let header_list = std::mem::ManuallyDrop::new(header_list);
let code = unsafe { curl_sys::curl_mime_headers(self.raw, list_raw(&header_list), 1) };
self.mime.easy.cvt(code)?;
Ok(())
}

/// Sets the handler that provides content data for this MIME part.
pub fn data_handler<P: PartDataHandler + Send + 'static>(
&mut self,
size: usize,
handler: P,
) -> Result<(), Error> {
let mut inner = Box::new(handler);
let code = unsafe {
curl_sys::curl_mime_data_cb(
self.raw,
size as curl_sys::curl_off_t,
Some(read_cb::<P>),
Some(seek_cb::<P>),
Some(free_handler::<P>),
&mut *inner as *mut _ as *mut c_void,
)
};
self.mime.easy.cvt(code)?;
Box::leak(inner);
Ok(())
}
}

impl Drop for MimeHandle {
fn drop(&mut self) {
unsafe { curl_sys::curl_mime_free(self.0) }
}
}

extern "C" fn read_cb<P: PartDataHandler + Send + 'static>(
ptr: *mut c_char,
size: size_t,
nmemb: size_t,
data: *mut c_void,
) -> size_t {
panic::catch(|| unsafe {
let input = slice::from_raw_parts_mut(ptr as *mut u8, size * nmemb);
match (*(data as *mut P)).read(input) {
Ok(s) => s,
Err(ReadError::Pause) => curl_sys::CURL_READFUNC_PAUSE,
Err(ReadError::Abort) => curl_sys::CURL_READFUNC_ABORT,
}
})
.unwrap_or(!0)
}

extern "C" fn seek_cb<P: PartDataHandler + Send + 'static>(
data: *mut c_void,
offset: curl_sys::curl_off_t,
origin: c_int,
) -> c_int {
panic::catch(|| unsafe {
let from = if origin == libc::SEEK_SET {
SeekFrom::Start(offset as u64)
} else {
panic!("unknown origin from libcurl: {}", origin);
};
(*(data as *mut P)).seek(from) as c_int
})
.unwrap_or(!0)
}

extern "C" fn free_handler<P: PartDataHandler + Send + 'static>(data: *mut c_void) {
panic::catch(|| unsafe {
let _ = Box::from_raw(data as *mut P);
})
.unwrap_or(());
}
Loading