minicurl (empty) → 0
raw patch · 5 files changed
+287/−0 lines, 5 filesdep +HUnitdep +basedep +bytestring
Dependencies added: HUnit, base, bytestring, cryptohash-sha256, minicurl
Files
- LICENSE +30/−0
- example/minicurl-example.hs +21/−0
- include/hs_minicurl.h +92/−0
- minicurl.cabal +57/−0
- src/MiniCurl.hs +87/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2023, Oleg Grenrus++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Oleg Grenrus nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ example/minicurl-example.hs view
@@ -0,0 +1,21 @@+{-# LANGUAGE OverloadedStrings #-}+module Main (main) where++import qualified Crypto.Hash.SHA256 as SHA256+import qualified Data.ByteString as BS+import Test.HUnit ((@?=))++import MiniCurl++main :: IO ()+main = withLibcurl $ do+ withCurl $ \curl -> do+ curlResponseCode curl >>= print++ bs1 <- curlPerform curl "http://oleg.fi/ClojuTre19.pdf" 188177+ curlResponseCode curl >>= print+ SHA256.hash bs1 @?= "\180A\131A\136<v22\FS\170'\255\166xI\v\217\136\DC3\f@\184\ENQe\187\128\DC3\148\148#/"++ bs2 <- curlPerform curl "http://oleg.fi/ClojuTre19.pdf" 188177+ curlResponseCode curl >>= print+ SHA256.hash bs2 @?= "\180A\131A\136<v22\FS\170'\255\166xI\v\217\136\DC3\f@\184\ENQe\187\128\DC3\148\148#/"
+ include/hs_minicurl.h view
@@ -0,0 +1,92 @@+#ifndef HS_MINICURL_H+#define HS_MINICURL_H++#include <stdint.h>+#include <stddef.h>+#include <string.h>+#include <curl/curl.h>++/*************************************************************************+ * declarations+ *************************************************************************/++static inline int hs_minicurl_global_init();+static inline void hs_minicurl_global_cleanup();++static inline CURL *hs_minicurl_init();+static inline void hs_minicurl_cleanup(CURL *curl);++static inline int hs_minicurl_perform(CURL *curl, const char *url, uint8_t *ptr, size_t size);+static inline int hs_minicurl_response_code(CURL *curl);++/*************************************************************************+ * implementations+ *************************************************************************/++static inline int hs_minicurl_global_init() {+ return curl_global_init(CURL_GLOBAL_ALL);+}++static inline void hs_minicurl_global_cleanup() {+ return curl_global_cleanup();+}++static inline CURL *hs_minicurl_init() {+ CURL *curl = curl_easy_init();+ if (curl == NULL) return NULL;++ /* Set common options */+ curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);+ curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);+ /* CURLcode curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); */++ return curl;+}++static inline void hs_minicurl_cleanup(CURL *curl) {+ curl_easy_cleanup(curl);+}++struct hs_minicurl_buffer {+ uint8_t *data;+ size_t off;+ size_t len;+};++static inline size_t write_data(uint8_t *read_buf, size_t always_one, size_t n, struct hs_minicurl_buffer *write_buf) {+ /* printf("write_data %p %lu %lu %p %lu %lu\n", read_buf, always_one, n, write_buf->data, write_buf->off, write_buf->len); */++ if (write_buf->off >= write_buf->len) {+ return 0;+ }++ size_t p = write_buf->len - write_buf->off;+ size_t m = n > p ? p : n; /* MIN(n, p) */++ memcpy(write_buf->data + write_buf->off, read_buf, m);+ write_buf->off += m;++ return m;+}++static inline int hs_minicurl_perform(CURL *curl, const char *url, uint8_t *ptr, size_t size) {+ CURLcode res = CURLE_OK;++ if (curl == NULL) return CURLE_FAILED_INIT;+ + struct hs_minicurl_buffer buf = { .data = ptr, .off = 0, .len = size };++ curl_easy_setopt(curl, CURLOPT_URL, url);+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buf);++ return curl_easy_perform(curl);+}++static inline int hs_minicurl_response_code(CURL *curl) {+ long codep = 0;+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &codep);+ return codep;+}++#endif
+ minicurl.cabal view
@@ -0,0 +1,57 @@+cabal-version: 3.0+name: minicurl+version: 0+synopsis: Minimal bindings to libcurl+category: Network+description:+ Minimal bindings to @libcurl@.+ Allows to download files if you know their size.++author: Oleg Grenrus <oleg.grenrus@iki.fi>+maintainer: Oleg Grenrus <oleg.grenrus@iki.fi>+homepage: https://github.com/phadej/minicurl+bug-reports: https://github.com/phadej/minicurl/issues+license: BSD-3-Clause+license-file: LICENSE+extra-source-files: include/hs_minicurl.h+tested-with:+ GHC ==8.0.2+ || ==8.2.2+ || ==8.4.4+ || ==8.6.5+ || ==8.8.4+ || ==8.10.7+ || ==9.0.2+ || ==9.2.5+ || ==9.4.4++source-repository head+ type: git+ location: https://github.com/phadej/minicurl.git++library+ default-language: Haskell2010+ hs-source-dirs: src+ include-dirs: include+ ghc-options: -Wall+ exposed-modules: MiniCurl+ pkgconfig-depends: libcurl >=7.58.0+ build-depends:+ , base >=4.9.0.0 && <4.18+ , bytestring ^>=0.10.8.1 || ^>=0.11.3.1++-- this "test-suite" requires internet connection.+test-suite minicurl-example+ type: exitcode-stdio-1.0+ default-language: Haskell2010+ hs-source-dirs: example+ main-is: minicurl-example.hs+ build-depends:+ , base+ , bytestring+ , minicurl++ -- test dependencies+ build-depends:+ , cryptohash-sha256 ^>=0.11.102.1+ , HUnit ^>=1.6.2.0
+ src/MiniCurl.hs view
@@ -0,0 +1,87 @@+{-# 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