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