/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/urlutils.py

  • Committer: Jelmer Vernooij
  • Date: 2019-03-05 07:32:38 UTC
  • mto: (7290.1.21 work)
  • mto: This revision was merged to the branch mainline in revision 7311.
  • Revision ID: jelmer@jelmer.uk-20190305073238-zlqn981opwnqsmzi
Add appveyor configuration.

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
 
19
21
import os
20
22
import re
21
23
import sys
22
24
 
23
 
from urllib import parse as urlparse
 
25
try:
 
26
    import urlparse
 
27
except ImportError:
 
28
    from urllib import parse as urlparse
24
29
 
25
30
from . import (
26
31
    errors,
32
37
from posixpath import split as _posix_split
33
38
""")
34
39
 
 
40
from .sixish import (
 
41
    int2byte,
 
42
    PY3,
 
43
    text_type,
 
44
    unichr,
 
45
    )
35
46
 
36
47
 
37
48
class InvalidURL(errors.PathError):
88
99
    return split(url, exclude_trailing_slash=exclude_trailing_slash)[0]
89
100
 
90
101
 
91
 
quote_from_bytes = urlparse.quote_from_bytes
92
 
quote = urlparse.quote
93
 
unquote_to_bytes = urlparse.unquote_to_bytes
 
102
if PY3:
 
103
    quote_from_bytes = urlparse.quote_from_bytes
 
104
    quote = urlparse.quote
 
105
    unquote_to_bytes = urlparse.unquote_to_bytes
 
106
else:
 
107
    # Private copies of quote and unquote, copied from Python's urllib module
 
108
    # because urllib unconditionally imports socket, which imports ssl.
 
109
 
 
110
    always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
 
111
                   'abcdefghijklmnopqrstuvwxyz'
 
112
                   '0123456789' '_.-')
 
113
    _safe_map = {}
 
114
    for i, c in zip(range(256), ''.join(map(chr, range(256)))):
 
115
        _safe_map[c] = c if (
 
116
            i < 128 and c in always_safe) else '%{0:02X}'.format(i)
 
117
    _safe_quoters = {}
 
118
 
 
119
    def quote_from_bytes(s, safe='/'):
 
120
        """quote('abc def') -> 'abc%20def'
 
121
 
 
122
        Each part of a URL, e.g. the path info, the query, etc., has a
 
123
        different set of reserved characters that must be quoted.
 
124
 
 
125
        RFC 2396 Uniform Resource Identifiers (URI): Generic Syntax lists
 
126
        the following reserved characters.
 
127
 
 
128
        reserved    = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
 
129
                      "$" | ","
 
130
 
 
131
        Each of these characters is reserved in some component of a URL,
 
132
        but not necessarily in all of them.
 
133
 
 
134
        By default, the quote function is intended for quoting the path
 
135
        section of a URL.  Thus, it will not encode '/'.  This character
 
136
        is reserved, but in typical usage the quote function is being
 
137
        called on a path where the existing slash characters are used as
 
138
        reserved characters.
 
139
        """
 
140
        # fastpath
 
141
        if not s:
 
142
            if s is None:
 
143
                raise TypeError('None object cannot be quoted')
 
144
            return s
 
145
        cachekey = (safe, always_safe)
 
146
        try:
 
147
            (quoter, safe) = _safe_quoters[cachekey]
 
148
        except KeyError:
 
149
            safe_map = _safe_map.copy()
 
150
            safe_map.update([(c, c) for c in safe])
 
151
            quoter = safe_map.__getitem__
 
152
            safe = always_safe + safe
 
153
            _safe_quoters[cachekey] = (quoter, safe)
 
154
        if not s.rstrip(safe):
 
155
            return s
 
156
        return ''.join(map(quoter, s))
 
157
 
 
158
    quote = quote_from_bytes
 
159
    unquote_to_bytes = urlparse.unquote
 
160
 
 
161
 
94
162
unquote = urlparse.unquote
95
163
 
96
164
 
97
 
def escape(relpath, safe='/~'):
 
165
def escape(relpath):
98
166
    """Escape relpath to be a valid url."""
99
 
    return quote(relpath, safe=safe)
 
167
    if not isinstance(relpath, str) and sys.version_info[0] == 2:
 
168
        relpath = relpath.encode('utf-8')
 
169
    return quote(relpath, safe='/~')
100
170
 
101
171
 
102
172
def file_relpath(base, path):
211
281
# jam 20060502 Sorted to 'l' because the final target is 'local_path_from_url'
212
282
def _posix_local_path_from_url(url):
213
283
    """Convert a url like file:///path/to/foo into /path/to/foo"""
214
 
    url = strip_segment_parameters(url)
 
284
    url = split_segment_parameters_raw(url)[0]
215
285
    file_localhost_prefix = 'file://localhost/'
216
286
    if url.startswith(file_localhost_prefix):
217
287
        path = url[len(file_localhost_prefix) - 1:]
239
309
    if not url.startswith('file://'):
240
310
        raise InvalidURL(url, 'local urls must start with file:///, '
241
311
                         'UNC path urls must start with file://')
242
 
    url = strip_segment_parameters(url)
 
312
    url = split_segment_parameters_raw(url)[0]
243
313
    # We strip off all 3 slashes
244
314
    win32_url = url[len('file:'):]
245
315
    # check for UNC path: //HOST/path
340
410
        return local_path_to_url(url)
341
411
    prefix = url[:path_start]
342
412
    path = url[path_start:]
343
 
    if not isinstance(url, str):
 
413
    if not isinstance(url, text_type):
344
414
        for c in url:
345
415
            if c not in _url_safe_characters:
346
416
                raise InvalidURL(url, 'URLs can only contain specific'
503
573
    return (base_url, parameters)
504
574
 
505
575
 
506
 
def strip_segment_parameters(url):
507
 
    """Strip the segment parameters from a URL.
508
 
 
509
 
    :param url: A relative or absolute URL
510
 
    :return: url
511
 
    """
512
 
    base_url, subsegments = split_segment_parameters_raw(url)
513
 
    return base_url
514
 
 
515
 
 
516
576
def join_segment_parameters_raw(base, *subsegments):
517
577
    """Create a new URL by adding subsegments to an existing one.
518
578
 
620
680
    #       try to encode the UNICODE => ASCII, and then decode
621
681
    #       it into utf-8.
622
682
 
623
 
    if isinstance(url, str):
 
683
    if PY3:
 
684
        if isinstance(url, text_type):
 
685
            try:
 
686
                url.encode("ascii")
 
687
            except UnicodeError as e:
 
688
                raise InvalidURL(
 
689
                    url, 'URL was not a plain ASCII url: %s' % (e,))
 
690
        return urlparse.unquote(url)
 
691
    else:
 
692
        if isinstance(url, text_type):
 
693
            try:
 
694
                url = url.encode("ascii")
 
695
            except UnicodeError as e:
 
696
                raise InvalidURL(
 
697
                    url, 'URL was not a plain ASCII url: %s' % (e,))
 
698
        unquoted = unquote(url)
624
699
        try:
625
 
            url.encode("ascii")
 
700
            unicode_path = unquoted.decode('utf-8')
626
701
        except UnicodeError as e:
627
702
            raise InvalidURL(
628
 
                url, 'URL was not a plain ASCII url: %s' % (e,))
629
 
    return urlparse.unquote(url)
 
703
                url, 'Unable to encode the URL as utf-8: %s' % (e,))
 
704
        return unicode_path
630
705
 
631
706
 
632
707
# These are characters that if escaped, should stay that way
634
709
_no_decode_ords = [ord(c) for c in _no_decode_chars]
635
710
_no_decode_hex = (['%02x' % o for o in _no_decode_ords]
636
711
                  + ['%02X' % o for o in _no_decode_ords])
637
 
_hex_display_map = dict(([('%02x' % o, bytes([o])) for o in range(256)]
638
 
                         + [('%02X' % o, bytes([o])) for o in range(256)]))
 
712
_hex_display_map = dict(([('%02x' % o, int2byte(o)) for o in range(256)]
 
713
                         + [('%02X' % o, int2byte(o)) for o in range(256)]))
639
714
# These entries get mapped to themselves
640
715
_hex_display_map.update((hex, b'%' + hex.encode('ascii'))
641
716
                        for hex in _no_decode_hex)
679
754
            escaped_chunks[j] = _hex_display_map[item[:2]]
680
755
        except KeyError:
681
756
            # Put back the percent symbol
682
 
            escaped_chunks[j] = b'%' + (item[:2].encode('utf-8'))
 
757
            escaped_chunks[j] = b'%' + \
 
758
                (item[:2].encode('utf-8') if PY3 else item[:2])
683
759
        except UnicodeDecodeError:
684
 
            escaped_chunks[j] = chr(int(item[:2], 16)).encode('utf-8')
685
 
        escaped_chunks[j] += (item[2:].encode('utf-8'))
 
760
            escaped_chunks[j] = unichr(int(item[:2], 16)).encode('utf-8')
 
761
        escaped_chunks[j] += (item[2:].encode('utf-8') if PY3 else item[2:])
686
762
    unescaped = b''.join(escaped_chunks)
687
763
    try:
688
764
        decoded = unescaped.decode('utf-8')
745
821
    is used without a path, e.g. c:foo-bar => foo-bar.
746
822
    If no /, path separator or : is found, the from_location is returned.
747
823
    """
748
 
    from_location = strip_segment_parameters(from_location)
 
824
    from_location, unused_params = split_segment_parameters(from_location)
749
825
    if from_location.find("/") >= 0 or from_location.find(os.sep) >= 0:
750
826
        return os.path.basename(from_location.rstrip("/\\"))
751
827
    else:
846
922
        # unicode.
847
923
        if isinstance(url, str):
848
924
            pass
849
 
        elif isinstance(url, str):
 
925
        elif isinstance(url, text_type):
850
926
            try:
851
927
                url = url.encode()
852
928
            except UnicodeEncodeError:
917
993
        # unicode.
918
994
        if isinstance(relpath, str):
919
995
            pass
920
 
        elif isinstance(relpath, str):
 
996
        elif isinstance(relpath, text_type):
921
997
            try:
922
998
                relpath = relpath.encode()
923
999
            except UnicodeEncodeError:
955
1031
        """
956
1032
        if offset is not None:
957
1033
            relative = unescape(offset)
 
1034
            if sys.version_info[0] == 2:
 
1035
                relative = relative.encode('utf-8')
958
1036
            path = self._combine_paths(self.path, relative)
959
1037
            path = quote(path, safe="/~")
960
1038
        else: