on master help sign in
Wrote parseQueryString
commit4c400b98ef125b42f53ba83466e6c7995b3bf7b3 authorMichael Snoyman <michael@snoyman.com> dateTue 18 May 2010, 12:46 (16 years ago) parent30241cf
3 files changed+127−1
Network/Wai/Parse.hs+95 runtests.hs+30 wai-extra.cabal+2−1
Network/Wai/Parse.hsnew file+95
@@ -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)
runtests.hsnew file+30
@@ -0,0 +1,30 @@
1+import Test.Framework (defaultMain, testGroup, Test)
2+import Test.Framework.Providers.HUnit
3+import Test.HUnit hiding (Test)
4+
5+import Network.Wai.Parse
6+import qualified Data.ByteString.Char8 as B8
7+import Control.Arrow
8+
9+main :: IO ()
10+main = defaultMain [testSuite]
11+
12+testSuite :: Test
13+testSuite = testGroup "Network.Wai.Parse"
14+ [ testCase "parseQueryString" caseParseQueryString
15+ ]
16+
17+caseParseQueryString :: Assertion
18+caseParseQueryString = do
19+ let go l r =
20+ map (B8.pack *** B8.pack) l @=? parseQueryString (B8.pack r)
21+
22+ go [] ""
23+ go [("foo", "")] "foo"
24+ go [("foo", "bar")] "foo=bar"
25+ go [("foo", "bar"), ("baz", "bin")] "foo=bar&baz=bin"
26+ go [("%Q", "")] "%Q"
27+ go [("%1Q", "")] "%1Q"
28+ go [("%1", "")] "%1"
29+ go [("/", "")] "%2F"
30+ go [("/", "")] "%2f"
wai-extra.cabal+2−1
@@ -1,5 +1,5 @@
11Name: wai-extra
2−Version: 0.1.1.1
2+Version: 0.1.2
33Synopsis: Provides some basic WAI handlers and middleware.
44Description: The goal here is to provide common features without many dependencies.
55License: BSD3
@@ -26,6 +26,7 @@ Library
2626 Network.Wai.Middleware.Gzip
2727 Network.Wai.Middleware.Jsonp
2828 Network.Wai.Zlib
29+ Network.Wai.Parse
2930 Other-modules: Network.Wai.Handler.Helper
3031 ghc-options: -Wall
3132 c-sources: c/helper.c