##// END OF EJS Templates
sslutil: fall back to commonName when no dNSName in subjectAltName (issue2798)...
Nicolas Bareil -
r14666:27b080aa default
parent child Browse files
Show More
@@ -1,128 +1,129
1 # sslutil.py - SSL handling for mercurial
1 # sslutil.py - SSL handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9 import os
9 import os
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 try:
13 try:
14 # avoid using deprecated/broken FakeSocket in python 2.6
14 # avoid using deprecated/broken FakeSocket in python 2.6
15 import ssl
15 import ssl
16 ssl_wrap_socket = ssl.wrap_socket
16 ssl_wrap_socket = ssl.wrap_socket
17 CERT_REQUIRED = ssl.CERT_REQUIRED
17 CERT_REQUIRED = ssl.CERT_REQUIRED
18 except ImportError:
18 except ImportError:
19 CERT_REQUIRED = 2
19 CERT_REQUIRED = 2
20
20
21 import socket, httplib
21 import socket, httplib
22
22
23 def ssl_wrap_socket(sock, key_file, cert_file,
23 def ssl_wrap_socket(sock, key_file, cert_file,
24 cert_reqs=CERT_REQUIRED, ca_certs=None):
24 cert_reqs=CERT_REQUIRED, ca_certs=None):
25 if ca_certs:
25 if ca_certs:
26 raise util.Abort(_(
26 raise util.Abort(_(
27 'certificate checking requires Python 2.6'))
27 'certificate checking requires Python 2.6'))
28
28
29 ssl = socket.ssl(sock, key_file, cert_file)
29 ssl = socket.ssl(sock, key_file, cert_file)
30 return httplib.FakeSocket(sock, ssl)
30 return httplib.FakeSocket(sock, ssl)
31
31
32 def _verifycert(cert, hostname):
32 def _verifycert(cert, hostname):
33 '''Verify that cert (in socket.getpeercert() format) matches hostname.
33 '''Verify that cert (in socket.getpeercert() format) matches hostname.
34 CRLs is not handled.
34 CRLs is not handled.
35
35
36 Returns error message if any problems are found and None on success.
36 Returns error message if any problems are found and None on success.
37 '''
37 '''
38 if not cert:
38 if not cert:
39 return _('no certificate received')
39 return _('no certificate received')
40 dnsname = hostname.lower()
40 dnsname = hostname.lower()
41 def matchdnsname(certname):
41 def matchdnsname(certname):
42 return (certname == dnsname or
42 return (certname == dnsname or
43 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
43 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
44
44
45 san = cert.get('subjectAltName', [])
45 san = cert.get('subjectAltName', [])
46 if san:
46 if san:
47 certnames = [value.lower() for key, value in san if key == 'DNS']
47 certnames = [value.lower() for key, value in san if key == 'DNS']
48 for name in certnames:
48 for name in certnames:
49 if matchdnsname(name):
49 if matchdnsname(name):
50 return None
50 return None
51 return _('certificate is for %s') % ', '.join(certnames)
51 if certnames:
52 return _('certificate is for %s') % ', '.join(certnames)
52
53
53 # subject is only checked when subjectAltName is empty
54 # subject is only checked when subjectAltName is empty
54 for s in cert.get('subject', []):
55 for s in cert.get('subject', []):
55 key, value = s[0]
56 key, value = s[0]
56 if key == 'commonName':
57 if key == 'commonName':
57 try:
58 try:
58 # 'subject' entries are unicode
59 # 'subject' entries are unicode
59 certname = value.lower().encode('ascii')
60 certname = value.lower().encode('ascii')
60 except UnicodeEncodeError:
61 except UnicodeEncodeError:
61 return _('IDN in certificate not supported')
62 return _('IDN in certificate not supported')
62 if matchdnsname(certname):
63 if matchdnsname(certname):
63 return None
64 return None
64 return _('certificate is for %s') % certname
65 return _('certificate is for %s') % certname
65 return _('no commonName or subjectAltName found in certificate')
66 return _('no commonName or subjectAltName found in certificate')
66
67
67
68
68 # CERT_REQUIRED means fetch the cert from the server all the time AND
69 # CERT_REQUIRED means fetch the cert from the server all the time AND
69 # validate it against the CA store provided in web.cacerts.
70 # validate it against the CA store provided in web.cacerts.
70 #
71 #
71 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
72 # We COMPLETELY ignore CERT_REQUIRED on Python <= 2.5, as it's totally
72 # busted on those versions.
73 # busted on those versions.
73
74
74 def sslkwargs(ui, host):
75 def sslkwargs(ui, host):
75 cacerts = ui.config('web', 'cacerts')
76 cacerts = ui.config('web', 'cacerts')
76 hostfingerprint = ui.config('hostfingerprints', host)
77 hostfingerprint = ui.config('hostfingerprints', host)
77 if cacerts and not hostfingerprint:
78 if cacerts and not hostfingerprint:
78 cacerts = util.expandpath(cacerts)
79 cacerts = util.expandpath(cacerts)
79 if not os.path.exists(cacerts):
80 if not os.path.exists(cacerts):
80 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
81 raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
81 return {'ca_certs': cacerts,
82 return {'ca_certs': cacerts,
82 'cert_reqs': CERT_REQUIRED,
83 'cert_reqs': CERT_REQUIRED,
83 }
84 }
84 return {}
85 return {}
85
86
86 class validator(object):
87 class validator(object):
87 def __init__(self, ui, host):
88 def __init__(self, ui, host):
88 self.ui = ui
89 self.ui = ui
89 self.host = host
90 self.host = host
90
91
91 def __call__(self, sock):
92 def __call__(self, sock):
92 host = self.host
93 host = self.host
93 cacerts = self.ui.config('web', 'cacerts')
94 cacerts = self.ui.config('web', 'cacerts')
94 hostfingerprint = self.ui.config('hostfingerprints', host)
95 hostfingerprint = self.ui.config('hostfingerprints', host)
95 if cacerts and not hostfingerprint:
96 if cacerts and not hostfingerprint:
96 msg = _verifycert(sock.getpeercert(), host)
97 msg = _verifycert(sock.getpeercert(), host)
97 if msg:
98 if msg:
98 raise util.Abort(_('%s certificate error: %s '
99 raise util.Abort(_('%s certificate error: %s '
99 '(use --insecure to connect '
100 '(use --insecure to connect '
100 'insecurely)') % (host, msg))
101 'insecurely)') % (host, msg))
101 self.ui.debug('%s certificate successfully verified\n' % host)
102 self.ui.debug('%s certificate successfully verified\n' % host)
102 else:
103 else:
103 if getattr(sock, 'getpeercert', False):
104 if getattr(sock, 'getpeercert', False):
104 peercert = sock.getpeercert(True)
105 peercert = sock.getpeercert(True)
105 peerfingerprint = util.sha1(peercert).hexdigest()
106 peerfingerprint = util.sha1(peercert).hexdigest()
106 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
107 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
107 for x in xrange(0, len(peerfingerprint), 2)])
108 for x in xrange(0, len(peerfingerprint), 2)])
108 if hostfingerprint:
109 if hostfingerprint:
109 if peerfingerprint.lower() != \
110 if peerfingerprint.lower() != \
110 hostfingerprint.replace(':', '').lower():
111 hostfingerprint.replace(':', '').lower():
111 raise util.Abort(_('invalid certificate for %s '
112 raise util.Abort(_('invalid certificate for %s '
112 'with fingerprint %s') %
113 'with fingerprint %s') %
113 (host, nicefingerprint))
114 (host, nicefingerprint))
114 self.ui.debug('%s certificate matched fingerprint %s\n' %
115 self.ui.debug('%s certificate matched fingerprint %s\n' %
115 (host, nicefingerprint))
116 (host, nicefingerprint))
116 else:
117 else:
117 self.ui.warn(_('warning: %s certificate '
118 self.ui.warn(_('warning: %s certificate '
118 'with fingerprint %s not verified '
119 'with fingerprint %s not verified '
119 '(check hostfingerprints or web.cacerts '
120 '(check hostfingerprints or web.cacerts '
120 'config setting)\n') %
121 'config setting)\n') %
121 (host, nicefingerprint))
122 (host, nicefingerprint))
122 else: # python 2.5 ?
123 else: # python 2.5 ?
123 if hostfingerprint:
124 if hostfingerprint:
124 raise util.Abort(_('no certificate for %s with '
125 raise util.Abort(_('no certificate for %s with '
125 'configured hostfingerprint') % host)
126 'configured hostfingerprint') % host)
126 self.ui.warn(_('warning: %s certificate not verified '
127 self.ui.warn(_('warning: %s certificate not verified '
127 '(check web.cacerts config setting)\n') %
128 '(check web.cacerts config setting)\n') %
128 host)
129 host)
@@ -1,217 +1,221
1 import sys
1 import sys
2
2
3 def check(a, b):
3 def check(a, b):
4 if a != b:
4 if a != b:
5 print (a, b)
5 print (a, b)
6
6
7 def cert(cn):
7 def cert(cn):
8 return dict(subject=((('commonName', cn),),))
8 return dict(subject=((('commonName', cn),),))
9
9
10 from mercurial.sslutil import _verifycert
10 from mercurial.sslutil import _verifycert
11
11
12 # Test non-wildcard certificates
12 # Test non-wildcard certificates
13 check(_verifycert(cert('example.com'), 'example.com'),
13 check(_verifycert(cert('example.com'), 'example.com'),
14 None)
14 None)
15 check(_verifycert(cert('example.com'), 'www.example.com'),
15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 'certificate is for example.com')
16 'certificate is for example.com')
17 check(_verifycert(cert('www.example.com'), 'example.com'),
17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 'certificate is for www.example.com')
18 'certificate is for www.example.com')
19
19
20 # Test wildcard certificates
20 # Test wildcard certificates
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 None)
22 None)
23 check(_verifycert(cert('*.example.com'), 'example.com'),
23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 'certificate is for *.example.com')
24 'certificate is for *.example.com')
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 'certificate is for *.example.com')
26 'certificate is for *.example.com')
27
27
28 # Test subjectAltName
28 # Test subjectAltName
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 'subjectAltName': (('DNS', '*.example.net'),
30 'subjectAltName': (('DNS', '*.example.net'),
31 ('DNS', 'example.net'))}
31 ('DNS', 'example.net'))}
32 check(_verifycert(san_cert, 'example.net'),
32 check(_verifycert(san_cert, 'example.net'),
33 None)
33 None)
34 check(_verifycert(san_cert, 'foo.example.net'),
34 check(_verifycert(san_cert, 'foo.example.net'),
35 None)
35 None)
36 # subject is only checked when subjectAltName is empty
36 # no fallback to subject commonName when subjectAltName has DNS
37 check(_verifycert(san_cert, 'example.com'),
37 check(_verifycert(san_cert, 'example.com'),
38 'certificate is for *.example.net, example.net')
38 'certificate is for *.example.net, example.net')
39 # fallback to subject commonName when no DNS in subjectAltName
40 san_cert = {'subject': ((('commonName', 'example.com'),),),
41 'subjectAltName': (('IP Address', '8.8.8.8'),)}
42 check(_verifycert(san_cert, 'example.com'), None)
39
43
40 # Avoid some pitfalls
44 # Avoid some pitfalls
41 check(_verifycert(cert('*.foo'), 'foo'),
45 check(_verifycert(cert('*.foo'), 'foo'),
42 'certificate is for *.foo')
46 'certificate is for *.foo')
43 check(_verifycert(cert('*o'), 'foo'),
47 check(_verifycert(cert('*o'), 'foo'),
44 'certificate is for *o')
48 'certificate is for *o')
45
49
46 check(_verifycert({'subject': ()},
50 check(_verifycert({'subject': ()},
47 'example.com'),
51 'example.com'),
48 'no commonName or subjectAltName found in certificate')
52 'no commonName or subjectAltName found in certificate')
49 check(_verifycert(None, 'example.com'),
53 check(_verifycert(None, 'example.com'),
50 'no certificate received')
54 'no certificate received')
51
55
56 # Unicode (IDN) certname isn't supported
57 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
58 'IDN in certificate not supported')
59
52 import doctest
60 import doctest
53
61
54 def test_url():
62 def test_url():
55 """
63 """
56 >>> from mercurial.util import url
64 >>> from mercurial.util import url
57
65
58 This tests for edge cases in url.URL's parsing algorithm. Most of
66 This tests for edge cases in url.URL's parsing algorithm. Most of
59 these aren't useful for documentation purposes, so they aren't
67 these aren't useful for documentation purposes, so they aren't
60 part of the class's doc tests.
68 part of the class's doc tests.
61
69
62 Query strings and fragments:
70 Query strings and fragments:
63
71
64 >>> url('http://host/a?b#c')
72 >>> url('http://host/a?b#c')
65 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
73 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
66 >>> url('http://host/a?')
74 >>> url('http://host/a?')
67 <url scheme: 'http', host: 'host', path: 'a'>
75 <url scheme: 'http', host: 'host', path: 'a'>
68 >>> url('http://host/a#b#c')
76 >>> url('http://host/a#b#c')
69 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
77 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
70 >>> url('http://host/a#b?c')
78 >>> url('http://host/a#b?c')
71 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
79 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
72 >>> url('http://host/?a#b')
80 >>> url('http://host/?a#b')
73 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
81 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
74 >>> url('http://host/?a#b', parsequery=False)
82 >>> url('http://host/?a#b', parsequery=False)
75 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
83 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
76 >>> url('http://host/?a#b', parsefragment=False)
84 >>> url('http://host/?a#b', parsefragment=False)
77 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
85 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
78 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
86 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
79 <url scheme: 'http', host: 'host', path: '?a#b'>
87 <url scheme: 'http', host: 'host', path: '?a#b'>
80
88
81 IPv6 addresses:
89 IPv6 addresses:
82
90
83 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
91 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
84 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
92 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
85 query: 'objectClass?one'>
93 query: 'objectClass?one'>
86 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
94 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
87 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
95 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
88 port: '80', path: 'c=GB', query: 'objectClass?one'>
96 port: '80', path: 'c=GB', query: 'objectClass?one'>
89
97
90 Missing scheme, host, etc.:
98 Missing scheme, host, etc.:
91
99
92 >>> url('://192.0.2.16:80/')
100 >>> url('://192.0.2.16:80/')
93 <url path: '://192.0.2.16:80/'>
101 <url path: '://192.0.2.16:80/'>
94 >>> url('http://mercurial.selenic.com')
102 >>> url('http://mercurial.selenic.com')
95 <url scheme: 'http', host: 'mercurial.selenic.com'>
103 <url scheme: 'http', host: 'mercurial.selenic.com'>
96 >>> url('/foo')
104 >>> url('/foo')
97 <url path: '/foo'>
105 <url path: '/foo'>
98 >>> url('bundle:/foo')
106 >>> url('bundle:/foo')
99 <url scheme: 'bundle', path: '/foo'>
107 <url scheme: 'bundle', path: '/foo'>
100 >>> url('a?b#c')
108 >>> url('a?b#c')
101 <url path: 'a?b', fragment: 'c'>
109 <url path: 'a?b', fragment: 'c'>
102 >>> url('http://x.com?arg=/foo')
110 >>> url('http://x.com?arg=/foo')
103 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
111 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
104 >>> url('http://joe:xxx@/foo')
112 >>> url('http://joe:xxx@/foo')
105 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
113 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
106
114
107 Just a scheme and a path:
115 Just a scheme and a path:
108
116
109 >>> url('mailto:John.Doe@example.com')
117 >>> url('mailto:John.Doe@example.com')
110 <url scheme: 'mailto', path: 'John.Doe@example.com'>
118 <url scheme: 'mailto', path: 'John.Doe@example.com'>
111 >>> url('a:b:c:d')
119 >>> url('a:b:c:d')
112 <url path: 'a:b:c:d'>
120 <url path: 'a:b:c:d'>
113 >>> url('aa:bb:cc:dd')
121 >>> url('aa:bb:cc:dd')
114 <url scheme: 'aa', path: 'bb:cc:dd'>
122 <url scheme: 'aa', path: 'bb:cc:dd'>
115
123
116 SSH examples:
124 SSH examples:
117
125
118 >>> url('ssh://joe@host//home/joe')
126 >>> url('ssh://joe@host//home/joe')
119 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
127 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
120 >>> url('ssh://joe:xxx@host/src')
128 >>> url('ssh://joe:xxx@host/src')
121 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
129 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
122 >>> url('ssh://joe:xxx@host')
130 >>> url('ssh://joe:xxx@host')
123 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
131 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
124 >>> url('ssh://joe@host')
132 >>> url('ssh://joe@host')
125 <url scheme: 'ssh', user: 'joe', host: 'host'>
133 <url scheme: 'ssh', user: 'joe', host: 'host'>
126 >>> url('ssh://host')
134 >>> url('ssh://host')
127 <url scheme: 'ssh', host: 'host'>
135 <url scheme: 'ssh', host: 'host'>
128 >>> url('ssh://')
136 >>> url('ssh://')
129 <url scheme: 'ssh'>
137 <url scheme: 'ssh'>
130 >>> url('ssh:')
138 >>> url('ssh:')
131 <url scheme: 'ssh'>
139 <url scheme: 'ssh'>
132
140
133 Non-numeric port:
141 Non-numeric port:
134
142
135 >>> url('http://example.com:dd')
143 >>> url('http://example.com:dd')
136 <url scheme: 'http', host: 'example.com', port: 'dd'>
144 <url scheme: 'http', host: 'example.com', port: 'dd'>
137 >>> url('ssh://joe:xxx@host:ssh/foo')
145 >>> url('ssh://joe:xxx@host:ssh/foo')
138 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
146 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
139 path: 'foo'>
147 path: 'foo'>
140
148
141 Bad authentication credentials:
149 Bad authentication credentials:
142
150
143 >>> url('http://joe@joeville:123@4:@host/a?b#c')
151 >>> url('http://joe@joeville:123@4:@host/a?b#c')
144 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
152 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
145 host: 'host', path: 'a', query: 'b', fragment: 'c'>
153 host: 'host', path: 'a', query: 'b', fragment: 'c'>
146 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
154 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
147 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
155 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
148 >>> url('http://!*#?@!*#?:@host/a?b#c')
156 >>> url('http://!*#?@!*#?:@host/a?b#c')
149 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
157 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
150 >>> url('http://!*@:!*@@host/a?b#c')
158 >>> url('http://!*@:!*@@host/a?b#c')
151 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
159 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
152 path: 'a', query: 'b', fragment: 'c'>
160 path: 'a', query: 'b', fragment: 'c'>
153
161
154 File paths:
162 File paths:
155
163
156 >>> url('a/b/c/d.g.f')
164 >>> url('a/b/c/d.g.f')
157 <url path: 'a/b/c/d.g.f'>
165 <url path: 'a/b/c/d.g.f'>
158 >>> url('/x///z/y/')
166 >>> url('/x///z/y/')
159 <url path: '/x///z/y/'>
167 <url path: '/x///z/y/'>
160 >>> url('/foo:bar')
168 >>> url('/foo:bar')
161 <url path: '/foo:bar'>
169 <url path: '/foo:bar'>
162 >>> url('\\\\foo:bar')
170 >>> url('\\\\foo:bar')
163 <url path: '\\\\foo:bar'>
171 <url path: '\\\\foo:bar'>
164 >>> url('./foo:bar')
172 >>> url('./foo:bar')
165 <url path: './foo:bar'>
173 <url path: './foo:bar'>
166
174
167 Non-localhost file URL:
175 Non-localhost file URL:
168
176
169 >>> u = url('file://mercurial.selenic.com/foo')
177 >>> u = url('file://mercurial.selenic.com/foo')
170 Traceback (most recent call last):
178 Traceback (most recent call last):
171 File "<stdin>", line 1, in ?
179 File "<stdin>", line 1, in ?
172 Abort: file:// URLs can only refer to localhost
180 Abort: file:// URLs can only refer to localhost
173
181
174 Empty URL:
182 Empty URL:
175
183
176 >>> u = url('')
184 >>> u = url('')
177 >>> u
185 >>> u
178 <url path: ''>
186 <url path: ''>
179 >>> str(u)
187 >>> str(u)
180 ''
188 ''
181
189
182 Empty path with query string:
190 Empty path with query string:
183
191
184 >>> str(url('http://foo/?bar'))
192 >>> str(url('http://foo/?bar'))
185 'http://foo/?bar'
193 'http://foo/?bar'
186
194
187 Invalid path:
195 Invalid path:
188
196
189 >>> u = url('http://foo/bar')
197 >>> u = url('http://foo/bar')
190 >>> u.path = 'bar'
198 >>> u.path = 'bar'
191 >>> str(u)
199 >>> str(u)
192 'http://foo/bar'
200 'http://foo/bar'
193
201
194 >>> u = url('file:/foo/bar/baz')
202 >>> u = url('file:/foo/bar/baz')
195 >>> u
203 >>> u
196 <url scheme: 'file', path: '/foo/bar/baz'>
204 <url scheme: 'file', path: '/foo/bar/baz'>
197 >>> str(u)
205 >>> str(u)
198 'file:///foo/bar/baz'
206 'file:///foo/bar/baz'
199
207
200 >>> u = url('file:///foo/bar/baz')
208 >>> u = url('file:///foo/bar/baz')
201 >>> u
209 >>> u
202 <url scheme: 'file', path: '/foo/bar/baz'>
210 <url scheme: 'file', path: '/foo/bar/baz'>
203 >>> str(u)
211 >>> str(u)
204 'file:///foo/bar/baz'
212 'file:///foo/bar/baz'
205
213
206 >>> u = url('file:foo/bar/baz')
214 >>> u = url('file:foo/bar/baz')
207 >>> u
215 >>> u
208 <url scheme: 'file', path: 'foo/bar/baz'>
216 <url scheme: 'file', path: 'foo/bar/baz'>
209 >>> str(u)
217 >>> str(u)
210 'file:foo/bar/baz'
218 'file:foo/bar/baz'
211 """
219 """
212
220
213 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
221 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
214
215 # Unicode (IDN) certname isn't supported
216 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
217 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now