diff options
author | Emmanuel Arias <eamanu@yaerobi.com> | 2021-11-17 09:07:54 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-17 09:07:54 (GMT) |
commit | 15409c720be0503131713e3d3abc1acd0da07378 (patch) | |
tree | 537fdc71b286093d8849dc0a7b685b5461016cad /Lib/netrc.py | |
parent | da20d7401de97b425897d3069f71f77b039eb16f (diff) | |
download | cpython-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.py | 131 |
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.""" |