388
429
raise NotImplementedError(self.external_url)
431
def get_segment_parameters(self):
432
"""Return the segment parameters for the top segment of the URL.
434
return self._segment_parameters
436
def set_segment_parameter(self, name, value):
437
"""Set a segment parameter.
439
:param name: Segment parameter name (urlencoded string)
440
:param value: Segment parameter value (urlencoded string)
444
del self._segment_parameters[name]
448
self._segment_parameters[name] = value
449
self.base = urlutils.join_segment_parameters(
450
self._raw_base, self._segment_parameters)
390
452
def _pump(self, from_file, to_file):
391
453
"""Most children will need to copy from one file-like
392
454
object or string to another one.
458
520
raise NotImplementedError(self.abspath)
460
def _combine_paths(self, base_path, relpath):
461
"""Transform a Transport-relative path to a remote absolute path.
463
This does not handle substitution of ~ but does handle '..' and '.'
468
t._combine_paths('/home/sarah', 'project/foo')
469
=> '/home/sarah/project/foo'
470
t._combine_paths('/home/sarah', '../../etc')
472
t._combine_paths('/home/sarah', '/etc')
475
:param base_path: urlencoded path for the transport root; typically a
476
URL but need not contain scheme/host/etc.
477
:param relpath: relative url string for relative part of remote path.
478
:return: urlencoded string for final path.
480
if not isinstance(relpath, str):
481
raise errors.InvalidURL(relpath)
482
if relpath.startswith('/'):
485
base_parts = base_path.split('/')
486
if len(base_parts) > 0 and base_parts[-1] == '':
487
base_parts = base_parts[:-1]
488
for p in relpath.split('/'):
490
if len(base_parts) == 0:
491
# In most filesystems, a request for the parent
492
# of root, just returns root.
499
path = '/'.join(base_parts)
500
if not path.startswith('/'):
504
522
def recommended_page_size(self):
505
523
"""Return the recommended page size for this transport.
855
873
yield self.get(relpath)
858
def put_bytes(self, relpath, bytes, mode=None):
876
def put_bytes(self, relpath, raw_bytes, mode=None):
859
877
"""Atomically put the supplied bytes into the given location.
861
879
:param relpath: The location to put the contents, relative to the
863
:param bytes: A bytestring of data.
881
:param raw_bytes: A bytestring of data.
864
882
:param mode: Create the file with the given mode.
867
if not isinstance(bytes, str):
868
raise AssertionError(
869
'bytes must be a plain string, not %s' % type(bytes))
870
return self.put_file(relpath, StringIO(bytes), mode=mode)
885
if not isinstance(raw_bytes, str):
887
'raw_bytes must be a plain string, not %s' % type(raw_bytes))
888
return self.put_file(relpath, StringIO(raw_bytes), mode=mode)
872
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
890
def put_bytes_non_atomic(self, relpath, raw_bytes, mode=None,
873
891
create_parent_dir=False,
875
893
"""Copy the string into the target location.
1342
1360
if not base.endswith('/'):
1345
self._user, self._password,
1346
self._host, self._port,
1347
self._path) = self._split_url(base)
1362
self._parsed_url = self._split_url(base)
1348
1363
if _from_transport is not None:
1349
1364
# Copy the password as it does not appear in base and will be lost
1350
1365
# otherwise. It can appear in the _split_url above if the user
1351
1366
# provided it on the command line. Otherwise, daughter classes will
1352
1367
# prompt the user for one when appropriate.
1353
self._password = _from_transport._password
1368
self._parsed_url.password = _from_transport._parsed_url.password
1369
self._parsed_url.quoted_password = (
1370
_from_transport._parsed_url.quoted_password)
1355
base = self._unsplit_url(self._scheme,
1356
self._user, self._password,
1357
self._host, self._port,
1372
base = str(self._parsed_url)
1360
1374
super(ConnectedTransport, self).__init__(base)
1361
1375
if _from_transport is None:
1378
1416
def _split_url(url):
1379
return urlutils.parse_url(url)
1417
return urlutils.URL.from_string(url)
1382
1420
def _unsplit_url(scheme, user, password, host, port, path):
1384
Build the full URL for the given already URL encoded path.
1421
"""Build the full URL for the given already URL encoded path.
1386
1423
user, password, host and path will be quoted if they contain reserved
1389
1426
:param scheme: protocol
1391
1427
:param user: login
1393
1428
:param password: associated password
1395
1429
:param host: the server address
1397
1430
:param port: the associated port
1399
1431
:param path: the absolute path on the server
1401
1433
:return: The corresponding URL.
1403
netloc = urllib.quote(host)
1435
netloc = urlutils.quote(host)
1404
1436
if user is not None:
1405
1437
# Note that we don't put the password back even if we
1406
1438
# have one so that it doesn't get accidentally
1408
netloc = '%s@%s' % (urllib.quote(user), netloc)
1440
netloc = '%s@%s' % (urlutils.quote(user), netloc)
1409
1441
if port is not None:
1410
1442
netloc = '%s:%d' % (netloc, port)
1411
1443
path = urlutils.escape(path)
1414
1446
def relpath(self, abspath):
1415
1447
"""Return the local path portion from a given absolute path"""
1416
scheme, user, password, host, port, path = self._split_url(abspath)
1448
parsed_url = self._split_url(abspath)
1418
if (scheme != self._scheme):
1450
if parsed_url.scheme != self._parsed_url.scheme:
1419
1451
error.append('scheme mismatch')
1420
if (user != self._user):
1452
if parsed_url.user != self._parsed_url.user:
1421
1453
error.append('user name mismatch')
1422
if (host != self._host):
1454
if parsed_url.host != self._parsed_url.host:
1423
1455
error.append('host mismatch')
1424
if (port != self._port):
1456
if parsed_url.port != self._parsed_url.port:
1425
1457
error.append('port mismatch')
1426
if not (path == self._path[:-1] or path.startswith(self._path)):
1458
if (not (parsed_url.path == self._parsed_url.path[:-1] or
1459
parsed_url.path.startswith(self._parsed_url.path))):
1427
1460
error.append('path mismatch')
1429
1462
extra = ', '.join(error)
1430
1463
raise errors.PathNotChild(abspath, self.base, extra=extra)
1431
pl = len(self._path)
1432
return path[pl:].strip('/')
1464
pl = len(self._parsed_url.path)
1465
return parsed_url.path[pl:].strip('/')
1434
1467
def abspath(self, relpath):
1435
1468
"""Return the full url to the given relative path.
1557
1586
raise NotImplementedError(self.disconnect)
1560
def get_transport(base, possible_transports=None):
1561
"""Open a transport to access a URL or directory.
1563
:param base: either a URL or a directory name.
1565
:param transports: optional reusable transports list. If not None, created
1566
transports will be added to the list.
1568
:return: A new transport optionally sharing its connection with one of
1569
possible_transports.
1589
def location_to_url(location):
1590
"""Determine a fully qualified URL from a location string.
1592
This will try to interpret location as both a URL and a directory path. It
1593
will also lookup the location in directories.
1595
:param location: Unicode or byte string object with a location
1596
:raise InvalidURL: If the location is already a URL, but not valid.
1597
:return: Byte string with resulting URL
1599
if not isinstance(location, basestring):
1600
raise AssertionError("location not a byte or unicode string")
1574
1601
from bzrlib.directory_service import directories
1575
base = directories.dereference(base)
1577
def convert_path_to_url(base, error_str):
1578
if urlutils.is_url(base):
1579
# This looks like a URL, but we weren't able to
1580
# instantiate it as such raise an appropriate error
1581
# FIXME: we have a 'error_str' unused and we use last_err below
1582
raise errors.UnsupportedProtocol(base, last_err)
1583
# This doesn't look like a protocol, consider it a local path
1584
new_base = urlutils.local_path_to_url(base)
1585
# mutter('converting os path %r => url %s', base, new_base)
1602
location = directories.dereference(location)
1588
1604
# Catch any URLs which are passing Unicode rather than ASCII
1590
base = base.encode('ascii')
1606
location = location.encode('ascii')
1591
1607
except UnicodeError:
1592
# Only local paths can be Unicode
1593
base = convert_path_to_url(base,
1594
'URLs must be properly escaped (protocol: %s)')
1608
if urlutils.is_url(location):
1609
raise errors.InvalidURL(path=location,
1610
extra='URLs must be properly escaped')
1611
location = urlutils.local_path_to_url(location)
1613
if location.startswith("file:") and not location.startswith("file://"):
1614
return urlutils.join(urlutils.local_path_to_url("."), location[5:])
1616
if not urlutils.is_url(location):
1617
return urlutils.local_path_to_url(location)
1622
def get_transport_from_path(path, possible_transports=None):
1623
"""Open a transport for a local path.
1625
:param path: Local path as byte or unicode string
1626
:return: Transport object for path
1628
return get_transport_from_url(urlutils.local_path_to_url(path),
1629
possible_transports)
1632
def get_transport_from_url(url, possible_transports=None):
1633
"""Open a transport to access a URL.
1636
:param transports: optional reusable transports list. If not None, created
1637
transports will be added to the list.
1639
:return: A new transport optionally sharing its connection with one of
1640
possible_transports.
1596
1642
transport = None
1597
1643
if possible_transports is not None:
1598
1644
for t in possible_transports:
1599
t_same_connection = t._reuse_for(base)
1645
t_same_connection = t._reuse_for(url)
1600
1646
if t_same_connection is not None:
1601
1647
# Add only new transports
1602
1648
if t_same_connection not in possible_transports:
1603
1649
possible_transports.append(t_same_connection)
1604
1650
return t_same_connection
1606
1653
for proto, factory_list in transport_list_registry.items():
1607
if proto is not None and base.startswith(proto):
1608
transport, last_err = _try_transport_factories(base, factory_list)
1654
if proto is not None and url.startswith(proto):
1655
transport, last_err = _try_transport_factories(url, factory_list)
1610
1657
if possible_transports is not None:
1611
1658
if transport in possible_transports:
1612
1659
raise AssertionError()
1613
1660
possible_transports.append(transport)
1614
1661
return transport
1616
# We tried all the different protocols, now try one last time
1617
# as a local protocol
1618
base = convert_path_to_url(base, 'Unsupported protocol: %s')
1620
# The default handler is the filesystem handler, stored as protocol None
1621
factory_list = transport_list_registry.get(None)
1622
transport, last_err = _try_transport_factories(base, factory_list)
1662
if not urlutils.is_url(url):
1663
raise errors.InvalidURL(path=url)
1664
raise errors.UnsupportedProtocol(url, last_err)
1667
def get_transport(base, possible_transports=None):
1668
"""Open a transport to access a URL or directory.
1670
:param base: either a URL or a directory name.
1672
:param transports: optional reusable transports list. If not None, created
1673
transports will be added to the list.
1675
:return: A new transport optionally sharing its connection with one of
1676
possible_transports.
1680
return get_transport_from_url(location_to_url(base), possible_transports)
1627
1683
def _try_transport_factories(base, factory_list):
1728
1783
help="Read-only access of branches exported on the web.")
1729
1784
register_transport_proto('https://',
1730
1785
help="Read-only access of branches exported on the web using SSL.")
1731
# The default http implementation is urllib, but https is pycurl if available
1786
# The default http implementation is urllib
1732
1787
register_lazy_transport('http://', 'bzrlib.transport.http._pycurl',
1733
1788
'PyCurlTransport')
1734
1789
register_lazy_transport('http://', 'bzrlib.transport.http._urllib',
1735
1790
'HttpTransport_urllib')
1791
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1736
1793
register_lazy_transport('https://', 'bzrlib.transport.http._urllib',
1737
1794
'HttpTransport_urllib')
1738
register_lazy_transport('https://', 'bzrlib.transport.http._pycurl',
1741
1796
register_transport_proto('ftp://', help="Access using passive FTP.")
1742
1797
register_lazy_transport('ftp://', 'bzrlib.transport.ftp', 'FtpTransport')