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]