Wrote parseQueryString

This commit is contained in:
Michael Snoyman 2010-05-18 12:46:33 +03:00
parent 30241cfdb1
commit 4c400b98ef
3 changed files with 127 additions and 1 deletions

95
Network/Wai/Parse.hs Normal file
View File

@ -0,0 +1,95 @@
-- | Some helpers for parsing data out of a raw WAI 'Request'.
module Network.Wai.Parse
( parseQueryString
) where
import Network.Wai
import qualified Data.ByteString as S
import Data.Word (Word8)
import Data.Bits
c2w :: Char -> Word8
c2w = toEnum . fromEnum
wequal :: Word8
wequal = c2w '='
wand :: Word8
wand = c2w '&'
wpercent :: Word8
wpercent = c2w '%'
type BufferedBS = ([Word8], S.ByteString)
uncons :: BufferedBS -> Maybe (Word8, BufferedBS)
uncons ((w:ws), s) = Just (w, (ws, s))
uncons ([], s)
| S.null s = Nothing
| otherwise = Just (S.head s, ([], S.tail s))
cons :: Word8 -> BufferedBS -> BufferedBS
cons w (ws, s) = (w:ws, s)
breakDiscard :: Word8 -> S.ByteString -> (S.ByteString, S.ByteString)
breakDiscard w s =
let (x, y) = S.break (== w) s
in (x, S.drop 1 y)
-- | Split out the query string into a list of keys and values. A few
-- importants points:
--
-- * There is no way to distinguish between a parameter with no value and a
-- parameter with an empty value. Eg, "foo=" and "foo" are the same.
--
-- * The result returned is still bytestrings, since we perform no character
-- decoding here. Most likely, you will want to use UTF-8 decoding, but this is
-- left to the user of the library.
--
-- * Percent decoding errors are ignored. In particular, "%Q" will be output as
-- "%Q".
parseQueryString :: S.ByteString -> [(S.ByteString, S.ByteString)]
parseQueryString q | S.null q = []
parseQueryString q =
let (x, xs) = breakDiscard wand q
in parsePair x : parseQueryString xs
where
parsePair x =
let (k, v) = breakDiscard wequal x
in (decode k, decode v)
decode x = fst $ S.unfoldrN (S.length x) go (NoPercent, ([], x))
go (state, x) =
case (state, uncons x) of
(NoPercent, Nothing) -> Nothing
(NoChar, Nothing) -> Just (wpercent, (NoPercent, x))
(OneChar (w, _), Nothing) ->
Just (wpercent, (NoPercent, cons w x))
(NoPercent, Just (w, ws)) ->
if w == wpercent
then go (NoChar, ws)
else Just (w, (NoPercent, ws))
(NoChar, Just (w, ws)) ->
case hexVal w of
Nothing -> Just (wpercent, (NoPercent, x))
Just v -> go (OneChar (w, v), ws)
(OneChar (w1, v1), Just (w2, ws)) ->
case hexVal w2 of
Nothing ->
Just (wpercent, (NoPercent, w1 `cons` x))
Just v2 -> Just (combine v1 v2, (NoPercent, ws))
w0 = c2w '0'
w9 = c2w '9'
wa = c2w 'a'
wf = c2w 'f'
wA = c2w 'A'
wF = c2w 'F'
hexVal w
| w0 <= w && w <= w9 = Just $ w - w0
| wa <= w && w <= wf = Just $ w - wa + 10
| wA <= w && w <= wF = Just $ w - wA + 10
| otherwise = Nothing
combine :: Word8 -> Word8 -> Word8
combine a b = shiftL a 4 .|. b
data DecodeHelper = NoPercent | NoChar | OneChar (Word8, Word8)

30
runtests.hs Normal file
View File

@ -0,0 +1,30 @@
import Test.Framework (defaultMain, testGroup, Test)
import Test.Framework.Providers.HUnit
import Test.HUnit hiding (Test)
import Network.Wai.Parse
import qualified Data.ByteString.Char8 as B8
import Control.Arrow
main :: IO ()
main = defaultMain [testSuite]
testSuite :: Test
testSuite = testGroup "Network.Wai.Parse"
[ testCase "parseQueryString" caseParseQueryString
]
caseParseQueryString :: Assertion
caseParseQueryString = do
let go l r =
map (B8.pack *** B8.pack) l @=? parseQueryString (B8.pack r)
go [] ""
go [("foo", "")] "foo"
go [("foo", "bar")] "foo=bar"
go [("foo", "bar"), ("baz", "bin")] "foo=bar&baz=bin"
go [("%Q", "")] "%Q"
go [("%1Q", "")] "%1Q"
go [("%1", "")] "%1"
go [("/", "")] "%2F"
go [("/", "")] "%2f"

View File

@ -1,5 +1,5 @@
Name: wai-extra
Version: 0.1.1.1
Version: 0.1.2
Synopsis: Provides some basic WAI handlers and middleware.
Description: The goal here is to provide common features without many dependencies.
License: BSD3
@ -26,6 +26,7 @@ Library
Network.Wai.Middleware.Gzip
Network.Wai.Middleware.Jsonp
Network.Wai.Zlib
Network.Wai.Parse
Other-modules: Network.Wai.Handler.Helper
ghc-options: -Wall
c-sources: c/helper.c