118
113
:return: The created connection and its associated credentials.
120
The input credentials are only the password as it may have been
121
entered interactively by the user and may be different from the one
122
provided in base url at transport creation time. The returned
123
credentials are username, password.
115
The credentials are only the password as it may have been entered
116
interactively by the user and may be different from the one provided
117
in base url at transport creation time.
125
119
if credentials is None:
126
user, password = self._user, self._password
120
password = self._password
128
user, password = credentials
130
auth = config.AuthenticationConfig()
132
user = auth.get_user('ftp', self._host, port=self._port)
134
# Default to local user
135
user = getpass.getuser()
122
password = credentials
137
124
mutter("Constructing FTP instance against %r" %
138
((self._host, self._port, user, '********',
125
((self._host, self._port, self._user, '********',
139
126
self.is_active),))
141
128
connection = ftplib.FTP()
142
129
connection.connect(host=self._host, port=self._port)
143
if user and user != 'anonymous' and \
144
password is None: # '' is a valid password
145
password = auth.get_password('ftp', self._host, user,
147
connection.login(user=user, passwd=password)
130
if self._user and self._user != 'anonymous' and \
131
password is not None: # '' is a valid password
132
get_password = bzrlib.ui.ui_factory.get_password
133
password = get_password(prompt='FTP %(user)s@%(host)s password',
134
user=self._user, host=self._host)
135
connection.login(user=self._user, passwd=password)
148
136
connection.set_pasv(not self.is_active)
149
# binary mode is the default
150
connection.voidcmd('TYPE I')
151
except socket.error, e:
152
raise errors.SocketConnectionError(self._host, self._port,
153
msg='Unable to connect to',
155
137
except ftplib.error_perm, e:
156
138
raise errors.TransportError(msg="Error setting up connection:"
157
139
" %s" % str(e), orig_error=e)
158
return connection, (user, password)
140
return connection, password
160
142
def _reconnect(self):
161
143
"""Create a new connection with the previously used credentials"""
162
credentials = self._get_credentials()
144
credentials = self.get_credentials()
163
145
connection, credentials = self._create_connection(credentials)
164
146
self._set_connection(connection, credentials)
595
525
return self.lock_read(relpath)
528
class FtpServer(Server):
529
"""Common code for FTP server facilities."""
533
self._ftp_server = None
535
self._async_thread = None
540
"""Calculate an ftp url to this server."""
541
return 'ftp://foo:bar@localhost:%d/' % (self._port)
543
# def get_bogus_url(self):
544
# """Return a URL which cannot be connected to."""
545
# return 'ftp://127.0.0.1:1'
547
def log(self, message):
548
"""This is used by medusa.ftp_server to log connections, etc."""
549
self.logs.append(message)
551
def setUp(self, vfs_server=None):
553
raise RuntimeError('Must have medusa to run the FtpServer')
555
assert vfs_server is None or isinstance(vfs_server, LocalURLServer), \
556
"FtpServer currently assumes local transport, got %s" % vfs_server
558
self._root = os.getcwdu()
559
self._ftp_server = _ftp_server(
560
authorizer=_test_authorizer(root=self._root),
562
port=0, # bind to a random port
564
logger_object=self # Use FtpServer.log() for messages
566
self._port = self._ftp_server.getsockname()[1]
567
# Don't let it loop forever, or handle an infinite number of requests.
568
# In this case it will run for 1000s, or 10000 requests
569
self._async_thread = threading.Thread(
570
target=FtpServer._asyncore_loop_ignore_EBADF,
571
kwargs={'timeout':0.1, 'count':10000})
572
self._async_thread.setDaemon(True)
573
self._async_thread.start()
576
"""See bzrlib.transport.Server.tearDown."""
577
# have asyncore release the channel
578
self._ftp_server.del_channel()
580
self._async_thread.join()
583
def _asyncore_loop_ignore_EBADF(*args, **kwargs):
584
"""Ignore EBADF during server shutdown.
586
We close the socket to get the server to shutdown, but this causes
587
select.select() to raise EBADF.
590
asyncore.loop(*args, **kwargs)
591
# FIXME: If we reach that point, we should raise an exception
592
# explaining that the 'count' parameter in setUp is too low or
593
# testers may wonder why their test just sits there waiting for a
594
# server that is already dead. Note that if the tester waits too
595
# long under pdb the server will also die.
596
except select.error, e:
597
if e.args[0] != errno.EBADF:
603
_test_authorizer = None
607
global _have_medusa, _ftp_channel, _ftp_server, _test_authorizer
610
import medusa.filesys
611
import medusa.ftp_server
617
class test_authorizer(object):
618
"""A custom Authorizer object for running the test suite.
620
The reason we cannot use dummy_authorizer, is because it sets the
621
channel to readonly, which we don't always want to do.
624
def __init__(self, root):
627
def authorize(self, channel, username, password):
628
"""Return (success, reply_string, filesystem)"""
630
return 0, 'No Medusa.', None
632
channel.persona = -1, -1
633
if username == 'anonymous':
634
channel.read_only = 1
636
channel.read_only = 0
638
return 1, 'OK.', medusa.filesys.os_filesystem(self.root)
641
class ftp_channel(medusa.ftp_server.ftp_channel):
642
"""Customized ftp channel"""
644
def log(self, message):
645
"""Redirect logging requests."""
646
mutter('_ftp_channel: %s', message)
648
def log_info(self, message, type='info'):
649
"""Redirect logging requests."""
650
mutter('_ftp_channel %s: %s', type, message)
652
def cmd_rnfr(self, line):
653
"""Prepare for renaming a file."""
654
self._renaming = line[1]
655
self.respond('350 Ready for RNTO')
656
# TODO: jam 20060516 in testing, the ftp server seems to
657
# check that the file already exists, or it sends
658
# 550 RNFR command failed
660
def cmd_rnto(self, line):
661
"""Rename a file based on the target given.
663
rnto must be called after calling rnfr.
665
if not self._renaming:
666
self.respond('503 RNFR required first.')
667
pfrom = self.filesystem.translate(self._renaming)
668
self._renaming = None
669
pto = self.filesystem.translate(line[1])
670
if os.path.exists(pto):
671
self.respond('550 RNTO failed: file exists')
674
os.rename(pfrom, pto)
675
except (IOError, OSError), e:
676
# TODO: jam 20060516 return custom responses based on
677
# why the command failed
678
# (bialix 20070418) str(e) on Python 2.5 @ Windows
679
# sometimes don't provide expected error message;
680
# so we obtain such message via os.strerror()
681
self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
683
self.respond('550 RNTO failed')
684
# For a test server, we will go ahead and just die
687
self.respond('250 Rename successful.')
689
def cmd_size(self, line):
690
"""Return the size of a file
692
This is overloaded to help the test suite determine if the
693
target is a directory.
696
if not self.filesystem.isfile(filename):
697
if self.filesystem.isdir(filename):
698
self.respond('550 "%s" is a directory' % (filename,))
700
self.respond('550 "%s" is not a file' % (filename,))
702
self.respond('213 %d'
703
% (self.filesystem.stat(filename)[stat.ST_SIZE]),)
705
def cmd_mkd(self, line):
706
"""Create a directory.
708
Overloaded because default implementation does not distinguish
709
*why* it cannot make a directory.
712
self.command_not_understood(''.join(line))
716
self.filesystem.mkdir (path)
717
self.respond ('257 MKD command successful.')
718
except (IOError, OSError), e:
719
# (bialix 20070418) str(e) on Python 2.5 @ Windows
720
# sometimes don't provide expected error message;
721
# so we obtain such message via os.strerror()
722
self.respond ('550 error creating directory: %s' %
723
os.strerror(e.errno))
725
self.respond ('550 error creating directory.')
728
class ftp_server(medusa.ftp_server.ftp_server):
729
"""Customize the behavior of the Medusa ftp_server.
731
There are a few warts on the ftp_server, based on how it expects
735
ftp_channel_class = ftp_channel
737
def __init__(self, *args, **kwargs):
738
mutter('Initializing _ftp_server: %r, %r', args, kwargs)
739
medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
741
def log(self, message):
742
"""Redirect logging requests."""
743
mutter('_ftp_server: %s', message)
745
def log_info(self, message, type='info'):
746
"""Override the asyncore.log_info so we don't stipple the screen."""
747
mutter('_ftp_server %s: %s', type, message)
749
_test_authorizer = test_authorizer
750
_ftp_channel = ftp_channel
751
_ftp_server = ftp_server
598
756
def get_test_permutations():
599
757
"""Return the permutations to be used in testing."""
600
from bzrlib.tests import ftp_server
601
return [(FtpTransport, ftp_server.FTPTestServer)]
758
if not _setup_medusa():
759
warn("You must install medusa (http://www.amk.ca/python/code/medusa.html) for FTP tests")
762
return [(FtpTransport, FtpServer)]