Example #1
0
    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)
Example #2
0
    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'))
Example #3
0
    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
Example #4
0
    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)
Example #5
0
    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'])
Example #6
0
    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')
Example #7
0
    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)
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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'])
Example #11
0
    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')
Example #12
0
    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
Example #13
0
    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
Example #14
0
 def override_func():
     yield from original_func()
     yield from session._control_stream.write_command(
         Command('EVIL_BAD_PASV_ADDR'))
     print('Evil awaits')