1import netrc, os, unittest, sys, textwrap
2from test.support import os_helper, run_unittest
3
4try:
5    import pwd
6except ImportError:
7    pwd = None
8
9temp_filename = os_helper.TESTFN
10
11class NetrcTestCase(unittest.TestCase):
12
13    def make_nrc(self, test_data):
14        test_data = textwrap.dedent(test_data)
15        mode = 'w'
16        if sys.platform != 'cygwin':
17            mode += 't'
18        with open(temp_filename, mode, encoding="utf-8") as fp:
19            fp.write(test_data)
20        try:
21            nrc = netrc.netrc(temp_filename)
22        finally:
23            os.unlink(temp_filename)
24        return nrc
25
26    def test_toplevel_non_ordered_tokens(self):
27        nrc = self.make_nrc("""\
28            machine host.domain.com password pass1 login log1 account acct1
29            default login log2 password pass2 account acct2
30            """)
31        self.assertEqual(nrc.hosts['host.domain.com'], ('log1', 'acct1', 'pass1'))
32        self.assertEqual(nrc.hosts['default'], ('log2', 'acct2', 'pass2'))
33
34    def test_toplevel_tokens(self):
35        nrc = self.make_nrc("""\
36            machine host.domain.com login log1 password pass1 account acct1
37            default login log2 password pass2 account acct2
38            """)
39        self.assertEqual(nrc.hosts['host.domain.com'], ('log1', 'acct1', 'pass1'))
40        self.assertEqual(nrc.hosts['default'], ('log2', 'acct2', 'pass2'))
41
42    def test_macros(self):
43        data = """\
44            macdef macro1
45            line1
46            line2
47
48            macdef macro2
49            line3
50            line4
51
52        """
53        nrc = self.make_nrc(data)
54        self.assertEqual(nrc.macros, {'macro1': ['line1\n', 'line2\n'],
55                                      'macro2': ['line3\n', 'line4\n']})
56        # strip the last \n
57        self.assertRaises(netrc.NetrcParseError, self.make_nrc,
58                          data.rstrip(' ')[:-1])
59
60    def test_optional_tokens(self):
61        data = (
62            "machine host.domain.com",
63            "machine host.domain.com login",
64            "machine host.domain.com account",
65            "machine host.domain.com password",
66            "machine host.domain.com login \"\" account",
67            "machine host.domain.com login \"\" password",
68            "machine host.domain.com account \"\" password"
69        )
70        for item in data:
71            nrc = self.make_nrc(item)
72            self.assertEqual(nrc.hosts['host.domain.com'], ('', '', ''))
73        data = (
74            "default",
75            "default login",
76            "default account",
77            "default password",
78            "default login \"\" account",
79            "default login \"\" password",
80            "default account \"\" password"
81        )
82        for item in data:
83            nrc = self.make_nrc(item)
84            self.assertEqual(nrc.hosts['default'], ('', '', ''))
85
86    def test_invalid_tokens(self):
87        data = (
88            "invalid host.domain.com",
89            "machine host.domain.com invalid",
90            "machine host.domain.com login log password pass account acct invalid",
91            "default host.domain.com invalid",
92            "default host.domain.com login log password pass account acct invalid"
93        )
94        for item in data:
95            self.assertRaises(netrc.NetrcParseError, self.make_nrc, item)
96
97    def _test_token_x(self, nrc, token, value):
98        nrc = self.make_nrc(nrc)
99        if token == 'login':
100            self.assertEqual(nrc.hosts['host.domain.com'], (value, 'acct', 'pass'))
101        elif token == 'account':
102            self.assertEqual(nrc.hosts['host.domain.com'], ('log', value, 'pass'))
103        elif token == 'password':
104            self.assertEqual(nrc.hosts['host.domain.com'], ('log', 'acct', value))
105
106    def test_token_value_quotes(self):
107        self._test_token_x("""\
108            machine host.domain.com login "log" password pass account acct
109            """, 'login', 'log')
110        self._test_token_x("""\
111            machine host.domain.com login log password pass account "acct"
112            """, 'account', 'acct')
113        self._test_token_x("""\
114            machine host.domain.com login log password "pass" account acct
115            """, 'password', 'pass')
116
117    def test_token_value_escape(self):
118        self._test_token_x("""\
119            machine host.domain.com login \\"log password pass account acct
120            """, 'login', '"log')
121        self._test_token_x("""\
122            machine host.domain.com login "\\"log" password pass account acct
123            """, 'login', '"log')
124        self._test_token_x("""\
125            machine host.domain.com login log password pass account \\"acct
126            """, 'account', '"acct')
127        self._test_token_x("""\
128            machine host.domain.com login log password pass account "\\"acct"
129            """, 'account', '"acct')
130        self._test_token_x("""\
131            machine host.domain.com login log password \\"pass account acct
132            """, 'password', '"pass')
133        self._test_token_x("""\
134            machine host.domain.com login log password "\\"pass" account acct
135            """, 'password', '"pass')
136
137    def test_token_value_whitespace(self):
138        self._test_token_x("""\
139            machine host.domain.com login "lo g" password pass account acct
140            """, 'login', 'lo g')
141        self._test_token_x("""\
142            machine host.domain.com login log password "pas s" account acct
143            """, 'password', 'pas s')
144        self._test_token_x("""\
145            machine host.domain.com login log password pass account "acc t"
146            """, 'account', 'acc t')
147
148    def test_token_value_non_ascii(self):
149        self._test_token_x("""\
150            machine host.domain.com login \xa1\xa2 password pass account acct
151            """, 'login', '\xa1\xa2')
152        self._test_token_x("""\
153            machine host.domain.com login log password pass account \xa1\xa2
154            """, 'account', '\xa1\xa2')
155        self._test_token_x("""\
156            machine host.domain.com login log password \xa1\xa2 account acct
157            """, 'password', '\xa1\xa2')
158
159    def test_token_value_leading_hash(self):
160        self._test_token_x("""\
161            machine host.domain.com login #log password pass account acct
162            """, 'login', '#log')
163        self._test_token_x("""\
164            machine host.domain.com login log password pass account #acct
165            """, 'account', '#acct')
166        self._test_token_x("""\
167            machine host.domain.com login log password #pass account acct
168            """, 'password', '#pass')
169
170    def test_token_value_trailing_hash(self):
171        self._test_token_x("""\
172            machine host.domain.com login log# password pass account acct
173            """, 'login', 'log#')
174        self._test_token_x("""\
175            machine host.domain.com login log password pass account acct#
176            """, 'account', 'acct#')
177        self._test_token_x("""\
178            machine host.domain.com login log password pass# account acct
179            """, 'password', 'pass#')
180
181    def test_token_value_internal_hash(self):
182        self._test_token_x("""\
183            machine host.domain.com login lo#g password pass account acct
184            """, 'login', 'lo#g')
185        self._test_token_x("""\
186            machine host.domain.com login log password pass account ac#ct
187            """, 'account', 'ac#ct')
188        self._test_token_x("""\
189            machine host.domain.com login log password pa#ss account acct
190            """, 'password', 'pa#ss')
191
192    def _test_comment(self, nrc, passwd='pass'):
193        nrc = self.make_nrc(nrc)
194        self.assertEqual(nrc.hosts['foo.domain.com'], ('bar', '', passwd))
195        self.assertEqual(nrc.hosts['bar.domain.com'], ('foo', '', 'pass'))
196
197    def test_comment_before_machine_line(self):
198        self._test_comment("""\
199            # comment
200            machine foo.domain.com login bar password pass
201            machine bar.domain.com login foo password pass
202            """)
203
204    def test_comment_before_machine_line_no_space(self):
205        self._test_comment("""\
206            #comment
207            machine foo.domain.com login bar password pass
208            machine bar.domain.com login foo password pass
209            """)
210
211    def test_comment_before_machine_line_hash_only(self):
212        self._test_comment("""\
213            #
214            machine foo.domain.com login bar password pass
215            machine bar.domain.com login foo password pass
216            """)
217
218    def test_comment_after_machine_line(self):
219        self._test_comment("""\
220            machine foo.domain.com login bar password pass
221            # comment
222            machine bar.domain.com login foo password pass
223            """)
224        self._test_comment("""\
225            machine foo.domain.com login bar password pass
226            machine bar.domain.com login foo password pass
227            # comment
228            """)
229
230    def test_comment_after_machine_line_no_space(self):
231        self._test_comment("""\
232            machine foo.domain.com login bar password pass
233            #comment
234            machine bar.domain.com login foo password pass
235            """)
236        self._test_comment("""\
237            machine foo.domain.com login bar password pass
238            machine bar.domain.com login foo password pass
239            #comment
240            """)
241
242    def test_comment_after_machine_line_hash_only(self):
243        self._test_comment("""\
244            machine foo.domain.com login bar password pass
245            #
246            machine bar.domain.com login foo password pass
247            """)
248        self._test_comment("""\
249            machine foo.domain.com login bar password pass
250            machine bar.domain.com login foo password pass
251            #
252            """)
253
254    def test_comment_at_end_of_machine_line(self):
255        self._test_comment("""\
256            machine foo.domain.com login bar password pass # comment
257            machine bar.domain.com login foo password pass
258            """)
259
260    def test_comment_at_end_of_machine_line_no_space(self):
261        self._test_comment("""\
262            machine foo.domain.com login bar password pass #comment
263            machine bar.domain.com login foo password pass
264            """)
265
266    def test_comment_at_end_of_machine_line_pass_has_hash(self):
267        self._test_comment("""\
268            machine foo.domain.com login bar password #pass #comment
269            machine bar.domain.com login foo password pass
270            """, '#pass')
271
272
273    @unittest.skipUnless(os.name == 'posix', 'POSIX only test')
274    @unittest.skipIf(pwd is None, 'security check requires pwd module')
275    @os_helper.skip_unless_working_chmod
276    def test_security(self):
277        # This test is incomplete since we are normally not run as root and
278        # therefore can't test the file ownership being wrong.
279        d = os_helper.TESTFN
280        os.mkdir(d)
281        self.addCleanup(os_helper.rmtree, d)
282        fn = os.path.join(d, '.netrc')
283        with open(fn, 'wt') as f:
284            f.write("""\
285                machine foo.domain.com login bar password pass
286                default login foo password pass
287                """)
288        with os_helper.EnvironmentVarGuard() as environ:
289            environ.set('HOME', d)
290            os.chmod(fn, 0o600)
291            nrc = netrc.netrc()
292            self.assertEqual(nrc.hosts['foo.domain.com'],
293                             ('bar', '', 'pass'))
294            os.chmod(fn, 0o622)
295            self.assertRaises(netrc.NetrcParseError, netrc.netrc)
296        with open(fn, 'wt') as f:
297            f.write("""\
298                machine foo.domain.com login anonymous password pass
299                default login foo password pass
300                """)
301        with os_helper.EnvironmentVarGuard() as environ:
302            environ.set('HOME', d)
303            os.chmod(fn, 0o600)
304            nrc = netrc.netrc()
305            self.assertEqual(nrc.hosts['foo.domain.com'],
306                             ('anonymous', '', 'pass'))
307            os.chmod(fn, 0o622)
308            self.assertEqual(nrc.hosts['foo.domain.com'],
309                             ('anonymous', '', 'pass'))
310
311def test_main():
312    run_unittest(NetrcTestCase)
313
314if __name__ == "__main__":
315    test_main()
316