##// END OF EJS Templates
url: do not continue HTTP authentication with user=None (issue6425)...
Yuya Nishihara -
r46381:ff48eea4 stable
parent child Browse files
Show More
@@ -1,728 +1,735 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 from __future__ import absolute_import
11 11
12 12 import base64
13 13 import os
14 14 import socket
15 15 import sys
16 16
17 17 from .i18n import _
18 18 from .pycompat import getattr
19 19 from . import (
20 20 encoding,
21 21 error,
22 22 httpconnection as httpconnectionmod,
23 23 keepalive,
24 24 pycompat,
25 25 sslutil,
26 26 urllibcompat,
27 27 util,
28 28 )
29 29 from .utils import stringutil
30 30
31 31 httplib = util.httplib
32 32 stringio = util.stringio
33 33 urlerr = util.urlerr
34 34 urlreq = util.urlreq
35 35
36 36
37 37 def escape(s, quote=None):
38 38 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
39 39 If the optional flag quote is true, the quotation mark character (")
40 40 is also translated.
41 41
42 42 This is the same as cgi.escape in Python, but always operates on
43 43 bytes, whereas cgi.escape in Python 3 only works on unicodes.
44 44 '''
45 45 s = s.replace(b"&", b"&amp;")
46 46 s = s.replace(b"<", b"&lt;")
47 47 s = s.replace(b">", b"&gt;")
48 48 if quote:
49 49 s = s.replace(b'"', b"&quot;")
50 50 return s
51 51
52 52
53 53 class passwordmgr(object):
54 54 def __init__(self, ui, passwddb):
55 55 self.ui = ui
56 56 self.passwddb = passwddb
57 57
58 58 def add_password(self, realm, uri, user, passwd):
59 59 return self.passwddb.add_password(realm, uri, user, passwd)
60 60
61 61 def find_user_password(self, realm, authuri):
62 62 assert isinstance(realm, (type(None), str))
63 63 assert isinstance(authuri, str)
64 64 authinfo = self.passwddb.find_user_password(realm, authuri)
65 65 user, passwd = authinfo
66 66 user, passwd = pycompat.bytesurl(user), pycompat.bytesurl(passwd)
67 67 if user and passwd:
68 68 self._writedebug(user, passwd)
69 69 return (pycompat.strurl(user), pycompat.strurl(passwd))
70 70
71 71 if not user or not passwd:
72 72 res = httpconnectionmod.readauthforuri(self.ui, authuri, user)
73 73 if res:
74 74 group, auth = res
75 75 user, passwd = auth.get(b'username'), auth.get(b'password')
76 76 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
77 77 if not user or not passwd:
78 78 u = util.url(pycompat.bytesurl(authuri))
79 79 u.query = None
80 80 if not self.ui.interactive():
81 81 raise error.Abort(
82 82 _(b'http authorization required for %s')
83 83 % util.hidepassword(bytes(u))
84 84 )
85 85
86 86 self.ui.write(
87 87 _(b"http authorization required for %s\n")
88 88 % util.hidepassword(bytes(u))
89 89 )
90 90 self.ui.write(_(b"realm: %s\n") % pycompat.bytesurl(realm))
91 91 if user:
92 92 self.ui.write(_(b"user: %s\n") % user)
93 93 else:
94 94 user = self.ui.prompt(_(b"user:"), default=None)
95 95
96 96 if not passwd:
97 97 passwd = self.ui.getpass()
98 98
99 # As of Python 3.8, the default implementation of
100 # AbstractBasicAuthHandler.retry_http_basic_auth() assumes the user
101 # is set if pw is not None. This means (None, str) is not a valid
102 # return type of find_user_password().
103 if user is None:
104 return None, None
105
99 106 self.passwddb.add_password(realm, authuri, user, passwd)
100 107 self._writedebug(user, passwd)
101 108 return (pycompat.strurl(user), pycompat.strurl(passwd))
102 109
103 110 def _writedebug(self, user, passwd):
104 111 msg = _(b'http auth: user %s, password %s\n')
105 112 self.ui.debug(msg % (user, passwd and b'*' * len(passwd) or b'not set'))
106 113
107 114 def find_stored_password(self, authuri):
108 115 return self.passwddb.find_user_password(None, authuri)
109 116
110 117
111 118 class proxyhandler(urlreq.proxyhandler):
112 119 def __init__(self, ui):
113 120 proxyurl = ui.config(b"http_proxy", b"host") or encoding.environ.get(
114 121 b'http_proxy'
115 122 )
116 123 # XXX proxyauthinfo = None
117 124
118 125 if proxyurl:
119 126 # proxy can be proper url or host[:port]
120 127 if not (
121 128 proxyurl.startswith(b'http:') or proxyurl.startswith(b'https:')
122 129 ):
123 130 proxyurl = b'http://' + proxyurl + b'/'
124 131 proxy = util.url(proxyurl)
125 132 if not proxy.user:
126 133 proxy.user = ui.config(b"http_proxy", b"user")
127 134 proxy.passwd = ui.config(b"http_proxy", b"passwd")
128 135
129 136 # see if we should use a proxy for this url
130 137 no_list = [b"localhost", b"127.0.0.1"]
131 138 no_list.extend(
132 139 [p.lower() for p in ui.configlist(b"http_proxy", b"no")]
133 140 )
134 141 no_list.extend(
135 142 [
136 143 p.strip().lower()
137 144 for p in encoding.environ.get(b"no_proxy", b'').split(b',')
138 145 if p.strip()
139 146 ]
140 147 )
141 148 # "http_proxy.always" config is for running tests on localhost
142 149 if ui.configbool(b"http_proxy", b"always"):
143 150 self.no_list = []
144 151 else:
145 152 self.no_list = no_list
146 153
147 154 # Keys and values need to be str because the standard library
148 155 # expects them to be.
149 156 proxyurl = str(proxy)
150 157 proxies = {'http': proxyurl, 'https': proxyurl}
151 158 ui.debug(b'proxying through %s\n' % util.hidepassword(bytes(proxy)))
152 159 else:
153 160 proxies = {}
154 161
155 162 urlreq.proxyhandler.__init__(self, proxies)
156 163 self.ui = ui
157 164
158 165 def proxy_open(self, req, proxy, type_):
159 166 host = pycompat.bytesurl(urllibcompat.gethost(req)).split(b':')[0]
160 167 for e in self.no_list:
161 168 if host == e:
162 169 return None
163 170 if e.startswith(b'*.') and host.endswith(e[2:]):
164 171 return None
165 172 if e.startswith(b'.') and host.endswith(e[1:]):
166 173 return None
167 174
168 175 return urlreq.proxyhandler.proxy_open(self, req, proxy, type_)
169 176
170 177
171 178 def _gen_sendfile(orgsend):
172 179 def _sendfile(self, data):
173 180 # send a file
174 181 if isinstance(data, httpconnectionmod.httpsendfile):
175 182 # if auth required, some data sent twice, so rewind here
176 183 data.seek(0)
177 184 for chunk in util.filechunkiter(data):
178 185 orgsend(self, chunk)
179 186 else:
180 187 orgsend(self, data)
181 188
182 189 return _sendfile
183 190
184 191
185 192 has_https = util.safehasattr(urlreq, b'httpshandler')
186 193
187 194
188 195 class httpconnection(keepalive.HTTPConnection):
189 196 # must be able to send big bundle as stream.
190 197 send = _gen_sendfile(keepalive.HTTPConnection.send)
191 198
192 199 def getresponse(self):
193 200 proxyres = getattr(self, 'proxyres', None)
194 201 if proxyres:
195 202 if proxyres.will_close:
196 203 self.close()
197 204 self.proxyres = None
198 205 return proxyres
199 206 return keepalive.HTTPConnection.getresponse(self)
200 207
201 208
202 209 # Large parts of this function have their origin from before Python 2.6
203 210 # and could potentially be removed.
204 211 def _generic_start_transaction(handler, h, req):
205 212 tunnel_host = req._tunnel_host
206 213 if tunnel_host:
207 214 if tunnel_host[:7] not in ['http://', 'https:/']:
208 215 tunnel_host = 'https://' + tunnel_host
209 216 new_tunnel = True
210 217 else:
211 218 tunnel_host = urllibcompat.getselector(req)
212 219 new_tunnel = False
213 220
214 221 if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
215 222 u = util.url(pycompat.bytesurl(tunnel_host))
216 223 if new_tunnel or u.scheme == b'https': # only use CONNECT for HTTPS
217 224 h.realhostport = b':'.join([u.host, (u.port or b'443')])
218 225 h.headers = req.headers.copy()
219 226 h.headers.update(handler.parent.addheaders)
220 227 return
221 228
222 229 h.realhostport = None
223 230 h.headers = None
224 231
225 232
226 233 def _generic_proxytunnel(self):
227 234 proxyheaders = {
228 235 pycompat.bytestr(x): pycompat.bytestr(self.headers[x])
229 236 for x in self.headers
230 237 if x.lower().startswith('proxy-')
231 238 }
232 239 self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
233 240 for header in pycompat.iteritems(proxyheaders):
234 241 self.send(b'%s: %s\r\n' % header)
235 242 self.send(b'\r\n')
236 243
237 244 # majority of the following code is duplicated from
238 245 # httplib.HTTPConnection as there are no adequate places to
239 246 # override functions to provide the needed functionality
240 247 # strict was removed in Python 3.4.
241 248 kwargs = {}
242 249 if not pycompat.ispy3:
243 250 kwargs[b'strict'] = self.strict
244 251
245 252 res = self.response_class(self.sock, method=self._method, **kwargs)
246 253
247 254 while True:
248 255 version, status, reason = res._read_status()
249 256 if status != httplib.CONTINUE:
250 257 break
251 258 # skip lines that are all whitespace
252 259 list(iter(lambda: res.fp.readline().strip(), b''))
253 260 res.status = status
254 261 res.reason = reason.strip()
255 262
256 263 if res.status == 200:
257 264 # skip lines until we find a blank line
258 265 list(iter(res.fp.readline, b'\r\n'))
259 266 return True
260 267
261 268 if version == b'HTTP/1.0':
262 269 res.version = 10
263 270 elif version.startswith(b'HTTP/1.'):
264 271 res.version = 11
265 272 elif version == b'HTTP/0.9':
266 273 res.version = 9
267 274 else:
268 275 raise httplib.UnknownProtocol(version)
269 276
270 277 if res.version == 9:
271 278 res.length = None
272 279 res.chunked = 0
273 280 res.will_close = 1
274 281 res.msg = httplib.HTTPMessage(stringio())
275 282 return False
276 283
277 284 res.msg = httplib.HTTPMessage(res.fp)
278 285 res.msg.fp = None
279 286
280 287 # are we using the chunked-style of transfer encoding?
281 288 trenc = res.msg.getheader(b'transfer-encoding')
282 289 if trenc and trenc.lower() == b"chunked":
283 290 res.chunked = 1
284 291 res.chunk_left = None
285 292 else:
286 293 res.chunked = 0
287 294
288 295 # will the connection close at the end of the response?
289 296 res.will_close = res._check_close()
290 297
291 298 # do we have a Content-Length?
292 299 # NOTE: RFC 2616, section 4.4, #3 says we ignore this if
293 300 # transfer-encoding is "chunked"
294 301 length = res.msg.getheader(b'content-length')
295 302 if length and not res.chunked:
296 303 try:
297 304 res.length = int(length)
298 305 except ValueError:
299 306 res.length = None
300 307 else:
301 308 if res.length < 0: # ignore nonsensical negative lengths
302 309 res.length = None
303 310 else:
304 311 res.length = None
305 312
306 313 # does the body have a fixed length? (of zero)
307 314 if (
308 315 status == httplib.NO_CONTENT
309 316 or status == httplib.NOT_MODIFIED
310 317 or 100 <= status < 200
311 318 or res._method == b'HEAD' # 1xx codes
312 319 ):
313 320 res.length = 0
314 321
315 322 # if the connection remains open, and we aren't using chunked, and
316 323 # a content-length was not provided, then assume that the connection
317 324 # WILL close.
318 325 if not res.will_close and not res.chunked and res.length is None:
319 326 res.will_close = 1
320 327
321 328 self.proxyres = res
322 329
323 330 return False
324 331
325 332
326 333 class httphandler(keepalive.HTTPHandler):
327 334 def http_open(self, req):
328 335 return self.do_open(httpconnection, req)
329 336
330 337 def _start_transaction(self, h, req):
331 338 _generic_start_transaction(self, h, req)
332 339 return keepalive.HTTPHandler._start_transaction(self, h, req)
333 340
334 341
335 342 class logginghttpconnection(keepalive.HTTPConnection):
336 343 def __init__(self, createconn, *args, **kwargs):
337 344 keepalive.HTTPConnection.__init__(self, *args, **kwargs)
338 345 self._create_connection = createconn
339 346
340 347 if sys.version_info < (2, 7, 7):
341 348 # copied from 2.7.14, since old implementations directly call
342 349 # socket.create_connection()
343 350 def connect(self):
344 351 self.sock = self._create_connection(
345 352 (self.host, self.port), self.timeout, self.source_address
346 353 )
347 354 if self._tunnel_host:
348 355 self._tunnel()
349 356
350 357
351 358 class logginghttphandler(httphandler):
352 359 """HTTP handler that logs socket I/O."""
353 360
354 361 def __init__(self, logfh, name, observeropts, timeout=None):
355 362 super(logginghttphandler, self).__init__(timeout=timeout)
356 363
357 364 self._logfh = logfh
358 365 self._logname = name
359 366 self._observeropts = observeropts
360 367
361 368 # do_open() calls the passed class to instantiate an HTTPConnection. We
362 369 # pass in a callable method that creates a custom HTTPConnection instance
363 370 # whose callback to create the socket knows how to proxy the socket.
364 371 def http_open(self, req):
365 372 return self.do_open(self._makeconnection, req)
366 373
367 374 def _makeconnection(self, *args, **kwargs):
368 375 def createconnection(*args, **kwargs):
369 376 sock = socket.create_connection(*args, **kwargs)
370 377 return util.makeloggingsocket(
371 378 self._logfh, sock, self._logname, **self._observeropts
372 379 )
373 380
374 381 return logginghttpconnection(createconnection, *args, **kwargs)
375 382
376 383
377 384 if has_https:
378 385
379 386 class httpsconnection(keepalive.HTTPConnection):
380 387 response_class = keepalive.HTTPResponse
381 388 default_port = httplib.HTTPS_PORT
382 389 # must be able to send big bundle as stream.
383 390 send = _gen_sendfile(keepalive.safesend)
384 391 getresponse = keepalive.wrapgetresponse(httplib.HTTPConnection)
385 392
386 393 def __init__(
387 394 self,
388 395 host,
389 396 port=None,
390 397 key_file=None,
391 398 cert_file=None,
392 399 *args,
393 400 **kwargs
394 401 ):
395 402 keepalive.HTTPConnection.__init__(self, host, port, *args, **kwargs)
396 403 self.key_file = key_file
397 404 self.cert_file = cert_file
398 405
399 406 def connect(self):
400 407 self.sock = socket.create_connection((self.host, self.port))
401 408
402 409 host = self.host
403 410 if self.realhostport: # use CONNECT proxy
404 411 _generic_proxytunnel(self)
405 412 host = self.realhostport.rsplit(b':', 1)[0]
406 413 self.sock = sslutil.wrapsocket(
407 414 self.sock,
408 415 self.key_file,
409 416 self.cert_file,
410 417 ui=self.ui,
411 418 serverhostname=host,
412 419 )
413 420 sslutil.validatesocket(self.sock)
414 421
415 422 class httpshandler(keepalive.KeepAliveHandler, urlreq.httpshandler):
416 423 def __init__(self, ui, timeout=None):
417 424 keepalive.KeepAliveHandler.__init__(self, timeout=timeout)
418 425 urlreq.httpshandler.__init__(self)
419 426 self.ui = ui
420 427 self.pwmgr = passwordmgr(self.ui, self.ui.httppasswordmgrdb)
421 428
422 429 def _start_transaction(self, h, req):
423 430 _generic_start_transaction(self, h, req)
424 431 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
425 432
426 433 def https_open(self, req):
427 434 # urllibcompat.getfullurl() does not contain credentials
428 435 # and we may need them to match the certificates.
429 436 url = urllibcompat.getfullurl(req)
430 437 user, password = self.pwmgr.find_stored_password(url)
431 438 res = httpconnectionmod.readauthforuri(self.ui, url, user)
432 439 if res:
433 440 group, auth = res
434 441 self.auth = auth
435 442 self.ui.debug(b"using auth.%s.* for authentication\n" % group)
436 443 else:
437 444 self.auth = None
438 445 return self.do_open(self._makeconnection, req)
439 446
440 447 def _makeconnection(self, host, port=None, *args, **kwargs):
441 448 keyfile = None
442 449 certfile = None
443 450
444 451 if len(args) >= 1: # key_file
445 452 keyfile = args[0]
446 453 if len(args) >= 2: # cert_file
447 454 certfile = args[1]
448 455 args = args[2:]
449 456
450 457 # if the user has specified different key/cert files in
451 458 # hgrc, we prefer these
452 459 if self.auth and b'key' in self.auth and b'cert' in self.auth:
453 460 keyfile = self.auth[b'key']
454 461 certfile = self.auth[b'cert']
455 462
456 463 conn = httpsconnection(
457 464 host, port, keyfile, certfile, *args, **kwargs
458 465 )
459 466 conn.ui = self.ui
460 467 return conn
461 468
462 469
463 470 class httpdigestauthhandler(urlreq.httpdigestauthhandler):
464 471 def __init__(self, *args, **kwargs):
465 472 urlreq.httpdigestauthhandler.__init__(self, *args, **kwargs)
466 473 self.retried_req = None
467 474
468 475 def reset_retry_count(self):
469 476 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
470 477 # forever. We disable reset_retry_count completely and reset in
471 478 # http_error_auth_reqed instead.
472 479 pass
473 480
474 481 def http_error_auth_reqed(self, auth_header, host, req, headers):
475 482 # Reset the retry counter once for each request.
476 483 if req is not self.retried_req:
477 484 self.retried_req = req
478 485 self.retried = 0
479 486 return urlreq.httpdigestauthhandler.http_error_auth_reqed(
480 487 self, auth_header, host, req, headers
481 488 )
482 489
483 490
484 491 class httpbasicauthhandler(urlreq.httpbasicauthhandler):
485 492 def __init__(self, *args, **kwargs):
486 493 self.auth = None
487 494 urlreq.httpbasicauthhandler.__init__(self, *args, **kwargs)
488 495 self.retried_req = None
489 496
490 497 def http_request(self, request):
491 498 if self.auth:
492 499 request.add_unredirected_header(self.auth_header, self.auth)
493 500
494 501 return request
495 502
496 503 def https_request(self, request):
497 504 if self.auth:
498 505 request.add_unredirected_header(self.auth_header, self.auth)
499 506
500 507 return request
501 508
502 509 def reset_retry_count(self):
503 510 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
504 511 # forever. We disable reset_retry_count completely and reset in
505 512 # http_error_auth_reqed instead.
506 513 pass
507 514
508 515 def http_error_auth_reqed(self, auth_header, host, req, headers):
509 516 # Reset the retry counter once for each request.
510 517 if req is not self.retried_req:
511 518 self.retried_req = req
512 519 self.retried = 0
513 520 return urlreq.httpbasicauthhandler.http_error_auth_reqed(
514 521 self, auth_header, host, req, headers
515 522 )
516 523
517 524 def retry_http_basic_auth(self, host, req, realm):
518 525 user, pw = self.passwd.find_user_password(
519 526 realm, urllibcompat.getfullurl(req)
520 527 )
521 528 if pw is not None:
522 529 raw = b"%s:%s" % (pycompat.bytesurl(user), pycompat.bytesurl(pw))
523 530 auth = 'Basic %s' % pycompat.strurl(base64.b64encode(raw).strip())
524 531 if req.get_header(self.auth_header, None) == auth:
525 532 return None
526 533 self.auth = auth
527 534 req.add_unredirected_header(self.auth_header, auth)
528 535 return self.parent.open(req)
529 536 else:
530 537 return None
531 538
532 539
533 540 class cookiehandler(urlreq.basehandler):
534 541 def __init__(self, ui):
535 542 self.cookiejar = None
536 543
537 544 cookiefile = ui.config(b'auth', b'cookiefile')
538 545 if not cookiefile:
539 546 return
540 547
541 548 cookiefile = util.expandpath(cookiefile)
542 549 try:
543 550 cookiejar = util.cookielib.MozillaCookieJar(
544 551 pycompat.fsdecode(cookiefile)
545 552 )
546 553 cookiejar.load()
547 554 self.cookiejar = cookiejar
548 555 except util.cookielib.LoadError as e:
549 556 ui.warn(
550 557 _(
551 558 b'(error loading cookie file %s: %s; continuing without '
552 559 b'cookies)\n'
553 560 )
554 561 % (cookiefile, stringutil.forcebytestr(e))
555 562 )
556 563
557 564 def http_request(self, request):
558 565 if self.cookiejar:
559 566 self.cookiejar.add_cookie_header(request)
560 567
561 568 return request
562 569
563 570 def https_request(self, request):
564 571 if self.cookiejar:
565 572 self.cookiejar.add_cookie_header(request)
566 573
567 574 return request
568 575
569 576
570 577 handlerfuncs = []
571 578
572 579
573 580 def opener(
574 581 ui,
575 582 authinfo=None,
576 583 useragent=None,
577 584 loggingfh=None,
578 585 loggingname=b's',
579 586 loggingopts=None,
580 587 sendaccept=True,
581 588 ):
582 589 '''
583 590 construct an opener suitable for urllib2
584 591 authinfo will be added to the password manager
585 592
586 593 The opener can be configured to log socket events if the various
587 594 ``logging*`` arguments are specified.
588 595
589 596 ``loggingfh`` denotes a file object to log events to.
590 597 ``loggingname`` denotes the name of the to print when logging.
591 598 ``loggingopts`` is a dict of keyword arguments to pass to the constructed
592 599 ``util.socketobserver`` instance.
593 600
594 601 ``sendaccept`` allows controlling whether the ``Accept`` request header
595 602 is sent. The header is sent by default.
596 603 '''
597 604 timeout = ui.configwith(float, b'http', b'timeout')
598 605 handlers = []
599 606
600 607 if loggingfh:
601 608 handlers.append(
602 609 logginghttphandler(
603 610 loggingfh, loggingname, loggingopts or {}, timeout=timeout
604 611 )
605 612 )
606 613 # We don't yet support HTTPS when logging I/O. If we attempt to open
607 614 # an HTTPS URL, we'll likely fail due to unknown protocol.
608 615
609 616 else:
610 617 handlers.append(httphandler(timeout=timeout))
611 618 if has_https:
612 619 handlers.append(httpshandler(ui, timeout=timeout))
613 620
614 621 handlers.append(proxyhandler(ui))
615 622
616 623 passmgr = passwordmgr(ui, ui.httppasswordmgrdb)
617 624 if authinfo is not None:
618 625 realm, uris, user, passwd = authinfo
619 626 saveduser, savedpass = passmgr.find_stored_password(uris[0])
620 627 if user != saveduser or passwd:
621 628 passmgr.add_password(realm, uris, user, passwd)
622 629 ui.debug(
623 630 b'http auth: user %s, password %s\n'
624 631 % (user, passwd and b'*' * len(passwd) or b'not set')
625 632 )
626 633
627 634 handlers.extend(
628 635 (httpbasicauthhandler(passmgr), httpdigestauthhandler(passmgr))
629 636 )
630 637 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
631 638 handlers.append(cookiehandler(ui))
632 639 opener = urlreq.buildopener(*handlers)
633 640
634 641 # keepalive.py's handlers will populate these attributes if they exist.
635 642 opener.requestscount = 0
636 643 opener.sentbytescount = 0
637 644 opener.receivedbytescount = 0
638 645
639 646 # The user agent should should *NOT* be used by servers for e.g.
640 647 # protocol detection or feature negotiation: there are other
641 648 # facilities for that.
642 649 #
643 650 # "mercurial/proto-1.0" was the original user agent string and
644 651 # exists for backwards compatibility reasons.
645 652 #
646 653 # The "(Mercurial %s)" string contains the distribution
647 654 # name and version. Other client implementations should choose their
648 655 # own distribution name. Since servers should not be using the user
649 656 # agent string for anything, clients should be able to define whatever
650 657 # user agent they deem appropriate.
651 658 #
652 659 # The custom user agent is for lfs, because unfortunately some servers
653 660 # do look at this value.
654 661 if not useragent:
655 662 agent = b'mercurial/proto-1.0 (Mercurial %s)' % util.version()
656 663 opener.addheaders = [('User-agent', pycompat.sysstr(agent))]
657 664 else:
658 665 opener.addheaders = [('User-agent', pycompat.sysstr(useragent))]
659 666
660 667 # This header should only be needed by wire protocol requests. But it has
661 668 # been sent on all requests since forever. We keep sending it for backwards
662 669 # compatibility reasons. Modern versions of the wire protocol use
663 670 # X-HgProto-<N> for advertising client support.
664 671 if sendaccept:
665 672 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
666 673
667 674 return opener
668 675
669 676
670 677 def open(ui, url_, data=None, sendaccept=True):
671 678 u = util.url(url_)
672 679 if u.scheme:
673 680 u.scheme = u.scheme.lower()
674 681 url_, authinfo = u.authinfo()
675 682 else:
676 683 path = util.normpath(os.path.abspath(url_))
677 684 url_ = b'file://' + pycompat.bytesurl(
678 685 urlreq.pathname2url(pycompat.fsdecode(path))
679 686 )
680 687 authinfo = None
681 688 return opener(ui, authinfo, sendaccept=sendaccept).open(
682 689 pycompat.strurl(url_), data
683 690 )
684 691
685 692
686 693 def wrapresponse(resp):
687 694 """Wrap a response object with common error handlers.
688 695
689 696 This ensures that any I/O from any consumer raises the appropriate
690 697 error and messaging.
691 698 """
692 699 origread = resp.read
693 700
694 701 class readerproxy(resp.__class__):
695 702 def read(self, size=None):
696 703 try:
697 704 return origread(size)
698 705 except httplib.IncompleteRead as e:
699 706 # e.expected is an integer if length known or None otherwise.
700 707 if e.expected:
701 708 got = len(e.partial)
702 709 total = e.expected + got
703 710 msg = _(
704 711 b'HTTP request error (incomplete response; '
705 712 b'expected %d bytes got %d)'
706 713 ) % (total, got)
707 714 else:
708 715 msg = _(b'HTTP request error (incomplete response)')
709 716
710 717 raise error.PeerTransportError(
711 718 msg,
712 719 hint=_(
713 720 b'this may be an intermittent network failure; '
714 721 b'if the error persists, consider contacting the '
715 722 b'network or server operator'
716 723 ),
717 724 )
718 725 except httplib.HTTPException as e:
719 726 raise error.PeerTransportError(
720 727 _(b'HTTP request error (%s)') % e,
721 728 hint=_(
722 729 b'this may be an intermittent network failure; '
723 730 b'if the error persists, consider contacting the '
724 731 b'network or server operator'
725 732 ),
726 733 )
727 734
728 735 resp.__class__ = readerproxy
@@ -1,584 +1,615 b''
1 1 #require serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 7 $ echo foo>foo.d/foo
8 8 $ echo bar>foo.d/bAr.hg.d/BaR
9 9 $ echo bar>foo.d/baR.d.hg/bAR
10 10 $ hg commit -A -m 1
11 11 adding foo
12 12 adding foo.d/bAr.hg.d/BaR
13 13 adding foo.d/baR.d.hg/bAR
14 14 adding foo.d/foo
15 15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17 17
18 18 Test server address cannot be reused
19 19
20 20 $ hg serve -p $HGPORT1 2>&1
21 21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 22 [255]
23 23
24 24 $ cd ..
25 25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26 26
27 27 clone via stream
28 28
29 29 #if no-reposimplestore
30 30 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
31 31 streaming all changes
32 32 9 files to transfer, 715 bytes of data
33 33 transferred * bytes in * seconds (*/sec) (glob)
34 34 updating to branch default
35 35 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ hg verify -R copy
37 37 checking changesets
38 38 checking manifests
39 39 crosschecking files in changesets and manifests
40 40 checking files
41 41 checked 1 changesets with 4 changes to 4 files
42 42 #endif
43 43
44 44 try to clone via stream, should use pull instead
45 45
46 46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 47 warning: stream clone requested but server has them disabled
48 48 requesting all changes
49 49 adding changesets
50 50 adding manifests
51 51 adding file changes
52 52 added 1 changesets with 4 changes to 4 files
53 53 new changesets 8b6053c928fe
54 54 updating to branch default
55 55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56
57 57 try to clone via stream but missing requirements, so should use pull instead
58 58
59 59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 60 > from mercurial import localrepo
61 61 > def extsetup(ui):
62 62 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
63 63 > EOF
64 64
65 65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 66 warning: stream clone requested but client is missing requirements: generaldelta
67 67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 68 requesting all changes
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 1 changesets with 4 changes to 4 files
73 73 new changesets 8b6053c928fe
74 74 updating to branch default
75 75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76
77 77 clone via pull
78 78
79 79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 80 requesting all changes
81 81 adding changesets
82 82 adding manifests
83 83 adding file changes
84 84 added 1 changesets with 4 changes to 4 files
85 85 new changesets 8b6053c928fe
86 86 updating to branch default
87 87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 88 $ hg verify -R copy-pull
89 89 checking changesets
90 90 checking manifests
91 91 crosschecking files in changesets and manifests
92 92 checking files
93 93 checked 1 changesets with 4 changes to 4 files
94 94 $ cd test
95 95 $ echo bar > bar
96 96 $ hg commit -A -d '1 0' -m 2
97 97 adding bar
98 98 $ cd ..
99 99
100 100 clone over http with --update
101 101
102 102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 103 requesting all changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 2 changesets with 5 changes to 5 files
108 108 new changesets 8b6053c928fe:5fed3813f7f5
109 109 updating to branch default
110 110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 $ hg log -r . -R updated
112 112 changeset: 0:8b6053c928fe
113 113 user: test
114 114 date: Thu Jan 01 00:00:00 1970 +0000
115 115 summary: 1
116 116
117 117 $ rm -rf updated
118 118
119 119 incoming via HTTP
120 120
121 121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 122 adding changesets
123 123 adding manifests
124 124 adding file changes
125 125 added 1 changesets with 4 changes to 4 files
126 126 new changesets 8b6053c928fe
127 127 updating to branch default
128 128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 129 $ cd partial
130 130 $ touch LOCAL
131 131 $ hg ci -qAm LOCAL
132 132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 133 comparing with http://localhost:$HGPORT1/
134 134 searching for changes
135 135 2
136 136 $ cd ..
137 137
138 138 pull
139 139
140 140 $ cd copy-pull
141 141 $ cat >> .hg/hgrc <<EOF
142 142 > [hooks]
143 143 > changegroup = sh -c "printenv.py --line changegroup"
144 144 > EOF
145 145 $ hg pull
146 146 pulling from http://localhost:$HGPORT1/
147 147 searching for changes
148 148 adding changesets
149 149 adding manifests
150 150 adding file changes
151 151 added 1 changesets with 1 changes to 1 files
152 152 new changesets 5fed3813f7f5
153 153 changegroup hook: HG_HOOKNAME=changegroup
154 154 HG_HOOKTYPE=changegroup
155 155 HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
156 156 HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d
157 157 HG_SOURCE=pull
158 158 HG_TXNID=TXN:$ID$
159 159 HG_TXNNAME=pull
160 160 http://localhost:$HGPORT1/
161 161 HG_URL=http://localhost:$HGPORT1/
162 162
163 163 (run 'hg update' to get a working copy)
164 164 $ cd ..
165 165
166 166 clone from invalid URL
167 167
168 168 $ hg clone http://localhost:$HGPORT/bad
169 169 abort: HTTP Error 404: Not Found
170 170 [255]
171 171
172 172 test http authentication
173 173 + use the same server to test server side streaming preference
174 174
175 175 $ cd test
176 176
177 177 $ hg serve --config extensions.x=$TESTDIR/httpserverauth.py -p $HGPORT2 -d \
178 178 > --pid-file=pid --config server.preferuncompressed=True -E ../errors2.log \
179 179 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
180 180 $ cat pid >> $DAEMON_PIDS
181 181
182 182 $ cat << EOF > get_pass.py
183 183 > import getpass
184 184 > def newgetpass(arg):
185 185 > return "pass"
186 186 > getpass.getpass = newgetpass
187 187 > EOF
188 188
189 189 $ hg id http://localhost:$HGPORT2/
190 190 abort: http authorization required for http://localhost:$HGPORT2/
191 191 [255]
192 192 $ hg id http://localhost:$HGPORT2/
193 193 abort: http authorization required for http://localhost:$HGPORT2/
194 194 [255]
195 $ hg id --config ui.interactive=true --debug http://localhost:$HGPORT2/
196 using http://localhost:$HGPORT2/
197 sending capabilities command
198 http authorization required for http://localhost:$HGPORT2/
199 realm: mercurial
200 user: abort: response expected
201 [255]
202 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
203 >
204 > EOF
205 using http://localhost:$HGPORT2/
206 sending capabilities command
207 http authorization required for http://localhost:$HGPORT2/
208 realm: mercurial
209 user:
210 password: abort: response expected
211 [255]
212 $ cat <<'EOF' | hg id --config ui.interactive=true --config ui.nontty=true --debug http://localhost:$HGPORT2/
213 >
214 >
215 > EOF
216 using http://localhost:$HGPORT2/
217 sending capabilities command
218 http authorization required for http://localhost:$HGPORT2/
219 realm: mercurial
220 user:
221 password: abort: authorization failed
222 [255]
195 223 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
196 224 http authorization required for http://localhost:$HGPORT2/
197 225 realm: mercurial
198 226 user: user
199 227 password: 5fed3813f7f5
200 228 $ hg id http://user:pass@localhost:$HGPORT2/
201 229 5fed3813f7f5
202 230 $ echo '[auth]' >> .hg/hgrc
203 231 $ echo 'l.schemes=http' >> .hg/hgrc
204 232 $ echo 'l.prefix=lo' >> .hg/hgrc
205 233 $ echo 'l.username=user' >> .hg/hgrc
206 234 $ echo 'l.password=pass' >> .hg/hgrc
207 235 $ hg id http://localhost:$HGPORT2/
208 236 5fed3813f7f5
209 237 $ hg id http://localhost:$HGPORT2/
210 238 5fed3813f7f5
211 239 $ hg id http://user@localhost:$HGPORT2/
212 240 5fed3813f7f5
213 241
214 242 $ cat > use_digests.py << EOF
215 243 > from mercurial import (
216 244 > exthelper,
217 245 > url,
218 246 > )
219 247 >
220 248 > eh = exthelper.exthelper()
221 249 > uisetup = eh.finaluisetup
222 250 >
223 251 > @eh.wrapfunction(url, 'opener')
224 252 > def urlopener(orig, *args, **kwargs):
225 253 > opener = orig(*args, **kwargs)
226 254 > opener.addheaders.append((r'X-HgTest-AuthType', r'Digest'))
227 255 > return opener
228 256 > EOF
229 257
230 258 $ hg id http://localhost:$HGPORT2/ --config extensions.x=use_digests.py
231 259 5fed3813f7f5
232 260
233 261 #if no-reposimplestore
234 262 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
235 263 streaming all changes
236 264 10 files to transfer, 1.01 KB of data
237 265 transferred * KB in * seconds (*/sec) (glob)
238 266 updating to branch default
239 267 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
240 268 #endif
241 269
242 270 --pull should override server's preferuncompressed
243 271 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
244 272 requesting all changes
245 273 adding changesets
246 274 adding manifests
247 275 adding file changes
248 276 added 2 changesets with 5 changes to 5 files
249 277 new changesets 8b6053c928fe:5fed3813f7f5
250 278 updating to branch default
251 279 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
252 280
253 281 $ hg id http://user2@localhost:$HGPORT2/
254 282 abort: http authorization required for http://localhost:$HGPORT2/
255 283 [255]
256 284 $ hg id http://user:pass2@localhost:$HGPORT2/
257 285 abort: HTTP Error 403: no
258 286 [255]
259 287
260 288 $ hg -R dest-pull tag -r tip top
261 289 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/
262 290 pushing to http://user:***@localhost:$HGPORT2/
263 291 searching for changes
264 292 remote: adding changesets
265 293 remote: adding manifests
266 294 remote: adding file changes
267 295 remote: added 1 changesets with 1 changes to 1 files
268 296 $ hg rollback -q
269 297 $ hg -R dest-pull push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
270 298 pushing to http://user:***@localhost:$HGPORT2/
271 299 using http://localhost:$HGPORT2/
272 300 http auth: user user, password ****
273 301 sending capabilities command
274 302 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
275 303 http auth: user user, password ****
276 304 devel-peer-request: finished in *.???? seconds (200) (glob)
277 305 query 1; heads
278 306 devel-peer-request: batched-content
279 307 devel-peer-request: - heads (0 arguments)
280 308 devel-peer-request: - known (1 arguments)
281 309 sending batch command
282 310 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
283 311 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
284 312 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
285 313 devel-peer-request: 68 bytes of commands arguments in headers
286 314 devel-peer-request: finished in *.???? seconds (200) (glob)
287 315 searching for changes
288 316 all remote heads known locally
289 317 preparing listkeys for "phases"
290 318 sending listkeys command
291 319 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
292 320 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
293 321 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
294 322 devel-peer-request: 16 bytes of commands arguments in headers
295 323 devel-peer-request: finished in *.???? seconds (200) (glob)
296 324 received listkey for "phases": 58 bytes
297 325 checking for updated bookmarks
298 326 preparing listkeys for "bookmarks"
299 327 sending listkeys command
300 328 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
301 329 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
302 330 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
303 331 devel-peer-request: 19 bytes of commands arguments in headers
304 332 devel-peer-request: finished in *.???? seconds (200) (glob)
305 333 received listkey for "bookmarks": 0 bytes
306 334 sending branchmap command
307 335 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
308 336 devel-peer-request: Vary X-HgProto-1
309 337 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
310 338 devel-peer-request: finished in *.???? seconds (200) (glob)
311 339 preparing listkeys for "bookmarks"
312 340 sending listkeys command
313 341 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
314 342 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
315 343 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
316 344 devel-peer-request: 19 bytes of commands arguments in headers
317 345 devel-peer-request: finished in *.???? seconds (200) (glob)
318 346 received listkey for "bookmarks": 0 bytes
319 347 1 changesets found
320 348 list of changesets:
321 349 7f4e523d01f2cc3765ac8934da3d14db775ff872
322 350 bundle2-output-bundle: "HG20", 5 parts total
323 351 bundle2-output-part: "replycaps" 224 bytes payload
324 352 bundle2-output-part: "check:phases" 24 bytes payload
325 353 bundle2-output-part: "check:updated-heads" streamed payload
326 354 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
327 355 bundle2-output-part: "phase-heads" 24 bytes payload
328 356 sending unbundle command
329 357 sending 1040 bytes
330 358 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
331 359 devel-peer-request: Content-length 1040
332 360 devel-peer-request: Content-type application/mercurial-0.1
333 361 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
334 362 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
335 363 devel-peer-request: 16 bytes of commands arguments in headers
336 364 devel-peer-request: 1040 bytes of data
337 365 devel-peer-request: finished in *.???? seconds (200) (glob)
338 366 bundle2-input-bundle: no-transaction
339 367 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
340 368 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
341 369 bundle2-input-part: total payload size 55
342 370 remote: adding changesets
343 371 remote: adding manifests
344 372 remote: adding file changes
345 373 bundle2-input-part: "output" (advisory) supported
346 374 bundle2-input-part: total payload size 45
347 375 remote: added 1 changesets with 1 changes to 1 files
348 376 bundle2-input-bundle: 3 parts total
349 377 preparing listkeys for "phases"
350 378 sending listkeys command
351 379 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
352 380 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
353 381 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
354 382 devel-peer-request: 16 bytes of commands arguments in headers
355 383 devel-peer-request: finished in *.???? seconds (200) (glob)
356 384 received listkey for "phases": 15 bytes
357 385 $ hg rollback -q
358 386
359 387 $ sed 's/.*] "/"/' < ../access.log
360 388 "GET /?cmd=capabilities HTTP/1.1" 401 -
361 389 "GET /?cmd=capabilities HTTP/1.1" 401 -
362 390 "GET /?cmd=capabilities HTTP/1.1" 401 -
391 "GET /?cmd=capabilities HTTP/1.1" 401 -
392 "GET /?cmd=capabilities HTTP/1.1" 401 -
393 "GET /?cmd=capabilities HTTP/1.1" 401 -
363 394 "GET /?cmd=capabilities HTTP/1.1" 200 -
364 395 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
365 396 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
366 397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
367 398 "GET /?cmd=capabilities HTTP/1.1" 401 -
368 399 "GET /?cmd=capabilities HTTP/1.1" 200 -
369 400 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
370 401 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
371 402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
372 403 "GET /?cmd=capabilities HTTP/1.1" 401 -
373 404 "GET /?cmd=capabilities HTTP/1.1" 200 -
374 405 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
375 406 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
376 407 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
377 408 "GET /?cmd=capabilities HTTP/1.1" 401 -
378 409 "GET /?cmd=capabilities HTTP/1.1" 200 -
379 410 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
380 411 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
381 412 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
382 413 "GET /?cmd=capabilities HTTP/1.1" 401 -
383 414 "GET /?cmd=capabilities HTTP/1.1" 200 -
384 415 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
385 416 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
386 417 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
387 418 "GET /?cmd=capabilities HTTP/1.1" 401 - x-hgtest-authtype:Digest
388 419 "GET /?cmd=capabilities HTTP/1.1" 200 - x-hgtest-authtype:Digest
389 420 "GET /?cmd=lookup HTTP/1.1" 401 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
390 421 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
391 422 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
392 423 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
393 424 "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
394 425 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull x-hgtest-authtype:Digest
395 426 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
396 427 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
397 428 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
398 429 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&stream=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull (no-reposimplestore !)
399 430 "GET /?cmd=capabilities HTTP/1.1" 401 - (no-reposimplestore !)
400 431 "GET /?cmd=capabilities HTTP/1.1" 200 - (no-reposimplestore !)
401 432 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
402 433 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
403 434 "GET /?cmd=capabilities HTTP/1.1" 401 -
404 435 "GET /?cmd=capabilities HTTP/1.1" 401 -
405 436 "GET /?cmd=capabilities HTTP/1.1" 403 -
406 437 "GET /?cmd=capabilities HTTP/1.1" 401 -
407 438 "GET /?cmd=capabilities HTTP/1.1" 200 -
408 439 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
409 440 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
410 441 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
411 442 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
412 443 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
413 444 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
414 445 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
415 446 "GET /?cmd=capabilities HTTP/1.1" 401 -
416 447 "GET /?cmd=capabilities HTTP/1.1" 200 -
417 448 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
418 449 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
419 450 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
420 451 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
421 452 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
422 453 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
423 454 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ partial-pull
424 455
425 456 $ cd ..
426 457
427 458 clone of serve with repo in root and unserved subrepo (issue2970)
428 459
429 460 $ hg --cwd test init sub
430 461 $ echo empty > test/sub/empty
431 462 $ hg --cwd test/sub add empty
432 463 $ hg --cwd test/sub commit -qm 'add empty'
433 464 $ hg --cwd test/sub tag -r 0 something
434 465 $ echo sub = sub > test/.hgsub
435 466 $ hg --cwd test add .hgsub
436 467 $ hg --cwd test commit -qm 'add subrepo'
437 468 $ hg clone http://localhost:$HGPORT noslash-clone
438 469 requesting all changes
439 470 adding changesets
440 471 adding manifests
441 472 adding file changes
442 473 added 3 changesets with 7 changes to 7 files
443 474 new changesets 8b6053c928fe:56f9bc90cce6
444 475 updating to branch default
445 476 cloning subrepo sub from http://localhost:$HGPORT/sub
446 477 abort: HTTP Error 404: Not Found
447 478 [255]
448 479 $ hg clone http://localhost:$HGPORT/ slash-clone
449 480 requesting all changes
450 481 adding changesets
451 482 adding manifests
452 483 adding file changes
453 484 added 3 changesets with 7 changes to 7 files
454 485 new changesets 8b6053c928fe:56f9bc90cce6
455 486 updating to branch default
456 487 cloning subrepo sub from http://localhost:$HGPORT/sub
457 488 abort: HTTP Error 404: Not Found
458 489 [255]
459 490
460 491 check error log
461 492
462 493 $ cat error.log
463 494
464 495 $ cat errors2.log
465 496
466 497 check abort error reporting while pulling/cloning
467 498
468 499 $ $RUNTESTDIR/killdaemons.py
469 500 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
470 501 $ cat hg3.pid >> $DAEMON_PIDS
471 502 $ hg clone http://localhost:$HGPORT/ abort-clone
472 503 requesting all changes
473 504 remote: abort: this is an exercise
474 505 abort: pull failed on remote
475 506 [255]
476 507 $ cat error.log
477 508
478 509 disable pull-based clones
479 510
480 511 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
481 512 $ cat hg4.pid >> $DAEMON_PIDS
482 513 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
483 514 requesting all changes
484 515 remote: abort: server has pull-based clones disabled
485 516 abort: pull failed on remote
486 517 (remove --pull if specified or upgrade Mercurial)
487 518 [255]
488 519
489 520 #if no-reposimplestore
490 521 ... but keep stream clones working
491 522
492 523 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
493 524 streaming all changes
494 525 * files to transfer, * of data (glob)
495 526 transferred * in * seconds (*/sec) (glob)
496 527 $ cat error.log
497 528 #endif
498 529
499 530 ... and also keep partial clones and pulls working
500 531 $ hg clone http://localhost:$HGPORT1 --rev 0 test/partial/clone
501 532 adding changesets
502 533 adding manifests
503 534 adding file changes
504 535 added 1 changesets with 4 changes to 4 files
505 536 new changesets 8b6053c928fe
506 537 updating to branch default
507 538 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
508 539 $ hg pull -R test/partial/clone
509 540 pulling from http://localhost:$HGPORT1/
510 541 searching for changes
511 542 adding changesets
512 543 adding manifests
513 544 adding file changes
514 545 added 2 changesets with 3 changes to 3 files
515 546 new changesets 5fed3813f7f5:56f9bc90cce6
516 547 (run 'hg update' to get a working copy)
517 548
518 549 $ hg clone -U -r 0 test/partial/clone test/another/clone
519 550 adding changesets
520 551 adding manifests
521 552 adding file changes
522 553 added 1 changesets with 4 changes to 4 files
523 554 new changesets 8b6053c928fe
524 555
525 556 corrupt cookies file should yield a warning
526 557
527 558 $ cat > $TESTTMP/cookies.txt << EOF
528 559 > bad format
529 560 > EOF
530 561
531 562 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
532 563 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
533 564 56f9bc90cce6
534 565
535 566 $ killdaemons.py
536 567
537 568 Create dummy authentication handler that looks for cookies. It doesn't do anything
538 569 useful. It just raises an HTTP 500 with details about the Cookie request header.
539 570 We raise HTTP 500 because its message is printed in the abort message.
540 571
541 572 $ cat > cookieauth.py << EOF
542 573 > from mercurial import util
543 574 > from mercurial.hgweb import common
544 575 > def perform_authentication(hgweb, req, op):
545 576 > cookie = req.headers.get(b'Cookie')
546 577 > if not cookie:
547 578 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'no-cookie')
548 579 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, b'Cookie: %s' % cookie)
549 580 > def extsetup(ui):
550 581 > common.permhooks.insert(0, perform_authentication)
551 582 > EOF
552 583
553 584 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
554 585 $ cat pid > $DAEMON_PIDS
555 586
556 587 Request without cookie sent should fail due to lack of cookie
557 588
558 589 $ hg id http://localhost:$HGPORT
559 590 abort: HTTP Error 500: no-cookie
560 591 [255]
561 592
562 593 Populate a cookies file
563 594
564 595 $ cat > cookies.txt << EOF
565 596 > # HTTP Cookie File
566 597 > # Expiration is 2030-01-01 at midnight
567 598 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
568 599 > EOF
569 600
570 601 Should not send a cookie for another domain
571 602
572 603 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
573 604 abort: HTTP Error 500: no-cookie
574 605 [255]
575 606
576 607 Add a cookie entry for our test server and verify it is sent
577 608
578 609 $ cat >> cookies.txt << EOF
579 610 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
580 611 > EOF
581 612
582 613 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
583 614 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
584 615 [255]
General Comments 0
You need to be logged in to leave comments. Login now