packages feed

minicurl-0: src/MiniCurl.hs

{-# LANGUAGE CApiFFI #-}
{-# LANGUAGE CPP     #-}
module MiniCurl (
    CURL,
    withLibcurl,
    withCurl,
    curlPerform,
    curlResponseCode,
) where

import Control.Exception        (bracket, bracket_)
import Data.ByteString.Internal (ByteString (..))
import Data.Coerce              (coerce)
import Data.Word                (Word8)
import Foreign.C.String         (CString, withCString)
import Control.Concurrent.MVar
import Foreign.C.Types          (CInt (..), CSize (..))
import Foreign.ForeignPtr       (withForeignPtr)
import Foreign.Ptr              (Ptr)
import GHC.ForeignPtr           (mallocPlainForeignPtrBytes)

-- | Curl handle.
newtype CURL = CURL (MVar (Ptr Curl))

-- | (Globally) initialize @libcurl@.
--
-- Wrap your @main@ in it:
--
-- @
-- main :: IO ()
-- main = 'withLibcurl' $ do
--     ...
-- @
--
withLibcurl :: IO r -> IO r
withLibcurl = bracket_ c_minicurl_global_init c_minicurl_global_cleanup

-- | Create curl handle.
--
-- Note: you can reuse 'CURL' handle for multiple requests.
withCurl :: (CURL -> IO r) -> IO r
withCurl kont = bracket c_minicurl_init c_minicurl_cleanup $ \ptr ->
    newMVar ptr >>= coerce kont

-- | Perform request.
--
-- The resulting 'ByteString' will be exactly of the size specified by size argument.
-- If response is smaller, the rest will be zeros; if larger the response will be truncated (not read further)!
-- It's your job to verify that transport was successful, e.g. if you know the expected hash of the download.
--
-- 'curlPerform' is thread-safe (underlying handle in 'CURL' is wrapped in 'MVar').
--
curlPerform
    :: CURL     -- ^ CURL handle
    -> String   -- ^ URL
    -> Int      -- ^ Expected size of the output.
    -> IO ByteString
curlPerform (CURL curlMVar) url size = withMVar curlMVar $ \curl -> do
    fptr <- mallocPlainForeignPtrBytes size
    withForeignPtr fptr $ \ptr ->
        withCString url $ \c_url -> do
            _res <- c_minicurl_perform curl c_url ptr (fromIntegral size :: CSize)
            return ()

#if MIN_VERSION_bytestring(0,11,0)
    return $ BS fptr size
#else
    return $ PS fptr 0 size
#endif

-- | Get (last) response code.
curlResponseCode :: CURL -> IO Int
curlResponseCode (CURL curlMVar) = withMVar curlMVar $ \curl ->
    fromIntegral <$> c_minicurl_response_code curl

-------------------------------------------------------------------------------
-- FFI
-------------------------------------------------------------------------------

data Curl

foreign import capi safe "hs_minicurl.h hs_minicurl_global_init" c_minicurl_global_init :: IO CInt
foreign import capi safe "hs_minicurl.h hs_minicurl_global_cleanup" c_minicurl_global_cleanup :: IO ()
foreign import capi safe "hs_minicurl.h hs_minicurl_init" c_minicurl_init :: IO (Ptr Curl)
foreign import capi safe "hs_minicurl.h hs_minicurl_cleanup" c_minicurl_cleanup :: Ptr Curl -> IO ()
foreign import capi safe "hs_minicurl.h hs_minicurl_perform" c_minicurl_perform :: Ptr Curl -> CString -> Ptr Word8 -> CSize -> IO CInt
foreign import capi safe "hs_minicurl.h hs_minicurl_response_code" c_minicurl_response_code :: Ptr Curl -> IO CInt