/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: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

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
 
try:
26
 
    import urlparse
27
 
except ImportError:
28
 
    from urllib import parse as urlparse
 
23
from urllib import parse as urlparse
29
24
 
30
25
from . import (
31
26
    errors,
37
32
from posixpath import split as _posix_split
38
33
""")
39
34
 
40
 
from .sixish import (
41
 
    int2byte,
42
 
    PY3,
43
 
    text_type,
44
 
    unichr,
45
 
    )
46
35
 
47
36
 
48
37
class InvalidURL(errors.PathError):
99
88
    return split(url, exclude_trailing_slash=exclude_trailing_slash)[0]
100
89
 
101
90
 
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
 
 
 
91
quote_from_bytes = urlparse.quote_from_bytes
 
92
quote = urlparse.quote
 
93
unquote_to_bytes = urlparse.unquote_to_bytes
162
94
unquote = urlparse.unquote
163
95
 
164
96
 
165
 
def escape(relpath):
 
97
def escape(relpath, safe='/~'):
166
98
    """Escape relpath to be a valid url."""
167
 
    if not isinstance(relpath, str) and sys.version_info[0] == 2:
168
 
        relpath = relpath.encode('utf-8')
169
 
    return quote(relpath, safe='/~')
 
99
    return quote(relpath, safe=safe)
170
100
 
171
101
 
172
102
def file_relpath(base, path):
281
211
# jam 20060502 Sorted to 'l' because the final target is 'local_path_from_url'
282
212
def _posix_local_path_from_url(url):
283
213
    """Convert a url like file:///path/to/foo into /path/to/foo"""
284
 
    url = split_segment_parameters_raw(url)[0]
 
214
    url = strip_segment_parameters(url)
285
215
    file_localhost_prefix = 'file://localhost/'
286
216
    if url.startswith(file_localhost_prefix):
287
217
        path = url[len(file_localhost_prefix) - 1:]
309
239
    if not url.startswith('file://'):
310
240
        raise InvalidURL(url, 'local urls must start with file:///, '
311
241
                         'UNC path urls must start with file://')
312
 
    url = split_segment_parameters_raw(url)[0]
 
242
    url = strip_segment_parameters(url)
313
243
    # We strip off all 3 slashes
314
244
    win32_url = url[len('file:'):]
315
245
    # check for UNC path: //HOST/path
410
340
        return local_path_to_url(url)
411
341
    prefix = url[:path_start]
412
342
    path = url[path_start:]
413
 
    if not isinstance(url, text_type):
 
343
    if not isinstance(url, str):
414
344
        for c in url:
415
345
            if c not in _url_safe_characters:
416
346
                raise InvalidURL(url, 'URLs can only contain specific'
561
491
    (base_url, subsegments) = split_segment_parameters_raw(url)
562
492
    parameters = {}
563
493
    for subsegment in subsegments:
564
 
        (key, value) = subsegment.split("=", 1)
 
494
        try:
 
495
            (key, value) = subsegment.split("=", 1)
 
496
        except ValueError:
 
497
            raise InvalidURL(url, "missing = in subsegment")
565
498
        if not isinstance(key, str):
566
499
            raise TypeError(key)
567
500
        if not isinstance(value, str):
570
503
    return (base_url, parameters)
571
504
 
572
505
 
 
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
 
573
516
def join_segment_parameters_raw(base, *subsegments):
574
517
    """Create a new URL by adding subsegments to an existing one.
575
518
 
677
620
    #       try to encode the UNICODE => ASCII, and then decode
678
621
    #       it into utf-8.
679
622
 
680
 
    if PY3:
681
 
        if isinstance(url, text_type):
682
 
            try:
683
 
                url.encode("ascii")
684
 
            except UnicodeError as e:
685
 
                raise InvalidURL(
686
 
                    url, 'URL was not a plain ASCII url: %s' % (e,))
687
 
        return urlparse.unquote(url)
688
 
    else:
689
 
        if isinstance(url, text_type):
690
 
            try:
691
 
                url = url.encode("ascii")
692
 
            except UnicodeError as e:
693
 
                raise InvalidURL(
694
 
                    url, 'URL was not a plain ASCII url: %s' % (e,))
695
 
        unquoted = unquote(url)
 
623
    if isinstance(url, str):
696
624
        try:
697
 
            unicode_path = unquoted.decode('utf-8')
 
625
            url.encode("ascii")
698
626
        except UnicodeError as e:
699
627
            raise InvalidURL(
700
 
                url, 'Unable to encode the URL as utf-8: %s' % (e,))
701
 
        return unicode_path
 
628
                url, 'URL was not a plain ASCII url: %s' % (e,))
 
629
    return urlparse.unquote(url)
702
630
 
703
631
 
704
632
# These are characters that if escaped, should stay that way
706
634
_no_decode_ords = [ord(c) for c in _no_decode_chars]
707
635
_no_decode_hex = (['%02x' % o for o in _no_decode_ords]
708
636
                  + ['%02X' % o for o in _no_decode_ords])
709
 
_hex_display_map = dict(([('%02x' % o, int2byte(o)) for o in range(256)]
710
 
                         + [('%02X' % o, int2byte(o)) for o in range(256)]))
 
637
_hex_display_map = dict(([('%02x' % o, bytes([o])) for o in range(256)]
 
638
                         + [('%02X' % o, bytes([o])) for o in range(256)]))
711
639
# These entries get mapped to themselves
712
640
_hex_display_map.update((hex, b'%' + hex.encode('ascii'))
713
641
                        for hex in _no_decode_hex)
751
679
            escaped_chunks[j] = _hex_display_map[item[:2]]
752
680
        except KeyError:
753
681
            # Put back the percent symbol
754
 
            escaped_chunks[j] = b'%' + \
755
 
                (item[:2].encode('utf-8') if PY3 else item[:2])
 
682
            escaped_chunks[j] = b'%' + (item[:2].encode('utf-8'))
756
683
        except UnicodeDecodeError:
757
 
            escaped_chunks[j] = unichr(int(item[:2], 16)).encode('utf-8')
758
 
        escaped_chunks[j] += (item[2:].encode('utf-8') if PY3 else item[2:])
 
684
            escaped_chunks[j] = chr(int(item[:2], 16)).encode('utf-8')
 
685
        escaped_chunks[j] += (item[2:].encode('utf-8'))
759
686
    unescaped = b''.join(escaped_chunks)
760
687
    try:
761
688
        decoded = unescaped.decode('utf-8')
818
745
    is used without a path, e.g. c:foo-bar => foo-bar.
819
746
    If no /, path separator or : is found, the from_location is returned.
820
747
    """
821
 
    from_location, unused_params = split_segment_parameters(from_location)
 
748
    from_location = strip_segment_parameters(from_location)
822
749
    if from_location.find("/") >= 0 or from_location.find(os.sep) >= 0:
823
750
        return os.path.basename(from_location.rstrip("/\\"))
824
751
    else:
919
846
        # unicode.
920
847
        if isinstance(url, str):
921
848
            pass
922
 
        elif isinstance(url, text_type):
 
849
        elif isinstance(url, str):
923
850
            try:
924
851
                url = url.encode()
925
852
            except UnicodeEncodeError:
990
917
        # unicode.
991
918
        if isinstance(relpath, str):
992
919
            pass
993
 
        elif isinstance(relpath, text_type):
 
920
        elif isinstance(relpath, str):
994
921
            try:
995
922
                relpath = relpath.encode()
996
923
            except UnicodeEncodeError:
1028
955
        """
1029
956
        if offset is not None:
1030
957
            relative = unescape(offset)
1031
 
            if sys.version_info[0] == 2:
1032
 
                relative = relative.encode('utf-8')
1033
958
            path = self._combine_paths(self.path, relative)
1034
959
            path = quote(path, safe="/~")
1035
960
        else: