1 module Network.HTTP.Lucu.Headers
17 import Data.ByteString.Base (ByteString, toForeignPtr, w2c, inlinePerformIO)
18 import qualified Data.ByteString.Char8 as C8
22 import qualified Data.Map as M
24 import Foreign.ForeignPtr
26 import Foreign.Storable
27 import Network.HTTP.Lucu.Parser
28 import Network.HTTP.Lucu.Parser.Http
29 import Network.HTTP.Lucu.Utils
32 type Headers = Map NCBS ByteString
33 newtype NCBS = NCBS ByteString
35 toNCBS :: ByteString -> NCBS
39 fromNCBS :: NCBS -> ByteString
41 {-# INLINE fromNCBS #-}
43 instance Eq NCBS where
44 (NCBS a) == (NCBS b) = a == b
46 instance Ord NCBS where
47 (NCBS a) `compare` (NCBS b) = a `noCaseCmp` b
49 instance Show NCBS where
50 show (NCBS x) = show x
52 noCaseCmp :: ByteString -> ByteString -> Ordering
53 noCaseCmp a b = toForeignPtr a `cmp` toForeignPtr b
55 cmp :: (ForeignPtr Word8, Int, Int) -> (ForeignPtr Word8, Int, Int) -> Ordering
56 cmp (x1, s1, l1) (x2, s2, l2)
57 | l1 == 0 && l2 == 0 = EQ
58 | x1 == x2 && s1 == s2 && l1 == l2 = EQ
61 withForeignPtr x1 $ \ p1 ->
62 withForeignPtr x2 $ \ p2 ->
63 noCaseCmp' (p1 `plusPtr` s1) l1 (p2 `plusPtr` s2) l2
64 {-# INLINE noCaseCmp #-}
66 -- もし先頭の文字列が等しければ、短い方が小さい。
67 noCaseCmp' :: Ptr Word8 -> Int -> Ptr Word8 -> Int -> IO Ordering
68 noCaseCmp' p1 l1 p2 l2
69 | l1 == 0 && l2 == 0 = return EQ
70 | l1 == 0 && l1 /= 0 = return LT
71 | l1 /= 0 && l2 == 0 = return GT
75 case toLower (w2c c1) `compare` toLower (w2c c2) of
76 EQ -> noCaseCmp' (p1 `plusPtr` 1) (l1 - 1) (p2 `plusPtr` 1) (l2 - 1)
80 noCaseEq :: ByteString -> ByteString -> Bool
81 noCaseEq a b = toForeignPtr a `cmp` toForeignPtr b
83 cmp :: (ForeignPtr Word8, Int, Int) -> (ForeignPtr Word8, Int, Int) -> Bool
84 cmp (x1, s1, l1) (x2, s2, l2)
86 | l1 == 0 && l2 == 0 = True
87 | x1 == x2 && s1 == s2 && l1 == l2 = True
90 withForeignPtr x1 $ \ p1 ->
91 withForeignPtr x2 $ \ p2 ->
92 noCaseEq' (p1 `plusPtr` s1) (p2 `plusPtr` s2) l1
95 noCaseEq' :: Ptr Word8 -> Ptr Word8 -> Int -> IO Bool
97 | l == 0 = return True
101 if toLower (w2c c1) == toLower (w2c c2) then
102 noCaseEq' (p1 `plusPtr` 1) (p2 `plusPtr` 1) (l - 1)
107 class HasHeaders a where
108 getHeaders :: a -> Headers
109 setHeaders :: a -> Headers -> a
111 getHeader :: ByteString -> a -> Maybe ByteString
114 M.lookup (toNCBS key) (getHeaders a)
116 deleteHeader :: ByteString -> a -> a
119 setHeaders a $ M.delete (toNCBS key) (getHeaders a)
121 setHeader :: ByteString -> ByteString -> a -> a
123 = key `seq` val `seq` a `seq`
124 setHeaders a $ M.insert (toNCBS key) val (getHeaders a)
127 emptyHeaders :: Headers
128 emptyHeaders = M.empty
131 toHeaders :: [(ByteString, ByteString)] -> Headers
132 toHeaders xs = M.fromList [(toNCBS a, b) | (a, b) <- xs]
135 fromHeaders :: Headers -> [(ByteString, ByteString)]
136 fromHeaders hs = [(fromNCBS a, b) | (a, b) <- M.toList hs]
140 message-header = field-name ":" [ field-value ]
142 field-value = *( field-content | LWS )
143 field-content = <field-value を構成し、*TEXT あるいは
144 token, separators, quoted-string を連結
147 field-value の先頭および末尾にある LWS は全て削除され、それ以外の
150 headersP :: Parser Headers
151 headersP = do xs <- many header
153 return (M.fromList xs)
155 header :: Parser (NCBS, ByteString)
156 header = do name <- token
158 -- FIXME: これは多少インチキだが、RFC 2616 のこの部分
159 -- の記述はひどく曖昧であり、この動作が本當に間違って
160 -- ゐるのかどうかも良く分からない。例へば
161 -- quoted-string の内部にある空白は纏めていいのか惡い
162 -- のか?直勸的には駄目さうに思へるが、そんな記述は見
164 contents <- many (lws <|> many1 text)
166 let value = foldr (++) "" contents
167 norm = normalize value
168 return (toNCBS $ C8.pack name, C8.pack norm)
170 normalize :: String -> String
171 normalize = trimBody . trim isWhiteSpace
173 trimBody = foldr (++) []
174 . map (\ s -> if head s == ' ' then
179 . map (\ c -> if isWhiteSpace c
184 hPutHeaders :: Handle -> Headers -> IO ()
187 mapM_ putH (M.toList hds) >> hPutStr h "\r\n"
189 putH :: (NCBS, ByteString) -> IO ()
191 = name `seq` value `seq`
192 do C8.hPutStr h (fromNCBS name)
193 C8.hPutStr h (C8.pack ": ")
195 C8.hPutStr h (C8.pack "\r\n")