X-Git-Url: http://git.cielonegro.org/gitweb.cgi?p=Lucu.git;a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FHeaders.hs;h=2378ebcc529295f9495f1f1e5daf5daef46ce907;hp=87d858c55ec023a07a263a3f6d2280adaf958eb6;hb=9bb89434103e9a22f100d6ecb7e65a5d461e0454;hpb=86d100e294fa482456980021cca10393b9830ec1 diff --git a/Network/HTTP/Lucu/Headers.hs b/Network/HTTP/Lucu/Headers.hs index 87d858c..2378ebc 100644 --- a/Network/HTTP/Lucu/Headers.hs +++ b/Network/HTTP/Lucu/Headers.hs @@ -1,11 +1,13 @@ +{-# LANGUAGE + BangPatterns + , GeneralizedNewtypeDeriving + , OverloadedStrings + , UnicodeSyntax + #-} module Network.HTTP.Lucu.Headers ( Headers , HasHeaders(..) - , noCaseCmp - , noCaseEq - - , emptyHeaders , toHeaders , fromHeaders @@ -13,153 +15,74 @@ module Network.HTTP.Lucu.Headers , hPutHeaders ) where - -import qualified Data.ByteString as Strict (ByteString) -import Data.ByteString.Internal (toForeignPtr, w2c, inlinePerformIO) -import qualified Data.ByteString.Char8 as C8 hiding (ByteString) -import Data.Char -import Data.List -import Data.Map (Map) +import Control.Applicative +import Data.Ascii (Ascii, CIAscii) +import qualified Data.Ascii as A +import Data.Attoparsec.Char8 as P +import qualified Data.ByteString as BS +import Data.Map (Map) import qualified Data.Map as M -import Data.Ord -import Data.Word -import Foreign.ForeignPtr -import Foreign.Ptr -import Foreign.Storable -import Network.HTTP.Lucu.HandleLike -import Network.HTTP.Lucu.Parser -import Network.HTTP.Lucu.Parser.Http -import Network.HTTP.Lucu.Utils - -type Headers = Map NCBS Strict.ByteString -newtype NCBS = NCBS Strict.ByteString - -toNCBS :: Strict.ByteString -> NCBS -toNCBS = NCBS -{-# INLINE toNCBS #-} - -fromNCBS :: NCBS -> Strict.ByteString -fromNCBS (NCBS x) = x -{-# INLINE fromNCBS #-} - -instance Eq NCBS where - (NCBS a) == (NCBS b) = a == b - -instance Ord NCBS where - (NCBS a) `compare` (NCBS b) = a `noCaseCmp` b - -instance Show NCBS where - show (NCBS x) = show x - -noCaseCmp :: Strict.ByteString -> Strict.ByteString -> Ordering -noCaseCmp a b = a `seq` b `seq` - toForeignPtr a `cmp` toForeignPtr b - where - cmp :: (ForeignPtr Word8, Int, Int) -> (ForeignPtr Word8, Int, Int) -> Ordering - cmp (x1, s1, l1) (x2, s2, l2) - | x1 `seq` s1 `seq` l1 `seq` x2 `seq` s2 `seq` l2 `seq` False = undefined - | l1 == 0 && l2 == 0 = EQ - | x1 == x2 && s1 == s2 && l1 == l2 = EQ - | otherwise - = inlinePerformIO $ - withForeignPtr x1 $ \ p1 -> - withForeignPtr x2 $ \ p2 -> - noCaseCmp' (p1 `plusPtr` s1) l1 (p2 `plusPtr` s2) l2 - - --- もし先頭の文字列が等しければ、短い方が小さい。 -noCaseCmp' :: Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO Ordering -noCaseCmp' p1 l1 p2 l2 - | p1 `seq` l1 `seq` p2 `seq` l2 `seq` False = undefined - | l1 == 0 && l2 == 0 = return EQ - | l1 == 0 = return LT - | l2 == 0 = return GT - | otherwise - = do c1 <- peek p1 - c2 <- peek p2 - case comparing (toLower . w2c) c1 c2 of - EQ -> noCaseCmp' (p1 `plusPtr` 1) (l1 - 1) (p2 `plusPtr` 1) (l2 - 1) - x -> return x - - -noCaseEq :: Strict.ByteString -> Strict.ByteString -> Bool -noCaseEq a b = a `seq` b `seq` - toForeignPtr a `cmp` toForeignPtr b - where - cmp :: (ForeignPtr Word8, Int, Int) -> (ForeignPtr Word8, Int, Int) -> Bool - cmp (x1, s1, l1) (x2, s2, l2) - | x1 `seq` s1 `seq` l1 `seq` x2 `seq` s2 `seq` l2 `seq` False = undefined - | l1 /= l2 = False - | l1 == 0 && l2 == 0 = True - | x1 == x2 && s1 == s2 && l1 == l2 = True - | otherwise - = inlinePerformIO $ - withForeignPtr x1 $ \ p1 -> - withForeignPtr x2 $ \ p2 -> - noCaseEq' (p1 `plusPtr` s1) (p2 `plusPtr` s2) l1 - - -noCaseEq' :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool -noCaseEq' p1 p2 l - | p1 `seq` p2 `seq` l `seq` False = undefined - | l == 0 = return True - | otherwise - = do c1 <- peek p1 - c2 <- peek p2 - if toLower (w2c c1) == toLower (w2c c2) then - noCaseEq' (p1 `plusPtr` 1) (p2 `plusPtr` 1) (l - 1) - else - return False +import Data.Monoid +import Data.Monoid.Unicode +import Network.HTTP.Lucu.HandleLike +import Network.HTTP.Lucu.Parser.Http +import Network.HTTP.Lucu.Utils +import Prelude.Unicode +newtype Headers + = Headers (Map CIAscii Ascii) + deriving (Eq, Show, Monoid) class HasHeaders a where - getHeaders :: a -> Headers - setHeaders :: a -> Headers -> a - - getHeader :: Strict.ByteString -> a -> Maybe Strict.ByteString - getHeader key a - = key `seq` a `seq` - M.lookup (toNCBS key) (getHeaders a) - - deleteHeader :: Strict.ByteString -> a -> a - deleteHeader key a - = key `seq` a `seq` - setHeaders a $ M.delete (toNCBS key) (getHeaders a) - - setHeader :: Strict.ByteString -> Strict.ByteString -> a -> a - setHeader key val a - = key `seq` val `seq` a `seq` - setHeaders a $ M.insert (toNCBS key) val (getHeaders a) - - -emptyHeaders :: Headers -emptyHeaders = M.empty - - -toHeaders :: [(Strict.ByteString, Strict.ByteString)] -> Headers -toHeaders xs = mkHeaders xs M.empty - - -mkHeaders :: [(Strict.ByteString, Strict.ByteString)] -> Headers -> Headers -mkHeaders [] m = m -mkHeaders ((key, val):xs) m = mkHeaders xs $ - case M.lookup (toNCBS key) m of - Nothing -> M.insert (toNCBS key) val m - Just old -> M.insert (toNCBS key) (merge old val) m + getHeaders ∷ a → Headers + setHeaders ∷ a → Headers → a + + getHeader ∷ CIAscii → a → Maybe Ascii + {-# INLINE getHeader #-} + getHeader !key !a + = case getHeaders a of + Headers m → M.lookup key m + + deleteHeader ∷ CIAscii → a → a + {-# INLINE deleteHeader #-} + deleteHeader !key !a + = case getHeaders a of + Headers m + → setHeaders a $ Headers $ M.delete key m + + 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 + +toHeaders ∷ [(CIAscii, Ascii)] → Headers +{-# INLINE toHeaders #-} +toHeaders = flip mkHeaders (∅) + +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 :: Strict.ByteString -> Strict.ByteString -> Strict.ByteString - -- カンマ區切りである事を假定する。RFC ではカンマ區切りに出來ない - -- ヘッダは複數個あってはならない事になってゐる。 + merge ∷ Ascii → Ascii → Ascii + {-# INLINE merge #-} merge a b - | C8.null a && C8.null b = C8.empty - | C8.null a = b - | C8.null b = a - | otherwise = C8.concat [a, C8.pack ", ", b] - + | nullA a ∧ nullA b = (∅) + | nullA a = b + | nullA b = a + | otherwise = a ⊕ ", " ⊕ b -fromHeaders :: Headers -> [(Strict.ByteString, Strict.ByteString)] -fromHeaders hs = [(fromNCBS a, b) | (a, b) <- M.toList hs] + 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 ] @@ -172,49 +95,39 @@ fromHeaders hs = [(fromNCBS a, b) | (a, b) <- M.toList hs] field-value の先頭および末尾にある LWS は全て削除され、それ以外の LWS は單一の SP に變換される。 -} -headersP :: Parser Headers -headersP = do xs <- many header - _ <- crlf - return $! toHeaders xs +headersP ∷ Parser Headers +{-# INLINEABLE headersP #-} +headersP = do xs ← P.many header + crlf + return $ toHeaders xs where - header :: Parser (Strict.ByteString, Strict.ByteString) - header = do name <- token - _ <- char ':' - -- FIXME: これは多少インチキだが、RFC 2616 のこの部分 - -- の記述はひどく曖昧であり、この動作が本當に間違って - -- ゐるのかどうかも良く分からない。例へば - -- quoted-string の内部にある空白は纏めていいのか惡い - -- のか?直勸的には駄目さうに思へるが、そんな記述は見 - -- 付からない。 - contents <- many (lws <|> many1 text) - _ <- crlf - let value = foldr (++) "" contents - norm = normalize value - return (C8.pack name, C8.pack norm) - - normalize :: String -> String - normalize = trimBody . trim isWhiteSpace - - trimBody = concat - . map (\ s -> if head s == ' ' then - " " - else - s) - . group - . map (\ c -> if isWhiteSpace c - then ' ' - else c) - - -hPutHeaders :: HandleLike h => h -> Headers -> IO () -hPutHeaders h hds - = h `seq` hds `seq` - mapM_ putH (M.toList hds) >> hPutBS h (C8.pack "\r\n") + header ∷ Parser (CIAscii, Ascii) + header = try $ + do name ← A.toCIAscii <$> token + _ ← char ':' + skipMany lws + values ← sepBy content lws + skipMany lws + crlf + return (name, joinValues values) + + content ∷ Parser Ascii + {-# INLINE content #-} + content = A.unsafeFromByteString + <$> + takeWhile1 (\c → ((¬) (isSPHT c)) ∧ isText c) + + joinValues ∷ [Ascii] → Ascii + {-# INLINE joinValues #-} + joinValues = A.fromAsciiBuilder ∘ joinWith "\x20" + +hPutHeaders ∷ HandleLike h => h → Headers → IO () +hPutHeaders !h !(Headers m) + = mapM_ putH (M.toList m) >> hPutBS h "\r\n" where - putH :: (NCBS, Strict.ByteString) -> IO () - putH (name, value) - = name `seq` value `seq` - do hPutBS h (fromNCBS name) - hPutBS h (C8.pack ": ") - hPutBS h value - hPutBS h (C8.pack "\r\n") + putH ∷ (CIAscii, Ascii) → IO () + putH (!name, !value) + = do hPutBS h (A.ciToByteString name) + hPutBS h ": " + hPutBS h (A.toByteString value) + hPutBS h "\r\n"