Skip to content

Commit 5ed10a7

Browse files
committed
fix: skip any trailing new line when lexing
1 parent dc72ad1 commit 5ed10a7

File tree

2 files changed

+62
-32
lines changed

2 files changed

+62
-32
lines changed

Lib/netrc.py

Lines changed: 50 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
# Module and documentation by Eric S. Raymond, 21 Dec 1998
44

5-
import os
6-
import stat
5+
import os, stat
76

87
__all__ = ["netrc", "NetrcParseError"]
98

@@ -23,41 +22,53 @@ def __str__(self):
2322
class _netrclex:
2423
def __init__(self, fp):
2524
self.lineno = 1
26-
self.dontskip = False
2725
self.instream = fp
2826
self.whitespace = "\n\t\r "
2927
self.pushback = []
28+
self.char_pushback = []
3029

3130
def _read_char(self):
31+
if self.char_pushback:
32+
return self.char_pushback.pop(0)
3233
ch = self.instream.read(1)
3334
if ch == "\n":
3435
self.lineno += 1
3536
return ch
3637

38+
def skip_blank_lines(self):
39+
fiter = iter(self._read_char, "")
40+
for ch in fiter:
41+
if ch == '\n':
42+
self.lineno += 1
43+
else:
44+
self.char_pushback.append(ch)
45+
return
46+
3747
def get_token(self):
38-
self.dontskip = False
3948
if self.pushback:
4049
return self.pushback.pop(0)
4150
token = ""
42-
enquoted = False
43-
while ch := self._read_char():
44-
if ch == '\\':
45-
ch = self._read_char()
46-
token += ch
51+
fiter = iter(self._read_char, "")
52+
for ch in fiter:
53+
if ch in self.whitespace:
4754
continue
48-
if ch in self.whitespace and not enquoted:
49-
if token == "":
50-
continue
51-
if ch == '\n':
52-
self.dontskip = True
53-
return token
5455
if ch == '"':
55-
if enquoted:
56-
return token
57-
enquoted = True
58-
continue
56+
for ch in fiter:
57+
if ch == '"':
58+
return token
59+
elif ch == "\\":
60+
ch = self._read_char()
61+
token += ch
5962
else:
63+
if ch == "\\":
64+
ch = self._read_char()
6065
token += ch
66+
for ch in fiter:
67+
if ch in self.whitespace:
68+
return token
69+
elif ch == "\\":
70+
ch = self._read_char()
71+
token += ch
6172
return token
6273

6374
def push_token(self, token):
@@ -67,7 +78,7 @@ def push_token(self, token):
6778
class netrc:
6879
def __init__(self, file=None):
6980
default_netrc = file is None
70-
if default_netrc:
81+
if file is None:
7182
file = os.path.join(os.path.expanduser("~"), ".netrc")
7283
self.hosts = {}
7384
self.macros = {}
@@ -82,15 +93,14 @@ def _parse(self, file, fp, default_netrc):
8293
lexer = _netrclex(fp)
8394
while 1:
8495
# Look for a machine, default, or macdef top-level keyword
85-
tt = lexer.get_token()
96+
lexer.skip_blank_lines()
97+
saved_lineno = lexer.lineno
98+
toplevel = tt = lexer.get_token()
8699
if not tt:
87100
break
88101
elif tt[0] == '#':
89-
# For top level tokens, we skip line if the # is followed
90-
# by a space / newline. Otherwise, we only skip the token.
91-
if tt == '#' and not lexer.dontskip:
102+
if lexer.lineno == saved_lineno and len(tt) == 1:
92103
lexer.instream.readline()
93-
lexer.lineno += 1
94104
continue
95105
elif tt == 'machine':
96106
entryname = lexer.get_token()
@@ -101,7 +111,6 @@ def _parse(self, file, fp, default_netrc):
101111
self.macros[entryname] = []
102112
while 1:
103113
line = lexer.instream.readline()
104-
lexer.lineno += 1
105114
if not line:
106115
raise NetrcParseError(
107116
"Macro definition missing null line terminator.",
@@ -118,18 +127,20 @@ def _parse(self, file, fp, default_netrc):
118127
"bad toplevel token %r" % tt, file, lexer.lineno)
119128

120129
if not entryname:
121-
raise NetrcParseError(
122-
"missing %r name" % tt, file, lexer.lineno)
130+
raise NetrcParseError("missing %r name" % tt, file, lexer.lineno)
123131

124132
# We're looking at start of an entry for a named machine or default.
125133
login = account = password = ''
126134
self.hosts[entryname] = {}
127135
while 1:
136+
# Trailing blank lines would break the checks that determine if the token
137+
# is the last one on its line.
138+
lexer.skip_blank_lines()
139+
prev_lineno = lexer.lineno
128140
tt = lexer.get_token()
129141
if tt.startswith('#'):
130-
if not lexer.dontskip:
142+
if lexer.lineno == prev_lineno:
131143
lexer.instream.readline()
132-
lexer.lineno += 1
133144
continue
134145
if tt in {'', 'machine', 'default', 'macdef'}:
135146
self.hosts[entryname] = (login, account, password)
@@ -170,7 +181,12 @@ def _security_check(self, fp, default_netrc, login):
170181

171182
def authenticators(self, host):
172183
"""Return a (user, account, password) tuple for given host."""
173-
return self.hosts.get(host, self.hosts.get('default'))
184+
if host in self.hosts:
185+
return self.hosts[host]
186+
elif 'default' in self.hosts:
187+
return self.hosts['default']
188+
else:
189+
return None
174190

175191
def __repr__(self):
176192
"""Dump the class data in the format of a .netrc file."""
@@ -187,3 +203,6 @@ def __repr__(self):
187203
rep += line
188204
rep += "\n"
189205
return rep
206+
207+
if __name__ == '__main__':
208+
print(netrc())

Lib/test/test_netrc.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,13 +259,24 @@ def test_comment_after_machine_line_hash_only(self):
259259
#
260260
""")
261261

262-
def test_comment_at_first_line(self):
262+
def test_comment_at_first_line_trailing_new_line(self):
263263
self._test_comment("""
264264
# TEST
265265
machine foo.domain.com login bar password pass
266266
machine bar.domain.com login foo password pass
267267
""")
268268

269+
def test_comment_multiple_trailing_new_lines(self):
270+
self._test_comment("""
271+
# TEST
272+
machine foo.domain.com login bar password pass
273+
274+
275+
#FTP
276+
277+
machine bar.domain.com login foo password pass
278+
""")
279+
269280
def test_comment_at_end_of_machine_line(self):
270281
self._test_comment("""\
271282
machine foo.domain.com login bar password pass # comment

0 commit comments

Comments
 (0)