441
440
:param url: A relative or absolute URL
442
441
:return: (url, subsegments)
444
# GZ 2011-11-18: Dodgy removing the terminal slash like this, function
445
# operates on urls not url+segments, and Transport classes
446
# should not be blindly adding slashes in the first place.
447
lurl = strip_trailing_slash(url)
448
# Segments begin at first comma after last forward slash, if one exists
449
segment_start = lurl.find(",", lurl.rfind("/")+1)
450
if segment_start == -1:
443
(parent_url, child_dir) = split(url)
444
subsegments = child_dir.split(",")
445
if len(subsegments) == 1:
452
return (lurl[:segment_start], lurl[segment_start+1:].split(","))
447
return (join(parent_url, subsegments[0]), subsegments[1:])
455
450
def split_segment_parameters(url):
735
730
return osutils.pathjoin(*segments)
741
def __init__(self, scheme, quoted_user, quoted_password, quoted_host,
744
self.quoted_host = quoted_host
745
self.host = urllib.unquote(self.quoted_host)
746
self.quoted_user = quoted_user
747
if self.quoted_user is not None:
748
self.user = urllib.unquote(self.quoted_user)
751
self.quoted_password = quoted_password
752
if self.quoted_password is not None:
753
self.password = urllib.unquote(self.quoted_password)
757
self.quoted_path = _url_hex_escapes_re.sub(_unescape_safe_chars, quoted_path)
758
self.path = urllib.unquote(self.quoted_path)
760
def __eq__(self, other):
761
return (isinstance(other, self.__class__) and
762
self.scheme == other.scheme and
763
self.host == other.host and
764
self.user == other.user and
765
self.password == other.password and
766
self.path == other.path)
769
return "<%s(%r, %r, %r, %r, %r, %r)>" % (
770
self.__class__.__name__,
771
self.scheme, self.quoted_user, self.quoted_password,
772
self.quoted_host, self.port, self.quoted_path)
775
def from_string(cls, url):
776
"""Create a URL object from a string.
778
:param url: URL as bytestring
780
if isinstance(url, unicode):
781
raise errors.InvalidURL('should be ascii:\n%r' % url)
782
url = url.encode('utf-8')
783
(scheme, netloc, path, params,
784
query, fragment) = urlparse.urlparse(url, allow_fragments=False)
785
user = password = host = port = None
787
user, host = netloc.rsplit('@', 1)
789
user, password = user.split(':', 1)
793
if ':' in host and not (host[0] == '[' and host[-1] == ']'):
795
host, port = host.rsplit(':',1)
799
raise errors.InvalidURL('invalid port number %s in url:\n%s' %
801
if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
804
return cls(scheme, user, password, host, port, path)
807
netloc = self.quoted_host
809
netloc = "[%s]" % netloc
810
if self.quoted_user is not None:
811
# Note that we don't put the password back even if we
812
# have one so that it doesn't get accidentally
814
netloc = '%s@%s' % (self.quoted_user, netloc)
815
if self.port is not None:
816
netloc = '%s:%d' % (netloc, self.port)
817
return urlparse.urlunparse(
818
(self.scheme, netloc, self.quoted_path, None, None, None))
821
def _combine_paths(base_path, relpath):
822
"""Transform a Transport-relative path to a remote absolute path.
824
This does not handle substitution of ~ but does handle '..' and '.'
829
t._combine_paths('/home/sarah', 'project/foo')
830
=> '/home/sarah/project/foo'
831
t._combine_paths('/home/sarah', '../../etc')
833
t._combine_paths('/home/sarah', '/etc')
836
:param base_path: base path
837
:param relpath: relative url string for relative part of remote path.
838
:return: urlencoded string for final path.
840
if not isinstance(relpath, str):
841
raise errors.InvalidURL(relpath)
842
relpath = _url_hex_escapes_re.sub(_unescape_safe_chars, relpath)
843
if relpath.startswith('/'):
846
base_parts = base_path.split('/')
847
if len(base_parts) > 0 and base_parts[-1] == '':
848
base_parts = base_parts[:-1]
849
for p in relpath.split('/'):
851
if len(base_parts) == 0:
852
# In most filesystems, a request for the parent
853
# of root, just returns root.
860
path = '/'.join(base_parts)
861
if not path.startswith('/'):
865
def clone(self, offset=None):
866
"""Return a new URL for a path relative to this URL.
868
:param offset: A relative path, already urlencoded
869
:return: `URL` instance
871
if offset is not None:
872
relative = unescape(offset).encode('utf-8')
873
path = self._combine_paths(self.path, relative)
874
path = urllib.quote(path, safe="/~")
876
path = self.quoted_path
877
return self.__class__(self.scheme, self.quoted_user,
878
self.quoted_password, self.quoted_host, self.port,
882
734
def parse_url(url):
883
735
"""Extract the server address, the credentials and the path from the url.
888
740
:param url: an quoted url
889
742
:return: (scheme, user, password, host, port, path) tuple, all fields
892
parsed_url = URL.from_string(url)
893
return (parsed_url.scheme, parsed_url.user, parsed_url.password,
894
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
752
user, host = netloc.rsplit('@', 1)
754
user, password = user.split(':', 1)
755
password = urllib.unquote(password)
756
user = urllib.unquote(user)
760
if ':' in host and not (host[0] == '[' and host[-1] == ']'): #there *is* port
761
host, port = host.rsplit(':',1)
765
raise errors.InvalidURL('invalid port number %s in url:\n%s' %
767
if host != "" and host[0] == '[' and host[-1] == ']': #IPv6
770
host = urllib.unquote(host)
771
path = urllib.unquote(path)
773
return (scheme, user, password, host, port, path)