@@ -242,21 +242,102 @@ pub struct C2paHttpRequest {
242242/// HTTP response filled in by the resolver callback.
243243///
244244/// The callback must set `status`, `body`, and `body_len`.
245- /// `body` must point to callback-allocated memory. Rust will call
246- /// `body_free(body)` to release it. If `body_free` is NULL, Rust
247- /// assumes the body is stack- or arena-owned and will not free it
248- /// (the data is copied before the callback returns).
245+ /// `body` must be allocated with `malloc()`. Rust will call `free()` on it
246+ /// after copying the data.
249247#[ repr( C ) ]
250248pub struct C2paHttpResponse {
251249 /// HTTP status code (e.g. 200, 404)
252250 pub status : i32 ,
253- /// Response body bytes, allocated by the callback.
251+ /// Response body bytes, allocated by the callback with `malloc()`.
252+ /// Rust takes ownership and will call `free()`.
254253 pub body : * mut c_uchar ,
255254 /// Length of `body` in bytes
256255 pub body_len : usize ,
257- /// Deallocator for `body`. Called by Rust after copying the data.
258- /// May be NULL if `body` does not need to be freed.
259- pub body_free : Option < unsafe extern "C" fn ( * mut c_uchar ) > ,
256+ }
257+
258+ /// Owns the backing storage for a [`C2paHttpRequest`].
259+ ///
260+ /// Use [`as_ffi`](OwnedC2paHttpRequest::as_ffi) to obtain the `#[repr(C)]` view
261+ /// whose pointers borrow from this struct.
262+ struct OwnedC2paHttpRequest {
263+ url : std:: ffi:: CString ,
264+ method : std:: ffi:: CString ,
265+ headers : std:: ffi:: CString ,
266+ body : Vec < u8 > ,
267+ }
268+
269+ impl TryFrom < c2pa:: http:: http:: Request < Vec < u8 > > > for OwnedC2paHttpRequest {
270+ type Error = c2pa:: http:: HttpResolverError ;
271+
272+ fn try_from ( request : c2pa:: http:: http:: Request < Vec < u8 > > ) -> Result < Self , Self :: Error > {
273+ use std:: ffi:: CString ;
274+
275+ use c2pa:: http:: HttpResolverError ;
276+
277+ let url = CString :: new ( request. uri ( ) . to_string ( ) )
278+ . map_err ( |e| HttpResolverError :: Other ( Box :: new ( e) ) ) ?;
279+ let method = CString :: new ( request. method ( ) . as_str ( ) )
280+ . map_err ( |e| HttpResolverError :: Other ( Box :: new ( e) ) ) ?;
281+ let headers_str: String = request
282+ . headers ( )
283+ . iter ( )
284+ . filter_map ( |( k, v) | v. to_str ( ) . ok ( ) . map ( |v| format ! ( "{k}: {v}\n " ) ) )
285+ . collect ( ) ;
286+ let headers =
287+ CString :: new ( headers_str) . map_err ( |e| HttpResolverError :: Other ( Box :: new ( e) ) ) ?;
288+ let body = request. into_body ( ) ;
289+
290+ Ok ( Self {
291+ url,
292+ method,
293+ headers,
294+ body,
295+ } )
296+ }
297+ }
298+
299+ impl OwnedC2paHttpRequest {
300+ /// Returns the `#[repr(C)]` view. The returned struct borrows from `self`
301+ /// and must not outlive it.
302+ fn as_ffi ( & self ) -> C2paHttpRequest {
303+ let ( body, body_len) = if self . body . is_empty ( ) {
304+ ( std:: ptr:: null ( ) , 0 )
305+ } else {
306+ ( self . body . as_ptr ( ) , self . body . len ( ) )
307+ } ;
308+ C2paHttpRequest {
309+ url : self . url . as_ptr ( ) ,
310+ method : self . method . as_ptr ( ) ,
311+ headers : self . headers . as_ptr ( ) ,
312+ body,
313+ body_len,
314+ }
315+ }
316+ }
317+
318+ impl C2paHttpResponse {
319+ /// Consumes the C-allocated response body and converts it into an
320+ /// `http::Response`. Calls `free()` on the body pointer.
321+ ///
322+ /// # Safety
323+ /// `self.body` must have been allocated with `malloc()` (or be null).
324+ unsafe fn into_http_response (
325+ self ,
326+ ) -> Result < c2pa:: http:: http:: Response < Box < dyn std:: io:: Read > > , c2pa:: http:: HttpResolverError >
327+ {
328+ let body_vec = if self . body . is_null ( ) || self . body_len == 0 {
329+ Vec :: new ( )
330+ } else {
331+ let v = std:: slice:: from_raw_parts ( self . body , self . body_len ) . to_vec ( ) ;
332+ libc:: free ( self . body as * mut c_void ) ;
333+ v
334+ } ;
335+
336+ c2pa:: http:: http:: Response :: builder ( )
337+ . status ( self . status as u16 )
338+ . body ( Box :: new ( std:: io:: Cursor :: new ( body_vec) ) as Box < dyn std:: io:: Read > )
339+ . map_err ( c2pa:: http:: HttpResolverError :: Http )
340+ }
260341}
261342
262343/// Callback type for custom HTTP request resolution.
@@ -292,73 +373,31 @@ impl c2pa::http::SyncHttpResolver for C2paHttpResolver {
292373 request : c2pa:: http:: http:: Request < Vec < u8 > > ,
293374 ) -> Result < c2pa:: http:: http:: Response < Box < dyn std:: io:: Read > > , c2pa:: http:: HttpResolverError >
294375 {
295- use std:: ffi:: CString ;
296-
297376 use c2pa:: http:: HttpResolverError ;
298377
299- let uri_cstr = CString :: new ( request. uri ( ) . to_string ( ) )
300- . map_err ( |e| HttpResolverError :: Other ( Box :: new ( e) ) ) ?;
301- let method_cstr = CString :: new ( request. method ( ) . as_str ( ) )
302- . map_err ( |e| HttpResolverError :: Other ( Box :: new ( e) ) ) ?;
303-
304- let headers_str: String = request
305- . headers ( )
306- . iter ( )
307- . filter_map ( |( k, v) | v. to_str ( ) . ok ( ) . map ( |v| format ! ( "{k}: {v}\n " ) ) )
308- . collect ( ) ;
309- let headers_cstr =
310- CString :: new ( headers_str) . map_err ( |e| HttpResolverError :: Other ( Box :: new ( e) ) ) ?;
311-
312- let body = request. into_body ( ) ;
313- let ( body_ptr, body_len) = if body. is_empty ( ) {
314- ( std:: ptr:: null ( ) , 0usize )
315- } else {
316- ( body. as_ptr ( ) , body. len ( ) )
317- } ;
318-
319- let c_request = C2paHttpRequest {
320- url : uri_cstr. as_ptr ( ) ,
321- method : method_cstr. as_ptr ( ) ,
322- headers : headers_cstr. as_ptr ( ) ,
323- body : body_ptr,
324- body_len,
325- } ;
378+ let owned = OwnedC2paHttpRequest :: try_from ( request) ?;
379+ let c_request = owned. as_ffi ( ) ;
326380
327381 let mut c_response = C2paHttpResponse {
328382 status : 0 ,
329383 body : std:: ptr:: null_mut ( ) ,
330384 body_len : 0 ,
331- body_free : None ,
332385 } ;
333386
334387 let rc =
335388 unsafe { ( self . callback ) ( self . context as * mut c_void , & c_request, & mut c_response) } ;
336389
337- // Copy the body before we potentially free it.
338- let response_body = if c_response. body . is_null ( ) || c_response. body_len == 0 {
339- Vec :: new ( )
340- } else {
341- unsafe { std:: slice:: from_raw_parts ( c_response. body , c_response. body_len ) . to_vec ( ) }
342- } ;
343-
344- // Release the callback-owned buffer.
345- if let Some ( free_fn) = c_response. body_free {
390+ if rc != 0 {
391+ // Free any body the callback may have allocated before the error.
346392 if !c_response. body . is_null ( ) {
347- unsafe { free_fn ( c_response. body ) } ;
393+ unsafe { libc :: free ( c_response. body as * mut c_void ) } ;
348394 }
349- }
350-
351- if rc != 0 {
352395 let msg = CimplError :: last_message ( )
353396 . unwrap_or_else ( || "HTTP callback returned error" . to_string ( ) ) ;
354397 return Err ( HttpResolverError :: Other ( msg. into ( ) ) ) ;
355398 }
356399
357- let response = c2pa:: http:: http:: Response :: builder ( )
358- . status ( c_response. status as u16 )
359- . body ( Box :: new ( std:: io:: Cursor :: new ( response_body) ) as Box < dyn std:: io:: Read > )
360- . map_err ( HttpResolverError :: Http ) ?;
361- Ok ( response)
400+ unsafe { c_response. into_http_response ( ) }
362401 }
363402}
364403
0 commit comments