Skip to content
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

add mime support (form api is deprecated according to libcurl) #537

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion ci/Dockerfile-linux64-curl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ubuntu:16.04
FROM ubuntu:18.04

RUN apt-get update
RUN apt-get install -y --no-install-recommends \
Expand Down
18 changes: 18 additions & 0 deletions curl-sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ pub enum curl_httppost {
// pub userp: *mut c_void,
}

pub enum curl_mime {}
pub enum curl_mimepart {}

// pub const HTTPPOST_FILENAME: c_long = 1 << 0;
// pub const HTTPPOST_READFILE: c_long = 1 << 1;
// pub const HTTPPOST_PTRNAME: c_long = 1 << 2;
Expand Down Expand Up @@ -607,6 +610,8 @@ pub const CURLOPT_PROXY_SSL_OPTIONS: CURLoption = CURLOPTTYPE_LONG + 261;

pub const CURLOPT_ABSTRACT_UNIX_SOCKET: CURLoption = CURLOPTTYPE_OBJECTPOINT + 264;

pub const CURLOPT_MIMEPOST: CURLoption = CURLOPTTYPE_OBJECTPOINT + 269;

pub const CURLOPT_DOH_URL: CURLoption = CURLOPTTYPE_OBJECTPOINT + 279;
pub const CURLOPT_UPLOAD_BUFFERSIZE: CURLoption = CURLOPTTYPE_LONG + 280;

Expand Down Expand Up @@ -1161,6 +1166,19 @@ extern "C" {
sockfd: curl_socket_t,
sockp: *mut c_void,
) -> CURLMcode;

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(
mimepart: *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_subparts(part: *mut curl_mimepart, subparts: *mut curl_mime) -> CURLcode;
}

pub fn rust_crate_version() -> &'static str {
Expand Down
6 changes: 6 additions & 0 deletions src/easy/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use libc::c_void;
use crate::easy::handler::{self, InfoType, ReadError, SeekResult, WriteError};
use crate::easy::handler::{Auth, NetRc, PostRedirections, ProxyType, SslOpt};
use crate::easy::handler::{HttpVersion, IpResolve, SslVersion, TimeCondition};
use crate::easy::mime::Mime;
use crate::easy::{Easy2, Handler};
use crate::easy::{Form, List};
use crate::Error;
Expand Down Expand Up @@ -1470,6 +1471,11 @@ impl Easy {
pub fn take_error_buf(&self) -> Option<String> {
self.inner.take_error_buf()
}

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

impl EasyData {
Expand Down
21 changes: 21 additions & 0 deletions src/easy/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use socket2::Socket;

use crate::easy::form;
use crate::easy::list;
use crate::easy::mime::Mime;
use crate::easy::windows;
use crate::easy::{Form, List};
use crate::panic;
Expand Down Expand Up @@ -378,6 +379,8 @@ pub fn ssl_ctx(cx: *mut c_void) -> Result<(), Error> {
/// ```
pub struct Easy2<H> {
inner: Box<Inner<H>>,
/// Mime handles to free upon drop
mimes: Vec<*mut curl_sys::curl_mime>,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only one mime object should be attached to an easy handle at a time since CURLOPT_MIMEPOST replaces any previously assigned mime, so this doesn't seem like it needs to be a Vec.

This should also probably go inside Inner like everything else.

I would also prefer to avoid a raw pointer here since that requires multiple implementations handling the calling of curl_mime_free in multiple places. I wonder if there is a way of keeping the drop code in the Mime struct?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Done.

}

struct Inner<H> {
Expand Down Expand Up @@ -599,6 +602,7 @@ impl<H: Handler> Easy2<H> {
error_buf: RefCell::new(vec![0; curl_sys::CURL_ERROR_SIZE]),
handler,
}),
mimes: vec![],
};
ret.default_configure();
ret
Expand Down Expand Up @@ -3509,6 +3513,20 @@ impl<H> Easy2<H> {
}
Err(err)
}

/// Create a mime handle attached to this [Easy2] instance.
pub fn add_mime(&mut self) -> Mime<H> {
Mime::new(self)
}

pub(crate) fn mimepost(&mut self, mime_handle: *mut curl_sys::curl_mime) -> Result<(), Error> {
self.mimes.push(mime_handle);

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

impl<H: fmt::Debug> fmt::Debug for Easy2<H> {
Expand All @@ -3524,6 +3542,9 @@ impl<H> Drop for Easy2<H> {
fn drop(&mut self) {
unsafe {
curl_sys::curl_easy_cleanup(self.inner.handle);
for &mime_handle in self.mimes.iter() {
curl_sys::curl_mime_free(mime_handle);
}
}
}
}
Expand Down
112 changes: 112 additions & 0 deletions src/easy/mime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
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,
};
use std::ffi::CString;
use std::marker::PhantomData;
use std::ptr::null_mut;

#[derive(Debug)]
pub struct Mime<'e, E> {
handle: *mut curl_sys::curl_mime,
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());

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)
}

/// Append a new empty part to a mime structure
pub fn add_part(&mut self) -> MimePart<'a> {
MimePart::new(self)
}
}

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,
// attach to the lifetime of our [Mime] handle, but without taking ownership
_lifetime: PhantomData<&'a ()>,
}

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

Self {
handle,
_lifetime: Default::default(),
}
}

/// Set a mime part's body data
pub fn set_data(self, data: impl AsRef<[u8]>) -> Result<Self, Error> {
let data = data.as_ref();
let code = unsafe { curl_mime_data(self.handle, data.as_ptr() as *const _, data.len()) };
code_ok(code).map(|_| self)
}

/// Set a mime part's name
///
/// # Panics
/// If `name` contains nul bytes, panic will occur.
pub fn set_name(self, name: &str) -> Result<Self, Error> {
let data = CString::new(name).unwrap();
let code = unsafe { curl_mime_name(self.handle, data.as_ptr()) };
code_ok(code).map(|_| self)
}

/// Set a mime part's remote file name
///
/// # Panics
/// If `filename` contains nul bytes, panic will occur.
pub fn set_filename(self, filename: &str) -> Result<Self, Error> {
let data = CString::new(filename).unwrap();
let code = unsafe { curl_mime_filename(self.handle, data.as_ptr()) };
code_ok(code).map(|_| self)
}

/// Set a mime part's content type
///
/// # Panics
/// If `content_type` contains nul bytes, panic will occur.
pub fn set_content_type(self, content_type: &str) -> Result<Self, Error> {
let data = CString::new(content_type).unwrap();
let code = unsafe { curl_mime_type(self.handle, data.as_ptr()) };
code_ok(code).map(|_| self)
}
}

fn code_ok(code: CURLcode) -> Result<(), Error> {
if code == CURLE_OK {
Ok(())
} else {
Err(Error::new(code))
}
}
1 change: 1 addition & 0 deletions src/easy/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ mod form;
mod handle;
mod handler;
mod list;
mod mime;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mime is kinda its own API that isn't specific to the easy API, so I would make mime a top-level module.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see what you're saying about "kinda its own API".
However, mime API doesn't have a meaning without the easy API, quite similar to the form API. So basically, I've put the mime module in the same level as the form module.

I don't mind extracting it, just thought it is worth double checking this before doing so.

Your call though, I'll do what you ask :)

mod windows;

pub use self::form::{Form, Part};
Expand Down
Loading