001619f7b1
Former-commit-id: c28e4cf38fd7e8b55d6453cc7ada8eef3cb722a8 Former-commit-id: 0e8573342851be0e0812a9d92548d65e1d234fdf [formerly 0ec3417203a97ffba77fad8074234e0009bbd578] [formerly 354294f60f429b260e49e3cd647d36b0aa360cf2 [formerly 01a7d505fdc8d95fe4f2bd591f91863a2d94f82d [formerly 01a7d505fdc8d95fe4f2bd591f91863a2d94f82d [formerly 01a7d505fdc8d95fe4f2bd591f91863a2d94f82d [formerly 2b55b3e5cefc3c9c908a54600772774d92aea898]]]]] Former-commit-id: 2fd3f1de8cdd14032219415083519e8f5b49283f [formerly 85e94bf33bd4caa21f9db9bf0d4013f1218b94c3] Former-commit-id: 86e9b904cd8018f72094e3da79d05d072cee9d9a Former-commit-id: 4601d50c8322c295bc91572481a13d15c4d3d4f7 Former-commit-id: eea6001db6f78629d3b22b094c02f1aad0f7e754 Former-commit-id: 68980f8021f05d20bcb61de43d008e9fae3ad611 [formerly 77587a0a95a23f76bd395ddd6078f85114e1c606] Former-commit-id: f80fc49e44f9cfe329f76d4f02e7742e8fa41f9b
80 lines
3.3 KiB
Haskell
80 lines
3.3 KiB
Haskell
{-# LANGUAGE FlexibleContexts #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
{-# LANGUAGE StrictData #-}
|
|
{-# LANGUAGE ViewPatterns #-}
|
|
|
|
module HttpProxy () where
|
|
|
|
|
|
|
|
import ClassyPrelude
|
|
import qualified Data.ByteString.Char8 as BC
|
|
|
|
import Control.Monad.Except
|
|
import qualified Data.Conduit.Network.TLS as N
|
|
import qualified Data.Streaming.Network as N
|
|
|
|
import qualified Data.ByteString.Base64 as B64
|
|
import Network.Socket (HostName, PortNumber)
|
|
import qualified Network.Socket as N hiding (recv, recvFrom, send,
|
|
sendTo)
|
|
import qualified Network.Socket.ByteString as N
|
|
|
|
import Logger
|
|
import Types
|
|
|
|
|
|
data HttpProxySettings = HttpProxySettings
|
|
{ proxyHost :: HostName
|
|
, proxyPort :: PortNumber
|
|
, credentials :: Maybe (ByteString, ByteString)
|
|
} deriving (Show)
|
|
|
|
|
|
httpProxyConnection :: MonadError Error m => HttpProxySettings -> (HostName, PortNumber) -> (Connection -> IO (m a)) -> IO (m a)
|
|
httpProxyConnection HttpProxySettings{..} (host, port) app = onError $ do
|
|
debug $ "Opening tcp connection to proxy " <> show proxyHost <> ":" <> show proxyPort
|
|
|
|
ret <- N.runTCPClient (N.clientSettingsTCP (fromIntegral proxyPort) (fromString proxyHost)) $ \conn' -> do
|
|
let conn = toConnection conn'
|
|
_ <- sendConnectRequest conn
|
|
|
|
-- wait 10sec for a reply before giving up
|
|
let _10sec = 1000000 * 10
|
|
responseM <- timeout _10sec $ readConnectResponse mempty conn
|
|
|
|
case responseM of
|
|
Just (isAuthorized -> True) -> app conn
|
|
Just response -> return . throwError $ ProxyForwardError (BC.unpack response)
|
|
Nothing -> return . throwError $ ProxyForwardError ("No response from the proxy after "
|
|
<> show (_10sec `div` 1000000) <> "sec" )
|
|
|
|
debug $ "Closing tcp connection to proxy " <> show proxyHost <> ":" <> show proxyPort
|
|
return ret
|
|
|
|
where
|
|
credentialsToHeader :: (ByteString, ByteString) -> ByteString
|
|
credentialsToHeader (user, password) = "Proxy-Authorization: Basic " <> B64.encode (user <> ":" <> password) <> "\r\n"
|
|
|
|
sendConnectRequest :: Connection -> IO ()
|
|
sendConnectRequest h = write h $ "CONNECT " <> fromString host <> ":" <> fromString (show port) <> " HTTP/1.0\r\n"
|
|
<> "Host: " <> fromString host <> ":" <> fromString (show port) <> "\r\n"
|
|
<> maybe mempty credentialsToHeader credentials
|
|
<> "\r\n"
|
|
|
|
readConnectResponse :: ByteString -> Connection -> IO ByteString
|
|
readConnectResponse buff conn = do
|
|
responseM <- read conn
|
|
case responseM of
|
|
Nothing -> return buff
|
|
Just response -> if "\r\n\r\n" `isInfixOf` response
|
|
then return $ buff <> response
|
|
else readConnectResponse (buff <> response) conn
|
|
|
|
isAuthorized :: ByteString -> Bool
|
|
isAuthorized response = " 200 " `isInfixOf` response
|
|
|
|
onError f = catch f $ \(e :: SomeException) -> return $
|
|
if take 10 (show e) == "user error"
|
|
then throwError $ ProxyConnectionError (show e)
|
|
else throwError $ ProxyConnectionError ("Unknown Error :: " <> show e)
|