HTTP 3001.0.4 → 3001.1.3
raw patch · 11 files changed
+230/−120 lines, 11 filesdep ~basenew-uploaderPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: base
API changes (from Hackage documentation)
+ Network.Browser: MkCookie :: String -> String -> String -> Maybe String -> Maybe String -> Maybe String -> Cookie
+ Network.Browser: ckComment :: Cookie -> Maybe String
+ Network.Browser: ckDomain :: Cookie -> String
+ Network.Browser: ckName :: Cookie -> String
+ Network.Browser: ckPath :: Cookie -> Maybe String
+ Network.Browser: ckValue :: Cookie -> String
+ Network.Browser: ckVersion :: Cookie -> Maybe String
+ Network.HTTP: Custom :: String -> RequestMethod
+ Network.HTTP: getAuth :: (Monad m) => Request -> m URIAuthority
+ Network.HTTP: getRequestHead :: (Stream s) => s -> IO (Result RequestData)
+ Network.HTTP: processRequest :: (Stream s) => s -> RequestData -> IO (Result Request)
+ Network.HTTP: type RequestData = (RequestMethod, URI, [Header])
+ Network.HTTP.Headers: headerMap :: [(String, HeaderName)]
+ Network.HTTP.Headers: parseHeader :: String -> Result Header
Files
- HTTP.cabal +5/−3
- Makefile +35/−0
- Network/Browser.hs +16/−7
- Network/HTTP.hs +61/−91
- Network/HTTP/Headers.hs +2/−0
- Network/HTTP/MD5Aux.hs +5/−4
- Network/StreamSocket.hs +1/−0
- Network/TCP.hs +15/−15
- README +25/−0
- test/Makefile +16/−0
- test/get.hs +49/−0
HTTP.cabal view
@@ -1,10 +1,11 @@ Name: HTTP-Version: 3001.0.4+Version: 3001.1.3 Cabal-Version: >= 1.2 Build-type: Simple License: BSD3 License-file: LICENSE-Copyright: +Category: Network+Copyright: Copyright (c) 2002, Warrick Gray Copyright (c) 2002-2005, Ian Lynagh Copyright (c) 2003-2006, Bjorn Bringert@@ -16,6 +17,7 @@ Maintainer: Bjorn Bringert <bjorn@bringert.net> Homepage: http://www.haskell.org/http/ Description: A library for client-side HTTP+Synopsis: A library for client-side HTTP Flag old-base description: Old, monolithic base@@ -40,4 +42,4 @@ if flag(old-base) Build-depends: base < 3 else- Build-depends: base >= 3, array+ Build-depends: base >= 3 && < 4, array
+ Makefile view
@@ -0,0 +1,35 @@+HADDOCK = haddock++TODAY = $(shell date +%Y%m%d)+DIST_NAME = http-$(TODAY)++HADDOCK_FILES = Network/HTTP.hs Network/Browser.hs ++.PHONY: all configure build install dist haddock clean++default all: configure build++configure:+ ./Setup.lhs configure++build:+ ./Setup.lhs build++install:+ ./Setup.lhs install++dist:+ darcs dist --dist-name=$(DIST_NAME)++haddock: $(HADDOCK_FILES)+ mkdir -p haddock+ $(HADDOCK) -o haddock -h $^++clean:+ -./Setup.lhs clean+ -rm -rf haddock+ -rm -rf dist+ $(MAKE) -C test clean++setup: Setup.lhs+ ghc --make -package Cabal -o setup Setup.lhs
Network/Browser.hs view
@@ -31,7 +31,7 @@ module Network.Browser ( BrowserState, BrowserAction, -- browser monad, effectively a state monad.- Cookie,+ Cookie(..), Form(..), Proxy(..), @@ -242,7 +242,7 @@ spaces = many (satisfy isSpace) - cvalue = quotedstring <|> many1 (satisfy $ not . (==';'))+ cvalue = quotedstring <|> many (satisfy $ not . (==';')) -- all keys in the result list MUST be in lower case cdetail :: Parser [(String,String)]@@ -312,8 +312,8 @@ 3) pick a challenge to respond to, usually the strongest challenge understood by the client, using "pickChallenge" 4) generate a username/password combination using the browsers- "bsAuthorityGen" function (the default behaviour is to ask- the user)+ "bsAuthorityGen" function (the default behaviour is to do nothing+ which means to not retry with a new username/password combination) 5) build an Authority object based upon the challenge and user data, store this new Authority in the browser state 6) convert the Authority to a request header and add this@@ -653,8 +653,9 @@ -- | Apply a browser action to a state. browse :: BrowserAction a -> IO a-browse act = do x <- lift act defaultBrowserState- return (snd x)+browse act = do (bs, x) <- lift act defaultBrowserState+ closePooledConnections bs+ return x where defaultBrowserState :: BrowserState defaultBrowserState = @@ -662,7 +663,7 @@ , bsOut = putStrLn , bsCookies = [] , bsCookieFilter = defaultCookieFilter- , bsAuthorityGen = (error "bsAuthGen wanted")+ , bsAuthorityGen = \_ _ -> return Nothing , bsAuthorities = [] , bsAllowRedirects = True , bsAllowBasicAuth = False@@ -670,6 +671,14 @@ , bsProxy = NoProxy , bsDebug = Nothing }++-- |+-- Close all connections that are in bs' connection pool.+-- This should have some sort of exception handling, soldier on until+-- all the connections have been closed. Not sure about portability+-- issues.+closePooledConnections :: BrowserState -> IO ()+closePooledConnections = mapM_ close . bsConnectionPool -- | Alter browser state alterBS :: (BrowserState -> BrowserState) -> BrowserAction ()
Network/HTTP.hs view
@@ -102,12 +102,15 @@ -- ** HTTP Request(..),+ RequestData, Response(..), RequestMethod(..), ResponseCode, simpleHTTP, simpleHTTP_, sendHTTP, receiveHTTP,+ processRequest,+ getRequestHead, respondHTTP, -- ** Header Functions@@ -120,6 +123,7 @@ -- ** URI authority parsing URIAuthority(..),+ getAuth, parseURIAuthority ) where @@ -132,6 +136,7 @@ ( URI(URI, uriScheme, uriAuthority, uriPath) , URIAuth(uriUserInfo, uriRegName, uriPort) , parseURIReference+ , unEscapeString, escapeURIString, isUnescapedInURI ) import Network.HTTP.Headers import Network.Stream@@ -141,7 +146,7 @@ import Control.Exception as Exception (catch, throw) import Data.Bits ((.&.)) import Data.Char (isSpace, intToDigit, digitToInt, ord, chr, toLower)-import Data.List (partition)+import Data.List (partition, intersperse) import Data.Maybe (listToMaybe, fromMaybe) import Control.Monad (when, guard) import Numeric (readHex)@@ -184,7 +189,7 @@ -- | Parse the authority part of a URL. ----- > RFC 1732, section 3.1:+-- > RFC 1738, section 3.1: -- > -- > //<user>:<password>@<host>:<port>/<url-path> -- > Some or all of the parts "<user>:<password>@", ":<password>",@@ -227,7 +232,7 @@ -- | The HTTP request method, to be used in the 'Request' object. -- We are missing a few of the stranger methods, but these are -- not really necessary until we add full TLS.-data RequestMethod = HEAD | PUT | GET | POST | DELETE | OPTIONS | TRACE+data RequestMethod = HEAD | PUT | GET | POST | DELETE | OPTIONS | TRACE | Custom String deriving(Show,Eq) rqMethodMap :: [(String, RequestMethod)]@@ -415,9 +420,10 @@ -- Then we make the request-URI an abs_path and make sure that there -- is a Host header. fixReq :: URIAuthority -> Request -> Request- fixReq URIAuthority{host=h} r = + fixReq URIAuthority{host=h,port=p} r = + let h' = h ++ maybe "" ((':':) . show) p in replaceHeader HdrConnection "close" $- insertHeaderIfMissing HdrHost h $+ insertHeaderIfMissing HdrHost h' $ r { rqURI = (rqURI r){ uriScheme = "", uriAuthority = Nothing } } @@ -556,37 +562,40 @@ uriRegName ua ++ uriPort ua --- | Receive and parse a HTTP request from the given Stream. Should be used +-- | Receive and parse a HTTP request from the given Stream. Should be used -- for server side interactions. receiveHTTP :: Stream s => s -> IO (Result Request)-receiveHTTP conn = do rq <- getRequestHead- processRequest rq - where- -- reads and parses headers- getRequestHead :: IO (Result RequestData)- getRequestHead =- do { lor <- readTillEmpty1 conn- ; return $ lor `bindE` parseRequestHead- }- - processRequest (Left e) = return $ Left e- processRequest (Right (rm,uri,hdrs)) = - do -- FIXME : Also handle 100-continue.- let tc = lookupHeader HdrTransferEncoding hdrs- cl = lookupHeader HdrContentLength hdrs- rslt <- case tc of- Nothing ->- case cl of- Just x -> linearTransfer conn (read x :: Int)- Nothing -> return (Right ([], "")) -- hopefulTransfer ""- Just x ->- case map toLower (trim x) of- "chunked" -> chunkedTransfer conn- _ -> uglyDeathTransfer conn- - return $ rslt `bindE` \(ftrs,bdy) -> Right (Request uri rm (hdrs++ftrs) bdy)+receiveHTTP conn = do rq <- getRequestHead conn+ case rq of+ Left e -> return (Left e)+ Right r -> processRequest conn r +-- | Reads and parses request headers.+getRequestHead :: Stream s => s -> IO (Result RequestData)+getRequestHead conn =+ do { lor <- readTillEmpty1 conn+ ; return $ lor `bindE` parseRequestHead+ } +-- | Process request body (called after successful getRequestHead)+processRequest :: Stream s => s -> RequestData -> IO (Result Request)+processRequest conn (rm,uri,hdrs) =+ do -- FIXME : Also handle 100-continue.+ let tc = lookupHeader HdrTransferEncoding hdrs+ cl = lookupHeader HdrContentLength hdrs+ rslt <- case tc of+ Nothing ->+ case cl of+ Just x -> linearTransfer conn (read x :: Int)+ Nothing -> return (Right ([], "")) -- hopefulTransfer ""+ Just x ->+ case map toLower (trim x) of+ "chunked" -> chunkedTransfer conn+ _ -> uglyDeathTransfer conn++ return $ rslt `bindE` \(ftrs,bdy) -> Right (Request uri rm (hdrs++ftrs) bdy)++ -- | Very simple function, send a HTTP response over the given stream. This -- could be improved on to use different transfer types. respondHTTP :: Stream s => s -> Response -> IO ()@@ -692,65 +701,26 @@ ------------------ A little friendly funtionality --------------- ----------------------------------------------------------------- --{-- I had a quick look around but couldn't find any RFC about- the encoding of data on the query string. I did find an- IETF memo, however, so this is how I justify the urlEncode- and urlDecode methods.-- Doc name: draft-tiwari-appl-wxxx-forms-01.txt (look on www.ietf.org)-- Reserved chars: ";", "/", "?", ":", "@", "&", "=", "+", ",", and "$" are reserved.- Unwise: "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"- URI delims: "<" | ">" | "#" | "%" | <">- Unallowed ASCII: <US-ASCII coded characters 00-1F and 7F hexadecimal>- <US-ASCII coded character 20 hexadecimal>- Also unallowed: any non-us-ascii character-- Escape method: char -> '%' a b where a, b :: Hex digits--}--urlEncode, urlDecode :: String -> String--urlDecode ('%':a:b:rest) = chr (16 * digitToInt a + digitToInt b)- : urlDecode rest-urlDecode (h:t) = h : urlDecode t-urlDecode [] = []--urlEncode (h:t) =- let str = if reserved (ord h) then escape h else [h]- in str ++ urlEncode t- where- reserved x- | x >= ord 'a' && x <= ord 'z' = False- | x >= ord 'A' && x <= ord 'Z' = False- | x >= ord '0' && x <= ord '9' = False- | x <= 0x20 || x >= 0x7F = True- | otherwise = x `elem` map ord [';','/','?',':','@','&'- ,'=','+',',','$','{','}'- ,'|','\\','^','[',']','`'- ,'<','>','#','%','"']- -- wouldn't it be nice if the compiler- -- optimised the above for us?-- escape x = - let y = ord x - in [ '%', intToDigit ((y `div` 16) .&. 0xf), intToDigit (y .&. 0xf) ]+-- | Formats name-value pairs as application\/x-www-form-urlencoded.+urlEncodeVars :: [(String,String)] -> String+urlEncodeVars xs = + concat $ intersperse "&" [urlEncode n ++ "=" ++ urlEncode v | (n,v) <- xs] -urlEncode [] = []- +-- | Converts a single value to the application\/x-www-form-urlencoded encoding.+urlEncode :: String -> String+urlEncode = replace ' ' '+' . escapeURIString okChar+ where okChar c = c == ' ' || + (isUnescapedInURI c && c `notElem` "&=+") +-- | Converts a single value from the +-- application\/x-www-form-urlencoded encoding.+urlDecode :: String -> String+urlDecode = unEscapeString . replace '+' ' ' --- Encode form variables, useable in either the--- query part of a URI, or the body of a POST request.--- I have no source for this information except experience,--- this sort of encoding worked fine in CGI programming.-urlEncodeVars :: [(String,String)] -> String-urlEncodeVars ((n,v):t) =- let (same,diff) = partition ((==n) . fst) t- in urlEncode n ++ '=' : foldl (\x y -> x ++ ',' : urlEncode y) (urlEncode $ v) (map snd same)- ++ urlEncodeRest diff- where urlEncodeRest [] = []- urlEncodeRest diff = '&' : urlEncodeVars diff-urlEncodeVars [] = []+-- | Replaces all instances of a value in a list by another value.+replace :: Eq a =>+ a -- ^ Value to look for+ -> a -- ^ Value to replace it with+ -> [a] -- ^ Input list+ -> [a] -- ^ Output list+replace x y = map (\z -> if z == x then y else z)
Network/HTTP/Headers.hs view
@@ -55,6 +55,8 @@ , findHeader , lookupHeader , parseHeaders+ , parseHeader+ , headerMap ) where import Data.Char (isSpace, toLower)
Network/HTTP/MD5Aux.hs view
@@ -4,9 +4,10 @@ Zord64, Str(..), BoolList(..), WordList(..)) where import Data.Char (ord, chr)-import Data.Bits (rotateL, shiftL, shiftR, (.&.), (.|.), xor, complement)+import Data.Bits (Bits, rotateL, shiftL, shiftR, (.&.), (.|.), xor, complement) import Data.Word (Word32, Word64) +rotL :: Bits a => a -> Int -> a rotL x = rotateL x type Zord64 = Word64 @@ -86,8 +87,8 @@ finished (WordList (_, z)) = z == 0 -instance Num ABCD where- ABCD (a1, b1, c1, d1) + ABCD (a2, b2, c2, d2) = ABCD (a1 + a2, b1 + b2, c1 + c2, d1 + d2)+add :: ABCD -> ABCD -> ABCD+ABCD (a1, b1, c1, d1) `add` ABCD (a2, b2, c2, d2) = ABCD (a1 + a2, b1 + b2, c1 + c2, d1 + d2) -- ===================== EXPORTED FUNCTIONS ========================@@ -128,7 +129,7 @@ md5_main padded ilen abcd m = if finished m && padded then abcd- else md5_main padded' (ilen + 512) (abcd + abcd') m''+ else md5_main padded' (ilen + 512) (abcd `add` abcd') m'' where (m16, l, m') = get_next m len' = ilen + fromIntegral l ((m16', _, m''), padded') = if not padded && l < 512
Network/StreamSocket.hs view
@@ -77,6 +77,7 @@ close sk = shutdown sk ShutdownBoth >> sClose sk myrecv :: Socket -> Int -> IO String+myrecv _ 0 = return "" myrecv sock len = let handler e = if isEOFError e then return [] else ioError e in System.IO.Error.catch (recv sock len) handler
Network/TCP.hs view
@@ -41,7 +41,7 @@ ) import Network.StreamSocket (myrecv, handleSocketError) -import Control.Exception as Exception (catch, throw)+import Control.Exception as Exception (catch, catchJust, finally, ioErrors, throw) import Data.List (elemIndex) import Data.Char (toLower) import Data.IORef (IORef, newIORef, readIORef, writeIORef, modifyIORef)@@ -158,23 +158,23 @@ -- (I think the behaviour here is TCP specific) close ref = do { c <- readIORef (getRef ref)- ; closeConn c `Exception.catch` (\_ -> return ())+ ; Exception.catchJust Exception.ioErrors (closeConn c) (\_ -> return ()) ; writeIORef (getRef ref) ConnClosed } where- -- Be kind to peer & close gracefully.- closeConn (ConnClosed) = return ()- closeConn (MkConn sk addr [] _) =- do { shutdown sk ShutdownSend- ; suck ref- ; shutdown sk ShutdownReceive- ; sClose sk- }-- suck :: Connection -> IO ()- suck cn = readLine cn >>= - either (\_ -> return ()) -- catch errors & ignore- (\x -> if null x then return () else suck cn)+ -- Be kind to peer & close gracefully.+ closeConn (ConnClosed) = return ()+ closeConn (MkConn sk addr [] _) =+ (`Exception.finally` sClose sk) $+ do { shutdown sk ShutdownSend+ ; suck ref+ ; shutdown sk ShutdownReceive+ }+ + suck :: Connection -> IO ()+ suck cn = readLine cn >>= + either (\_ -> return ()) -- catch errors & ignore+ (\x -> if null x then return () else suck cn) -- | Checks both that the underlying Socket is connected -- and that the connection peer matches the given
+ README view
@@ -0,0 +1,25 @@+DESCRIPTION++This is the Haskell HTTP and Browser module package. It was originally+written by Warrick Gray and the original version is still available+from: http://homepages.paradise.net.nz/warrickg/haskell/http/++REQUIREMENTS++* A Haskell implementation such as GHC (http://www.haskell.org/ghc/)+or Hugs (http://www.haskell.org/hugs/) with support for Cabal.++INSTALLATION+++* Configure:++$ runhaskell Setup.lhs configure++* Compile:++$ runhaskell Setup.lhs build++* Install (as root):++# runhaskell Setup.lhs install
+ test/Makefile view
@@ -0,0 +1,16 @@+GHC = ghc+GHCFLAGS = -O2 -package HTTP++TEST_PROGS = get++.SUFFIXES: .hs .hi .o++.PHONY: all clean++default all: $(TEST_PROGS)++%: %.hs+ $(GHC) $(GHCFLAGS) --make -o $@ $<++clean:+ -rm -f *.hi *.o $(TEST_PROGS)
+ test/get.hs view
@@ -0,0 +1,49 @@+-- A simple test program which takes a url on the commandline+-- and outputs the contents to stdout.++-- ghc --make -package HTTP get.hs -o get++import Data.Char (intToDigit)+import Network.HTTP+import Network.URI+import System.Environment (getArgs)+import System.Exit (exitFailure)+import System.IO (hPutStrLn, stderr)++main = + do+ args <- getArgs+ case args of + [addr] -> case parseURI addr of+ Nothing -> err "Could not parse URI"+ Just uri -> do+ cont <- get uri+ putStr cont+ _ -> err "Usage: get <url>"++err :: String -> IO a+err msg = do + hPutStrLn stderr msg+ exitFailure++get :: URI -> IO String+get uri =+ do+ eresp <- simpleHTTP (request uri)+ resp <- handleE (err . show) eresp+ case rspCode resp of+ (2,0,0) -> return (rspBody resp)+ _ -> err (httpError resp)+ where+ showRspCode (a,b,c) = map intToDigit [a,b,c]+ httpError resp = showRspCode (rspCode resp) ++ " " ++ rspReason resp++request :: URI -> Request+request uri = Request{ rqURI = uri,+ rqMethod = GET,+ rqHeaders = [],+ rqBody = "" }++handleE :: Monad m => (ConnError -> m a) -> Either ConnError a -> m a+handleE h (Left e) = h e+handleE _ (Right v) = return v