diff --git a/src/easy/handler.rs b/src/easy/handler.rs index d9aa7b6ff..8a6b2eef2 100644 --- a/src/easy/handler.rs +++ b/src/easy/handler.rs @@ -376,9 +376,51 @@ pub fn ssl_ctx(cx: *mut c_void) -> Result<(), Error> { /// println!("{}", String::from_utf8_lossy(&contents.0)); /// ``` pub struct Easy2 { - inner: Box>, + inner: InnerBox, } +// Keep the callback target behind an owning raw pointer. Callback userdata can +// outlive temporary references to Easy2 and must remain valid when Easy2 moves. +struct InnerBox { + ptr: *mut Inner, +} + +impl InnerBox { + fn new(inner: Inner) -> Self { + Self { + ptr: Box::into_raw(Box::new(inner)), + } + } + + fn as_ptr(&self) -> *mut Inner { + self.ptr + } +} + +impl std::ops::Deref for InnerBox { + type Target = Inner; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr } + } +} + +impl std::ops::DerefMut for InnerBox { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { &mut *self.ptr } + } +} + +impl Drop for InnerBox { + fn drop(&mut self) { + unsafe { + drop(Box::from_raw(self.ptr)); + } + } +} + +unsafe impl Send for InnerBox {} + struct Inner { handle: *mut curl_sys::CURL, header_list: Option, @@ -591,7 +633,7 @@ impl Easy2 { let handle = curl_sys::curl_easy_init(); assert!(!handle.is_null()); let mut ret = Easy2 { - inner: Box::new(Inner { + inner: InnerBox::new(Inner { handle, header_list: None, resolve_list: None, @@ -628,7 +670,7 @@ impl Easy2 { let _ = self.signal(false); self.ssl_configure(); - let ptr = &*self.inner as *const _ as *const _; + let ptr = self.inner.as_ptr() as *const _; let cb: extern "C" fn(*mut c_char, size_t, size_t, *mut c_void) -> size_t = header_cb::; self.setopt_ptr(curl_sys::CURLOPT_HEADERFUNCTION, cb as *const _) @@ -3548,6 +3590,51 @@ impl Drop for Easy2 { } } +#[cfg(test)] +mod callback_data_tests { + use super::*; + + struct Collector(Vec); + + impl Handler for Collector { + fn write(&mut self, data: &[u8]) -> Result { + self.0.extend_from_slice(data); + Ok(data.len()) + } + } + + fn move_owner(owner: InnerBox) -> InnerBox { + owner + } + + #[test] + fn callback_data_survives_moving_its_owner() { + let owner = InnerBox::new(Inner { + handle: ptr::null_mut(), + header_list: None, + resolve_list: None, + connect_to_list: None, + proxy_header_list: None, + form: None, + error_buf: Box::new(UnsafeCell::new([0; curl_sys::CURL_ERROR_SIZE])), + handler: Collector(Vec::new()), + }); + let callback_data = owner.as_ptr() as *mut c_void; + let owner = move_owner(owner); + let payload = b"synthetic response"; + + let written = write_cb::( + payload.as_ptr() as *mut c_char, + 1, + payload.len(), + callback_data, + ); + + assert_eq!(written, payload.len()); + assert_eq!(owner.handler.0, payload); + } +} + extern "C" fn header_cb( buffer: *mut c_char, size: size_t,