https://github.com/python/cpython/commit/a4a28e50fe5a4c1a0b1335bc19025c874421de4f
commit: a4a28e50fe5a4c1a0b1335bc19025c874421de4f
branch: 3.11
author: Miss Islington (bot) <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2024-02-23T20:43:53Z
summary:

[3.11] gh-67044: Always quote or escape \r and \n in csv.writer() (GH-115741) 
(GH-115867)

(cherry picked from commit c688c0f130906ff7725a126fff143d1389884f89)

Co-authored-by: Serhiy Storchaka <[email protected]>

files:
A Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst
M Lib/test/test_csv.py
M Modules/_csv.c

diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py
index e56b0022e091ea..1b56330abdf0cf 100644
--- a/Lib/test/test_csv.py
+++ b/Lib/test/test_csv.py
@@ -237,9 +237,11 @@ def test_write_lineterminator(self):
                     writer = csv.writer(sio, lineterminator=lineterminator)
                     writer.writerow(['a', 'b'])
                     writer.writerow([1, 2])
+                    writer.writerow(['\r', '\n'])
                     self.assertEqual(sio.getvalue(),
                                      f'a,b{lineterminator}'
-                                     f'1,2{lineterminator}')
+                                     f'1,2{lineterminator}'
+                                     f'"\r","\n"{lineterminator}')
 
     def test_write_iterable(self):
         self._write_test(iter(['a', 1, 'p,q']), 'a,1,"p,q"')
@@ -455,22 +457,44 @@ def test_read_linenum(self):
         self.assertEqual(r.line_num, 3)
 
     def test_roundtrip_quoteed_newlines(self):
-        with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj:
-            writer = csv.writer(fileobj)
-            rows = [['a\nb','b'],['c','x\r\nd']]
-            writer.writerows(rows)
-            fileobj.seek(0)
-            for i, row in enumerate(csv.reader(fileobj)):
-                self.assertEqual(row, rows[i])
+        rows = [
+            ['\na', 'b\nc', 'd\n'],
+            ['\re', 'f\rg', 'h\r'],
+            ['\r\ni', 'j\r\nk', 'l\r\n'],
+            ['\n\rm', 'n\n\ro', 'p\n\r'],
+            ['\r\rq', 'r\r\rs', 't\r\r'],
+            ['\n\nu', 'v\n\nw', 'x\n\n'],
+        ]
+        for lineterminator in '\r\n', '\n', '\r':
+            with self.subTest(lineterminator=lineterminator):
+                with TemporaryFile("w+", encoding="utf-8", newline='') as 
fileobj:
+                    writer = csv.writer(fileobj, lineterminator=lineterminator)
+                    writer.writerows(rows)
+                    fileobj.seek(0)
+                    for i, row in enumerate(csv.reader(fileobj)):
+                        self.assertEqual(row, rows[i])
 
     def test_roundtrip_escaped_unquoted_newlines(self):
-        with TemporaryFile("w+", encoding="utf-8", newline='') as fileobj:
-            writer = csv.writer(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")
-            rows = [['a\nb','b'],['c','x\r\nd']]
-            writer.writerows(rows)
-            fileobj.seek(0)
-            for i, row in 
enumerate(csv.reader(fileobj,quoting=csv.QUOTE_NONE,escapechar="\\")):
-                self.assertEqual(row,rows[i])
+        rows = [
+            ['\na', 'b\nc', 'd\n'],
+            ['\re', 'f\rg', 'h\r'],
+            ['\r\ni', 'j\r\nk', 'l\r\n'],
+            ['\n\rm', 'n\n\ro', 'p\n\r'],
+            ['\r\rq', 'r\r\rs', 't\r\r'],
+            ['\n\nu', 'v\n\nw', 'x\n\n'],
+        ]
+        for lineterminator in '\r\n', '\n', '\r':
+            with self.subTest(lineterminator=lineterminator):
+                with TemporaryFile("w+", encoding="utf-8", newline='') as 
fileobj:
+                    writer = csv.writer(fileobj, lineterminator=lineterminator,
+                                        quoting=csv.QUOTE_NONE, 
escapechar="\\")
+                    writer.writerows(rows)
+                    fileobj.seek(0)
+                    for i, row in enumerate(csv.reader(fileobj,
+                                                       quoting=csv.QUOTE_NONE,
+                                                       escapechar="\\")):
+                        self.assertEqual(row, rows[i])
+
 
 class TestDialectRegistry(unittest.TestCase):
     def test_registry_badargs(self):
diff --git 
a/Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst 
b/Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst
new file mode 100644
index 00000000000000..095e69b6cadab6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2024-02-20-22-02-34.gh-issue-67044.QF9_Ru.rst
@@ -0,0 +1,2 @@
+:func:`csv.writer` now always quotes or escapes ``'\r'`` and ``'\n'``,
+regardless of *lineterminator* value.
diff --git a/Modules/_csv.c b/Modules/_csv.c
index 101278315643aa..045b71a5c05db1 100644
--- a/Modules/_csv.c
+++ b/Modules/_csv.c
@@ -1109,6 +1109,8 @@ join_append_data(WriterObj *self, unsigned int 
field_kind, const void *field_dat
         if (c == dialect->delimiter ||
             c == dialect->escapechar ||
             c == dialect->quotechar  ||
+            c == '\n'  ||
+            c == '\r'  ||
             PyUnicode_FindChar(
                 dialect->lineterminator, c, 0,
                 PyUnicode_GET_LENGTH(dialect->lineterminator), 1) >= 0) {

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to