]> gitweb @ CieloNegro.org - Lucu.git/blob - Network/HTTP/Lucu/Headers.hs
Fixed many bugs...
[Lucu.git] / Network / HTTP / Lucu / Headers.hs
1 {-# LANGUAGE
2     GeneralizedNewtypeDeriving
3   , OverloadedStrings
4   , UnicodeSyntax
5   #-}
6 module Network.HTTP.Lucu.Headers
7     ( Headers
8     , HasHeaders(..)
9
10     , singleton
11
12     , toHeaders
13     , fromHeaders
14
15     , headersP
16     , printHeaders
17     )
18     where
19 import Control.Applicative
20 import Data.Ascii (Ascii, AsciiBuilder, CIAscii)
21 import qualified Data.Ascii as A
22 import Data.Attoparsec.Char8 as P
23 import qualified Data.ByteString as BS
24 import Data.Map (Map)
25 import qualified Data.Map as M
26 import qualified Data.Map.Unicode as M
27 import Data.Monoid
28 import Data.Monoid.Unicode
29 import Network.HTTP.Lucu.Parser.Http
30 import Network.HTTP.Lucu.Utils
31 import Prelude.Unicode
32
33 newtype Headers
34     = Headers (Map CIAscii Ascii)
35       deriving (Eq, Show, Monoid)
36
37 class HasHeaders a where
38     getHeaders ∷ a → Headers
39     setHeaders ∷ a → Headers → a
40
41     getHeader ∷ CIAscii → a → Maybe Ascii
42     getHeader key a
43         = case getHeaders a of
44             Headers m → M.lookup key m
45
46     hasHeader ∷ CIAscii → a → Bool
47     {-# INLINE hasHeader #-}
48     hasHeader key a
49         = case getHeaders a of
50             Headers m → key M.∈ m
51
52     getCIHeader ∷ CIAscii → a → Maybe CIAscii
53     {-# INLINE getCIHeader #-}
54     getCIHeader key a
55         = A.toCIAscii <$> getHeader key a
56
57     deleteHeader ∷ CIAscii → a → a
58     {-# INLINE deleteHeader #-}
59     deleteHeader key a
60         = case getHeaders a of
61             Headers m
62               → setHeaders a $ Headers $ M.delete key m
63
64     setHeader ∷ CIAscii → Ascii → a → a
65     {-# INLINE setHeader #-}
66     setHeader key val a
67         = case getHeaders a of
68             Headers m
69               → setHeaders a $ Headers $ M.insert key val m
70
71 instance HasHeaders Headers where
72     getHeaders   = id
73     setHeaders _ = id
74
75 singleton ∷ CIAscii → Ascii → Headers
76 {-# INLINE singleton #-}
77 singleton key val
78     = Headers $ M.singleton key val
79
80 toHeaders ∷ [(CIAscii, Ascii)] → Headers
81 {-# INLINE toHeaders #-}
82 toHeaders = flip mkHeaders (∅)
83
84 mkHeaders ∷ [(CIAscii, Ascii)] → Headers → Headers
85 mkHeaders []              (Headers m) = Headers m
86 mkHeaders ((key, val):xs) (Headers m)
87     = mkHeaders xs $ Headers $
88       case M.lookup key m of
89         Nothing  → M.insert key val m
90         Just old → M.insert key (merge old val) m
91     where
92       merge ∷ Ascii → Ascii → Ascii
93       {-# INLINE merge #-}
94       merge a b
95           | nullA a ∧ nullA b = (∅)
96           | nullA a           = b
97           |           nullA b = a
98           | otherwise         = a ⊕ ", " ⊕ b
99
100       nullA ∷ Ascii → Bool
101       {-# INLINE nullA #-}
102       nullA = BS.null ∘ A.toByteString
103
104 fromHeaders ∷ Headers → [(CIAscii, Ascii)]
105 fromHeaders (Headers m) = M.toList m
106
107 {-
108   message-header = field-name ":" [ field-value ]
109   field-name     = token
110   field-value    = *( field-content | LWS )
111   field-content  = <field-value を構成し、*TEXT あるいは
112                     token, separators, quoted-string を連結
113                     したものから成る OCTET>
114
115   field-value の先頭および末尾にある LWS は全て削除され、それ以外の
116   LWS は單一の SP に變換される。
117 -}
118 headersP ∷ Parser Headers
119 {-# INLINEABLE headersP #-}
120 headersP = do xs ← P.many header
121               crlf
122               return $ toHeaders xs
123     where
124       header ∷ Parser (CIAscii, Ascii)
125       header = do name ← A.toCIAscii <$> token
126                   _    ← char ':'
127                   skipMany lws
128                   values ← sepBy content (try lws)
129                   skipMany (try lws)
130                   crlf
131                   return (name, joinValues values)
132
133       content ∷ Parser Ascii
134       {-# INLINE content #-}
135       content = A.unsafeFromByteString
136                 <$>
137                 takeWhile1 (\c → (¬) (isSPHT c) ∧ isText c)
138
139       joinValues ∷ [Ascii] → Ascii
140       {-# INLINE joinValues #-}
141       joinValues = A.fromAsciiBuilder ∘ joinWith "\x20" ∘ map A.toAsciiBuilder
142
143 printHeaders ∷ Headers → AsciiBuilder
144 printHeaders (Headers m)
145     = mconcat (map printHeader (M.toList m)) ⊕
146       A.toAsciiBuilder "\x0D\x0A"
147     where
148       printHeader ∷ (CIAscii, Ascii) → AsciiBuilder
149       printHeader (name, value)
150           = A.toAsciiBuilder (A.fromCIAscii name) ⊕
151             A.toAsciiBuilder ": "                 ⊕
152             A.toAsciiBuilder value                ⊕
153             A.toAsciiBuilder "\x0D\x0A"