1089
1106
# several questions about the transport.
1093
# jam 20060426 For compatibility we copy the functions here
1094
# TODO: The should be marked as deprecated
1095
urlescape = urlutils.escape
1096
urlunescape = urlutils.unescape
1097
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<path>.*)$')
1100
def get_transport(base):
1109
def _reuse_for(self, other_base):
1110
# This is really needed for ConnectedTransport only, but it's easier to
1111
# have Transport refuses to be reused than testing that the reuse
1112
# should be asked to ConnectedTransport only.
1116
class _SharedConnection(object):
1117
"""A connection shared between several transports."""
1119
def __init__(self, connection=None, credentials=None):
1122
:param connection: An opaque object specific to each transport.
1124
:param credentials: An opaque object containing the credentials used to
1125
create the connection.
1127
self.connection = connection
1128
self.credentials = credentials
1131
class ConnectedTransport(Transport):
1132
"""A transport connected to a remote server.
1134
This class provide the basis to implement transports that need to connect
1137
Host and credentials are available as private attributes, cloning preserves
1138
them and share the underlying, protocol specific, connection.
1141
def __init__(self, base, _from_transport=None):
1144
The caller should ensure that _from_transport points at the same host
1147
:param base: transport root URL
1149
:param _from_transport: optional transport to build from. The built
1150
transport will share the connection with this transport.
1152
if not base.endswith('/'):
1155
self._user, self._password,
1156
self._host, self._port,
1157
self._path) = self._split_url(base)
1158
if _from_transport is not None:
1159
# Copy the password as it does not appear in base and will be lost
1160
# otherwise. It can appear in the _split_url above if the user
1161
# provided it on the command line. Otherwise, daughter classes will
1162
# prompt the user for one when appropriate.
1163
self._password = _from_transport._password
1165
base = self._unsplit_url(self._scheme,
1166
self._user, self._password,
1167
self._host, self._port,
1170
super(ConnectedTransport, self).__init__(base)
1171
if _from_transport is None:
1172
self._shared_connection = _SharedConnection()
1174
self._shared_connection = _from_transport._shared_connection
1176
def clone(self, offset=None):
1177
"""Return a new transport with root at self.base + offset
1179
We leave the daughter classes take advantage of the hint
1180
that it's a cloning not a raw creation.
1183
return self.__class__(self.base, _from_transport=self)
1185
return self.__class__(self.abspath(offset), _from_transport=self)
1188
def _split_url(url):
1190
Extract the server address, the credentials and the path from the url.
1192
user, password, host and path should be quoted if they contain reserved
1195
:param url: an quoted url
1197
:return: (scheme, user, password, host, port, path) tuple, all fields
1200
if isinstance(url, unicode):
1201
raise errors.InvalidURL('should be ascii:\n%r' % url)
1202
url = url.encode('utf-8')
1203
(scheme, netloc, path, params,
1204
query, fragment) = urlparse.urlparse(url, allow_fragments=False)
1205
user = password = host = port = None
1207
user, host = netloc.split('@', 1)
1209
user, password = user.split(':', 1)
1210
password = urllib.unquote(password)
1211
user = urllib.unquote(user)
1216
host, port = host.rsplit(':', 1)
1220
raise errors.InvalidURL('invalid port number %s in url:\n%s' %
1222
host = urllib.unquote(host)
1223
path = urllib.unquote(path)
1225
return (scheme, user, password, host, port, path)
1228
def _unsplit_url(scheme, user, password, host, port, path):
1230
Build the full URL for the given already URL encoded path.
1232
user, password, host and path will be quoted if they contain reserved
1235
:param scheme: protocol
1239
:param password: associated password
1241
:param host: the server address
1243
:param port: the associated port
1245
:param path: the absolute path on the server
1247
:return: The corresponding URL.
1249
netloc = urllib.quote(host)
1250
if user is not None:
1251
# Note that we don't put the password back even if we
1252
# have one so that it doesn't get accidentally
1254
netloc = '%s@%s' % (urllib.quote(user), netloc)
1255
if port is not None:
1256
netloc = '%s:%d' % (netloc, port)
1257
path = urllib.quote(path)
1258
return urlparse.urlunparse((scheme, netloc, path, None, None, None))
1260
def relpath(self, abspath):
1261
"""Return the local path portion from a given absolute path"""
1262
scheme, user, password, host, port, path = self._split_url(abspath)
1264
if (scheme != self._scheme):
1265
error.append('scheme mismatch')
1266
if (user != self._user):
1267
error.append('user name mismatch')
1268
if (host != self._host):
1269
error.append('host mismatch')
1270
if (port != self._port):
1271
error.append('port mismatch')
1272
if not (path == self._path[:-1] or path.startswith(self._path)):
1273
error.append('path mismatch')
1275
extra = ', '.join(error)
1276
raise errors.PathNotChild(abspath, self.base, extra=extra)
1277
pl = len(self._path)
1278
return path[pl:].strip('/')
1280
def abspath(self, relpath):
1281
"""Return the full url to the given relative path.
1283
:param relpath: the relative path urlencoded
1285
:returns: the Unicode version of the absolute path for relpath.
1287
relative = urlutils.unescape(relpath).encode('utf-8')
1288
path = self._combine_paths(self._path, relative)
1289
return self._unsplit_url(self._scheme, self._user, self._password,
1290
self._host, self._port,
1293
def _remote_path(self, relpath):
1294
"""Return the absolute path part of the url to the given relative path.
1296
This is the path that the remote server expect to receive in the
1297
requests, daughter classes should redefine this method if needed and
1298
use the result to build their requests.
1300
:param relpath: the path relative to the transport base urlencoded.
1302
:return: the absolute Unicode path on the server,
1304
relative = urlutils.unescape(relpath).encode('utf-8')
1305
remote_path = self._combine_paths(self._path, relative)
1308
def _get_shared_connection(self):
1309
"""Get the object shared amongst cloned transports.
1311
This should be used only by classes that needs to extend the sharing
1312
with other objects than tramsports.
1314
Use _get_connection to get the connection itself.
1316
return self._shared_connection
1318
def _set_connection(self, connection, credentials=None):
1319
"""Record a newly created connection with its associated credentials.
1321
Note: To ensure that connection is still shared after a temporary
1322
failure and a new one needs to be created, daughter classes should
1323
always call this method to set the connection and do so each time a new
1324
connection is created.
1326
:param connection: An opaque object representing the connection used by
1329
:param credentials: An opaque object representing the credentials
1330
needed to create the connection.
1332
self._shared_connection.connection = connection
1333
self._shared_connection.credentials = credentials
1335
def _get_connection(self):
1336
"""Returns the transport specific connection object."""
1337
return self._shared_connection.connection
1339
def _get_credentials(self):
1340
"""Returns the credentials used to establish the connection."""
1341
return self._shared_connection.credentials
1343
def _update_credentials(self, credentials):
1344
"""Update the credentials of the current connection.
1346
Some protocols can renegociate the credentials within a connection,
1347
this method allows daughter classes to share updated credentials.
1349
:param credentials: the updated credentials.
1351
# We don't want to call _set_connection here as we are only updating
1352
# the credentials not creating a new connection.
1353
self._shared_connection.credentials = credentials
1355
def _reuse_for(self, other_base):
1356
"""Returns a transport sharing the same connection if possible.
1358
Note: we share the connection if the expected credentials are the
1359
same: (host, port, user). Some protocols may disagree and redefine the
1360
criteria in daughter classes.
1362
Note: we don't compare the passwords here because other_base may have
1363
been obtained from an existing transport.base which do not mention the
1366
:param other_base: the URL we want to share the connection with.
1368
:return: A new transport or None if the connection cannot be shared.
1370
(scheme, user, password, host, port, path) = self._split_url(other_base)
1372
# Don't compare passwords, they may be absent from other_base or from
1373
# self and they don't carry more information than user anyway.
1374
if (scheme == self._scheme
1375
and user == self._user
1376
and host == self._host
1377
and port == self._port):
1378
if not path.endswith('/'):
1379
# This normally occurs at __init__ time, but it's easier to do
1380
# it now to avoid creating two transports for the same base.
1382
if self._path == path:
1383
# shortcut, it's really the same transport
1385
# We don't call clone here because the intent is different: we
1386
# build a new transport on a different base (which may be totally
1387
# unrelated) but we share the connection.
1388
transport = self.__class__(other_base, _from_transport=self)
1392
@deprecated_function(zero_nineteen)
1393
def urlescape(relpath):
1394
urlutils.escape(relpath)
1395
@deprecated_function(zero_nineteen)
1396
def urlunescape(url):
1397
urlutils.unescape(url)
1399
# We try to recognize an url lazily (ignoring user, password, etc)
1400
_urlRE = re.compile(r'^(?P<proto>[^:/\\]+)://(?P<rest>.*)$')
1402
def get_transport(base, possible_transports=None):
1101
1403
"""Open a transport to access a URL or directory.
1103
base is either a URL or a directory name.
1405
:param base: either a URL or a directory name.
1407
:param transports: optional reusable transports list. If not None, created
1408
transports will be added to the list.
1410
:return: A new transport optionally sharing its connection with one of
1411
possible_transports.
1106
1413
if base is None:
1108
1415
last_err = None