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.
149
104
user and passwords are not embedded in the path provided to the server.
151
relative = urlutils.unescape(relpath).encode('utf-8')
152
path = self._combine_paths(self._path, relative)
153
return self._unsplit_url(self._unqualified_scheme,
154
None, None, self._host, self._port, path)
106
url = self._parsed_url.clone(relpath)
107
url.user = url.quoted_user = None
108
url.password = url.quoted_password = None
109
url.scheme = self._unqualified_scheme
156
112
def _create_auth(self):
157
113
"""Returns a dict containing the credentials provided at build time."""
158
auth = dict(host=self._host, port=self._port,
159
user=self._user, password=self._password,
114
auth = dict(host=self._parsed_url.host, port=self._parsed_url.port,
115
user=self._parsed_url.user, password=self._parsed_url.password,
160
116
protocol=self._unqualified_scheme,
117
path=self._parsed_url.path)
164
120
def get_smart_medium(self):
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).
532
path) = self._split_url(abspath)
534
return path[pl:].strip('/')
536
relpath = relpath(source)
537
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):
538
482
# The final part of the url has been renamed, we can't handle the
545
path) = self._split_url(target)
546
# Recalculate base path. This is needed to ensure that when the
547
# redirected tranport will be used to re-try whatever request was
548
# redirected, we end up with the same url
549
base_path = path[:-len(relpath)]
550
if 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'):
551
493
# Same protocol family (i.e. http[s]), we will preserve the same
552
494
# http client implementation when a redirection occurs from one to
553
495
# the other (otherwise users may be surprised that bzr switches
554
496
# from one implementation to the other, and devs may suffer
556
if (scheme == self._unqualified_scheme
557
and host == self._host
558
and port == self._port
559
and (user is None or user == self._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)):
560
503
# If a user is specified, it should match, we don't care about
561
504
# passwords, wrong passwords will be rejected anyway.
562
new_transport = self.clone(base_path)
505
return self.clone(target_path)
564
507
# Rebuild the url preserving the scheme qualification and the
565
508
# credentials (if they don't apply, the redirected to server
566
509
# will tell us, but if they do apply, we avoid prompting the
568
redir_scheme = scheme + '+' + self._impl_name
511
redir_scheme = parsed_target.scheme + '+' + self._impl_name
569
512
new_url = self._unsplit_url(redir_scheme,
570
self._user, self._password,
573
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)
575
519
# Redirected to a different protocol
576
new_url = self._unsplit_url(scheme,
580
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)
584
528
# TODO: May be better located in smart/medium.py with the other