/breezy/unstable

To get this branch, use:
bzr branch https://code.breezy-vcs.org/breezy/unstable

« back to all changes in this revision

Viewing changes to breezy/urlutils.py

Merge trunk, address review comments.

Show diffs side-by-side

added added

removed removed

Lines of Context:
27
27
except ImportError:
28
28
    from urllib import parse as urlparse
29
29
 
 
30
from . import (
 
31
    errors,
 
32
    osutils,
 
33
    )
 
34
 
30
35
from .lazy_import import lazy_import
31
36
lazy_import(globals(), """
32
37
from posixpath import split as _posix_split
33
 
 
34
 
from breezy import (
35
 
    errors,
36
 
    osutils,
37
 
    )
38
38
""")
39
39
 
40
40
from .sixish import (
43
43
    )
44
44
 
45
45
 
 
46
class InvalidURL(errors.PathError):
 
47
 
 
48
    _fmt = 'Invalid url supplied to transport: "%(path)s"%(extra)s'
 
49
 
 
50
 
 
51
class InvalidURLJoin(errors.PathError):
 
52
 
 
53
    _fmt = "Invalid URL join request: %(reason)s: %(base)r + %(join_args)r"
 
54
 
 
55
    def __init__(self, reason, base, join_args):
 
56
        self.reason = reason
 
57
        self.base = base
 
58
        self.join_args = join_args
 
59
        errors.PathError.__init__(self, base, reason)
 
60
 
 
61
 
 
62
class InvalidRebaseURLs(errors.PathError):
 
63
 
 
64
    _fmt = "URLs differ by more than path: %(from_)r and %(to)r"
 
65
 
 
66
    def __init__(self, from_, to):
 
67
        self.from_ = from_
 
68
        self.to = to
 
69
        errors.PathError.__init__(self, from_, 'URLs differ by more than path.')
 
70
 
 
71
 
46
72
def basename(url, exclude_trailing_slash=True):
47
73
    """Return the last component of a URL.
48
74
 
231
257
                continue
232
258
            elif chunk == '..':
233
259
                if path == ['']:
234
 
                    raise errors.InvalidURLJoin('Cannot go above root',
 
260
                    raise InvalidURLJoin('Cannot go above root',
235
261
                            base, args)
236
262
                path.pop()
237
263
            else:
250
276
    if url.startswith(file_localhost_prefix):
251
277
        path = url[len(file_localhost_prefix) - 1:]
252
278
    elif not url.startswith('file:///'):
253
 
        raise errors.InvalidURL(
 
279
        raise InvalidURL(
254
280
            url, 'local urls must start with file:/// or file://localhost/')
255
281
    else:
256
282
        path = url[len('file://'):]
271
297
def _win32_local_path_from_url(url):
272
298
    """Convert a url like file:///C:/path/to/foo into C:/path/to/foo"""
273
299
    if not url.startswith('file://'):
274
 
        raise errors.InvalidURL(url, 'local urls must start with file:///, '
 
300
        raise InvalidURL(url, 'local urls must start with file:///, '
275
301
                                     'UNC path urls must start with file://')
276
302
    url = split_segment_parameters_raw(url)[0]
277
303
    # We strip off all 3 slashes
280
306
    if not win32_url.startswith('///'):
281
307
        if (win32_url[2] == '/'
282
308
            or win32_url[3] in '|:'):
283
 
            raise errors.InvalidURL(url, 'Win32 UNC path urls'
 
309
            raise InvalidURL(url, 'Win32 UNC path urls'
284
310
                ' have form file://HOST/path')
285
311
        return unescape(win32_url)
286
312
 
294
320
                                'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
295
321
        or win32_url[4] not in  '|:'
296
322
        or win32_url[5] != '/'):
297
 
        raise errors.InvalidURL(url, 'Win32 file urls start with'
 
323
        raise InvalidURL(url, 'Win32 file urls start with'
298
324
                ' file:///x:/, where x is a valid drive letter')
299
325
    return win32_url[3].upper() + u':' + unescape(win32_url[5:])
300
326
 
377
403
    if not isinstance(url, text_type):
378
404
        for c in url:
379
405
            if c not in _url_safe_characters:
380
 
                raise errors.InvalidURL(url, 'URLs can only contain specific'
 
406
                raise InvalidURL(url, 'URLs can only contain specific'
381
407
                                            ' safe characters (not %r)' % c)
382
408
        path = _url_hex_escapes_re.sub(_unescape_safe_chars, path)
383
409
        return str(prefix + ''.join(path))
452
478
    # Strip off the drive letter
453
479
    # path is currently /C:/foo
454
480
    if len(path) < 4 or path[2] not in ':|' or path[3] != '/':
455
 
        raise errors.InvalidURL(url_base + path,
 
481
        raise InvalidURL(url_base + path,
456
482
            'win32 file:/// paths need a drive letter')
457
483
    url_base += path[0:3] # file:// + /C:
458
484
    path = path[3:] # /foo
544
570
        if not isinstance(subsegment, str):
545
571
            raise TypeError("Subsegment %r is not a bytestring" % subsegment)
546
572
        if "," in subsegment:
547
 
            raise errors.InvalidURLJoin(", exists in subsegments",
 
573
            raise InvalidURLJoin(", exists in subsegments",
548
574
                                        base, subsegments)
549
575
    return ",".join((base,) + subsegments)
550
576
 
568
594
            raise TypeError("parameter value %r for %s is not a bytestring" %
569
595
                (key, value))
570
596
        if "=" in key:
571
 
            raise errors.InvalidURLJoin("= exists in parameter key", url,
 
597
            raise InvalidURLJoin("= exists in parameter key", url,
572
598
                parameters)
573
599
        new_parameters[key] = value
574
600
    return join_segment_parameters_raw(base, 
640
666
        try:
641
667
            url = url.encode("ascii")
642
668
        except UnicodeError as e:
643
 
            raise errors.InvalidURL(url, 'URL was not a plain ASCII url: %s' % (e,))
 
669
            raise InvalidURL(url, 'URL was not a plain ASCII url: %s' % (e,))
644
670
    if PY3:
645
671
        unquoted = urlparse.unquote_to_bytes(url)
646
672
    else:
648
674
    try:
649
675
        unicode_path = unquoted.decode('utf-8')
650
676
    except UnicodeError as e:
651
 
        raise errors.InvalidURL(url, 'Unable to encode the URL as utf-8: %s' % (e,))
 
677
        raise InvalidURL(url, 'Unable to encode the URL as utf-8: %s' % (e,))
652
678
    return unicode_path
653
679
 
654
680
 
778
804
    old_parsed = urlparse.urlparse(old_base)
779
805
    new_parsed = urlparse.urlparse(new_base)
780
806
    if (old_parsed[:2]) != (new_parsed[:2]):
781
 
        raise errors.InvalidRebaseURLs(old_base, new_base)
 
807
        raise InvalidRebaseURLs(old_base, new_base)
782
808
    return determine_relative_path(new_parsed[2],
783
809
                                   join(old_parsed[2], url))
784
810
 
846
872
        """
847
873
        # GZ 2017-06-09: Actually validate ascii-ness
848
874
        if not isinstance(url, str):
849
 
            raise errors.InvalidURL('should be ascii:\n%r' % url)
 
875
            raise InvalidURL('should be ascii:\n%r' % url)
850
876
        (scheme, netloc, path, params,
851
877
         query, fragment) = urlparse.urlparse(url, allow_fragments=False)
852
878
        user = password = host = port = None
863
889
            try:
864
890
                port = int(port)
865
891
            except ValueError:
866
 
                raise errors.InvalidURL('invalid port number %s in url:\n%s' %
867
 
                                        (port, url))
 
892
                raise InvalidURL('invalid port number %s in url:\n%s' %
 
893
                                 (port, url))
868
894
        if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
869
895
            host = host[1:-1]
870
896
 
905
931
        :return: urlencoded string for final path.
906
932
        """
907
933
        if not isinstance(relpath, str):
908
 
            raise errors.InvalidURL(relpath)
 
934
            raise InvalidURL(relpath)
909
935
        relpath = _url_hex_escapes_re.sub(_unescape_safe_chars, relpath)
910
936
        if relpath.startswith('/'):
911
937
            base_parts = []