From 3c65db577780025a6df3d7f6fbbea41a2e1efaea Mon Sep 17 00:00:00 2001 From: Allen Luce Date: Fri, 18 Mar 2016 12:30:07 -0700 Subject: [PATCH] Add CWD/NLST/LIST and remove threading. Threading really isn't necessary and just adds complexity. --- stubserver/ftpserver.py | 67 +++++++++++++++++++++-------------------- test.py | 42 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 33 deletions(-) diff --git a/stubserver/ftpserver.py b/stubserver/ftpserver.py index 14c8e0a..af48a1f 100644 --- a/stubserver/ftpserver.py +++ b/stubserver/ftpserver.py @@ -12,6 +12,7 @@ def __init__(self, hostname, port, interactions, files): self.port = port self.interactions = interactions self.files = files + self.cwd = '/' def __call__(self, request, client_address, server): self.request = request @@ -30,10 +31,13 @@ def handle(self): self.communicating = True while self.communicating: cmd = self.request.recv(1024) + if len(cmd) == 0: + break if cmd: self.interactions.append(cmd) - cmd = cmd.decode('utf-8') - getattr(self, '_' + cmd[:4])(cmd) + cmd = cmd.decode('utf-8').rstrip() + first = cmd.split(' ', 1)[0] + getattr(self, '_' + first)(cmd) def _USER(self, cmd): self.request.send(b'331 Please specify password.\r\n') @@ -45,23 +49,12 @@ def _TYPE(self, cmd): self.request.send(b'200 Switching to ascii mode.\r\n') def _PASV(self, cmd): - self.parent_event = threading.Event() - self.child_event = threading.Event() - self.data_handler = FTPDataServer(self.interactions, self.files, self.child_event) + self.data_handler = FTPDataServer(self.interactions, self.files) + self.port = self.port + 1 + SocketServer.TCPServer.allow_reuse_address = True + self.data_server = SocketServer.TCPServer((self.hostname, self.port + 1), self.data_handler) - def start_data_server(): - self.port = self.port + 1 - SocketServer.TCPServer.allow_reuse_address = True - data_server = SocketServer.TCPServer((self.hostname, self.port + 1), self.data_handler) - # needs to signal ready here - self.parent_event.set() - data_server.handle_request() - data_server.server_close() - self.parent_event.set() - - self.t2 = threading.Thread(target=start_data_server) - self.t2.start() - self.parent_event.wait() + self.request.send(('227 Entering Passive Mode. (127,0,0,1,%s,%s)\r\n' % ( int((self.port + 1) / 256), (self.port + 1) % 256)).encode('utf-8')) @@ -69,40 +62,45 @@ def filename(self): return self.interactions[-1:][0][5:].strip() def child_go(self): - self.child_event.set() - self.parent_event.clear() - self.parent_event.wait() + self.data_server.handle_request() + self.data_server.server_close() def _STOR(self, cmd): self.request.send(b'150 Okay to send data\r\n') self.child_go() self.request.send(b'226 Got the file\r\n') - self.t2.join(1) def _LIST(self, cmd): self.request.send(b'150 Accepted data connection\r\n') self.child_go() self.request.send(b'226 You got the listings now\r\n') - self.t2.join(1) def _RETR(self, cmd): self.request.send(b'150 Accepted data connection\r\n') self.child_go() self.request.send(b'226 Enjoy your file\r\n') - self.t2.join(1) + + def _CWD(self, cmd): + self.cwd = cmd.split(' ',2)[1] + self.request.send(b'250 OK. Current directory is "%s"\r\n' % self.cwd.encode('utf-8')) + + def _PWD(self, cmd): + self.request.send(b'257 "%s" is your current location\r\n' % self.cwd.encode('utf-8')) + + def _NLST(self, cmd): + self.request.send(b'150 Accepted data connection\r\n') + self.child_go() + self.request.send(b'226 You got the listings now\r\n') def _QUIT(self, cmd): - self.request.send(b'221 Goodbye\r\n') self.communicating = False - time.sleep(0.001) - + self.request.send(b'221-Goodbye.\r\n221 Have fun.') class FTPDataServer(SocketServer.StreamRequestHandler): - def __init__(self, interactions, files, event): + def __init__(self, interactions, files): self.interactions = interactions self.files = files self.command = 'LIST' - self.child_event = event def __call__(self, request, client_address, server): self.request = request @@ -116,7 +114,6 @@ def __call__(self, request, client_address, server): self.finish() def handle(self): - self.child_event.wait() cmd = self.interactions[-1:][0].decode('utf-8') if cmd[:4] == 'PASV': return @@ -131,11 +128,15 @@ def _STOR(self): self.files[self.filename()] = self.rfile.read().strip() def _LIST(self): - data = '\n'.join([name for name in self.files.keys()]) - self.wfile.write(data.encode('utf-8')) + data = b'\n'.join([name for name in self.files.keys()]) + self.wfile.write(data) + + def _NLST(self): + data = b'\015\012'.join([name for name in self.files.keys()]) + self.wfile.write(data) def _RETR(self): - self.wfile.write(self.files[self.filename().decode('utf-8')].encode('utf-8')) + self.wfile.write(self.files[self.filename()]) class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): diff --git a/test.py b/test.py index 82dd1eb..9320595 100644 --- a/test.py +++ b/test.py @@ -143,6 +143,16 @@ def setUp(self): def tearDown(self): self.server.stop() + def test_change_directory(self): + ftp = FTP() + ftp.set_debuglevel(0) + ftp.connect('localhost', self.port) + ftp.login('user1', 'passwd') + ftp.cwd('newdir') + self.assertEqual(ftp.pwd(), 'newdir') + ftp.quit() + ftp.close() + def test_put_test_file(self): self.assertFalse(self.server.files("foo.txt")) ftp = FTP() @@ -170,6 +180,38 @@ def test_put_2_files_associates_the_correct_content_with_the_correct_filename(se self.server.files("robot.txt").strip()) self.assertEquals("file2 content", self.server.files("monster.txt").strip()) + def test_list_2_files(self): + ftp = FTP() + ftp.connect('localhost', self.port) + ftp.set_debuglevel(0) + ftp.login('user2','other_pass') + self.lines = [] + def accumulate(line): + self.lines.append(line) + + ftp.storlines('STOR palladium.csv', BytesIO(b'data')) + ftp.storlines('STOR vanadiyam.pdf', BytesIO(b'more data')) + ftp.retrlines('LIST', accumulate) + self.assertEqual(self.lines, ['palladium.csv', 'vanadiyam.pdf']) + ftp.quit() + ftp.close() + + def test_nlst_2_files(self): + ftp = FTP() + ftp.connect('localhost', self.port) + ftp.set_debuglevel(0) + ftp.login('user2','other_pass') + self.lines = [] + def accumulate(line): + self.lines.append(line) + + ftp.storlines('STOR palladium.csv', BytesIO(b'data')) + ftp.storlines('STOR vanadiyam.pdf', BytesIO(b'more data')) + ftp.retrlines('NLST', accumulate) + self.assertEqual(self.lines, ['palladium.csv', 'vanadiyam.pdf']) + ftp.quit() + ftp.close() + def test_retrieve_expected_file_returns_file(self): expected_content = 'content of my file\nis a complete mystery to me.' self.server.add_file(b'foo.txt', expected_content)