]> gitweb @ CieloNegro.org - Lucu.git/blob - Network/HTTP/Lucu/Headers.hs
Code clean-up using convertible-text.
[Lucu.git] / Network / HTTP / Lucu / Headers.hs
1 {-# LANGUAGE
2     FlexibleInstances
3   , GeneralizedNewtypeDeriving
4   , MultiParamTypeClasses
5   , TemplateHaskell
6   , TypeSynonymInstances
7   , OverloadedStrings
8   , UnicodeSyntax
9   #-}
10 {-# OPTIONS_GHC -fno-warn-missing-methods #-}
11 -- |An internal module for HTTP headers.
12 module Network.HTTP.Lucu.Headers
13     ( Headers
14     , HasHeaders(..)
15     , headers
16     )
17     where
18 import Control.Applicative hiding (empty)
19 import Control.Applicative.Unicode hiding ((∅))
20 import Control.Monad
21 import Data.Ascii (Ascii, AsciiBuilder, CIAscii)
22 import qualified Data.Ascii as A
23 import Data.Attoparsec.Char8
24 import qualified Data.Collections.Newtype.TH as C
25 import Data.Convertible.Base
26 import Data.Convertible.Instances.Ascii ()
27 import Data.Convertible.Utils
28 import Data.List (intersperse)
29 import qualified Data.Map as M (Map)
30 import Data.Collections
31 import Data.Collections.BaseInstances ()
32 import Data.Monoid
33 import Data.Monoid.Unicode
34 import Network.HTTP.Lucu.Parser.Http
35 import Prelude hiding (lookup, null)
36 import Prelude.Unicode
37
38 newtype Headers
39     = Headers (M.Map CIAscii Ascii)
40       deriving (Eq, Show)
41
42 class HasHeaders a where
43     getHeaders ∷ a → Headers
44     setHeaders ∷ a → Headers → a
45
46     modifyHeaders ∷ (Headers → Headers) → a → a
47     {-# INLINE modifyHeaders #-}
48     modifyHeaders = (setHeaders ⊛) ∘ (∘ getHeaders)
49
50     getHeader ∷ CIAscii → a → Maybe Ascii
51     {-# INLINE getHeader #-}
52     getHeader = (∘ getHeaders) ∘ lookup
53
54     hasHeader ∷ CIAscii → a → Bool
55     {-# INLINE hasHeader #-}
56     hasHeader = (∘ getHeaders) ∘ member
57
58     getCIHeader ∷ CIAscii → a → Maybe CIAscii
59     {-# INLINE getCIHeader #-}
60     getCIHeader = ((A.toCIAscii <$>) ∘) ∘ getHeader
61
62     deleteHeader ∷ CIAscii → a → a
63     {-# INLINE deleteHeader #-}
64     deleteHeader = modifyHeaders ∘ delete
65
66     setHeader ∷ CIAscii → Ascii → a → a
67     {-# INLINE setHeader #-}
68     setHeader = (modifyHeaders ∘) ∘ insertWith const
69
70 instance HasHeaders Headers where
71     getHeaders   = id
72     setHeaders _ = id
73
74 C.derive [d| instance Foldable   Headers (CIAscii, Ascii)
75              instance Collection Headers (CIAscii, Ascii)
76              instance Indexed    Headers  CIAscii  Ascii
77              instance Map        Headers  CIAscii  Ascii
78              instance SortingCollection Headers (CIAscii, Ascii)
79            |]
80
81 -- |@'insert' (key, val)@ merges @val@ with an existing one if any.
82 instance Unfoldable Headers (CIAscii, Ascii) where
83     {-# INLINE insert #-}
84     insert (key, val) (Headers m)
85         = Headers $ insertWith merge key val m
86     {-# INLINE empty #-}
87     empty     = Headers empty
88     {-# INLINE singleton #-}
89     singleton = Headers ∘ singleton
90
91 instance Monoid Headers where
92     {-# INLINE mempty #-}
93     mempty  = empty
94     {-# INLINE mappend #-}
95     mappend = insertMany
96
97 merge ∷ Ascii → Ascii → Ascii
98 {-# INLINE merge #-}
99 merge a b
100     | nullA a ∧ nullA b = (∅)
101     | nullA a           = b
102     |           nullA b = a
103     | otherwise         = a ⊕ ", " ⊕ b
104     where
105       nullA ∷ Ascii → Bool
106       {-# INLINE nullA #-}
107       nullA = null ∘ A.toByteString
108
109 instance ConvertSuccess Headers Ascii where
110     {-# INLINE convertSuccess #-}
111     convertSuccess = convertSuccessVia ((⊥) ∷ AsciiBuilder)
112
113 instance ConvertSuccess Headers AsciiBuilder where
114     {-# INLINEABLE convertSuccess #-}
115     convertSuccess (Headers m)
116         = mconcat (header <$> fromFoldable m) ⊕ cs ("\x0D\x0A" ∷ Ascii)
117         where
118           header ∷ (CIAscii, Ascii) → AsciiBuilder
119           {-# INLINE header #-}
120           header (name, value)
121               = cs name                 ⊕
122                 cs (": " ∷ Ascii)       ⊕
123                 cs value                ⊕
124                 cs ("\x0D\x0A" ∷ Ascii)
125
126 deriveAttempts [ ([t| Headers |], [t| Ascii        |])
127                , ([t| Headers |], [t| AsciiBuilder |])
128                ]
129
130 {-
131   message-header = field-name ":" [ field-value ]
132   field-name     = token
133   field-value    = *( field-content | LWS )
134   field-content  = <field-value を構成し、*TEXT あるいは
135                     token, separators, quoted-string を連結
136                     したものから成る OCTET>
137
138   field-value の先頭および末尾にある LWS は全て削除され、それ以外の
139   LWS は單一の SP に變換される。
140 -}
141 headers ∷ Parser Headers
142 {-# INLINEABLE headers #-}
143 headers = do xs ← many header
144              crlf
145              return $ fromFoldable xs
146     where
147       header ∷ Parser (CIAscii, Ascii)
148       header = do name ← A.toCIAscii <$> token
149                   void $ char ':'
150                   skipMany lws
151                   values ← content `sepBy` try lws
152                   skipMany (try lws)
153                   crlf
154                   return (name, joinValues values)
155
156       content ∷ Parser Ascii
157       {-# INLINE content #-}
158       content = A.unsafeFromByteString
159                 <$>
160                 takeWhile1 (\c → isText c ∧ c ≢ '\x20')
161
162       joinValues ∷ [Ascii] → Ascii
163       {-# INLINE joinValues #-}
164       joinValues = A.fromAsciiBuilder
165                    ∘ mconcat
166                    ∘ intersperse (A.toAsciiBuilder "\x20")
167                    ∘ (A.toAsciiBuilder <$>)