//! Rust bindings to the libcurl C library
//!
//! This crate contains bindings for an HTTP/HTTPS client which is powered by
//! [libcurl], the same library behind the `curl` command line tool. The API
//! currently closely matches that of libcurl itself, except that a Rustic layer
//! of safety is applied on top.
//!
//! [libcurl]: https://curl.haxx.se/libcurl/
//!
//! # The "Easy" API
//!
//! The easiest way to send a request is to use the `Easy` api which corresponds
//! to `CURL` in libcurl. This handle supports a wide variety of options and can
//! be used to make a single blocking request in a thread. Callbacks can be
//! specified to deal with data as it arrives and a handle can be reused to
//! cache connections and such.
//!
//! ```rust,no_run
//! use std::io::{stdout, Write};
//!
//! use curl::easy::Easy;
//!
//! // Write the contents of rust-lang.org to stdout
//! let mut easy = Easy::new();
//! easy.url("https://www.rust-lang.org/").unwrap();
//! easy.write_function(|data| {
//!     stdout().write_all(data).unwrap();
//!     Ok(data.len())
//! }).unwrap();
//! easy.perform().unwrap();
//! ```
//!
//! # What about multiple concurrent HTTP requests?
//!
//! One option you have currently is to send multiple requests in multiple
//! threads, but otherwise libcurl has a "multi" interface for doing this
//! operation. Initial bindings of this interface can be found in the `multi`
//! module, but feedback is welcome!
//!
//! # Where does libcurl come from?
//!
//! This crate links to the `curl-sys` crate which is in turn responsible for
//! acquiring and linking to the libcurl library. Currently this crate will
//! build libcurl from source if one is not already detected on the system.
//!
//! There is a large number of releases for libcurl, all with different sets of
//! capabilities. Robust programs may wish to inspect `Version::get()` to test
//! what features are implemented in the linked build of libcurl at runtime.
//!
//! # Initialization
//!
//! The underlying libcurl library must be initialized before use and has
//! certain requirements on how this is done. Check the documentation for
//! [`init`] for more details.

#![deny(missing_docs, missing_debug_implementations)]
#![doc(html_root_url = "https://docs.rs/curl/0.4")]

use std::ffi::CStr;
use std::str;
use std::sync::Once;

pub use crate::error::{Error, FormError, MultiError, ShareError};
mod error;

pub use crate::version::{Protocols, Version};
mod version;

pub mod easy;
pub mod multi;
mod panic;

#[cfg(test)]
static INITIALIZED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);

/// Initializes the underlying libcurl library.
///
/// The underlying libcurl library must be initialized before use, and must be
/// done so on the main thread before any other threads are created by the
/// program. This crate will do this for you automatically in the following
/// scenarios:
///
/// - Creating a new [`Easy`][easy::Easy] or [`Multi`][multi::Multi] handle
/// - At program startup on Windows, macOS, Linux, Android, or FreeBSD systems
///
/// This should be sufficient for most applications and scenarios, but in any
/// other case, it is strongly recommended that you call this function manually
/// as soon as your program starts.
///
/// Calling this function more than once is harmless and has no effect.
#[inline]
pub fn init() {
    /// Used to prevent concurrent or duplicate initialization.
    static INIT: Once = Once::new();

    INIT.call_once(|| {
        #[cfg(need_openssl_init)]
        openssl_probe::init_ssl_cert_env_vars();
        #[cfg(need_openssl_init)]
        openssl_sys::init();

        unsafe {
            assert_eq!(curl_sys::curl_global_init(curl_sys::CURL_GLOBAL_ALL), 0);
        }

        #[cfg(test)]
        {
            INITIALIZED.store(true, std::sync::atomic::Ordering::SeqCst);
        }

        // Note that we explicitly don't schedule a call to
        // `curl_global_cleanup`. The documentation for that function says
        //
        // > You must not call it when any other thread in the program (i.e. a
        // > thread sharing the same memory) is running. This doesn't just mean
        // > no other thread that is using libcurl.
        //
        // We can't ever be sure of that, so unfortunately we can't call the
        // function.
    });
}

/// An exported constructor function. On supported platforms, this will be
/// invoked automatically before the program's `main` is called. This is done
/// for the convenience of library users since otherwise the thread-safety rules
/// around initialization can be difficult to fulfill.
///
/// This is a hidden public item to ensure the symbol isn't optimized away by a
/// rustc/LLVM bug: https://github.com/rust-lang/rust/issues/47384. As long as
/// any item in this module is used by the final binary (which `init` will be)
/// then this symbol should be preserved.
#[used]
#[doc(hidden)]
#[cfg_attr(
    any(target_os = "linux", target_os = "freebsd", target_os = "android"),
    link_section = ".init_array"
)]
#[cfg_attr(target_os = "macos", link_section = "__DATA,__mod_init_func")]
#[cfg_attr(target_os = "windows", link_section = ".CRT$XCU")]
pub static INIT_CTOR: extern "C" fn() = {
    /// This is the body of our constructor function.
    #[cfg_attr(
        any(target_os = "linux", target_os = "android"),
        link_section = ".text.startup"
    )]
    extern "C" fn init_ctor() {
        init();
    }

    init_ctor
};

unsafe fn opt_str<'a>(ptr: *const libc::c_char) -> Option<&'a str> {
    if ptr.is_null() {
        None
    } else {
        Some(str::from_utf8(CStr::from_ptr(ptr).to_bytes()).unwrap())
    }
}

fn cvt(r: curl_sys::CURLcode) -> Result<(), Error> {
    if r == curl_sys::CURLE_OK {
        Ok(())
    } else {
        Err(Error::new(r))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[cfg(any(
        target_os = "linux",
        target_os = "macos",
        target_os = "windows",
        target_os = "freebsd",
        target_os = "android"
    ))]
    fn is_initialized_before_main() {
        assert!(INITIALIZED.load(std::sync::atomic::Ordering::SeqCst));
    }
}
