13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""Base implementation of Transport over http.
92
93
# _unqualified_scheme: "http" or "https"
93
94
# _scheme: may have "+pycurl", etc
95
def __init__(self, base, _from_transport=None):
96
def __init__(self, base, _impl_name, _from_transport=None):
96
97
"""Set the base path where files will be stored."""
97
98
proto_match = re.match(r'^(https?)(\+\w+)?://', base)
98
99
if not proto_match:
99
100
raise AssertionError("not a http url: %r" % base)
100
101
self._unqualified_scheme = proto_match.group(1)
101
impl_name = proto_match.group(2)
103
impl_name = impl_name[1:]
104
self._impl_name = impl_name
102
self._impl_name = _impl_name
105
103
super(HttpTransportBase, self).__init__(base,
106
104
_from_transport=_from_transport)
107
105
self._medium = None
156
154
None, None, self._host, self._port, path)
158
156
def _create_auth(self):
159
"""Returns a dict returning the credentials provided at build time."""
157
"""Returns a dict containing the credentials provided at build time."""
160
158
auth = dict(host=self._host, port=self._port,
161
159
user=self._user, password=self._password,
162
160
protocol=self._unqualified_scheme,
415
411
def external_url(self):
416
412
"""See bzrlib.transport.Transport.external_url."""
417
# HTTP URL's are externally usable.
413
# HTTP URL's are externally usable as long as they don't mention their
414
# implementation qualifier
415
return self._unsplit_url(self._unqualified_scheme,
416
self._user, self._password,
417
self._host, self._port,
420
420
def is_readonly(self):
421
421
"""See Transport.is_readonly."""
452
452
raise errors.TransportNotPossible('http does not support lock_write()')
454
def clone(self, offset=None):
455
"""Return a new HttpTransportBase with root at self.base + offset
457
We leave the daughter classes take advantage of the hint
458
that it's a cloning not a raw creation.
461
return self.__class__(self.base, self)
463
return self.__class__(self.abspath(offset), self)
465
454
def _attempted_range_header(self, offsets, tail_amount):
466
455
"""Prepare a HTTP Range header at a level the server should accept.
518
507
return ','.join(strings)
509
def _redirected_to(self, source, target):
510
"""Returns a transport suitable to re-issue a redirected request.
512
:param source: The source url as returned by the server.
513
:param target: The target url as returned by the server.
515
The redirection can be handled only if the relpath involved is not
516
renamed by the redirection.
518
: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):
538
# 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'):
551
# Same protocol family (i.e. http[s]), we will preserve the same
552
# http client implementation when a redirection occurs from one to
553
# the other (otherwise users may be surprised that bzr switches
554
# 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)):
560
# If a user is specified, it should match, we don't care about
561
# passwords, wrong passwords will be rejected anyway.
562
new_transport = self.clone(base_path)
564
# Rebuild the url preserving the scheme qualification and the
565
# credentials (if they don't apply, the redirected to server
566
# will tell us, but if they do apply, we avoid prompting the
568
redir_scheme = scheme + '+' + self._impl_name
569
new_url = self._unsplit_url(redir_scheme,
570
self._user, self._password,
573
new_transport = get_transport(new_url)
575
# Redirected to a different protocol
576
new_url = self._unsplit_url(scheme,
580
new_transport = get_transport(new_url)
521
584
# TODO: May be better located in smart/medium.py with the other
522
585
# SmartMedium classes
554
617
raise InvalidHttpResponse(
555
618
t._remote_path('.bzr/smart'),
556
619
'Expected 200 response code, got %r' % (code,))
557
except errors.InvalidHttpResponse, e:
620
except (errors.InvalidHttpResponse, errors.ConnectionReset), e:
558
621
raise errors.SmartProtocolError(str(e))
559
622
return body_filelike
624
def _report_activity(self, bytes, direction):
625
"""See SmartMedium._report_activity.
627
Does nothing; the underlying plain HTTP transport will report the
628
activity that this medium would report.
562
633
# TODO: May be better located in smart/medium.py with the other
563
634
# SmartMediumRequest classes