/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: Breezy landing bot
  • Author(s): Jelmer Vernooij
  • Date: 2018-11-16 18:59:44 UTC
  • mfrom: (7143.15.15 more-cleanups)
  • Revision ID: breezy.the.bot@gmail.com-20181116185944-biefv1sub37qfybm
Sprinkle some PEP8iness.

Merged from https://code.launchpad.net/~jelmer/brz/more-cleanups/+merge/358611

Show diffs side-by-side

added added

removed removed

Lines of Context:
41
41
    int2byte,
42
42
    PY3,
43
43
    text_type,
 
44
    unichr,
44
45
    )
45
46
 
46
47
 
67
68
    def __init__(self, from_, to):
68
69
        self.from_ = from_
69
70
        self.to = to
70
 
        errors.PathError.__init__(self, from_, 'URLs differ by more than path.')
 
71
        errors.PathError.__init__(
 
72
            self, from_, 'URLs differ by more than path.')
71
73
 
72
74
 
73
75
def basename(url, exclude_trailing_slash=True):
102
104
    quote = urlparse.quote
103
105
    unquote_to_bytes = urlparse.unquote_to_bytes
104
106
else:
105
 
    # Private copies of quote and unquote, copied from Python's
106
 
    # urllib module because urllib unconditionally imports socket, which imports
107
 
    # ssl.
 
107
    # Private copies of quote and unquote, copied from Python's urllib module
 
108
    # because urllib unconditionally imports socket, which imports ssl.
108
109
 
109
110
    always_safe = ('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
110
111
                   'abcdefghijklmnopqrstuvwxyz'
111
112
                   '0123456789' '_.-')
112
113
    _safe_map = {}
113
114
    for i, c in zip(range(256), ''.join(map(chr, range(256)))):
114
 
        _safe_map[c] = c if (i < 128 and c in always_safe) else '%{0:02X}'.format(i)
 
115
        _safe_map[c] = c if (
 
116
            i < 128 and c in always_safe) else '%{0:02X}'.format(i)
115
117
    _safe_quoters = {}
116
118
 
117
119
    def quote_from_bytes(s, safe='/'):
174
176
    """
175
177
    if len(base) < MIN_ABS_FILEURL_LENGTH:
176
178
        raise ValueError('Length of base (%r) must equal or'
177
 
            ' exceed the platform minimum url length (which is %d)' %
178
 
            (base, MIN_ABS_FILEURL_LENGTH))
 
179
                         ' exceed the platform minimum url length (which is %d)' %
 
180
                         (base, MIN_ABS_FILEURL_LENGTH))
179
181
    base = osutils.normpath(local_path_from_url(base))
180
182
    path = osutils.normpath(local_path_from_url(path))
181
183
    return escape(osutils.relpath(base, path))
199
201
    first_path_slash = path.find('/')
200
202
    if first_path_slash == -1:
201
203
        return len(scheme), None
202
 
    return len(scheme), first_path_slash+m.start('path')
 
204
    return len(scheme), first_path_slash + m.start('path')
203
205
 
204
206
 
205
207
def is_url(url):
255
257
    """
256
258
    path = base.split('/')
257
259
    if len(path) > 1 and path[-1] == '':
258
 
        #If the path ends in a trailing /, remove it.
 
260
        # If the path ends in a trailing /, remove it.
259
261
        path.pop()
260
262
    for arg in args:
261
263
        if arg.startswith('/'):
266
268
            elif chunk == '..':
267
269
                if path == ['']:
268
270
                    raise InvalidURLJoin('Cannot go above root',
269
 
                            base, args)
 
271
                                         base, args)
270
272
                path.pop()
271
273
            else:
272
274
                path.append(chunk)
306
308
    """Convert a url like file:///C:/path/to/foo into C:/path/to/foo"""
307
309
    if not url.startswith('file://'):
308
310
        raise InvalidURL(url, 'local urls must start with file:///, '
309
 
                                     'UNC path urls must start with file://')
 
311
                         'UNC path urls must start with file://')
310
312
    url = split_segment_parameters_raw(url)[0]
311
313
    # We strip off all 3 slashes
312
314
    win32_url = url[len('file:'):]
313
315
    # check for UNC path: //HOST/path
314
316
    if not win32_url.startswith('///'):
315
317
        if (win32_url[2] == '/'
316
 
            or win32_url[3] in '|:'):
 
318
                or win32_url[3] in '|:'):
317
319
            raise InvalidURL(url, 'Win32 UNC path urls'
318
 
                ' have form file://HOST/path')
 
320
                             ' have form file://HOST/path')
319
321
        return unescape(win32_url)
320
322
 
321
323
    # allow empty paths so we can serve all roots
325
327
    # usual local path with drive letter
326
328
    if (len(win32_url) < 6
327
329
        or win32_url[3] not in ('abcdefghijklmnopqrstuvwxyz'
328
 
                                'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
329
 
        or win32_url[4] not in  '|:'
330
 
        or win32_url[5] != '/'):
 
330
                                'ABCDEFGHIJKLMNOPQRSTUVWXYZ') or
 
331
        win32_url[4] not in '|:'
 
332
            or win32_url[5] != '/'):
331
333
        raise InvalidURL(url, 'Win32 file urls start with'
332
 
                ' file:///x:/, where x is a valid drive letter')
 
334
                         ' file:///x:/, where x is a valid drive letter')
333
335
    return win32_url[3].upper() + u':' + unescape(win32_url[5:])
334
336
 
335
337
 
352
354
    if win32_path.startswith('//'):
353
355
        return 'file:' + escape(win32_path)
354
356
    return ('file:///' + str(win32_path[0].upper()) + ':' +
355
 
        escape(win32_path[2:]))
 
357
            escape(win32_path[2:]))
356
358
 
357
359
 
358
360
local_path_to_url = _posix_local_path_to_url
412
414
        for c in url:
413
415
            if c not in _url_safe_characters:
414
416
                raise InvalidURL(url, 'URLs can only contain specific'
415
 
                                            ' safe characters (not %r)' % c)
 
417
                                 ' safe characters (not %r)' % c)
416
418
        path = _url_hex_escapes_re.sub(_unescape_safe_chars, path)
417
419
        return str(prefix + ''.join(path))
418
420
 
421
423
 
422
424
    for i in range(len(path_chars)):
423
425
        if path_chars[i] not in _url_safe_characters:
424
 
            chars = path_chars[i].encode('utf-8')
425
426
            path_chars[i] = ''.join(
426
427
                ['%%%02X' % c for c in bytearray(path_chars[i].encode('utf-8'))])
427
428
    path = ''.join(path_chars)
449
450
    if base_scheme != other_scheme:
450
451
        return other
451
452
    elif sys.platform == 'win32' and base_scheme == 'file://':
452
 
        base_drive = base[base_first_slash+1:base_first_slash+3]
453
 
        other_drive = other[other_first_slash+1:other_first_slash+3]
 
453
        base_drive = base[base_first_slash + 1:base_first_slash + 3]
 
454
        other_drive = other[other_first_slash + 1:other_first_slash + 3]
454
455
        if base_drive != other_drive:
455
456
            return other
456
457
 
457
 
    base_path = base[base_first_slash+1:]
458
 
    other_path = other[other_first_slash+1:]
 
458
    base_path = base[base_first_slash + 1:]
 
459
    other_path = other[other_first_slash + 1:]
459
460
 
460
461
    if base_path.endswith('/'):
461
462
        base_path = base_path[:-1]
487
488
    # path is currently /C:/foo
488
489
    if len(path) < 4 or path[2] not in ':|' or path[3] != '/':
489
490
        raise InvalidURL(url_base + path,
490
 
            'win32 file:/// paths need a drive letter')
491
 
    url_base += path[0:3] # file:// + /C:
492
 
    path = path[3:] # /foo
 
491
                         'win32 file:/// paths need a drive letter')
 
492
    url_base += path[0:3]  # file:// + /C:
 
493
    path = path[3:]  # /foo
493
494
    return url_base, path
494
495
 
495
496
 
500
501
    :param exclude_trailing_slash: Strip off a final '/' if it is part
501
502
        of the path (but not if it is part of the protocol specification)
502
503
 
503
 
    :return: (parent_url, child_dir).  child_dir may be the empty string if we're at
504
 
        the root.
 
504
    :return: (parent_url, child_dir).  child_dir may be the empty string if
 
505
        we're at the root.
505
506
    """
506
507
    scheme_loc, first_path_slash = _find_scheme_and_separator(url)
507
508
 
517
518
            return url, ''
518
519
 
519
520
    # We have a fully defined path
520
 
    url_base = url[:first_path_slash] # http://host, file://
521
 
    path = url[first_path_slash:] # /file/foo
 
521
    url_base = url[:first_path_slash]  # http://host, file://
 
522
    path = url[first_path_slash:]  # /file/foo
522
523
 
523
524
    if sys.platform == 'win32' and url.startswith('file:///'):
524
525
        # Strip off the drive letter
541
542
    """
542
543
    # GZ 2011-11-18: Dodgy removing the terminal slash like this, function
543
544
    #                operates on urls not url+segments, and Transport classes
544
 
    #                should not be blindly adding slashes in the first place. 
 
545
    #                should not be blindly adding slashes in the first place.
545
546
    lurl = strip_trailing_slash(url)
546
547
    # Segments begin at first comma after last forward slash, if one exists
547
 
    segment_start = lurl.find(",", lurl.rfind("/")+1)
 
548
    segment_start = lurl.find(",", lurl.rfind("/") + 1)
548
549
    if segment_start == -1:
549
550
        return (url, [])
550
 
    return (lurl[:segment_start], [str(s) for s in lurl[segment_start+1:].split(",")])
 
551
    return (lurl[:segment_start],
 
552
            [str(s) for s in lurl[segment_start + 1:].split(",")])
551
553
 
552
554
 
553
555
def split_segment_parameters(url):
569
571
 
570
572
 
571
573
def join_segment_parameters_raw(base, *subsegments):
572
 
    """Create a new URL by adding subsegments to an existing one. 
 
574
    """Create a new URL by adding subsegments to an existing one.
573
575
 
574
576
    This adds the specified subsegments to the last path in the specified
575
577
    base URL. The subsegments should be bytestrings.
583
585
            raise TypeError("Subsegment %r is not a bytestring" % subsegment)
584
586
        if "," in subsegment:
585
587
            raise InvalidURLJoin(", exists in subsegments",
586
 
                                        base, subsegments)
 
588
                                 base, subsegments)
587
589
    return ",".join((base,) + subsegments)
588
590
 
589
591
 
604
606
            raise TypeError("parameter key %r is not a str" % key)
605
607
        if not isinstance(value, str):
606
608
            raise TypeError("parameter value %r for %r is not a str" %
607
 
                (value, key))
 
609
                            (value, key))
608
610
        if "=" in key:
609
611
            raise InvalidURLJoin("= exists in parameter key", url,
610
 
                parameters)
 
612
                                 parameters)
611
613
        new_parameters[key] = value
612
 
    return join_segment_parameters_raw(base,
613
 
        *["%s=%s" % item for item in sorted(new_parameters.items())])
 
614
    return join_segment_parameters_raw(
 
615
        base, *["%s=%s" % item for item in sorted(new_parameters.items())])
614
616
 
615
617
 
616
618
def _win32_strip_local_trailing_slash(url):
654
656
        # so just chop off the last character
655
657
        return url[:-1]
656
658
 
657
 
    if first_path_slash is None or first_path_slash == len(url)-1:
 
659
    if first_path_slash is None or first_path_slash == len(url) - 1:
658
660
        # Don't chop off anything if the only slash is the path
659
661
        # separating slash
660
662
        return url
680
682
            try:
681
683
                url.encode("ascii")
682
684
            except UnicodeError as e:
683
 
                raise InvalidURL(url, 'URL was not a plain ASCII url: %s' % (e,))
 
685
                raise InvalidURL(
 
686
                    url, 'URL was not a plain ASCII url: %s' % (e,))
684
687
        return urlparse.unquote(url)
685
688
    else:
686
689
        if isinstance(url, text_type):
687
690
            try:
688
691
                url = url.encode("ascii")
689
692
            except UnicodeError as e:
690
 
                raise InvalidURL(url, 'URL was not a plain ASCII url: %s' % (e,))
 
693
                raise InvalidURL(
 
694
                    url, 'URL was not a plain ASCII url: %s' % (e,))
691
695
        unquoted = unquote(url)
692
696
        try:
693
697
            unicode_path = unquoted.decode('utf-8')
694
698
        except UnicodeError as e:
695
 
            raise InvalidURL(url, 'Unable to encode the URL as utf-8: %s' % (e,))
 
699
            raise InvalidURL(
 
700
                url, 'Unable to encode the URL as utf-8: %s' % (e,))
696
701
        return unicode_path
697
702
 
698
703
 
700
705
_no_decode_chars = ';/?:@&=+$,#'
701
706
_no_decode_ords = [ord(c) for c in _no_decode_chars]
702
707
_no_decode_hex = (['%02x' % o for o in _no_decode_ords]
703
 
                + ['%02X' % o for o in _no_decode_ords])
 
708
                  + ['%02X' % o for o in _no_decode_ords])
704
709
_hex_display_map = dict(([('%02x' % o, int2byte(o)) for o in range(256)]
705
 
                    + [('%02X' % o, int2byte(o)) for o in range(256)]))
706
 
#These entries get mapped to themselves
707
 
_hex_display_map.update((hex, b'%'+hex.encode('ascii')) for hex in _no_decode_hex)
 
710
                         + [('%02X' % o, int2byte(o)) for o in range(256)]))
 
711
# These entries get mapped to themselves
 
712
_hex_display_map.update((hex, b'%' + hex.encode('ascii'))
 
713
                        for hex in _no_decode_hex)
708
714
 
709
715
# These characters shouldn't be percent-encoded, and it's always safe to
710
716
# unencode them if they are.
711
717
_url_dont_escape_characters = set(
712
 
   "abcdefghijklmnopqrstuvwxyz" # Lowercase alpha
713
 
   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # Uppercase alpha
714
 
   "0123456789" # Numbers
715
 
   "-._~"  # Unreserved characters
 
718
    "abcdefghijklmnopqrstuvwxyz"  # Lowercase alpha
 
719
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  # Uppercase alpha
 
720
    "0123456789"  # Numbers
 
721
    "-._~"  # Unreserved characters
716
722
)
717
723
 
718
724
# These characters should not be escaped
719
725
_url_safe_characters = set(
720
 
   "abcdefghijklmnopqrstuvwxyz" # Lowercase alpha
721
 
   "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # Uppercase alpha
722
 
   "0123456789" # Numbers
723
 
   "_.-!~*'()"  # Unreserved characters
724
 
   "/;?:@&=+$," # Reserved characters
725
 
   "%#"         # Extra reserved characters
 
726
    "abcdefghijklmnopqrstuvwxyz"  # Lowercase alpha
 
727
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"  # Uppercase alpha
 
728
    "0123456789"  # Numbers
 
729
    "_.-!~*'()"  # Unreserved characters
 
730
    "/;?:@&=+$,"  # Reserved characters
 
731
    "%#"         # Extra reserved characters
726
732
)
727
733
 
728
734
 
745
751
            escaped_chunks[j] = _hex_display_map[item[:2]]
746
752
        except KeyError:
747
753
            # Put back the percent symbol
748
 
            escaped_chunks[j] = b'%' + (item[:2].encode('utf-8') if PY3 else item[:2])
 
754
            escaped_chunks[j] = b'%' + \
 
755
                (item[:2].encode('utf-8') if PY3 else item[:2])
749
756
        except UnicodeDecodeError:
750
757
            escaped_chunks[j] = unichr(int(item[:2], 16)).encode('utf-8')
751
 
        escaped_chunks[j] +=  (item[2:].encode('utf-8') if PY3 else item[2:])
 
758
        escaped_chunks[j] += (item[2:].encode('utf-8') if PY3 else item[2:])
752
759
    unescaped = b''.join(escaped_chunks)
753
760
    try:
754
761
        decoded = unescaped.decode('utf-8')
817
824
    else:
818
825
        sep = from_location.find(":")
819
826
        if sep > 0:
820
 
            return from_location[sep+1:]
 
827
            return from_location[sep + 1:]
821
828
        else:
822
829
            return from_location
823
830
 
851
858
    to_segments = osutils.splitpath(to_path)
852
859
    count = -1
853
860
    for count, (from_element, to_element) in enumerate(zip(from_segments,
854
 
                                                       to_segments)):
 
861
                                                           to_segments)):
855
862
        if from_element != to_element:
856
863
            break
857
864
    else:
868
875
    """Parsed URL."""
869
876
 
870
877
    def __init__(self, scheme, quoted_user, quoted_password, quoted_host,
871
 
            port, quoted_path):
 
878
                 port, quoted_path):
872
879
        self.scheme = scheme
873
880
        self.quoted_host = quoted_host
874
881
        self.host = unquote(self.quoted_host)
883
890
        else:
884
891
            self.password = None
885
892
        self.port = port
886
 
        self.quoted_path = _url_hex_escapes_re.sub(_unescape_safe_chars, quoted_path)
 
893
        self.quoted_path = _url_hex_escapes_re.sub(
 
894
            _unescape_safe_chars, quoted_path)
887
895
        self.path = unquote(self.quoted_path)
888
896
 
889
897
    def __eq__(self, other):
907
915
        :param url: URL as bytestring
908
916
        """
909
917
        # GZ 2017-06-09: Actually validate ascii-ness
910
 
        # pad.lv/1696545: For the moment, accept both native strings and unicode.
 
918
        # pad.lv/1696545: For the moment, accept both native strings and
 
919
        # unicode.
911
920
        if isinstance(url, str):
912
921
            pass
913
922
        elif isinstance(url, text_type):
938
947
                                     (port, url))
939
948
            else:
940
949
                port = None
941
 
        if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
 
950
        if host != "" and host[0] == '[' and host[-1] == ']':  # IPv6
942
951
            host = host[1:-1]
943
952
 
944
953
        return cls(scheme, user, password, host, port, path)
977
986
        :param relpath: relative url string for relative part of remote path.
978
987
        :return: urlencoded string for final path.
979
988
        """
980
 
        # pad.lv/1696545: For the moment, accept both native strings and unicode.
 
989
        # pad.lv/1696545: For the moment, accept both native strings and
 
990
        # unicode.
981
991
        if isinstance(relpath, str):
982
992
            pass
983
993
        elif isinstance(relpath, text_type):
1002
1012
                    continue
1003
1013
                base_parts.pop()
1004
1014
            elif p == '.':
1005
 
                continue # No-op
 
1015
                continue  # No-op
1006
1016
            elif p != '':
1007
1017
                base_parts.append(p)
1008
1018
        path = '/'.join(base_parts)
1025
1035
        else:
1026
1036
            path = self.quoted_path
1027
1037
        return self.__class__(self.scheme, self.quoted_user,
1028
 
                self.quoted_password, self.quoted_host, self.port,
1029
 
                path)
 
1038
                              self.quoted_password, self.quoted_host, self.port,
 
1039
                              path)
1030
1040
 
1031
1041
 
1032
1042
def parse_url(url):
1041
1051
    """
1042
1052
    parsed_url = URL.from_string(url)
1043
1053
    return (parsed_url.scheme, parsed_url.user, parsed_url.password,
1044
 
        parsed_url.host, parsed_url.port, parsed_url.path)
 
1054
            parsed_url.host, parsed_url.port, parsed_url.path)