summaryrefslogtreecommitdiffstats
path: root/Lib/netrc.py
diff options
context:
space:
mode:
authorEmmanuel Arias <eamanu@yaerobi.com>2021-11-17 09:07:54 (GMT)
committerGitHub <noreply@github.com>2021-11-17 09:07:54 (GMT)
commit15409c720be0503131713e3d3abc1acd0da07378 (patch)
tree537fdc71b286093d8849dc0a7b685b5461016cad /Lib/netrc.py
parentda20d7401de97b425897d3069f71f77b039eb16f (diff)
downloadcpython-15409c720be0503131713e3d3abc1acd0da07378.zip
cpython-15409c720be0503131713e3d3abc1acd0da07378.tar.gz
cpython-15409c720be0503131713e3d3abc1acd0da07378.tar.bz2
bpo-28806: Continue work: improve the netrc library (GH-26330)
Continue with the improvement of the library netrc Original work and report Xiang Zhang <angwerzx@126.com> * 📜🤖 Added by blurb_it. Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com>
Diffstat (limited to 'Lib/netrc.py')
-rw-r--r--Lib/netrc.py131
1 files changed, 90 insertions, 41 deletions
diff --git a/Lib/netrc.py b/Lib/netrc.py
index 734d94c..c1358aa 100644
--- a/Lib/netrc.py
+++ b/Lib/netrc.py
@@ -19,6 +19,50 @@ class NetrcParseError(Exception):
return "%s (%s, line %s)" % (self.msg, self.filename, self.lineno)
+class _netrclex:
+ def __init__(self, fp):
+ self.lineno = 1
+ self.instream = fp
+ self.whitespace = "\n\t\r "
+ self.pushback = []
+
+ def _read_char(self):
+ ch = self.instream.read(1)
+ if ch == "\n":
+ self.lineno += 1
+ return ch
+
+ def get_token(self):
+ if self.pushback:
+ return self.pushback.pop(0)
+ token = ""
+ fiter = iter(self._read_char, "")
+ for ch in fiter:
+ if ch in self.whitespace:
+ continue
+ if ch == '"':
+ for ch in fiter:
+ if ch == '"':
+ return token
+ elif ch == "\\":
+ ch = self._read_char()
+ token += ch
+ else:
+ if ch == "\\":
+ ch = self._read_char()
+ token += ch
+ for ch in fiter:
+ if ch in self.whitespace:
+ return token
+ elif ch == "\\":
+ ch = self._read_char()
+ token += ch
+ return token
+
+ def push_token(self, token):
+ self.pushback.append(token)
+
+
class netrc:
def __init__(self, file=None):
default_netrc = file is None
@@ -34,9 +78,7 @@ class netrc:
self._parse(file, fp, default_netrc)
def _parse(self, file, fp, default_netrc):
- lexer = shlex.shlex(fp)
- lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"""
- lexer.commenters = lexer.commenters.replace('#', '')
+ lexer = _netrclex(fp)
while 1:
# Look for a machine, default, or macdef top-level keyword
saved_lineno = lexer.lineno
@@ -51,14 +93,19 @@ class netrc:
entryname = lexer.get_token()
elif tt == 'default':
entryname = 'default'
- elif tt == 'macdef': # Just skip to end of macdefs
+ elif tt == 'macdef':
entryname = lexer.get_token()
self.macros[entryname] = []
- lexer.whitespace = ' \t'
while 1:
line = lexer.instream.readline()
- if not line or line == '\012':
- lexer.whitespace = ' \t\r\n'
+ if not line:
+ raise NetrcParseError(
+ "Macro definition missing null line terminator.",
+ file, lexer.lineno)
+ if line == '\n':
+ # a macro definition finished with consecutive new-line
+ # characters. The first \n is encountered by the
+ # readline() method and this is the second \n.
break
self.macros[entryname].append(line)
continue
@@ -66,53 +113,55 @@ class netrc:
raise NetrcParseError(
"bad toplevel token %r" % tt, file, lexer.lineno)
+ if not entryname:
+ raise NetrcParseError("missing %r name" % tt, file, lexer.lineno)
+
# We're looking at start of an entry for a named machine or default.
- login = ''
- account = password = None
+ login = account = password = ''
self.hosts[entryname] = {}
while 1:
+ prev_lineno = lexer.lineno
tt = lexer.get_token()
- if (tt.startswith('#') or
- tt in {'', 'machine', 'default', 'macdef'}):
- if password:
- self.hosts[entryname] = (login, account, password)
- lexer.push_token(tt)
- break
- else:
- raise NetrcParseError(
- "malformed %s entry %s terminated by %s"
- % (toplevel, entryname, repr(tt)),
- file, lexer.lineno)
+ if tt.startswith('#'):
+ if lexer.lineno == prev_lineno:
+ lexer.instream.readline()
+ continue
+ if 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()
elif tt == 'account':
account = lexer.get_token()
elif tt == 'password':
- if os.name == 'posix' and default_netrc:
- prop = os.fstat(fp.fileno())
- if prop.st_uid != os.getuid():
- import pwd
- try:
- fowner = pwd.getpwuid(prop.st_uid)[0]
- except KeyError:
- fowner = 'uid %s' % prop.st_uid
- try:
- user = pwd.getpwuid(os.getuid())[0]
- except KeyError:
- user = 'uid %s' % os.getuid()
- raise NetrcParseError(
- ("~/.netrc file owner (%s) does not match"
- " current user (%s)") % (fowner, user),
- file, lexer.lineno)
- if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
- raise NetrcParseError(
- "~/.netrc access too permissive: access"
- " permissions must restrict access to only"
- " the owner", file, lexer.lineno)
password = lexer.get_token()
else:
raise NetrcParseError("bad follower token %r" % tt,
file, lexer.lineno)
+ self._security_check(fp, default_netrc, self.hosts[entryname][0])
+
+ def _security_check(self, fp, default_netrc, login):
+ if os.name == 'posix' and default_netrc and login != "anonymous":
+ prop = os.fstat(fp.fileno())
+ if prop.st_uid != os.getuid():
+ import pwd
+ try:
+ fowner = pwd.getpwuid(prop.st_uid)[0]
+ except KeyError:
+ fowner = 'uid %s' % prop.st_uid
+ try:
+ user = pwd.getpwuid(os.getuid())[0]
+ except KeyError:
+ user = 'uid %s' % os.getuid()
+ raise NetrcParseError(
+ (f"~/.netrc file owner ({fowner}, {user}) does not match"
+ " current user"))
+ if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
+ raise NetrcParseError(
+ "~/.netrc access too permissive: access"
+ " permissions must restrict access to only"
+ " the owner")
def authenticators(self, host):
"""Return a (user, account, password) tuple for given host."""