Skip to content

Commit

Permalink
clean up relationship between Mime & Easy2 objects
Browse files Browse the repository at this point in the history
 - The raw mime handle is now wrapped by a rust struct which will call curl_mime_free() upon Drop
 - Easy2 now keeps the MimeHandle as an Option instead of Vec
 - The above Option is kept inside Inner instead of directly under Easy2
  • Loading branch information
tsnoam committed Nov 27, 2023
1 parent e37a551 commit 2994ec7
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 39 deletions.
31 changes: 15 additions & 16 deletions src/easy/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use socket2::Socket;
use crate::easy::form;
use crate::easy::list;
#[cfg(feature = "mime")]
use crate::easy::mime::Mime;
use crate::easy::mime::{Mime, MimeHandle};
use crate::easy::windows;
use crate::easy::{Form, List};
use crate::panic;
Expand Down Expand Up @@ -380,9 +380,6 @@ pub fn ssl_ctx(cx: *mut c_void) -> Result<(), Error> {
/// ```
pub struct Easy2<H> {
inner: Box<Inner<H>>,
#[cfg(feature = "mime")]
/// Mime handles to free upon drop
mimes: Vec<*mut curl_sys::curl_mime>,
}

struct Inner<H> {
Expand All @@ -393,6 +390,9 @@ struct Inner<H> {
form: Option<Form>,
error_buf: RefCell<Vec<u8>>,
handler: H,
#[cfg(feature = "mime")]
/// [MimeHandle] object to drop when it's safe
mime: Option<MimeHandle>,
}

unsafe impl<H: Send> Send for Inner<H> {}
Expand Down Expand Up @@ -603,9 +603,9 @@ impl<H: Handler> Easy2<H> {
form: None,
error_buf: RefCell::new(vec![0; curl_sys::CURL_ERROR_SIZE]),
handler,
#[cfg(feature = "mime")]
mime: Default::default(),
}),
#[cfg(feature = "mime")]
mimes: vec![],
};
ret.default_configure();
ret
Expand Down Expand Up @@ -3525,12 +3525,16 @@ impl<H> Easy2<H> {
Mime::new(self)
}

pub(crate) fn mimepost(&mut self, mime_handle: *mut curl_sys::curl_mime) -> Result<(), Error> {
self.mimes.push(mime_handle);
pub(super) fn mimepost(&mut self, mime: MimeHandle) -> Result<(), Error> {
assert!(self.inner.mime.is_none());

let rc =
unsafe { curl_sys::curl_easy_setopt(self.raw(), curl_sys::CURLOPT_MIMEPOST, mime.0) };

if rc == curl_sys::CURLE_OK {
self.inner.mime = Some(mime);
}

let rc = unsafe {
curl_sys::curl_easy_setopt(self.raw(), curl_sys::CURLOPT_MIMEPOST, mime_handle)
};
self.cvt(rc)
}
}
Expand All @@ -3548,11 +3552,6 @@ impl<H> Drop for Easy2<H> {
fn drop(&mut self) {
unsafe {
curl_sys::curl_easy_cleanup(self.inner.handle);

#[cfg(feature = "mime")]
for &mime_handle in self.mimes.iter() {
curl_sys::curl_mime_free(mime_handle);
}
}
}
}
Expand Down
81 changes: 58 additions & 23 deletions src/easy/mime.rs
Original file line number Diff line number Diff line change
@@ -1,35 +1,49 @@
use crate::easy::Easy2;
use crate::error::Error;
use curl_sys::{
curl_mime_addpart, curl_mime_data, curl_mime_filename, curl_mime_free, curl_mime_init,
curl_mime_name, curl_mime_type, curl_mimepart, CURLcode, CURLE_OK,
curl_mime, curl_mime_addpart, curl_mime_data, curl_mime_filename, curl_mime_free,
curl_mime_init, curl_mime_name, curl_mime_type, curl_mimepart, CURLcode, CURL, CURLE_OK,
};
use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr::null_mut;

#[derive(Debug)]
pub(super) struct MimeHandle(pub *mut curl_mime);

impl MimeHandle {
fn new(easy: *mut CURL) -> Self {
let handle = unsafe { curl_mime_init(easy) };
assert!(!handle.is_null());

Self(handle)
}
}

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

#[derive(Debug)]
pub struct Mime<'e, E> {
handle: *mut curl_sys::curl_mime,
pub(super) handle: MimeHandle,
easy: &'e mut Easy2<E>,
}

impl<'a, T> Mime<'a, T> {
/// Create a mime handle
pub(crate) fn new(easy: &'a mut Easy2<T>) -> Self {
let handle = unsafe { curl_mime_init(easy.raw()) };
assert!(!handle.is_null());
pub(super) fn new(easy: &'a mut Easy2<T>) -> Self {
let handle = MimeHandle::new(easy.raw());

Self { handle, easy }
}

/// Finalize creation of a mime post.
pub fn post(mut self) -> Result<(), Error> {
// once giving the mime handle to `Easy2` it is now their responsibility to free the handle.
// so we need to make sure `Drop` below won't try to free it.
let mime_handle = self.handle;
self.handle = null_mut();
self.easy.mimepost(mime_handle)
pub fn post(self) -> Result<(), Error> {
// We give ownership on `MimeHandle` to `Easy2`. `Easy2` will keep record of this object
// until it is safe to free (drop) it.
self.easy.mimepost(self.handle)
}

/// Append a new empty part to a mime structure
Expand All @@ -38,15 +52,6 @@ impl<'a, T> Mime<'a, T> {
}
}

impl<E> Drop for Mime<'_, E> {
fn drop(&mut self) {
// we only need to free mime handles which hadn't been given to the ownership of `Easy2`.
if !self.handle.is_null() {
unsafe { curl_mime_free(self.handle) }
}
}
}

#[derive(Debug)]
pub struct MimePart<'a> {
handle: *mut curl_mimepart,
Expand All @@ -56,7 +61,7 @@ pub struct MimePart<'a> {

impl<'a> MimePart<'a> {
fn new<E>(mime: &mut Mime<E>) -> Self {
let handle = unsafe { curl_mime_addpart(mime.handle) };
let handle = unsafe { curl_mime_addpart(mime.handle.0) };
assert!(!handle.is_null());

Self {
Expand Down Expand Up @@ -110,3 +115,33 @@ fn code_ok(code: CURLcode) -> Result<(), Error> {
Err(Error::new(code))
}
}

#[cfg(test)]
mod tests {
use crate::easy::Easy;

/// Trivial test which checks that objects can be used as planned.
#[test]
fn test_ownership() {
let mut easy = Easy::new();
let mut mime = easy.add_mime();

for i in 1..5 {
let name = format!("name{i}");
let data = format!("data{i}");
let fname = format!("fname{i}");

mime.add_part()
.set_data(name)
.unwrap()
.set_data(data)
.unwrap()
.set_filename(&fname)
.unwrap()
.set_content_type("plain/text")
.unwrap();
}

mime.post().unwrap();
}
}

0 comments on commit 2994ec7

Please sign in to comment.