|
Packit |
acf257 |
{-# LANGUAGE CPP #-}
|
|
Packit |
acf257 |
-----------------------------------------------------------------------------
|
|
Packit |
acf257 |
-- |
|
|
Packit |
acf257 |
-- Module : Network.HTTP.Proxy
|
|
Packit |
acf257 |
-- Copyright : See LICENSE file
|
|
Packit |
acf257 |
-- License : BSD
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
-- Maintainer : Ganesh Sittampalam <ganesh@earth.li>
|
|
Packit |
acf257 |
-- Stability : experimental
|
|
Packit |
acf257 |
-- Portability : non-portable (not tested)
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
-- Handling proxy server settings and their resolution.
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
-----------------------------------------------------------------------------
|
|
Packit |
acf257 |
module Network.HTTP.Proxy
|
|
Packit |
acf257 |
( Proxy(..)
|
|
Packit |
acf257 |
, noProxy -- :: Proxy
|
|
Packit |
acf257 |
, fetchProxy -- :: Bool -> IO Proxy
|
|
Packit |
acf257 |
, parseProxy -- :: String -> Maybe Proxy
|
|
Packit |
acf257 |
) where
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
{-
|
|
Packit |
acf257 |
#if !defined(WIN32) && defined(mingw32_HOST_OS)
|
|
Packit |
acf257 |
#define WIN32 1
|
|
Packit |
acf257 |
#endif
|
|
Packit |
acf257 |
-}
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
import Control.Monad ( when, mplus, join, liftM2 )
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
#if defined(WIN32)
|
|
Packit |
acf257 |
import Network.HTTP.Base ( catchIO )
|
|
Packit |
acf257 |
import Control.Monad ( liftM )
|
|
Packit |
acf257 |
import Data.List ( isPrefixOf )
|
|
Packit |
acf257 |
#endif
|
|
Packit |
acf257 |
import Network.HTTP.Utils ( dropWhileTail, chopAtDelim )
|
|
Packit |
acf257 |
import Network.HTTP.Auth
|
|
Packit |
acf257 |
import Network.URI
|
|
Packit |
acf257 |
( URI(..), URIAuth(..), parseAbsoluteURI, unEscapeString )
|
|
Packit |
acf257 |
import System.IO ( hPutStrLn, stderr )
|
|
Packit |
acf257 |
import System.Environment
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
{-
|
|
Packit |
acf257 |
#if !defined(WIN32) && defined(mingw32_HOST_OS)
|
|
Packit |
acf257 |
#define WIN32 1
|
|
Packit |
acf257 |
#endif
|
|
Packit |
acf257 |
-}
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
#if defined(WIN32)
|
|
Packit |
acf257 |
import System.Win32.Types ( DWORD, HKEY )
|
|
Packit |
acf257 |
import System.Win32.Registry( hKEY_CURRENT_USER, regOpenKey, regCloseKey, regQueryValue, regQueryValueEx )
|
|
Packit |
acf257 |
import Control.Exception ( bracket )
|
|
Packit |
acf257 |
import Foreign ( toBool, Storable(peek, sizeOf), castPtr, alloca )
|
|
Packit |
acf257 |
#endif
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- | HTTP proxies (or not) are represented via 'Proxy', specifying if a
|
|
Packit |
acf257 |
-- proxy should be used for the request (see 'Network.Browser.setProxy')
|
|
Packit |
acf257 |
data Proxy
|
|
Packit |
acf257 |
= NoProxy -- ^ Don't use a proxy.
|
|
Packit |
acf257 |
| Proxy String
|
|
Packit |
acf257 |
(Maybe Authority) -- ^ Use the proxy given. Should be of the
|
|
Packit |
acf257 |
-- form "http:\/\/host:port", "host", "host:port", or "http:\/\/host".
|
|
Packit |
acf257 |
-- Additionally, an optional 'Authority' for authentication with the proxy.
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
noProxy :: Proxy
|
|
Packit |
acf257 |
noProxy = NoProxy
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- | @envProxyString@ locates proxy server settings by looking
|
|
Packit |
acf257 |
-- up env variable @HTTP_PROXY@ (or its lower-case equivalent.)
|
|
Packit |
acf257 |
-- If no mapping found, returns @Nothing@.
|
|
Packit |
acf257 |
envProxyString :: IO (Maybe String)
|
|
Packit |
acf257 |
envProxyString = do
|
|
Packit |
acf257 |
env <- getEnvironment
|
|
Packit |
acf257 |
return (lookup "http_proxy" env `mplus` lookup "HTTP_PROXY" env)
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- | @proxyString@ tries to locate the user's proxy server setting.
|
|
Packit |
acf257 |
-- Consults environment variable, and in case of Windows, by querying
|
|
Packit |
acf257 |
-- the Registry (cf. @registryProxyString@.)
|
|
Packit |
acf257 |
proxyString :: IO (Maybe String)
|
|
Packit |
acf257 |
proxyString = liftM2 mplus envProxyString windowsProxyString
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
windowsProxyString :: IO (Maybe String)
|
|
Packit |
acf257 |
#if !defined(WIN32)
|
|
Packit |
acf257 |
windowsProxyString = return Nothing
|
|
Packit |
acf257 |
#else
|
|
Packit |
acf257 |
windowsProxyString = liftM (>>= parseWindowsProxy) registryProxyString
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
registryProxyLoc :: (HKEY,String)
|
|
Packit |
acf257 |
registryProxyLoc = (hive, path)
|
|
Packit |
acf257 |
where
|
|
Packit |
acf257 |
-- some sources say proxy settings should be at
|
|
Packit |
acf257 |
-- HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows
|
|
Packit |
acf257 |
-- \CurrentVersion\Internet Settings\ProxyServer
|
|
Packit |
acf257 |
-- but if the user sets them with IE connection panel they seem to
|
|
Packit |
acf257 |
-- end up in the following place:
|
|
Packit |
acf257 |
hive = hKEY_CURRENT_USER
|
|
Packit |
acf257 |
path = "Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings"
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- read proxy settings from the windows registry; this is just a best
|
|
Packit |
acf257 |
-- effort and may not work on all setups.
|
|
Packit |
acf257 |
registryProxyString :: IO (Maybe String)
|
|
Packit |
acf257 |
registryProxyString = catchIO
|
|
Packit |
acf257 |
(bracket (uncurry regOpenKey registryProxyLoc) regCloseKey $ \hkey -> do
|
|
Packit |
acf257 |
enable <- fmap toBool $ regQueryValueDWORD hkey "ProxyEnable"
|
|
Packit |
acf257 |
if enable
|
|
Packit |
acf257 |
then fmap Just $ regQueryValue hkey (Just "ProxyServer")
|
|
Packit |
acf257 |
else return Nothing)
|
|
Packit |
acf257 |
(\_ -> return Nothing)
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- the proxy string is in the format "http=x.x.x.x:yyyy;https=...;ftp=...;socks=..."
|
|
Packit |
acf257 |
-- even though the following article indicates otherwise
|
|
Packit |
acf257 |
-- https://support.microsoft.com/en-us/kb/819961
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
-- to be sure, parse strings where each entry in the ';'-separated list above is
|
|
Packit |
acf257 |
-- either in the format "protocol=..." or "protocol://..."
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
-- only return the first "http" of them, if it exists
|
|
Packit |
acf257 |
parseWindowsProxy :: String -> Maybe String
|
|
Packit |
acf257 |
parseWindowsProxy s =
|
|
Packit |
acf257 |
case proxies of
|
|
Packit |
acf257 |
x:_ -> Just x
|
|
Packit |
acf257 |
_ -> Nothing
|
|
Packit |
acf257 |
where
|
|
Packit |
acf257 |
parts = split ';' s
|
|
Packit |
acf257 |
pr x = case break (== '=') x of
|
|
Packit |
acf257 |
(p, []) -> p -- might be in format http://
|
|
Packit |
acf257 |
(p, u) -> p ++ "://" ++ drop 1 u
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
proxies = filter (isPrefixOf "http://") . map pr $ parts
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
split :: Eq a => a -> [a] -> [[a]]
|
|
Packit |
acf257 |
split _ [] = []
|
|
Packit |
acf257 |
split a xs = case break (a ==) xs of
|
|
Packit |
acf257 |
(ys, []) -> [ys]
|
|
Packit |
acf257 |
(ys, _:zs) -> ys:split a zs
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
#endif
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- | @fetchProxy flg@ gets the local proxy settings and parse the string
|
|
Packit |
acf257 |
-- into a @Proxy@ value. If you want to be informed of ill-formed proxy
|
|
Packit |
acf257 |
-- configuration strings, supply @True@ for @flg@.
|
|
Packit |
acf257 |
-- Proxy settings are sourced from the @HTTP_PROXY@ environment variable,
|
|
Packit |
acf257 |
-- and in the case of Windows platforms, by consulting IE/WinInet's proxy
|
|
Packit |
acf257 |
-- setting in the Registry.
|
|
Packit |
acf257 |
fetchProxy :: Bool -> IO Proxy
|
|
Packit |
acf257 |
fetchProxy warnIfIllformed = do
|
|
Packit |
acf257 |
mstr <- proxyString
|
|
Packit |
acf257 |
case mstr of
|
|
Packit |
acf257 |
Nothing -> return NoProxy
|
|
Packit |
acf257 |
Just str -> case parseProxy str of
|
|
Packit |
acf257 |
Just p -> return p
|
|
Packit |
acf257 |
Nothing -> do
|
|
Packit |
acf257 |
when warnIfIllformed $ System.IO.hPutStrLn System.IO.stderr $ unlines
|
|
Packit |
acf257 |
[ "invalid http proxy uri: " ++ show str
|
|
Packit |
acf257 |
, "proxy uri must be http with a hostname"
|
|
Packit |
acf257 |
, "ignoring http proxy, trying a direct connection"
|
|
Packit |
acf257 |
]
|
|
Packit |
acf257 |
return NoProxy
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- | @parseProxy str@ translates a proxy server string into a @Proxy@ value;
|
|
Packit |
acf257 |
-- returns @Nothing@ if not well-formed.
|
|
Packit |
acf257 |
parseProxy :: String -> Maybe Proxy
|
|
Packit |
acf257 |
parseProxy "" = Nothing
|
|
Packit |
acf257 |
parseProxy str = join
|
|
Packit |
acf257 |
. fmap uri2proxy
|
|
Packit |
acf257 |
$ parseHttpURI str
|
|
Packit |
acf257 |
`mplus` parseHttpURI ("http://" ++ str)
|
|
Packit |
acf257 |
where
|
|
Packit |
acf257 |
parseHttpURI str' =
|
|
Packit |
acf257 |
case parseAbsoluteURI str' of
|
|
Packit |
acf257 |
Just uri@URI{uriAuthority = Just{}} -> Just (fixUserInfo uri)
|
|
Packit |
acf257 |
_ -> Nothing
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- Note: we need to be able to parse non-URIs like @\"wwwcache.example.com:80\"@
|
|
Packit |
acf257 |
-- which lack the @\"http://\"@ URI scheme. The problem is that
|
|
Packit |
acf257 |
-- @\"wwwcache.example.com:80\"@ is in fact a valid URI but with scheme
|
|
Packit |
acf257 |
-- @\"wwwcache.example.com:\"@, no authority part and a path of @\"80\"@.
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
-- So our strategy is to try parsing as normal uri first and if it lacks the
|
|
Packit |
acf257 |
-- 'uriAuthority' then we try parsing again with a @\"http://\"@ prefix.
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- | tidy up user portion, don't want the trailing "\@".
|
|
Packit |
acf257 |
fixUserInfo :: URI -> URI
|
|
Packit |
acf257 |
fixUserInfo uri = uri{ uriAuthority = f `fmap` uriAuthority uri }
|
|
Packit |
acf257 |
where
|
|
Packit |
acf257 |
f a@URIAuth{uriUserInfo=s} = a{uriUserInfo=dropWhileTail (=='@') s}
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
--
|
|
Packit |
acf257 |
uri2proxy :: URI -> Maybe Proxy
|
|
Packit |
acf257 |
uri2proxy uri@URI{ uriScheme = "http:"
|
|
Packit |
acf257 |
, uriAuthority = Just (URIAuth auth' hst prt)
|
|
Packit |
acf257 |
} =
|
|
Packit |
acf257 |
Just (Proxy (hst ++ prt) auth)
|
|
Packit |
acf257 |
where
|
|
Packit |
acf257 |
auth =
|
|
Packit |
acf257 |
case auth' of
|
|
Packit |
acf257 |
[] -> Nothing
|
|
Packit |
acf257 |
as -> Just (AuthBasic "" (unEscapeString usr) (unEscapeString pwd) uri)
|
|
Packit |
acf257 |
where
|
|
Packit |
acf257 |
(usr,pwd) = chopAtDelim ':' as
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
uri2proxy _ = Nothing
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
-- utilities
|
|
Packit |
acf257 |
#if defined(WIN32)
|
|
Packit |
acf257 |
regQueryValueDWORD :: HKEY -> String -> IO DWORD
|
|
Packit |
acf257 |
regQueryValueDWORD hkey name = alloca $ \ptr -> do
|
|
Packit |
acf257 |
-- TODO: this throws away the key type returned by regQueryValueEx
|
|
Packit |
acf257 |
-- we should check it's what we expect instead
|
|
Packit |
acf257 |
_ <- regQueryValueEx hkey name (castPtr ptr) (sizeOf (undefined :: DWORD))
|
|
Packit |
acf257 |
peek ptr
|
|
Packit |
acf257 |
|
|
Packit |
acf257 |
#endif
|