Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for exception on hanging doublequote #222

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions src/Data/Csv/Parser.hs
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,20 @@ field !delim = do
escapedField :: AL.Parser S.ByteString
escapedField = do
_ <- dquote
-- The scan state is 'True' if the previous character was a double
-- quote. We need to drop a trailing double quote left by scan.
s <- S.init <$> (A.scan False $ \s c -> if c == doubleQuote
then Just (not s)
else if s then Nothing
else Just False)
if doubleQuote `S.elem` s
then case Z.parse unescape s of
Right r -> return r
Left err -> fail err
else return s
-- The scan state is 'True' if the previous character was a double quote.
s' <- A.scan False $ \s c -> if c == doubleQuote
then Just (not s)
else if s then Nothing
else Just False
-- We need to drop a trailing double quote left by scan.
if S.null s'
then fail "trailing double quote"
else let s = S.init s'
in if doubleQuote `S.elem` s
then case Z.parse unescape s of
Right r -> return r
Left err -> fail err
else return s

unescapedField :: Word8 -> AL.Parser S.ByteString
unescapedField !delim = A.takeWhile (\ c -> c /= doubleQuote &&
Expand Down
19 changes: 19 additions & 0 deletions tests/UnitTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import qualified Data.Vector as V
import qualified Data.Foldable as F
import Data.List (isPrefixOf)
import Data.Word
import Numeric.Natural
import GHC.Generics (Generic)
Expand Down Expand Up @@ -56,6 +57,14 @@ assertResult input expected res = case res of
" input: " ++ show (BL8.unpack input) ++ "\n" ++
"parse error: " ++ err

decodeFailsWith :: BL.ByteString -> String -> Assertion
decodeFailsWith input expected = case decode NoHeader input of
Right r -> assertFailure $ "input: " ++ show (BL8.unpack input) ++ "\n" ++
"retuned: " ++ show (r :: (V.Vector (V.Vector B.ByteString))) ++ "\n" ++
"whereas should have failed with " <> expected
Left err -> assertBool ("got " <> err <> "\ninstead of " <> expected)
$ ("parse error ("++expected++")") `isPrefixOf` err

encodesAs :: [[B.ByteString]] -> BL.ByteString -> Assertion
encodesAs input expected =
encode (map V.fromList input) @?= expected
Expand Down Expand Up @@ -166,6 +175,7 @@ positionalTests =
[ testGroup "decode" $ map streamingDecodeTest decodeTests
, testGroup "decodeWith" $ map streamingDecodeWithTest decodeWithTests
]
, testGroup "escaped" escapedTests
]
where
rfc4180Input = BL8.pack $
Expand Down Expand Up @@ -210,6 +220,15 @@ positionalTests =
defEncAllEnq = defaultEncodeOptions { encQuoting = QuoteAll }
defDec = defaultDecodeOptions

escapedTests = [
testCase "escaped" $
"\"x,y\",z\nbaz,\"bar\nfoo,\"" `decodesAs` [["x,y", "z"], ["baz", "bar\nfoo,"]],
testCase "escapedMalformed1" $
"\"x,\"y" `decodeFailsWith` "Failed reading: satisfy",
testCase "escapedMalformed0" $
"baz,\"" `decodeFailsWith` "Failed reading: trailing double quote"
]

nameBasedTests :: [TF.Test]
nameBasedTests =
[ testGroup "encode" $ map encodeTest
Expand Down
Loading