From ba4af66058f2d7558a7e8fcc7a099771274f50d5 Mon Sep 17 00:00:00 2001 From: lipengyu Date: Mon, 10 Aug 2026 15:27:12 +0800 Subject: [PATCH] gh-155468: Distinguish empty quoted tokens from EOF in netrc --- Lib/netrc.py | 17 ++++++++++++----- Lib/test/test_netrc.py | 9 +++++++++ ...26-08-10-15-24-13.gh-issue-155468.W7qL2p.rst | 2 ++ 3 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-10-15-24-13.gh-issue-155468.W7qL2p.rst diff --git a/Lib/netrc.py b/Lib/netrc.py index a28ea297df894b..10bc1968fc278e 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,26 @@ 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 is None or tt in {'machine', 'default', 'macdef'}: self.hosts[entryname] = (login, account, password) lexer.push_token(tt) break elif tt == 'login' or tt == 'user': login = lexer.get_token() + if login is None: + login = '' elif tt == 'account': account = lexer.get_token() + if account is None: + account = '' elif tt == 'password': password = lexer.get_token() + if password is None: + password = '' 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..dc66276cfc6e60 100644 --- a/Lib/test/test_netrc.py +++ b/Lib/test/test_netrc.py @@ -82,6 +82,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.