packages feed

http-client-tls 0.3.3 → 0.4.0

raw patch · 5 files changed

Files

ChangeLog.md view
@@ -1,3 +1,74 @@+# Changelog for http-client-tls++## 0.4.0++* For MD5 hashes in Base16 format, depend on packages `cryptohash-md5` and+  `base16` rather than `crypton` and `memory` (the latter is unmaintained).++## 0.3.6.4++* data-default-class -> data-default [#546](https://github.com/snoyberg/http-client/pull/546/files)++## 0.3.6.3++* catching up to tls 1.8.0 [#515](https://github.com/snoyberg/http-client/pull/515)++## 0.3.6.2++* Migrate to `crypton`++## 0.3.6.1++* [#482](https://github.com/snoyberg/http-client/issues/482):+  Raise lower bound on `http-client` to fix build.++## 0.3.6++* Allow making requests to raw IPv6 hosts [#477](https://github.com/snoyberg/http-client/pull/477)++## 0.3.5.3++* Fix `newTlsManager` [#325](https://github.com/snoyberg/http-client/issues/325)++## 0.3.5.2++* [#289](https://github.com/snoyberg/http-client/issues/289):+  Keep original `TLSSettings` when creating a `Manager` using `newTlsManagerWith`.++## 0.3.5.1++* Also catch TLSError exceptions [#273](https://github.com/snoyberg/http-client/pull/273)++## 0.3.5++* Add `newTlsManagerWith`+  [#278](https://github.com/snoyberg/http-client/issues/278), which+  provides a variant of `newTlsManager` that takes a `ManagerSettings`+  to base its settings off of.++## 0.3.4.2++* Never throw exceptions on 401 status in `applyDigestAuth`++## 0.3.4.1++* Better exception cleanup behavior++## 0.3.4++* Add 'newTlsManager'+  [#263](https://github.com/snoyberg/http-client/issues/263), which adds+  support for respecting `socks5://` and `socks5h://` `http_proxy` and+  `https_proxy` environment variables.++## 0.3.3.2++* Better handling of internal exceptions++## 0.3.3.1++* Better exception safety via `bracketOnError`+ ## 0.3.3  * Add `DigestAuthException` and generalize `applyDigestAuth`
Network/HTTP/Client/TLS.hs view
@@ -11,6 +11,8 @@       tlsManagerSettings     , mkManagerSettings     , mkManagerSettingsContext+    , newTlsManager+    , newTlsManagerWith       -- * Digest authentication     , applyDigestAuth     , DigestAuthException (..)@@ -21,7 +23,10 @@     , setGlobalManager     ) where -import Data.Default.Class+import Control.Applicative ((<|>))+import Control.Arrow (first)+import System.Environment (getEnvironment)+import Data.Default import Network.HTTP.Client hiding (host, port) import Network.HTTP.Client.Internal hiding (host, port) import Control.Exception@@ -36,11 +41,16 @@ import qualified Data.CaseInsensitive as CI import Data.Maybe (fromMaybe, isJust) import Network.HTTP.Types (status401)-import Crypto.Hash (hash, Digest, MD5)+import qualified Crypto.Hash.MD5 as MD5 import Control.Arrow ((***))-import Data.ByteArray.Encoding (convertToBase, Base (Base16))+import Data.Base16.Types (extractBase16)+import Data.ByteString.Base16 (encodeBase16') import Data.Typeable (Typeable) import Control.Monad.Catch (MonadThrow, throwM)+import qualified Data.Map as Map+import qualified Data.Text as T+import Data.Text.Read (decimal)+import qualified Network.URI as U  -- | Create a TLS-enabled 'ManagerSettings' with the given 'NC.TLSSettings' and -- 'NC.SockSettings'@@ -62,27 +72,43 @@     -> NC.TLSSettings     -> Maybe NC.SockSettings     -> ManagerSettings-mkManagerSettingsContext mcontext tls sock = defaultManagerSettings-    { managerTlsConnection = getTlsConnection mcontext (Just tls) sock-    , managerTlsProxyConnection = getTlsProxyConnection mcontext tls sock+mkManagerSettingsContext mcontext tls sock = mkManagerSettingsContext' defaultManagerSettings mcontext tls sock sock++-- | Internal, allow different SockSettings for HTTP and HTTPS+mkManagerSettingsContext'+    :: ManagerSettings+    -> Maybe NC.ConnectionContext+    -> NC.TLSSettings+    -> Maybe NC.SockSettings -- ^ insecure+    -> Maybe NC.SockSettings -- ^ secure+    -> ManagerSettings+mkManagerSettingsContext' set mcontext tls sockHTTP sockHTTPS = set+    { managerTlsConnection = getTlsConnection mcontext (Just tls) sockHTTPS+    , managerTlsProxyConnection = getTlsProxyConnection mcontext tls sockHTTPS     , managerRawConnection =-        case sock of+        case sockHTTP of             Nothing -> managerRawConnection defaultManagerSettings-            Just _ -> getTlsConnection mcontext Nothing sock+            Just _ -> getTlsConnection mcontext Nothing sockHTTP     , managerRetryableException = \e ->         case () of             ()+#if MIN_VERSION_tls(1,8,0)+                | ((fromException e)::(Maybe TLS.TLSException))==Just (TLS.PostHandshake TLS.Error_EOF) -> True+#else                 | ((fromException e)::(Maybe TLS.TLSError))==Just TLS.Error_EOF -> True+#endif                 | otherwise -> managerRetryableException defaultManagerSettings e     , managerWrapException = \req ->-        let wrapper se =-                case fromException se of-                    Just (_ :: IOException) -> se'-                    Nothing -> case fromException se of-                      Just TLS.Terminated{} -> se'-                      Just TLS.HandshakeFailed{} -> se'-                      Just TLS.ConnectionNotEstablished -> se'-                      _ -> se+        let wrapper se+              | Just (_ :: IOException)          <- fromException se = se'+              | Just (_ :: TLS.TLSException)     <- fromException se = se'+#if !MIN_VERSION_tls(1,8,0)+              | Just (_ :: TLS.TLSError)         <- fromException se = se'+#endif+              | Just (_ :: NC.LineTooLong)       <- fromException se = se'+              | Just (_ :: NC.HostNotResolved)   <- fromException se = se'+              | Just (_ :: NC.HostCannotConnect) <- fromException se = se'+              | otherwise = se               where                 se' = toException $ HttpExceptionRequest req $ InternalException se          in handle $ throwIO . wrapper@@ -98,14 +124,15 @@                  -> IO (Maybe HostAddress -> String -> Int -> IO Connection) getTlsConnection mcontext tls sock = do     context <- maybe NC.initConnectionContext return mcontext-    return $ \_ha host port -> do-        conn <- NC.connectTo context NC.ConnectionParams-            { NC.connectionHostname = host+    return $ \_ha host port -> bracketOnError+        (NC.connectTo context NC.ConnectionParams+            { NC.connectionHostname = strippedHostName host             , NC.connectionPort = fromIntegral port             , NC.connectionUseSecure = tls             , NC.connectionUseSocks = sock-            }-        convertConnection conn+            })+        NC.connectionClose+        convertConnection  getTlsProxyConnection     :: Maybe NC.ConnectionContext@@ -114,26 +141,26 @@     -> IO (S.ByteString -> (Connection -> IO ()) -> String -> Maybe HostAddress -> String -> Int -> IO Connection) getTlsProxyConnection mcontext tls sock = do     context <- maybe NC.initConnectionContext return mcontext-    return $ \connstr checkConn serverName _ha host port -> do-        --error $ show (connstr, host, port)-        conn <- NC.connectTo context NC.ConnectionParams-            { NC.connectionHostname = serverName+    return $ \connstr checkConn serverName _ha host port -> bracketOnError+        (NC.connectTo context NC.ConnectionParams+            { NC.connectionHostname = strippedHostName serverName             , NC.connectionPort = fromIntegral port             , NC.connectionUseSecure = Nothing             , NC.connectionUseSocks =                 case sock of                     Just _ -> error "Cannot use SOCKS and TLS proxying together"-                    Nothing -> Just $ NC.OtherProxy host $ fromIntegral port-            }--        NC.connectionPut conn connstr-        conn' <- convertConnection conn+                    Nothing -> Just $ NC.OtherProxy (strippedHostName host) $ fromIntegral port+            })+        NC.connectionClose+        $ \conn -> do+            NC.connectionPut conn connstr+            conn' <- convertConnection conn -        checkConn conn'+            checkConn conn' -        NC.connectionSetSecure context conn tls+            NC.connectionSetSecure context conn tls -        return conn'+            return conn'  convertConnection :: NC.Connection -> IO Connection convertConnection conn = makeConnection@@ -144,18 +171,84 @@     -- already closed, and we get a @ResourceVanished@.     (NC.connectionClose conn `Control.Exception.catch` \(_ :: IOException) -> return ()) +-- We may decide in the future to just have a global+-- ConnectionContext and use it directly in tlsManagerSettings, at+-- which point this can again be a simple (newManager+-- tlsManagerSettings >>= newIORef). See:+-- https://github.com/snoyberg/http-client/pull/227.+globalConnectionContext :: NC.ConnectionContext+globalConnectionContext = unsafePerformIO NC.initConnectionContext+{-# NOINLINE globalConnectionContext #-}++-- | Load up a new TLS manager with default settings, respecting proxy+-- environment variables.+--+-- @since 0.3.4+newTlsManager :: MonadIO m => m Manager+newTlsManager = liftIO $ do+    env <- getEnvironment+    let lenv = Map.fromList $ map (first $ T.toLower . T.pack) env+        msocksHTTP = parseSocksSettings env lenv "http_proxy"+        msocksHTTPS = parseSocksSettings env lenv "https_proxy"+        settings = mkManagerSettingsContext' defaultManagerSettings (Just globalConnectionContext) def msocksHTTP msocksHTTPS+        settings' = maybe id (const $ managerSetInsecureProxy proxyFromRequest) msocksHTTP+                  $ maybe id (const $ managerSetSecureProxy proxyFromRequest) msocksHTTPS+                    settings+    newManager settings'++-- | Load up a new TLS manager based upon specified settings,+-- respecting proxy environment variables.+--+-- @since 0.3.5+newTlsManagerWith :: MonadIO m => ManagerSettings -> m Manager+newTlsManagerWith set = liftIO $ do+    env <- getEnvironment+    let lenv = Map.fromList $ map (first $ T.toLower . T.pack) env+        msocksHTTP = parseSocksSettings env lenv "http_proxy"+        msocksHTTPS = parseSocksSettings env lenv "https_proxy"+        settings = mkManagerSettingsContext' set (Just globalConnectionContext) def msocksHTTP msocksHTTPS+        settings' = maybe id (const $ managerSetInsecureProxy proxyFromRequest) msocksHTTP+                  $ maybe id (const $ managerSetSecureProxy proxyFromRequest) msocksHTTPS+                    settings+                        -- We want to keep the original TLS settings that were+                        -- passed in. Sadly they aren't available as a record+                        -- field on `ManagerSettings`. So instead we grab the+                        -- fields that depend on the TLS settings.+                        -- https://github.com/snoyberg/http-client/issues/289+                        { managerTlsConnection = managerTlsConnection set+                        , managerTlsProxyConnection = managerTlsProxyConnection set+                        }+    newManager settings'++parseSocksSettings :: [(String, String)] -- ^ original environment+                   -> Map.Map T.Text String -- ^ lower-cased keys+                   -> T.Text -- ^ env name+                   -> Maybe NC.SockSettings+parseSocksSettings env lenv n = do+  str <- lookup (T.unpack n) env <|> Map.lookup n lenv+  let allowedScheme x = x == "socks5:" || x == "socks5h:"+  uri <- U.parseURI str++  guard $ allowedScheme $ U.uriScheme uri+  guard $ null (U.uriPath uri) || U.uriPath uri == "/"+  guard $ null $ U.uriQuery uri+  guard $ null $ U.uriFragment uri++  auth <- U.uriAuthority uri+  port' <-+      case U.uriPort auth of+          "" -> Nothing -- should we use some default?+          ':':rest ->+              case decimal $ T.pack rest of+                  Right (p, "") -> Just p+                  _ -> Nothing+          _ -> Nothing++  Just $ NC.SockSettingsSimple (U.uriRegName auth) port'+ -- | Evil global manager, to make life easier for the common use case globalManager :: IORef Manager-globalManager = unsafePerformIO $ do-    -- We may decide in the future to just have a global-    -- ConnectionContext and use it directly in tlsManagerSettings, at-    -- which point this can again be a simple (newManager-    -- tlsManagerSettings >>= newIORef). See:-    -- https://github.com/snoyberg/http-client/pull/227.-    context <- NC.initConnectionContext-    let settings = mkManagerSettingsContext (Just context) def Nothing-    manager <- newManager settings-    newIORef manager+globalManager = unsafePerformIO $ newTlsManager >>= newIORef {-# NOINLINE globalManager #-}  -- | Get the current global 'Manager'@@ -238,7 +331,7 @@                 -> Request                 -> Manager                 -> m (n Request)-applyDigestAuth user pass req man = liftIO $ do+applyDigestAuth user pass req0 man = liftIO $ do     res <- httpNoBody req man     let throw' = throwM . DigestAuthException req res     return $ do@@ -269,7 +362,7 @@                 -- we always use no qop or qop=auth                 ha2 = md5 $ S.concat [method req, ":", path req] -                md5 bs = convertToBase Base16 (hash bs :: Digest MD5)+                md5 = extractBase16 . encodeBase16' . MD5.hash             key = "Authorization"             val = S.concat                 [ "Digest username=\""@@ -299,6 +392,10 @@             , cookieJar = Just $ responseCookieJar res             }   where+    -- Since we're expecting a non-200 response, ensure we do not+    -- throw exceptions for such responses.+    req = req0 { checkResponse = \_ _ -> return () }+     stripCI x y         | CI.mk x == CI.mk (S.take len y) = Just $ S.drop len y         | otherwise = Nothing
bench/Bench.hs view
@@ -1,6 +1,11 @@+{-# LANGUAGE CPP #-} module Main where -import Criterion.Main+#if MIN_VERSION_gauge(0, 2, 0)+import Gauge+#else+import Gauge.Main+#endif import Network.HTTP.Client import Network.HTTP.Client.TLS 
http-client-tls.cabal view
@@ -1,5 +1,5 @@ name:                http-client-tls-version:             0.3.3+version:             0.4.0 synopsis:            http-client backend using the connection package and tls library description:         Hackage documentation generation is not reliable. For up to date documentation, please see: <https://www.stackage.org/package/http-client-tls>. homepage:            https://github.com/snoyberg/http-client@@ -16,19 +16,22 @@ library   exposed-modules:     Network.HTTP.Client.TLS   other-extensions:    ScopedTypeVariables-  build-depends:       base >= 4 && < 5-                     , data-default-class-                     , http-client >= 0.5.0-                     , connection >= 0.2.2+  build-depends:       base >= 4.10 && < 5+                     , base16 >= 1.0+                     , cryptohash-md5+                     , data-default+                     , http-client >= 0.7.11+                     , crypton-connection                      , network-                     , tls >= 1.2+                     , tls >= 2.1.2                      , bytestring                      , case-insensitive                      , transformers                      , http-types-                     , cryptonite-                     , memory                      , exceptions+                     , containers+                     , text+                     , network-uri   default-language:    Haskell2010   ghc-options:         -Wall @@ -42,6 +45,9 @@                      , http-client                      , http-client-tls                      , http-types+                     , crypton-connection+                     , data-default+                     , tls  benchmark benchmark   main-is:             Bench.hs@@ -49,6 +55,6 @@   hs-source-dirs:      bench   default-language:    Haskell2010   build-depends:       base-                     , criterion+                     , gauge                      , http-client                      , http-client-tls
test/Spec.hs view
@@ -1,12 +1,28 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} import Test.Hspec+import Network.Connection import Network.HTTP.Client-import Network.HTTP.Client.TLS+import Network.HTTP.Client.TLS hiding (tlsManagerSettings) import Network.HTTP.Types import Control.Monad (join)+import Data.Default+import qualified Network.TLS as TLS  main :: IO () main = hspec $ do+    let tlsSettings = def+    -- Since the release of v2.0.0 of the `tls` package , the default value of+    -- the `supportedExtendedMainSecret` parameter `is `RequireEMS`, this means+    -- that all the connections to a server not supporting TLS1.2+EMS will fail.+    -- The badssl.com service does not yet support TLS1.2+EMS connections, so+    -- let's switch to the value `AllowEMS`, ie: TLS1.2 conenctions without EMS.+#if MIN_VERSION_crypton_connection(0,4,0)+            {settingClientSupported = def {TLS.supportedExtendedMainSecret = TLS.AllowEMS}}+#endif++    let tlsManagerSettings = mkManagerSettings tlsSettings Nothing+     it "make a TLS connection" $ do         manager <- newManager tlsManagerSettings         withResponse "https://httpbin.org/status/418" manager $ \res ->@@ -27,3 +43,45 @@         join (applyDigestAuth "user" "passwd" "http://httpbin.org/" man)             `shouldThrow` \(DigestAuthException _ _ det) ->                 det == UnexpectedStatusCode++    it "BadSSL: expired" $ do+        manager <- newManager tlsManagerSettings+        let action = withResponse "https://expired.badssl.com/" manager (const (return ()))+        action `shouldThrow` anyException++    it "BadSSL: self-signed" $ do+        manager <- newManager tlsManagerSettings+        let action = withResponse "https://self-signed.badssl.com/" manager (const (return ()))+        action `shouldThrow` anyException++    it "BadSSL: wrong.host" $ do+        manager <- newManager tlsManagerSettings+        let action = withResponse "https://wrong.host.badssl.com/" manager (const (return ()))+        action `shouldThrow` anyException++    it "BadSSL: we do have case-insensitivity though" $ do+        manager <- newManager $ tlsManagerSettings+        withResponse "https://BADSSL.COM" manager $ \res ->+            responseStatus res `shouldBe` status200++    -- https://github.com/snoyberg/http-client/issues/289+    it "accepts TLS settings" $ do+        let+          tlsSettings' = tlsSettings+            { settingDisableCertificateValidation = True+            , settingDisableSession = False+            , settingUseServerName = False+            }+          socketSettings = Nothing+          managerSettings = mkManagerSettings tlsSettings' socketSettings+        manager <- newTlsManagerWith managerSettings+        let url = "https://wrong.host.badssl.com"+        request <- parseRequest url+        response <- httpNoBody request manager+        responseStatus response `shouldBe` status200++    it "global supports TLS" $ do+        manager <- getGlobalManager+        request <- parseRequest "https://httpbin.org"+        response <- httpNoBody request manager+        responseStatus response `shouldBe` status200