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