| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1d4601c commit 601b3f7
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -18,7 +18,7 @@ the Unix :program:`ftp` program and other FTP clients. | |||
| 18 | 18 | ||
| 19 | 19 | .. class:: netrc([file]) | |
| 20 | 20 | ||
| 21 | - A :class:`~netrc.netrc` instance or subclass instance encapsulates data from a netrc | ||
| 21 | + A :class:`~netrc.netrc` instance or subclass instance encapsulates data from a netrc | ||
| 22 | 22 | file. The initialization argument, if present, specifies the file to parse. If | |
| 23 | 23 | no argument is given, the file :file:`.netrc` in the user's home directory will | |
| 24 | 24 | be read. Parse errors will raise :exc:`NetrcParseError` with diagnostic | |
@@ -32,6 +32,12 @@ the Unix :program:`ftp` program and other FTP clients. | |||
| 32 | 32 | ||
| 33 | 33 | .. versionchanged:: 3.4 Added the POSIX permission check. | |
| 34 | 34 | ||
| 35 | + .. versionchanged:: 3.7 | ||
| 36 | + The entry in the netrc file no longer needs to contain all tokens. The missing | ||
| 37 | + tokens' value default to an empty string. All the tokens and their values now | ||
| 38 | + can contain arbitrary characters, like whitespace and non-ASCII characters. | ||
| 39 | + If the login name is anonymous, it won't trigger the security check. | ||
| 40 | + | ||
| 35 | 41 | ||
| 36 | 42 | .. exception:: NetrcParseError | |
| 37 | 43 | ||
@@ -75,11 +81,3 @@ Instances of :class:`~netrc.netrc` have public instance variables: | |||
| 75 | 81 | .. attribute:: netrc.macros | |
| 76 | 82 | ||
| 77 | 83 | Dictionary mapping macro names to string lists. | |
| 78 | - | ||
| 79 | - .. note:: | ||
| 80 | - | ||
| 81 | - Passwords are limited to a subset of the ASCII character set. All ASCII | ||
| 82 | - punctuation is allowed in passwords, however, note that whitespace and | ||
| 83 | - non-printable characters are not allowed in passwords. This is a limitation | ||
| 84 | - of the way the .netrc file is parsed and may be removed in the future. | ||
| 85 | - | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -19,6 +19,51 @@ def __str__(self): | |||
| 19 | 19 | return "%s (%s, line %s)" % (self.msg, self.filename, self.lineno) | |
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | + class _netrclex: | ||
| 23 | + def __init__(self, fp): | ||
| 24 | + self.lineno = 1 | ||
| 25 | + self.instream = fp | ||
| 26 | + self.whitespace = "\n\t\r " | ||
| 27 | + self._stack = [] | ||
| 28 | + | ||
| 29 | + def _read_char(self): | ||
| 30 | + ch = self.instream.read(1) | ||
| 31 | + if ch == "\n": | ||
| 32 | + self.lineno += 1 | ||
| 33 | + return ch | ||
| 34 | + | ||
| 35 | + def get_token(self): | ||
| 36 | + if self._stack: | ||
| 37 | + return self._stack.pop(0) | ||
| 38 | + token = "" | ||
| 39 | + fiter = iter(self._read_char, "") | ||
| 40 | + for ch in fiter: | ||
| 41 | + if ch in self.whitespace: | ||
| 42 | + continue | ||
| 43 | + if ch == "\"": | ||
| 44 | + for ch in fiter: | ||
| 45 | + if ch != "\"": | ||
| 46 | + if ch == "\\": | ||
| 47 | + ch = self._read_char() | ||
| 48 | + token += ch | ||
| 49 | + continue | ||
| 50 | + return token | ||
| 51 | + else: | ||
| 52 | + if ch == "\\": | ||
| 53 | + ch = self._read_char() | ||
| 54 | + token += ch | ||
| 55 | + for ch in fiter: | ||
| 56 | + if ch not in self.whitespace: | ||
| 57 | + if ch == "\\": | ||
| 58 | + ch = self._read_char() | ||
| 59 | + token += ch | ||
| 60 | + continue | ||
| 61 | + return token | ||
| 62 | + return token | ||
| 63 | + | ||
| 64 | + def push_token(self, token): | ||
| 65 | + self._stack.append(token) | ||
| 66 | + | ||
| 22 | 67 | class netrc: | |
| 23 | 68 | def __init__(self, file=None): | |
| 24 | 69 | default_netrc = file is None | |
@@ -33,61 +78,61 @@ def __init__(self, file=None): | |||
| 33 | 78 | self._parse(file, fp, default_netrc) | |
| 34 | 79 | ||
| 35 | 80 | def _parse(self, file, fp, default_netrc): | |
| 36 | - lexer = shlex.shlex(fp) | ||
| 37 | - lexer.wordchars += r"""!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~""" | ||
| 38 | - lexer.commenters = lexer.commenters.replace('#', '') | ||
| 81 | + lexer = _netrclex(fp) | ||
| 39 | 82 | while 1: | |
| 40 | 83 | # Look for a machine, default, or macdef top-level keyword | |
| 41 | - saved_lineno = lexer.lineno | ||
| 42 | - toplevel = tt = lexer.get_token() | ||
| 84 | + prev_lineno = lexer.lineno | ||
| 85 | + tt = lexer.get_token() | ||
| 43 | 86 | if not tt: | |
| 44 | 87 | break | |
| 45 | 88 | elif tt[0] == '#': | |
| 46 | - if lexer.lineno == saved_lineno and len(tt) == 1: | ||
| 89 | + if prev_lineno == lexer.lineno: | ||
| 47 | 90 | lexer.instream.readline() | |
| 48 | 91 | continue | |
| 49 | 92 | elif tt == 'machine': | |
| 50 | 93 | entryname = lexer.get_token() | |
| 51 | 94 | elif tt == 'default': | |
| 52 | 95 | entryname = 'default' | |
| 53 | - elif tt == 'macdef': # Just skip to end of macdefs | ||
| 96 | + elif tt == 'macdef': | ||
| 54 | 97 | entryname = lexer.get_token() | |
| 55 | 98 | self.macros[entryname] = [] | |
| 56 | - lexer.whitespace = ' \t' | ||
| 57 | 99 | while 1: | |
| 58 | 100 | line = lexer.instream.readline() | |
| 59 | - if not line or line == '\012': | ||
| 60 | - lexer.whitespace = ' \t\r\n' | ||
| 101 | + if not line: | ||
| 102 | + raise NetrcParseError( | ||
| 103 | + "Macro definition missing null line terminator.", | ||
| 104 | + file, lexer.lineno) | ||
| 105 | + if line == '\n': | ||
| 61 | 106 | break | |
| 62 | 107 | self.macros[entryname].append(line) | |
| 63 | 108 | continue | |
| 64 | 109 | else: | |
| 65 | 110 | raise NetrcParseError( | |
| 66 | 111 | "bad toplevel token %r" % tt, file, lexer.lineno) | |
| 67 | 112 | ||
| 113 | + if not entryname: | ||
| 114 | + raise NetrcParseError("missing %r name" % tt, file, lexer.lineno) | ||
| 115 | + | ||
| 68 | 116 | # We're looking at start of an entry for a named machine or default. | |
| 69 | - login = '' | ||
| 70 | - account = password = None | ||
| 117 | + login = account = password = '' | ||
| 71 | 118 | self.hosts[entryname] = {} | |
| 72 | 119 | while 1: | |
| 120 | + prev_lineno = lexer.lineno | ||
| 73 | 121 | tt = lexer.get_token() | |
| 74 | - if (tt.startswith('#') or | ||
| 75 | - tt in {'', 'machine', 'default', 'macdef'}): | ||
| 76 | - if password: | ||
| 77 | - self.hosts[entryname] = (login, account, password) | ||
| 78 | - lexer.push_token(tt) | ||
| 79 | - break | ||
| 80 | - else: | ||
| 81 | - raise NetrcParseError( | ||
| 82 | - "malformed %s entry %s terminated by %s" | ||
| 83 | - % (toplevel, entryname, repr(tt)), | ||
| 84 | - file, lexer.lineno) | ||
| 122 | + if tt.startswith('#'): | ||
| 123 | + if lexer.lineno == prev_lineno: | ||
| 124 | + lexer.instream.readline() | ||
| 125 | + continue | ||
| 126 | + if tt in {'', 'machine', 'default', 'macdef'}: | ||
| 127 | + self.hosts[entryname] = (login, account, password) | ||
| 128 | + lexer.push_token(tt) | ||
| 129 | + break | ||
| 85 | 130 | elif tt == 'login' or tt == 'user': | |
| 86 | 131 | login = lexer.get_token() | |
| 87 | 132 | elif tt == 'account': | |
| 88 | 133 | account = lexer.get_token() | |
| 89 | 134 | elif tt == 'password': | |
| 90 | - if os.name == 'posix' and default_netrc: | ||
| 135 | + if os.name == 'posix' and default_netrc and login != "anonymous": | ||
| 91 | 136 | prop = os.fstat(fp.fileno()) | |
| 92 | 137 | if prop.st_uid != os.getuid(): | |
| 93 | 138 | import pwd | |
@@ -127,10 +172,10 @@ def __repr__(self): | |||
| 127 | 172 | rep = "" | |
| 128 | 173 | for host in self.hosts.keys(): | |
| 129 | 174 | attrs = self.hosts[host] | |
| 130 | - rep = rep + "machine "+ host + "\n\tlogin " + repr(attrs[0]) + "\n" | ||
| 175 | + rep = rep + "machine "+ host + "\n\tlogin " + attrs[0] + "\n" | ||
| 131 | 176 | if attrs[1]: | |
| 132 | - rep = rep + "account " + repr(attrs[1]) | ||
| 133 | - rep = rep + "\tpassword " + repr(attrs[2]) + "\n" | ||
| 177 | + rep = rep + "account " + attrs[1] | ||
| 178 | + rep = rep + "\tpassword " + attrs[2] + "\n" | ||
| 134 | 179 | for macro in self.macros.keys(): | |
| 135 | 180 | rep = rep + "macdef " + macro + "\n" | |
| 136 | 181 | for line in self.macros[macro]: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments