X-Git-Url: http://git.cielonegro.org/gitweb.cgi?a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FHeaders.hs;h=80b9b1339501d95a08c80511645e3603a5b1d9ea;hb=cc074d0;hp=fbab8563852c1efc56e6bab72006257934340938;hpb=9961a721f98b101825ef154a2122c1fc2fa6d1ac;p=Lucu.git diff --git a/Network/HTTP/Lucu/Headers.hs b/Network/HTTP/Lucu/Headers.hs index fbab856..80b9b13 100644 --- a/Network/HTTP/Lucu/Headers.hs +++ b/Network/HTTP/Lucu/Headers.hs @@ -1,45 +1,158 @@ +{-# LANGUAGE + GeneralizedNewtypeDeriving + , OverloadedStrings + , UnicodeSyntax + #-} +-- |An internal module for HTTP headers. module Network.HTTP.Lucu.Headers ( Headers , HasHeaders(..) - , emptyHeaders -- Headers + + , singleton + + , toHeaders + , fromHeaders + + , headers + , printHeaders ) where +import Control.Applicative +import Control.Monad +import Data.Ascii (Ascii, AsciiBuilder, CIAscii) +import qualified Data.Ascii as A +import Data.Attoparsec.Char8 as P +import qualified Data.ByteString as BS +import Data.List +import Data.Map (Map) +import qualified Data.Map as M +import qualified Data.Map.Unicode as M +import Data.Monoid +import Data.Monoid.Unicode +import Network.HTTP.Lucu.Parser.Http +import Prelude.Unicode -import qualified Data.ByteString.Lazy.Char8 as B -import Data.ByteString.Lazy.Char8 (ByteString) -import Data.Char -import Data.List - -type Headers = [ (ByteString, ByteString) ] +newtype Headers + = Headers (Map CIAscii Ascii) + deriving (Eq, Show, Monoid) class HasHeaders a where - getHeaders :: a -> Headers - setHeaders :: a -> Headers -> a + getHeaders ∷ a → Headers + setHeaders ∷ a → Headers → a + + getHeader ∷ CIAscii → a → Maybe Ascii + getHeader key a + = case getHeaders a of + Headers m → M.lookup key m + + hasHeader ∷ CIAscii → a → Bool + {-# INLINE hasHeader #-} + hasHeader key a + = case getHeaders a of + Headers m → key M.∈ m + + getCIHeader ∷ CIAscii → a → Maybe CIAscii + {-# INLINE getCIHeader #-} + getCIHeader key a + = A.toCIAscii <$> getHeader key a + + deleteHeader ∷ CIAscii → a → a + {-# INLINE deleteHeader #-} + deleteHeader key a + = case getHeaders a of + Headers m + → setHeaders a $ Headers $ M.delete key m - getHeader :: a -> ByteString -> Maybe ByteString - getHeader a key - = fmap snd $ find (noCaseEq key . fst) (getHeaders a) + setHeader ∷ CIAscii → Ascii → a → a + {-# INLINE setHeader #-} + setHeader key val a + = case getHeaders a of + Headers m + → setHeaders a $ Headers $ M.insert key val m - deleteHeader :: a -> ByteString -> a - deleteHeader a key - = setHeaders a $ filter (not . noCaseEq key . fst) (getHeaders a) +instance HasHeaders Headers where + getHeaders = id + setHeaders _ = id - addHeader :: a -> ByteString -> ByteString -> a - addHeader a key val - = setHeaders a $ (getHeaders a) ++ [(key, val)] +singleton ∷ CIAscii → Ascii → Headers +{-# INLINE singleton #-} +singleton key val + = Headers $ M.singleton key val - setHeader :: a -> ByteString -> ByteString -> a - setHeader a key val - = let list = getHeaders a - deleted = filter (not . noCaseEq key . fst) list - added = deleted ++ [(key, val)] - in - setHeaders a added +toHeaders ∷ [(CIAscii, Ascii)] → Headers +{-# INLINE toHeaders #-} +toHeaders = flip mkHeaders (∅) -noCaseEq :: ByteString -> ByteString -> Bool -noCaseEq a b - = (B.map toLower a) == (B.map toLower b) +mkHeaders ∷ [(CIAscii, Ascii)] → Headers → Headers +mkHeaders [] (Headers m) = Headers m +mkHeaders ((key, val):xs) (Headers m) + = mkHeaders xs $ Headers $ + case M.lookup key m of + Nothing → M.insert key val m + Just old → M.insert key (merge old val) m + where + merge ∷ Ascii → Ascii → Ascii + {-# INLINE merge #-} + merge a b + | nullA a ∧ nullA b = (∅) + | nullA a = b + | nullA b = a + | otherwise = a ⊕ ", " ⊕ b + + nullA ∷ Ascii → Bool + {-# INLINE nullA #-} + nullA = BS.null ∘ A.toByteString + +fromHeaders ∷ Headers → [(CIAscii, Ascii)] +fromHeaders (Headers m) = M.toList m + +{- + message-header = field-name ":" [ field-value ] + field-name = token + field-value = *( field-content | LWS ) + field-content = + field-value の先頭および末尾にある LWS は全て削除され、それ以外の + LWS は單一の SP に變換される。 +-} +headers ∷ Parser Headers +{-# INLINEABLE headers #-} +headers = do xs ← P.many header + crlf + return $ toHeaders xs + where + header ∷ Parser (CIAscii, Ascii) + header = do name ← A.toCIAscii <$> token + void $ char ':' + skipMany lws + values ← content `sepBy` try lws + skipMany (try lws) + crlf + return (name, joinValues values) + + content ∷ Parser Ascii + {-# INLINE content #-} + content = A.unsafeFromByteString + <$> + takeWhile1 (\c → isText c ∧ c ≢ '\x20') -emptyHeaders :: Headers -emptyHeaders = [] \ No newline at end of file + joinValues ∷ [Ascii] → Ascii + {-# INLINE joinValues #-} + joinValues = A.fromAsciiBuilder + ∘ mconcat + ∘ intersperse (A.toAsciiBuilder "\x20") + ∘ map A.toAsciiBuilder + +printHeaders ∷ Headers → AsciiBuilder +printHeaders (Headers m) + = mconcat (map printHeader (M.toList m)) ⊕ + A.toAsciiBuilder "\x0D\x0A" + where + printHeader ∷ (CIAscii, Ascii) → AsciiBuilder + printHeader (name, value) + = A.toAsciiBuilder (A.fromCIAscii name) ⊕ + A.toAsciiBuilder ": " ⊕ + A.toAsciiBuilder value ⊕ + A.toAsciiBuilder "\x0D\x0A"