|
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 |
|