~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/__init__.py

  • Committer: Patch Queue Manager
  • Date: 2016-01-31 13:36:59 UTC
  • mfrom: (6613.1.5 1538480-match-hostname)
  • Revision ID: pqm@pqm.ubuntu.com-20160131133659-ouy92ee2wlv9xz8m
(vila) Use ssl.match_hostname instead of our own. (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
There are separate implementation modules for each http client implementation.
20
20
"""
21
21
 
22
 
from cStringIO import StringIO
23
 
import mimetools
 
22
from __future__ import absolute_import
 
23
 
 
24
import os
24
25
import re
25
26
import urlparse
26
 
import urllib
27
27
import sys
28
28
import weakref
29
29
 
30
30
from bzrlib import (
31
31
    debug,
32
32
    errors,
 
33
    transport,
33
34
    ui,
34
35
    urlutils,
35
36
    )
36
37
from bzrlib.smart import medium
37
 
from bzrlib.symbol_versioning import (
38
 
        deprecated_method,
39
 
        )
40
38
from bzrlib.trace import mutter
41
39
from bzrlib.transport import (
42
40
    ConnectedTransport,
43
 
    _CoalescedOffset,
44
 
    get_transport,
45
 
    Transport,
46
41
    )
47
42
 
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
55
 
    confuse urllib2).
56
 
    """
57
 
    if not re.match(r'^(https?)(\+\w+)?://', url):
58
 
        raise ValueError(
59
 
            'invalid absolute url %r' % (url,))
60
 
    scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
61
 
 
62
 
    if '@' in netloc:
63
 
        auth, netloc = netloc.split('@', 1)
64
 
        if ':' in auth:
65
 
            username, password = auth.split(':', 1)
66
 
        else:
67
 
            username, password = auth, None
68
 
        if ':' in netloc:
69
 
            host = netloc.split(':', 1)[0]
70
 
        else:
71
 
            host = netloc
72
 
        username = urllib.unquote(username)
73
 
        if password is not None:
74
 
            password = urllib.unquote(password)
75
 
        else:
76
 
            password = ui.ui_factory.get_password(
77
 
                prompt='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))
81
 
    return url
82
 
 
83
43
 
84
44
class HttpTransportBase(ConnectedTransport):
85
45
    """Base class for http implementations.
124
84
        :param relpath: The relative path to the file
125
85
        """
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())
 
87
        return response_file
133
88
 
134
89
    def _get(self, relpath, ranges, tail_amount=0):
135
90
        """Get a file, or part of a file.
148
103
 
149
104
        user and passwords are not embedded in the path provided to the server.
150
105
        """
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
 
110
        return str(url)
155
111
 
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,
161
 
                    path=self._path)
 
117
                    path=self._parsed_url.path)
162
118
        return auth
163
119
 
164
120
    def get_smart_medium(self):
246
202
                    # Split the received chunk
247
203
                    for offset, size in cur_coal.ranges:
248
204
                        start = cur_coal.start + offset
249
 
                        rfile.seek(start, 0)
 
205
                        rfile.seek(start, os.SEEK_SET)
250
206
                        data = rfile.read(size)
251
207
                        data_len = len(data)
252
208
                        if data_len != size:
271
227
                        cur_offset_and_size = iter_offsets.next()
272
228
 
273
229
            except (errors.ShortReadvError, errors.InvalidRange,
274
 
                    errors.InvalidHttpRange), e:
 
230
                    errors.InvalidHttpRange, errors.HttpBoundaryMissing), e:
275
231
                mutter('Exception %r: %s during http._readv',e, e)
276
232
                if (not isinstance(e, errors.ShortReadvError)
277
233
                    or retried_offset == cur_offset_and_size):
412
368
        """See bzrlib.transport.Transport.external_url."""
413
369
        # HTTP URL's are externally usable as long as they don't mention their
414
370
        # implementation qualifier
415
 
        return self._unsplit_url(self._unqualified_scheme,
416
 
                                 self._user, self._password,
417
 
                                 self._host, self._port,
418
 
                                 self._path)
 
371
        url = self._parsed_url.clone()
 
372
        url.scheme = self._unqualified_scheme
 
373
        return str(url)
419
374
 
420
375
    def is_readonly(self):
421
376
        """See Transport.is_readonly."""
517
472
 
518
473
        :returns: A transport or None.
519
474
        """
520
 
        def relpath(abspath):
521
 
            """Returns the path relative to our base.
522
 
 
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).
528
 
            """
529
 
            (scheme,
530
 
             user, password,
531
 
             host, port,
532
 
             path) = self._split_url(abspath)
533
 
            pl = len(self._path)
534
 
            return path[pl:].strip('/')
535
 
 
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
539
483
            # redirection.
540
484
            return None
541
 
        new_transport = None
542
 
        (scheme,
543
 
         user, password,
544
 
         host, port,
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'):
 
485
 
 
486
        target_path = parsed_target.path
 
487
        if excess_tail:
 
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)]
 
491
 
 
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
555
497
            # debugging it).
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)
563
506
            else:
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
567
510
                # user)
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,
571
 
                                            host, port,
572
 
                                            base_path)
573
 
                new_transport = get_transport(new_url)
 
513
                    self._parsed_url.user,
 
514
                    self._parsed_url.password,
 
515
                    parsed_target.host, parsed_target.port,
 
516
                    target_path)
 
517
                return transport.get_transport_from_url(new_url)
574
518
        else:
575
519
            # Redirected to a different protocol
576
 
            new_url = self._unsplit_url(scheme,
577
 
                                        user, password,
578
 
                                        host, port,
579
 
                                        base_path)
580
 
            new_transport = get_transport(new_url)
581
 
        return new_transport
 
520
            new_url = self._unsplit_url(parsed_target.scheme,
 
521
                    parsed_target.user,
 
522
                    parsed_target.password,
 
523
                    parsed_target.host, parsed_target.port,
 
524
                    target_path)
 
525
            return transport.get_transport_from_url(new_url)
582
526
 
583
527
 
584
528
# TODO: May be better located in smart/medium.py with the other
606
550
        if transport_base.startswith('bzr+'):
607
551
            transport_base = transport_base[4:]
608
552
        rel_url = urlutils.relative_url(self.base, transport_base)
609
 
        return urllib.unquote(rel_url)
 
553
        return urlutils.unquote(rel_url)
610
554
 
611
555
    def send_http_smart_request(self, bytes):
612
556
        try:
614
558
            t = self._http_transport_ref()
615
559
            code, body_filelike = t._post(bytes)
616
560
            if code != 200:
617
 
                raise InvalidHttpResponse(
 
561
                raise errors.InvalidHttpResponse(
618
562
                    t._remote_path('.bzr/smart'),
619
563
                    'Expected 200 response code, got %r' % (code,))
620
564
        except (errors.InvalidHttpResponse, errors.ConnectionReset), e:
666
610
    def _finished_reading(self):
667
611
        """See SmartClientMediumRequest._finished_reading."""
668
612
        pass
 
613
 
 
614
 
 
615
def unhtml_roughly(maybe_html, length_limit=1000):
 
616
    """Very approximate html->text translation, for presenting error bodies.
 
617
 
 
618
    :param length_limit: Truncate the result to this many characters.
 
619
 
 
620
    >>> unhtml_roughly("<b>bad</b> things happened\\n")
 
621
    ' bad  things happened '
 
622
    """
 
623
    return re.subn(r"(<[^>]*>|\n|&nbsp;)", " ", maybe_html)[0][:length_limit]