37
37
from bzrlib.smart import medium
38
from bzrlib.symbol_versioning import (
41
38
from bzrlib.trace import mutter
42
39
from bzrlib.transport import (
43
40
ConnectedTransport,
48
# TODO: This is not used anymore by HttpTransport_urllib
49
# (extracting the auth info and prompting the user for a password
50
# have been split), only the tests still use it. It should be
51
# deleted and the tests rewritten ASAP to stay in sync.
52
def extract_auth(url, password_manager):
53
"""Extract auth parameters from am HTTP/HTTPS url and add them to the given
54
password manager. Return the url, minus those auth parameters (which
57
if not re.match(r'^(https?)(\+\w+)?://', url):
59
'invalid absolute url %r' % (url,))
60
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
63
auth, netloc = netloc.split('@', 1)
65
username, password = auth.split(':', 1)
67
username, password = auth, None
69
host = netloc.split(':', 1)[0]
72
username = urllib.unquote(username)
73
if password is not None:
74
password = urllib.unquote(password)
76
password = ui.ui_factory.get_password(
77
prompt=u'HTTP %(user)s@%(host)s password',
78
user=username, host=host)
79
password_manager.add_password(None, host, username, password)
80
url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
84
44
class HttpTransportBase(ConnectedTransport):
85
45
"""Base class for http implementations.
124
84
:param relpath: The relative path to the file
126
86
code, response_file = self._get(relpath, None)
127
# FIXME: some callers want an iterable... One step forward, three steps
128
# backwards :-/ And not only an iterable, but an iterable that can be
129
# seeked backwards, so we will never be able to do that. One such
130
# known client is bzrlib.bundle.serializer.v4.get_bundle_reader. At the
131
# time of this writing it's even the only known client -- vila20071203
132
return StringIO(response_file.read())
134
89
def _get(self, relpath, ranges, tail_amount=0):
135
90
"""Get a file, or part of a file.
518
473
:returns: A transport or None.
520
def relpath(abspath):
521
"""Returns the path relative to our base.
523
The constraints are weaker than the real relpath method because the
524
abspath is coming from the server and may slightly differ from our
525
base. We don't check the scheme, host, port, user, password parts,
526
relying on the caller to give us a proper url (i.e. one returned by
527
the server mirroring the one we sent).
529
parsed_url = self._split_url(abspath)
530
pl = len(self._parsed_url.path)
531
return parsed_url.path[pl:].strip('/')
533
relpath = relpath(source)
534
if not target.endswith(relpath):
475
parsed_source = self._split_url(source)
476
parsed_target = self._split_url(target)
477
pl = len(self._parsed_url.path)
478
# determine the excess tail - the relative path that was in
479
# the original request but not part of this transports' URL.
480
excess_tail = parsed_source.path[pl:].strip("/")
481
if not target.endswith(excess_tail):
535
482
# The final part of the url has been renamed, we can't handle the
539
parsed_url = self._split_url(target)
540
# Recalculate base path. This is needed to ensure that when the
541
# redirected transport will be used to re-try whatever request was
542
# redirected, we end up with the same url
543
base_path = parsed_url.path[:-len(relpath)]
544
if parsed_url.scheme in ('http', 'https'):
486
target_path = parsed_target.path
488
# Drop the tail that was in the redirect but not part of
489
# the path of this transport.
490
target_path = target_path[:-len(excess_tail)]
492
if parsed_target.scheme in ('http', 'https'):
545
493
# Same protocol family (i.e. http[s]), we will preserve the same
546
494
# http client implementation when a redirection occurs from one to
547
495
# the other (otherwise users may be surprised that bzr switches
548
496
# from one implementation to the other, and devs may suffer
550
if (parsed_url.scheme == self._unqualified_scheme
551
and parsed_url.host == self._parsed_url.host
552
and parsed_url.port == self._parsed_url.port
553
and (parsed_url.user is None or
554
parsed_url.user == self._parsed_url.user)):
498
if (parsed_target.scheme == self._unqualified_scheme
499
and parsed_target.host == self._parsed_url.host
500
and parsed_target.port == self._parsed_url.port
501
and (parsed_target.user is None or
502
parsed_target.user == self._parsed_url.user)):
555
503
# If a user is specified, it should match, we don't care about
556
504
# passwords, wrong passwords will be rejected anyway.
557
new_transport = self.clone(base_path)
505
return self.clone(target_path)
559
507
# Rebuild the url preserving the scheme qualification and the
560
508
# credentials (if they don't apply, the redirected to server
561
509
# will tell us, but if they do apply, we avoid prompting the
563
redir_scheme = parsed_url.scheme + '+' + self._impl_name
511
redir_scheme = parsed_target.scheme + '+' + self._impl_name
564
512
new_url = self._unsplit_url(redir_scheme,
565
self._parsed_url.user,
566
self._parsed_url.password,
567
parsed_url.host, parsed_url.port,
569
new_transport = transport.get_transport(new_url)
513
self._parsed_url.user,
514
self._parsed_url.password,
515
parsed_target.host, parsed_target.port,
517
return transport.get_transport_from_url(new_url)
571
519
# Redirected to a different protocol
572
new_url = self._unsplit_url(parsed_url.scheme,
573
parsed_url.user, parsed_url.password,
574
parsed_url.host, parsed_url.port,
576
new_transport = transport.get_transport(new_url)
520
new_url = self._unsplit_url(parsed_target.scheme,
522
parsed_target.password,
523
parsed_target.host, parsed_target.port,
525
return transport.get_transport_from_url(new_url)
580
528
# TODO: May be better located in smart/medium.py with the other