{-# LANGUAGE OverloadedStrings #-} module Test.Hspec.Wai.Util where import Control.Monad import Data.Monoid import Data.Maybe import Data.List import Data.Word import Data.Char hiding (ord) import qualified Data.Char as Char import Data.ByteString (ByteString) import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8 import qualified Data.ByteString.Lazy as LB import Data.ByteString.Lazy.Builder (Builder) import qualified Data.ByteString.Lazy.Builder as Builder import qualified Data.Text as T import qualified Data.Text.Encoding as T import qualified Data.CaseInsensitive as CI import Network.HTTP.Types formatHeader :: Header -> String formatHeader header@(name, value) = " " ++ fromMaybe (show header) (safeToString $ B8.concat [CI.original name, ": ", value]) safeToString :: ByteString -> Maybe String safeToString bs = do str <- either (const Nothing) (Just . T.unpack) (T.decodeUtf8' bs) let isSafe = not $ case str of [] -> True _ -> isSpace (last str) || any (not . isPrint) str guard isSafe >> return str -- for compatibility with older versions of `bytestring` toStrict :: LB.ByteString -> ByteString toStrict = mconcat . LB.toChunks formUrlEncodeQuery :: [(String, String)] -> LB.ByteString formUrlEncodeQuery = Builder.toLazyByteString . mconcat . intersperse amp . map encodePair where equals = Builder.word8 (ord '=') amp = Builder.word8 (ord '&') percent = Builder.word8 (ord '%') plus = Builder.word8 (ord '+') encodePair :: (String, String) -> Builder encodePair (key, value) = encode key <> equals <> encode value encode :: String -> Builder encode = escape . T.encodeUtf8 . T.pack . newlineNormalize newlineNormalize :: String -> String newlineNormalize input = case input of [] -> [] '\n' : xs -> '\r' : '\n': newlineNormalize xs x : xs -> x : newlineNormalize xs escape :: ByteString -> Builder escape = mconcat . map f . B.unpack where f :: Word8 -> Builder f c | p c = Builder.word8 c | c == ord ' ' = plus | otherwise = percentEncode c p :: Word8 -> Bool p c = ord 'a' <= c && c <= ord 'z' || c == ord '_' || c == ord '*' || c == ord '-' || c == ord '.' || ord '0' <= c && c <= ord '9' || ord 'A' <= c && c <= ord 'Z' ord :: Char -> Word8 ord = fromIntegral . Char.ord percentEncode :: Word8 -> Builder percentEncode n = percent <> hex hi <> hex lo where (hi, lo) = n `divMod` 16 hex :: Word8 -> Builder hex n = Builder.word8 (offset + n) where offset | n < 10 = 48 | otherwise = 55