def login(self, username: str = 'anonymous', password: str = '-wpull-lib@'): '''Log in. Coroutine. ''' yield from self._control_stream.write_command(Command( 'USER', username)) reply = yield from self._control_stream.read_reply() if reply.code == ReplyCodes.user_logged_in_proceed: return self.raise_if_not_match('Login username', ReplyCodes.user_name_okay_need_password, reply) yield from self._control_stream.write_command(Command( 'PASS', password)) reply = yield from self._control_stream.read_reply() self.raise_if_not_match('Login password', ReplyCodes.user_logged_in_proceed, reply)
def test_control_stream(self): def log_cb(data_type, data): _logger.debug(__('{0}={1}', data_type, data)) connection = Connection(('127.0.0.1', self.server_port())) yield from connection.connect() control_stream = ControlStream(connection) control_stream.data_event_dispatcher.add_read_listener( functools.partial(log_cb, 'read')) control_stream.data_event_dispatcher.add_write_listener( functools.partial(log_cb, 'write')) reply = yield from control_stream.read_reply() self.assertEqual(220, reply.code) yield from control_stream.write_command(Command('USER', 'smaug')) reply = yield from control_stream.read_reply() self.assertEqual(331, reply.code) yield from control_stream.write_command(Command('PASS', 'gold1')) reply = yield from control_stream.read_reply() self.assertEqual(230, reply.code) yield from control_stream.write_command(Command('PASV')) reply = yield from control_stream.read_reply() self.assertEqual(227, reply.code) address = parse_address(reply.text) data_connection = Connection(address) yield from data_connection.connect() data_stream = DataStream(data_connection) yield from control_stream.write_command( Command('RETR', 'example (copy).txt')) reply = yield from control_stream.read_reply() self.assertEqual(150, reply.code) my_file = io.BytesIO() yield from data_stream.read_file(my_file) reply = yield from control_stream.read_reply() self.assertEqual(226, reply.code) self.assertEqual('The real treasure is in Smaug’s heart 💗.\n', my_file.getvalue().decode('utf-8'))
def start_listing(self, request: Request) -> ListingResponse: '''Fetch a file listing. Args: request: Request. Returns: A listing response populated with the initial data connection reply. Once the response is received, call :meth:`download_listing`. Coroutine. ''' if self._session_state != SessionState.ready: raise RuntimeError('Session not ready') response = ListingResponse() yield from self._prepare_fetch(request, response) yield from self._open_data_stream() mlsd_command = Command('MLSD', self._request.file_path) list_command = Command('LIST', self._request.file_path) try: yield from self._begin_stream(mlsd_command) self._listing_type = 'mlsd' except FTPServerError as error: if error.reply_code in ( ReplyCodes.syntax_error_command_unrecognized, ReplyCodes.command_not_implemented): self._listing_type = None else: raise if not self._listing_type: # This code not in exception handler to avoid incorrect # exception chaining yield from self._begin_stream(list_command) self._listing_type = 'list' _logger.debug('Listing type is %s', self._listing_type) self._session_state = SessionState.directory_request_sent return response
def write_command(self, command: Command): '''Write a command to the stream. Args: command: The command. Coroutine. ''' _logger.debug('Write command.') data = command.to_bytes() yield from self._connection.write(data) self._data_event_dispatcher.notify_write(data)
def test_command(self): command = Command('User', '*****@*****.**') self.assertEqual('USER', command.name) self.assertEqual('*****@*****.**', command.argument) self.assertEqual('USER', command.to_dict()['name']) self.assertEqual('*****@*****.**', command.to_dict()['argument']) command = Command('Poke') self.assertEqual('POKE', command.name) self.assertEqual('', command.argument) self.assertEqual('POKE', command.to_dict()['name']) self.assertEqual('', command.to_dict()['argument'])
def test_parse_command(self): command = Command() command.parse(b'User [email protected]\r\n') self.assertEqual('USER', command.name) self.assertEqual('*****@*****.**', command.argument) self.assertRaises(AssertionError, command.parse, b'OOPS\r\n') command = Command() command.parse(b'POKE\r\n') self.assertEqual('POKE', command.name) self.assertEqual('', command.argument) self.assertRaises(AssertionError, command.parse, b'OOPS\r\n')
def restart(self, offset: int): '''Send restart command. Coroutine. ''' yield from self._control_stream.write_command( Command('REST', str(offset))) reply = yield from self._control_stream.read_reply() self.raise_if_not_match( 'Restart', ReplyCodes.requested_file_action_pending_further_information, reply)
def size(self, filename: str) -> int: '''Get size of file. Coroutine. ''' yield from self._control_stream.write_command(Command( 'SIZE', filename)) reply = yield from self._control_stream.read_reply() self.raise_if_not_match('File size', ReplyCodes.file_status, reply) try: return int(reply.text.strip()) except ValueError: return
def passive_mode(self) -> Tuple[str, int]: '''Enable passive mode. Returns: The address (IP address, port) of the passive port. Coroutine. ''' yield from self._control_stream.write_command(Command('PASV')) reply = yield from self._control_stream.read_reply() self.raise_if_not_match('Passive mode', ReplyCodes.entering_passive_mode, reply) try: return wpull.protocol.ftp.util.parse_address(reply.text) except ValueError as error: raise ProtocolError(str(error)) from error
def test_command(self): command = Command('User', '*****@*****.**') self.assertEqual('USER', command.name) self.assertEqual('*****@*****.**', command.argument) self.assertEqual('USER', command.to_dict()['name']) self.assertEqual( '*****@*****.**', command.to_dict()['argument']) command = Command('Poke') self.assertEqual('POKE', command.name) self.assertEqual('', command.argument) self.assertEqual('POKE', command.to_dict()['name']) self.assertEqual('', command.to_dict()['argument'])
def start(self, request: Request) -> Response: '''Start a file or directory listing download. Args: request: Request. Returns: A Response populated with the initial data connection reply. Once the response is received, call :meth:`download`. Coroutine. ''' if self._session_state != SessionState.ready: raise RuntimeError('Session not ready') response = Response() yield from self._prepare_fetch(request, response) response.file_transfer_size = yield from self._fetch_size(request) if request.restart_value: try: yield from self._commander.restart(request.restart_value) response.restart_value = request.restart_value except FTPServerError: _logger.debug('Could not restart file.', exc_info=1) yield from self._open_data_stream() command = Command('RETR', request.file_path) yield from self._begin_stream(command) self._session_state = SessionState.file_request_sent return response
def setup_data_stream( self, connection_factory: Callable[[tuple], Connection], data_stream_factory: Callable[[Connection], DataStream]=DataStream) -> \ DataStream: '''Create and setup a data stream. This function will set up passive and binary mode and handle connecting to the data connection. Args: connection_factory: A coroutine callback that returns a connection data_stream_factory: A callback that returns a data stream Coroutine. Returns: DataStream ''' yield from self._control_stream.write_command(Command('TYPE', 'I')) reply = yield from self._control_stream.read_reply() self.raise_if_not_match('Binary mode', ReplyCodes.command_okay, reply) address = yield from self.passive_mode() connection = yield from connection_factory(address) # TODO: unit test for following line for connections that have # the same port over time but within pool cleaning intervals connection.reset() yield from connection.connect() data_stream = data_stream_factory(connection) return data_stream
def override_func(): yield from original_func() yield from session._control_stream.write_command( Command('EVIL_BAD_PASV_ADDR')) print('Evil awaits')