X-Git-Url: http://git.cielonegro.org/gitweb.cgi?p=Lucu.git;a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FHeaders.hs;h=5391743d1163833a1b47b8f10e14ef4edf91e369;hp=fbab8563852c1efc56e6bab72006257934340938;hb=90fca0675b1694e69b8e431c989343855cbd125d;hpb=9961a721f98b101825ef154a2122c1fc2fa6d1ac diff --git a/Network/HTTP/Lucu/Headers.hs b/Network/HTTP/Lucu/Headers.hs index fbab856..5391743 100644 --- a/Network/HTTP/Lucu/Headers.hs +++ b/Network/HTTP/Lucu/Headers.hs @@ -1,45 +1,168 @@ +{-# LANGUAGE + FlexibleInstances + , GeneralizedNewtypeDeriving + , MultiParamTypeClasses + , TemplateHaskell + , TypeSynonymInstances + , OverloadedStrings + , UnicodeSyntax + #-} +{-# OPTIONS_GHC -fno-warn-missing-methods #-} +-- |An internal module for HTTP headers. module Network.HTTP.Lucu.Headers ( Headers , HasHeaders(..) - , emptyHeaders -- Headers ) where +import Control.Applicative hiding (empty) +import Control.Applicative.Unicode hiding ((∅)) +import Control.Monad +import Data.Ascii (Ascii, AsciiBuilder, CIAscii) +import qualified Data.Ascii as A +import Data.Attoparsec.Char8 +import qualified Data.Collections.Newtype.TH as C +import Data.Convertible.Base +import Data.Convertible.Instances.Ascii () +import Data.Convertible.Utils +import Data.Default +import Data.List (intersperse) +import qualified Data.Map as M (Map) +import Data.Collections +import Data.Collections.BaseInstances () +import Data.Monoid +import Data.Monoid.Unicode +import Network.HTTP.Lucu.Parser.Http +import Prelude hiding (lookup, null) +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 (M.Map CIAscii Ascii) + deriving (Eq, Show) class HasHeaders a where - getHeaders :: a -> Headers - setHeaders :: a -> Headers -> a + getHeaders ∷ a → Headers + setHeaders ∷ a → Headers → a + + modifyHeaders ∷ (Headers → Headers) → a → a + {-# INLINE modifyHeaders #-} + modifyHeaders = (setHeaders ⊛) ∘ (∘ getHeaders) + + getHeader ∷ CIAscii → a → Maybe Ascii + {-# INLINE getHeader #-} + getHeader = (∘ getHeaders) ∘ lookup + + hasHeader ∷ CIAscii → a → Bool + {-# INLINE hasHeader #-} + hasHeader = (∘ getHeaders) ∘ member + + getCIHeader ∷ CIAscii → a → Maybe CIAscii + {-# INLINE getCIHeader #-} + getCIHeader = ((cs <$>) ∘) ∘ getHeader + + deleteHeader ∷ CIAscii → a → a + {-# INLINE deleteHeader #-} + deleteHeader = modifyHeaders ∘ delete + + setHeader ∷ CIAscii → Ascii → a → a + {-# INLINE setHeader #-} + setHeader = (modifyHeaders ∘) ∘ insertWith const + +instance HasHeaders Headers where + getHeaders = id + setHeaders _ = id + +C.derive [d| instance Foldable Headers (CIAscii, Ascii) + instance Collection Headers (CIAscii, Ascii) + instance Indexed Headers CIAscii Ascii + instance Map Headers CIAscii Ascii + instance SortingCollection Headers (CIAscii, Ascii) + |] + +-- |@'insert' (key, val)@ merges @val@ with an existing one if any. +instance Unfoldable Headers (CIAscii, Ascii) where + {-# INLINE insert #-} + insert (key, val) (Headers m) + = Headers $ insertWith merge key val m + {-# INLINE empty #-} + empty = Headers empty + {-# INLINE singleton #-} + singleton = Headers ∘ singleton + +instance Monoid Headers where + {-# INLINE mempty #-} + mempty = empty + {-# INLINE mappend #-} + mappend = insertMany + +merge ∷ Ascii → Ascii → Ascii +{-# INLINE merge #-} +merge a b + | nullA a ∧ nullA b = (∅) + | nullA a = b + | nullA b = a + | otherwise = a ⊕ ", " ⊕ b + where + nullA ∷ Ascii → Bool + {-# INLINE nullA #-} + nullA = null ∘ A.toByteString - getHeader :: a -> ByteString -> Maybe ByteString - getHeader a key - = fmap snd $ find (noCaseEq key . fst) (getHeaders a) +instance ConvertSuccess Headers Ascii where + {-# INLINE convertSuccess #-} + convertSuccess = convertSuccessVia ((⊥) ∷ AsciiBuilder) - deleteHeader :: a -> ByteString -> a - deleteHeader a key - = setHeaders a $ filter (not . noCaseEq key . fst) (getHeaders a) +instance ConvertSuccess Headers AsciiBuilder where + {-# INLINEABLE convertSuccess #-} + convertSuccess (Headers m) + = mconcat (header <$> fromFoldable m) ⊕ cs ("\x0D\x0A" ∷ Ascii) + where + header ∷ (CIAscii, Ascii) → AsciiBuilder + {-# INLINE header #-} + header (name, value) + = cs name ⊕ + cs (": " ∷ Ascii) ⊕ + cs value ⊕ + cs ("\x0D\x0A" ∷ Ascii) - addHeader :: a -> ByteString -> ByteString -> a - addHeader a key val - = setHeaders a $ (getHeaders a) ++ [(key, val)] +deriveAttempts [ ([t| Headers |], [t| Ascii |]) + , ([t| Headers |], [t| AsciiBuilder |]) + ] - 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 +{- + message-header = field-name ":" [ field-value ] + field-name = token + field-value = *( field-content | LWS ) + field-content = -noCaseEq :: ByteString -> ByteString -> Bool -noCaseEq a b - = (B.map toLower a) == (B.map toLower b) + field-value の先頭および末尾にある LWS は全て削除され、それ以外の + LWS は單一の SP に變換される。 +-} +instance Default (Parser Headers) where + {-# INLINEABLE def #-} + def = do xs ← many header + crlf + return $ fromFoldable xs + where + header ∷ Parser (CIAscii, Ascii) + {-# INLINEABLE header #-} + header = do name ← cs <$> token + void $ char ':' + skipMany lws + values ← content `sepBy` try lws + skipMany (try lws) + crlf + return (name, joinValues values) + content ∷ Parser Ascii + {-# INLINEABLE content #-} + content = A.unsafeFromByteString + <$> + takeWhile1 (\c → isText c ∧ c ≢ '\x20') -emptyHeaders :: Headers -emptyHeaders = [] \ No newline at end of file + joinValues ∷ [Ascii] → Ascii + {-# INLINEABLE joinValues #-} + joinValues = cs + ∘ mconcat + ∘ intersperse (cs ("\x20" ∷ Ascii) ∷ AsciiBuilder) + ∘ (cs <$>)