/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: 2020-07-05 12:50:01 UTC
  • mfrom: (7490.40.46 work)
  • mto: (7490.40.48 work)
  • mto: This revision was merged to the branch mainline in revision 7519.
  • Revision ID: jelmer@jelmer.uk-20200705125001-7s3vo0p55szbbws7
Merge lp:brz/3.1.

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
165
def escape(relpath, safe='/~'):
98
166
    """Escape relpath to be a valid url."""
 
167
    if not isinstance(relpath, str) and sys.version_info[0] == 2:
 
168
        # GZ 2019-06-16: Should use _fs_enc instead here really?
 
169
        relpath = relpath.encode('utf-8')
99
170
    return quote(relpath, safe=safe)
100
171
 
101
172
 
340
411
        return local_path_to_url(url)
341
412
    prefix = url[:path_start]
342
413
    path = url[path_start:]
343
 
    if not isinstance(url, str):
 
414
    if not isinstance(url, text_type):
344
415
        for c in url:
345
416
            if c not in _url_safe_characters:
346
417
                raise InvalidURL(url, 'URLs can only contain specific'
620
691
    #       try to encode the UNICODE => ASCII, and then decode
621
692
    #       it into utf-8.
622
693
 
623
 
    if isinstance(url, str):
 
694
    if PY3:
 
695
        if isinstance(url, text_type):
 
696
            try:
 
697
                url.encode("ascii")
 
698
            except UnicodeError as e:
 
699
                raise InvalidURL(
 
700
                    url, 'URL was not a plain ASCII url: %s' % (e,))
 
701
        return urlparse.unquote(url)
 
702
    else:
 
703
        if isinstance(url, text_type):
 
704
            try:
 
705
                url = url.encode("ascii")
 
706
            except UnicodeError as e:
 
707
                raise InvalidURL(
 
708
                    url, 'URL was not a plain ASCII url: %s' % (e,))
 
709
        unquoted = unquote(url)
624
710
        try:
625
 
            url.encode("ascii")
 
711
            unicode_path = unquoted.decode('utf-8')
626
712
        except UnicodeError as e:
627
713
            raise InvalidURL(
628
 
                url, 'URL was not a plain ASCII url: %s' % (e,))
629
 
    return urlparse.unquote(url)
 
714
                url, 'Unable to encode the URL as utf-8: %s' % (e,))
 
715
        return unicode_path
630
716
 
631
717
 
632
718
# These are characters that if escaped, should stay that way
634
720
_no_decode_ords = [ord(c) for c in _no_decode_chars]
635
721
_no_decode_hex = (['%02x' % o for o in _no_decode_ords]
636
722
                  + ['%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)]))
 
723
_hex_display_map = dict(([('%02x' % o, int2byte(o)) for o in range(256)]
 
724
                         + [('%02X' % o, int2byte(o)) for o in range(256)]))
639
725
# These entries get mapped to themselves
640
726
_hex_display_map.update((hex, b'%' + hex.encode('ascii'))
641
727
                        for hex in _no_decode_hex)
679
765
            escaped_chunks[j] = _hex_display_map[item[:2]]
680
766
        except KeyError:
681
767
            # Put back the percent symbol
682
 
            escaped_chunks[j] = b'%' + (item[:2].encode('utf-8'))
 
768
            escaped_chunks[j] = b'%' + \
 
769
                (item[:2].encode('utf-8') if PY3 else item[:2])
683
770
        except UnicodeDecodeError:
684
 
            escaped_chunks[j] = chr(int(item[:2], 16)).encode('utf-8')
685
 
        escaped_chunks[j] += (item[2:].encode('utf-8'))
 
771
            escaped_chunks[j] = unichr(int(item[:2], 16)).encode('utf-8')
 
772
        escaped_chunks[j] += (item[2:].encode('utf-8') if PY3 else item[2:])
686
773
    unescaped = b''.join(escaped_chunks)
687
774
    try:
688
775
        decoded = unescaped.decode('utf-8')
846
933
        # unicode.
847
934
        if isinstance(url, str):
848
935
            pass
849
 
        elif isinstance(url, str):
 
936
        elif isinstance(url, text_type):
850
937
            try:
851
938
                url = url.encode()
852
939
            except UnicodeEncodeError:
917
1004
        # unicode.
918
1005
        if isinstance(relpath, str):
919
1006
            pass
920
 
        elif isinstance(relpath, str):
 
1007
        elif isinstance(relpath, text_type):
921
1008
            try:
922
1009
                relpath = relpath.encode()
923
1010
            except UnicodeEncodeError:
955
1042
        """
956
1043
        if offset is not None:
957
1044
            relative = unescape(offset)
 
1045
            if sys.version_info[0] == 2:
 
1046
                relative = relative.encode('utf-8')
958
1047
            path = self._combine_paths(self.path, relative)
959
1048
            path = quote(path, safe="/~")
960
1049
        else: