798
799
return cls(scheme, user, password, host, port, path)
802
netloc = self.quoted_host
804
netloc = "[%s]" % netloc
805
if self.quoted_user is not None:
806
# Note that we don't put the password back even if we
807
# have one so that it doesn't get accidentally
809
netloc = '%s@%s' % (self.quoted_user, netloc)
810
if self.port is not None:
811
netloc = '%s:%d' % (netloc, self.port)
812
return urlparse.urlunparse(
813
(self.scheme, netloc, self.quoted_path, None, None, None))
816
def _combine_paths(base_path, relpath):
817
"""Transform a Transport-relative path to a remote absolute path.
819
This does not handle substitution of ~ but does handle '..' and '.'
824
t._combine_paths('/home/sarah', 'project/foo')
825
=> '/home/sarah/project/foo'
826
t._combine_paths('/home/sarah', '../../etc')
828
t._combine_paths('/home/sarah', '/etc')
831
:param base_path: base path
832
:param relpath: relative url string for relative part of remote path.
833
:return: urlencoded string for final path.
835
if not isinstance(relpath, str):
836
raise errors.InvalidURL(relpath)
837
if relpath.startswith('/'):
840
base_parts = base_path.split('/')
841
if len(base_parts) > 0 and base_parts[-1] == '':
842
base_parts = base_parts[:-1]
843
for p in relpath.split('/'):
845
if len(base_parts) == 0:
846
# In most filesystems, a request for the parent
847
# of root, just returns root.
854
path = '/'.join(base_parts)
855
if not path.startswith('/'):
859
def clone(self, offset=None):
860
"""Return a new URL for a path relative to this URL.
862
:param offset: A relative path, already urlencoded
863
:return: `URL` instance
865
if offset is not None:
866
relative = unescape(offset).encode('utf-8')
867
path = self._combine_paths(self.path, relative)
868
path = urllib.quote(path)
870
path = self.quoted_path
871
return self.__class__(self.scheme, self.quoted_user,
872
self.quoted_password, self.quoted_host, self.port,
801
876
def parse_url(url):
802
877
"""Extract the server address, the credentials and the path from the url.