https://github.com/python/cpython/commit/2a37d678ee96bba13e58e68020ccdcf666ff23a3
commit: 2a37d678ee96bba13e58e68020ccdcf666ff23a3
branch: main
author: stevens <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-27T15:27:20+03:00
summary:

gh-155468: Distinguish empty quoted tokens from EOF in netrc (GH-155471)

files:
A Misc/NEWS.d/next/Library/2026-08-10-15-24-13.gh-issue-155468.W7qL2p.rst
M Lib/netrc.py
M Lib/test/test_netrc.py

diff --git a/Lib/netrc.py b/Lib/netrc.py
index e9b5538d2c4399..67a287237cbf8b 100644
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -48,11 +48,12 @@ def _read_char(self):
     def get_token(self):
         if self.pushback:
             return self.pushback.pop(0)
-        token = ""
+        token = None
         fiter = iter(self._read_char, "")
         for ch in fiter:
             if ch in self.whitespace:
                 continue
+            token = ""
             if ch == '"':
                 for ch in fiter:
                     if ch == '"':
@@ -96,9 +97,9 @@ def _parse(self, file, fp, default_netrc):
             # Look for a machine, default, or macdef top-level keyword
             saved_lineno = lexer.lineno
             tt = lexer.get_token()
-            if not tt:
+            if tt is None:
                 break
-            elif tt[0] == '#':
+            elif tt.startswith('#'):
                 if lexer.lineno == saved_lineno and len(tt) == 1:
                     lexer.instream.readline()
                 continue
@@ -135,20 +136,20 @@ def _parse(self, file, fp, default_netrc):
             while 1:
                 prev_lineno = lexer.lineno
                 tt = lexer.get_token()
-                if tt.startswith('#'):
+                if tt is not None and tt.startswith('#'):
                     if lexer.lineno == prev_lineno:
                         lexer.instream.readline()
                     continue
-                if tt in {'', 'machine', 'default', 'macdef'}:
+                if tt in {None, 'machine', 'default', 'macdef'}:
                     self.hosts[entryname] = (login, account, password)
                     lexer.push_token(tt)
                     break
                 elif tt == 'login' or tt == 'user':
-                    login = lexer.get_token()
+                    login = lexer.get_token() or ''
                 elif tt == 'account':
-                    account = lexer.get_token()
+                    account = lexer.get_token() or ''
                 elif tt == 'password':
-                    password = lexer.get_token()
+                    password = lexer.get_token() or ''
                 else:
                     raise NetrcParseError("bad follower token %r" % tt,
                                           file, lexer.lineno)
diff --git a/Lib/test/test_netrc.py b/Lib/test/test_netrc.py
index 354081e96213a6..bbb49bf15ce8f1 100644
--- a/Lib/test/test_netrc.py
+++ b/Lib/test/test_netrc.py
@@ -62,6 +62,9 @@ def test_optional_tokens(self):
             "machine host.domain.com login",
             "machine host.domain.com account",
             "machine host.domain.com password",
+            "machine host.domain.com login \"\"",
+            "machine host.domain.com account \"\"",
+            "machine host.domain.com password \"\"",
             "machine host.domain.com login \"\" account",
             "machine host.domain.com login \"\" password",
             "machine host.domain.com account \"\" password"
@@ -74,6 +77,9 @@ def test_optional_tokens(self):
             "default login",
             "default account",
             "default password",
+            "default login \"\"",
+            "default account \"\"",
+            "default password \"\"",
             "default login \"\" account",
             "default login \"\" password",
             "default account \"\" password"
@@ -82,6 +88,15 @@ def test_optional_tokens(self):
             nrc = self.make_nrc(item)
             self.assertEqual(nrc.hosts['default'], ('', '', ''))
 
+    def test_empty_quoted_token_is_not_eof(self):
+        data = (
+            '"" invalid',
+            'machine host.domain.com "" invalid',
+        )
+        for item in data:
+            with self.subTest(item=item):
+                self.assertRaises(netrc.NetrcParseError, self.make_nrc, item)
+
     def test_invalid_tokens(self):
         data = (
             "invalid host.domain.com",
diff --git 
a/Misc/NEWS.d/next/Library/2026-08-10-15-24-13.gh-issue-155468.W7qL2p.rst 
b/Misc/NEWS.d/next/Library/2026-08-10-15-24-13.gh-issue-155468.W7qL2p.rst
new file mode 100644
index 00000000000000..4d495b9de62fa5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-08-10-15-24-13.gh-issue-155468.W7qL2p.rst
@@ -0,0 +1,2 @@
+Fix :mod:`netrc` to distinguish empty quoted tokens from end-of-file, so
+malformed files no longer cause the remaining content to be silently ignored.

_______________________________________________
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