Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  libcurl.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Partial libcurl bindings with some wrappers for safe cleanup.

use crate::std::path::Path;
use libloading::{Library, Symbol};
use once_cell::sync::Lazy;
use std::ffi::{c_char, c_long, c_uint, CStr, CString};

// Constants lifted from `curl.h`
const CURLE_OK: CurlCode = 0;
const CURLE_OUT_OF_MEMORY: CurlCode = 27;
const CURL_ERROR_SIZE: usize = 256;

const CURLOPTTYPE_LONG: CurlOption = 0;
const CURLOPTTYPE_OBJECTPOINT: CurlOption = 10000;
const CURLOPTTYPE_FUNCTIONPOINT: CurlOption = 20000;
const CURLOPTTYPE_STRINGPOINT: CurlOption = CURLOPTTYPE_OBJECTPOINT;
const CURLOPTTYPE_CBPOINT: CurlOption = CURLOPTTYPE_OBJECTPOINT;
const CURLOPTTYPE_SLISTPOINT: CurlOption = CURLOPTTYPE_OBJECTPOINT;

const CURLOPT_WRITEDATA: CurlOption = CURLOPTTYPE_CBPOINT + 1;
const CURLOPT_URL: CurlOption = CURLOPTTYPE_STRINGPOINT + 2;
const CURLOPT_ERRORBUFFER: CurlOption = CURLOPTTYPE_OBJECTPOINT + 10;
const CURLOPT_WRITEFUNCTION: CurlOption = CURLOPTTYPE_FUNCTIONPOINT + 11;
const CURLOPT_USERAGENT: CurlOption = CURLOPTTYPE_STRINGPOINT + 18;
const CURLOPT_MIMEPOST: CurlOption = CURLOPTTYPE_OBJECTPOINT + 269;
const CURLOPT_MAXREDIRS: CurlOption = CURLOPTTYPE_LONG + 68;
const CURLOPT_HTTPHEADER: CurlOption = CURLOPTTYPE_SLISTPOINT + 23;
const CURLOPT_POSTFIELDS: CurlOption = CURLOPTTYPE_OBJECTPOINT + 15;
const CURLOPT_POSTFIELDSIZE: CurlOption = CURLOPTTYPE_LONG + 60;

const CURLINFO_LONG: CurlInfo = 0x200000;
const CURLINFO_RESPONSE_CODE: CurlInfo = CURLINFO_LONG + 2;

const CURL_LIB_NAMES: &[&str] = if cfg!(target_os = "linux") {
    &[
        "libcurl.so",
        "libcurl.so.4",
        // Debian gives libcurl a different name when it is built against GnuTLS
        "libcurl-gnutls.so",
        "libcurl-gnutls.so.4",
        // Older versions in case we find nothing better
        "libcurl.so.3",
        "libcurl-gnutls.so.3", // See above for Debian
    ]
} else if cfg!(target_os = "macos") {
    &[
        "/usr/lib/libcurl.dylib",
        "/usr/lib/libcurl.4.dylib",
        "/usr/lib/libcurl.3.dylib",
    ]
} else if cfg!(target_os = "windows") {
    &["libcurl.dll", "curl.dll"]
} else {
    &[]
};

#[repr(transparent)]
#[derive(Clone, Copy)]
struct CurlHandle(*mut ());
type CurlCode = c_uint;
type CurlOption = c_uint;
type CurlInfo = c_uint;
#[repr(transparent)]
#[derive(Clone, Copy)]
struct CurlMime(*mut ());
#[repr(transparent)]
#[derive(Clone, Copy)]
struct CurlMimePart(*mut ());
#[repr(transparent)]
#[derive(Clone, Copy)]
struct CurlSlist(*mut ());

// # Safety
// Curl handles are safe to pass among threads: https://curl.se/libcurl/c/threadsafe.html.
unsafe impl Send for CurlHandle {}
unsafe impl Send for CurlMime {}
unsafe impl Send for CurlMimePart {}
unsafe impl Send for CurlSlist {}

macro_rules! library_binding {
    ( $localname:ident members[$($members:tt)*] load[$($load:tt)*] fn $name:ident $args:tt $( -> $ret:ty )? ; $($rest:tt)* ) => {
        library_binding! {
            $localname
            members[
                $($members)*
                $name: Symbol<'static, unsafe extern "C" fn $args $(->$ret)?>,
            ]
            load[
                $($load)*
                $name: unsafe {
                    let symbol = $localname.get::<unsafe extern "C" fn $args $(->$ret)?>(stringify!($name).as_bytes())
                    .map_err(|e| std::io::Error::new(std::io::ErrorKind::NotFound, e))?;
                    // All symbols refer to library, so `'static` lifetimes are safe (`library`
                    // will outlive them).
                    std::mem::transmute(symbol)
                },
            ]
            $($rest)*
        }
    };
    ( $localname:ident members[$($members:tt)*] load[$($load:tt)*] ) => {
        pub struct Curl {
            $($members)*
            _library: Library
        }

        impl Curl {
            fn load() -> std::io::Result<Self> {
                // Try each of the libraries, debug-logging load failures.
                let library = CURL_LIB_NAMES.iter().find_map(|name| {
                    log::debug!("attempting to load {name}");
                    match unsafe { Library::new(name) } {
                        Ok(lib) => {
                            log::info!("loaded {name}");
                            Some(lib)
                        }
                        Err(e) => {
                            log::debug!("error when loading {name}: {e}");
                            None
                        }
                    }
                });

                let $localname = library.ok_or_else(|| {
                    std::io::Error::new(std::io::ErrorKind::NotFound, "failed to find curl library")
                })?;

                Ok(Curl { $($load)* _library: $localname })
            }
        }
    };
    ( $($rest:tt)* ) => {
        library_binding! {
            library members[] load[] $($rest)*
        }
    }
}

library_binding! {
    fn curl_easy_init() -> CurlHandle;
    fn curl_easy_setopt(CurlHandle, CurlOption, ...) -> CurlCode;
    fn curl_easy_perform(CurlHandle) -> CurlCode;
    fn curl_easy_getinfo(CurlHandle, CurlInfo, ...) -> CurlCode;
    fn curl_easy_cleanup(CurlHandle);
    fn curl_mime_init(CurlHandle) -> CurlMime;
    fn curl_mime_addpart(CurlMime) -> CurlMimePart;
    fn curl_mime_name(CurlMimePart, *const c_char) -> CurlCode;
    fn curl_mime_filename(CurlMimePart, *const c_char) -> CurlCode;
    fn curl_mime_type(CurlMimePart, *const c_char) -> CurlCode;
    fn curl_mime_data(CurlMimePart, *const c_char, usize) -> CurlCode;
    fn curl_mime_filedata(CurlMimePart, *const c_char) -> CurlCode;
    fn curl_mime_free(CurlMime);
    fn curl_slist_append(CurlSlist, *const c_char) -> CurlSlist;
    fn curl_slist_free_all(CurlSlist);
}

/// Load libcurl if possible.
pub fn load() -> std::io::Result<&'static Curl> {
    static CURL: Lazy<std::io::Result<Curl>> = Lazy::new(Curl::load);
    CURL.as_ref().map_err(std::io::Error::other)
}

#[derive(Debug)]
pub struct Error {
    code: CurlCode,
    error: Option<String>,
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "curl error code {}", self.code)?;
        if let Some(e) = &self.error {
            write!(f, ": {e}")?;
        }
        Ok(())
    }
}

impl std::error::Error for Error {}

impl From<Error> for std::io::Error {
    fn from(e: Error) -> Self {
        std::io::Error::other(e)
    }
}

pub type Result<T> = std::result::Result<T, Error>;

fn to_result(code: CurlCode) -> Result<()> {
    if code == CURLE_OK {
        Ok(())
    } else {
        Err(Error { code, error: None })
    }
}

impl Curl {
    pub fn easy(&self) -> std::io::Result<Easy> {
        let handle = unsafe { (self.curl_easy_init)() };
        if handle.0.is_null() {
            Err(std::io::Error::other("curl_easy_init failed"))
        } else {
            Ok(Easy {
                lib: self,
                handle,
                mime: None,
                headers: None,
                postdata: None,
            })
        }
    }
}

struct ErrorBuffer([u8; CURL_ERROR_SIZE]);

impl Default for ErrorBuffer {
    fn default() -> Self {
        ErrorBuffer([0; CURL_ERROR_SIZE])
    }
}

pub struct Easy<'a> {
    lib: &'a Curl,
    handle: CurlHandle,
    mime: Option<Mime<'a>>,
    headers: Option<Slist<'a>>,
    postdata: Option<Box<[u8]>>,
}

impl<'a> Easy<'a> {
    pub fn set_url(&mut self, url: &str) -> Result<()> {
        let url = CString::new(url).unwrap();
        to_result(unsafe { (self.lib.curl_easy_setopt)(self.handle, CURLOPT_URL, url.as_ptr()) })
    }

    pub fn set_user_agent(&mut self, user_agent: &str) -> Result<()> {
        let ua = CString::new(user_agent).unwrap();
        to_result(unsafe {
            (self.lib.curl_easy_setopt)(self.handle, CURLOPT_USERAGENT, ua.as_ptr())
        })
    }

    pub fn mime(&self) -> std::io::Result<Mime<'a>> {
        let handle = unsafe { (self.lib.curl_mime_init)(self.handle) };
        if handle.0.is_null() {
            Err(std::io::Error::other("curl_mime_init failed"))
        } else {
            Ok(Mime {
                lib: self.lib,
                handle,
            })
        }
    }

    pub fn set_mime_post(&mut self, mime: Mime<'a>) -> Result<()> {
        to_result(unsafe {
            (self.lib.curl_easy_setopt)(self.handle, CURLOPT_MIMEPOST, mime.handle)
        })?;
        self.mime = Some(mime);
        Ok(())
    }

    pub fn set_max_redirs(&mut self, redirs: c_long) -> Result<()> {
        to_result(unsafe { (self.lib.curl_easy_setopt)(self.handle, CURLOPT_MAXREDIRS, redirs) })
    }

    /// Create a new, empty string list.
    pub fn slist(&self) -> Slist<'a> {
        Slist {
            lib: self.lib,
            handle: CurlSlist(std::ptr::null_mut()),
        }
    }

    pub fn set_headers(&mut self, headers: Slist<'a>) -> Result<()> {
        to_result(unsafe {
            (self.lib.curl_easy_setopt)(self.handle, CURLOPT_HTTPHEADER, headers.handle)
        })?;
        self.headers = Some(headers);
        Ok(())
    }

    pub fn set_postfields(&mut self, data: impl Into<Box<[u8]>>) -> std::io::Result<()> {
        let data = data.into();
        let size: c_long = data.len().try_into().map_err(std::io::Error::other)?;
        to_result(unsafe {
            (self.lib.curl_easy_setopt)(self.handle, CURLOPT_POSTFIELDSIZE, size)
        })?;
        to_result(unsafe {
            (self.lib.curl_easy_setopt)(
                self.handle,
                CURLOPT_POSTFIELDS,
                data.as_ptr() as *const c_char,
            )
        })?;
        self.postdata = Some(data);
        Ok(())
    }

    /// Returns the response data on success.
    pub fn perform(&self) -> Result<Vec<u8>> {
        // Set error buffer, but degrade service if it doesn't work.
        let mut error_buffer = ErrorBuffer::default();
        let error_buffer_set = unsafe {
            (self.lib.curl_easy_setopt)(
                self.handle,
                CURLOPT_ERRORBUFFER,
                error_buffer.0.as_mut_ptr() as *mut c_char,
            )
        } == CURLE_OK;

        // Set the write function to fill a Vec. If there is a panic, this might leave stale
        // pointers in the curl options, but they won't be used without another perform, at which
        // point they'll be overwritten.
        let mut data: Vec<u8> = Vec::new();
        extern "C" fn write_callback(
            data: *const u8,
            size: usize,
            nmemb: usize,
            dest: &mut Vec<u8>,
        ) -> usize {
            let total = size * nmemb;
            dest.extend(unsafe { std::slice::from_raw_parts(data, total) });
            total
        }
        unsafe {
            to_result((self.lib.curl_easy_setopt)(
                self.handle,
                CURLOPT_WRITEFUNCTION,
                write_callback as extern "C" fn(*const u8, usize, usize, &mut Vec<u8>) -> usize,
            ))?;
            to_result((self.lib.curl_easy_setopt)(
                self.handle,
                CURLOPT_WRITEDATA,
                &mut data as *mut _,
            ))?;
        };

        let mut result = to_result(unsafe { (self.lib.curl_easy_perform)(self.handle) });

        // Clean up a bit by unsetting the write function and write data, though they won't be used
        // anywhere else. Ignore return values.
        unsafe {
            (self.lib.curl_easy_setopt)(
                self.handle,
                CURLOPT_WRITEFUNCTION,
                std::ptr::null_mut::<()>(),
            );
            (self.lib.curl_easy_setopt)(self.handle, CURLOPT_WRITEDATA, std::ptr::null_mut::<()>());
        }

        if error_buffer_set {
            unsafe {
                (self.lib.curl_easy_setopt)(
                    self.handle,
                    CURLOPT_ERRORBUFFER,
                    std::ptr::null_mut::<()>(),
                )
            };
            if let Err(e) = &mut result {
                if let Ok(cstr) = CStr::from_bytes_until_nul(error_buffer.0.as_slice()) {
                    e.error = Some(cstr.to_string_lossy().into_owned());
                }
            }
        }

        result.map(move |()| data)
    }

    pub fn get_response_code(&self) -> Result<u64> {
        let mut code = c_long::default();
        to_result(unsafe {
            (self.lib.curl_easy_getinfo)(
                self.handle,
                CURLINFO_RESPONSE_CODE,
                &mut code as *mut c_long,
            )
        })?;
        Ok(code.try_into().expect("negative http response code"))
    }
}

impl Drop for Easy<'_> {
    fn drop(&mut self) {
        self.mime.take();
        unsafe { (self.lib.curl_easy_cleanup)(self.handle) };
    }
}

pub struct Mime<'a> {
    lib: &'a Curl,
    handle: CurlMime,
}

impl<'a> Mime<'a> {
    pub fn add_part(&mut self) -> std::io::Result<MimePart<'a>> {
        let handle = unsafe { (self.lib.curl_mime_addpart)(self.handle) };
        if handle.0.is_null() {
            Err(std::io::Error::other("curl_mime_addpart failed"))
        } else {
            Ok(MimePart {
                lib: self.lib,
                handle,
            })
        }
    }
}

impl Drop for Mime<'_> {
    fn drop(&mut self) {
        unsafe { (self.lib.curl_mime_free)(self.handle) };
    }
}

pub struct MimePart<'a> {
    lib: &'a Curl,
    handle: CurlMimePart,
}

impl MimePart<'_> {
    pub fn set_name(&mut self, name: &str) -> Result<()> {
        let name = CString::new(name).unwrap();
        to_result(unsafe { (self.lib.curl_mime_name)(self.handle, name.as_ptr()) })
    }

    pub fn set_filename(&mut self, filename: &str) -> Result<()> {
        let filename = CString::new(filename).unwrap();
        to_result(unsafe { (self.lib.curl_mime_filename)(self.handle, filename.as_ptr()) })
    }

    pub fn set_type(&mut self, mime_type: &str) -> Result<()> {
        let mime_type = CString::new(mime_type).unwrap();
        to_result(unsafe { (self.lib.curl_mime_type)(self.handle, mime_type.as_ptr()) })
    }

    pub fn set_filedata(&mut self, file: &Path) -> Result<()> {
        let file = CString::new(file.display().to_string()).unwrap();
        to_result(unsafe { (self.lib.curl_mime_filedata)(self.handle, file.as_ptr()) })
    }

    pub fn set_data(&mut self, data: &[u8]) -> Result<()> {
        to_result(unsafe {
            (self.lib.curl_mime_data)(self.handle, data.as_ptr() as *const c_char, data.len())
        })
    }
}

pub struct Slist<'a> {
    lib: &'a Curl,
    handle: CurlSlist,
}

impl Slist<'_> {
    pub fn append(&mut self, s: &str) -> Result<()> {
        let cs = CString::new(s).unwrap();
        let new_handle = unsafe { (self.lib.curl_slist_append)(self.handle, cs.as_ptr()) };
        if new_handle.0.is_null() {
            return Err(Error {
                // From source inspection, failure modes are all malloc errors,
                // which are more than likely only from OOM.
                code: CURLE_OUT_OF_MEMORY,
                error: Some(format!("failed to append {s} to slist")),
            });
        }
        self.handle = new_handle;
        Ok(())
    }
}

impl Drop for Slist<'_> {
    fn drop(&mut self) {
        unsafe { (self.lib.curl_slist_free_all)(self.handle) };
    }
}

[ Dauer der Verarbeitung: 0.29 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge