Skip to content
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
8 changes: 7 additions & 1 deletion Lib/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,14 @@ def _guess_quote_and_delimiter(self, data, delimiters):

# if we see an extra quote between delimiters, we've got a
# double quoted format
#
# The first character class must exclude the quote character. If it
# can cross a quote, the engine has to try every way of splitting a
# run of quotes between the two classes, which is pathological on
# quote-heavy input (gh-109638). The second class must keep admitting
# the quote, since that is what lets `"a""b"` match.
dq_regexp = re.compile(
r"((%(delim)s)|^)\W*%(quote)s[^%(delim)s\n]*%(quote)s[^%(delim)s\n]*%(quote)s\W*((%(delim)s)|$)" % \
r"((%(delim)s)|^)\W*%(quote)s[^%(delim)s%(quote)s\n]*%(quote)s[^%(delim)s\n]*%(quote)s\W*((%(delim)s)|$)" % \
{'delim':re.escape(delim), 'quote':quotechar}, re.MULTILINE)


Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,6 +1501,12 @@ def test_delimiters(self):
self.assertEqual(dialect.delimiter, ',')
self.assertEqual(dialect.quotechar, '"')

def test_sniff_regex_backtracking(self):
# gh-109638: this artificial sample used to take about ten seconds.
sniffer = csv.Sniffer()
sample = '"",' * 60 + '"' * 60 + '0' + '"' * 60 + '0'
self.assertEqual(sniffer.sniff(sample).delimiter, ',')

def test_doublequote(self):
sniffer = csv.Sniffer()
dialect = sniffer.sniff(self.header1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix excessive regular expression backtracking in :class:`csv.Sniffer` on
quote-heavy samples. Detecting a doubled-quote dialect could try every way of
splitting a run of quotes between two character classes, making
:meth:`~csv.Sniffer.sniff` take seconds on samples of a few hundred characters.
Loading