Blame Network/Browser.hs

Packit acf257
{-# LANGUAGE MultiParamTypeClasses, GeneralizedNewtypeDeriving, CPP, FlexibleContexts #-}
Packit acf257
{- |
Packit acf257
Packit acf257
Module      :  Network.Browser
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
Session-level interactions over HTTP.
Packit acf257
 
Packit acf257
The "Network.Browser" goes beyond the basic "Network.HTTP" functionality in 
Packit acf257
providing support for more involved, and real, request/response interactions over 
Packit acf257
HTTP. Additional features supported are:
Packit acf257
Packit acf257
* HTTP Authentication handling
Packit acf257
Packit acf257
* Transparent handling of redirects
Packit acf257
Packit acf257
* Cookie stores + transmission.
Packit acf257
Packit acf257
* Transaction logging
Packit acf257
Packit acf257
* Proxy-mediated connections.
Packit acf257
Packit acf257
Example use:
Packit acf257
Packit acf257
>    do
Packit acf257
>      (_, rsp)
Packit acf257
>         <- Network.Browser.browse $ do
Packit acf257
>               setAllowRedirects True -- handle HTTP redirects
Packit acf257
>               request $ getRequest "http://www.haskell.org/"
Packit acf257
>      return (take 100 (rspBody rsp))
Packit acf257
 
Packit acf257
-}
Packit acf257
module Network.Browser 
Packit acf257
       ( BrowserState
Packit acf257
       , BrowserAction      -- browser monad, effectively a state monad.
Packit acf257
       , Proxy(..)
Packit acf257
       
Packit acf257
       , browse             -- :: BrowserAction a -> IO a
Packit acf257
       , request            -- :: Request -> BrowserAction Response
Packit acf257
    
Packit acf257
       , getBrowserState    -- :: BrowserAction t (BrowserState t)
Packit acf257
       , withBrowserState   -- :: BrowserState t -> BrowserAction t a -> BrowserAction t a
Packit acf257
       
Packit acf257
       , setAllowRedirects  -- :: Bool -> BrowserAction t ()
Packit acf257
       , getAllowRedirects  -- :: BrowserAction t Bool
Packit acf257
Packit acf257
       , setMaxRedirects    -- :: Int -> BrowserAction t ()
Packit acf257
       , getMaxRedirects    -- :: BrowserAction t (Maybe Int)
Packit acf257
       
Packit acf257
       , Authority(..)
Packit acf257
       , getAuthorities
Packit acf257
       , setAuthorities
Packit acf257
       , addAuthority
Packit acf257
       , Challenge(..)
Packit acf257
       , Qop(..)
Packit acf257
       , Algorithm(..)
Packit acf257
       
Packit acf257
       , getAuthorityGen
Packit acf257
       , setAuthorityGen
Packit acf257
       , setAllowBasicAuth
Packit acf257
       , getAllowBasicAuth
Packit acf257
       
Packit acf257
       , setMaxErrorRetries  -- :: Maybe Int -> BrowserAction t ()
Packit acf257
       , getMaxErrorRetries  -- :: BrowserAction t (Maybe Int)
Packit acf257
Packit acf257
       , setMaxPoolSize     -- :: Int -> BrowserAction t ()
Packit acf257
       , getMaxPoolSize     -- :: BrowserAction t (Maybe Int)
Packit acf257
Packit acf257
       , setMaxAuthAttempts  -- :: Maybe Int -> BrowserAction t ()
Packit acf257
       , getMaxAuthAttempts  -- :: BrowserAction t (Maybe Int)
Packit acf257
Packit acf257
       , setCookieFilter     -- :: (URI -> Cookie -> IO Bool) -> BrowserAction t ()
Packit acf257
       , getCookieFilter     -- :: BrowserAction t (URI -> Cookie -> IO Bool)
Packit acf257
       , defaultCookieFilter -- :: URI -> Cookie -> IO Bool
Packit acf257
       , userCookieFilter    -- :: URI -> Cookie -> IO Bool
Packit acf257
       
Packit acf257
       , Cookie(..)
Packit acf257
       , getCookies        -- :: BrowserAction t [Cookie]
Packit acf257
       , setCookies        -- :: [Cookie] -> BrowserAction t ()
Packit acf257
       , addCookie         -- :: Cookie   -> BrowserAction t ()
Packit acf257
       
Packit acf257
       , setErrHandler     -- :: (String -> IO ()) -> BrowserAction t ()
Packit acf257
       , setOutHandler     -- :: (String -> IO ()) -> BrowserAction t ()
Packit acf257
    
Packit acf257
       , setEventHandler   -- :: (BrowserEvent -> BrowserAction t ()) -> BrowserAction t ()
Packit acf257
       
Packit acf257
       , BrowserEvent(..)
Packit acf257
       , BrowserEventType(..)
Packit acf257
       , RequestID
Packit acf257
       
Packit acf257
       , setProxy         -- :: Proxy -> BrowserAction t ()
Packit acf257
       , getProxy         -- :: BrowserAction t Proxy
Packit acf257
Packit acf257
       , setCheckForProxy -- :: Bool -> BrowserAction t ()
Packit acf257
       , getCheckForProxy -- :: BrowserAction t Bool
Packit acf257
Packit acf257
       , setDebugLog      -- :: Maybe String -> BrowserAction t ()
Packit acf257
       
Packit acf257
       , getUserAgent     -- :: BrowserAction t String
Packit acf257
       , setUserAgent     -- :: String -> BrowserAction t ()
Packit acf257
       
Packit acf257
       , out              -- :: String -> BrowserAction t ()
Packit acf257
       , err              -- :: String -> BrowserAction t ()
Packit acf257
       , ioAction         -- :: IO a -> BrowserAction a
Packit acf257
Packit acf257
       , defaultGETRequest
Packit acf257
       , defaultGETRequest_
Packit acf257
       
Packit acf257
       , formToRequest
Packit acf257
       , uriDefaultTo
Packit acf257
       
Packit acf257
         -- old and half-baked; don't use:
Packit acf257
       , Form(..)
Packit acf257
       , FormVar
Packit acf257
       ) where
Packit acf257
Packit acf257
import Network.URI
Packit acf257
   ( URI(..)
Packit acf257
   , URIAuth(..)
Packit acf257
   , parseURI, parseURIReference, relativeTo
Packit acf257
   )
Packit acf257
import Network.StreamDebugger (debugByteStream)
Packit acf257
import Network.HTTP hiding ( sendHTTP_notify )
Packit acf257
import Network.HTTP.HandleStream ( sendHTTP_notify )
Packit acf257
import Network.HTTP.Auth
Packit acf257
import Network.HTTP.Cookie
Packit acf257
import Network.HTTP.Proxy
Packit acf257
Packit acf257
import Network.Stream ( ConnError(..), Result )
Packit acf257
import Network.BufferType
Packit acf257
Packit acf257
import Data.Char (toLower)
Packit acf257
import Data.List (isPrefixOf)
Packit acf257
import Data.Maybe (fromMaybe, listToMaybe, catMaybes )
Packit acf257
import Control.Applicative (Applicative (..), (<$>))
Packit acf257
#ifdef MTL1
Packit acf257
import Control.Monad (filterM, forM_, when, ap)
Packit acf257
#else
Packit acf257
import Control.Monad (filterM, forM_, when)
Packit acf257
#endif
Packit acf257
import Control.Monad.State (StateT (..), MonadIO (..), modify, gets, withStateT, evalStateT, MonadState (..))
Packit acf257
Packit acf257
import qualified System.IO
Packit acf257
   ( hSetBuffering, hPutStr, stdout, stdin, hGetChar
Packit acf257
   , BufferMode(NoBuffering, LineBuffering)
Packit acf257
   )
Packit acf257
import Data.Time.Clock ( UTCTime, getCurrentTime )
Packit acf257
Packit acf257
Packit acf257
------------------------------------------------------------------
Packit acf257
----------------------- Cookie Stuff -----------------------------
Packit acf257
------------------------------------------------------------------
Packit acf257
Packit acf257
-- | @defaultCookieFilter@ is the initial cookie acceptance filter.
Packit acf257
-- It welcomes them all into the store @:-)@
Packit acf257
defaultCookieFilter :: URI -> Cookie -> IO Bool
Packit acf257
defaultCookieFilter _url _cky = return True
Packit acf257
Packit acf257
-- | @userCookieFilter@ is a handy acceptance filter, asking the
Packit acf257
-- user if he/she is willing to accept an incoming cookie before
Packit acf257
-- adding it to the store.
Packit acf257
userCookieFilter :: URI -> Cookie -> IO Bool
Packit acf257
userCookieFilter url cky = do
Packit acf257
    do putStrLn ("Set-Cookie received when requesting: " ++ show url)
Packit acf257
       case ckComment cky of
Packit acf257
          Nothing -> return ()
Packit acf257
          Just x  -> putStrLn ("Cookie Comment:\n" ++ x)
Packit acf257
       let pth = maybe "" ('/':) (ckPath cky)
Packit acf257
       putStrLn ("Domain/Path: " ++ ckDomain cky ++ pth)
Packit acf257
       putStrLn (ckName cky ++ '=' : ckValue cky)
Packit acf257
       System.IO.hSetBuffering System.IO.stdout System.IO.NoBuffering
Packit acf257
       System.IO.hSetBuffering System.IO.stdin System.IO.NoBuffering
Packit acf257
       System.IO.hPutStr System.IO.stdout "Accept [y/n]? "
Packit acf257
       x <- System.IO.hGetChar System.IO.stdin
Packit acf257
       System.IO.hSetBuffering System.IO.stdin System.IO.LineBuffering
Packit acf257
       System.IO.hSetBuffering System.IO.stdout System.IO.LineBuffering
Packit acf257
       return (toLower x == 'y')
Packit acf257
Packit acf257
-- | @addCookie c@ adds a cookie to the browser state, removing duplicates.
Packit acf257
addCookie :: Cookie -> BrowserAction t ()
Packit acf257
addCookie c = modify (\b -> b{bsCookies = c : filter (/=c) (bsCookies b) })
Packit acf257
Packit acf257
-- | @setCookies cookies@ replaces the set of cookies known to
Packit acf257
-- the browser to @cookies@. Useful when wanting to restore cookies
Packit acf257
-- used across 'browse' invocations.
Packit acf257
setCookies :: [Cookie] -> BrowserAction t ()
Packit acf257
setCookies cs = modify (\b -> b { bsCookies=cs })
Packit acf257
Packit acf257
-- | @getCookies@ returns the current set of cookies known to
Packit acf257
-- the browser.
Packit acf257
getCookies :: BrowserAction t [Cookie]
Packit acf257
getCookies = gets bsCookies
Packit acf257
Packit acf257
-- ...get domain specific cookies...
Packit acf257
-- ... this needs changing for consistency with rfc2109...
Packit acf257
-- ... currently too broad.
Packit acf257
getCookiesFor :: String -> String -> BrowserAction t [Cookie]
Packit acf257
getCookiesFor dom path =
Packit acf257
    do cks <- getCookies
Packit acf257
       return (filter cookiematch cks)
Packit acf257
    where
Packit acf257
        cookiematch :: Cookie -> Bool
Packit acf257
        cookiematch = cookieMatch (dom,path)
Packit acf257
      
Packit acf257
Packit acf257
-- | @setCookieFilter fn@ sets the cookie acceptance filter to @fn@.
Packit acf257
setCookieFilter :: (URI -> Cookie -> IO Bool) -> BrowserAction t ()
Packit acf257
setCookieFilter f = modify (\b -> b { bsCookieFilter=f })
Packit acf257
Packit acf257
-- | @getCookieFilter@ returns the current cookie acceptance filter.
Packit acf257
getCookieFilter :: BrowserAction t (URI -> Cookie -> IO Bool)
Packit acf257
getCookieFilter = gets bsCookieFilter
Packit acf257
Packit acf257
------------------------------------------------------------------
Packit acf257
----------------------- Authorisation Stuff ----------------------
Packit acf257
------------------------------------------------------------------
Packit acf257
Packit acf257
{-
Packit acf257
Packit acf257
The browser handles 401 responses in the following manner:
Packit acf257
  1) extract all WWW-Authenticate headers from a 401 response
Packit acf257
  2) rewrite each as a Challenge object, using "headerToChallenge"
Packit acf257
  3) pick a challenge to respond to, usually the strongest
Packit acf257
     challenge understood by the client, using "pickChallenge"
Packit acf257
  4) generate a username/password combination using the browsers
Packit acf257
     "bsAuthorityGen" function (the default behaviour is to ask
Packit acf257
     the user)
Packit acf257
  5) build an Authority object based upon the challenge and user
Packit acf257
     data, store this new Authority in the browser state
Packit acf257
  6) convert the Authority to a request header and add this
Packit acf257
     to a request using "withAuthority"
Packit acf257
  7) send the amended request
Packit acf257
Packit acf257
Note that by default requests are annotated with authority headers
Packit acf257
before the first sending, based upon previously generated Authority
Packit acf257
objects (which contain domain information).  Once a specific authority
Packit acf257
is added to a rejected request this predictive annotation is suppressed.
Packit acf257
Packit acf257
407 responses are handled in a similar manner, except
Packit acf257
   a) Authorities are not collected, only a single proxy authority
Packit acf257
      is kept by the browser
Packit acf257
   b) If the proxy used by the browser (type Proxy) is NoProxy, then
Packit acf257
      a 407 response will generate output on the "err" stream and
Packit acf257
      the response will be returned.
Packit acf257
Packit acf257
Packit acf257
Notes:
Packit acf257
 - digest authentication so far ignores qop, so fails to authenticate 
Packit acf257
   properly with qop=auth-int challenges
Packit acf257
 - calculates a1 more than necessary
Packit acf257
 - doesn't reverse authenticate
Packit acf257
 - doesn't properly receive AuthenticationInfo headers, so fails
Packit acf257
   to use next-nonce etc
Packit acf257
Packit acf257
-}
Packit acf257
Packit acf257
-- | Return authorities for a given domain and path.
Packit acf257
-- Assumes "dom" is lower case
Packit acf257
getAuthFor :: String -> String -> BrowserAction t [Authority]
Packit acf257
getAuthFor dom pth = getAuthorities >>= return . (filter match)
Packit acf257
   where
Packit acf257
    match :: Authority -> Bool
Packit acf257
    match au@AuthBasic{}  = matchURI (auSite au)
Packit acf257
    match au@AuthDigest{} = or (map matchURI (auDomain au))
Packit acf257
Packit acf257
    matchURI :: URI -> Bool
Packit acf257
    matchURI s = (uriToAuthorityString s == dom) && (uriPath s `isPrefixOf` pth)
Packit acf257
    
Packit acf257
Packit acf257
-- | @getAuthorities@ return the current set of @Authority@s known
Packit acf257
-- to the browser.
Packit acf257
getAuthorities :: BrowserAction t [Authority]
Packit acf257
getAuthorities = gets bsAuthorities
Packit acf257
Packit acf257
-- @setAuthorities as@ replaces the Browser's known set
Packit acf257
-- of 'Authority's to @as@.
Packit acf257
setAuthorities :: [Authority] -> BrowserAction t ()
Packit acf257
setAuthorities as = modify (\b -> b { bsAuthorities=as })
Packit acf257
Packit acf257
-- @addAuthority a@ adds 'Authority' @a@ to the Browser's
Packit acf257
-- set of known authorities.
Packit acf257
addAuthority :: Authority -> BrowserAction t ()
Packit acf257
addAuthority a = modify (\b -> b { bsAuthorities=a:bsAuthorities b })
Packit acf257
Packit acf257
-- | @getAuthorityGen@ returns the current authority generator
Packit acf257
getAuthorityGen :: BrowserAction t (URI -> String -> IO (Maybe (String,String)))
Packit acf257
getAuthorityGen = gets bsAuthorityGen
Packit acf257
Packit acf257
-- | @setAuthorityGen genAct@ sets the auth generator to @genAct@.
Packit acf257
setAuthorityGen :: (URI -> String -> IO (Maybe (String,String))) -> BrowserAction t ()
Packit acf257
setAuthorityGen f = modify (\b -> b { bsAuthorityGen=f })
Packit acf257
Packit acf257
-- | @setAllowBasicAuth onOff@ enables\/disables HTTP Basic Authentication.
Packit acf257
setAllowBasicAuth :: Bool -> BrowserAction t ()
Packit acf257
setAllowBasicAuth ba = modify (\b -> b { bsAllowBasicAuth=ba })
Packit acf257
Packit acf257
getAllowBasicAuth :: BrowserAction t Bool
Packit acf257
getAllowBasicAuth = gets bsAllowBasicAuth
Packit acf257
Packit acf257
-- | @setMaxAuthAttempts mbMax@ sets the maximum number of authentication attempts
Packit acf257
-- to do. If @Nothing@, rever to default max.
Packit acf257
setMaxAuthAttempts :: Maybe Int -> BrowserAction t ()
Packit acf257
setMaxAuthAttempts mb 
Packit acf257
 | fromMaybe 0 mb < 0 = return ()
Packit acf257
 | otherwise          = modify (\ b -> b{bsMaxAuthAttempts=mb})
Packit acf257
Packit acf257
-- | @getMaxAuthAttempts@ returns the current max auth attempts. If @Nothing@,
Packit acf257
-- the browser's default is used.
Packit acf257
getMaxAuthAttempts :: BrowserAction t (Maybe Int)
Packit acf257
getMaxAuthAttempts = gets bsMaxAuthAttempts
Packit acf257
Packit acf257
-- | @setMaxErrorRetries mbMax@ sets the maximum number of attempts at
Packit acf257
-- transmitting a request. If @Nothing@, rever to default max.
Packit acf257
setMaxErrorRetries :: Maybe Int -> BrowserAction t ()
Packit acf257
setMaxErrorRetries mb
Packit acf257
 | fromMaybe 0 mb < 0 = return ()
Packit acf257
 | otherwise          = modify (\ b -> b{bsMaxErrorRetries=mb})
Packit acf257
Packit acf257
-- | @getMaxErrorRetries@ returns the current max number of error retries.
Packit acf257
getMaxErrorRetries :: BrowserAction t (Maybe Int)
Packit acf257
getMaxErrorRetries = gets bsMaxErrorRetries
Packit acf257
Packit acf257
-- TO BE CHANGED!!!
Packit acf257
pickChallenge :: Bool -> [Challenge] -> Maybe Challenge
Packit acf257
pickChallenge allowBasic []
Packit acf257
 | allowBasic = Just (ChalBasic "/") -- manufacture a challenge if one missing; more robust.
Packit acf257
pickChallenge _ ls = listToMaybe ls
Packit acf257
Packit acf257
-- | Retrieve a likely looking authority for a Request.
Packit acf257
anticipateChallenge :: Request ty -> BrowserAction t (Maybe Authority)
Packit acf257
anticipateChallenge rq =
Packit acf257
    let uri = rqURI rq in
Packit acf257
    do { authlist <- getAuthFor (uriAuthToString $ reqURIAuth rq) (uriPath uri)
Packit acf257
       ; return (listToMaybe authlist)
Packit acf257
       }
Packit acf257
Packit acf257
-- | Asking the user to respond to a challenge
Packit acf257
challengeToAuthority :: URI -> Challenge -> BrowserAction t (Maybe Authority)
Packit acf257
challengeToAuthority uri ch
Packit acf257
 | not (answerable ch) = return Nothing
Packit acf257
 | otherwise = do
Packit acf257
      -- prompt user for authority
Packit acf257
    prompt <- getAuthorityGen
Packit acf257
    userdetails <- liftIO $ prompt uri (chRealm ch)
Packit acf257
    case userdetails of
Packit acf257
     Nothing    -> return Nothing
Packit acf257
     Just (u,p) -> return (Just $ buildAuth ch u p)
Packit acf257
 where
Packit acf257
  answerable :: Challenge -> Bool
Packit acf257
  answerable ChalBasic{} = True
Packit acf257
  answerable chall       = (chAlgorithm chall) == Just AlgMD5
Packit acf257
Packit acf257
  buildAuth :: Challenge -> String -> String -> Authority
Packit acf257
  buildAuth (ChalBasic r) u p = 
Packit acf257
       AuthBasic { auSite=uri
Packit acf257
                 , auRealm=r
Packit acf257
                 , auUsername=u
Packit acf257
                 , auPassword=p
Packit acf257
                 }
Packit acf257
Packit acf257
    -- note to self: this is a pretty stupid operation
Packit acf257
    -- to perform isn't it? ChalX and AuthX are so very
Packit acf257
    -- similar.
Packit acf257
  buildAuth (ChalDigest r d n o _stale a q) u p =
Packit acf257
            AuthDigest { auRealm=r
Packit acf257
                       , auUsername=u
Packit acf257
                       , auPassword=p
Packit acf257
                       , auDomain=d
Packit acf257
                       , auNonce=n
Packit acf257
                       , auOpaque=o
Packit acf257
                       , auAlgorithm=a
Packit acf257
                       , auQop=q
Packit acf257
                       }
Packit acf257
Packit acf257
Packit acf257
------------------------------------------------------------------
Packit acf257
------------------ Browser State Actions -------------------------
Packit acf257
------------------------------------------------------------------
Packit acf257
Packit acf257
Packit acf257
-- | @BrowserState@ is the (large) record type tracking the current
Packit acf257
-- settings of the browser.
Packit acf257
data BrowserState connection
Packit acf257
 = BS { bsErr, bsOut      :: String -> IO ()
Packit acf257
      , bsCookies         :: [Cookie]
Packit acf257
      , bsCookieFilter    :: URI -> Cookie -> IO Bool
Packit acf257
      , bsAuthorityGen    :: URI -> String -> IO (Maybe (String,String))
Packit acf257
      , bsAuthorities     :: [Authority]
Packit acf257
      , bsAllowRedirects  :: Bool
Packit acf257
      , bsAllowBasicAuth  :: Bool
Packit acf257
      , bsMaxRedirects    :: Maybe Int
Packit acf257
      , bsMaxErrorRetries :: Maybe Int
Packit acf257
      , bsMaxAuthAttempts :: Maybe Int
Packit acf257
      , bsMaxPoolSize     :: Maybe Int
Packit acf257
      , bsConnectionPool  :: [connection]
Packit acf257
      , bsCheckProxy      :: Bool
Packit acf257
      , bsProxy           :: Proxy
Packit acf257
      , bsDebug           :: Maybe String
Packit acf257
      , bsEvent           :: Maybe (BrowserEvent -> BrowserAction connection ())
Packit acf257
      , bsRequestID       :: RequestID
Packit acf257
      , bsUserAgent       :: Maybe String
Packit acf257
      }
Packit acf257
Packit acf257
instance Show (BrowserState t) where
Packit acf257
    show bs =  "BrowserState { " 
Packit acf257
            ++ shows (bsCookies bs) ("\n"
Packit acf257
           {- ++ show (bsAuthorities bs) ++ "\n"-}
Packit acf257
            ++ "AllowRedirects: " ++ shows (bsAllowRedirects bs) "} ")
Packit acf257
Packit acf257
-- | @BrowserAction@ is the IO monad, but carrying along a 'BrowserState'.
Packit acf257
newtype BrowserAction conn a
Packit acf257
 = BA { unBA :: StateT (BrowserState conn) IO a }
Packit acf257
#ifdef MTL1
Packit acf257
 deriving (Functor, Monad, MonadIO, MonadState (BrowserState conn))
Packit acf257
Packit acf257
instance Applicative (BrowserAction conn) where
Packit acf257
  pure  = return
Packit acf257
  (<*>) = ap
Packit acf257
#else
Packit acf257
 deriving (Functor, Applicative, Monad, MonadIO, MonadState (BrowserState conn))
Packit acf257
#endif
Packit acf257
Packit acf257
runBA :: BrowserState conn -> BrowserAction conn a -> IO a
Packit acf257
runBA bs = flip evalStateT bs . unBA
Packit acf257
Packit acf257
-- | @browse act@ is the toplevel action to perform a 'BrowserAction'.
Packit acf257
-- Example use: @browse (request (getRequest yourURL))@.
Packit acf257
browse :: BrowserAction conn a -> IO a
Packit acf257
browse = runBA defaultBrowserState
Packit acf257
Packit acf257
-- | The default browser state has the settings 
Packit acf257
defaultBrowserState :: BrowserState t
Packit acf257
defaultBrowserState = res
Packit acf257
 where
Packit acf257
   res = BS
Packit acf257
     { bsErr              = putStrLn
Packit acf257
     , bsOut              = putStrLn
Packit acf257
     , bsCookies          = []
Packit acf257
     , bsCookieFilter     = defaultCookieFilter
Packit acf257
     , bsAuthorityGen     = \ _uri _realm -> do
Packit acf257
          bsErr res "No action for prompting/generating user+password credentials provided (use: setAuthorityGen); returning Nothing"
Packit acf257
          return Nothing
Packit acf257
     , bsAuthorities      = []
Packit acf257
     , bsAllowRedirects   = True
Packit acf257
     , bsAllowBasicAuth   = False
Packit acf257
     , bsMaxRedirects     = Nothing
Packit acf257
     , bsMaxErrorRetries  = Nothing
Packit acf257
     , bsMaxAuthAttempts  = Nothing
Packit acf257
     , bsMaxPoolSize      = Nothing
Packit acf257
     , bsConnectionPool   = []
Packit acf257
     , bsCheckProxy       = defaultAutoProxyDetect
Packit acf257
     , bsProxy            = noProxy
Packit acf257
     , bsDebug            = Nothing 
Packit acf257
     , bsEvent            = Nothing
Packit acf257
     , bsRequestID        = 0
Packit acf257
     , bsUserAgent        = Nothing
Packit acf257
     }
Packit acf257
Packit acf257
{-# DEPRECATED getBrowserState "Use Control.Monad.State.get instead." #-}
Packit acf257
-- | @getBrowserState@ returns the current browser config. Useful
Packit acf257
-- for restoring state across 'BrowserAction's.
Packit acf257
getBrowserState :: BrowserAction t (BrowserState t)
Packit acf257
getBrowserState = get
Packit acf257
Packit acf257
-- | @withBrowserAction st act@ performs @act@ with 'BrowserState' @st@.
Packit acf257
withBrowserState :: BrowserState t -> BrowserAction t a -> BrowserAction t a
Packit acf257
withBrowserState bs = BA . withStateT (const bs) . unBA
Packit acf257
Packit acf257
-- | @nextRequest act@ performs the browser action @act@ as
Packit acf257
-- the next request, i.e., setting up a new request context
Packit acf257
-- before doing so.
Packit acf257
nextRequest :: BrowserAction t a -> BrowserAction t a
Packit acf257
nextRequest act = do
Packit acf257
  let updReqID st = 
Packit acf257
       let 
Packit acf257
        rid = succ (bsRequestID st)
Packit acf257
       in
Packit acf257
       rid `seq` st{bsRequestID=rid}
Packit acf257
  modify updReqID
Packit acf257
  act
Packit acf257
Packit acf257
-- | Lifts an IO action into the 'BrowserAction' monad.
Packit acf257
{-# DEPRECATED ioAction "Use Control.Monad.Trans.liftIO instead." #-}
Packit acf257
ioAction :: IO a -> BrowserAction t a
Packit acf257
ioAction = liftIO
Packit acf257
Packit acf257
-- | @setErrHandler@ sets the IO action to call when
Packit acf257
-- the browser reports running errors. To disable any
Packit acf257
-- such, set it to @const (return ())@.
Packit acf257
setErrHandler :: (String -> IO ()) -> BrowserAction t ()
Packit acf257
setErrHandler h = modify (\b -> b { bsErr=h })
Packit acf257
Packit acf257
-- | @setOutHandler@ sets the IO action to call when
Packit acf257
-- the browser chatters info on its running. To disable any
Packit acf257
-- such, set it to @const (return ())@.
Packit acf257
setOutHandler :: (String -> IO ()) -> BrowserAction t ()
Packit acf257
setOutHandler h = modify (\b -> b { bsOut=h })
Packit acf257
Packit acf257
out, err :: String -> BrowserAction t ()
Packit acf257
out s = do { f <- gets bsOut ; liftIO $ f s }
Packit acf257
err s = do { f <- gets bsErr ; liftIO $ f s }
Packit acf257
Packit acf257
-- | @setAllowRedirects onOff@ toggles the willingness to
Packit acf257
-- follow redirects (HTTP responses with 3xx status codes).
Packit acf257
setAllowRedirects :: Bool -> BrowserAction t ()
Packit acf257
setAllowRedirects bl = modify (\b -> b {bsAllowRedirects=bl})
Packit acf257
Packit acf257
-- | @getAllowRedirects@ returns current setting of the do-chase-redirects flag.
Packit acf257
getAllowRedirects :: BrowserAction t Bool
Packit acf257
getAllowRedirects = gets bsAllowRedirects
Packit acf257
Packit acf257
-- | @setMaxRedirects maxCount@ sets the maxiumum number of forwarding hops
Packit acf257
-- we are willing to jump through. A no-op if the count is negative; if zero,
Packit acf257
-- the max is set to whatever default applies. Notice that setting the max
Packit acf257
-- redirects count does /not/ enable following of redirects itself; use
Packit acf257
-- 'setAllowRedirects' to do so.
Packit acf257
setMaxRedirects :: Maybe Int -> BrowserAction t ()
Packit acf257
setMaxRedirects c 
Packit acf257
 | fromMaybe 0 c < 0  = return ()
Packit acf257
 | otherwise          = modify (\b -> b{bsMaxRedirects=c})
Packit acf257
Packit acf257
-- | @getMaxRedirects@ returns the current setting for the max-redirect count.
Packit acf257
-- If @Nothing@, the "Network.Browser"'s default is used.
Packit acf257
getMaxRedirects :: BrowserAction t (Maybe Int)
Packit acf257
getMaxRedirects = gets bsMaxRedirects
Packit acf257
Packit acf257
-- | @setMaxPoolSize maxCount@ sets the maximum size of the connection pool
Packit acf257
-- that is used to cache connections between requests
Packit acf257
setMaxPoolSize :: Maybe Int -> BrowserAction t ()
Packit acf257
setMaxPoolSize c = modify (\b -> b{bsMaxPoolSize=c})
Packit acf257
Packit acf257
-- | @getMaxPoolSize@ gets the maximum size of the connection pool
Packit acf257
-- that is used to cache connections between requests.
Packit acf257
-- If @Nothing@, the "Network.Browser"'s default is used.
Packit acf257
getMaxPoolSize :: BrowserAction t (Maybe Int)
Packit acf257
getMaxPoolSize = gets bsMaxPoolSize
Packit acf257
Packit acf257
-- | @setProxy p@ will disable proxy usage if @p@ is @NoProxy@.
Packit acf257
-- If @p@ is @Proxy proxyURL mbAuth@, then @proxyURL@ is interpreted
Packit acf257
-- as the URL of the proxy to use, possibly authenticating via 
Packit acf257
-- 'Authority' information in @mbAuth@.
Packit acf257
setProxy :: Proxy -> BrowserAction t ()
Packit acf257
setProxy p =
Packit acf257
   -- Note: if user _explicitly_ sets the proxy, we turn
Packit acf257
   -- off any auto-detection of proxies.
Packit acf257
  modify (\b -> b {bsProxy = p, bsCheckProxy=False})
Packit acf257
Packit acf257
-- | @getProxy@ returns the current proxy settings. If
Packit acf257
-- the auto-proxy flag is set to @True@, @getProxy@ will
Packit acf257
-- perform the necessary 
Packit acf257
getProxy :: BrowserAction t Proxy
Packit acf257
getProxy = do
Packit acf257
  p <- gets bsProxy
Packit acf257
  case p of
Packit acf257
      -- Note: if there is a proxy, no need to perform any auto-detect.
Packit acf257
      -- Presumably this is the user's explicit and preferred proxy server.
Packit acf257
    Proxy{} -> return p
Packit acf257
    NoProxy{} -> do
Packit acf257
     flg <- gets bsCheckProxy
Packit acf257
     if not flg
Packit acf257
      then return p 
Packit acf257
      else do
Packit acf257
       np <- liftIO $ fetchProxy True{-issue warning on stderr if ill-formed...-}
Packit acf257
        -- note: this resets the check-proxy flag; a one-off affair.
Packit acf257
       setProxy np
Packit acf257
       return np
Packit acf257
Packit acf257
-- | @setCheckForProxy flg@ sets the one-time check for proxy
Packit acf257
-- flag to @flg@. If @True@, the session will try to determine
Packit acf257
-- the proxy server is locally configured. See 'Network.HTTP.Proxy.fetchProxy'
Packit acf257
-- for details of how this done.
Packit acf257
setCheckForProxy :: Bool -> BrowserAction t ()
Packit acf257
setCheckForProxy flg = modify (\ b -> b{bsCheckProxy=flg})
Packit acf257
Packit acf257
-- | @getCheckForProxy@ returns the current check-proxy setting.
Packit acf257
-- Notice that this may not be equal to @True@ if the session has
Packit acf257
-- set it to that via 'setCheckForProxy' and subsequently performed
Packit acf257
-- some HTTP protocol interactions. i.e., the flag return represents
Packit acf257
-- whether a proxy will be checked for again before any future protocol
Packit acf257
-- interactions.
Packit acf257
getCheckForProxy :: BrowserAction t Bool
Packit acf257
getCheckForProxy = gets bsCheckProxy
Packit acf257
Packit acf257
-- | @setDebugLog mbFile@ turns off debug logging iff @mbFile@
Packit acf257
-- is @Nothing@. If set to @Just fStem@, logs of browser activity
Packit acf257
-- is appended to files of the form @fStem-url-authority@, i.e.,
Packit acf257
-- @fStem@ is just the prefix for a set of log files, one per host/authority.
Packit acf257
setDebugLog :: Maybe String -> BrowserAction t ()
Packit acf257
setDebugLog v = modify (\b -> b {bsDebug=v})
Packit acf257
Packit acf257
-- | @setUserAgent ua@ sets the current @User-Agent:@ string to @ua@. It
Packit acf257
-- will be used if no explicit user agent header is found in subsequent requests.
Packit acf257
--
Packit acf257
-- A common form of user agent string is @\"name\/version (details)\"@. For
Packit acf257
-- example @\"cabal-install/0.10.2 (HTTP 4000.1.2)\"@. Including the version
Packit acf257
-- of this HTTP package can be helpful if you ever need to track down HTTP
Packit acf257
-- compatability quirks. This version is available via 'httpPackageVersion'.
Packit acf257
-- For more info see <http://en.wikipedia.org/wiki/User_agent>.
Packit acf257
--
Packit acf257
setUserAgent :: String -> BrowserAction t ()
Packit acf257
setUserAgent ua = modify (\b -> b{bsUserAgent=Just ua})
Packit acf257
Packit acf257
-- | @getUserAgent@ returns the current @User-Agent:@ default string.
Packit acf257
getUserAgent :: BrowserAction t String
Packit acf257
getUserAgent  = do
Packit acf257
  n <- gets bsUserAgent
Packit acf257
  return (maybe defaultUserAgent id n)
Packit acf257
Packit acf257
-- | @RequestState@ is an internal tallying type keeping track of various 
Packit acf257
-- per-connection counters, like the number of authorization attempts and 
Packit acf257
-- forwards we've gone through.
Packit acf257
data RequestState 
Packit acf257
  = RequestState
Packit acf257
      { reqDenies     :: Int   -- ^ number of 401 responses so far
Packit acf257
      , reqRedirects  :: Int   -- ^ number of redirects so far
Packit acf257
      , reqRetries    :: Int   -- ^ number of retries so far
Packit acf257
      , reqStopOnDeny :: Bool  -- ^ whether to pre-empt 401 response
Packit acf257
      }
Packit acf257
Packit acf257
type RequestID = Int -- yeah, it will wrap around.
Packit acf257
Packit acf257
nullRequestState :: RequestState
Packit acf257
nullRequestState = RequestState
Packit acf257
      { reqDenies     = 0
Packit acf257
      , reqRedirects  = 0
Packit acf257
      , reqRetries    = 0
Packit acf257
      , reqStopOnDeny = True
Packit acf257
      }
Packit acf257
Packit acf257
-- | @BrowserEvent@ is the event record type that a user-defined handler, set
Packit acf257
-- via 'setEventHandler', will be passed. It indicates various state changes
Packit acf257
-- encountered in the processing of a given 'RequestID', along with timestamps
Packit acf257
-- at which they occurred.
Packit acf257
data BrowserEvent
Packit acf257
 = BrowserEvent
Packit acf257
      { browserTimestamp  :: UTCTime
Packit acf257
      , browserRequestID  :: RequestID
Packit acf257
      , browserRequestURI :: {-URI-}String
Packit acf257
      , browserEventType  :: BrowserEventType
Packit acf257
      }
Packit acf257
Packit acf257
-- | 'BrowserEventType' is the enumerated list of events that the browser
Packit acf257
-- internals will report to a user-defined event handler.
Packit acf257
data BrowserEventType
Packit acf257
 = OpenConnection
Packit acf257
 | ReuseConnection
Packit acf257
 | RequestSent
Packit acf257
 | ResponseEnd ResponseData
Packit acf257
 | ResponseFinish
Packit acf257
{- not yet, you will have to determine these via the ResponseEnd event.
Packit acf257
 | Redirect
Packit acf257
 | AuthChallenge
Packit acf257
 | AuthResponse
Packit acf257
-}
Packit acf257
 
Packit acf257
-- | @setEventHandler onBrowserEvent@ configures event handling.
Packit acf257
-- If @onBrowserEvent@ is @Nothing@, event handling is turned off;
Packit acf257
-- setting it to @Just onEv@ causes the @onEv@ IO action to be
Packit acf257
-- notified of browser events during the processing of a request
Packit acf257
-- by the Browser pipeline.
Packit acf257
setEventHandler :: Maybe (BrowserEvent -> BrowserAction ty ()) -> BrowserAction ty ()
Packit acf257
setEventHandler mbH = modify (\b -> b { bsEvent=mbH})
Packit acf257
Packit acf257
buildBrowserEvent :: BrowserEventType -> {-URI-}String -> RequestID -> IO BrowserEvent
Packit acf257
buildBrowserEvent bt uri reqID = do
Packit acf257
  ct <- getCurrentTime
Packit acf257
  return BrowserEvent 
Packit acf257
         { browserTimestamp  = ct
Packit acf257
         , browserRequestID  = reqID
Packit acf257
         , browserRequestURI = uri
Packit acf257
         , browserEventType  = bt
Packit acf257
         }
Packit acf257
Packit acf257
reportEvent :: BrowserEventType -> {-URI-}String -> BrowserAction t ()
Packit acf257
reportEvent bt uri = do
Packit acf257
  st <- get
Packit acf257
  case bsEvent st of
Packit acf257
    Nothing -> return ()
Packit acf257
    Just evH -> do
Packit acf257
       evt <- liftIO $ buildBrowserEvent bt uri (bsRequestID st)
Packit acf257
       evH evt -- if it fails, we fail.
Packit acf257
Packit acf257
-- | The default number of hops we are willing not to go beyond for 
Packit acf257
-- request forwardings.
Packit acf257
defaultMaxRetries :: Int
Packit acf257
defaultMaxRetries = 4
Packit acf257
Packit acf257
-- | The default number of error retries we are willing to perform.
Packit acf257
defaultMaxErrorRetries :: Int
Packit acf257
defaultMaxErrorRetries = 4
Packit acf257
Packit acf257
-- | The default maximum HTTP Authentication attempts we will make for
Packit acf257
-- a single request.
Packit acf257
defaultMaxAuthAttempts :: Int
Packit acf257
defaultMaxAuthAttempts = 2
Packit acf257
Packit acf257
-- | The default setting for auto-proxy detection.
Packit acf257
-- You may change this within a session via 'setAutoProxyDetect'.
Packit acf257
-- To avoid initial backwards compatibility issues, leave this as @False@.
Packit acf257
defaultAutoProxyDetect :: Bool
Packit acf257
defaultAutoProxyDetect = False
Packit acf257
Packit acf257
-- | @request httpRequest@ tries to submit the 'Request' @httpRequest@
Packit acf257
-- to some HTTP server (possibly going via a /proxy/, see 'setProxy'.)
Packit acf257
-- Upon successful delivery, the URL where the response was fetched from
Packit acf257
-- is returned along with the 'Response' itself.
Packit acf257
request :: HStream ty
Packit acf257
        => Request ty
Packit acf257
        -> BrowserAction (HandleStream ty) (URI,Response ty)
Packit acf257
request req = nextRequest $ do
Packit acf257
  res <- request' nullVal initialState req
Packit acf257
  reportEvent ResponseFinish (show (rqURI req))
Packit acf257
  case res of
Packit acf257
    Right r -> return r
Packit acf257
    Left e  -> do
Packit acf257
     let errStr = ("Network.Browser.request: Error raised " ++ show e)
Packit acf257
     err errStr
Packit acf257
     fail errStr
Packit acf257
 where
Packit acf257
  initialState = nullRequestState
Packit acf257
  nullVal      = buf_empty bufferOps
Packit acf257
Packit acf257
-- | Internal helper function, explicitly carrying along per-request 
Packit acf257
-- counts.
Packit acf257
request' :: HStream ty
Packit acf257
         => ty
Packit acf257
         -> RequestState
Packit acf257
         -> Request ty
Packit acf257
         -> BrowserAction (HandleStream ty) (Result (URI,Response ty))
Packit acf257
request' nullVal rqState rq = do
Packit acf257
   let uri = rqURI rq
Packit acf257
   failHTTPS uri
Packit acf257
   let uria = reqURIAuth rq 
Packit acf257
     -- add cookies to request
Packit acf257
   cookies <- getCookiesFor (uriAuthToString uria) (uriPath uri)
Packit acf257
{- Not for now:
Packit acf257
   (case uriUserInfo uria of
Packit acf257
     "" -> id
Packit acf257
     xs ->
Packit acf257
       case chopAtDelim ':' xs of
Packit acf257
         (_,[])    -> id
Packit acf257
	 (usr,pwd) -> withAuth
Packit acf257
	                  AuthBasic{ auUserName = usr
Packit acf257
                                   , auPassword = pwd
Packit acf257
			           , auRealm    = "/"
Packit acf257
			           , auSite     = uri
Packit acf257
			           }) $ do
Packit acf257
-}
Packit acf257
   when (not $ null cookies) 
Packit acf257
        (out $ "Adding cookies to request.  Cookie names: "  ++ unwords (map ckName cookies))
Packit acf257
    -- add credentials to request
Packit acf257
   rq' <- 
Packit acf257
    if not (reqStopOnDeny rqState) 
Packit acf257
     then return rq 
Packit acf257
     else do 
Packit acf257
       auth <- anticipateChallenge rq
Packit acf257
       case auth of
Packit acf257
         Nothing -> return rq
Packit acf257
         Just x  -> return (insertHeader HdrAuthorization (withAuthority x rq) rq)
Packit acf257
   let rq'' = if not $ null cookies then insertHeaders [cookiesToHeader cookies] rq' else rq'
Packit acf257
   p <- getProxy
Packit acf257
   def_ua <- gets bsUserAgent
Packit acf257
   let defaultOpts =
Packit acf257
         case p of 
Packit acf257
           NoProxy     -> defaultNormalizeRequestOptions{normUserAgent=def_ua}
Packit acf257
           Proxy _ ath ->
Packit acf257
              defaultNormalizeRequestOptions
Packit acf257
                { normForProxy  = True
Packit acf257
                , normUserAgent = def_ua
Packit acf257
                , normCustoms   =
Packit acf257
                    maybe []
Packit acf257
                          (\ authS -> [\ _ r -> insertHeader HdrProxyAuthorization (withAuthority authS r) r])
Packit acf257
                          ath
Packit acf257
                }
Packit acf257
   let final_req = normalizeRequest defaultOpts rq''
Packit acf257
   out ("Sending:\n" ++ show final_req)
Packit acf257
   e_rsp <- 
Packit acf257
     case p of
Packit acf257
       NoProxy        -> dorequest (reqURIAuth rq'') final_req
Packit acf257
       Proxy str _ath -> do
Packit acf257
          let notURI 
Packit acf257
               | null pt || null hst =
Packit acf257
                 URIAuth{ uriUserInfo = ""
Packit acf257
                        , uriRegName  = str
Packit acf257
                        , uriPort     = ""
Packit acf257
                        }
Packit acf257
               | otherwise =
Packit acf257
                 URIAuth{ uriUserInfo = ""
Packit acf257
                        , uriRegName  = hst
Packit acf257
                        , uriPort     = pt
Packit acf257
                        }
Packit acf257
                  -- If the ':' is dropped from port below, dorequest will assume port 80. Leave it!
Packit acf257
                 where (hst, pt) = span (':'/=) str
Packit acf257
           -- Proxy can take multiple forms - look for http://host:port first,
Packit acf257
           -- then host:port. Fall back to just the string given (probably a host name).
Packit acf257
          let proxyURIAuth =
Packit acf257
                maybe notURI
Packit acf257
                      (\parsed -> maybe notURI id (uriAuthority parsed))
Packit acf257
                      (parseURI str)
Packit acf257
Packit acf257
          out $ "proxy uri host: " ++ uriRegName proxyURIAuth ++ ", port: " ++ uriPort proxyURIAuth
Packit acf257
          dorequest proxyURIAuth final_req
Packit acf257
   mbMx <- getMaxErrorRetries
Packit acf257
   case e_rsp of
Packit acf257
    Left v 
Packit acf257
     | (reqRetries rqState < fromMaybe defaultMaxErrorRetries mbMx) && 
Packit acf257
       (v == ErrorReset || v == ErrorClosed) -> do
Packit acf257
       --empty connnection pool in case connection has become invalid
Packit acf257
       modify (\b -> b { bsConnectionPool=[] })       
Packit acf257
       request' nullVal rqState{reqRetries=succ (reqRetries rqState)} rq
Packit acf257
     | otherwise -> 
Packit acf257
       return (Left v)
Packit acf257
    Right rsp -> do 
Packit acf257
     out ("Received:\n" ++ show rsp)
Packit acf257
      -- add new cookies to browser state
Packit acf257
     handleCookies uri (uriAuthToString $ reqURIAuth rq) 
Packit acf257
                       (retrieveHeaders HdrSetCookie rsp)
Packit acf257
     -- Deal with "Connection: close" in response.
Packit acf257
     handleConnectionClose (reqURIAuth rq) (retrieveHeaders HdrConnection rsp)
Packit acf257
     mbMxAuths <- getMaxAuthAttempts
Packit acf257
     case rspCode rsp of
Packit acf257
      (4,0,1) -- Credentials not sent or refused.
Packit acf257
        | reqDenies rqState > fromMaybe defaultMaxAuthAttempts mbMxAuths -> do
Packit acf257
          out "401 - credentials again refused; exceeded retry count (2)"
Packit acf257
          return (Right (uri,rsp))
Packit acf257
        | otherwise -> do
Packit acf257
          out "401 - credentials not supplied or refused; retrying.."
Packit acf257
          let hdrs = retrieveHeaders HdrWWWAuthenticate rsp
Packit acf257
          flg <- getAllowBasicAuth
Packit acf257
          case pickChallenge flg (catMaybes $ map (headerToChallenge uri) hdrs) of
Packit acf257
            Nothing -> do
Packit acf257
              out "no challenge"
Packit acf257
              return (Right (uri,rsp))   {- do nothing -}
Packit acf257
            Just x  -> do
Packit acf257
              au <- challengeToAuthority uri x
Packit acf257
              case au of
Packit acf257
                Nothing  -> do
Packit acf257
                  out "no auth"
Packit acf257
                  return (Right (uri,rsp)) {- do nothing -}
Packit acf257
                Just au' -> do
Packit acf257
                  out "Retrying request with new credentials"
Packit acf257
                  request' nullVal
Packit acf257
                           rqState{ reqDenies     = succ(reqDenies rqState)
Packit acf257
                                  , reqStopOnDeny = False
Packit acf257
                                  }
Packit acf257
                           (insertHeader HdrAuthorization (withAuthority au' rq) rq)
Packit acf257
Packit acf257
      (4,0,7)  -- Proxy Authentication required
Packit acf257
        | reqDenies rqState > fromMaybe defaultMaxAuthAttempts mbMxAuths -> do
Packit acf257
          out "407 - proxy authentication required; max deny count exceeeded (2)"
Packit acf257
          return (Right (uri,rsp))
Packit acf257
        | otherwise -> do
Packit acf257
          out "407 - proxy authentication required"
Packit acf257
          let hdrs = retrieveHeaders HdrProxyAuthenticate rsp
Packit acf257
          flg <- getAllowBasicAuth
Packit acf257
          case pickChallenge flg (catMaybes $ map (headerToChallenge uri) hdrs) of
Packit acf257
            Nothing -> return (Right (uri,rsp))   {- do nothing -}
Packit acf257
            Just x  -> do
Packit acf257
              au <- challengeToAuthority uri x
Packit acf257
              case au of
Packit acf257
               Nothing  -> return (Right (uri,rsp))  {- do nothing -}
Packit acf257
               Just au' -> do
Packit acf257
                 pxy <- gets bsProxy
Packit acf257
                 case pxy of
Packit acf257
                   NoProxy -> do
Packit acf257
                     err "Proxy authentication required without proxy!"
Packit acf257
                     return (Right (uri,rsp))
Packit acf257
                   Proxy px _ -> do
Packit acf257
                     out "Retrying with proxy authentication"
Packit acf257
                     setProxy (Proxy px (Just au'))
Packit acf257
                     request' nullVal
Packit acf257
                              rqState{ reqDenies     = succ(reqDenies rqState)
Packit acf257
                                     , reqStopOnDeny = False
Packit acf257
                                     }
Packit acf257
                              rq
Packit acf257
Packit acf257
      (3,0,x) | x `elem` [2,3,1,7]  ->  do
Packit acf257
        out ("30" ++ show x ++  " - redirect")
Packit acf257
        allow_redirs <- allowRedirect rqState
Packit acf257
        case allow_redirs of
Packit acf257
          False -> return (Right (uri,rsp))
Packit acf257
          _ -> do
Packit acf257
           case retrieveHeaders HdrLocation rsp of
Packit acf257
            [] -> do 
Packit acf257
              err "No Location: header in redirect response"
Packit acf257
              return (Right (uri,rsp))
Packit acf257
            (Header _ u:_) -> 
Packit acf257
              case parseURIReference u of
Packit acf257
                Nothing -> do
Packit acf257
                  err ("Parse of Location: header in a redirect response failed: " ++ u)
Packit acf257
                  return (Right (uri,rsp))
Packit acf257
                Just newURI
Packit acf257
                 | {-uriScheme newURI_abs /= uriScheme uri && -}(not (supportedScheme newURI_abs)) -> do
Packit acf257
                    err ("Unable to handle redirect, unsupported scheme: " ++ show newURI_abs)
Packit acf257
                    return (Right (uri, rsp))
Packit acf257
                 | otherwise -> do
Packit acf257
                    out ("Redirecting to " ++ show newURI_abs ++ " ...")
Packit acf257
                    
Packit acf257
                    -- Redirect using GET request method, depending on
Packit acf257
                    -- response code.
Packit acf257
                    let toGet = x `elem` [2,3]
Packit acf257
                        method = if toGet then GET else rqMethod rq
Packit acf257
                        rq1 = rq { rqMethod=method, rqURI=newURI_abs }
Packit acf257
                        rq2 = if toGet then (replaceHeader HdrContentLength "0") (rq1 {rqBody = nullVal}) else rq1
Packit acf257
                    
Packit acf257
                    request' nullVal
Packit acf257
                            rqState{ reqDenies     = 0
Packit acf257
                                   , reqRedirects  = succ(reqRedirects rqState)
Packit acf257
                                   , reqStopOnDeny = True
Packit acf257
                                   }
Packit acf257
                             rq2
Packit acf257
                 where
Packit acf257
                   newURI_abs = uriDefaultTo newURI uri
Packit acf257
Packit acf257
      (3,0,5) ->
Packit acf257
        case retrieveHeaders HdrLocation rsp of
Packit acf257
         [] -> do 
Packit acf257
           err "No Location header in proxy redirect response."
Packit acf257
           return (Right (uri,rsp))
Packit acf257
         (Header _ u:_) -> 
Packit acf257
           case parseURIReference u of
Packit acf257
            Nothing -> do
Packit acf257
             err ("Parse of Location header in a proxy redirect response failed: " ++ u)
Packit acf257
             return (Right (uri,rsp))
Packit acf257
            Just newuri -> do
Packit acf257
             out ("Retrying with proxy " ++ show newuri ++ "...")
Packit acf257
             setProxy (Proxy (uriToAuthorityString newuri) Nothing)
Packit acf257
             request' nullVal rqState{ reqDenies     = 0
Packit acf257
                                     , reqRedirects  = 0
Packit acf257
                                     , reqRetries    = succ (reqRetries rqState)
Packit acf257
                                     , reqStopOnDeny = True
Packit acf257
                                     }
Packit acf257
                                     rq
Packit acf257
      _       -> return (Right (uri,rsp))
Packit acf257
Packit acf257
-- | The internal request handling state machine.
Packit acf257
dorequest :: (HStream ty)
Packit acf257
          => URIAuth
Packit acf257
          -> Request ty
Packit acf257
          -> BrowserAction (HandleStream ty)
Packit acf257
                           (Result (Response ty))
Packit acf257
dorequest hst rqst = do
Packit acf257
  pool <- gets bsConnectionPool
Packit acf257
  let uPort = uriAuthPort Nothing{-ToDo: feed in complete URL-} hst
Packit acf257
  conn <- liftIO $ filterM (\c -> c `isTCPConnectedTo` EndPoint (uriRegName hst) uPort) pool
Packit acf257
  rsp <- 
Packit acf257
    case conn of
Packit acf257
      [] -> do 
Packit acf257
        out ("Creating new connection to " ++ uriAuthToString hst)
Packit acf257
        reportEvent OpenConnection (show (rqURI rqst))
Packit acf257
        c <- liftIO $ openStream (uriRegName hst) uPort
Packit acf257
        updateConnectionPool c
Packit acf257
        dorequest2 c rqst
Packit acf257
      (c:_) -> do
Packit acf257
        out ("Recovering connection to " ++ uriAuthToString hst)
Packit acf257
        reportEvent ReuseConnection (show (rqURI rqst))
Packit acf257
        dorequest2 c rqst
Packit acf257
  case rsp of 
Packit acf257
     Right (Response a b c _) -> 
Packit acf257
         reportEvent (ResponseEnd (a,b,c)) (show (rqURI rqst)) ; _ -> return ()
Packit acf257
  return rsp
Packit acf257
 where
Packit acf257
  dorequest2 c r = do
Packit acf257
    dbg <- gets bsDebug
Packit acf257
    st  <- get
Packit acf257
    let 
Packit acf257
     onSendComplete =
Packit acf257
       maybe (return ())
Packit acf257
             (\evh -> do
Packit acf257
                x <- buildBrowserEvent RequestSent (show (rqURI r)) (bsRequestID st)
Packit acf257
                runBA st (evh x)
Packit acf257
                return ())
Packit acf257
             (bsEvent st)
Packit acf257
    liftIO $ 
Packit acf257
      maybe (sendHTTP_notify c r onSendComplete)
Packit acf257
            (\ f -> do
Packit acf257
               c' <- debugByteStream (f++'-': uriAuthToString hst) c
Packit acf257
               sendHTTP_notify c' r onSendComplete)
Packit acf257
            dbg
Packit acf257
Packit acf257
updateConnectionPool :: HStream hTy
Packit acf257
                     => HandleStream hTy
Packit acf257
                     -> BrowserAction (HandleStream hTy) ()
Packit acf257
updateConnectionPool c = do
Packit acf257
   pool <- gets bsConnectionPool
Packit acf257
   let len_pool = length pool
Packit acf257
   maxPoolSize <- fromMaybe defaultMaxPoolSize <$> gets bsMaxPoolSize
Packit acf257
   when (len_pool > maxPoolSize)
Packit acf257
        (liftIO $ close (last pool))
Packit acf257
   let pool' 
Packit acf257
        | len_pool > maxPoolSize = init pool
Packit acf257
        | otherwise              = pool
Packit acf257
   when (maxPoolSize > 0) $ modify (\b -> b { bsConnectionPool=c:pool' })
Packit acf257
   return ()
Packit acf257
                             
Packit acf257
-- | Default maximum number of open connections we are willing to have active.
Packit acf257
defaultMaxPoolSize :: Int
Packit acf257
defaultMaxPoolSize = 5
Packit acf257
Packit acf257
cleanConnectionPool :: HStream hTy
Packit acf257
                    => URIAuth -> BrowserAction (HandleStream hTy) ()
Packit acf257
cleanConnectionPool uri = do
Packit acf257
  let ep = EndPoint (uriRegName uri) (uriAuthPort Nothing uri)
Packit acf257
  pool <- gets bsConnectionPool
Packit acf257
  bad <- liftIO $ mapM (\c -> c `isTCPConnectedTo` ep) pool
Packit acf257
  let tmp = zip bad pool
Packit acf257
      newpool = map snd $ filter (not . fst) tmp
Packit acf257
      toclose = map snd $ filter fst tmp
Packit acf257
  liftIO $ forM_ toclose close
Packit acf257
  modify (\b -> b { bsConnectionPool = newpool })
Packit acf257
Packit acf257
handleCookies :: URI -> String -> [Header] -> BrowserAction t ()
Packit acf257
handleCookies _   _              [] = return () -- cut short the silliness.
Packit acf257
handleCookies uri dom cookieHeaders = do
Packit acf257
  when (not $ null errs)
Packit acf257
       (err $ unlines ("Errors parsing these cookie values: ":errs))
Packit acf257
  when (not $ null newCookies)
Packit acf257
       (out $ foldl (\x y -> x ++ "\n  " ++ show y) "Cookies received:" newCookies)
Packit acf257
  filterfn    <- getCookieFilter
Packit acf257
  newCookies' <- liftIO (filterM (filterfn uri) newCookies)
Packit acf257
  when (not $ null newCookies')
Packit acf257
       (out $ "Accepting cookies with names: " ++ unwords (map ckName newCookies'))
Packit acf257
  mapM_ addCookie newCookies'
Packit acf257
 where
Packit acf257
  (errs, newCookies) = processCookieHeaders dom cookieHeaders
Packit acf257
Packit acf257
handleConnectionClose :: HStream hTy
Packit acf257
                      => URIAuth -> [Header]
Packit acf257
                      -> BrowserAction (HandleStream hTy) ()
Packit acf257
handleConnectionClose _ [] = return ()
Packit acf257
handleConnectionClose uri headers = do
Packit acf257
  let doClose = any (== "close") $ map headerToConnType headers
Packit acf257
  when doClose $ cleanConnectionPool uri
Packit acf257
  where headerToConnType (Header _ t) = map toLower t
Packit acf257
Packit acf257
------------------------------------------------------------------
Packit acf257
----------------------- Miscellaneous ----------------------------
Packit acf257
------------------------------------------------------------------
Packit acf257
Packit acf257
allowRedirect :: RequestState -> BrowserAction t Bool
Packit acf257
allowRedirect rqState = do
Packit acf257
  rd <- getAllowRedirects
Packit acf257
  mbMxRetries <- getMaxRedirects
Packit acf257
  return (rd && (reqRedirects rqState <= fromMaybe defaultMaxRetries mbMxRetries))
Packit acf257
Packit acf257
-- | Return @True@ iff the package is able to handle requests and responses
Packit acf257
-- over it.
Packit acf257
supportedScheme :: URI -> Bool
Packit acf257
supportedScheme u = uriScheme u == "http:"
Packit acf257
Packit acf257
-- | @uriDefaultTo a b@ returns a URI that is consistent with the first
Packit acf257
-- argument URI @a@ when read in the context of the second URI @b@.
Packit acf257
-- If the second argument is not sufficient context for determining
Packit acf257
-- a full URI then anarchy reins.
Packit acf257
uriDefaultTo :: URI -> URI -> URI
Packit acf257
#if MIN_VERSION_network(2,4,0)
Packit acf257
uriDefaultTo a b = a `relativeTo` b
Packit acf257
#else
Packit acf257
uriDefaultTo a b = maybe a id (a `relativeTo` b)
Packit acf257
#endif
Packit acf257
Packit acf257
Packit acf257
-- This form junk is completely untested...
Packit acf257
Packit acf257
type FormVar = (String,String)
Packit acf257
Packit acf257
data Form = Form RequestMethod URI [FormVar]
Packit acf257
Packit acf257
formToRequest :: Form -> Request_String
Packit acf257
formToRequest (Form m u vs) =
Packit acf257
    let enc = urlEncodeVars vs
Packit acf257
    in case m of
Packit acf257
        GET -> Request { rqMethod=GET
Packit acf257
                       , rqHeaders=[ Header HdrContentLength "0" ]
Packit acf257
                       , rqBody=""
Packit acf257
                       , rqURI=u { uriQuery= '?' : enc }  -- What about old query?
Packit acf257
                       }
Packit acf257
        POST -> Request { rqMethod=POST
Packit acf257
                        , rqHeaders=[ Header HdrContentType "application/x-www-form-urlencoded",
Packit acf257
                                      Header HdrContentLength (show $ length enc) ]
Packit acf257
                        , rqBody=enc
Packit acf257
                        , rqURI=u
Packit acf257
                        }
Packit acf257
        _ -> error ("unexpected request: " ++ show m)
Packit acf257
Packit acf257