Blame Network/HTTP/Proxy.hs

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