~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/urlutils.py

  • Committer: John Arbash Meinel
  • Date: 2010-01-13 16:23:07 UTC
  • mto: (4634.119.7 2.0)
  • mto: This revision was merged to the branch mainline in revision 4959.
  • Revision ID: john@arbash-meinel.com-20100113162307-0bs82td16gzih827
Update the MANIFEST.in file.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Bazaar -- distributed version control
2
 
#
3
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006, 2008 Canonical Ltd
4
2
#
5
3
# This program is free software; you can redistribute it and/or modify
6
4
# it under the terms of the GNU General Public License as published by
14
12
#
15
13
# You should have received a copy of the GNU General Public License
16
14
# along with this program; if not, write to the Free Software
17
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
16
 
19
17
"""A collection of function for handling URL operations."""
20
18
 
26
24
lazy_import(globals(), """
27
25
from posixpath import split as _posix_split, normpath as _posix_normpath
28
26
import urllib
 
27
import urlparse
29
28
 
30
29
from bzrlib import (
31
30
    errors,
67
66
        relpath = relpath.encode('utf-8')
68
67
    # After quoting and encoding, the path should be perfectly
69
68
    # safe as a plain ASCII string, str() just enforces this
70
 
    return str(urllib.quote(relpath))
 
69
    return str(urllib.quote(relpath, safe='/~'))
71
70
 
72
71
 
73
72
def file_relpath(base, path):
74
73
    """Compute just the relative sub-portion of a url
75
 
    
 
74
 
76
75
    This assumes that both paths are already fully specified file:// URLs.
77
76
    """
78
 
    assert len(base) >= MIN_ABS_FILEURL_LENGTH, ('Length of base must be equal or'
79
 
        ' exceed the platform minimum url length (which is %d)' % 
80
 
        MIN_ABS_FILEURL_LENGTH)
81
 
 
 
77
    if len(base) < MIN_ABS_FILEURL_LENGTH:
 
78
        raise ValueError('Length of base (%r) must equal or'
 
79
            ' exceed the platform minimum url length (which is %d)' %
 
80
            (base, MIN_ABS_FILEURL_LENGTH))
82
81
    base = local_path_from_url(base)
83
82
    path = local_path_from_url(path)
84
83
    return escape(osutils.relpath(base, path))
184
183
 
185
184
def joinpath(base, *args):
186
185
    """Join URL path segments to a URL path segment.
187
 
    
 
186
 
188
187
    This is somewhat like osutils.joinpath, but intended for URLs.
189
188
 
190
189
    XXX: this duplicates some normalisation logic, and also duplicates a lot of
229
228
 
230
229
    This also handles transforming escaping unicode characters, etc.
231
230
    """
232
 
    # importing directly from posixpath allows us to test this 
 
231
    # importing directly from posixpath allows us to test this
233
232
    # on non-posix platforms
234
233
    return 'file://' + escape(_posix_normpath(
235
234
        osutils._posix_abspath(path)))
249
248
            raise errors.InvalidURL(url, 'Win32 UNC path urls'
250
249
                ' have form file://HOST/path')
251
250
        return unescape(win32_url)
 
251
 
 
252
    # allow empty paths so we can serve all roots
 
253
    if win32_url == '///':
 
254
        return '/'
 
255
 
252
256
    # usual local path with drive letter
253
257
    if (win32_url[3] not in ('abcdefghijklmnopqrstuvwxyz'
254
258
                             'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
264
268
 
265
269
    This also handles transforming escaping unicode characters, etc.
266
270
    """
267
 
    # importing directly from ntpath allows us to test this 
 
271
    # importing directly from ntpath allows us to test this
268
272
    # on non-win32 platform
269
273
    # FIXME: It turns out that on nt, ntpath.abspath uses nt._getfullpathname
270
274
    #       which actually strips trailing space characters.
271
275
    #       The worst part is that under linux ntpath.abspath has different
272
276
    #       semantics, since 'nt' is not an available module.
 
277
    if path == '/':
 
278
        return 'file:///'
 
279
 
273
280
    win32_path = osutils._win32_abspath(path)
274
281
    # check for UNC path \\HOST\path
275
282
    if win32_path.startswith('//'):
276
283
        return 'file:' + escape(win32_path)
277
 
    return 'file:///' + win32_path[0].upper() + ':' + escape(win32_path[2:])
 
284
    return ('file:///' + str(win32_path[0].upper()) + ':' +
 
285
        escape(win32_path[2:]))
278
286
 
279
287
 
280
288
local_path_to_url = _posix_local_path_to_url
295
303
 
296
304
def _unescape_safe_chars(matchobj):
297
305
    """re.sub callback to convert hex-escapes to plain characters (if safe).
298
 
    
 
306
 
299
307
    e.g. '%7E' will be converted to '~'.
300
308
    """
301
309
    hex_digits = matchobj.group(0)[1:]
308
316
 
309
317
def normalize_url(url):
310
318
    """Make sure that a path string is in fully normalized URL form.
311
 
    
 
319
 
312
320
    This handles URLs which have unicode characters, spaces,
313
321
    special characters, etc.
314
322
 
360
368
    dummy, base_first_slash = _find_scheme_and_separator(base)
361
369
    if base_first_slash is None:
362
370
        return other
363
 
    
 
371
 
364
372
    dummy, other_first_slash = _find_scheme_and_separator(other)
365
373
    if other_first_slash is None:
366
374
        return other
370
378
    other_scheme = other[:other_first_slash]
371
379
    if base_scheme != other_scheme:
372
380
        return other
 
381
    elif sys.platform == 'win32' and base_scheme == 'file://':
 
382
        base_drive = base[base_first_slash+1:base_first_slash+3]
 
383
        other_drive = other[other_first_slash+1:other_first_slash+3]
 
384
        if base_drive != other_drive:
 
385
            return other
373
386
 
374
387
    base_path = base[base_first_slash+1:]
375
388
    other_path = other[other_first_slash+1:]
403
416
    # Strip off the drive letter
404
417
    # path is currently /C:/foo
405
418
    if len(path) < 3 or path[2] not in ':|' or path[3] != '/':
406
 
        raise errors.InvalidURL(url_base + path, 
 
419
        raise errors.InvalidURL(url_base + path,
407
420
            'win32 file:/// paths need a drive letter')
408
421
    url_base += path[0:3] # file:// + /C:
409
422
    path = path[3:] # /foo
417
430
    :param exclude_trailing_slash: Strip off a final '/' if it is part
418
431
        of the path (but not if it is part of the protocol specification)
419
432
 
420
 
    :return: (parent_url, child_dir).  child_dir may be the empty string if we're at 
 
433
    :return: (parent_url, child_dir).  child_dir may be the empty string if we're at
421
434
        the root.
422
435
    """
423
436
    scheme_loc, first_path_slash = _find_scheme_and_separator(url)
527
540
# These are characters that if escaped, should stay that way
528
541
_no_decode_chars = ';/?:@&=+$,#'
529
542
_no_decode_ords = [ord(c) for c in _no_decode_chars]
530
 
_no_decode_hex = (['%02x' % o for o in _no_decode_ords] 
 
543
_no_decode_hex = (['%02x' % o for o in _no_decode_ords]
531
544
                + ['%02X' % o for o in _no_decode_ords])
532
545
_hex_display_map = dict(([('%02x' % o, chr(o)) for o in range(256)]
533
546
                    + [('%02X' % o, chr(o)) for o in range(256)]))
559
572
    This will turn file:// urls into local paths, and try to decode
560
573
    any portions of a http:// style url that it can.
561
574
 
562
 
    Any sections of the URL which can't be represented in the encoding or 
 
575
    Any sections of the URL which can't be represented in the encoding or
563
576
    need to stay as escapes are left alone.
564
577
 
565
578
    :param url: A 7-bit ASCII URL
566
579
    :param encoding: The final output encoding
567
580
 
568
 
    :return: A unicode string which can be safely encoded into the 
 
581
    :return: A unicode string which can be safely encoded into the
569
582
         specified encoding.
570
583
    """
571
 
    assert encoding is not None, 'you cannot specify None for the display encoding.'
 
584
    if encoding is None:
 
585
        raise ValueError('you cannot specify None for the display encoding')
572
586
    if url.startswith('file://'):
573
587
        try:
574
588
            path = local_path_from_url(url)
629
643
            return from_location[sep+1:]
630
644
        else:
631
645
            return from_location
 
646
 
 
647
 
 
648
def _is_absolute(url):
 
649
    return (osutils.pathjoin('/foo', url) == url)
 
650
 
 
651
 
 
652
def rebase_url(url, old_base, new_base):
 
653
    """Convert a relative path from an old base URL to a new base URL.
 
654
 
 
655
    The result will be a relative path.
 
656
    Absolute paths and full URLs are returned unaltered.
 
657
    """
 
658
    scheme, separator = _find_scheme_and_separator(url)
 
659
    if scheme is not None:
 
660
        return url
 
661
    if _is_absolute(url):
 
662
        return url
 
663
    old_parsed = urlparse.urlparse(old_base)
 
664
    new_parsed = urlparse.urlparse(new_base)
 
665
    if (old_parsed[:2]) != (new_parsed[:2]):
 
666
        raise errors.InvalidRebaseURLs(old_base, new_base)
 
667
    return determine_relative_path(new_parsed[2],
 
668
                                   join(old_parsed[2], url))
 
669
 
 
670
 
 
671
def determine_relative_path(from_path, to_path):
 
672
    """Determine a relative path from from_path to to_path."""
 
673
    from_segments = osutils.splitpath(from_path)
 
674
    to_segments = osutils.splitpath(to_path)
 
675
    count = -1
 
676
    for count, (from_element, to_element) in enumerate(zip(from_segments,
 
677
                                                       to_segments)):
 
678
        if from_element != to_element:
 
679
            break
 
680
    else:
 
681
        count += 1
 
682
    unique_from = from_segments[count:]
 
683
    unique_to = to_segments[count:]
 
684
    segments = (['..'] * len(unique_from) + unique_to)
 
685
    if len(segments) == 0:
 
686
        return '.'
 
687
    return osutils.pathjoin(*segments)
 
688
 
 
689
 
 
690
 
 
691
def parse_url(url):
 
692
    """Extract the server address, the credentials and the path from the url.
 
693
 
 
694
    user, password, host and path should be quoted if they contain reserved
 
695
    chars.
 
696
 
 
697
    :param url: an quoted url
 
698
 
 
699
    :return: (scheme, user, password, host, port, path) tuple, all fields
 
700
        are unquoted.
 
701
    """
 
702
    if isinstance(url, unicode):
 
703
        raise errors.InvalidURL('should be ascii:\n%r' % url)
 
704
    url = url.encode('utf-8')
 
705
    (scheme, netloc, path, params,
 
706
     query, fragment) = urlparse.urlparse(url, allow_fragments=False)
 
707
    user = password = host = port = None
 
708
    if '@' in netloc:
 
709
        user, host = netloc.rsplit('@', 1)
 
710
        if ':' in user:
 
711
            user, password = user.split(':', 1)
 
712
            password = urllib.unquote(password)
 
713
        user = urllib.unquote(user)
 
714
    else:
 
715
        host = netloc
 
716
 
 
717
    if ':' in host and not (host[0] == '[' and host[-1] == ']'): #there *is* port
 
718
        host, port = host.rsplit(':',1)
 
719
        try:
 
720
            port = int(port)
 
721
        except ValueError:
 
722
            raise errors.InvalidURL('invalid port number %s in url:\n%s' %
 
723
                                    (port, url))
 
724
    if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
 
725
        host = host[1:-1]
 
726
 
 
727
    host = urllib.unquote(host)
 
728
    path = urllib.unquote(path)
 
729
 
 
730
    return (scheme, user, password, host, port, path)