##// END OF EJS Templates
url: expand path for web.cacerts
Eduard-Cristian Stefan -
r13231:b335882c stable
parent child Browse files
Show More
@@ -1,556 +1,556 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 from node import hex, nullid, nullrev, short
12 12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 13 import lock, util, extensions, error, encoding, node
14 14 import cmdutil, discovery, url
15 15 import merge as mergemod
16 16 import verify as verifymod
17 17 import errno, os, shutil
18 18
19 19 def _local(path):
20 20 path = util.expandpath(util.drop_scheme('file', path))
21 21 return (os.path.isfile(path) and bundlerepo or localrepo)
22 22
23 23 def addbranchrevs(lrepo, repo, branches, revs):
24 24 hashbranch, branches = branches
25 25 if not hashbranch and not branches:
26 26 return revs or None, revs and revs[0] or None
27 27 revs = revs and list(revs) or []
28 28 if not repo.capable('branchmap'):
29 29 if branches:
30 30 raise util.Abort(_("remote branch lookup not supported"))
31 31 revs.append(hashbranch)
32 32 return revs, revs[0]
33 33 branchmap = repo.branchmap()
34 34
35 35 def primary(butf8):
36 36 if butf8 == '.':
37 37 if not lrepo or not lrepo.local():
38 38 raise util.Abort(_("dirstate branch not accessible"))
39 39 butf8 = lrepo.dirstate.branch()
40 40 if butf8 in branchmap:
41 41 revs.extend(node.hex(r) for r in reversed(branchmap[butf8]))
42 42 return True
43 43 else:
44 44 return False
45 45
46 46 for branch in branches:
47 47 butf8 = encoding.fromlocal(branch)
48 48 if not primary(butf8):
49 49 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
50 50 if hashbranch:
51 51 butf8 = encoding.fromlocal(hashbranch)
52 52 if not primary(butf8):
53 53 revs.append(hashbranch)
54 54 return revs, revs[0]
55 55
56 56 def parseurl(url, branches=None):
57 57 '''parse url#branch, returning (url, (branch, branches))'''
58 58
59 59 if '#' not in url:
60 60 return url, (None, branches or [])
61 61 url, branch = url.split('#', 1)
62 62 return url, (branch, branches or [])
63 63
64 64 schemes = {
65 65 'bundle': bundlerepo,
66 66 'file': _local,
67 67 'http': httprepo,
68 68 'https': httprepo,
69 69 'ssh': sshrepo,
70 70 'static-http': statichttprepo,
71 71 }
72 72
73 73 def _lookup(path):
74 74 scheme = 'file'
75 75 if path:
76 76 c = path.find(':')
77 77 if c > 0:
78 78 scheme = path[:c]
79 79 thing = schemes.get(scheme) or schemes['file']
80 80 try:
81 81 return thing(path)
82 82 except TypeError:
83 83 return thing
84 84
85 85 def islocal(repo):
86 86 '''return true if repo or path is local'''
87 87 if isinstance(repo, str):
88 88 try:
89 89 return _lookup(repo).islocal(repo)
90 90 except AttributeError:
91 91 return False
92 92 return repo.local()
93 93
94 94 def repository(ui, path='', create=False):
95 95 """return a repository object for the specified path"""
96 96 repo = _lookup(path).instance(ui, path, create)
97 97 ui = getattr(repo, "ui", ui)
98 98 for name, module in extensions.extensions():
99 99 hook = getattr(module, 'reposetup', None)
100 100 if hook:
101 101 hook(ui, repo)
102 102 return repo
103 103
104 104 def defaultdest(source):
105 105 '''return default destination of clone if none is given'''
106 106 return os.path.basename(os.path.normpath(source))
107 107
108 108 def localpath(path):
109 109 if path.startswith('file://localhost/'):
110 110 return path[16:]
111 111 if path.startswith('file://'):
112 112 return path[7:]
113 113 if path.startswith('file:'):
114 114 return path[5:]
115 115 return path
116 116
117 117 def share(ui, source, dest=None, update=True):
118 118 '''create a shared repository'''
119 119
120 120 if not islocal(source):
121 121 raise util.Abort(_('can only share local repositories'))
122 122
123 123 if not dest:
124 124 dest = defaultdest(source)
125 125 else:
126 126 dest = ui.expandpath(dest)
127 127
128 128 if isinstance(source, str):
129 129 origsource = ui.expandpath(source)
130 130 source, branches = parseurl(origsource)
131 131 srcrepo = repository(ui, source)
132 132 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
133 133 else:
134 134 srcrepo = source
135 135 origsource = source = srcrepo.url()
136 136 checkout = None
137 137
138 138 sharedpath = srcrepo.sharedpath # if our source is already sharing
139 139
140 140 root = os.path.realpath(dest)
141 141 roothg = os.path.join(root, '.hg')
142 142
143 143 if os.path.exists(roothg):
144 144 raise util.Abort(_('destination already exists'))
145 145
146 146 if not os.path.isdir(root):
147 147 os.mkdir(root)
148 148 os.mkdir(roothg)
149 149
150 150 requirements = ''
151 151 try:
152 152 requirements = srcrepo.opener('requires').read()
153 153 except IOError, inst:
154 154 if inst.errno != errno.ENOENT:
155 155 raise
156 156
157 157 requirements += 'shared\n'
158 158 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
159 159 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
160 160
161 161 default = srcrepo.ui.config('paths', 'default')
162 162 if default:
163 163 f = file(os.path.join(roothg, 'hgrc'), 'w')
164 164 f.write('[paths]\ndefault = %s\n' % default)
165 165 f.close()
166 166
167 167 r = repository(ui, root)
168 168
169 169 if update:
170 170 r.ui.status(_("updating working directory\n"))
171 171 if update is not True:
172 172 checkout = update
173 173 for test in (checkout, 'default', 'tip'):
174 174 if test is None:
175 175 continue
176 176 try:
177 177 uprev = r.lookup(test)
178 178 break
179 179 except error.RepoLookupError:
180 180 continue
181 181 _update(r, uprev)
182 182
183 183 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
184 184 stream=False, branch=None):
185 185 """Make a copy of an existing repository.
186 186
187 187 Create a copy of an existing repository in a new directory. The
188 188 source and destination are URLs, as passed to the repository
189 189 function. Returns a pair of repository objects, the source and
190 190 newly created destination.
191 191
192 192 The location of the source is added to the new repository's
193 193 .hg/hgrc file, as the default to be used for future pulls and
194 194 pushes.
195 195
196 196 If an exception is raised, the partly cloned/updated destination
197 197 repository will be deleted.
198 198
199 199 Arguments:
200 200
201 201 source: repository object or URL
202 202
203 203 dest: URL of destination repository to create (defaults to base
204 204 name of source repository)
205 205
206 206 pull: always pull from source repository, even in local case
207 207
208 208 stream: stream raw data uncompressed from repository (fast over
209 209 LAN, slow over WAN)
210 210
211 211 rev: revision to clone up to (implies pull=True)
212 212
213 213 update: update working directory after clone completes, if
214 214 destination is local repository (True means update to default rev,
215 215 anything else is treated as a revision)
216 216
217 217 branch: branches to clone
218 218 """
219 219
220 220 if isinstance(source, str):
221 221 origsource = ui.expandpath(source)
222 222 source, branch = parseurl(origsource, branch)
223 223 src_repo = repository(ui, source)
224 224 else:
225 225 src_repo = source
226 226 branch = (None, branch or [])
227 227 origsource = source = src_repo.url()
228 228 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
229 229
230 230 if dest is None:
231 231 dest = defaultdest(source)
232 232 ui.status(_("destination directory: %s\n") % dest)
233 233 else:
234 234 dest = ui.expandpath(dest)
235 235
236 236 dest = localpath(dest)
237 237 source = localpath(source)
238 238
239 239 if os.path.exists(dest):
240 240 if not os.path.isdir(dest):
241 241 raise util.Abort(_("destination '%s' already exists") % dest)
242 242 elif os.listdir(dest):
243 243 raise util.Abort(_("destination '%s' is not empty") % dest)
244 244
245 245 class DirCleanup(object):
246 246 def __init__(self, dir_):
247 247 self.rmtree = shutil.rmtree
248 248 self.dir_ = dir_
249 249 def close(self):
250 250 self.dir_ = None
251 251 def cleanup(self):
252 252 if self.dir_:
253 253 self.rmtree(self.dir_, True)
254 254
255 255 src_lock = dest_lock = dir_cleanup = None
256 256 try:
257 257 if islocal(dest):
258 258 dir_cleanup = DirCleanup(dest)
259 259
260 260 abspath = origsource
261 261 copy = False
262 262 if src_repo.cancopy() and islocal(dest):
263 263 abspath = os.path.abspath(util.drop_scheme('file', origsource))
264 264 copy = not pull and not rev
265 265
266 266 if copy:
267 267 try:
268 268 # we use a lock here because if we race with commit, we
269 269 # can end up with extra data in the cloned revlogs that's
270 270 # not pointed to by changesets, thus causing verify to
271 271 # fail
272 272 src_lock = src_repo.lock(wait=False)
273 273 except error.LockError:
274 274 copy = False
275 275
276 276 if copy:
277 277 src_repo.hook('preoutgoing', throw=True, source='clone')
278 278 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
279 279 if not os.path.exists(dest):
280 280 os.mkdir(dest)
281 281 else:
282 282 # only clean up directories we create ourselves
283 283 dir_cleanup.dir_ = hgdir
284 284 try:
285 285 dest_path = hgdir
286 286 os.mkdir(dest_path)
287 287 except OSError, inst:
288 288 if inst.errno == errno.EEXIST:
289 289 dir_cleanup.close()
290 290 raise util.Abort(_("destination '%s' already exists")
291 291 % dest)
292 292 raise
293 293
294 294 hardlink = None
295 295 num = 0
296 296 for f in src_repo.store.copylist():
297 297 src = os.path.join(src_repo.sharedpath, f)
298 298 dst = os.path.join(dest_path, f)
299 299 dstbase = os.path.dirname(dst)
300 300 if dstbase and not os.path.exists(dstbase):
301 301 os.mkdir(dstbase)
302 302 if os.path.exists(src):
303 303 if dst.endswith('data'):
304 304 # lock to avoid premature writing to the target
305 305 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
306 306 hardlink, n = util.copyfiles(src, dst, hardlink)
307 307 num += n
308 308 if hardlink:
309 309 ui.debug("linked %d files\n" % num)
310 310 else:
311 311 ui.debug("copied %d files\n" % num)
312 312
313 313 # we need to re-init the repo after manually copying the data
314 314 # into it
315 315 dest_repo = repository(ui, dest)
316 316 src_repo.hook('outgoing', source='clone',
317 317 node=node.hex(node.nullid))
318 318 else:
319 319 try:
320 320 dest_repo = repository(ui, dest, create=True)
321 321 except OSError, inst:
322 322 if inst.errno == errno.EEXIST:
323 323 dir_cleanup.close()
324 324 raise util.Abort(_("destination '%s' already exists")
325 325 % dest)
326 326 raise
327 327
328 328 revs = None
329 329 if rev:
330 330 if 'lookup' not in src_repo.capabilities:
331 331 raise util.Abort(_("src repository does not support "
332 332 "revision lookup and so doesn't "
333 333 "support clone by revision"))
334 334 revs = [src_repo.lookup(r) for r in rev]
335 335 checkout = revs[0]
336 336 if dest_repo.local():
337 337 dest_repo.clone(src_repo, heads=revs, stream=stream)
338 338 elif src_repo.local():
339 339 src_repo.push(dest_repo, revs=revs)
340 340 else:
341 341 raise util.Abort(_("clone from remote to remote not supported"))
342 342
343 343 if dir_cleanup:
344 344 dir_cleanup.close()
345 345
346 346 if dest_repo.local():
347 347 fp = dest_repo.opener("hgrc", "w", text=True)
348 348 fp.write("[paths]\n")
349 349 fp.write("default = %s\n" % abspath)
350 350 fp.close()
351 351
352 352 dest_repo.ui.setconfig('paths', 'default', abspath)
353 353
354 354 if update:
355 355 if update is not True:
356 356 checkout = update
357 357 if src_repo.local():
358 358 checkout = src_repo.lookup(update)
359 359 for test in (checkout, 'default', 'tip'):
360 360 if test is None:
361 361 continue
362 362 try:
363 363 uprev = dest_repo.lookup(test)
364 364 break
365 365 except error.RepoLookupError:
366 366 continue
367 367 bn = dest_repo[uprev].branch()
368 368 dest_repo.ui.status(_("updating to branch %s\n")
369 369 % encoding.tolocal(bn))
370 370 _update(dest_repo, uprev)
371 371
372 372 return src_repo, dest_repo
373 373 finally:
374 374 release(src_lock, dest_lock)
375 375 if dir_cleanup is not None:
376 376 dir_cleanup.cleanup()
377 377
378 378 def _showstats(repo, stats):
379 379 repo.ui.status(_("%d files updated, %d files merged, "
380 380 "%d files removed, %d files unresolved\n") % stats)
381 381
382 382 def update(repo, node):
383 383 """update the working directory to node, merging linear changes"""
384 384 stats = mergemod.update(repo, node, False, False, None)
385 385 _showstats(repo, stats)
386 386 if stats[3]:
387 387 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
388 388 return stats[3] > 0
389 389
390 390 # naming conflict in clone()
391 391 _update = update
392 392
393 393 def clean(repo, node, show_stats=True):
394 394 """forcibly switch the working directory to node, clobbering changes"""
395 395 stats = mergemod.update(repo, node, False, True, None)
396 396 if show_stats:
397 397 _showstats(repo, stats)
398 398 return stats[3] > 0
399 399
400 400 def merge(repo, node, force=None, remind=True):
401 401 """branch merge with node, resolving changes"""
402 402 stats = mergemod.update(repo, node, True, force, False)
403 403 _showstats(repo, stats)
404 404 if stats[3]:
405 405 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
406 406 "or 'hg update -C .' to abandon\n"))
407 407 elif remind:
408 408 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
409 409 return stats[3] > 0
410 410
411 411 def _incoming(displaychlist, subreporecurse, ui, repo, source,
412 412 opts, buffered=False):
413 413 """
414 414 Helper for incoming / gincoming.
415 415 displaychlist gets called with
416 416 (remoterepo, incomingchangesetlist, displayer) parameters,
417 417 and is supposed to contain only code that can't be unified.
418 418 """
419 419 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
420 420 other = repository(remoteui(repo, opts), source)
421 421 ui.status(_('comparing with %s\n') % url.hidepassword(source))
422 422 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
423 423
424 424 if revs:
425 425 revs = [other.lookup(rev) for rev in revs]
426 426 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
427 427 opts["bundle"], opts["force"])
428 428 if incoming is None:
429 429 ui.status(_("no changes found\n"))
430 430 return subreporecurse()
431 431
432 432 try:
433 433 chlist = other.changelog.nodesbetween(incoming, revs)[0]
434 434 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
435 435
436 436 # XXX once graphlog extension makes it into core,
437 437 # should be replaced by a if graph/else
438 438 displaychlist(other, chlist, displayer)
439 439
440 440 displayer.close()
441 441 finally:
442 442 if hasattr(other, 'close'):
443 443 other.close()
444 444 if bundle:
445 445 os.unlink(bundle)
446 446 subreporecurse()
447 447 return 0 # exit code is zero since we found incoming changes
448 448
449 449 def incoming(ui, repo, source, opts):
450 450 def subreporecurse():
451 451 ret = 1
452 452 if opts.get('subrepos'):
453 453 ctx = repo[None]
454 454 for subpath in sorted(ctx.substate):
455 455 sub = ctx.sub(subpath)
456 456 ret = min(ret, sub.incoming(ui, source, opts))
457 457 return ret
458 458
459 459 def display(other, chlist, displayer):
460 460 limit = cmdutil.loglimit(opts)
461 461 if opts.get('newest_first'):
462 462 chlist.reverse()
463 463 count = 0
464 464 for n in chlist:
465 465 if limit is not None and count >= limit:
466 466 break
467 467 parents = [p for p in other.changelog.parents(n) if p != nullid]
468 468 if opts.get('no_merges') and len(parents) == 2:
469 469 continue
470 470 count += 1
471 471 displayer.show(other[n])
472 472 return _incoming(display, subreporecurse, ui, repo, source, opts)
473 473
474 474 def _outgoing(ui, repo, dest, opts):
475 475 dest = ui.expandpath(dest or 'default-push', dest or 'default')
476 476 dest, branches = parseurl(dest, opts.get('branch'))
477 477 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
478 478 if revs:
479 479 revs = [repo.lookup(rev) for rev in revs]
480 480
481 481 other = repository(remoteui(repo, opts), dest)
482 482 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
483 483 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
484 484 if not o:
485 485 ui.status(_("no changes found\n"))
486 486 return None
487 487
488 488 return repo.changelog.nodesbetween(o, revs)[0]
489 489
490 490 def outgoing(ui, repo, dest, opts):
491 491 def recurse():
492 492 ret = 1
493 493 if opts.get('subrepos'):
494 494 ctx = repo[None]
495 495 for subpath in sorted(ctx.substate):
496 496 sub = ctx.sub(subpath)
497 497 ret = min(ret, sub.outgoing(ui, dest, opts))
498 498 return ret
499 499
500 500 limit = cmdutil.loglimit(opts)
501 501 o = _outgoing(ui, repo, dest, opts)
502 502 if o is None:
503 503 return recurse()
504 504
505 505 if opts.get('newest_first'):
506 506 o.reverse()
507 507 displayer = cmdutil.show_changeset(ui, repo, opts)
508 508 count = 0
509 509 for n in o:
510 510 if limit is not None and count >= limit:
511 511 break
512 512 parents = [p for p in repo.changelog.parents(n) if p != nullid]
513 513 if opts.get('no_merges') and len(parents) == 2:
514 514 continue
515 515 count += 1
516 516 displayer.show(repo[n])
517 517 displayer.close()
518 518 recurse()
519 519 return 0 # exit code is zero since we found outgoing changes
520 520
521 521 def revert(repo, node, choose):
522 522 """revert changes to revision in node without updating dirstate"""
523 523 return mergemod.update(repo, node, False, True, choose)[3] > 0
524 524
525 525 def verify(repo):
526 526 """verify the consistency of a repository"""
527 527 return verifymod.verify(repo)
528 528
529 529 def remoteui(src, opts):
530 530 'build a remote ui from ui or repo and opts'
531 531 if hasattr(src, 'baseui'): # looks like a repository
532 532 dst = src.baseui.copy() # drop repo-specific config
533 533 src = src.ui # copy target options from repo
534 534 else: # assume it's a global ui object
535 535 dst = src.copy() # keep all global options
536 536
537 537 # copy ssh-specific options
538 538 for o in 'ssh', 'remotecmd':
539 539 v = opts.get(o) or src.config('ui', o)
540 540 if v:
541 541 dst.setconfig("ui", o, v)
542 542
543 543 # copy bundle-specific options
544 544 r = src.config('bundle', 'mainreporoot')
545 545 if r:
546 546 dst.setconfig('bundle', 'mainreporoot', r)
547 547
548 548 # copy selected local settings to the remote ui
549 549 for sect in ('auth', 'http_proxy'):
550 550 for key, val in src.configitems(sect):
551 551 dst.setconfig(sect, key, val)
552 552 v = src.config('web', 'cacerts')
553 553 if v:
554 dst.setconfig('web', 'cacerts', v)
554 dst.setconfig('web', 'cacerts', util.expandpath(v))
555 555
556 556 return dst
@@ -1,701 +1,703 b''
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, urlparse, httplib, os, re, socket, cStringIO
11 11 import __builtin__
12 12 from i18n import _
13 13 import keepalive, util
14 14
15 15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
16 16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
17 17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
18 18 if (scheme and
19 19 result.startswith(scheme + ':') and
20 20 not result.startswith(scheme + '://') and
21 21 url.startswith(scheme + '://')
22 22 ):
23 23 result = scheme + '://' + result[len(scheme + ':'):]
24 24 return result
25 25
26 26 def hidepassword(url):
27 27 '''hide user credential in a url string'''
28 28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
29 29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
30 30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
31 31
32 32 def removeauth(url):
33 33 '''remove all authentication information from a url string'''
34 34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
35 35 netloc = netloc[netloc.find('@')+1:]
36 36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
37 37
38 38 def netlocsplit(netloc):
39 39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
40 40
41 41 a = netloc.find('@')
42 42 if a == -1:
43 43 user, passwd = None, None
44 44 else:
45 45 userpass, netloc = netloc[:a], netloc[a + 1:]
46 46 c = userpass.find(':')
47 47 if c == -1:
48 48 user, passwd = urllib.unquote(userpass), None
49 49 else:
50 50 user = urllib.unquote(userpass[:c])
51 51 passwd = urllib.unquote(userpass[c + 1:])
52 52 c = netloc.find(':')
53 53 if c == -1:
54 54 host, port = netloc, None
55 55 else:
56 56 host, port = netloc[:c], netloc[c + 1:]
57 57 return host, port, user, passwd
58 58
59 59 def netlocunsplit(host, port, user=None, passwd=None):
60 60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
61 61 if port:
62 62 hostport = host + ':' + port
63 63 else:
64 64 hostport = host
65 65 if user:
66 66 quote = lambda s: urllib.quote(s, safe='')
67 67 if passwd:
68 68 userpass = quote(user) + ':' + quote(passwd)
69 69 else:
70 70 userpass = quote(user)
71 71 return userpass + '@' + hostport
72 72 return hostport
73 73
74 74 _safe = ('abcdefghijklmnopqrstuvwxyz'
75 75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
76 76 '0123456789' '_.-/')
77 77 _safeset = None
78 78 _hex = None
79 79 def quotepath(path):
80 80 '''quote the path part of a URL
81 81
82 82 This is similar to urllib.quote, but it also tries to avoid
83 83 quoting things twice (inspired by wget):
84 84
85 85 >>> quotepath('abc def')
86 86 'abc%20def'
87 87 >>> quotepath('abc%20def')
88 88 'abc%20def'
89 89 >>> quotepath('abc%20 def')
90 90 'abc%20%20def'
91 91 >>> quotepath('abc def%20')
92 92 'abc%20def%20'
93 93 >>> quotepath('abc def%2')
94 94 'abc%20def%252'
95 95 >>> quotepath('abc def%')
96 96 'abc%20def%25'
97 97 '''
98 98 global _safeset, _hex
99 99 if _safeset is None:
100 100 _safeset = set(_safe)
101 101 _hex = set('abcdefABCDEF0123456789')
102 102 l = list(path)
103 103 for i in xrange(len(l)):
104 104 c = l[i]
105 105 if (c == '%' and i + 2 < len(l) and
106 106 l[i + 1] in _hex and l[i + 2] in _hex):
107 107 pass
108 108 elif c not in _safeset:
109 109 l[i] = '%%%02X' % ord(c)
110 110 return ''.join(l)
111 111
112 112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
113 113 def __init__(self, ui):
114 114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
115 115 self.ui = ui
116 116
117 117 def find_user_password(self, realm, authuri):
118 118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
119 119 self, realm, authuri)
120 120 user, passwd = authinfo
121 121 if user and passwd:
122 122 self._writedebug(user, passwd)
123 123 return (user, passwd)
124 124
125 125 if not user:
126 126 auth = self.readauthtoken(authuri)
127 127 if auth:
128 128 user, passwd = auth.get('username'), auth.get('password')
129 129 if not user or not passwd:
130 130 if not self.ui.interactive():
131 131 raise util.Abort(_('http authorization required'))
132 132
133 133 self.ui.write(_("http authorization required\n"))
134 134 self.ui.write(_("realm: %s\n") % realm)
135 135 if user:
136 136 self.ui.write(_("user: %s\n") % user)
137 137 else:
138 138 user = self.ui.prompt(_("user:"), default=None)
139 139
140 140 if not passwd:
141 141 passwd = self.ui.getpass()
142 142
143 143 self.add_password(realm, authuri, user, passwd)
144 144 self._writedebug(user, passwd)
145 145 return (user, passwd)
146 146
147 147 def _writedebug(self, user, passwd):
148 148 msg = _('http auth: user %s, password %s\n')
149 149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
150 150
151 151 def readauthtoken(self, uri):
152 152 # Read configuration
153 153 config = dict()
154 154 for key, val in self.ui.configitems('auth'):
155 155 if '.' not in key:
156 156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
157 157 continue
158 158 group, setting = key.split('.', 1)
159 159 gdict = config.setdefault(group, dict())
160 160 if setting in ('username', 'cert', 'key'):
161 161 val = util.expandpath(val)
162 162 gdict[setting] = val
163 163
164 164 # Find the best match
165 165 scheme, hostpath = uri.split('://', 1)
166 166 bestlen = 0
167 167 bestauth = None
168 168 for auth in config.itervalues():
169 169 prefix = auth.get('prefix')
170 170 if not prefix:
171 171 continue
172 172 p = prefix.split('://', 1)
173 173 if len(p) > 1:
174 174 schemes, prefix = [p[0]], p[1]
175 175 else:
176 176 schemes = (auth.get('schemes') or 'https').split()
177 177 if (prefix == '*' or hostpath.startswith(prefix)) and \
178 178 len(prefix) > bestlen and scheme in schemes:
179 179 bestlen = len(prefix)
180 180 bestauth = auth
181 181 return bestauth
182 182
183 183 class proxyhandler(urllib2.ProxyHandler):
184 184 def __init__(self, ui):
185 185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
186 186 # XXX proxyauthinfo = None
187 187
188 188 if proxyurl:
189 189 # proxy can be proper url or host[:port]
190 190 if not (proxyurl.startswith('http:') or
191 191 proxyurl.startswith('https:')):
192 192 proxyurl = 'http://' + proxyurl + '/'
193 193 snpqf = urlparse.urlsplit(proxyurl)
194 194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
195 195 hpup = netlocsplit(proxynetloc)
196 196
197 197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
198 198 if not proxyuser:
199 199 proxyuser = ui.config("http_proxy", "user")
200 200 proxypasswd = ui.config("http_proxy", "passwd")
201 201
202 202 # see if we should use a proxy for this url
203 203 no_list = ["localhost", "127.0.0.1"]
204 204 no_list.extend([p.lower() for
205 205 p in ui.configlist("http_proxy", "no")])
206 206 no_list.extend([p.strip().lower() for
207 207 p in os.getenv("no_proxy", '').split(',')
208 208 if p.strip()])
209 209 # "http_proxy.always" config is for running tests on localhost
210 210 if ui.configbool("http_proxy", "always"):
211 211 self.no_list = []
212 212 else:
213 213 self.no_list = no_list
214 214
215 215 proxyurl = urlparse.urlunsplit((
216 216 proxyscheme, netlocunsplit(proxyhost, proxyport,
217 217 proxyuser, proxypasswd or ''),
218 218 proxypath, proxyquery, proxyfrag))
219 219 proxies = {'http': proxyurl, 'https': proxyurl}
220 220 ui.debug('proxying through http://%s:%s\n' %
221 221 (proxyhost, proxyport))
222 222 else:
223 223 proxies = {}
224 224
225 225 # urllib2 takes proxy values from the environment and those
226 226 # will take precedence if found, so drop them
227 227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
228 228 try:
229 229 if env in os.environ:
230 230 del os.environ[env]
231 231 except OSError:
232 232 pass
233 233
234 234 urllib2.ProxyHandler.__init__(self, proxies)
235 235 self.ui = ui
236 236
237 237 def proxy_open(self, req, proxy, type_):
238 238 host = req.get_host().split(':')[0]
239 239 if host in self.no_list:
240 240 return None
241 241
242 242 # work around a bug in Python < 2.4.2
243 243 # (it leaves a "\n" at the end of Proxy-authorization headers)
244 244 baseclass = req.__class__
245 245 class _request(baseclass):
246 246 def add_header(self, key, val):
247 247 if key.lower() == 'proxy-authorization':
248 248 val = val.strip()
249 249 return baseclass.add_header(self, key, val)
250 250 req.__class__ = _request
251 251
252 252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
253 253
254 254 class httpsendfile(object):
255 255 """This is a wrapper around the objects returned by python's "open".
256 256
257 257 Its purpose is to send file-like objects via HTTP and, to do so, it
258 258 defines a __len__ attribute to feed the Content-Length header.
259 259 """
260 260
261 261 def __init__(self, *args, **kwargs):
262 262 # We can't just "self._data = open(*args, **kwargs)" here because there
263 263 # is an "open" function defined in this module that shadows the global
264 264 # one
265 265 self._data = __builtin__.open(*args, **kwargs)
266 266 self.read = self._data.read
267 267 self.seek = self._data.seek
268 268 self.close = self._data.close
269 269 self.write = self._data.write
270 270
271 271 def __len__(self):
272 272 return os.fstat(self._data.fileno()).st_size
273 273
274 274 def _gen_sendfile(connection):
275 275 def _sendfile(self, data):
276 276 # send a file
277 277 if isinstance(data, httpsendfile):
278 278 # if auth required, some data sent twice, so rewind here
279 279 data.seek(0)
280 280 for chunk in util.filechunkiter(data):
281 281 connection.send(self, chunk)
282 282 else:
283 283 connection.send(self, data)
284 284 return _sendfile
285 285
286 286 has_https = hasattr(urllib2, 'HTTPSHandler')
287 287 if has_https:
288 288 try:
289 289 # avoid using deprecated/broken FakeSocket in python 2.6
290 290 import ssl
291 291 _ssl_wrap_socket = ssl.wrap_socket
292 292 CERT_REQUIRED = ssl.CERT_REQUIRED
293 293 except ImportError:
294 294 CERT_REQUIRED = 2
295 295
296 296 def _ssl_wrap_socket(sock, key_file, cert_file,
297 297 cert_reqs=CERT_REQUIRED, ca_certs=None):
298 298 if ca_certs:
299 299 raise util.Abort(_(
300 300 'certificate checking requires Python 2.6'))
301 301
302 302 ssl = socket.ssl(sock, key_file, cert_file)
303 303 return httplib.FakeSocket(sock, ssl)
304 304
305 305 try:
306 306 _create_connection = socket.create_connection
307 307 except AttributeError:
308 308 _GLOBAL_DEFAULT_TIMEOUT = object()
309 309
310 310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
311 311 source_address=None):
312 312 # lifted from Python 2.6
313 313
314 314 msg = "getaddrinfo returns an empty list"
315 315 host, port = address
316 316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
317 317 af, socktype, proto, canonname, sa = res
318 318 sock = None
319 319 try:
320 320 sock = socket.socket(af, socktype, proto)
321 321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
322 322 sock.settimeout(timeout)
323 323 if source_address:
324 324 sock.bind(source_address)
325 325 sock.connect(sa)
326 326 return sock
327 327
328 328 except socket.error, msg:
329 329 if sock is not None:
330 330 sock.close()
331 331
332 332 raise socket.error, msg
333 333
334 334 class httpconnection(keepalive.HTTPConnection):
335 335 # must be able to send big bundle as stream.
336 336 send = _gen_sendfile(keepalive.HTTPConnection)
337 337
338 338 def connect(self):
339 339 if has_https and self.realhostport: # use CONNECT proxy
340 340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
341 341 self.sock.connect((self.host, self.port))
342 342 if _generic_proxytunnel(self):
343 343 # we do not support client x509 certificates
344 344 self.sock = _ssl_wrap_socket(self.sock, None, None)
345 345 else:
346 346 keepalive.HTTPConnection.connect(self)
347 347
348 348 def getresponse(self):
349 349 proxyres = getattr(self, 'proxyres', None)
350 350 if proxyres:
351 351 if proxyres.will_close:
352 352 self.close()
353 353 self.proxyres = None
354 354 return proxyres
355 355 return keepalive.HTTPConnection.getresponse(self)
356 356
357 357 # general transaction handler to support different ways to handle
358 358 # HTTPS proxying before and after Python 2.6.3.
359 359 def _generic_start_transaction(handler, h, req):
360 360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
361 361 tunnel_host = req._tunnel_host
362 362 if tunnel_host[:7] not in ['http://', 'https:/']:
363 363 tunnel_host = 'https://' + tunnel_host
364 364 new_tunnel = True
365 365 else:
366 366 tunnel_host = req.get_selector()
367 367 new_tunnel = False
368 368
369 369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
370 370 urlparts = urlparse.urlparse(tunnel_host)
371 371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
372 372 realhostport = urlparts[1]
373 373 if realhostport[-1] == ']' or ':' not in realhostport:
374 374 realhostport += ':443'
375 375
376 376 h.realhostport = realhostport
377 377 h.headers = req.headers.copy()
378 378 h.headers.update(handler.parent.addheaders)
379 379 return
380 380
381 381 h.realhostport = None
382 382 h.headers = None
383 383
384 384 def _generic_proxytunnel(self):
385 385 proxyheaders = dict(
386 386 [(x, self.headers[x]) for x in self.headers
387 387 if x.lower().startswith('proxy-')])
388 388 self._set_hostport(self.host, self.port)
389 389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
390 390 for header in proxyheaders.iteritems():
391 391 self.send('%s: %s\r\n' % header)
392 392 self.send('\r\n')
393 393
394 394 # majority of the following code is duplicated from
395 395 # httplib.HTTPConnection as there are no adequate places to
396 396 # override functions to provide the needed functionality
397 397 res = self.response_class(self.sock,
398 398 strict=self.strict,
399 399 method=self._method)
400 400
401 401 while True:
402 402 version, status, reason = res._read_status()
403 403 if status != httplib.CONTINUE:
404 404 break
405 405 while True:
406 406 skip = res.fp.readline().strip()
407 407 if not skip:
408 408 break
409 409 res.status = status
410 410 res.reason = reason.strip()
411 411
412 412 if res.status == 200:
413 413 while True:
414 414 line = res.fp.readline()
415 415 if line == '\r\n':
416 416 break
417 417 return True
418 418
419 419 if version == 'HTTP/1.0':
420 420 res.version = 10
421 421 elif version.startswith('HTTP/1.'):
422 422 res.version = 11
423 423 elif version == 'HTTP/0.9':
424 424 res.version = 9
425 425 else:
426 426 raise httplib.UnknownProtocol(version)
427 427
428 428 if res.version == 9:
429 429 res.length = None
430 430 res.chunked = 0
431 431 res.will_close = 1
432 432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
433 433 return False
434 434
435 435 res.msg = httplib.HTTPMessage(res.fp)
436 436 res.msg.fp = None
437 437
438 438 # are we using the chunked-style of transfer encoding?
439 439 trenc = res.msg.getheader('transfer-encoding')
440 440 if trenc and trenc.lower() == "chunked":
441 441 res.chunked = 1
442 442 res.chunk_left = None
443 443 else:
444 444 res.chunked = 0
445 445
446 446 # will the connection close at the end of the response?
447 447 res.will_close = res._check_close()
448 448
449 449 # do we have a Content-Length?
450 450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
451 451 length = res.msg.getheader('content-length')
452 452 if length and not res.chunked:
453 453 try:
454 454 res.length = int(length)
455 455 except ValueError:
456 456 res.length = None
457 457 else:
458 458 if res.length < 0: # ignore nonsensical negative lengths
459 459 res.length = None
460 460 else:
461 461 res.length = None
462 462
463 463 # does the body have a fixed length? (of zero)
464 464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
465 465 100 <= status < 200 or # 1xx codes
466 466 res._method == 'HEAD'):
467 467 res.length = 0
468 468
469 469 # if the connection remains open, and we aren't using chunked, and
470 470 # a content-length was not provided, then assume that the connection
471 471 # WILL close.
472 472 if (not res.will_close and
473 473 not res.chunked and
474 474 res.length is None):
475 475 res.will_close = 1
476 476
477 477 self.proxyres = res
478 478
479 479 return False
480 480
481 481 class httphandler(keepalive.HTTPHandler):
482 482 def http_open(self, req):
483 483 return self.do_open(httpconnection, req)
484 484
485 485 def _start_transaction(self, h, req):
486 486 _generic_start_transaction(self, h, req)
487 487 return keepalive.HTTPHandler._start_transaction(self, h, req)
488 488
489 489 def _verifycert(cert, hostname):
490 490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
491 491 CRLs and subjectAltName are not handled.
492 492
493 493 Returns error message if any problems are found and None on success.
494 494 '''
495 495 if not cert:
496 496 return _('no certificate received')
497 497 dnsname = hostname.lower()
498 498 for s in cert.get('subject', []):
499 499 key, value = s[0]
500 500 if key == 'commonName':
501 501 certname = value.lower()
502 502 if (certname == dnsname or
503 503 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
504 504 return None
505 505 return _('certificate is for %s') % certname
506 506 return _('no commonName found in certificate')
507 507
508 508 if has_https:
509 509 class BetterHTTPS(httplib.HTTPSConnection):
510 510 send = keepalive.safesend
511 511
512 512 def connect(self):
513 513 if hasattr(self, 'ui'):
514 514 cacerts = self.ui.config('web', 'cacerts')
515 if cacerts:
516 cacerts = util.expandpath(cacerts)
515 517 else:
516 518 cacerts = None
517 519
518 520 if cacerts:
519 521 sock = _create_connection((self.host, self.port))
520 522 self.sock = _ssl_wrap_socket(sock, self.key_file,
521 523 self.cert_file, cert_reqs=CERT_REQUIRED,
522 524 ca_certs=cacerts)
523 525 msg = _verifycert(self.sock.getpeercert(), self.host)
524 526 if msg:
525 527 raise util.Abort(_('%s certificate error: %s') %
526 528 (self.host, msg))
527 529 self.ui.debug('%s certificate successfully verified\n' %
528 530 self.host)
529 531 else:
530 532 self.ui.warn(_("warning: %s certificate not verified "
531 533 "(check web.cacerts config setting)\n") %
532 534 self.host)
533 535 httplib.HTTPSConnection.connect(self)
534 536
535 537 class httpsconnection(BetterHTTPS):
536 538 response_class = keepalive.HTTPResponse
537 539 # must be able to send big bundle as stream.
538 540 send = _gen_sendfile(BetterHTTPS)
539 541 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
540 542
541 543 def connect(self):
542 544 if self.realhostport: # use CONNECT proxy
543 545 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
544 546 self.sock.connect((self.host, self.port))
545 547 if _generic_proxytunnel(self):
546 548 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
547 549 self.cert_file)
548 550 else:
549 551 BetterHTTPS.connect(self)
550 552
551 553 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
552 554 def __init__(self, ui):
553 555 keepalive.KeepAliveHandler.__init__(self)
554 556 urllib2.HTTPSHandler.__init__(self)
555 557 self.ui = ui
556 558 self.pwmgr = passwordmgr(self.ui)
557 559
558 560 def _start_transaction(self, h, req):
559 561 _generic_start_transaction(self, h, req)
560 562 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
561 563
562 564 def https_open(self, req):
563 565 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
564 566 return self.do_open(self._makeconnection, req)
565 567
566 568 def _makeconnection(self, host, port=None, *args, **kwargs):
567 569 keyfile = None
568 570 certfile = None
569 571
570 572 if len(args) >= 1: # key_file
571 573 keyfile = args[0]
572 574 if len(args) >= 2: # cert_file
573 575 certfile = args[1]
574 576 args = args[2:]
575 577
576 578 # if the user has specified different key/cert files in
577 579 # hgrc, we prefer these
578 580 if self.auth and 'key' in self.auth and 'cert' in self.auth:
579 581 keyfile = self.auth['key']
580 582 certfile = self.auth['cert']
581 583
582 584 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
583 585 conn.ui = self.ui
584 586 return conn
585 587
586 588 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
587 589 def __init__(self, *args, **kwargs):
588 590 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
589 591 self.retried_req = None
590 592
591 593 def reset_retry_count(self):
592 594 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
593 595 # forever. We disable reset_retry_count completely and reset in
594 596 # http_error_auth_reqed instead.
595 597 pass
596 598
597 599 def http_error_auth_reqed(self, auth_header, host, req, headers):
598 600 # Reset the retry counter once for each request.
599 601 if req is not self.retried_req:
600 602 self.retried_req = req
601 603 self.retried = 0
602 604 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
603 605 # it doesn't know about the auth type requested. This can happen if
604 606 # somebody is using BasicAuth and types a bad password.
605 607 try:
606 608 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
607 609 self, auth_header, host, req, headers)
608 610 except ValueError, inst:
609 611 arg = inst.args[0]
610 612 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
611 613 return
612 614 raise
613 615
614 616 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
615 617 def __init__(self, *args, **kwargs):
616 618 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
617 619 self.retried_req = None
618 620
619 621 def reset_retry_count(self):
620 622 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
621 623 # forever. We disable reset_retry_count completely and reset in
622 624 # http_error_auth_reqed instead.
623 625 pass
624 626
625 627 def http_error_auth_reqed(self, auth_header, host, req, headers):
626 628 # Reset the retry counter once for each request.
627 629 if req is not self.retried_req:
628 630 self.retried_req = req
629 631 self.retried = 0
630 632 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
631 633 self, auth_header, host, req, headers)
632 634
633 635 def getauthinfo(path):
634 636 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
635 637 if not urlpath:
636 638 urlpath = '/'
637 639 if scheme != 'file':
638 640 # XXX: why are we quoting the path again with some smart
639 641 # heuristic here? Anyway, it cannot be done with file://
640 642 # urls since path encoding is os/fs dependent (see
641 643 # urllib.pathname2url() for details).
642 644 urlpath = quotepath(urlpath)
643 645 host, port, user, passwd = netlocsplit(netloc)
644 646
645 647 # urllib cannot handle URLs with embedded user or passwd
646 648 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
647 649 urlpath, query, frag))
648 650 if user:
649 651 netloc = host
650 652 if port:
651 653 netloc += ':' + port
652 654 # Python < 2.4.3 uses only the netloc to search for a password
653 655 authinfo = (None, (url, netloc), user, passwd or '')
654 656 else:
655 657 authinfo = None
656 658 return url, authinfo
657 659
658 660 handlerfuncs = []
659 661
660 662 def opener(ui, authinfo=None):
661 663 '''
662 664 construct an opener suitable for urllib2
663 665 authinfo will be added to the password manager
664 666 '''
665 667 handlers = [httphandler()]
666 668 if has_https:
667 669 handlers.append(httpshandler(ui))
668 670
669 671 handlers.append(proxyhandler(ui))
670 672
671 673 passmgr = passwordmgr(ui)
672 674 if authinfo is not None:
673 675 passmgr.add_password(*authinfo)
674 676 user, passwd = authinfo[2:4]
675 677 ui.debug('http auth: user %s, password %s\n' %
676 678 (user, passwd and '*' * len(passwd) or 'not set'))
677 679
678 680 handlers.extend((httpbasicauthhandler(passmgr),
679 681 httpdigestauthhandler(passmgr)))
680 682 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
681 683 opener = urllib2.build_opener(*handlers)
682 684
683 685 # 1.0 here is the _protocol_ version
684 686 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
685 687 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
686 688 return opener
687 689
688 690 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
689 691
690 692 def open(ui, url, data=None):
691 693 scheme = None
692 694 m = scheme_re.search(url)
693 695 if m:
694 696 scheme = m.group(1).lower()
695 697 if not scheme:
696 698 path = util.normpath(os.path.abspath(url))
697 699 url = 'file://' + urllib.pathname2url(path)
698 700 authinfo = None
699 701 else:
700 702 url, authinfo = getauthinfo(url)
701 703 return opener(ui, authinfo).open(url, data)
@@ -1,189 +1,190 b''
1 1 Proper https client requires the built-in ssl from Python 2.6.
2 2
3 3 $ "$TESTDIR/hghave" ssl || exit 80
4 4
5 5 Certificates created with:
6 6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 8 Can be dumped with:
9 9 openssl x509 -in pub.pem -text
10 10
11 11 $ cat << EOT > priv.pem
12 12 > -----BEGIN PRIVATE KEY-----
13 13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 20 > HY8gUVkVRVs=
21 21 > -----END PRIVATE KEY-----
22 22 > EOT
23 23
24 24 $ cat << EOT > pub.pem
25 25 > -----BEGIN CERTIFICATE-----
26 26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 35 > -----END CERTIFICATE-----
36 36 > EOT
37 37 $ cat priv.pem pub.pem >> server.pem
38 38 $ PRIV=`pwd`/server.pem
39 39
40 40 $ cat << EOT > pub-other.pem
41 41 > -----BEGIN CERTIFICATE-----
42 42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 51 > -----END CERTIFICATE-----
52 52 > EOT
53 53
54 54 pub.pem patched with other notBefore / notAfter:
55 55
56 56 $ cat << EOT > pub-not-yet.pem
57 57 > -----BEGIN CERTIFICATE-----
58 58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 66 > -----END CERTIFICATE-----
67 67 > EOT
68 68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69 69
70 70 $ cat << EOT > pub-expired.pem
71 71 > -----BEGIN CERTIFICATE-----
72 72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 80 > -----END CERTIFICATE-----
81 81 > EOT
82 82 $ cat priv.pem pub-expired.pem > server-expired.pem
83 83
84 84 $ hg init test
85 85 $ cd test
86 86 $ echo foo>foo
87 87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 88 $ echo foo>foo.d/foo
89 89 $ echo bar>foo.d/bAr.hg.d/BaR
90 90 $ echo bar>foo.d/baR.d.hg/bAR
91 91 $ hg commit -A -m 1
92 92 adding foo
93 93 adding foo.d/bAr.hg.d/BaR
94 94 adding foo.d/baR.d.hg/bAR
95 95 adding foo.d/foo
96 96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 97 $ cat ../hg0.pid >> $DAEMON_PIDS
98 98
99 99 Test server address cannot be reused
100 100
101 101 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
102 102 abort: cannot start server at ':$HGPORT': Address already in use
103 103 [255]
104 104 $ cd ..
105 105
106 106 clone via pull
107 107
108 108 $ hg clone https://localhost:$HGPORT/ copy-pull
109 109 warning: localhost certificate not verified (check web.cacerts config setting)
110 110 requesting all changes
111 111 adding changesets
112 112 adding manifests
113 113 adding file changes
114 114 added 1 changesets with 4 changes to 4 files
115 115 updating to branch default
116 116 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 117 $ hg verify -R copy-pull
118 118 checking changesets
119 119 checking manifests
120 120 crosschecking files in changesets and manifests
121 121 checking files
122 122 4 files, 1 changesets, 4 total revisions
123 123 $ cd test
124 124 $ echo bar > bar
125 125 $ hg commit -A -d '1 0' -m 2
126 126 adding bar
127 127 $ cd ..
128 128
129 129 pull without cacert
130 130
131 131 $ cd copy-pull
132 132 $ echo '[hooks]' >> .hg/hgrc
133 133 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
134 134 $ hg pull
135 135 warning: localhost certificate not verified (check web.cacerts config setting)
136 136 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
137 137 pulling from https://localhost:$HGPORT/
138 138 searching for changes
139 139 adding changesets
140 140 adding manifests
141 141 adding file changes
142 142 added 1 changesets with 1 changes to 1 files
143 143 (run 'hg update' to get a working copy)
144 144 $ cd ..
145 145
146 146 cacert configured in local repo
147 147
148 148 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
149 149 $ echo "[web]" >> copy-pull/.hg/hgrc
150 150 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
151 151 $ hg -R copy-pull pull --traceback
152 152 pulling from https://localhost:$HGPORT/
153 153 searching for changes
154 154 no changes found
155 155 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
156 156
157 cacert configured globally
157 cacert configured globally, also testing expansion of environment
158 variables in the filename
158 159
159 160 $ echo "[web]" >> $HGRCPATH
160 $ echo "cacerts=`pwd`/pub.pem" >> $HGRCPATH
161 $ hg -R copy-pull pull
161 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
162 $ P=`pwd` hg -R copy-pull pull
162 163 pulling from https://localhost:$HGPORT/
163 164 searching for changes
164 165 no changes found
165 166
166 167 cacert mismatch
167 168
168 169 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
169 170 abort: 127.0.0.1 certificate error: certificate is for localhost
170 171 [255]
171 172 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
172 173 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
173 174 [255]
174 175
175 176 Test server cert which isn't valid yet
176 177
177 178 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
178 179 $ cat hg1.pid >> $DAEMON_PIDS
179 180 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
180 181 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
181 182 [255]
182 183
183 184 Test server cert which no longer is valid
184 185
185 186 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
186 187 $ cat hg2.pid >> $DAEMON_PIDS
187 188 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
188 189 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
189 190 [255]
General Comments 0
You need to be logged in to leave comments. Login now