{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} module Pantry.HTTP ( module Export , withResponse , httpSink , httpSinkChecked ) where import Conduit import Network.HTTP.Client as Export (parseRequest) import Network.HTTP.Client as Export (parseUrlThrow) import Network.HTTP.Client as Export (BodyReader, HttpExceptionContent (StatusCodeException)) import qualified Network.HTTP.Client as HTTP (withResponse) import Network.HTTP.Client.Internal as Export (setUri) import Network.HTTP.Client.TLS (getGlobalManager) import Network.HTTP.Simple as Export (HttpException (..), Request, Response, addRequestHeader, defaultRequest, getResponseBody, getResponseHeaders, getResponseStatus, setRequestHeader, setRequestHeaders) import qualified Network.HTTP.Simple as HTTP hiding (withResponse) import Network.HTTP.Types as Export (Header, HeaderName, Status, hCacheControl, hRange, ok200, partialContent206, statusCode) import qualified Pantry.SHA256 as SHA256 import Pantry.Types import RIO import qualified RIO.ByteString as B import qualified RIO.Text as T setUserAgent :: Request -> Request setUserAgent = setRequestHeader "User-Agent" ["Haskell pantry package"] withResponse :: MonadUnliftIO m => HTTP.Request -> (Response BodyReader -> m a) -> m a withResponse req inner = withRunInIO $ \run -> do manager <- getGlobalManager HTTP.withResponse (setUserAgent req) manager (run . inner) httpSink :: MonadUnliftIO m => Request -> (Response () -> ConduitT ByteString Void m a) -> m a httpSink req inner = HTTP.httpSink (setUserAgent req) inner httpSinkChecked :: MonadUnliftIO m => Text -> Maybe SHA256 -> Maybe FileSize -> ConduitT ByteString Void m a -> m (SHA256, FileSize, a) httpSinkChecked url msha msize sink = do req <- liftIO $ parseUrlThrow $ T.unpack url httpSink req $ const $ getZipSink $ (,,) <$> ZipSink (checkSha msha) <*> ZipSink (checkSize msize) <*> ZipSink sink where checkSha mexpected = do actual <- SHA256.sinkHash for_ mexpected $ \expected -> unless (actual == expected) $ throwIO $ DownloadInvalidSHA256 url Mismatch { mismatchExpected = expected , mismatchActual = actual } pure actual checkSize mexpected = loop 0 where loop accum = do mbs <- await case mbs of Nothing -> case mexpected of Just (FileSize expected) | expected /= accum -> throwIO $ DownloadInvalidSize url Mismatch { mismatchExpected = FileSize expected , mismatchActual = FileSize accum } _ -> pure (FileSize accum) Just bs -> do let accum' = accum + fromIntegral (B.length bs) case mexpected of Just (FileSize expected) | accum' > expected -> throwIO $ DownloadTooLarge url Mismatch { mismatchExpected = FileSize expected , mismatchActual = FileSize accum' } _ -> loop accum'