Show More
@@ -1,250 +1,422 b'' | |||||
|
1 | # coding=utf-8 | |||
1 | from __future__ import absolute_import, print_function |
|
2 | from __future__ import absolute_import, print_function | |
2 |
|
3 | |||
3 | import doctest |
|
4 | import doctest | |
4 | import os |
|
5 | import os | |
5 |
|
6 | |||
6 | def check(a, b): |
|
7 | def check(a, b): | |
7 | if a != b: |
|
8 | if a != b: | |
8 | print((a, b)) |
|
9 | print((a, b)) | |
9 |
|
10 | |||
10 | def cert(cn): |
|
11 | def cert(cn): | |
11 | return {'subject': ((('commonName', cn),),)} |
|
12 | return {'subject': ((('commonName', cn),),)} | |
12 |
|
13 | |||
13 | from mercurial import ( |
|
14 | from mercurial import ( | |
14 | sslutil, |
|
15 | sslutil, | |
15 | ) |
|
16 | ) | |
16 |
|
17 | |||
17 | _verifycert = sslutil._verifycert |
|
18 | _verifycert = sslutil._verifycert | |
18 | # Test non-wildcard certificates |
|
19 | # Test non-wildcard certificates | |
19 | check(_verifycert(cert('example.com'), 'example.com'), |
|
20 | check(_verifycert(cert('example.com'), 'example.com'), | |
20 | None) |
|
21 | None) | |
21 | check(_verifycert(cert('example.com'), 'www.example.com'), |
|
22 | check(_verifycert(cert('example.com'), 'www.example.com'), | |
22 | 'certificate is for example.com') |
|
23 | 'certificate is for example.com') | |
23 | check(_verifycert(cert('www.example.com'), 'example.com'), |
|
24 | check(_verifycert(cert('www.example.com'), 'example.com'), | |
24 | 'certificate is for www.example.com') |
|
25 | 'certificate is for www.example.com') | |
25 |
|
26 | |||
26 | # Test wildcard certificates |
|
27 | # Test wildcard certificates | |
27 | check(_verifycert(cert('*.example.com'), 'www.example.com'), |
|
28 | check(_verifycert(cert('*.example.com'), 'www.example.com'), | |
28 | None) |
|
29 | None) | |
29 | check(_verifycert(cert('*.example.com'), 'example.com'), |
|
30 | check(_verifycert(cert('*.example.com'), 'example.com'), | |
30 | 'certificate is for *.example.com') |
|
31 | 'certificate is for *.example.com') | |
31 | check(_verifycert(cert('*.example.com'), 'w.w.example.com'), |
|
32 | check(_verifycert(cert('*.example.com'), 'w.w.example.com'), | |
32 | 'certificate is for *.example.com') |
|
33 | 'certificate is for *.example.com') | |
33 |
|
34 | |||
34 | # Test subjectAltName |
|
35 | # Test subjectAltName | |
35 | san_cert = {'subject': ((('commonName', 'example.com'),),), |
|
36 | san_cert = {'subject': ((('commonName', 'example.com'),),), | |
36 | 'subjectAltName': (('DNS', '*.example.net'), |
|
37 | 'subjectAltName': (('DNS', '*.example.net'), | |
37 | ('DNS', 'example.net'))} |
|
38 | ('DNS', 'example.net'))} | |
38 | check(_verifycert(san_cert, 'example.net'), |
|
39 | check(_verifycert(san_cert, 'example.net'), | |
39 | None) |
|
40 | None) | |
40 | check(_verifycert(san_cert, 'foo.example.net'), |
|
41 | check(_verifycert(san_cert, 'foo.example.net'), | |
41 | None) |
|
42 | None) | |
42 | # no fallback to subject commonName when subjectAltName has DNS |
|
43 | # no fallback to subject commonName when subjectAltName has DNS | |
43 | check(_verifycert(san_cert, 'example.com'), |
|
44 | check(_verifycert(san_cert, 'example.com'), | |
44 | 'certificate is for *.example.net, example.net') |
|
45 | 'certificate is for *.example.net, example.net') | |
45 | # fallback to subject commonName when no DNS in subjectAltName |
|
46 | # fallback to subject commonName when no DNS in subjectAltName | |
46 | san_cert = {'subject': ((('commonName', 'example.com'),),), |
|
47 | san_cert = {'subject': ((('commonName', 'example.com'),),), | |
47 | 'subjectAltName': (('IP Address', '8.8.8.8'),)} |
|
48 | 'subjectAltName': (('IP Address', '8.8.8.8'),)} | |
48 | check(_verifycert(san_cert, 'example.com'), None) |
|
49 | check(_verifycert(san_cert, 'example.com'), None) | |
49 |
|
50 | |||
50 | # Avoid some pitfalls |
|
51 | # Avoid some pitfalls | |
51 | check(_verifycert(cert('*.foo'), 'foo'), |
|
52 | check(_verifycert(cert('*.foo'), 'foo'), | |
52 | 'certificate is for *.foo') |
|
53 | 'certificate is for *.foo') | |
53 | check(_verifycert(cert('*o'), 'foo'), |
|
54 | check(_verifycert(cert('*o'), 'foo'), | |
54 | 'certificate is for *o') |
|
55 | 'certificate is for *o') | |
55 |
|
56 | |||
56 | check(_verifycert({'subject': ()}, |
|
57 | check(_verifycert({'subject': ()}, | |
57 | 'example.com'), |
|
58 | 'example.com'), | |
58 | 'no commonName or subjectAltName found in certificate') |
|
59 | 'no commonName or subjectAltName found in certificate') | |
59 | check(_verifycert(None, 'example.com'), |
|
60 | check(_verifycert(None, 'example.com'), | |
60 | 'no certificate received') |
|
61 | 'no certificate received') | |
61 |
|
62 | |||
62 | # Unicode (IDN) certname isn't supported |
|
63 | # Unicode (IDN) certname isn't supported | |
63 | check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'), |
|
64 | check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'), | |
64 | 'IDN in certificate not supported') |
|
65 | 'IDN in certificate not supported') | |
65 |
|
66 | |||
|
67 | # The following tests are from CPython's test_ssl.py. | |||
|
68 | check(_verifycert(cert('example.com'), 'example.com'), None) | |||
|
69 | check(_verifycert(cert('example.com'), 'ExAmple.cOm'), None) | |||
|
70 | check(_verifycert(cert('example.com'), 'www.example.com'), | |||
|
71 | 'certificate is for example.com') | |||
|
72 | check(_verifycert(cert('example.com'), '.example.com'), | |||
|
73 | 'certificate is for example.com') | |||
|
74 | check(_verifycert(cert('example.com'), 'example.org'), | |||
|
75 | 'certificate is for example.com') | |||
|
76 | check(_verifycert(cert('example.com'), 'exampleXcom'), | |||
|
77 | 'certificate is for example.com') | |||
|
78 | check(_verifycert(cert('*.a.com'), 'foo.a.com'), None) | |||
|
79 | check(_verifycert(cert('*.a.com'), 'bar.foo.a.com'), | |||
|
80 | 'certificate is for *.a.com') | |||
|
81 | check(_verifycert(cert('*.a.com'), 'a.com'), | |||
|
82 | 'certificate is for *.a.com') | |||
|
83 | check(_verifycert(cert('*.a.com'), 'Xa.com'), | |||
|
84 | 'certificate is for *.a.com') | |||
|
85 | check(_verifycert(cert('*.a.com'), '.a.com'), None) | |||
|
86 | ||||
|
87 | # only match one left-most wildcard | |||
|
88 | check(_verifycert(cert('f*.com'), 'foo.com'), | |||
|
89 | 'certificate is for f*.com') | |||
|
90 | check(_verifycert(cert('f*.com'), 'f.com'), | |||
|
91 | 'certificate is for f*.com') | |||
|
92 | check(_verifycert(cert('f*.com'), 'bar.com'), | |||
|
93 | 'certificate is for f*.com') | |||
|
94 | check(_verifycert(cert('f*.com'), 'foo.a.com'), | |||
|
95 | 'certificate is for f*.com') | |||
|
96 | check(_verifycert(cert('f*.com'), 'bar.foo.com'), | |||
|
97 | 'certificate is for f*.com') | |||
|
98 | ||||
|
99 | # NULL bytes are bad, CVE-2013-4073 | |||
|
100 | check(_verifycert(cert('null.python.org\x00example.org'), | |||
|
101 | 'null.python.org\x00example.org'), None) | |||
|
102 | check(_verifycert(cert('null.python.org\x00example.org'), | |||
|
103 | 'example.org'), | |||
|
104 | 'certificate is for null.python.org\x00example.org') | |||
|
105 | check(_verifycert(cert('null.python.org\x00example.org'), | |||
|
106 | 'null.python.org'), | |||
|
107 | 'certificate is for null.python.org\x00example.org') | |||
|
108 | ||||
|
109 | # error cases with wildcards | |||
|
110 | check(_verifycert(cert('*.*.a.com'), 'bar.foo.a.com'), | |||
|
111 | 'certificate is for *.*.a.com') | |||
|
112 | check(_verifycert(cert('*.*.a.com'), 'a.com'), | |||
|
113 | 'certificate is for *.*.a.com') | |||
|
114 | check(_verifycert(cert('*.*.a.com'), 'Xa.com'), | |||
|
115 | 'certificate is for *.*.a.com') | |||
|
116 | check(_verifycert(cert('*.*.a.com'), '.a.com'), | |||
|
117 | 'certificate is for *.*.a.com') | |||
|
118 | ||||
|
119 | check(_verifycert(cert('a.*.com'), 'a.foo.com'), | |||
|
120 | 'certificate is for a.*.com') | |||
|
121 | check(_verifycert(cert('a.*.com'), 'a..com'), | |||
|
122 | 'certificate is for a.*.com') | |||
|
123 | check(_verifycert(cert('a.*.com'), 'a.com'), | |||
|
124 | 'certificate is for a.*.com') | |||
|
125 | ||||
|
126 | # wildcard doesn't match IDNA prefix 'xn--' | |||
|
127 | idna = u'pΓΌthon.python.org'.encode('idna').decode('ascii') | |||
|
128 | check(_verifycert(cert(idna), idna), None) | |||
|
129 | check(_verifycert(cert('x*.python.org'), idna), | |||
|
130 | 'certificate is for x*.python.org') | |||
|
131 | check(_verifycert(cert('xn--p*.python.org'), idna), | |||
|
132 | 'certificate is for xn--p*.python.org') | |||
|
133 | ||||
|
134 | # wildcard in first fragment and IDNA A-labels in sequent fragments | |||
|
135 | # are supported. | |||
|
136 | idna = u'www*.pythΓΆn.org'.encode('idna').decode('ascii') | |||
|
137 | check(_verifycert(cert(idna), | |||
|
138 | u'www.pythΓΆn.org'.encode('idna').decode('ascii')), | |||
|
139 | 'certificate is for www*.xn--pythn-mua.org') | |||
|
140 | check(_verifycert(cert(idna), | |||
|
141 | u'www1.pythΓΆn.org'.encode('idna').decode('ascii')), | |||
|
142 | 'certificate is for www*.xn--pythn-mua.org') | |||
|
143 | check(_verifycert(cert(idna), | |||
|
144 | u'ftp.pythΓΆn.org'.encode('idna').decode('ascii')), | |||
|
145 | 'certificate is for www*.xn--pythn-mua.org') | |||
|
146 | check(_verifycert(cert(idna), | |||
|
147 | u'pythΓΆn.org'.encode('idna').decode('ascii')), | |||
|
148 | 'certificate is for www*.xn--pythn-mua.org') | |||
|
149 | ||||
|
150 | c = { | |||
|
151 | 'notAfter': 'Jun 26 21:41:46 2011 GMT', | |||
|
152 | 'subject': (((u'commonName', u'linuxfrz.org'),),), | |||
|
153 | 'subjectAltName': ( | |||
|
154 | ('DNS', 'linuxfr.org'), | |||
|
155 | ('DNS', 'linuxfr.com'), | |||
|
156 | ('othername', '<unsupported>'), | |||
|
157 | ) | |||
|
158 | } | |||
|
159 | check(_verifycert(c, 'linuxfr.org'), None) | |||
|
160 | check(_verifycert(c, 'linuxfr.com'), None) | |||
|
161 | # Not a "DNS" entry | |||
|
162 | check(_verifycert(c, '<unsupported>'), | |||
|
163 | 'certificate is for linuxfr.org, linuxfr.com') | |||
|
164 | # When there is a subjectAltName, commonName isn't used | |||
|
165 | check(_verifycert(c, 'linuxfrz.org'), | |||
|
166 | 'certificate is for linuxfr.org, linuxfr.com') | |||
|
167 | ||||
|
168 | # A pristine real-world example | |||
|
169 | c = { | |||
|
170 | 'notAfter': 'Dec 18 23:59:59 2011 GMT', | |||
|
171 | 'subject': ( | |||
|
172 | ((u'countryName', u'US'),), | |||
|
173 | ((u'stateOrProvinceName', u'California'),), | |||
|
174 | ((u'localityName', u'Mountain View'),), | |||
|
175 | ((u'organizationName', u'Google Inc'),), | |||
|
176 | ((u'commonName', u'mail.google.com'),), | |||
|
177 | ), | |||
|
178 | } | |||
|
179 | check(_verifycert(c, 'mail.google.com'), None) | |||
|
180 | check(_verifycert(c, 'gmail.com'), 'certificate is for mail.google.com') | |||
|
181 | ||||
|
182 | # Only commonName is considered | |||
|
183 | check(_verifycert(c, 'California'), 'certificate is for mail.google.com') | |||
|
184 | ||||
|
185 | # Neither commonName nor subjectAltName | |||
|
186 | c = { | |||
|
187 | 'notAfter': 'Dec 18 23:59:59 2011 GMT', | |||
|
188 | 'subject': ( | |||
|
189 | ((u'countryName', u'US'),), | |||
|
190 | ((u'stateOrProvinceName', u'California'),), | |||
|
191 | ((u'localityName', u'Mountain View'),), | |||
|
192 | ((u'organizationName', u'Google Inc'),), | |||
|
193 | ), | |||
|
194 | } | |||
|
195 | check(_verifycert(c, 'mail.google.com'), | |||
|
196 | 'no commonName or subjectAltName found in certificate') | |||
|
197 | ||||
|
198 | # No DNS entry in subjectAltName but a commonName | |||
|
199 | c = { | |||
|
200 | 'notAfter': 'Dec 18 23:59:59 2099 GMT', | |||
|
201 | 'subject': ( | |||
|
202 | ((u'countryName', u'US'),), | |||
|
203 | ((u'stateOrProvinceName', u'California'),), | |||
|
204 | ((u'localityName', u'Mountain View'),), | |||
|
205 | ((u'commonName', u'mail.google.com'),), | |||
|
206 | ), | |||
|
207 | 'subjectAltName': (('othername', 'blabla'),), | |||
|
208 | } | |||
|
209 | check(_verifycert(c, 'mail.google.com'), None) | |||
|
210 | ||||
|
211 | # No DNS entry subjectAltName and no commonName | |||
|
212 | c = { | |||
|
213 | 'notAfter': 'Dec 18 23:59:59 2099 GMT', | |||
|
214 | 'subject': ( | |||
|
215 | ((u'countryName', u'US'),), | |||
|
216 | ((u'stateOrProvinceName', u'California'),), | |||
|
217 | ((u'localityName', u'Mountain View'),), | |||
|
218 | ((u'organizationName', u'Google Inc'),), | |||
|
219 | ), | |||
|
220 | 'subjectAltName': (('othername', 'blabla'),), | |||
|
221 | } | |||
|
222 | check(_verifycert(c, 'google.com'), | |||
|
223 | 'no commonName or subjectAltName found in certificate') | |||
|
224 | ||||
|
225 | # Empty cert / no cert | |||
|
226 | check(_verifycert(None, 'example.com'), 'no certificate received') | |||
|
227 | check(_verifycert({}, 'example.com'), 'no certificate received') | |||
|
228 | ||||
|
229 | # avoid denials of service by refusing more than one | |||
|
230 | # wildcard per fragment. | |||
|
231 | check(_verifycert({'subject': (((u'commonName', u'a*b.com'),),)}, | |||
|
232 | 'axxb.com'), 'certificate is for a*b.com') | |||
|
233 | check(_verifycert({'subject': (((u'commonName', u'a*b.co*'),),)}, | |||
|
234 | 'axxb.com'), 'certificate is for a*b.co*') | |||
|
235 | check(_verifycert({'subject': (((u'commonName', u'a*b*.com'),),)}, | |||
|
236 | 'axxbxxc.com'), 'certificate is for a*b*.com') | |||
|
237 | ||||
66 | def test_url(): |
|
238 | def test_url(): | |
67 | """ |
|
239 | """ | |
68 | >>> from mercurial.util import url |
|
240 | >>> from mercurial.util import url | |
69 |
|
241 | |||
70 | This tests for edge cases in url.URL's parsing algorithm. Most of |
|
242 | This tests for edge cases in url.URL's parsing algorithm. Most of | |
71 | these aren't useful for documentation purposes, so they aren't |
|
243 | these aren't useful for documentation purposes, so they aren't | |
72 | part of the class's doc tests. |
|
244 | part of the class's doc tests. | |
73 |
|
245 | |||
74 | Query strings and fragments: |
|
246 | Query strings and fragments: | |
75 |
|
247 | |||
76 | >>> url('http://host/a?b#c') |
|
248 | >>> url('http://host/a?b#c') | |
77 | <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'> |
|
249 | <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'> | |
78 | >>> url('http://host/a?') |
|
250 | >>> url('http://host/a?') | |
79 | <url scheme: 'http', host: 'host', path: 'a'> |
|
251 | <url scheme: 'http', host: 'host', path: 'a'> | |
80 | >>> url('http://host/a#b#c') |
|
252 | >>> url('http://host/a#b#c') | |
81 | <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'> |
|
253 | <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'> | |
82 | >>> url('http://host/a#b?c') |
|
254 | >>> url('http://host/a#b?c') | |
83 | <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'> |
|
255 | <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'> | |
84 | >>> url('http://host/?a#b') |
|
256 | >>> url('http://host/?a#b') | |
85 | <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'> |
|
257 | <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'> | |
86 | >>> url('http://host/?a#b', parsequery=False) |
|
258 | >>> url('http://host/?a#b', parsequery=False) | |
87 | <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'> |
|
259 | <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'> | |
88 | >>> url('http://host/?a#b', parsefragment=False) |
|
260 | >>> url('http://host/?a#b', parsefragment=False) | |
89 | <url scheme: 'http', host: 'host', path: '', query: 'a#b'> |
|
261 | <url scheme: 'http', host: 'host', path: '', query: 'a#b'> | |
90 | >>> url('http://host/?a#b', parsequery=False, parsefragment=False) |
|
262 | >>> url('http://host/?a#b', parsequery=False, parsefragment=False) | |
91 | <url scheme: 'http', host: 'host', path: '?a#b'> |
|
263 | <url scheme: 'http', host: 'host', path: '?a#b'> | |
92 |
|
264 | |||
93 | IPv6 addresses: |
|
265 | IPv6 addresses: | |
94 |
|
266 | |||
95 | >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one') |
|
267 | >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one') | |
96 | <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB', |
|
268 | <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB', | |
97 | query: 'objectClass?one'> |
|
269 | query: 'objectClass?one'> | |
98 | >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one') |
|
270 | >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one') | |
99 | <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]', |
|
271 | <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]', | |
100 | port: '80', path: 'c=GB', query: 'objectClass?one'> |
|
272 | port: '80', path: 'c=GB', query: 'objectClass?one'> | |
101 |
|
273 | |||
102 | Missing scheme, host, etc.: |
|
274 | Missing scheme, host, etc.: | |
103 |
|
275 | |||
104 | >>> url('://192.0.2.16:80/') |
|
276 | >>> url('://192.0.2.16:80/') | |
105 | <url path: '://192.0.2.16:80/'> |
|
277 | <url path: '://192.0.2.16:80/'> | |
106 | >>> url('https://mercurial-scm.org') |
|
278 | >>> url('https://mercurial-scm.org') | |
107 | <url scheme: 'https', host: 'mercurial-scm.org'> |
|
279 | <url scheme: 'https', host: 'mercurial-scm.org'> | |
108 | >>> url('/foo') |
|
280 | >>> url('/foo') | |
109 | <url path: '/foo'> |
|
281 | <url path: '/foo'> | |
110 | >>> url('bundle:/foo') |
|
282 | >>> url('bundle:/foo') | |
111 | <url scheme: 'bundle', path: '/foo'> |
|
283 | <url scheme: 'bundle', path: '/foo'> | |
112 | >>> url('a?b#c') |
|
284 | >>> url('a?b#c') | |
113 | <url path: 'a?b', fragment: 'c'> |
|
285 | <url path: 'a?b', fragment: 'c'> | |
114 | >>> url('http://x.com?arg=/foo') |
|
286 | >>> url('http://x.com?arg=/foo') | |
115 | <url scheme: 'http', host: 'x.com', query: 'arg=/foo'> |
|
287 | <url scheme: 'http', host: 'x.com', query: 'arg=/foo'> | |
116 | >>> url('http://joe:xxx@/foo') |
|
288 | >>> url('http://joe:xxx@/foo') | |
117 | <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'> |
|
289 | <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'> | |
118 |
|
290 | |||
119 | Just a scheme and a path: |
|
291 | Just a scheme and a path: | |
120 |
|
292 | |||
121 | >>> url('mailto:John.Doe@example.com') |
|
293 | >>> url('mailto:John.Doe@example.com') | |
122 | <url scheme: 'mailto', path: 'John.Doe@example.com'> |
|
294 | <url scheme: 'mailto', path: 'John.Doe@example.com'> | |
123 | >>> url('a:b:c:d') |
|
295 | >>> url('a:b:c:d') | |
124 | <url path: 'a:b:c:d'> |
|
296 | <url path: 'a:b:c:d'> | |
125 | >>> url('aa:bb:cc:dd') |
|
297 | >>> url('aa:bb:cc:dd') | |
126 | <url scheme: 'aa', path: 'bb:cc:dd'> |
|
298 | <url scheme: 'aa', path: 'bb:cc:dd'> | |
127 |
|
299 | |||
128 | SSH examples: |
|
300 | SSH examples: | |
129 |
|
301 | |||
130 | >>> url('ssh://joe@host//home/joe') |
|
302 | >>> url('ssh://joe@host//home/joe') | |
131 | <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'> |
|
303 | <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'> | |
132 | >>> url('ssh://joe:xxx@host/src') |
|
304 | >>> url('ssh://joe:xxx@host/src') | |
133 | <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'> |
|
305 | <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'> | |
134 | >>> url('ssh://joe:xxx@host') |
|
306 | >>> url('ssh://joe:xxx@host') | |
135 | <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'> |
|
307 | <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'> | |
136 | >>> url('ssh://joe@host') |
|
308 | >>> url('ssh://joe@host') | |
137 | <url scheme: 'ssh', user: 'joe', host: 'host'> |
|
309 | <url scheme: 'ssh', user: 'joe', host: 'host'> | |
138 | >>> url('ssh://host') |
|
310 | >>> url('ssh://host') | |
139 | <url scheme: 'ssh', host: 'host'> |
|
311 | <url scheme: 'ssh', host: 'host'> | |
140 | >>> url('ssh://') |
|
312 | >>> url('ssh://') | |
141 | <url scheme: 'ssh'> |
|
313 | <url scheme: 'ssh'> | |
142 | >>> url('ssh:') |
|
314 | >>> url('ssh:') | |
143 | <url scheme: 'ssh'> |
|
315 | <url scheme: 'ssh'> | |
144 |
|
316 | |||
145 | Non-numeric port: |
|
317 | Non-numeric port: | |
146 |
|
318 | |||
147 | >>> url('http://example.com:dd') |
|
319 | >>> url('http://example.com:dd') | |
148 | <url scheme: 'http', host: 'example.com', port: 'dd'> |
|
320 | <url scheme: 'http', host: 'example.com', port: 'dd'> | |
149 | >>> url('ssh://joe:xxx@host:ssh/foo') |
|
321 | >>> url('ssh://joe:xxx@host:ssh/foo') | |
150 | <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh', |
|
322 | <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh', | |
151 | path: 'foo'> |
|
323 | path: 'foo'> | |
152 |
|
324 | |||
153 | Bad authentication credentials: |
|
325 | Bad authentication credentials: | |
154 |
|
326 | |||
155 | >>> url('http://joe@joeville:123@4:@host/a?b#c') |
|
327 | >>> url('http://joe@joeville:123@4:@host/a?b#c') | |
156 | <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:', |
|
328 | <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:', | |
157 | host: 'host', path: 'a', query: 'b', fragment: 'c'> |
|
329 | host: 'host', path: 'a', query: 'b', fragment: 'c'> | |
158 | >>> url('http://!*#?/@!*#?/:@host/a?b#c') |
|
330 | >>> url('http://!*#?/@!*#?/:@host/a?b#c') | |
159 | <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'> |
|
331 | <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'> | |
160 | >>> url('http://!*#?@!*#?:@host/a?b#c') |
|
332 | >>> url('http://!*#?@!*#?:@host/a?b#c') | |
161 | <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'> |
|
333 | <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'> | |
162 | >>> url('http://!*@:!*@@host/a?b#c') |
|
334 | >>> url('http://!*@:!*@@host/a?b#c') | |
163 | <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host', |
|
335 | <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host', | |
164 | path: 'a', query: 'b', fragment: 'c'> |
|
336 | path: 'a', query: 'b', fragment: 'c'> | |
165 |
|
337 | |||
166 | File paths: |
|
338 | File paths: | |
167 |
|
339 | |||
168 | >>> url('a/b/c/d.g.f') |
|
340 | >>> url('a/b/c/d.g.f') | |
169 | <url path: 'a/b/c/d.g.f'> |
|
341 | <url path: 'a/b/c/d.g.f'> | |
170 | >>> url('/x///z/y/') |
|
342 | >>> url('/x///z/y/') | |
171 | <url path: '/x///z/y/'> |
|
343 | <url path: '/x///z/y/'> | |
172 | >>> url('/foo:bar') |
|
344 | >>> url('/foo:bar') | |
173 | <url path: '/foo:bar'> |
|
345 | <url path: '/foo:bar'> | |
174 | >>> url('\\\\foo:bar') |
|
346 | >>> url('\\\\foo:bar') | |
175 | <url path: '\\\\foo:bar'> |
|
347 | <url path: '\\\\foo:bar'> | |
176 | >>> url('./foo:bar') |
|
348 | >>> url('./foo:bar') | |
177 | <url path: './foo:bar'> |
|
349 | <url path: './foo:bar'> | |
178 |
|
350 | |||
179 | Non-localhost file URL: |
|
351 | Non-localhost file URL: | |
180 |
|
352 | |||
181 | >>> u = url('file://mercurial-scm.org/foo') |
|
353 | >>> u = url('file://mercurial-scm.org/foo') | |
182 | Traceback (most recent call last): |
|
354 | Traceback (most recent call last): | |
183 | File "<stdin>", line 1, in ? |
|
355 | File "<stdin>", line 1, in ? | |
184 | Abort: file:// URLs can only refer to localhost |
|
356 | Abort: file:// URLs can only refer to localhost | |
185 |
|
357 | |||
186 | Empty URL: |
|
358 | Empty URL: | |
187 |
|
359 | |||
188 | >>> u = url('') |
|
360 | >>> u = url('') | |
189 | >>> u |
|
361 | >>> u | |
190 | <url path: ''> |
|
362 | <url path: ''> | |
191 | >>> str(u) |
|
363 | >>> str(u) | |
192 | '' |
|
364 | '' | |
193 |
|
365 | |||
194 | Empty path with query string: |
|
366 | Empty path with query string: | |
195 |
|
367 | |||
196 | >>> str(url('http://foo/?bar')) |
|
368 | >>> str(url('http://foo/?bar')) | |
197 | 'http://foo/?bar' |
|
369 | 'http://foo/?bar' | |
198 |
|
370 | |||
199 | Invalid path: |
|
371 | Invalid path: | |
200 |
|
372 | |||
201 | >>> u = url('http://foo/bar') |
|
373 | >>> u = url('http://foo/bar') | |
202 | >>> u.path = 'bar' |
|
374 | >>> u.path = 'bar' | |
203 | >>> str(u) |
|
375 | >>> str(u) | |
204 | 'http://foo/bar' |
|
376 | 'http://foo/bar' | |
205 |
|
377 | |||
206 | >>> u = url('file:/foo/bar/baz') |
|
378 | >>> u = url('file:/foo/bar/baz') | |
207 | >>> u |
|
379 | >>> u | |
208 | <url scheme: 'file', path: '/foo/bar/baz'> |
|
380 | <url scheme: 'file', path: '/foo/bar/baz'> | |
209 | >>> str(u) |
|
381 | >>> str(u) | |
210 | 'file:///foo/bar/baz' |
|
382 | 'file:///foo/bar/baz' | |
211 | >>> u.localpath() |
|
383 | >>> u.localpath() | |
212 | '/foo/bar/baz' |
|
384 | '/foo/bar/baz' | |
213 |
|
385 | |||
214 | >>> u = url('file:///foo/bar/baz') |
|
386 | >>> u = url('file:///foo/bar/baz') | |
215 | >>> u |
|
387 | >>> u | |
216 | <url scheme: 'file', path: '/foo/bar/baz'> |
|
388 | <url scheme: 'file', path: '/foo/bar/baz'> | |
217 | >>> str(u) |
|
389 | >>> str(u) | |
218 | 'file:///foo/bar/baz' |
|
390 | 'file:///foo/bar/baz' | |
219 | >>> u.localpath() |
|
391 | >>> u.localpath() | |
220 | '/foo/bar/baz' |
|
392 | '/foo/bar/baz' | |
221 |
|
393 | |||
222 | >>> u = url('file:///f:oo/bar/baz') |
|
394 | >>> u = url('file:///f:oo/bar/baz') | |
223 | >>> u |
|
395 | >>> u | |
224 | <url scheme: 'file', path: 'f:oo/bar/baz'> |
|
396 | <url scheme: 'file', path: 'f:oo/bar/baz'> | |
225 | >>> str(u) |
|
397 | >>> str(u) | |
226 | 'file:///f:oo/bar/baz' |
|
398 | 'file:///f:oo/bar/baz' | |
227 | >>> u.localpath() |
|
399 | >>> u.localpath() | |
228 | 'f:oo/bar/baz' |
|
400 | 'f:oo/bar/baz' | |
229 |
|
401 | |||
230 | >>> u = url('file://localhost/f:oo/bar/baz') |
|
402 | >>> u = url('file://localhost/f:oo/bar/baz') | |
231 | >>> u |
|
403 | >>> u | |
232 | <url scheme: 'file', host: 'localhost', path: 'f:oo/bar/baz'> |
|
404 | <url scheme: 'file', host: 'localhost', path: 'f:oo/bar/baz'> | |
233 | >>> str(u) |
|
405 | >>> str(u) | |
234 | 'file://localhost/f:oo/bar/baz' |
|
406 | 'file://localhost/f:oo/bar/baz' | |
235 | >>> u.localpath() |
|
407 | >>> u.localpath() | |
236 | 'f:oo/bar/baz' |
|
408 | 'f:oo/bar/baz' | |
237 |
|
409 | |||
238 | >>> u = url('file:foo/bar/baz') |
|
410 | >>> u = url('file:foo/bar/baz') | |
239 | >>> u |
|
411 | >>> u | |
240 | <url scheme: 'file', path: 'foo/bar/baz'> |
|
412 | <url scheme: 'file', path: 'foo/bar/baz'> | |
241 | >>> str(u) |
|
413 | >>> str(u) | |
242 | 'file:foo/bar/baz' |
|
414 | 'file:foo/bar/baz' | |
243 | >>> u.localpath() |
|
415 | >>> u.localpath() | |
244 | 'foo/bar/baz' |
|
416 | 'foo/bar/baz' | |
245 | """ |
|
417 | """ | |
246 |
|
418 | |||
247 | if 'TERM' in os.environ: |
|
419 | if 'TERM' in os.environ: | |
248 | del os.environ['TERM'] |
|
420 | del os.environ['TERM'] | |
249 |
|
421 | |||
250 | doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) |
|
422 | doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE) |
General Comments 0
You need to be logged in to leave comments.
Login now