~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/urlutils.py

  • Committer: Vincent Ladeuil
  • Date: 2011-06-15 11:36:05 UTC
  • mto: This revision was merged to the branch mainline in revision 5975.
  • Revision ID: v.ladeuil+lp@free.fr-20110615113605-p7zyyfry9wy1hquc
Make ContentConflict resolution more robust

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
 
23
23
from bzrlib.lazy_import import lazy_import
24
24
lazy_import(globals(), """
25
 
from posixpath import split as _posix_split
 
25
from posixpath import split as _posix_split, normpath as _posix_normpath
26
26
import urllib
27
27
import urlparse
28
28
 
181
181
# jam 20060502 Sorted to 'l' because the final target is 'local_path_from_url'
182
182
def _posix_local_path_from_url(url):
183
183
    """Convert a url like file:///path/to/foo into /path/to/foo"""
184
 
    url = split_segment_parameters_raw(url)[0]
185
184
    file_localhost_prefix = 'file://localhost/'
186
185
    if url.startswith(file_localhost_prefix):
187
186
        path = url[len(file_localhost_prefix) - 1:]
201
200
    """
202
201
    # importing directly from posixpath allows us to test this
203
202
    # on non-posix platforms
204
 
    return 'file://' + escape(osutils._posix_abspath(path))
 
203
    return 'file://' + escape(_posix_normpath(
 
204
        osutils._posix_abspath(path)))
205
205
 
206
206
 
207
207
def _win32_local_path_from_url(url):
209
209
    if not url.startswith('file://'):
210
210
        raise errors.InvalidURL(url, 'local urls must start with file:///, '
211
211
                                     'UNC path urls must start with file://')
212
 
    url = split_segment_parameters_raw(url)[0]
213
212
    # We strip off all 3 slashes
214
213
    win32_url = url[len('file:'):]
215
214
    # check for UNC path: //HOST/path
387
386
    """On win32 the drive letter needs to be added to the url base."""
388
387
    # Strip off the drive letter
389
388
    # path is currently /C:/foo
390
 
    if len(path) < 4 or path[2] not in ':|' or path[3] != '/':
 
389
    if len(path) < 3 or path[2] not in ':|' or path[3] != '/':
391
390
        raise errors.InvalidURL(url_base + path,
392
391
            'win32 file:/// paths need a drive letter')
393
392
    url_base += path[0:3] # file:// + /C:
731
730
    return osutils.pathjoin(*segments)
732
731
 
733
732
 
734
 
class URL(object):
735
 
    """Parsed URL."""
736
 
 
737
 
    def __init__(self, scheme, quoted_user, quoted_password, quoted_host,
738
 
            port, quoted_path):
739
 
        self.scheme = scheme
740
 
        self.quoted_host = quoted_host
741
 
        self.host = urllib.unquote(self.quoted_host)
742
 
        self.quoted_user = quoted_user
743
 
        if self.quoted_user is not None:
744
 
            self.user = urllib.unquote(self.quoted_user)
745
 
        else:
746
 
            self.user = None
747
 
        self.quoted_password = quoted_password
748
 
        if self.quoted_password is not None:
749
 
            self.password = urllib.unquote(self.quoted_password)
750
 
        else:
751
 
            self.password = None
752
 
        self.port = port
753
 
        self.quoted_path = quoted_path
754
 
        self.path = urllib.unquote(self.quoted_path)
755
 
 
756
 
    def __eq__(self, other):
757
 
        return (isinstance(other, self.__class__) and
758
 
                self.scheme == other.scheme and
759
 
                self.host == other.host and
760
 
                self.user == other.user and
761
 
                self.password == other.password and
762
 
                self.path == other.path)
763
 
 
764
 
    def __repr__(self):
765
 
        return "<%s(%r, %r, %r, %r, %r, %r)>" % (
766
 
            self.__class__.__name__,
767
 
            self.scheme, self.quoted_user, self.quoted_password,
768
 
            self.quoted_host, self.port, self.quoted_path)
769
 
 
770
 
    @classmethod
771
 
    def from_string(cls, url):
772
 
        """Create a URL object from a string.
773
 
 
774
 
        :param url: URL as bytestring
775
 
        """
776
 
        if isinstance(url, unicode):
777
 
            raise errors.InvalidURL('should be ascii:\n%r' % url)
778
 
        url = url.encode('utf-8')
779
 
        (scheme, netloc, path, params,
780
 
         query, fragment) = urlparse.urlparse(url, allow_fragments=False)
781
 
        user = password = host = port = None
782
 
        if '@' in netloc:
783
 
            user, host = netloc.rsplit('@', 1)
784
 
            if ':' in user:
785
 
                user, password = user.split(':', 1)
786
 
        else:
787
 
            host = netloc
788
 
 
789
 
        if ':' in host and not (host[0] == '[' and host[-1] == ']'):
790
 
            # there *is* port
791
 
            host, port = host.rsplit(':',1)
792
 
            try:
793
 
                port = int(port)
794
 
            except ValueError:
795
 
                raise errors.InvalidURL('invalid port number %s in url:\n%s' %
796
 
                                        (port, url))
797
 
        if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
798
 
            host = host[1:-1]
799
 
 
800
 
        return cls(scheme, user, password, host, port, path)
801
 
 
802
 
    def __str__(self):
803
 
        netloc = self.quoted_host
804
 
        if ":" in netloc:
805
 
            netloc = "[%s]" % netloc
806
 
        if self.quoted_user is not None:
807
 
            # Note that we don't put the password back even if we
808
 
            # have one so that it doesn't get accidentally
809
 
            # exposed.
810
 
            netloc = '%s@%s' % (self.quoted_user, netloc)
811
 
        if self.port is not None:
812
 
            netloc = '%s:%d' % (netloc, self.port)
813
 
        return urlparse.urlunparse(
814
 
            (self.scheme, netloc, self.quoted_path, None, None, None))
815
 
 
816
 
    @staticmethod
817
 
    def _combine_paths(base_path, relpath):
818
 
        """Transform a Transport-relative path to a remote absolute path.
819
 
 
820
 
        This does not handle substitution of ~ but does handle '..' and '.'
821
 
        components.
822
 
 
823
 
        Examples::
824
 
 
825
 
            t._combine_paths('/home/sarah', 'project/foo')
826
 
                => '/home/sarah/project/foo'
827
 
            t._combine_paths('/home/sarah', '../../etc')
828
 
                => '/etc'
829
 
            t._combine_paths('/home/sarah', '/etc')
830
 
                => '/etc'
831
 
 
832
 
        :param base_path: base path
833
 
        :param relpath: relative url string for relative part of remote path.
834
 
        :return: urlencoded string for final path.
835
 
        """
836
 
        if not isinstance(relpath, str):
837
 
            raise errors.InvalidURL(relpath)
838
 
        if relpath.startswith('/'):
839
 
            base_parts = []
840
 
        else:
841
 
            base_parts = base_path.split('/')
842
 
        if len(base_parts) > 0 and base_parts[-1] == '':
843
 
            base_parts = base_parts[:-1]
844
 
        for p in relpath.split('/'):
845
 
            if p == '..':
846
 
                if len(base_parts) == 0:
847
 
                    # In most filesystems, a request for the parent
848
 
                    # of root, just returns root.
849
 
                    continue
850
 
                base_parts.pop()
851
 
            elif p == '.':
852
 
                continue # No-op
853
 
            elif p != '':
854
 
                base_parts.append(p)
855
 
        path = '/'.join(base_parts)
856
 
        if not path.startswith('/'):
857
 
            path = '/' + path
858
 
        return path
859
 
 
860
 
    def clone(self, offset=None):
861
 
        """Return a new URL for a path relative to this URL.
862
 
 
863
 
        :param offset: A relative path, already urlencoded
864
 
        :return: `URL` instance
865
 
        """
866
 
        if offset is not None:
867
 
            relative = unescape(offset).encode('utf-8')
868
 
            path = self._combine_paths(self.path, relative)
869
 
            path = urllib.quote(path)
870
 
        else:
871
 
            path = self.quoted_path
872
 
        return self.__class__(self.scheme, self.quoted_user,
873
 
                self.quoted_password, self.quoted_host, self.port,
874
 
                path)
875
 
 
876
733
 
877
734
def parse_url(url):
878
735
    """Extract the server address, the credentials and the path from the url.
881
738
    chars.
882
739
 
883
740
    :param url: an quoted url
 
741
 
884
742
    :return: (scheme, user, password, host, port, path) tuple, all fields
885
743
        are unquoted.
886
744
    """
887
 
    parsed_url = URL.from_string(url)
888
 
    return (parsed_url.scheme, parsed_url.user, parsed_url.password,
889
 
        parsed_url.host, parsed_url.port, parsed_url.path)
 
745
    if isinstance(url, unicode):
 
746
        raise errors.InvalidURL('should be ascii:\n%r' % url)
 
747
    url = url.encode('utf-8')
 
748
    (scheme, netloc, path, params,
 
749
     query, fragment) = urlparse.urlparse(url, allow_fragments=False)
 
750
    user = password = host = port = None
 
751
    if '@' in netloc:
 
752
        user, host = netloc.rsplit('@', 1)
 
753
        if ':' in user:
 
754
            user, password = user.split(':', 1)
 
755
            password = urllib.unquote(password)
 
756
        user = urllib.unquote(user)
 
757
    else:
 
758
        host = netloc
 
759
 
 
760
    if ':' in host and not (host[0] == '[' and host[-1] == ']'): #there *is* port
 
761
        host, port = host.rsplit(':',1)
 
762
        try:
 
763
            port = int(port)
 
764
        except ValueError:
 
765
            raise errors.InvalidURL('invalid port number %s in url:\n%s' %
 
766
                                    (port, url))
 
767
    if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
 
768
        host = host[1:-1]
 
769
 
 
770
    host = urllib.unquote(host)
 
771
    path = urllib.unquote(path)
 
772
 
 
773
    return (scheme, user, password, host, port, path)