https://github.com/python/cpython/commit/a2d0545f36c77f30ae0e3cafa605b35586ea29f0
commit: a2d0545f36c77f30ae0e3cafa605b35586ea29f0
branch: 3.14
author: lpyuu <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-30T10:42:49+03:00
summary:
[3.14] gh-155468: Distinguish empty quoted tokens from EOF in netrc(GH-155471)
(GH-156469)
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 bd003e80a48081..26e4002c4a6f18 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
toplevel = 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 9d720f627102e3..7eca33fc44e90e 100644
--- a/Lib/test/test_netrc.py
+++ b/Lib/test/test_netrc.py
@@ -59,6 +59,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"
@@ -71,6 +74,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"
@@ -79,6 +85,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]