##// END OF EJS Templates
url: nuke some newly-introduced underbars in identifiers
Matt Mackall -
r13827:f1823b9f default
parent child Browse files
Show More
@@ -1,98 +1,98
1 1 # Copyright 2009, Alexander Solovyov <piranha@piranha.org.ua>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 """extend schemes with shortcuts to repository swarms
7 7
8 8 This extension allows you to specify shortcuts for parent URLs with a
9 9 lot of repositories to act like a scheme, for example::
10 10
11 11 [schemes]
12 12 py = http://code.python.org/hg/
13 13
14 14 After that you can use it like::
15 15
16 16 hg clone py://trunk/
17 17
18 18 Additionally there is support for some more complex schemas, for
19 19 example used by Google Code::
20 20
21 21 [schemes]
22 22 gcode = http://{1}.googlecode.com/hg/
23 23
24 24 The syntax is taken from Mercurial templates, and you have unlimited
25 25 number of variables, starting with ``{1}`` and continuing with
26 26 ``{2}``, ``{3}`` and so on. This variables will receive parts of URL
27 27 supplied, split by ``/``. Anything not specified as ``{part}`` will be
28 28 just appended to an URL.
29 29
30 30 For convenience, the extension adds these schemes by default::
31 31
32 32 [schemes]
33 33 py = http://hg.python.org/
34 34 bb = https://bitbucket.org/
35 35 bb+ssh = ssh://hg@bitbucket.org/
36 36 gcode = https://{1}.googlecode.com/hg/
37 37 kiln = https://{1}.kilnhg.com/Repo/
38 38
39 39 You can override a predefined scheme by defining a new scheme with the
40 40 same name.
41 41 """
42 42
43 43 import os, re
44 44 from mercurial import extensions, hg, templater, url as urlmod, util
45 45 from mercurial.i18n import _
46 46
47 47
48 48 class ShortRepository(object):
49 49 def __init__(self, url, scheme, templater):
50 50 self.scheme = scheme
51 51 self.templater = templater
52 52 self.url = url
53 53 try:
54 54 self.parts = max(map(int, re.findall(r'\{(\d+)\}', self.url)))
55 55 except ValueError:
56 56 self.parts = 0
57 57
58 58 def __repr__(self):
59 59 return '<ShortRepository: %s>' % self.scheme
60 60
61 61 def instance(self, ui, url, create):
62 62 # Should this use urlmod.url(), or is manual parsing better?
63 63 url = url.split('://', 1)[1]
64 64 parts = url.split('/', self.parts)
65 65 if len(parts) > self.parts:
66 66 tail = parts[-1]
67 67 parts = parts[:-1]
68 68 else:
69 69 tail = ''
70 70 context = dict((str(i + 1), v) for i, v in enumerate(parts))
71 71 url = ''.join(self.templater.process(self.url, context)) + tail
72 72 return hg._lookup(url).instance(ui, url, create)
73 73
74 def has_drive_letter(orig, path):
74 def hasdriveletter(orig, path):
75 75 for scheme in schemes:
76 76 if path.startswith(scheme + ':'):
77 77 return False
78 78 return orig(path)
79 79
80 80 schemes = {
81 81 'py': 'http://hg.python.org/',
82 82 'bb': 'https://bitbucket.org/',
83 83 'bb+ssh': 'ssh://hg@bitbucket.org/',
84 84 'gcode': 'https://{1}.googlecode.com/hg/',
85 85 'kiln': 'https://{1}.kilnhg.com/Repo/'
86 86 }
87 87
88 88 def extsetup(ui):
89 89 schemes.update(dict(ui.configitems('schemes')))
90 90 t = templater.engine(lambda x: x)
91 91 for scheme, url in schemes.items():
92 92 if (os.name == 'nt' and len(scheme) == 1 and scheme.isalpha()
93 93 and os.path.exists('%s:\\' % scheme)):
94 94 raise util.Abort(_('custom scheme %s:// conflicts with drive '
95 95 'letter %s:\\\n') % (scheme, scheme.upper()))
96 96 hg.schemes[scheme] = ShortRepository(url, scheme, t)
97 97
98 extensions.wrapfunction(urlmod, 'has_drive_letter', has_drive_letter)
98 extensions.wrapfunction(urlmod, 'hasdriveletter', hasdriveletter)
@@ -1,214 +1,214
1 1 # sshrepo.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import util, error, wireproto, url
10 10
11 11 class remotelock(object):
12 12 def __init__(self, repo):
13 13 self.repo = repo
14 14 def release(self):
15 15 self.repo.unlock()
16 16 self.repo = None
17 17 def __del__(self):
18 18 if self.repo:
19 19 self.release()
20 20
21 21 class sshrepository(wireproto.wirerepository):
22 22 def __init__(self, ui, path, create=0):
23 23 self._url = path
24 24 self.ui = ui
25 25
26 u = url.url(path, parse_query=False, parse_fragment=False)
26 u = url.url(path, parsequery=False, parsefragment=False)
27 27 if u.scheme != 'ssh' or not u.host or u.path is None:
28 28 self._abort(error.RepoError(_("couldn't parse location %s") % path))
29 29
30 30 self.user = u.user
31 31 if u.passwd is not None:
32 32 self._abort(error.RepoError(_("password in URL not supported")))
33 33 self.host = u.host
34 34 self.port = u.port
35 35 self.path = u.path or "."
36 36
37 37 sshcmd = self.ui.config("ui", "ssh", "ssh")
38 38 remotecmd = self.ui.config("ui", "remotecmd", "hg")
39 39
40 40 args = util.sshargs(sshcmd, self.host, self.user, self.port)
41 41
42 42 if create:
43 43 cmd = '%s %s "%s init %s"'
44 44 cmd = cmd % (sshcmd, args, remotecmd, self.path)
45 45
46 46 ui.note(_('running %s\n') % cmd)
47 47 res = util.system(cmd)
48 48 if res != 0:
49 49 self._abort(error.RepoError(_("could not create remote repo")))
50 50
51 51 self.validate_repo(ui, sshcmd, args, remotecmd)
52 52
53 53 def url(self):
54 54 return self._url
55 55
56 56 def validate_repo(self, ui, sshcmd, args, remotecmd):
57 57 # cleanup up previous run
58 58 self.cleanup()
59 59
60 60 cmd = '%s %s "%s -R %s serve --stdio"'
61 61 cmd = cmd % (sshcmd, args, remotecmd, self.path)
62 62
63 63 cmd = util.quotecommand(cmd)
64 64 ui.note(_('running %s\n') % cmd)
65 65 self.pipeo, self.pipei, self.pipee = util.popen3(cmd)
66 66
67 67 # skip any noise generated by remote shell
68 68 self._callstream("hello")
69 69 r = self._callstream("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
70 70 lines = ["", "dummy"]
71 71 max_noise = 500
72 72 while lines[-1] and max_noise:
73 73 l = r.readline()
74 74 self.readerr()
75 75 if lines[-1] == "1\n" and l == "\n":
76 76 break
77 77 if l:
78 78 ui.debug("remote: ", l)
79 79 lines.append(l)
80 80 max_noise -= 1
81 81 else:
82 82 self._abort(error.RepoError(_("no suitable response from remote hg")))
83 83
84 84 self.capabilities = set()
85 85 for l in reversed(lines):
86 86 if l.startswith("capabilities:"):
87 87 self.capabilities.update(l[:-1].split(":")[1].split())
88 88 break
89 89
90 90 def readerr(self):
91 91 while 1:
92 92 size = util.fstat(self.pipee).st_size
93 93 if size == 0:
94 94 break
95 95 s = self.pipee.read(size)
96 96 if not s:
97 97 break
98 98 for l in s.splitlines():
99 99 self.ui.status(_("remote: "), l, '\n')
100 100
101 101 def _abort(self, exception):
102 102 self.cleanup()
103 103 raise exception
104 104
105 105 def cleanup(self):
106 106 try:
107 107 self.pipeo.close()
108 108 self.pipei.close()
109 109 # read the error descriptor until EOF
110 110 for l in self.pipee:
111 111 self.ui.status(_("remote: "), l)
112 112 self.pipee.close()
113 113 except:
114 114 pass
115 115
116 116 __del__ = cleanup
117 117
118 118 def _callstream(self, cmd, **args):
119 119 self.ui.debug("sending %s command\n" % cmd)
120 120 self.pipeo.write("%s\n" % cmd)
121 121 _func, names = wireproto.commands[cmd]
122 122 keys = names.split()
123 123 wireargs = {}
124 124 for k in keys:
125 125 if k == '*':
126 126 wireargs['*'] = args
127 127 break
128 128 else:
129 129 wireargs[k] = args[k]
130 130 del args[k]
131 131 for k, v in sorted(wireargs.iteritems()):
132 132 self.pipeo.write("%s %d\n" % (k, len(v)))
133 133 if isinstance(v, dict):
134 134 for dk, dv in v.iteritems():
135 135 self.pipeo.write("%s %d\n" % (dk, len(dv)))
136 136 self.pipeo.write(dv)
137 137 else:
138 138 self.pipeo.write(v)
139 139 self.pipeo.flush()
140 140
141 141 return self.pipei
142 142
143 143 def _call(self, cmd, **args):
144 144 self._callstream(cmd, **args)
145 145 return self._recv()
146 146
147 147 def _callpush(self, cmd, fp, **args):
148 148 r = self._call(cmd, **args)
149 149 if r:
150 150 return '', r
151 151 while 1:
152 152 d = fp.read(4096)
153 153 if not d:
154 154 break
155 155 self._send(d)
156 156 self._send("", flush=True)
157 157 r = self._recv()
158 158 if r:
159 159 return '', r
160 160 return self._recv(), ''
161 161
162 162 def _decompress(self, stream):
163 163 return stream
164 164
165 165 def _recv(self):
166 166 l = self.pipei.readline()
167 167 self.readerr()
168 168 try:
169 169 l = int(l)
170 170 except:
171 171 self._abort(error.ResponseError(_("unexpected response:"), l))
172 172 return self.pipei.read(l)
173 173
174 174 def _send(self, data, flush=False):
175 175 self.pipeo.write("%d\n" % len(data))
176 176 if data:
177 177 self.pipeo.write(data)
178 178 if flush:
179 179 self.pipeo.flush()
180 180 self.readerr()
181 181
182 182 def lock(self):
183 183 self._call("lock")
184 184 return remotelock(self)
185 185
186 186 def unlock(self):
187 187 self._call("unlock")
188 188
189 189 def addchangegroup(self, cg, source, url):
190 190 '''Send a changegroup to the remote server. Return an integer
191 191 similar to unbundle(). DEPRECATED, since it requires locking the
192 192 remote.'''
193 193 d = self._call("addchangegroup")
194 194 if d:
195 195 self._abort(error.RepoError(_("push refused: %s") % d))
196 196 while 1:
197 197 d = cg.read(4096)
198 198 if not d:
199 199 break
200 200 self.pipeo.write(d)
201 201 self.readerr()
202 202
203 203 self.pipeo.flush()
204 204
205 205 self.readerr()
206 206 r = self._recv()
207 207 if not r:
208 208 return 1
209 209 try:
210 210 return int(r)
211 211 except:
212 212 self._abort(error.ResponseError(_("unexpected response:"), r))
213 213
214 214 instance = sshrepository
@@ -1,636 +1,636
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, socket, sys, tempfile, traceback
10 10 import config, util, error, url
11 11
12 12 class ui(object):
13 13 def __init__(self, src=None):
14 14 self._buffers = []
15 15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 16 self._reportuntrusted = True
17 17 self._ocfg = config.config() # overlay
18 18 self._tcfg = config.config() # trusted
19 19 self._ucfg = config.config() # untrusted
20 20 self._trustusers = set()
21 21 self._trustgroups = set()
22 22
23 23 if src:
24 24 self._tcfg = src._tcfg.copy()
25 25 self._ucfg = src._ucfg.copy()
26 26 self._ocfg = src._ocfg.copy()
27 27 self._trustusers = src._trustusers.copy()
28 28 self._trustgroups = src._trustgroups.copy()
29 29 self.environ = src.environ
30 30 self.fixconfig()
31 31 else:
32 32 # shared read-only environment
33 33 self.environ = os.environ
34 34 # we always trust global config files
35 35 for f in util.rcpath():
36 36 self.readconfig(f, trust=True)
37 37
38 38 def copy(self):
39 39 return self.__class__(self)
40 40
41 41 def _is_trusted(self, fp, f):
42 42 st = util.fstat(fp)
43 43 if util.isowner(st):
44 44 return True
45 45
46 46 tusers, tgroups = self._trustusers, self._trustgroups
47 47 if '*' in tusers or '*' in tgroups:
48 48 return True
49 49
50 50 user = util.username(st.st_uid)
51 51 group = util.groupname(st.st_gid)
52 52 if user in tusers or group in tgroups or user == util.username():
53 53 return True
54 54
55 55 if self._reportuntrusted:
56 56 self.warn(_('Not trusting file %s from untrusted '
57 57 'user %s, group %s\n') % (f, user, group))
58 58 return False
59 59
60 60 def readconfig(self, filename, root=None, trust=False,
61 61 sections=None, remap=None):
62 62 try:
63 63 fp = open(filename)
64 64 except IOError:
65 65 if not sections: # ignore unless we were looking for something
66 66 return
67 67 raise
68 68
69 69 cfg = config.config()
70 70 trusted = sections or trust or self._is_trusted(fp, filename)
71 71
72 72 try:
73 73 cfg.read(filename, fp, sections=sections, remap=remap)
74 74 except error.ConfigError, inst:
75 75 if trusted:
76 76 raise
77 77 self.warn(_("Ignored: %s\n") % str(inst))
78 78
79 79 if self.plain():
80 80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
81 81 'logtemplate', 'style',
82 82 'traceback', 'verbose'):
83 83 if k in cfg['ui']:
84 84 del cfg['ui'][k]
85 85 for k, v in cfg.items('alias'):
86 86 del cfg['alias'][k]
87 87 for k, v in cfg.items('defaults'):
88 88 del cfg['defaults'][k]
89 89
90 90 if trusted:
91 91 self._tcfg.update(cfg)
92 92 self._tcfg.update(self._ocfg)
93 93 self._ucfg.update(cfg)
94 94 self._ucfg.update(self._ocfg)
95 95
96 96 if root is None:
97 97 root = os.path.expanduser('~')
98 98 self.fixconfig(root=root)
99 99
100 100 def fixconfig(self, root=None, section=None):
101 101 if section in (None, 'paths'):
102 102 # expand vars and ~
103 103 # translate paths relative to root (or home) into absolute paths
104 104 root = root or os.getcwd()
105 105 for c in self._tcfg, self._ucfg, self._ocfg:
106 106 for n, p in c.items('paths'):
107 107 if not p:
108 108 continue
109 109 if '%%' in p:
110 110 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
111 111 % (n, p, self.configsource('paths', n)))
112 112 p = p.replace('%%', '%')
113 113 p = util.expandpath(p)
114 if not url.has_scheme(p) and not os.path.isabs(p):
114 if not url.hasscheme(p) and not os.path.isabs(p):
115 115 p = os.path.normpath(os.path.join(root, p))
116 116 c.set("paths", n, p)
117 117
118 118 if section in (None, 'ui'):
119 119 # update ui options
120 120 self.debugflag = self.configbool('ui', 'debug')
121 121 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
122 122 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
123 123 if self.verbose and self.quiet:
124 124 self.quiet = self.verbose = False
125 125 self._reportuntrusted = self.debugflag or self.configbool("ui",
126 126 "report_untrusted", True)
127 127 self.tracebackflag = self.configbool('ui', 'traceback', False)
128 128
129 129 if section in (None, 'trusted'):
130 130 # update trust information
131 131 self._trustusers.update(self.configlist('trusted', 'users'))
132 132 self._trustgroups.update(self.configlist('trusted', 'groups'))
133 133
134 134 def setconfig(self, section, name, value, overlay=True):
135 135 if overlay:
136 136 self._ocfg.set(section, name, value)
137 137 self._tcfg.set(section, name, value)
138 138 self._ucfg.set(section, name, value)
139 139 self.fixconfig(section=section)
140 140
141 141 def _data(self, untrusted):
142 142 return untrusted and self._ucfg or self._tcfg
143 143
144 144 def configsource(self, section, name, untrusted=False):
145 145 return self._data(untrusted).source(section, name) or 'none'
146 146
147 147 def config(self, section, name, default=None, untrusted=False):
148 148 value = self._data(untrusted).get(section, name, default)
149 149 if self.debugflag and not untrusted and self._reportuntrusted:
150 150 uvalue = self._ucfg.get(section, name)
151 151 if uvalue is not None and uvalue != value:
152 152 self.debug(_("ignoring untrusted configuration option "
153 153 "%s.%s = %s\n") % (section, name, uvalue))
154 154 return value
155 155
156 156 def configpath(self, section, name, default=None, untrusted=False):
157 157 'get a path config item, expanded relative to config file'
158 158 v = self.config(section, name, default, untrusted)
159 159 if not os.path.isabs(v) or "://" not in v:
160 160 src = self.configsource(section, name, untrusted)
161 161 if ':' in src:
162 162 base = os.path.dirname(src.rsplit(':'))
163 163 v = os.path.join(base, os.path.expanduser(v))
164 164 return v
165 165
166 166 def configbool(self, section, name, default=False, untrusted=False):
167 167 v = self.config(section, name, None, untrusted)
168 168 if v is None:
169 169 return default
170 170 if isinstance(v, bool):
171 171 return v
172 172 b = util.parsebool(v)
173 173 if b is None:
174 174 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
175 175 % (section, name, v))
176 176 return b
177 177
178 178 def configlist(self, section, name, default=None, untrusted=False):
179 179 """Return a list of comma/space separated strings"""
180 180
181 181 def _parse_plain(parts, s, offset):
182 182 whitespace = False
183 183 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
184 184 whitespace = True
185 185 offset += 1
186 186 if offset >= len(s):
187 187 return None, parts, offset
188 188 if whitespace:
189 189 parts.append('')
190 190 if s[offset] == '"' and not parts[-1]:
191 191 return _parse_quote, parts, offset + 1
192 192 elif s[offset] == '"' and parts[-1][-1] == '\\':
193 193 parts[-1] = parts[-1][:-1] + s[offset]
194 194 return _parse_plain, parts, offset + 1
195 195 parts[-1] += s[offset]
196 196 return _parse_plain, parts, offset + 1
197 197
198 198 def _parse_quote(parts, s, offset):
199 199 if offset < len(s) and s[offset] == '"': # ""
200 200 parts.append('')
201 201 offset += 1
202 202 while offset < len(s) and (s[offset].isspace() or
203 203 s[offset] == ','):
204 204 offset += 1
205 205 return _parse_plain, parts, offset
206 206
207 207 while offset < len(s) and s[offset] != '"':
208 208 if (s[offset] == '\\' and offset + 1 < len(s)
209 209 and s[offset + 1] == '"'):
210 210 offset += 1
211 211 parts[-1] += '"'
212 212 else:
213 213 parts[-1] += s[offset]
214 214 offset += 1
215 215
216 216 if offset >= len(s):
217 217 real_parts = _configlist(parts[-1])
218 218 if not real_parts:
219 219 parts[-1] = '"'
220 220 else:
221 221 real_parts[0] = '"' + real_parts[0]
222 222 parts = parts[:-1]
223 223 parts.extend(real_parts)
224 224 return None, parts, offset
225 225
226 226 offset += 1
227 227 while offset < len(s) and s[offset] in [' ', ',']:
228 228 offset += 1
229 229
230 230 if offset < len(s):
231 231 if offset + 1 == len(s) and s[offset] == '"':
232 232 parts[-1] += '"'
233 233 offset += 1
234 234 else:
235 235 parts.append('')
236 236 else:
237 237 return None, parts, offset
238 238
239 239 return _parse_plain, parts, offset
240 240
241 241 def _configlist(s):
242 242 s = s.rstrip(' ,')
243 243 if not s:
244 244 return []
245 245 parser, parts, offset = _parse_plain, [''], 0
246 246 while parser:
247 247 parser, parts, offset = parser(parts, s, offset)
248 248 return parts
249 249
250 250 result = self.config(section, name, untrusted=untrusted)
251 251 if result is None:
252 252 result = default or []
253 253 if isinstance(result, basestring):
254 254 result = _configlist(result.lstrip(' ,\n'))
255 255 if result is None:
256 256 result = default or []
257 257 return result
258 258
259 259 def has_section(self, section, untrusted=False):
260 260 '''tell whether section exists in config.'''
261 261 return section in self._data(untrusted)
262 262
263 263 def configitems(self, section, untrusted=False):
264 264 items = self._data(untrusted).items(section)
265 265 if self.debugflag and not untrusted and self._reportuntrusted:
266 266 for k, v in self._ucfg.items(section):
267 267 if self._tcfg.get(section, k) != v:
268 268 self.debug(_("ignoring untrusted configuration option "
269 269 "%s.%s = %s\n") % (section, k, v))
270 270 return items
271 271
272 272 def walkconfig(self, untrusted=False):
273 273 cfg = self._data(untrusted)
274 274 for section in cfg.sections():
275 275 for name, value in self.configitems(section, untrusted):
276 276 yield section, name, value
277 277
278 278 def plain(self):
279 279 '''is plain mode active?
280 280
281 281 Plain mode means that all configuration variables which affect the
282 282 behavior and output of Mercurial should be ignored. Additionally, the
283 283 output should be stable, reproducible and suitable for use in scripts or
284 284 applications.
285 285
286 286 The only way to trigger plain mode is by setting the `HGPLAIN'
287 287 environment variable.
288 288 '''
289 289 return 'HGPLAIN' in os.environ
290 290
291 291 def username(self):
292 292 """Return default username to be used in commits.
293 293
294 294 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
295 295 and stop searching if one of these is set.
296 296 If not found and ui.askusername is True, ask the user, else use
297 297 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
298 298 """
299 299 user = os.environ.get("HGUSER")
300 300 if user is None:
301 301 user = self.config("ui", "username")
302 302 if user is not None:
303 303 user = os.path.expandvars(user)
304 304 if user is None:
305 305 user = os.environ.get("EMAIL")
306 306 if user is None and self.configbool("ui", "askusername"):
307 307 user = self.prompt(_("enter a commit username:"), default=None)
308 308 if user is None and not self.interactive():
309 309 try:
310 310 user = '%s@%s' % (util.getuser(), socket.getfqdn())
311 311 self.warn(_("No username found, using '%s' instead\n") % user)
312 312 except KeyError:
313 313 pass
314 314 if not user:
315 315 raise util.Abort(_('no username supplied (see "hg help config")'))
316 316 if "\n" in user:
317 317 raise util.Abort(_("username %s contains a newline\n") % repr(user))
318 318 return user
319 319
320 320 def shortuser(self, user):
321 321 """Return a short representation of a user name or email address."""
322 322 if not self.verbose:
323 323 user = util.shortuser(user)
324 324 return user
325 325
326 326 def expandpath(self, loc, default=None):
327 327 """Return repository location relative to cwd or from [paths]"""
328 if url.has_scheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
328 if url.hasscheme(loc) or os.path.isdir(os.path.join(loc, '.hg')):
329 329 return loc
330 330
331 331 path = self.config('paths', loc)
332 332 if not path and default is not None:
333 333 path = self.config('paths', default)
334 334 return path or loc
335 335
336 336 def pushbuffer(self):
337 337 self._buffers.append([])
338 338
339 339 def popbuffer(self, labeled=False):
340 340 '''pop the last buffer and return the buffered output
341 341
342 342 If labeled is True, any labels associated with buffered
343 343 output will be handled. By default, this has no effect
344 344 on the output returned, but extensions and GUI tools may
345 345 handle this argument and returned styled output. If output
346 346 is being buffered so it can be captured and parsed or
347 347 processed, labeled should not be set to True.
348 348 '''
349 349 return "".join(self._buffers.pop())
350 350
351 351 def write(self, *args, **opts):
352 352 '''write args to output
353 353
354 354 By default, this method simply writes to the buffer or stdout,
355 355 but extensions or GUI tools may override this method,
356 356 write_err(), popbuffer(), and label() to style output from
357 357 various parts of hg.
358 358
359 359 An optional keyword argument, "label", can be passed in.
360 360 This should be a string containing label names separated by
361 361 space. Label names take the form of "topic.type". For example,
362 362 ui.debug() issues a label of "ui.debug".
363 363
364 364 When labeling output for a specific command, a label of
365 365 "cmdname.type" is recommended. For example, status issues
366 366 a label of "status.modified" for modified files.
367 367 '''
368 368 if self._buffers:
369 369 self._buffers[-1].extend([str(a) for a in args])
370 370 else:
371 371 for a in args:
372 372 sys.stdout.write(str(a))
373 373
374 374 def write_err(self, *args, **opts):
375 375 try:
376 376 if not getattr(sys.stdout, 'closed', False):
377 377 sys.stdout.flush()
378 378 for a in args:
379 379 sys.stderr.write(str(a))
380 380 # stderr may be buffered under win32 when redirected to files,
381 381 # including stdout.
382 382 if not getattr(sys.stderr, 'closed', False):
383 383 sys.stderr.flush()
384 384 except IOError, inst:
385 385 if inst.errno not in (errno.EPIPE, errno.EIO):
386 386 raise
387 387
388 388 def flush(self):
389 389 try: sys.stdout.flush()
390 390 except: pass
391 391 try: sys.stderr.flush()
392 392 except: pass
393 393
394 394 def interactive(self):
395 395 '''is interactive input allowed?
396 396
397 397 An interactive session is a session where input can be reasonably read
398 398 from `sys.stdin'. If this function returns false, any attempt to read
399 399 from stdin should fail with an error, unless a sensible default has been
400 400 specified.
401 401
402 402 Interactiveness is triggered by the value of the `ui.interactive'
403 403 configuration variable or - if it is unset - when `sys.stdin' points
404 404 to a terminal device.
405 405
406 406 This function refers to input only; for output, see `ui.formatted()'.
407 407 '''
408 408 i = self.configbool("ui", "interactive", None)
409 409 if i is None:
410 410 try:
411 411 return sys.stdin.isatty()
412 412 except AttributeError:
413 413 # some environments replace stdin without implementing isatty
414 414 # usually those are non-interactive
415 415 return False
416 416
417 417 return i
418 418
419 419 def termwidth(self):
420 420 '''how wide is the terminal in columns?
421 421 '''
422 422 if 'COLUMNS' in os.environ:
423 423 try:
424 424 return int(os.environ['COLUMNS'])
425 425 except ValueError:
426 426 pass
427 427 return util.termwidth()
428 428
429 429 def formatted(self):
430 430 '''should formatted output be used?
431 431
432 432 It is often desirable to format the output to suite the output medium.
433 433 Examples of this are truncating long lines or colorizing messages.
434 434 However, this is not often not desirable when piping output into other
435 435 utilities, e.g. `grep'.
436 436
437 437 Formatted output is triggered by the value of the `ui.formatted'
438 438 configuration variable or - if it is unset - when `sys.stdout' points
439 439 to a terminal device. Please note that `ui.formatted' should be
440 440 considered an implementation detail; it is not intended for use outside
441 441 Mercurial or its extensions.
442 442
443 443 This function refers to output only; for input, see `ui.interactive()'.
444 444 This function always returns false when in plain mode, see `ui.plain()'.
445 445 '''
446 446 if self.plain():
447 447 return False
448 448
449 449 i = self.configbool("ui", "formatted", None)
450 450 if i is None:
451 451 try:
452 452 return sys.stdout.isatty()
453 453 except AttributeError:
454 454 # some environments replace stdout without implementing isatty
455 455 # usually those are non-interactive
456 456 return False
457 457
458 458 return i
459 459
460 460 def _readline(self, prompt=''):
461 461 if sys.stdin.isatty():
462 462 try:
463 463 # magically add command line editing support, where
464 464 # available
465 465 import readline
466 466 # force demandimport to really load the module
467 467 readline.read_history_file
468 468 # windows sometimes raises something other than ImportError
469 469 except Exception:
470 470 pass
471 471 line = raw_input(prompt)
472 472 # When stdin is in binary mode on Windows, it can cause
473 473 # raw_input() to emit an extra trailing carriage return
474 474 if os.linesep == '\r\n' and line and line[-1] == '\r':
475 475 line = line[:-1]
476 476 return line
477 477
478 478 def prompt(self, msg, default="y"):
479 479 """Prompt user with msg, read response.
480 480 If ui is not interactive, the default is returned.
481 481 """
482 482 if not self.interactive():
483 483 self.write(msg, ' ', default, "\n")
484 484 return default
485 485 try:
486 486 r = self._readline(self.label(msg, 'ui.prompt') + ' ')
487 487 if not r:
488 488 return default
489 489 return r
490 490 except EOFError:
491 491 raise util.Abort(_('response expected'))
492 492
493 493 def promptchoice(self, msg, choices, default=0):
494 494 """Prompt user with msg, read response, and ensure it matches
495 495 one of the provided choices. The index of the choice is returned.
496 496 choices is a sequence of acceptable responses with the format:
497 497 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
498 498 If ui is not interactive, the default is returned.
499 499 """
500 500 resps = [s[s.index('&')+1].lower() for s in choices]
501 501 while True:
502 502 r = self.prompt(msg, resps[default])
503 503 if r.lower() in resps:
504 504 return resps.index(r.lower())
505 505 self.write(_("unrecognized response\n"))
506 506
507 507 def getpass(self, prompt=None, default=None):
508 508 if not self.interactive():
509 509 return default
510 510 try:
511 511 return getpass.getpass(prompt or _('password: '))
512 512 except EOFError:
513 513 raise util.Abort(_('response expected'))
514 514 def status(self, *msg, **opts):
515 515 '''write status message to output (if ui.quiet is False)
516 516
517 517 This adds an output label of "ui.status".
518 518 '''
519 519 if not self.quiet:
520 520 opts['label'] = opts.get('label', '') + ' ui.status'
521 521 self.write(*msg, **opts)
522 522 def warn(self, *msg, **opts):
523 523 '''write warning message to output (stderr)
524 524
525 525 This adds an output label of "ui.warning".
526 526 '''
527 527 opts['label'] = opts.get('label', '') + ' ui.warning'
528 528 self.write_err(*msg, **opts)
529 529 def note(self, *msg, **opts):
530 530 '''write note to output (if ui.verbose is True)
531 531
532 532 This adds an output label of "ui.note".
533 533 '''
534 534 if self.verbose:
535 535 opts['label'] = opts.get('label', '') + ' ui.note'
536 536 self.write(*msg, **opts)
537 537 def debug(self, *msg, **opts):
538 538 '''write debug message to output (if ui.debugflag is True)
539 539
540 540 This adds an output label of "ui.debug".
541 541 '''
542 542 if self.debugflag:
543 543 opts['label'] = opts.get('label', '') + ' ui.debug'
544 544 self.write(*msg, **opts)
545 545 def edit(self, text, user):
546 546 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
547 547 text=True)
548 548 try:
549 549 f = os.fdopen(fd, "w")
550 550 f.write(text)
551 551 f.close()
552 552
553 553 editor = self.geteditor()
554 554
555 555 util.system("%s \"%s\"" % (editor, name),
556 556 environ={'HGUSER': user},
557 557 onerr=util.Abort, errprefix=_("edit failed"))
558 558
559 559 f = open(name)
560 560 t = f.read()
561 561 f.close()
562 562 finally:
563 563 os.unlink(name)
564 564
565 565 return t
566 566
567 567 def traceback(self, exc=None):
568 568 '''print exception traceback if traceback printing enabled.
569 569 only to call in exception handler. returns true if traceback
570 570 printed.'''
571 571 if self.tracebackflag:
572 572 if exc:
573 573 traceback.print_exception(exc[0], exc[1], exc[2])
574 574 else:
575 575 traceback.print_exc()
576 576 return self.tracebackflag
577 577
578 578 def geteditor(self):
579 579 '''return editor to use'''
580 580 return (os.environ.get("HGEDITOR") or
581 581 self.config("ui", "editor") or
582 582 os.environ.get("VISUAL") or
583 583 os.environ.get("EDITOR", "vi"))
584 584
585 585 def progress(self, topic, pos, item="", unit="", total=None):
586 586 '''show a progress message
587 587
588 588 With stock hg, this is simply a debug message that is hidden
589 589 by default, but with extensions or GUI tools it may be
590 590 visible. 'topic' is the current operation, 'item' is a
591 591 non-numeric marker of the current position (ie the currently
592 592 in-process file), 'pos' is the current numeric position (ie
593 593 revision, bytes, etc.), unit is a corresponding unit label,
594 594 and total is the highest expected pos.
595 595
596 596 Multiple nested topics may be active at a time.
597 597
598 598 All topics should be marked closed by setting pos to None at
599 599 termination.
600 600 '''
601 601
602 602 if pos is None or not self.debugflag:
603 603 return
604 604
605 605 if unit:
606 606 unit = ' ' + unit
607 607 if item:
608 608 item = ' ' + item
609 609
610 610 if total:
611 611 pct = 100.0 * pos / total
612 612 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
613 613 % (topic, item, pos, total, unit, pct))
614 614 else:
615 615 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
616 616
617 617 def log(self, service, message):
618 618 '''hook for logging facility extensions
619 619
620 620 service should be a readily-identifiable subsystem, which will
621 621 allow filtering.
622 622 message should be a newline-terminated string to log.
623 623 '''
624 624 pass
625 625
626 626 def label(self, msg, label):
627 627 '''style msg based on supplied label
628 628
629 629 Like ui.write(), this just returns msg unchanged, but extensions
630 630 and GUI tools can override it to allow styling output without
631 631 writing it.
632 632
633 633 ui.write(s, 'label') is equivalent to
634 634 ui.write(ui.label(s, 'label')).
635 635 '''
636 636 return msg
@@ -1,960 +1,960
1 1 # url.py - HTTP handling for mercurial
2 2 #
3 3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import urllib, urllib2, httplib, os, socket, cStringIO
11 11 import __builtin__
12 12 from i18n import _
13 13 import keepalive, util
14 14
15 15 class url(object):
16 16 """Reliable URL parser.
17 17
18 18 This parses URLs and provides attributes for the following
19 19 components:
20 20
21 21 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
22 22
23 23 Missing components are set to None. The only exception is
24 24 fragment, which is set to '' if present but empty.
25 25
26 If parse_fragment is False, fragment is included in query. If
27 parse_query is False, query is included in path. If both are
26 If parsefragment is False, fragment is included in query. If
27 parsequery is False, query is included in path. If both are
28 28 False, both fragment and query are included in path.
29 29
30 30 See http://www.ietf.org/rfc/rfc2396.txt for more information.
31 31
32 32 Note that for backward compatibility reasons, bundle URLs do not
33 33 take host names. That means 'bundle://../' has a path of '../'.
34 34
35 35 Examples:
36 36
37 37 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
38 38 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
39 39 >>> url('ssh://[::1]:2200//home/joe/repo')
40 40 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
41 41 >>> url('file:///home/joe/repo')
42 42 <url scheme: 'file', path: '/home/joe/repo'>
43 43 >>> url('bundle:foo')
44 44 <url scheme: 'bundle', path: 'foo'>
45 45 >>> url('bundle://../foo')
46 46 <url scheme: 'bundle', path: '../foo'>
47 47 >>> url('c:\\\\foo\\\\bar')
48 48 <url path: 'c:\\\\foo\\\\bar'>
49 49
50 50 Authentication credentials:
51 51
52 52 >>> url('ssh://joe:xyz@x/repo')
53 53 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
54 54 >>> url('ssh://joe@x/repo')
55 55 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
56 56
57 57 Query strings and fragments:
58 58
59 59 >>> url('http://host/a?b#c')
60 60 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
61 >>> url('http://host/a?b#c', parse_query=False, parse_fragment=False)
61 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
62 62 <url scheme: 'http', host: 'host', path: 'a?b#c'>
63 63 """
64 64
65 65 _safechars = "!~*'()+"
66 66 _safepchars = "/!~*'()+"
67 67
68 def __init__(self, path, parse_query=True, parse_fragment=True):
68 def __init__(self, path, parsequery=True, parsefragment=True):
69 69 # We slowly chomp away at path until we have only the path left
70 70 self.scheme = self.user = self.passwd = self.host = None
71 71 self.port = self.path = self.query = self.fragment = None
72 72 self._localpath = True
73 73 self._hostport = ''
74 74 self._origpath = path
75 75
76 76 # special case for Windows drive letters
77 if has_drive_letter(path):
77 if hasdriveletter(path):
78 78 self.path = path
79 79 return
80 80
81 81 # For compatibility reasons, we can't handle bundle paths as
82 82 # normal URLS
83 83 if path.startswith('bundle:'):
84 84 self.scheme = 'bundle'
85 85 path = path[7:]
86 86 if path.startswith('//'):
87 87 path = path[2:]
88 88 self.path = path
89 89 return
90 90
91 91 if not path.startswith('/') and ':' in path:
92 92 parts = path.split(':', 1)
93 93 if parts[0]:
94 94 self.scheme, path = parts
95 95 self._localpath = False
96 96
97 97 if not path:
98 98 path = None
99 99 if self._localpath:
100 100 self.path = ''
101 101 return
102 102 else:
103 if parse_fragment and '#' in path:
103 if parsefragment and '#' in path:
104 104 path, self.fragment = path.split('#', 1)
105 105 if not path:
106 106 path = None
107 107 if self._localpath:
108 108 self.path = path
109 109 return
110 110
111 if parse_query and '?' in path:
111 if parsequery and '?' in path:
112 112 path, self.query = path.split('?', 1)
113 113 if not path:
114 114 path = None
115 115 if not self.query:
116 116 self.query = None
117 117
118 118 # // is required to specify a host/authority
119 119 if path and path.startswith('//'):
120 120 parts = path[2:].split('/', 1)
121 121 if len(parts) > 1:
122 122 self.host, path = parts
123 123 path = path
124 124 else:
125 125 self.host = parts[0]
126 126 path = None
127 127 if not self.host:
128 128 self.host = None
129 129 if path:
130 130 path = '/' + path
131 131
132 132 if self.host and '@' in self.host:
133 133 self.user, self.host = self.host.rsplit('@', 1)
134 134 if ':' in self.user:
135 135 self.user, self.passwd = self.user.split(':', 1)
136 136 if not self.host:
137 137 self.host = None
138 138
139 139 # Don't split on colons in IPv6 addresses without ports
140 140 if (self.host and ':' in self.host and
141 141 not (self.host.startswith('[') and self.host.endswith(']'))):
142 142 self._hostport = self.host
143 143 self.host, self.port = self.host.rsplit(':', 1)
144 144 if not self.host:
145 145 self.host = None
146 146
147 147 if (self.host and self.scheme == 'file' and
148 148 self.host not in ('localhost', '127.0.0.1', '[::1]')):
149 149 raise util.Abort(_('file:// URLs can only refer to localhost'))
150 150
151 151 self.path = path
152 152
153 153 for a in ('user', 'passwd', 'host', 'port',
154 154 'path', 'query', 'fragment'):
155 155 v = getattr(self, a)
156 156 if v is not None:
157 157 setattr(self, a, urllib.unquote(v))
158 158
159 159 def __repr__(self):
160 160 attrs = []
161 161 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
162 162 'query', 'fragment'):
163 163 v = getattr(self, a)
164 164 if v is not None:
165 165 attrs.append('%s: %r' % (a, v))
166 166 return '<url %s>' % ', '.join(attrs)
167 167
168 168 def __str__(self):
169 169 """Join the URL's components back into a URL string.
170 170
171 171 Examples:
172 172
173 173 >>> str(url('http://user:pw@host:80/?foo#bar'))
174 174 'http://user:pw@host:80/?foo#bar'
175 175 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
176 176 'ssh://user:pw@[::1]:2200//home/joe#'
177 177 >>> str(url('http://localhost:80//'))
178 178 'http://localhost:80//'
179 179 >>> str(url('http://localhost:80/'))
180 180 'http://localhost:80/'
181 181 >>> str(url('http://localhost:80'))
182 182 'http://localhost:80/'
183 183 >>> str(url('bundle:foo'))
184 184 'bundle:foo'
185 185 >>> str(url('bundle://../foo'))
186 186 'bundle:../foo'
187 187 >>> str(url('path'))
188 188 'path'
189 189 """
190 190 if self._localpath:
191 191 s = self.path
192 192 if self.scheme == 'bundle':
193 193 s = 'bundle:' + s
194 194 if self.fragment:
195 195 s += '#' + self.fragment
196 196 return s
197 197
198 198 s = self.scheme + ':'
199 199 if (self.user or self.passwd or self.host or
200 200 self.scheme and not self.path):
201 201 s += '//'
202 202 if self.user:
203 203 s += urllib.quote(self.user, safe=self._safechars)
204 204 if self.passwd:
205 205 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
206 206 if self.user or self.passwd:
207 207 s += '@'
208 208 if self.host:
209 209 if not (self.host.startswith('[') and self.host.endswith(']')):
210 210 s += urllib.quote(self.host)
211 211 else:
212 212 s += self.host
213 213 if self.port:
214 214 s += ':' + urllib.quote(self.port)
215 215 if self.host:
216 216 s += '/'
217 217 if self.path:
218 218 s += urllib.quote(self.path, safe=self._safepchars)
219 219 if self.query:
220 220 s += '?' + urllib.quote(self.query, safe=self._safepchars)
221 221 if self.fragment is not None:
222 222 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
223 223 return s
224 224
225 225 def authinfo(self):
226 226 user, passwd = self.user, self.passwd
227 227 try:
228 228 self.user, self.passwd = None, None
229 229 s = str(self)
230 230 finally:
231 231 self.user, self.passwd = user, passwd
232 232 if not self.user:
233 233 return (s, None)
234 234 return (s, (None, (str(self), self.host),
235 235 self.user, self.passwd or ''))
236 236
237 237 def localpath(self):
238 238 if self.scheme == 'file' or self.scheme == 'bundle':
239 239 path = self.path or '/'
240 240 # For Windows, we need to promote hosts containing drive
241 241 # letters to paths with drive letters.
242 if has_drive_letter(self._hostport):
242 if hasdriveletter(self._hostport):
243 243 path = self._hostport + '/' + self.path
244 244 elif self.host is not None and self.path:
245 245 path = '/' + path
246 246 # We also need to handle the case of file:///C:/, which
247 247 # should return C:/, not /C:/.
248 elif has_drive_letter(path):
248 elif hasdriveletter(path):
249 249 # Strip leading slash from paths with drive names
250 250 return path[1:]
251 251 return path
252 252 return self._origpath
253 253
254 def has_scheme(path):
254 def hasscheme(path):
255 255 return bool(url(path).scheme)
256 256
257 def has_drive_letter(path):
257 def hasdriveletter(path):
258 258 return path[1:2] == ':' and path[0:1].isalpha()
259 259
260 260 def localpath(path):
261 return url(path, parse_query=False, parse_fragment=False).localpath()
261 return url(path, parsequery=False, parsefragment=False).localpath()
262 262
263 263 def hidepassword(u):
264 264 '''hide user credential in a url string'''
265 265 u = url(u)
266 266 if u.passwd:
267 267 u.passwd = '***'
268 268 return str(u)
269 269
270 270 def removeauth(u):
271 271 '''remove all authentication information from a url string'''
272 272 u = url(u)
273 273 u.user = u.passwd = None
274 274 return str(u)
275 275
276 276 def netlocsplit(netloc):
277 277 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
278 278
279 279 a = netloc.find('@')
280 280 if a == -1:
281 281 user, passwd = None, None
282 282 else:
283 283 userpass, netloc = netloc[:a], netloc[a + 1:]
284 284 c = userpass.find(':')
285 285 if c == -1:
286 286 user, passwd = urllib.unquote(userpass), None
287 287 else:
288 288 user = urllib.unquote(userpass[:c])
289 289 passwd = urllib.unquote(userpass[c + 1:])
290 290 c = netloc.find(':')
291 291 if c == -1:
292 292 host, port = netloc, None
293 293 else:
294 294 host, port = netloc[:c], netloc[c + 1:]
295 295 return host, port, user, passwd
296 296
297 297 def netlocunsplit(host, port, user=None, passwd=None):
298 298 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
299 299 if port:
300 300 hostport = host + ':' + port
301 301 else:
302 302 hostport = host
303 303 if user:
304 304 quote = lambda s: urllib.quote(s, safe='')
305 305 if passwd:
306 306 userpass = quote(user) + ':' + quote(passwd)
307 307 else:
308 308 userpass = quote(user)
309 309 return userpass + '@' + hostport
310 310 return hostport
311 311
312 312 def readauthforuri(ui, uri):
313 313 # Read configuration
314 314 config = dict()
315 315 for key, val in ui.configitems('auth'):
316 316 if '.' not in key:
317 317 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
318 318 continue
319 319 group, setting = key.rsplit('.', 1)
320 320 gdict = config.setdefault(group, dict())
321 321 if setting in ('username', 'cert', 'key'):
322 322 val = util.expandpath(val)
323 323 gdict[setting] = val
324 324
325 325 # Find the best match
326 326 scheme, hostpath = uri.split('://', 1)
327 327 bestlen = 0
328 328 bestauth = None
329 329 for group, auth in config.iteritems():
330 330 prefix = auth.get('prefix')
331 331 if not prefix:
332 332 continue
333 333 p = prefix.split('://', 1)
334 334 if len(p) > 1:
335 335 schemes, prefix = [p[0]], p[1]
336 336 else:
337 337 schemes = (auth.get('schemes') or 'https').split()
338 338 if (prefix == '*' or hostpath.startswith(prefix)) and \
339 339 len(prefix) > bestlen and scheme in schemes:
340 340 bestlen = len(prefix)
341 341 bestauth = group, auth
342 342 return bestauth
343 343
344 344 _safe = ('abcdefghijklmnopqrstuvwxyz'
345 345 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
346 346 '0123456789' '_.-/')
347 347 _safeset = None
348 348 _hex = None
349 349 def quotepath(path):
350 350 '''quote the path part of a URL
351 351
352 352 This is similar to urllib.quote, but it also tries to avoid
353 353 quoting things twice (inspired by wget):
354 354
355 355 >>> quotepath('abc def')
356 356 'abc%20def'
357 357 >>> quotepath('abc%20def')
358 358 'abc%20def'
359 359 >>> quotepath('abc%20 def')
360 360 'abc%20%20def'
361 361 >>> quotepath('abc def%20')
362 362 'abc%20def%20'
363 363 >>> quotepath('abc def%2')
364 364 'abc%20def%252'
365 365 >>> quotepath('abc def%')
366 366 'abc%20def%25'
367 367 '''
368 368 global _safeset, _hex
369 369 if _safeset is None:
370 370 _safeset = set(_safe)
371 371 _hex = set('abcdefABCDEF0123456789')
372 372 l = list(path)
373 373 for i in xrange(len(l)):
374 374 c = l[i]
375 375 if (c == '%' and i + 2 < len(l) and
376 376 l[i + 1] in _hex and l[i + 2] in _hex):
377 377 pass
378 378 elif c not in _safeset:
379 379 l[i] = '%%%02X' % ord(c)
380 380 return ''.join(l)
381 381
382 382 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
383 383 def __init__(self, ui):
384 384 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
385 385 self.ui = ui
386 386
387 387 def find_user_password(self, realm, authuri):
388 388 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
389 389 self, realm, authuri)
390 390 user, passwd = authinfo
391 391 if user and passwd:
392 392 self._writedebug(user, passwd)
393 393 return (user, passwd)
394 394
395 395 if not user:
396 396 res = readauthforuri(self.ui, authuri)
397 397 if res:
398 398 group, auth = res
399 399 user, passwd = auth.get('username'), auth.get('password')
400 400 self.ui.debug("using auth.%s.* for authentication\n" % group)
401 401 if not user or not passwd:
402 402 if not self.ui.interactive():
403 403 raise util.Abort(_('http authorization required'))
404 404
405 405 self.ui.write(_("http authorization required\n"))
406 406 self.ui.write(_("realm: %s\n") % realm)
407 407 if user:
408 408 self.ui.write(_("user: %s\n") % user)
409 409 else:
410 410 user = self.ui.prompt(_("user:"), default=None)
411 411
412 412 if not passwd:
413 413 passwd = self.ui.getpass()
414 414
415 415 self.add_password(realm, authuri, user, passwd)
416 416 self._writedebug(user, passwd)
417 417 return (user, passwd)
418 418
419 419 def _writedebug(self, user, passwd):
420 420 msg = _('http auth: user %s, password %s\n')
421 421 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
422 422
423 423 class proxyhandler(urllib2.ProxyHandler):
424 424 def __init__(self, ui):
425 425 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
426 426 # XXX proxyauthinfo = None
427 427
428 428 if proxyurl:
429 429 # proxy can be proper url or host[:port]
430 430 if not (proxyurl.startswith('http:') or
431 431 proxyurl.startswith('https:')):
432 432 proxyurl = 'http://' + proxyurl + '/'
433 433 proxy = url(proxyurl)
434 434 if not proxy.user:
435 435 proxy.user = ui.config("http_proxy", "user")
436 436 proxy.passwd = ui.config("http_proxy", "passwd")
437 437
438 438 # see if we should use a proxy for this url
439 439 no_list = ["localhost", "127.0.0.1"]
440 440 no_list.extend([p.lower() for
441 441 p in ui.configlist("http_proxy", "no")])
442 442 no_list.extend([p.strip().lower() for
443 443 p in os.getenv("no_proxy", '').split(',')
444 444 if p.strip()])
445 445 # "http_proxy.always" config is for running tests on localhost
446 446 if ui.configbool("http_proxy", "always"):
447 447 self.no_list = []
448 448 else:
449 449 self.no_list = no_list
450 450
451 451 proxyurl = str(proxy)
452 452 proxies = {'http': proxyurl, 'https': proxyurl}
453 453 ui.debug('proxying through http://%s:%s\n' %
454 454 (proxy.host, proxy.port))
455 455 else:
456 456 proxies = {}
457 457
458 458 # urllib2 takes proxy values from the environment and those
459 459 # will take precedence if found, so drop them
460 460 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
461 461 try:
462 462 if env in os.environ:
463 463 del os.environ[env]
464 464 except OSError:
465 465 pass
466 466
467 467 urllib2.ProxyHandler.__init__(self, proxies)
468 468 self.ui = ui
469 469
470 470 def proxy_open(self, req, proxy, type_):
471 471 host = req.get_host().split(':')[0]
472 472 if host in self.no_list:
473 473 return None
474 474
475 475 # work around a bug in Python < 2.4.2
476 476 # (it leaves a "\n" at the end of Proxy-authorization headers)
477 477 baseclass = req.__class__
478 478 class _request(baseclass):
479 479 def add_header(self, key, val):
480 480 if key.lower() == 'proxy-authorization':
481 481 val = val.strip()
482 482 return baseclass.add_header(self, key, val)
483 483 req.__class__ = _request
484 484
485 485 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
486 486
487 487 class httpsendfile(object):
488 488 """This is a wrapper around the objects returned by python's "open".
489 489
490 490 Its purpose is to send file-like objects via HTTP and, to do so, it
491 491 defines a __len__ attribute to feed the Content-Length header.
492 492 """
493 493
494 494 def __init__(self, ui, *args, **kwargs):
495 495 # We can't just "self._data = open(*args, **kwargs)" here because there
496 496 # is an "open" function defined in this module that shadows the global
497 497 # one
498 498 self.ui = ui
499 499 self._data = __builtin__.open(*args, **kwargs)
500 500 self.seek = self._data.seek
501 501 self.close = self._data.close
502 502 self.write = self._data.write
503 503 self._len = os.fstat(self._data.fileno()).st_size
504 504 self._pos = 0
505 505 self._total = len(self) / 1024 * 2
506 506
507 507 def read(self, *args, **kwargs):
508 508 try:
509 509 ret = self._data.read(*args, **kwargs)
510 510 except EOFError:
511 511 self.ui.progress(_('sending'), None)
512 512 self._pos += len(ret)
513 513 # We pass double the max for total because we currently have
514 514 # to send the bundle twice in the case of a server that
515 515 # requires authentication. Since we can't know until we try
516 516 # once whether authentication will be required, just lie to
517 517 # the user and maybe the push succeeds suddenly at 50%.
518 518 self.ui.progress(_('sending'), self._pos / 1024,
519 519 unit=_('kb'), total=self._total)
520 520 return ret
521 521
522 522 def __len__(self):
523 523 return self._len
524 524
525 525 def _gen_sendfile(orgsend):
526 526 def _sendfile(self, data):
527 527 # send a file
528 528 if isinstance(data, httpsendfile):
529 529 # if auth required, some data sent twice, so rewind here
530 530 data.seek(0)
531 531 for chunk in util.filechunkiter(data):
532 532 orgsend(self, chunk)
533 533 else:
534 534 orgsend(self, data)
535 535 return _sendfile
536 536
537 537 has_https = hasattr(urllib2, 'HTTPSHandler')
538 538 if has_https:
539 539 try:
540 540 # avoid using deprecated/broken FakeSocket in python 2.6
541 541 import ssl
542 542 _ssl_wrap_socket = ssl.wrap_socket
543 543 CERT_REQUIRED = ssl.CERT_REQUIRED
544 544 except ImportError:
545 545 CERT_REQUIRED = 2
546 546
547 547 def _ssl_wrap_socket(sock, key_file, cert_file,
548 548 cert_reqs=CERT_REQUIRED, ca_certs=None):
549 549 if ca_certs:
550 550 raise util.Abort(_(
551 551 'certificate checking requires Python 2.6'))
552 552
553 553 ssl = socket.ssl(sock, key_file, cert_file)
554 554 return httplib.FakeSocket(sock, ssl)
555 555
556 556 try:
557 557 _create_connection = socket.create_connection
558 558 except AttributeError:
559 559 _GLOBAL_DEFAULT_TIMEOUT = object()
560 560
561 561 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
562 562 source_address=None):
563 563 # lifted from Python 2.6
564 564
565 565 msg = "getaddrinfo returns an empty list"
566 566 host, port = address
567 567 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
568 568 af, socktype, proto, canonname, sa = res
569 569 sock = None
570 570 try:
571 571 sock = socket.socket(af, socktype, proto)
572 572 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
573 573 sock.settimeout(timeout)
574 574 if source_address:
575 575 sock.bind(source_address)
576 576 sock.connect(sa)
577 577 return sock
578 578
579 579 except socket.error, msg:
580 580 if sock is not None:
581 581 sock.close()
582 582
583 583 raise socket.error, msg
584 584
585 585 class httpconnection(keepalive.HTTPConnection):
586 586 # must be able to send big bundle as stream.
587 587 send = _gen_sendfile(keepalive.HTTPConnection.send)
588 588
589 589 def connect(self):
590 590 if has_https and self.realhostport: # use CONNECT proxy
591 591 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
592 592 self.sock.connect((self.host, self.port))
593 593 if _generic_proxytunnel(self):
594 594 # we do not support client x509 certificates
595 595 self.sock = _ssl_wrap_socket(self.sock, None, None)
596 596 else:
597 597 keepalive.HTTPConnection.connect(self)
598 598
599 599 def getresponse(self):
600 600 proxyres = getattr(self, 'proxyres', None)
601 601 if proxyres:
602 602 if proxyres.will_close:
603 603 self.close()
604 604 self.proxyres = None
605 605 return proxyres
606 606 return keepalive.HTTPConnection.getresponse(self)
607 607
608 608 # general transaction handler to support different ways to handle
609 609 # HTTPS proxying before and after Python 2.6.3.
610 610 def _generic_start_transaction(handler, h, req):
611 611 if hasattr(req, '_tunnel_host') and req._tunnel_host:
612 612 tunnel_host = req._tunnel_host
613 613 if tunnel_host[:7] not in ['http://', 'https:/']:
614 614 tunnel_host = 'https://' + tunnel_host
615 615 new_tunnel = True
616 616 else:
617 617 tunnel_host = req.get_selector()
618 618 new_tunnel = False
619 619
620 620 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
621 621 u = url(tunnel_host)
622 622 if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
623 623 h.realhostport = ':'.join([u.host, (u.port or '443')])
624 624 h.headers = req.headers.copy()
625 625 h.headers.update(handler.parent.addheaders)
626 626 return
627 627
628 628 h.realhostport = None
629 629 h.headers = None
630 630
631 631 def _generic_proxytunnel(self):
632 632 proxyheaders = dict(
633 633 [(x, self.headers[x]) for x in self.headers
634 634 if x.lower().startswith('proxy-')])
635 635 self._set_hostport(self.host, self.port)
636 636 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
637 637 for header in proxyheaders.iteritems():
638 638 self.send('%s: %s\r\n' % header)
639 639 self.send('\r\n')
640 640
641 641 # majority of the following code is duplicated from
642 642 # httplib.HTTPConnection as there are no adequate places to
643 643 # override functions to provide the needed functionality
644 644 res = self.response_class(self.sock,
645 645 strict=self.strict,
646 646 method=self._method)
647 647
648 648 while True:
649 649 version, status, reason = res._read_status()
650 650 if status != httplib.CONTINUE:
651 651 break
652 652 while True:
653 653 skip = res.fp.readline().strip()
654 654 if not skip:
655 655 break
656 656 res.status = status
657 657 res.reason = reason.strip()
658 658
659 659 if res.status == 200:
660 660 while True:
661 661 line = res.fp.readline()
662 662 if line == '\r\n':
663 663 break
664 664 return True
665 665
666 666 if version == 'HTTP/1.0':
667 667 res.version = 10
668 668 elif version.startswith('HTTP/1.'):
669 669 res.version = 11
670 670 elif version == 'HTTP/0.9':
671 671 res.version = 9
672 672 else:
673 673 raise httplib.UnknownProtocol(version)
674 674
675 675 if res.version == 9:
676 676 res.length = None
677 677 res.chunked = 0
678 678 res.will_close = 1
679 679 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
680 680 return False
681 681
682 682 res.msg = httplib.HTTPMessage(res.fp)
683 683 res.msg.fp = None
684 684
685 685 # are we using the chunked-style of transfer encoding?
686 686 trenc = res.msg.getheader('transfer-encoding')
687 687 if trenc and trenc.lower() == "chunked":
688 688 res.chunked = 1
689 689 res.chunk_left = None
690 690 else:
691 691 res.chunked = 0
692 692
693 693 # will the connection close at the end of the response?
694 694 res.will_close = res._check_close()
695 695
696 696 # do we have a Content-Length?
697 697 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
698 698 length = res.msg.getheader('content-length')
699 699 if length and not res.chunked:
700 700 try:
701 701 res.length = int(length)
702 702 except ValueError:
703 703 res.length = None
704 704 else:
705 705 if res.length < 0: # ignore nonsensical negative lengths
706 706 res.length = None
707 707 else:
708 708 res.length = None
709 709
710 710 # does the body have a fixed length? (of zero)
711 711 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
712 712 100 <= status < 200 or # 1xx codes
713 713 res._method == 'HEAD'):
714 714 res.length = 0
715 715
716 716 # if the connection remains open, and we aren't using chunked, and
717 717 # a content-length was not provided, then assume that the connection
718 718 # WILL close.
719 719 if (not res.will_close and
720 720 not res.chunked and
721 721 res.length is None):
722 722 res.will_close = 1
723 723
724 724 self.proxyres = res
725 725
726 726 return False
727 727
728 728 class httphandler(keepalive.HTTPHandler):
729 729 def http_open(self, req):
730 730 return self.do_open(httpconnection, req)
731 731
732 732 def _start_transaction(self, h, req):
733 733 _generic_start_transaction(self, h, req)
734 734 return keepalive.HTTPHandler._start_transaction(self, h, req)
735 735
736 736 def _verifycert(cert, hostname):
737 737 '''Verify that cert (in socket.getpeercert() format) matches hostname.
738 738 CRLs is not handled.
739 739
740 740 Returns error message if any problems are found and None on success.
741 741 '''
742 742 if not cert:
743 743 return _('no certificate received')
744 744 dnsname = hostname.lower()
745 745 def matchdnsname(certname):
746 746 return (certname == dnsname or
747 747 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
748 748
749 749 san = cert.get('subjectAltName', [])
750 750 if san:
751 751 certnames = [value.lower() for key, value in san if key == 'DNS']
752 752 for name in certnames:
753 753 if matchdnsname(name):
754 754 return None
755 755 return _('certificate is for %s') % ', '.join(certnames)
756 756
757 757 # subject is only checked when subjectAltName is empty
758 758 for s in cert.get('subject', []):
759 759 key, value = s[0]
760 760 if key == 'commonName':
761 761 try:
762 762 # 'subject' entries are unicode
763 763 certname = value.lower().encode('ascii')
764 764 except UnicodeEncodeError:
765 765 return _('IDN in certificate not supported')
766 766 if matchdnsname(certname):
767 767 return None
768 768 return _('certificate is for %s') % certname
769 769 return _('no commonName or subjectAltName found in certificate')
770 770
771 771 if has_https:
772 772 class httpsconnection(httplib.HTTPSConnection):
773 773 response_class = keepalive.HTTPResponse
774 774 # must be able to send big bundle as stream.
775 775 send = _gen_sendfile(keepalive.safesend)
776 776 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
777 777
778 778 def connect(self):
779 779 self.sock = _create_connection((self.host, self.port))
780 780
781 781 host = self.host
782 782 if self.realhostport: # use CONNECT proxy
783 783 something = _generic_proxytunnel(self)
784 784 host = self.realhostport.rsplit(':', 1)[0]
785 785
786 786 cacerts = self.ui.config('web', 'cacerts')
787 787 hostfingerprint = self.ui.config('hostfingerprints', host)
788 788
789 789 if cacerts and not hostfingerprint:
790 790 cacerts = util.expandpath(cacerts)
791 791 if not os.path.exists(cacerts):
792 792 raise util.Abort(_('could not find '
793 793 'web.cacerts: %s') % cacerts)
794 794 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
795 795 self.cert_file, cert_reqs=CERT_REQUIRED,
796 796 ca_certs=cacerts)
797 797 msg = _verifycert(self.sock.getpeercert(), host)
798 798 if msg:
799 799 raise util.Abort(_('%s certificate error: %s '
800 800 '(use --insecure to connect '
801 801 'insecurely)') % (host, msg))
802 802 self.ui.debug('%s certificate successfully verified\n' % host)
803 803 else:
804 804 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
805 805 self.cert_file)
806 806 if hasattr(self.sock, 'getpeercert'):
807 807 peercert = self.sock.getpeercert(True)
808 808 peerfingerprint = util.sha1(peercert).hexdigest()
809 809 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
810 810 for x in xrange(0, len(peerfingerprint), 2)])
811 811 if hostfingerprint:
812 812 if peerfingerprint.lower() != \
813 813 hostfingerprint.replace(':', '').lower():
814 814 raise util.Abort(_('invalid certificate for %s '
815 815 'with fingerprint %s') %
816 816 (host, nicefingerprint))
817 817 self.ui.debug('%s certificate matched fingerprint %s\n' %
818 818 (host, nicefingerprint))
819 819 else:
820 820 self.ui.warn(_('warning: %s certificate '
821 821 'with fingerprint %s not verified '
822 822 '(check hostfingerprints or web.cacerts '
823 823 'config setting)\n') %
824 824 (host, nicefingerprint))
825 825 else: # python 2.5 ?
826 826 if hostfingerprint:
827 827 raise util.Abort(_('no certificate for %s with '
828 828 'configured hostfingerprint') % host)
829 829 self.ui.warn(_('warning: %s certificate not verified '
830 830 '(check web.cacerts config setting)\n') %
831 831 host)
832 832
833 833 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
834 834 def __init__(self, ui):
835 835 keepalive.KeepAliveHandler.__init__(self)
836 836 urllib2.HTTPSHandler.__init__(self)
837 837 self.ui = ui
838 838 self.pwmgr = passwordmgr(self.ui)
839 839
840 840 def _start_transaction(self, h, req):
841 841 _generic_start_transaction(self, h, req)
842 842 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
843 843
844 844 def https_open(self, req):
845 845 res = readauthforuri(self.ui, req.get_full_url())
846 846 if res:
847 847 group, auth = res
848 848 self.auth = auth
849 849 self.ui.debug("using auth.%s.* for authentication\n" % group)
850 850 else:
851 851 self.auth = None
852 852 return self.do_open(self._makeconnection, req)
853 853
854 854 def _makeconnection(self, host, port=None, *args, **kwargs):
855 855 keyfile = None
856 856 certfile = None
857 857
858 858 if len(args) >= 1: # key_file
859 859 keyfile = args[0]
860 860 if len(args) >= 2: # cert_file
861 861 certfile = args[1]
862 862 args = args[2:]
863 863
864 864 # if the user has specified different key/cert files in
865 865 # hgrc, we prefer these
866 866 if self.auth and 'key' in self.auth and 'cert' in self.auth:
867 867 keyfile = self.auth['key']
868 868 certfile = self.auth['cert']
869 869
870 870 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
871 871 conn.ui = self.ui
872 872 return conn
873 873
874 874 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
875 875 def __init__(self, *args, **kwargs):
876 876 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
877 877 self.retried_req = None
878 878
879 879 def reset_retry_count(self):
880 880 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
881 881 # forever. We disable reset_retry_count completely and reset in
882 882 # http_error_auth_reqed instead.
883 883 pass
884 884
885 885 def http_error_auth_reqed(self, auth_header, host, req, headers):
886 886 # Reset the retry counter once for each request.
887 887 if req is not self.retried_req:
888 888 self.retried_req = req
889 889 self.retried = 0
890 890 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
891 891 # it doesn't know about the auth type requested. This can happen if
892 892 # somebody is using BasicAuth and types a bad password.
893 893 try:
894 894 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
895 895 self, auth_header, host, req, headers)
896 896 except ValueError, inst:
897 897 arg = inst.args[0]
898 898 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
899 899 return
900 900 raise
901 901
902 902 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
903 903 def __init__(self, *args, **kwargs):
904 904 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
905 905 self.retried_req = None
906 906
907 907 def reset_retry_count(self):
908 908 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
909 909 # forever. We disable reset_retry_count completely and reset in
910 910 # http_error_auth_reqed instead.
911 911 pass
912 912
913 913 def http_error_auth_reqed(self, auth_header, host, req, headers):
914 914 # Reset the retry counter once for each request.
915 915 if req is not self.retried_req:
916 916 self.retried_req = req
917 917 self.retried = 0
918 918 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
919 919 self, auth_header, host, req, headers)
920 920
921 921 handlerfuncs = []
922 922
923 923 def opener(ui, authinfo=None):
924 924 '''
925 925 construct an opener suitable for urllib2
926 926 authinfo will be added to the password manager
927 927 '''
928 928 handlers = [httphandler()]
929 929 if has_https:
930 930 handlers.append(httpshandler(ui))
931 931
932 932 handlers.append(proxyhandler(ui))
933 933
934 934 passmgr = passwordmgr(ui)
935 935 if authinfo is not None:
936 936 passmgr.add_password(*authinfo)
937 937 user, passwd = authinfo[2:4]
938 938 ui.debug('http auth: user %s, password %s\n' %
939 939 (user, passwd and '*' * len(passwd) or 'not set'))
940 940
941 941 handlers.extend((httpbasicauthhandler(passmgr),
942 942 httpdigestauthhandler(passmgr)))
943 943 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
944 944 opener = urllib2.build_opener(*handlers)
945 945
946 946 # 1.0 here is the _protocol_ version
947 947 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
948 948 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
949 949 return opener
950 950
951 951 def open(ui, url_, data=None):
952 952 u = url(url_)
953 953 if u.scheme:
954 954 u.scheme = u.scheme.lower()
955 955 url_, authinfo = u.authinfo()
956 956 else:
957 957 path = util.normpath(os.path.abspath(url_))
958 958 url_ = 'file://' + urllib.pathname2url(path)
959 959 authinfo = None
960 960 return opener(ui, authinfo).open(url_, data)
@@ -1,199 +1,199
1 1 import sys
2 2
3 3 def check(a, b):
4 4 if a != b:
5 5 print (a, b)
6 6
7 7 def cert(cn):
8 8 return dict(subject=((('commonName', cn),),))
9 9
10 10 from mercurial.url import _verifycert
11 11
12 12 # Test non-wildcard certificates
13 13 check(_verifycert(cert('example.com'), 'example.com'),
14 14 None)
15 15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 16 'certificate is for example.com')
17 17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 18 'certificate is for www.example.com')
19 19
20 20 # Test wildcard certificates
21 21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 22 None)
23 23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 24 'certificate is for *.example.com')
25 25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 26 'certificate is for *.example.com')
27 27
28 28 # Test subjectAltName
29 29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 30 'subjectAltName': (('DNS', '*.example.net'),
31 31 ('DNS', 'example.net'))}
32 32 check(_verifycert(san_cert, 'example.net'),
33 33 None)
34 34 check(_verifycert(san_cert, 'foo.example.net'),
35 35 None)
36 36 # subject is only checked when subjectAltName is empty
37 37 check(_verifycert(san_cert, 'example.com'),
38 38 'certificate is for *.example.net, example.net')
39 39
40 40 # Avoid some pitfalls
41 41 check(_verifycert(cert('*.foo'), 'foo'),
42 42 'certificate is for *.foo')
43 43 check(_verifycert(cert('*o'), 'foo'),
44 44 'certificate is for *o')
45 45
46 46 check(_verifycert({'subject': ()},
47 47 'example.com'),
48 48 'no commonName or subjectAltName found in certificate')
49 49 check(_verifycert(None, 'example.com'),
50 50 'no certificate received')
51 51
52 52 import doctest
53 53
54 54 def test_url():
55 55 """
56 56 >>> from mercurial.url import url
57 57
58 58 This tests for edge cases in url.URL's parsing algorithm. Most of
59 59 these aren't useful for documentation purposes, so they aren't
60 60 part of the class's doc tests.
61 61
62 62 Query strings and fragments:
63 63
64 64 >>> url('http://host/a?b#c')
65 65 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
66 66 >>> url('http://host/a?')
67 67 <url scheme: 'http', host: 'host', path: 'a'>
68 68 >>> url('http://host/a#b#c')
69 69 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
70 70 >>> url('http://host/a#b?c')
71 71 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
72 72 >>> url('http://host/?a#b')
73 73 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
74 >>> url('http://host/?a#b', parse_query=False)
74 >>> url('http://host/?a#b', parsequery=False)
75 75 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
76 >>> url('http://host/?a#b', parse_fragment=False)
76 >>> url('http://host/?a#b', parsefragment=False)
77 77 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
78 >>> url('http://host/?a#b', parse_query=False, parse_fragment=False)
78 >>> url('http://host/?a#b', parsequery=False, parsefragment=False)
79 79 <url scheme: 'http', host: 'host', path: '?a#b'>
80 80
81 81 IPv6 addresses:
82 82
83 83 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
84 84 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
85 85 query: 'objectClass?one'>
86 86 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
87 87 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
88 88 port: '80', path: 'c=GB', query: 'objectClass?one'>
89 89
90 90 Missing scheme, host, etc.:
91 91
92 92 >>> url('://192.0.2.16:80/')
93 93 <url path: '://192.0.2.16:80/'>
94 94 >>> url('http://mercurial.selenic.com')
95 95 <url scheme: 'http', host: 'mercurial.selenic.com'>
96 96 >>> url('/foo')
97 97 <url path: '/foo'>
98 98 >>> url('bundle:/foo')
99 99 <url scheme: 'bundle', path: '/foo'>
100 100 >>> url('a?b#c')
101 101 <url path: 'a?b', fragment: 'c'>
102 102 >>> url('http://x.com?arg=/foo')
103 103 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
104 104 >>> url('http://joe:xxx@/foo')
105 105 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
106 106
107 107 Just a scheme and a path:
108 108
109 109 >>> url('mailto:John.Doe@example.com')
110 110 <url scheme: 'mailto', path: 'John.Doe@example.com'>
111 111 >>> url('a:b:c:d')
112 112 <url path: 'a:b:c:d'>
113 113 >>> url('aa:bb:cc:dd')
114 114 <url scheme: 'aa', path: 'bb:cc:dd'>
115 115
116 116 SSH examples:
117 117
118 118 >>> url('ssh://joe@host//home/joe')
119 119 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
120 120 >>> url('ssh://joe:xxx@host/src')
121 121 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
122 122 >>> url('ssh://joe:xxx@host')
123 123 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
124 124 >>> url('ssh://joe@host')
125 125 <url scheme: 'ssh', user: 'joe', host: 'host'>
126 126 >>> url('ssh://host')
127 127 <url scheme: 'ssh', host: 'host'>
128 128 >>> url('ssh://')
129 129 <url scheme: 'ssh'>
130 130 >>> url('ssh:')
131 131 <url scheme: 'ssh'>
132 132
133 133 Non-numeric port:
134 134
135 135 >>> url('http://example.com:dd')
136 136 <url scheme: 'http', host: 'example.com', port: 'dd'>
137 137 >>> url('ssh://joe:xxx@host:ssh/foo')
138 138 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
139 139 path: 'foo'>
140 140
141 141 Bad authentication credentials:
142 142
143 143 >>> url('http://joe@joeville:123@4:@host/a?b#c')
144 144 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
145 145 host: 'host', path: 'a', query: 'b', fragment: 'c'>
146 146 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
147 147 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
148 148 >>> url('http://!*#?@!*#?:@host/a?b#c')
149 149 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
150 150 >>> url('http://!*@:!*@@host/a?b#c')
151 151 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
152 152 path: 'a', query: 'b', fragment: 'c'>
153 153
154 154 File paths:
155 155
156 156 >>> url('a/b/c/d.g.f')
157 157 <url path: 'a/b/c/d.g.f'>
158 158 >>> url('/x///z/y/')
159 159 <url path: '/x///z/y/'>
160 160
161 161 Non-localhost file URL:
162 162
163 163 >>> u = url('file://mercurial.selenic.com/foo')
164 164 Traceback (most recent call last):
165 165 File "<stdin>", line 1, in ?
166 166 Abort: file:// URLs can only refer to localhost
167 167
168 168 Empty URL:
169 169
170 170 >>> u = url('')
171 171 >>> u
172 172 <url path: ''>
173 173 >>> str(u)
174 174 ''
175 175
176 176 Empty path with query string:
177 177
178 178 >>> str(url('http://foo/?bar'))
179 179 'http://foo/?bar'
180 180
181 181 Invalid path:
182 182
183 183 >>> u = url('http://foo/bar')
184 184 >>> u.path = 'bar'
185 185 >>> str(u)
186 186 'http://foo/bar'
187 187
188 188 >>> u = url('file:///foo/bar/baz')
189 189 >>> u
190 190 <url scheme: 'file', path: '/foo/bar/baz'>
191 191 >>> str(u)
192 192 'file:/foo/bar/baz'
193 193 """
194 194
195 195 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
196 196
197 197 # Unicode (IDN) certname isn't supported
198 198 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
199 199 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now