@@ -0,0 +1,95 @@
1+-- | Some helpers for parsing data out of a raw WAI 'Request'.
2+
3+module Network.Wai.Parse
4+ ( parseQueryString
5+ ) where
6+
7+import Network.Wai
8+import qualified Data.ByteString as S
9+import Data.Word (Word8)
10+import Data.Bits
11+
12+c2w :: Char -> Word8
13+c2w = toEnum . fromEnum
14+
15+wequal :: Word8
16+wequal = c2w '='
17+
18+wand :: Word8
19+wand = c2w '&'
20+
21+wpercent :: Word8
22+wpercent = c2w '%'
23+
24+type BufferedBS = ([Word8], S.ByteString)
25+
26+uncons :: BufferedBS -> Maybe (Word8, BufferedBS)
27+uncons ((w:ws), s) = Just (w, (ws, s))
28+uncons ([], s)
29+ | S.null s = Nothing
30+ | otherwise = Just (S.head s, ([], S.tail s))
31+
32+cons :: Word8 -> BufferedBS -> BufferedBS
33+cons w (ws, s) = (w:ws, s)
34+
35+breakDiscard :: Word8 -> S.ByteString -> (S.ByteString, S.ByteString)
36+breakDiscard w s =
37+ let (x, y) = S.break (== w) s
38+ in (x, S.drop 1 y)
39+
40+-- | Split out the query string into a list of keys and values. A few
41+-- importants points:
42+--
43+-- * There is no way to distinguish between a parameter with no value and a
44+-- parameter with an empty value. Eg, "foo=" and "foo" are the same.
45+--
46+-- * The result returned is still bytestrings, since we perform no character
47+-- decoding here. Most likely, you will want to use UTF-8 decoding, but this is
48+-- left to the user of the library.
49+--
50+-- * Percent decoding errors are ignored. In particular, "%Q" will be output as
51+-- "%Q".
52+parseQueryString :: S.ByteString -> [(S.ByteString, S.ByteString)]
53+parseQueryString q | S.null q = []
54+parseQueryString q =
55+ let (x, xs) = breakDiscard wand q
56+ in parsePair x : parseQueryString xs
57+ where
58+ parsePair x =
59+ let (k, v) = breakDiscard wequal x
60+ in (decode k, decode v)
61+ decode x = fst $ S.unfoldrN (S.length x) go (NoPercent, ([], x))
62+ go (state, x) =
63+ case (state, uncons x) of
64+ (NoPercent, Nothing) -> Nothing
65+ (NoChar, Nothing) -> Just (wpercent, (NoPercent, x))
66+ (OneChar (w, _), Nothing) ->
67+ Just (wpercent, (NoPercent, cons w x))
68+ (NoPercent, Just (w, ws)) ->
69+ if w == wpercent
70+ then go (NoChar, ws)
71+ else Just (w, (NoPercent, ws))
72+ (NoChar, Just (w, ws)) ->
73+ case hexVal w of
74+ Nothing -> Just (wpercent, (NoPercent, x))
75+ Just v -> go (OneChar (w, v), ws)
76+ (OneChar (w1, v1), Just (w2, ws)) ->
77+ case hexVal w2 of
78+ Nothing ->
79+ Just (wpercent, (NoPercent, w1 `cons` x))
80+ Just v2 -> Just (combine v1 v2, (NoPercent, ws))
81+ w0 = c2w '0'
82+ w9 = c2w '9'
83+ wa = c2w 'a'
84+ wf = c2w 'f'
85+ wA = c2w 'A'
86+ wF = c2w 'F'
87+ hexVal w
88+ | w0 <= w && w <= w9 = Just $ w - w0
89+ | wa <= w && w <= wf = Just $ w - wa + 10
90+ | wA <= w && w <= wF = Just $ w - wA + 10
91+ | otherwise = Nothing
92+ combine :: Word8 -> Word8 -> Word8
93+ combine a b = shiftL a 4 .|. b
94+
95+data DecodeHelper = NoPercent | NoChar | OneChar (Word8, Word8)