xref: /aosp_15_r20/prebuilts/build-tools/common/py3-stdlib/netrc.py (revision cda5da8d549138a6648c5ee6d7a49cf8f4a657be)
1*cda5da8dSAndroid Build Coastguard Worker"""An object-oriented interface to .netrc files."""
2*cda5da8dSAndroid Build Coastguard Worker
3*cda5da8dSAndroid Build Coastguard Worker# Module and documentation by Eric S. Raymond, 21 Dec 1998
4*cda5da8dSAndroid Build Coastguard Worker
5*cda5da8dSAndroid Build Coastguard Workerimport os, shlex, stat
6*cda5da8dSAndroid Build Coastguard Worker
7*cda5da8dSAndroid Build Coastguard Worker__all__ = ["netrc", "NetrcParseError"]
8*cda5da8dSAndroid Build Coastguard Worker
9*cda5da8dSAndroid Build Coastguard Worker
10*cda5da8dSAndroid Build Coastguard Workerclass NetrcParseError(Exception):
11*cda5da8dSAndroid Build Coastguard Worker    """Exception raised on syntax errors in the .netrc file."""
12*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, msg, filename=None, lineno=None):
13*cda5da8dSAndroid Build Coastguard Worker        self.filename = filename
14*cda5da8dSAndroid Build Coastguard Worker        self.lineno = lineno
15*cda5da8dSAndroid Build Coastguard Worker        self.msg = msg
16*cda5da8dSAndroid Build Coastguard Worker        Exception.__init__(self, msg)
17*cda5da8dSAndroid Build Coastguard Worker
18*cda5da8dSAndroid Build Coastguard Worker    def __str__(self):
19*cda5da8dSAndroid Build Coastguard Worker        return "%s (%s, line %s)" % (self.msg, self.filename, self.lineno)
20*cda5da8dSAndroid Build Coastguard Worker
21*cda5da8dSAndroid Build Coastguard Worker
22*cda5da8dSAndroid Build Coastguard Workerclass _netrclex:
23*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, fp):
24*cda5da8dSAndroid Build Coastguard Worker        self.lineno = 1
25*cda5da8dSAndroid Build Coastguard Worker        self.instream = fp
26*cda5da8dSAndroid Build Coastguard Worker        self.whitespace = "\n\t\r "
27*cda5da8dSAndroid Build Coastguard Worker        self.pushback = []
28*cda5da8dSAndroid Build Coastguard Worker
29*cda5da8dSAndroid Build Coastguard Worker    def _read_char(self):
30*cda5da8dSAndroid Build Coastguard Worker        ch = self.instream.read(1)
31*cda5da8dSAndroid Build Coastguard Worker        if ch == "\n":
32*cda5da8dSAndroid Build Coastguard Worker            self.lineno += 1
33*cda5da8dSAndroid Build Coastguard Worker        return ch
34*cda5da8dSAndroid Build Coastguard Worker
35*cda5da8dSAndroid Build Coastguard Worker    def get_token(self):
36*cda5da8dSAndroid Build Coastguard Worker        if self.pushback:
37*cda5da8dSAndroid Build Coastguard Worker            return self.pushback.pop(0)
38*cda5da8dSAndroid Build Coastguard Worker        token = ""
39*cda5da8dSAndroid Build Coastguard Worker        fiter = iter(self._read_char, "")
40*cda5da8dSAndroid Build Coastguard Worker        for ch in fiter:
41*cda5da8dSAndroid Build Coastguard Worker            if ch in self.whitespace:
42*cda5da8dSAndroid Build Coastguard Worker                continue
43*cda5da8dSAndroid Build Coastguard Worker            if ch == '"':
44*cda5da8dSAndroid Build Coastguard Worker                for ch in fiter:
45*cda5da8dSAndroid Build Coastguard Worker                    if ch == '"':
46*cda5da8dSAndroid Build Coastguard Worker                        return token
47*cda5da8dSAndroid Build Coastguard Worker                    elif ch == "\\":
48*cda5da8dSAndroid Build Coastguard Worker                        ch = self._read_char()
49*cda5da8dSAndroid Build Coastguard Worker                    token += ch
50*cda5da8dSAndroid Build Coastguard Worker            else:
51*cda5da8dSAndroid Build Coastguard Worker                if ch == "\\":
52*cda5da8dSAndroid Build Coastguard Worker                    ch = self._read_char()
53*cda5da8dSAndroid Build Coastguard Worker                token += ch
54*cda5da8dSAndroid Build Coastguard Worker                for ch in fiter:
55*cda5da8dSAndroid Build Coastguard Worker                    if ch in self.whitespace:
56*cda5da8dSAndroid Build Coastguard Worker                        return token
57*cda5da8dSAndroid Build Coastguard Worker                    elif ch == "\\":
58*cda5da8dSAndroid Build Coastguard Worker                        ch = self._read_char()
59*cda5da8dSAndroid Build Coastguard Worker                    token += ch
60*cda5da8dSAndroid Build Coastguard Worker        return token
61*cda5da8dSAndroid Build Coastguard Worker
62*cda5da8dSAndroid Build Coastguard Worker    def push_token(self, token):
63*cda5da8dSAndroid Build Coastguard Worker        self.pushback.append(token)
64*cda5da8dSAndroid Build Coastguard Worker
65*cda5da8dSAndroid Build Coastguard Worker
66*cda5da8dSAndroid Build Coastguard Workerclass netrc:
67*cda5da8dSAndroid Build Coastguard Worker    def __init__(self, file=None):
68*cda5da8dSAndroid Build Coastguard Worker        default_netrc = file is None
69*cda5da8dSAndroid Build Coastguard Worker        if file is None:
70*cda5da8dSAndroid Build Coastguard Worker            file = os.path.join(os.path.expanduser("~"), ".netrc")
71*cda5da8dSAndroid Build Coastguard Worker        self.hosts = {}
72*cda5da8dSAndroid Build Coastguard Worker        self.macros = {}
73*cda5da8dSAndroid Build Coastguard Worker        try:
74*cda5da8dSAndroid Build Coastguard Worker            with open(file, encoding="utf-8") as fp:
75*cda5da8dSAndroid Build Coastguard Worker                self._parse(file, fp, default_netrc)
76*cda5da8dSAndroid Build Coastguard Worker        except UnicodeDecodeError:
77*cda5da8dSAndroid Build Coastguard Worker            with open(file, encoding="locale") as fp:
78*cda5da8dSAndroid Build Coastguard Worker                self._parse(file, fp, default_netrc)
79*cda5da8dSAndroid Build Coastguard Worker
80*cda5da8dSAndroid Build Coastguard Worker    def _parse(self, file, fp, default_netrc):
81*cda5da8dSAndroid Build Coastguard Worker        lexer = _netrclex(fp)
82*cda5da8dSAndroid Build Coastguard Worker        while 1:
83*cda5da8dSAndroid Build Coastguard Worker            # Look for a machine, default, or macdef top-level keyword
84*cda5da8dSAndroid Build Coastguard Worker            saved_lineno = lexer.lineno
85*cda5da8dSAndroid Build Coastguard Worker            toplevel = tt = lexer.get_token()
86*cda5da8dSAndroid Build Coastguard Worker            if not tt:
87*cda5da8dSAndroid Build Coastguard Worker                break
88*cda5da8dSAndroid Build Coastguard Worker            elif tt[0] == '#':
89*cda5da8dSAndroid Build Coastguard Worker                if lexer.lineno == saved_lineno and len(tt) == 1:
90*cda5da8dSAndroid Build Coastguard Worker                    lexer.instream.readline()
91*cda5da8dSAndroid Build Coastguard Worker                continue
92*cda5da8dSAndroid Build Coastguard Worker            elif tt == 'machine':
93*cda5da8dSAndroid Build Coastguard Worker                entryname = lexer.get_token()
94*cda5da8dSAndroid Build Coastguard Worker            elif tt == 'default':
95*cda5da8dSAndroid Build Coastguard Worker                entryname = 'default'
96*cda5da8dSAndroid Build Coastguard Worker            elif tt == 'macdef':
97*cda5da8dSAndroid Build Coastguard Worker                entryname = lexer.get_token()
98*cda5da8dSAndroid Build Coastguard Worker                self.macros[entryname] = []
99*cda5da8dSAndroid Build Coastguard Worker                while 1:
100*cda5da8dSAndroid Build Coastguard Worker                    line = lexer.instream.readline()
101*cda5da8dSAndroid Build Coastguard Worker                    if not line:
102*cda5da8dSAndroid Build Coastguard Worker                        raise NetrcParseError(
103*cda5da8dSAndroid Build Coastguard Worker                            "Macro definition missing null line terminator.",
104*cda5da8dSAndroid Build Coastguard Worker                            file, lexer.lineno)
105*cda5da8dSAndroid Build Coastguard Worker                    if line == '\n':
106*cda5da8dSAndroid Build Coastguard Worker                        # a macro definition finished with consecutive new-line
107*cda5da8dSAndroid Build Coastguard Worker                        # characters. The first \n is encountered by the
108*cda5da8dSAndroid Build Coastguard Worker                        # readline() method and this is the second \n.
109*cda5da8dSAndroid Build Coastguard Worker                        break
110*cda5da8dSAndroid Build Coastguard Worker                    self.macros[entryname].append(line)
111*cda5da8dSAndroid Build Coastguard Worker                continue
112*cda5da8dSAndroid Build Coastguard Worker            else:
113*cda5da8dSAndroid Build Coastguard Worker                raise NetrcParseError(
114*cda5da8dSAndroid Build Coastguard Worker                    "bad toplevel token %r" % tt, file, lexer.lineno)
115*cda5da8dSAndroid Build Coastguard Worker
116*cda5da8dSAndroid Build Coastguard Worker            if not entryname:
117*cda5da8dSAndroid Build Coastguard Worker                raise NetrcParseError("missing %r name" % tt, file, lexer.lineno)
118*cda5da8dSAndroid Build Coastguard Worker
119*cda5da8dSAndroid Build Coastguard Worker            # We're looking at start of an entry for a named machine or default.
120*cda5da8dSAndroid Build Coastguard Worker            login = account = password = ''
121*cda5da8dSAndroid Build Coastguard Worker            self.hosts[entryname] = {}
122*cda5da8dSAndroid Build Coastguard Worker            while 1:
123*cda5da8dSAndroid Build Coastguard Worker                prev_lineno = lexer.lineno
124*cda5da8dSAndroid Build Coastguard Worker                tt = lexer.get_token()
125*cda5da8dSAndroid Build Coastguard Worker                if tt.startswith('#'):
126*cda5da8dSAndroid Build Coastguard Worker                    if lexer.lineno == prev_lineno:
127*cda5da8dSAndroid Build Coastguard Worker                        lexer.instream.readline()
128*cda5da8dSAndroid Build Coastguard Worker                    continue
129*cda5da8dSAndroid Build Coastguard Worker                if tt in {'', 'machine', 'default', 'macdef'}:
130*cda5da8dSAndroid Build Coastguard Worker                    self.hosts[entryname] = (login, account, password)
131*cda5da8dSAndroid Build Coastguard Worker                    lexer.push_token(tt)
132*cda5da8dSAndroid Build Coastguard Worker                    break
133*cda5da8dSAndroid Build Coastguard Worker                elif tt == 'login' or tt == 'user':
134*cda5da8dSAndroid Build Coastguard Worker                    login = lexer.get_token()
135*cda5da8dSAndroid Build Coastguard Worker                elif tt == 'account':
136*cda5da8dSAndroid Build Coastguard Worker                    account = lexer.get_token()
137*cda5da8dSAndroid Build Coastguard Worker                elif tt == 'password':
138*cda5da8dSAndroid Build Coastguard Worker                    password = lexer.get_token()
139*cda5da8dSAndroid Build Coastguard Worker                else:
140*cda5da8dSAndroid Build Coastguard Worker                    raise NetrcParseError("bad follower token %r" % tt,
141*cda5da8dSAndroid Build Coastguard Worker                                          file, lexer.lineno)
142*cda5da8dSAndroid Build Coastguard Worker            self._security_check(fp, default_netrc, self.hosts[entryname][0])
143*cda5da8dSAndroid Build Coastguard Worker
144*cda5da8dSAndroid Build Coastguard Worker    def _security_check(self, fp, default_netrc, login):
145*cda5da8dSAndroid Build Coastguard Worker        if os.name == 'posix' and default_netrc and login != "anonymous":
146*cda5da8dSAndroid Build Coastguard Worker            prop = os.fstat(fp.fileno())
147*cda5da8dSAndroid Build Coastguard Worker            if prop.st_uid != os.getuid():
148*cda5da8dSAndroid Build Coastguard Worker                import pwd
149*cda5da8dSAndroid Build Coastguard Worker                try:
150*cda5da8dSAndroid Build Coastguard Worker                    fowner = pwd.getpwuid(prop.st_uid)[0]
151*cda5da8dSAndroid Build Coastguard Worker                except KeyError:
152*cda5da8dSAndroid Build Coastguard Worker                    fowner = 'uid %s' % prop.st_uid
153*cda5da8dSAndroid Build Coastguard Worker                try:
154*cda5da8dSAndroid Build Coastguard Worker                    user = pwd.getpwuid(os.getuid())[0]
155*cda5da8dSAndroid Build Coastguard Worker                except KeyError:
156*cda5da8dSAndroid Build Coastguard Worker                    user = 'uid %s' % os.getuid()
157*cda5da8dSAndroid Build Coastguard Worker                raise NetrcParseError(
158*cda5da8dSAndroid Build Coastguard Worker                    (f"~/.netrc file owner ({fowner}, {user}) does not match"
159*cda5da8dSAndroid Build Coastguard Worker                     " current user"))
160*cda5da8dSAndroid Build Coastguard Worker            if (prop.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
161*cda5da8dSAndroid Build Coastguard Worker                raise NetrcParseError(
162*cda5da8dSAndroid Build Coastguard Worker                    "~/.netrc access too permissive: access"
163*cda5da8dSAndroid Build Coastguard Worker                    " permissions must restrict access to only"
164*cda5da8dSAndroid Build Coastguard Worker                    " the owner")
165*cda5da8dSAndroid Build Coastguard Worker
166*cda5da8dSAndroid Build Coastguard Worker    def authenticators(self, host):
167*cda5da8dSAndroid Build Coastguard Worker        """Return a (user, account, password) tuple for given host."""
168*cda5da8dSAndroid Build Coastguard Worker        if host in self.hosts:
169*cda5da8dSAndroid Build Coastguard Worker            return self.hosts[host]
170*cda5da8dSAndroid Build Coastguard Worker        elif 'default' in self.hosts:
171*cda5da8dSAndroid Build Coastguard Worker            return self.hosts['default']
172*cda5da8dSAndroid Build Coastguard Worker        else:
173*cda5da8dSAndroid Build Coastguard Worker            return None
174*cda5da8dSAndroid Build Coastguard Worker
175*cda5da8dSAndroid Build Coastguard Worker    def __repr__(self):
176*cda5da8dSAndroid Build Coastguard Worker        """Dump the class data in the format of a .netrc file."""
177*cda5da8dSAndroid Build Coastguard Worker        rep = ""
178*cda5da8dSAndroid Build Coastguard Worker        for host in self.hosts.keys():
179*cda5da8dSAndroid Build Coastguard Worker            attrs = self.hosts[host]
180*cda5da8dSAndroid Build Coastguard Worker            rep += f"machine {host}\n\tlogin {attrs[0]}\n"
181*cda5da8dSAndroid Build Coastguard Worker            if attrs[1]:
182*cda5da8dSAndroid Build Coastguard Worker                rep += f"\taccount {attrs[1]}\n"
183*cda5da8dSAndroid Build Coastguard Worker            rep += f"\tpassword {attrs[2]}\n"
184*cda5da8dSAndroid Build Coastguard Worker        for macro in self.macros.keys():
185*cda5da8dSAndroid Build Coastguard Worker            rep += f"macdef {macro}\n"
186*cda5da8dSAndroid Build Coastguard Worker            for line in self.macros[macro]:
187*cda5da8dSAndroid Build Coastguard Worker                rep += line
188*cda5da8dSAndroid Build Coastguard Worker            rep += "\n"
189*cda5da8dSAndroid Build Coastguard Worker        return rep
190*cda5da8dSAndroid Build Coastguard Worker
191*cda5da8dSAndroid Build Coastguard Workerif __name__ == '__main__':
192*cda5da8dSAndroid Build Coastguard Worker    print(netrc())
193