X-Git-Url: http://git.cielonegro.org/gitweb.cgi?a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FHeaders.hs;h=e413eb2886554f33c04ef2e3d9cd76d1de6ec3bf;hb=2bcf36a739341aaaf56d812286d57233fff81ad5;hp=a5808838990efb9ea501b95b1770a19890273e20;hpb=858129cb755aa09da2b7bd758efb8519f2c89103;p=Lucu.git diff --git a/Network/HTTP/Lucu/Headers.hs b/Network/HTTP/Lucu/Headers.hs index a580883..e413eb2 100644 --- a/Network/HTTP/Lucu/Headers.hs +++ b/Network/HTTP/Lucu/Headers.hs @@ -1,53 +1,196 @@ --- #hide +{-# LANGUAGE + FlexibleInstances + , GeneralizedNewtypeDeriving + , MultiParamTypeClasses + , TypeSynonymInstances + , OverloadedStrings + , UnicodeSyntax + #-} +-- |An internal module for HTTP headers. module Network.HTTP.Lucu.Headers ( Headers , HasHeaders(..) - , emptyHeaders - , headersP - , hPutHeaders + + , headers + , printHeaders ) where +import Control.Applicative hiding (empty) +import Control.Applicative.Unicode hiding ((∅)) +import Control.Arrow +import Control.Monad +import Data.Ascii (Ascii, AsciiBuilder, CIAscii) +import qualified Data.Ascii as A +import Data.Attoparsec.Char8 as P +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 (filter, foldl, foldl1, foldr, foldr1, lookup, null) +import Prelude.Unicode -import Data.Char -import Data.List -import Network.HTTP.Lucu.Parser -import Network.HTTP.Lucu.Parser.Http -import Network.HTTP.Lucu.Utils -import System.IO - -type Headers = [ (String, String) ] +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 = ((A.toCIAscii <$>) ∘) ∘ getHeader + + deleteHeader ∷ CIAscii → a → a + {-# INLINE deleteHeader #-} + deleteHeader = modifyHeaders ∘ delete + + setHeader ∷ CIAscii → Ascii → a → a + {-# INLINE setHeader #-} + setHeader = (modifyHeaders ∘) ∘ insertWith const - getHeader :: String -> a -> Maybe String - getHeader key a - = key `seq` a `seq` - fmap snd $ find (noCaseEq' key . fst) (getHeaders a) +instance HasHeaders Headers where + getHeaders = id + setHeaders _ = id - deleteHeader :: String -> a -> a - deleteHeader key a - = key `seq` a `seq` - setHeaders a $ filter (not . noCaseEq' key . fst) (getHeaders a) +-- |@'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 - addHeader :: String -> String -> a -> a - addHeader key val a - = key `seq` val `seq` a `seq` - setHeaders a $ (getHeaders a) ++ [(key, val)] +-- FIXME: auto-derive +instance Foldable Headers (CIAscii, Ascii) where + {-# INLINE fold #-} + fold (Headers m) = fold m + {-# INLINE foldMap #-} + foldMap f (Headers m) = foldMap f m + {-# INLINE foldr #-} + foldr f b (Headers m) = foldr f b m + {-# INLINE foldl #-} + foldl f b (Headers m) = foldl f b m + {-# INLINE foldr1 #-} + foldr1 f (Headers m) = foldr1 f m + {-# INLINE foldl1 #-} + foldl1 f (Headers m) = foldl1 f m + {-# INLINE null #-} + null (Headers m) = null m + {-# INLINE size #-} + size (Headers m) = size m + {-# INLINE isSingleton #-} + isSingleton (Headers m) = isSingleton m - setHeader :: String -> String -> a -> a - setHeader key val a - = key `seq` val `seq` a `seq` - let list = getHeaders a - deleted = filter (not . noCaseEq' key . fst) list - added = deleted ++ [(key, val)] - in - setHeaders a added +-- FIXME: auto-derive +instance Collection Headers (CIAscii, Ascii) where + {-# INLINE filter #-} + filter f (Headers m) = Headers $ filter f m -emptyHeaders :: Headers -emptyHeaders = [] +-- FIXME: auto-derive +instance Indexed Headers CIAscii Ascii where + {-# INLINE index #-} + index k (Headers m) = index k m + {-# INLINE adjust #-} + adjust f k (Headers m) = Headers $ adjust f k m + {-# INLINE inDomain #-} + inDomain k (Headers m) = inDomain k m + {-# INLINE (//) #-} + Headers m // l = Headers $ m // l + {-# INLINE accum #-} + accum f (Headers m) l = Headers $ accum f m l +instance Monoid Headers where + {-# INLINE mempty #-} + mempty = empty + {-# INLINE mappend #-} + mappend = insertMany + +-- FIXME: auto-derive +instance Map Headers CIAscii Ascii where + {-# INLINE delete #-} + delete k (Headers m) = Headers $ delete k m + {-# INLINE member #-} + member k (Headers m) = member k m + {-# INLINE union #-} + union (Headers α) (Headers β) + = Headers $ union α β + {-# INLINE intersection #-} + intersection (Headers α) (Headers β) + = Headers $ intersection α β + {-# INLINE difference #-} + difference (Headers α) (Headers β) + = Headers $ difference α β + {-# INLINE isSubset #-} + isSubset (Headers α) (Headers β) + = isSubset α β + {-# INLINE isProperSubset #-} + isProperSubset (Headers α) (Headers β) + = isProperSubset α β + {-# INLINE lookup #-} + lookup k (Headers m) = lookup k m + {-# INLINE alter #-} + alter f k (Headers m) + = Headers $ alter f k m + {-# INLINE insertWith #-} + insertWith f k v (Headers m) + = Headers $ insertWith f k v m + {-# INLINE fromFoldableWith #-} + fromFoldableWith = (Headers ∘) ∘ fromFoldableWith + {-# INLINE foldGroups #-} + foldGroups = ((Headers ∘) ∘) ∘ foldGroups + {-# INLINE mapWithKey #-} + mapWithKey f (Headers m) + = Headers $ mapWithKey f m + {-# INLINE unionWith #-} + unionWith f (Headers α) (Headers β) + = Headers $ unionWith f α β + {-# INLINE intersectionWith #-} + intersectionWith f (Headers α) (Headers β) + = Headers $ intersectionWith f α β + {-# INLINE differenceWith #-} + differenceWith f (Headers α) (Headers β) + = Headers $ differenceWith f α β + {-# INLINE isSubmapBy #-} + isSubmapBy f (Headers α) (Headers β) + = isSubmapBy f α β + {-# INLINE isProperSubmapBy #-} + isProperSubmapBy f (Headers α) (Headers β) + = isProperSubmapBy f α β + +-- FIXME: auto-derive +instance SortingCollection Headers (CIAscii, Ascii) where + {-# INLINE minView #-} + minView (Headers m) = second Headers <$> minView m + +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 {- message-header = field-name ":" [ field-value ] @@ -60,48 +203,42 @@ emptyHeaders = [] field-value の先頭および末尾にある LWS は全て削除され、それ以外の LWS は單一の SP に變換される。 -} -headersP :: Parser Headers -headersP = do xs <- many header - crlf - return xs +headers ∷ Parser Headers +{-# INLINEABLE headers #-} +headers = do xs ← P.many header + crlf + return $ fromFoldable xs where - header :: Parser (String, String) - header = do name <- token - char ':' - -- FIXME: これは多少インチキだが、RFC 2616 のこの部分 - -- の記述はひどく曖昧であり、この動作が本當に間違って - -- ゐるのかどうかも良く分からない。例へば - -- quoted-string の内部にある空白は纏めていいのか惡い - -- のか?直勸的には駄目さうに思へるが、そんな記述は見 - -- 付からない。 - contents <- many (lws <|> many1 text) + header ∷ Parser (CIAscii, Ascii) + header = do name ← A.toCIAscii <$> token + void $ char ':' + skipMany lws + values ← content `sepBy` try lws + skipMany (try lws) crlf - let value = foldr (++) "" contents - return (name, normalize value) - - normalize :: String -> String - normalize = trimBody . trim isWhiteSpace - - trimBody = foldr (++) [] - . map (\ s -> if head s == ' ' then - " " - else - s) - . group - . map (\ c -> if isWhiteSpace c - then ' ' - else c) - - -hPutHeaders :: Handle -> Headers -> IO () -hPutHeaders h hds - = h `seq` hds `seq` - mapM_ putH hds >> hPutStr h "\r\n" + return (name, joinValues values) + + content ∷ Parser Ascii + {-# INLINE content #-} + content = A.unsafeFromByteString + <$> + takeWhile1 (\c → isText c ∧ c ≢ '\x20') + + joinValues ∷ [Ascii] → Ascii + {-# INLINE joinValues #-} + joinValues = A.fromAsciiBuilder + ∘ mconcat + ∘ intersperse (A.toAsciiBuilder "\x20") + ∘ (A.toAsciiBuilder <$>) + +printHeaders ∷ Headers → AsciiBuilder +printHeaders (Headers m) + = mconcat (printHeader <$> fromFoldable m) ⊕ + A.toAsciiBuilder "\x0D\x0A" where - putH :: (String, String) -> IO () - putH (name, value) - = name `seq` value `seq` - do hPutStr h name - hPutStr h ": " - hPutStr h value - hPutStr h "\r\n" + printHeader ∷ (CIAscii, Ascii) → AsciiBuilder + printHeader (name, value) + = A.toAsciiBuilder (A.fromCIAscii name) ⊕ + A.toAsciiBuilder ": " ⊕ + A.toAsciiBuilder value ⊕ + A.toAsciiBuilder "\x0D\x0A"