From fd328fd3dcefd2b9c4b6c2e40a09215cc9e25efe Mon Sep 17 00:00:00 2001 From: bdbai Date: Mon, 26 May 2025 22:46:27 +0800 Subject: [PATCH 1/6] add mime (WIP) --- Cargo.toml | 1 + curl-sys/Cargo.toml | 1 + curl-sys/lib.rs | 40 ++++++++++ src/easy/handle.rs | 8 ++ src/easy/handler.rs | 26 ++++++- src/easy/mod.rs | 2 +- src/lib.rs | 2 + src/mime.rs | 186 ++++++++++++++++++++++++++++++++++++++++++++ src/mime/handler.rs | 11 +++ 9 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 src/mime.rs create mode 100644 src/mime/handler.rs diff --git a/Cargo.toml b/Cargo.toml index a3efedc3a..dfcda0fcf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/curl-sys/Cargo.toml b/curl-sys/Cargo.toml index 4d3b91828..b429785f2 100644 --- a/curl-sys/Cargo.toml +++ b/curl-sys/Cargo.toml @@ -55,3 +55,4 @@ zlib-ng-compat = ["libz-sys/zlib-ng", "static-curl"] upkeep_7_62_0 = [] poll_7_68_0 = [] ntlm = [] +mime = [] diff --git a/curl-sys/lib.rs b/curl-sys/lib.rs index f71d99aa5..d1110e5ba 100644 --- a/curl-sys/lib.rs +++ b/curl-sys/lib.rs @@ -609,6 +609,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; @@ -1167,6 +1169,44 @@ extern "C" { ) -> CURLMcode; } +#[cfg(feature = "mime")] +mod mime { + use super::*; + + 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, + seekfunc: Option, + freefunc: Option, + 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") } diff --git a/src/easy/handle.rs b/src/easy/handle.rs index 6d074be1f..fc71a3cb6 100644 --- a/src/easy/handle.rs +++ b/src/easy/handle.rs @@ -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". @@ -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 { + self.inner.new_mime() + } + /// Same as [`Easy2::raw`](struct.Easy2.html#method.raw) pub fn raw(&self) -> *mut curl_sys::CURL { self.inner.raw() diff --git a/src/easy/handler.rs b/src/easy/handler.rs index 7be38450e..aa4c65aec 100644 --- a/src/easy/handler.rs +++ b/src/easy/handler.rs @@ -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; @@ -385,6 +387,8 @@ struct Inner { resolve_list: Option, connect_to_list: Option, form: Option
, + #[cfg(feature = "mime")] + mime: Option, error_buf: RefCell>, handler: H, } @@ -595,6 +599,8 @@ impl Easy2 { 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, }), @@ -3371,6 +3377,24 @@ impl Easy2 { } } + /// 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`]. + #[cfg(feature = "mime")] + pub fn new_mime(&mut self) -> Mime { + 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 @@ -3498,7 +3522,7 @@ impl Easy2 { 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(()); } diff --git a/src/easy/mod.rs b/src/easy/mod.rs index 0b0e23a7e..4feb4d48b 100644 --- a/src/easy/mod.rs +++ b/src/easy/mod.rs @@ -10,7 +10,7 @@ mod form; mod handle; mod handler; -mod list; +pub(crate) mod list; mod windows; pub use self::form::{Form, Part}; diff --git a/src/lib.rs b/src/lib.rs index 2965e2bed..3bb143451 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; diff --git a/src/mime.rs b/src/mime.rs new file mode 100644 index 000000000..717ada60c --- /dev/null +++ b/src/mime.rs @@ -0,0 +1,186 @@ +//! 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, +} + +/// 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 Send for Mime<'_, H> {} + +impl<'h, H> Mime<'h, H> { + pub(crate) fn new(easy: &'h mut Easy2) -> 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 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( + &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::

), + Some(seek_cb::

), + Some(free_handler::

), + &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( + 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 Box

)).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( + 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 Box

)).seek(from) as c_int + }) + .unwrap_or(!0) +} + +extern "C" fn free_handler(data: *mut c_void) { + panic::catch(|| unsafe { + let _ = Box::from_raw(data as *mut Box

); + }) + .unwrap_or(()); +} diff --git a/src/mime/handler.rs b/src/mime/handler.rs new file mode 100644 index 000000000..5ba6750ee --- /dev/null +++ b/src/mime/handler.rs @@ -0,0 +1,11 @@ +use std::io::SeekFrom; + +use crate::easy::{ReadError, SeekResult}; + +pub trait PartDataHandler { + fn read(&mut self, data: &mut [u8]) -> Result; + fn seek(&mut self, whence: SeekFrom) -> SeekResult { + let _ = whence; // ignore unused + SeekResult::CantSeek + } +} From b01b12aeb7f869482f48c3a980558f479743fba2 Mon Sep 17 00:00:00 2001 From: bdbai Date: Thu, 29 May 2025 20:29:28 +0800 Subject: [PATCH 2/6] mime tests and handler docs --- src/easy/handler.rs | 13 ++++ src/mime.rs | 7 +- src/mime/handler.rs | 38 +++++++++ tests/mime.rs | 182 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 237 insertions(+), 3 deletions(-) create mode 100644 tests/mime.rs diff --git a/src/easy/handler.rs b/src/easy/handler.rs index aa4c65aec..089b11c5a 100644 --- a/src/easy/handler.rs +++ b/src/easy/handler.rs @@ -3381,6 +3381,19 @@ impl Easy2 { /// /// With the returned [`Mime`] handle, you can add some parts and attach it to the /// easy handle using [`Mime::post`]. + /// + /// # Examples + /// + /// ``` + /// use curl::easy::{Easy, List}; + /// + /// let mut handle = Easy::new(); + /// let mut mime = handle.new_mime(); + /// mime.add_part().name("key1").unwrap().data(b"value1").unwrap(); + /// mime.add_part().name("key2").unwrap().data(b"value2").unwrap(); + /// mime.post().unwrap(); + /// handle.perform().unwrap(); + /// ``` #[cfg(feature = "mime")] pub fn new_mime(&mut self) -> Mime { Mime::new(self) diff --git a/src/mime.rs b/src/mime.rs index 717ada60c..f788f74e0 100644 --- a/src/mime.rs +++ b/src/mime.rs @@ -111,6 +111,7 @@ impl<'m, 'h, H> MimePart<'m, 'h, H> { /// 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(()) @@ -153,7 +154,7 @@ extern "C" fn read_cb( ) -> size_t { panic::catch(|| unsafe { let input = slice::from_raw_parts_mut(ptr as *mut u8, size * nmemb); - match (*(data as *mut Box

)).read(input) { + 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, @@ -173,14 +174,14 @@ extern "C" fn seek_cb( } else { panic!("unknown origin from libcurl: {}", origin); }; - (*(data as *mut Box

)).seek(from) as c_int + (*(data as *mut P)).seek(from) as c_int }) .unwrap_or(!0) } extern "C" fn free_handler(data: *mut c_void) { panic::catch(|| unsafe { - let _ = Box::from_raw(data as *mut Box

); + let _ = Box::from_raw(data as *mut P); }) .unwrap_or(()); } diff --git a/src/mime/handler.rs b/src/mime/handler.rs index 5ba6750ee..a1b5a3657 100644 --- a/src/mime/handler.rs +++ b/src/mime/handler.rs @@ -2,8 +2,46 @@ use std::io::SeekFrom; use crate::easy::{ReadError, SeekResult}; +/// A trait to provide data for a MIME part. pub trait PartDataHandler { + /// Read callback for data uploads. + /// + /// This callback function gets called by libcurl as soon as it needs to + /// read data in order to send it to the peer. + /// + /// Your function must then return the actual number of bytes that it stored + /// in that memory area. Returning 0 will signal end-of-file to the library + /// and cause it to stop the current transfer. + /// + /// If you stop the current transfer by returning 0 "pre-maturely" (i.e + /// before the server expected it, like when you've said you will upload N + /// bytes and you upload less than N bytes), you may experience that the + /// server "hangs" waiting for the rest of the data that won't come. + /// + /// The read callback may return `Err(ReadError::Abort)` to stop the + /// current operation immediately, resulting in a `is_aborted_by_callback` + /// error code from the transfer. + /// + /// The callback can return `Err(ReadError::Pause)` to cause reading from + /// this connection to pause. See `unpause_read` for further details. fn read(&mut self, data: &mut [u8]) -> Result; + + /// User callback for seeking in input stream. + /// + /// This function gets called by libcurl to seek to a certain position in + /// the input stream and can be used to fast forward a file in a resumed + /// upload (instead of reading all uploaded bytes with the normal read + /// function/callback). It is also called to rewind a stream when data has + /// already been sent to the server and needs to be sent again. This may + /// happen when doing a HTTP PUT or POST with a multi-pass authentication + /// method, or when an existing HTTP connection is reused too late and the + /// server closes the connection. + /// + /// The callback function must return `SeekResult::Ok` on success, + /// `SeekResult::Fail` to cause the upload operation to fail or + /// `SeekResult::CantSeek` to indicate that while the seek failed, libcurl + /// is free to work around the problem if possible. The latter can sometimes + /// be done by instead reading from the input or similar. fn seek(&mut self, whence: SeekFrom) -> SeekResult { let _ = whence; // ignore unused SeekResult::CantSeek diff --git a/tests/mime.rs b/tests/mime.rs new file mode 100644 index 000000000..da61776c1 --- /dev/null +++ b/tests/mime.rs @@ -0,0 +1,182 @@ +use curl::{ + easy::{ReadError, SeekResult}, + mime::PartDataHandler, + Version, +}; +use std::{io::SeekFrom, time::Duration}; + +macro_rules! t { + ($e:expr) => { + match $e { + Ok(e) => e, + Err(e) => panic!("{} failed with {:?}", stringify!($e), e), + } + }; +} + +use curl::easy::{Easy, List}; + +use crate::server::Server; +mod server; + +fn handle() -> Easy { + let mut e = Easy::new(); + t!(e.timeout(Duration::new(20, 0))); + let mut list = List::new(); + t!(list.append("Expect:")); + t!(e.http_headers(list)); + e +} + +fn multipart_boundary_size() -> usize { + // Versions before 8.4.0 used a smaller multipart mime boundary, so the + // exact content-length will differ between versions. + if Version::get().version_num() >= 0x80400 { + 148 + } else { + 136 + } +} + +#[test] +fn data() { + let s = Server::new(); + s.receive(&format!( + "\ + POST / HTTP/1.1\r\n\ + Host: 127.0.0.1:$PORT\r\n\ + Accept: */*\r\n\ + Content-Length: {}\r\n\ + Content-Type: multipart/form-data; boundary=--[..]\r\n\ + \r\n\ + --[..]\r\n\ + Content-Disposition: form-data; name=\"foo\"; filename=\"data.txt\"\r\n\ + Content-Type: text/plain\r\n\ + Content-Language: en-US\r\n\ + \r\n\ + 1234\r\n\ + --[..]\r\n", + multipart_boundary_size() + 78 + )); + s.send("HTTP/1.1 200 OK\r\n\r\n"); + + let mut handle = handle(); + let mime = handle.new_mime(); + let mut part = mime.add_part(); + t!(part.name("foo")); + t!(part.data(b"1234")); + t!(part.content_type("text/plain")); + let mut part_headers = List::new(); + part_headers.append("Content-Language: en-US").unwrap(); + t!(part.headers(part_headers)); + t!(part.filename("data.txt")); + t!(mime.post()); + t!(handle.url(&s.url("/"))); + t!(handle.perform()); +} + +#[test] +fn two_parts() { + let s = Server::new(); + s.receive(&format!( + "\ + POST / HTTP/1.1\r\n\ + Host: 127.0.0.1:$PORT\r\n\ + Accept: */*\r\n\ + Content-Length: {}\r\n\ + Content-Type: multipart/form-data; boundary=--[..]\r\n\ + \r\n\ + --[..]\r\n\ + Content-Disposition: form-data; name=\"foo\"\r\n\ + \r\n\ + 1234\r\n\ + --[..]\r\n\ + Content-Disposition: form-data; name=\"bar\"\r\n\ + \r\n\ + 5678\r\n\ + --[..]\r\n", + multipart_boundary_size() + 108 + )); + s.send("HTTP/1.1 200 OK\r\n\r\n"); + + let mut handle = handle(); + let mime = handle.new_mime(); + let mut part = mime.add_part(); + t!(part.name("foo")); + t!(part.data(b"1234")); + part = mime.add_part(); + t!(part.name("bar")); + t!(part.data(b"5678")); + t!(mime.post()); + t!(handle.url(&s.url("/"))); + t!(handle.perform()); +} + +#[test] +fn handler() { + let s = Server::new(); + s.receive(&format!( + "\ + POST / HTTP/1.1\r\n\ + Host: 127.0.0.1:$PORT\r\n\ + Accept: */*\r\n\ + Content-Length: {}\r\n\ + Content-Type: multipart/form-data; boundary=--[..]\r\n\ + \r\n\ + --[..]\r\n\ + Content-Disposition: form-data; name=\"foo\"\r\n\ + \r\n\ + 1234\r\n\ + --[..]\r\n", + multipart_boundary_size() + 6 + )); + s.send("HTTP/1.1 200 OK\r\n\r\n"); + + let mut handle = handle(); + let mime = handle.new_mime(); + let mut part = mime.add_part(); + t!(part.name("foo")); + let buf = b"1234".to_vec(); + t!(part.data_handler(buf.len(), ByteVecHandler::new(buf))); + t!(mime.post()); + t!(handle.url(&s.url("/"))); + t!(handle.perform()); +} + +#[derive(Debug)] +struct ByteVecHandler { + data: Vec, + pos: usize, +} + +impl ByteVecHandler { + fn new(data: Vec) -> Self { + Self { data, pos: 0 } + } +} + +impl PartDataHandler for ByteVecHandler { + fn read(&mut self, data: &mut [u8]) -> Result { + if self.pos > self.data.len() { + return Ok(0); + } + let remaining_data = &mut self.data[self.pos..]; + let len = remaining_data.len().min(data.len()); + data[..len].copy_from_slice(&remaining_data[..len]); + self.pos += len; + Ok(len) + } + + fn seek(&mut self, whence: SeekFrom) -> SeekResult { + match whence { + SeekFrom::Start(pos) => { + if pos > self.data.len() as u64 { + return SeekResult::Fail; + } + self.pos = pos as usize; + } + SeekFrom::End(_) | SeekFrom::Current(_) => return SeekResult::CantSeek, + } + SeekResult::Ok + } +} From f468d3a3210ef695c4e8ebef37a826a435e66f79 Mon Sep 17 00:00:00 2001 From: bdbai Date: Thu, 29 May 2025 20:39:05 +0800 Subject: [PATCH 3/6] tweak mime features for ci --- ci/run.sh | 1 + tests/mime.rs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ci/run.sh b/ci/run.sh index 82b547f94..bd06216ae 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -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 mime $features # Note that `-Clink-dead-code` is passed here to suppress `--gc-sections` to # help confirm that we're compiling everything necessary for curl itself. diff --git a/tests/mime.rs b/tests/mime.rs index da61776c1..1f0fda221 100644 --- a/tests/mime.rs +++ b/tests/mime.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "mime")] + use curl::{ easy::{ReadError, SeekResult}, mime::PartDataHandler, From cabfff5fd6f9b3a78c718e8dde914e0a4d4668aa Mon Sep 17 00:00:00 2001 From: bdbai Date: Thu, 29 May 2025 20:48:34 +0800 Subject: [PATCH 4/6] fix doctest --- src/easy/handler.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/easy/handler.rs b/src/easy/handler.rs index 089b11c5a..d59ab1326 100644 --- a/src/easy/handler.rs +++ b/src/easy/handler.rs @@ -3384,14 +3384,22 @@ impl Easy2 { /// /// # Examples /// - /// ``` + /// ```no_run /// use curl::easy::{Easy, List}; /// /// let mut handle = Easy::new(); - /// let mut mime = handle.new_mime(); - /// mime.add_part().name("key1").unwrap().data(b"value1").unwrap(); - /// mime.add_part().name("key2").unwrap().data(b"value2").unwrap(); + /// 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")] From 770cffc043d18af42bf191b313d5798839bce3b2 Mon Sep 17 00:00:00 2001 From: bdbai Date: Thu, 29 May 2025 20:55:56 +0800 Subject: [PATCH 5/6] use static-curl in ci for mime tests --- ci/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/run.sh b/ci/run.sh index bd06216ae..564fa9106 100755 --- a/ci/run.sh +++ b/ci/run.sh @@ -29,7 +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 mime $features + cargo test --target $TARGET --features static-curl,mime $features # Note that `-Clink-dead-code` is passed here to suppress `--gc-sections` to # help confirm that we're compiling everything necessary for curl itself. From 8487da64d32ee322ade1710951d09905fd43ad21 Mon Sep 17 00:00:00 2001 From: bdbai Date: Thu, 29 May 2025 21:08:33 +0800 Subject: [PATCH 6/6] feature guard CURLOPT_MIMEPOST --- curl-sys/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/curl-sys/lib.rs b/curl-sys/lib.rs index d1110e5ba..e7a079ac6 100644 --- a/curl-sys/lib.rs +++ b/curl-sys/lib.rs @@ -609,8 +609,6 @@ 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; @@ -1173,6 +1171,8 @@ extern "C" { mod mime { use super::*; + pub const CURLOPT_MIMEPOST: CURLoption = CURLOPTTYPE_OBJECTPOINT + 269; + pub enum curl_mime {} pub enum curl_mimepart {}