Blame Network/HTTP/Headers.hs

Packit acf257
-----------------------------------------------------------------------------
Packit acf257
-- |
Packit acf257
-- Module      :  Network.HTTP.Headers
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
-- This module provides the data types for representing HTTP headers, and
Packit acf257
-- operations for looking up header values and working with sequences of
Packit acf257
-- header values in 'Request's and 'Response's. To avoid having to provide
Packit acf257
-- separate set of operations for doing so, we introduce a type class 'HasHeaders'
Packit acf257
-- to facilitate writing such processing using overloading instead.
Packit acf257
-- 
Packit acf257
-----------------------------------------------------------------------------
Packit acf257
module Network.HTTP.Headers
Packit acf257
   ( HasHeaders(..)     -- type class
Packit acf257
Packit acf257
   , Header(..)
Packit acf257
   , mkHeader           -- :: HeaderName -> String -> Header
Packit acf257
   , hdrName            -- :: Header     -> HeaderName
Packit acf257
   , hdrValue           -- :: Header     -> String
Packit acf257
Packit acf257
   , HeaderName(..)
Packit acf257
Packit acf257
   , insertHeader          -- :: HasHeaders a => HeaderName -> String -> a -> a
Packit acf257
   , insertHeaderIfMissing -- :: HasHeaders a => HeaderName -> String -> a -> a
Packit acf257
   , insertHeaders         -- :: HasHeaders a => [Header] -> a -> a
Packit acf257
   , retrieveHeaders       -- :: HasHeaders a => HeaderName -> a -> [Header]
Packit acf257
   , replaceHeader         -- :: HasHeaders a => HeaderName -> String -> a -> a
Packit acf257
   , findHeader            -- :: HasHeaders a => HeaderName -> a -> Maybe String
Packit acf257
   , lookupHeader          -- :: HeaderName -> [Header] -> Maybe String
Packit acf257
Packit acf257
   , parseHeader           -- :: parseHeader :: String -> Result Header
Packit acf257
   , parseHeaders          -- :: [String] -> Result [Header]
Packit acf257
   
Packit acf257
   , headerMap             -- :: [(String, HeaderName)]
Packit acf257
   
Packit acf257
   , HeaderSetter
Packit acf257
   ) where
Packit acf257
Packit acf257
import Data.Char (toLower)
Packit acf257
import Network.Stream (Result, failParse)
Packit acf257
import Network.HTTP.Utils ( trim, split, crlf )
Packit acf257
Packit acf257
-- | The @Header@ data type pairs header names & values.
Packit acf257
data Header = Header HeaderName String
Packit acf257
Packit acf257
hdrName :: Header -> HeaderName
Packit acf257
hdrName (Header h _) = h
Packit acf257
Packit acf257
hdrValue :: Header -> String
Packit acf257
hdrValue (Header _ v) = v
Packit acf257
Packit acf257
-- | Header constructor as a function, hiding above rep.
Packit acf257
mkHeader :: HeaderName -> String -> Header
Packit acf257
mkHeader = Header
Packit acf257
Packit acf257
instance Show Header where
Packit acf257
    show (Header key value) = shows key (':':' ':value ++ crlf)
Packit acf257
Packit acf257
-- | HTTP @HeaderName@ type, a Haskell data constructor for each
Packit acf257
-- specification-defined header, prefixed with @Hdr@ and CamelCased,
Packit acf257
-- (i.e., eliding the @-@ in the process.) Should you require using
Packit acf257
-- a custom header, there's the @HdrCustom@ constructor which takes
Packit acf257
-- a @String@ argument.
Packit acf257
--
Packit acf257
-- Encoding HTTP header names differently, as Strings perhaps, is an
Packit acf257
-- equally fine choice..no decidedly clear winner, but let's stick
Packit acf257
-- with data constructors here.
Packit acf257
-- 
Packit acf257
data HeaderName 
Packit acf257
    -- Generic Headers --
Packit acf257
 = HdrCacheControl
Packit acf257
 | HdrConnection
Packit acf257
 | HdrDate
Packit acf257
 | HdrPragma
Packit acf257
 | HdrTransferEncoding        
Packit acf257
 | HdrUpgrade                
Packit acf257
 | HdrVia
Packit acf257
    -- Request Headers --
Packit acf257
 | HdrAccept
Packit acf257
 | HdrAcceptCharset
Packit acf257
 | HdrAcceptEncoding
Packit acf257
 | HdrAcceptLanguage
Packit acf257
 | HdrAuthorization
Packit acf257
 | HdrCookie
Packit acf257
 | HdrExpect
Packit acf257
 | HdrFrom
Packit acf257
 | HdrHost
Packit acf257
 | HdrIfModifiedSince
Packit acf257
 | HdrIfMatch
Packit acf257
 | HdrIfNoneMatch
Packit acf257
 | HdrIfRange
Packit acf257
 | HdrIfUnmodifiedSince
Packit acf257
 | HdrMaxForwards
Packit acf257
 | HdrProxyAuthorization
Packit acf257
 | HdrRange
Packit acf257
 | HdrReferer
Packit acf257
 | HdrUserAgent
Packit acf257
    -- Response Headers
Packit acf257
 | HdrAge
Packit acf257
 | HdrLocation
Packit acf257
 | HdrProxyAuthenticate
Packit acf257
 | HdrPublic
Packit acf257
 | HdrRetryAfter
Packit acf257
 | HdrServer
Packit acf257
 | HdrSetCookie
Packit acf257
 | HdrTE
Packit acf257
 | HdrTrailer
Packit acf257
 | HdrVary
Packit acf257
 | HdrWarning
Packit acf257
 | HdrWWWAuthenticate
Packit acf257
    -- Entity Headers
Packit acf257
 | HdrAllow
Packit acf257
 | HdrContentBase
Packit acf257
 | HdrContentEncoding
Packit acf257
 | HdrContentLanguage
Packit acf257
 | HdrContentLength
Packit acf257
 | HdrContentLocation
Packit acf257
 | HdrContentMD5
Packit acf257
 | HdrContentRange
Packit acf257
 | HdrContentType
Packit acf257
 | HdrETag
Packit acf257
 | HdrExpires
Packit acf257
 | HdrLastModified
Packit acf257
    -- | MIME entity headers (for sub-parts)
Packit acf257
 | HdrContentTransferEncoding
Packit acf257
    -- | Allows for unrecognised or experimental headers.
Packit acf257
 | HdrCustom String -- not in header map below.
Packit acf257
    deriving(Eq)
Packit acf257
Packit acf257
-- | @headerMap@ is a straight assoc list for translating between header names 
Packit acf257
-- and values.
Packit acf257
headerMap :: [ (String,HeaderName) ]
Packit acf257
headerMap =
Packit acf257
   [ p "Cache-Control"        HdrCacheControl
Packit acf257
   , p "Connection"           HdrConnection
Packit acf257
   , p "Date"                 HdrDate
Packit acf257
   , p "Pragma"               HdrPragma
Packit acf257
   , p "Transfer-Encoding"    HdrTransferEncoding
Packit acf257
   , p "Upgrade"              HdrUpgrade
Packit acf257
   , p "Via"                  HdrVia
Packit acf257
   , p "Accept"               HdrAccept
Packit acf257
   , p "Accept-Charset"       HdrAcceptCharset
Packit acf257
   , p "Accept-Encoding"      HdrAcceptEncoding
Packit acf257
   , p "Accept-Language"      HdrAcceptLanguage
Packit acf257
   , p "Authorization"        HdrAuthorization
Packit acf257
   , p "Cookie"               HdrCookie
Packit acf257
   , p "Expect"               HdrExpect
Packit acf257
   , p "From"                 HdrFrom
Packit acf257
   , p "Host"                 HdrHost
Packit acf257
   , p "If-Modified-Since"    HdrIfModifiedSince
Packit acf257
   , p "If-Match"             HdrIfMatch
Packit acf257
   , p "If-None-Match"        HdrIfNoneMatch
Packit acf257
   , p "If-Range"             HdrIfRange
Packit acf257
   , p "If-Unmodified-Since"  HdrIfUnmodifiedSince
Packit acf257
   , p "Max-Forwards"         HdrMaxForwards
Packit acf257
   , p "Proxy-Authorization"  HdrProxyAuthorization
Packit acf257
   , p "Range"                HdrRange
Packit acf257
   , p "Referer"              HdrReferer
Packit acf257
   , p "User-Agent"           HdrUserAgent
Packit acf257
   , p "Age"                  HdrAge
Packit acf257
   , p "Location"             HdrLocation
Packit acf257
   , p "Proxy-Authenticate"   HdrProxyAuthenticate
Packit acf257
   , p "Public"               HdrPublic
Packit acf257
   , p "Retry-After"          HdrRetryAfter
Packit acf257
   , p "Server"               HdrServer
Packit acf257
   , p "Set-Cookie"           HdrSetCookie
Packit acf257
   , p "TE"                   HdrTE
Packit acf257
   , p "Trailer"              HdrTrailer
Packit acf257
   , p "Vary"                 HdrVary
Packit acf257
   , p "Warning"              HdrWarning
Packit acf257
   , p "WWW-Authenticate"     HdrWWWAuthenticate
Packit acf257
   , p "Allow"                HdrAllow
Packit acf257
   , p "Content-Base"         HdrContentBase
Packit acf257
   , p "Content-Encoding"     HdrContentEncoding
Packit acf257
   , p "Content-Language"     HdrContentLanguage
Packit acf257
   , p "Content-Length"       HdrContentLength
Packit acf257
   , p "Content-Location"     HdrContentLocation
Packit acf257
   , p "Content-MD5"          HdrContentMD5
Packit acf257
   , p "Content-Range"        HdrContentRange
Packit acf257
   , p "Content-Type"         HdrContentType
Packit acf257
   , p "ETag"                 HdrETag
Packit acf257
   , p "Expires"              HdrExpires
Packit acf257
   , p "Last-Modified"        HdrLastModified
Packit acf257
   , p "Content-Transfer-Encoding" HdrContentTransferEncoding
Packit acf257
   ]
Packit acf257
 where
Packit acf257
  p a b = (a,b)
Packit acf257
Packit acf257
instance Show HeaderName where
Packit acf257
    show (HdrCustom s) = s
Packit acf257
    show x = case filter ((==x).snd) headerMap of
Packit acf257
                [] -> error "headerMap incomplete"
Packit acf257
                (h:_) -> fst h
Packit acf257
Packit acf257
-- | @HasHeaders@ is a type class for types containing HTTP headers, allowing
Packit acf257
-- you to write overloaded header manipulation functions
Packit acf257
-- for both 'Request' and 'Response' data types, for instance.
Packit acf257
class HasHeaders x where
Packit acf257
    getHeaders :: x -> [Header]
Packit acf257
    setHeaders :: x -> [Header] -> x
Packit acf257
Packit acf257
-- Header manipulation functions
Packit acf257
Packit acf257
type HeaderSetter a = HeaderName -> String -> a -> a
Packit acf257
Packit acf257
-- | @insertHeader hdr val x@ inserts a header with the given header name
Packit acf257
-- and value. Does not check for existing headers with same name, allowing
Packit acf257
-- duplicates to be introduce (use 'replaceHeader' if you want to avoid this.)
Packit acf257
insertHeader :: HasHeaders a => HeaderSetter a
Packit acf257
insertHeader name value x = setHeaders x newHeaders
Packit acf257
    where
Packit acf257
        newHeaders = (Header name value) : getHeaders x
Packit acf257
Packit acf257
-- | @insertHeaderIfMissing hdr val x@ adds the new header only if no previous
Packit acf257
-- header with name @hdr@ exists in @x@.
Packit acf257
insertHeaderIfMissing :: HasHeaders a => HeaderSetter a
Packit acf257
insertHeaderIfMissing name value x = setHeaders x (newHeaders $ getHeaders x)
Packit acf257
    where
Packit acf257
        newHeaders list@(h@(Header n _): rest)
Packit acf257
            | n == name  = list
Packit acf257
            | otherwise  = h : newHeaders rest
Packit acf257
        newHeaders [] = [Header name value]
Packit acf257
Packit acf257
-- | @replaceHeader hdr val o@ replaces the header @hdr@ with the
Packit acf257
-- value @val@, dropping any existing 
Packit acf257
replaceHeader :: HasHeaders a => HeaderSetter a
Packit acf257
replaceHeader name value h = setHeaders h newHeaders
Packit acf257
    where
Packit acf257
        newHeaders = Header name value : [ x | x@(Header n _) <- getHeaders h, name /= n ]
Packit acf257
          
Packit acf257
-- | @insertHeaders hdrs x@ appends multiple headers to @x@'s existing
Packit acf257
-- set.
Packit acf257
insertHeaders :: HasHeaders a => [Header] -> a -> a
Packit acf257
insertHeaders hdrs x = setHeaders x (getHeaders x ++ hdrs)
Packit acf257
Packit acf257
-- | @retrieveHeaders hdrNm x@ gets a list of headers with 'HeaderName' @hdrNm@.
Packit acf257
retrieveHeaders :: HasHeaders a => HeaderName -> a -> [Header]
Packit acf257
retrieveHeaders name x = filter matchname (getHeaders x)
Packit acf257
    where
Packit acf257
        matchname (Header n _) = n == name 
Packit acf257
Packit acf257
-- | @findHeader hdrNm x@ looks up @hdrNm@ in @x@, returning the first
Packit acf257
-- header that matches, if any.
Packit acf257
findHeader :: HasHeaders a => HeaderName -> a -> Maybe String
Packit acf257
findHeader n x = lookupHeader n (getHeaders x)
Packit acf257
Packit acf257
-- | @lookupHeader hdr hdrs@ locates the first header matching @hdr@ in the
Packit acf257
-- list @hdrs@.
Packit acf257
lookupHeader :: HeaderName -> [Header] -> Maybe String
Packit acf257
lookupHeader _ [] = Nothing
Packit acf257
lookupHeader v (Header n s:t)  
Packit acf257
  |  v == n   =  Just s
Packit acf257
  | otherwise =  lookupHeader v t
Packit acf257
Packit acf257
-- | @parseHeader headerNameAndValueString@ tries to unscramble a
Packit acf257
-- @header: value@ pairing and returning it as a 'Header'.
Packit acf257
parseHeader :: String -> Result Header
Packit acf257
parseHeader str =
Packit acf257
    case split ':' str of
Packit acf257
        Nothing -> failParse ("Unable to parse header: " ++ str)
Packit acf257
        Just (k,v) -> return $ Header (fn k) (trim $ drop 1 v)
Packit acf257
    where
Packit acf257
        fn k = case map snd $ filter (match k . fst) headerMap of
Packit acf257
                 [] -> (HdrCustom k)
Packit acf257
                 (h:_) -> h
Packit acf257
Packit acf257
        match :: String -> String -> Bool
Packit acf257
        match s1 s2 = map toLower s1 == map toLower s2
Packit acf257
    
Packit acf257
-- | @parseHeaders hdrs@ takes a sequence of strings holding header
Packit acf257
-- information and parses them into a set of headers (preserving their
Packit acf257
-- order in the input argument.) Handles header values split up over
Packit acf257
-- multiple lines.
Packit acf257
parseHeaders :: [String] -> Result [Header]
Packit acf257
parseHeaders = catRslts [] . 
Packit acf257
                 map (parseHeader . clean) . 
Packit acf257
                     joinExtended ""
Packit acf257
   where
Packit acf257
        -- Joins consecutive lines where the second line
Packit acf257
        -- begins with ' ' or '\t'.
Packit acf257
        joinExtended old      [] = [old]
Packit acf257
        joinExtended old (h : t)
Packit acf257
          | isLineExtension h    = joinExtended (old ++ ' ' : tail h) t
Packit acf257
          | otherwise            = old : joinExtended h t
Packit acf257
Packit acf257
        isLineExtension (x:_) = x == ' ' || x == '\t'
Packit acf257
        isLineExtension _ = False
Packit acf257
Packit acf257
        clean [] = []
Packit acf257
        clean (h:t) | h `elem` "\t\r\n" = ' ' : clean t
Packit acf257
                    | otherwise = h : clean t
Packit acf257
Packit acf257
        -- tolerant of errors?  should parse
Packit acf257
        -- errors here be reported or ignored?
Packit acf257
        -- currently ignored.
Packit acf257
        catRslts :: [a] -> [Result a] -> Result [a]
Packit acf257
        catRslts list (h:t) = 
Packit acf257
            case h of
Packit acf257
                Left _ -> catRslts list t
Packit acf257
                Right v -> catRslts (v:list) t
Packit acf257
        catRslts list [] = Right $ reverse list