~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/urlutils.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-08-17 18:13:57 UTC
  • mfrom: (5268.7.29 transport-segments)
  • Revision ID: pqm@pqm.ubuntu.com-20110817181357-y5q5eth1hk8bl3om
(jelmer) Allow specifying the colocated branch to use in the branch URL,
 and retrieving the branch name using ControlDir._get_selected_branch.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""A collection of function for handling URL operations."""
18
18
 
19
 
from __future__ import absolute_import
20
 
 
21
19
import os
22
20
import re
23
21
import sys
24
22
 
25
23
from bzrlib.lazy_import import lazy_import
26
24
lazy_import(globals(), """
27
 
from posixpath import split as _posix_split
 
25
from posixpath import split as _posix_split, normpath as _posix_normpath
 
26
import urllib
28
27
import urlparse
29
28
 
30
29
from bzrlib import (
61
60
    return split(url, exclude_trailing_slash=exclude_trailing_slash)[0]
62
61
 
63
62
 
64
 
# Private copies of quote and unquote, copied from Python's
65
 
# urllib module because urllib unconditionally imports socket, which imports
66
 
# ssl.
67
 
 
68
 
always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
69
 
               'abcdefghijklmnopqrstuvwxyz'
70
 
               '0123456789' '_.-')
71
 
_safe_map = {}
72
 
for i, c in zip(xrange(256), str(bytearray(xrange(256)))):
73
 
    _safe_map[c] = c if (i < 128 and c in always_safe) else '%{0:02X}'.format(i)
74
 
_safe_quoters = {}
75
 
 
76
 
 
77
 
def quote(s, safe='/'):
78
 
    """quote('abc def') -> 'abc%20def'
79
 
 
80
 
    Each part of a URL, e.g. the path info, the query, etc., has a
81
 
    different set of reserved characters that must be quoted.
82
 
 
83
 
    RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists
84
 
    the following reserved characters.
85
 
 
86
 
    reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
87
 
                  "$" | ","
88
 
 
89
 
    Each of these characters is reserved in some component of a URL,
90
 
    but not necessarily in all of them.
91
 
 
92
 
    By default, the quote function is intended for quoting the path
93
 
    section of a URL.  Thus, it will not encode '/'.  This character
94
 
    is reserved, but in typical usage the quote function is being
95
 
    called on a path where the existing slash characters are used as
96
 
    reserved characters.
97
 
    """
98
 
    # fastpath
99
 
    if not s:
100
 
        if s is None:
101
 
            raise TypeError('None object cannot be quoted')
102
 
        return s
103
 
    cachekey = (safe, always_safe)
104
 
    try:
105
 
        (quoter, safe) = _safe_quoters[cachekey]
106
 
    except KeyError:
107
 
        safe_map = _safe_map.copy()
108
 
        safe_map.update([(c, c) for c in safe])
109
 
        quoter = safe_map.__getitem__
110
 
        safe = always_safe + safe
111
 
        _safe_quoters[cachekey] = (quoter, safe)
112
 
    if not s.rstrip(safe):
113
 
        return s
114
 
    return ''.join(map(quoter, s))
115
 
 
116
 
 
117
 
_hexdig = '0123456789ABCDEFabcdef'
118
 
_hextochr = dict((a + b, chr(int(a + b, 16)))
119
 
                 for a in _hexdig for b in _hexdig)
120
 
 
121
 
def unquote(s):
122
 
    """unquote('abc%20def') -> 'abc def'."""
123
 
    res = s.split('%')
124
 
    # fastpath
125
 
    if len(res) == 1:
126
 
        return s
127
 
    s = res[0]
128
 
    for item in res[1:]:
129
 
        try:
130
 
            s += _hextochr[item[:2]] + item[2:]
131
 
        except KeyError:
132
 
            s += '%' + item
133
 
        except UnicodeDecodeError:
134
 
            s += unichr(int(item[:2], 16)) + item[2:]
135
 
    return s
136
 
 
137
 
 
138
63
def escape(relpath):
139
64
    """Escape relpath to be a valid url."""
140
65
    if isinstance(relpath, unicode):
141
66
        relpath = relpath.encode('utf-8')
142
67
    # After quoting and encoding, the path should be perfectly
143
68
    # safe as a plain ASCII string, str() just enforces this
144
 
    return str(quote(relpath, safe='/~'))
 
69
    return str(urllib.quote(relpath, safe='/~'))
145
70
 
146
71
 
147
72
def file_relpath(base, path):
153
78
        raise ValueError('Length of base (%r) must equal or'
154
79
            ' exceed the platform minimum url length (which is %d)' %
155
80
            (base, MIN_ABS_FILEURL_LENGTH))
156
 
    base = osutils.normpath(local_path_from_url(base))
157
 
    path = osutils.normpath(local_path_from_url(path))
 
81
    base = local_path_from_url(base)
 
82
    path = local_path_from_url(path)
158
83
    return escape(osutils.relpath(base, path))
159
84
 
160
85
 
276
201
    """
277
202
    # importing directly from posixpath allows us to test this
278
203
    # on non-posix platforms
279
 
    return 'file://' + escape(osutils._posix_abspath(path))
 
204
    return 'file://' + escape(_posix_normpath(
 
205
        osutils._posix_abspath(path)))
280
206
 
281
207
 
282
208
def _win32_local_path_from_url(url):
462
388
    """On win32 the drive letter needs to be added to the url base."""
463
389
    # Strip off the drive letter
464
390
    # path is currently /C:/foo
465
 
    if len(path) < 4 or path[2] not in ':|' or path[3] != '/':
 
391
    if len(path) < 3 or path[2] not in ':|' or path[3] != '/':
466
392
        raise errors.InvalidURL(url_base + path,
467
393
            'win32 file:/// paths need a drive letter')
468
394
    url_base += path[0:3] # file:// + /C:
516
442
    :param url: A relative or absolute URL
517
443
    :return: (url, subsegments)
518
444
    """
519
 
    # GZ 2011-11-18: Dodgy removing the terminal slash like this, function
520
 
    #                operates on urls not url+segments, and Transport classes
521
 
    #                should not be blindly adding slashes in the first place. 
522
 
    lurl = strip_trailing_slash(url)
523
 
    # Segments begin at first comma after last forward slash, if one exists
524
 
    segment_start = lurl.find(",", lurl.rfind("/")+1)
525
 
    if segment_start == -1:
 
445
    (parent_url, child_dir) = split(url)
 
446
    subsegments = child_dir.split(",")
 
447
    if len(subsegments) == 1:
526
448
        return (url, [])
527
 
    return (lurl[:segment_start], lurl[segment_start+1:].split(","))
 
449
    return (join(parent_url, subsegments[0]), subsegments[1:])
528
450
 
529
451
 
530
452
def split_segment_parameters(url):
641
563
    This returns a Unicode path from a URL
642
564
    """
643
565
    # jam 20060427 URLs are supposed to be ASCII only strings
644
 
    #       If they are passed in as unicode, unquote
 
566
    #       If they are passed in as unicode, urllib.unquote
645
567
    #       will return a UNICODE string, which actually contains
646
568
    #       utf-8 bytes. So we have to ensure that they are
647
569
    #       plain ASCII strings, or the final .decode will
652
574
    except UnicodeError, e:
653
575
        raise errors.InvalidURL(url, 'URL was not a plain ASCII url: %s' % (e,))
654
576
 
655
 
    unquoted = unquote(url)
 
577
    unquoted = urllib.unquote(url)
656
578
    try:
657
579
        unicode_path = unquoted.decode('utf-8')
658
580
    except UnicodeError, e:
817
739
            port, quoted_path):
818
740
        self.scheme = scheme
819
741
        self.quoted_host = quoted_host
820
 
        self.host = unquote(self.quoted_host)
 
742
        self.host = urllib.unquote(self.quoted_host)
821
743
        self.quoted_user = quoted_user
822
744
        if self.quoted_user is not None:
823
 
            self.user = unquote(self.quoted_user)
 
745
            self.user = urllib.unquote(self.quoted_user)
824
746
        else:
825
747
            self.user = None
826
748
        self.quoted_password = quoted_password
827
749
        if self.quoted_password is not None:
828
 
            self.password = unquote(self.quoted_password)
 
750
            self.password = urllib.unquote(self.quoted_password)
829
751
        else:
830
752
            self.password = None
831
753
        self.port = port
832
 
        self.quoted_path = _url_hex_escapes_re.sub(_unescape_safe_chars, quoted_path)
833
 
        self.path = unquote(self.quoted_path)
 
754
        self.quoted_path = quoted_path
 
755
        self.path = urllib.unquote(self.quoted_path)
834
756
 
835
757
    def __eq__(self, other):
836
758
        return (isinstance(other, self.__class__) and
914
836
        """
915
837
        if not isinstance(relpath, str):
916
838
            raise errors.InvalidURL(relpath)
917
 
        relpath = _url_hex_escapes_re.sub(_unescape_safe_chars, relpath)
918
839
        if relpath.startswith('/'):
919
840
            base_parts = []
920
841
        else:
946
867
        if offset is not None:
947
868
            relative = unescape(offset).encode('utf-8')
948
869
            path = self._combine_paths(self.path, relative)
949
 
            path = quote(path, safe="/~")
 
870
            path = urllib.quote(path)
950
871
        else:
951
872
            path = self.quoted_path
952
873
        return self.__class__(self.scheme, self.quoted_user,