Module lib.server.ftpservercore

Ftp server implementation core class

Expand source code
# Distributed under Pycameresp License
# Copyright (c) 2023 Remi BERTHOLET
# historically based on :
# https://github.com/robert-hh/FTP-Server-for-ESP8266-ESP32-and-PYBD/blob/master/ftp.py
# but I have modified a lot, there must still be some original functions.
# pylint:disable=consider-using-f-string
# pylint:disable=unspecified-encoding
""" Ftp server implementation core class """
import socket
import os
import server.stream
import server.user
import wifi.accesspoint
import wifi.station
import tools.logger
import tools.fnmatch
import tools.filesystem
import tools.strings
import tools.date
import tools.tasking

MONTHS  = [b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov", b"Dec"]

class FtpServerCore:
        """ Ftp implementation server core """
        portbase = [12345]
        def __init__(self):
                """ Ftp constructor method """
                self.portbase[0] += 1
                self.dataport = self.portbase[0]
                self.pasvsocket = None
                self.addr = b""
                self.user = b""
                self.password = b""
                self.path = b""
                self.cwd = b"/"
                self.fromname = None
                if tools.filesystem.ismicropython():
                        self.root = b""
                        self.path_length = 64
                else:
                        self.root = tools.strings.tobytes(os.getcwd() + "/ftp")
                        self.path_length = 256
                self.command = b""
                self.payload = b""

                self.datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                self.datasocket.bind(socket.getaddrinfo("0.0.0.0", self.dataport)[0][4])
                self.datasocket.listen(1)
                self.datasocket.settimeout(None)
                self.data_addr = None
                self.quit = None
                self.received = None
                self.remoteaddr = None
                self.client = None
                self.log(b"Open data %d"%self.dataport)

        def log(self, err, msg="", write=False):
                """ Log message """
                if write:
                        tools.logger.syslog(err, msg=msg, write=write)

        def get_ip(self):
                """ Get the ip address of the board """
                if wifi.station.Station.is_ip_on_interface(self.remoteaddr):
                        result = tools.strings.tobytes(wifi.station.Station.get_info()[0])
                else:
                        result = tools.strings.tobytes(wifi.accesspoint.AccessPoint.get_info()[0])
                return result

        def close(self):
                """ Close all ftp connections """
                self.close_pasv()
                if self.datasocket:
                        self.datasocket.close()
                        self.datasocket = None

        def __del__(self):
                """ Destroy ftp instance """
                self.close()

        def get_file_description(self, filename, typ, size, current_date, now, full):
                """ Build list of file description """
                if full:
                        file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--"

                        d = tools.date.local_time(current_date)
                        year,month,day,hour,minute,_,_,_ = d[:8]

                        if year != now[0] and month != now[1]:
                                file_date = b"%s %2d  %4d"%(MONTHS[month-1], day, year)
                        else:
                                file_date = b"%s %2d %02d:%02d"%(MONTHS[month-1], day, hour, minute)
                        description = b"%s    1 owner group %10d %s %s\r\n"%(file_permissions, size, file_date, tools.strings.tobytes(filename))
                else:
                        description = tools.strings.tobytes(filename) + b"\r\n"
                return description

        def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None):
                """ Send the list of file with pattern """
                description = b""
                quantity = 0
                counter = 0
                for fileinfo in tools.filesystem.list_directory(tools.strings.tostrings(path)):
                        filename = fileinfo[0]
                        typ = fileinfo[1]
                        if len(fileinfo) > 3:
                                size = fileinfo[3]
                        else:
                                size = 0
                        if pattern is None:
                                accepted = True
                        else:
                                accepted = tools.fnmatch.fnmatch(tools.strings.tostrings(filename), tools.strings.tostrings(pattern))
                        if accepted:
                                if quantity > 100:
                                        current_date = 0
                                else:
                                        sta = (0,0,0,0,0,0,0,0,0)
                                        try:
                                                # If it is a file
                                                if not (typ & 0xF000 == 0x4000):
                                                        sta = tools.filesystem.fileinfo(tools.strings.tostrings(tools.filesystem.abspathbytes(path,tools.strings.tobytes(filename))))
                                        except Exception:
                                                pass
                                        current_date = sta[8]

                                description += self.get_file_description(filename, typ, size, current_date, now, full)
                                counter += 1
                                if counter == 20:
                                        counter = 0
                                        stream_.write(description)
                                        description = b""
                        quantity += 1
                if description != b"":
                        stream_.write(description)

        def send_file_list(self, path, stream_, full):
                """ Send the list of file """
                now = tools.date.local_time()
                try:
                        self.send_file_list_with_pattern(path, stream_, full, now)
                except Exception as err:
                        self.log(err, write=True)
                        pattern = path.split(b"/")[-1]
                        path = path[:-(len(pattern) + 1)]
                        if path == b"":
                                path = b"/"
                        self.send_file_list_with_pattern(path, stream_, full, now, pattern)

        async def send_ok(self):
                """ Send ok to ftp client """
                await self.send_response(250,b"OK")

        async def send_response(self, code, message):
                """ Send response to ftp client """
                self.log(b"%d %s"%(code, message))
                await self.client.write(b'%d %s\r\n'%(code,message))

        async def send_error(self, err):
                """ Send error to ftp client """
                showError = False
                if type(err) != type(b""):
                        if tools.filesystem.ismicropython():
                                if type(err) != type(OSError):
                                        showError = True
                        else:
                                if isinstance(err,FileNotFoundError) or isinstance(err,NotADirectoryError):
                                        showError = False
                                else:
                                        showError = True
                if showError:
                        self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload))
                await self.send_response(550, b"Failed")

        async def USER(self):
                """ Ftp command USER """
                if server.user.User.get_user() == b"":
                        await self.send_response(230, b"User Logged In.")
                else:
                        self.user = self.path[1:]
                        await self.send_response(331, b"User known, enter password")

        async def PASS(self):
                """ Ftp command PASS """
                self.password = self.path[1:]
                if server.user.User.check(self.user, self.password, False):
                        await self.send_response(230, b"Logged in.")
                else:
                        await self.send_response(430, b"Invalid username or password")

        async def SYST(self):
                """ Ftp command SYST """
                await self.send_response(215, b"UNIX Type: L8")

        async def NOOP(self):
                """ Ftp command NOOP """
                await self.send_response(200, b"OK")

        async def FEAT(self):
                """ Ftp command FEAT """
                await self.send_response(211, b"no-features")

        async def XPWD(self):
                """ Ftp command XPWD """
                await self.PWD()

        async def PWD(self):
                """ Ftp command PWD """
                await self.send_response(257,b'"%s" is current directory.'%self.cwd)

        async def XCWD(self):
                """ Ftp command XCWD """
                await self.CWD()

        async def CWD(self):
                """ Ftp command CWD """
                if len(self.path) <= self.path_length:
                        try:
                                dd = os.listdir(tools.strings.tostrings(self.root + self.path))
                                self.cwd = self.path
                                await self.send_response(250,b"CWD command successful.")
                        except Exception as err:
                                self.log(err, write=True)
                                await self.send_error(b"Path not existing")
                else:
                        await self.send_error(b"Path too long")

        async def CDUP(self):
                """ Ftp command CDUP """
                self.cwd = tools.filesystem.abspathbytes(self.cwd, b"..")
                await self.send_ok()

        async def TYPE(self):
                """ Ftp command TYPE """
                await self.send_response(200, b"Binary transfer mode active.")

        async def SIZE(self):
                """ Ftp command SIZE """
                size = tools.filesystem.filesize(tools.strings.tostrings(self.root + self.path))
                await self.send_response(213, b"%d"%(size))

        async def PASV(self):
                """ Ftp command PASV """
                await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256))
                self.close_pasv()
                self.pasvsocket, self.data_addr = self.datasocket.accept()
                self.log(b"PASV Accepted")

        async def PORT(self):
                """ Ftp command PORT """
                items = self.payload.split(b",")
                if len(items) >= 6:
                        self.data_addr = b'.'.join(items[:4])
                        if self.data_addr == b"127.0.1.1":
                                self.data_addr = self.remoteaddr
                        self.dataport = int(items[4]) * 256 + int(items[5])
                        self.close_pasv()
                        self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        self.pasvsocket.settimeout(1000)
                        self.pasvsocket.connect((self.data_addr, self.dataport))
                        self.log("Data connection with: %s"%tools.strings.tostrings(self.data_addr))
                        await self.send_response(200, b"OK")
                else:
                        await self.send_response(504, b"Fail")

        async def NLST(self):
                """ Ftp command NLST """
                await self.LIST()

        async def LIST(self):
                """ Ftp command LIST """
                if not self.payload.startswith(b"-"):
                        place = self.path
                else:
                        place = self.cwd
                await self.send_response(150, b"Connection accepted.") # Start list files
                listsocket = server.stream.Socket(self.pasvsocket)
                self.log("List %s"%(tools.strings.tostrings(self.root+place)))
                self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l")
                listsocket.close()
                await self.send_response(226, b"Transfert complete.") # End list files
                self.close_pasv()

        async def STAT(self):
                """ Ftp command STAT """
                if self.payload == b"":
                        await self.send_response(211, b"Connected to (%s)"%self.remoteaddr[0])
                        await self.send_response(211, b"Data address (%s)"%self.addr)
                        await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream")
                else:
                        await self.send_response(213,b"Directory listing:")
                        self.log("List %s"%tools.strings.tostrings(self.root+self.path))
                        self.send_file_list(self.root + self.path, self.client, True)
                        await self.send_response(213, b"Stat end")

        async def RETR(self):
                """ Ftp command RETR """
                await self.send_response(150, b"Start send file")
                self.log("Send %s"%tools.strings.tostrings(self.root+self.path), write=True)
                filename = self.root + self.path

                if tools.filesystem.ismicropython():
                        buffer_size = 1440
                        chunk = bytearray(buffer_size)
                        with open(tools.strings.tostrings(filename), "r") as file:
                                length = file.readinto(chunk)
                                while length > 0:
                                        # pylint: disable=no-member
                                        sent = self.pasvsocket.write(chunk[:length])
                                        length = file.readinto(chunk)
                else:
                        with open(tools.strings.tostrings(filename), "rb") as file:
                                self.pasvsocket.sendall(file.read())
                await self.send_response(226, b"End send file")
                self.close_pasv()

        def close_pasv(self):
                """ Close PASV connection """
                if self.pasvsocket is not None:
                        self.log(b"Close PASV")
                        self.pasvsocket.close()
                        self.pasvsocket = None

        def write_file(self, path, dataclient):
                """ Write ftp received """
                chunk = bytearray(1440)
                with open(tools.strings.tostrings(path), "wb") as file:
                        length = dataclient.readinto(chunk)
                        while length > 0:
                                file.write(chunk, length)
                                length = dataclient.readinto(chunk)

        async def STOR(self):
                """ Ftp command STOR """
                await self.send_response(150, b"Start receive file")
                self.log("Receive %s"%tools.strings.tostrings(self.root + self.path), write=True)
                filename = self.root + self.path

                if tools.filesystem.ismicropython():
                        try:
                                self.write_file(filename, self.pasvsocket)
                        except Exception as err:
                                self.log(err, write=True)
                                directory, file = tools.filesystem.split(tools.strings.tostrings(filename))
                                tools.filesystem.makedir(directory, True)
                                self.write_file(filename, self.pasvsocket)
                else:
                        with open(filename, "wb") as file:
                                data = b" "
                                while len(data) > 0:
                                        data = self.pasvsocket.recv(1440)
                                        file.write(data)
                                data = b""
                await self.send_response(226, b"End receive file")
                self.close_pasv()

        async def DELE(self):
                """ Ftp command DELE """
                self.log("Delete %s"%tools.strings.tostrings(self.root + self.path), write=True)
                os.remove(tools.strings.tostrings(self.root + self.path))
                await self.send_ok()

        async def XRMD(self):
                """ Ftp command XRMD """
                await self.RMD()

        async def RMD(self):
                """ Ftp command RMD """
                os.rmdir(tools.strings.tostrings(self.root + self.path))
                await self.send_ok()

        async def XMKD(self):
                """ Ftp command XMKD """
                await self.MKD()

        async def MKD(self):
                """ Ftp command MKD """
                os.mkdir(tools.strings.tostrings(self.root + self.path))
                await self.send_ok()

        async def RNFR(self):
                """ Ftp command RNFR """
                self.fromname = self.path
                await self.send_response(350, b"Rename from")

        async def RNTO(self):
                """ Ftp command RNTO """
                if self.fromname is not None:
                        self.log("Rename %s to %s"%(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)), write=True)
                        os.rename(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path))
                        await self.send_ok()
                else:
                        await self.send_error(self.fromname)
                self.fromname = None

        async def QUIT(self):
                """ Ftp command QUIT """
                self.quit = True
                await self.send_response(221, b"Bye.")

        async def unsupported_command(self):
                """ Ftp unknown command """
                await self.send_response(502, b"Unsupported command")

        async def receive_command(self):
                """ Ftp command reception """
                tools.tasking.Tasks.slow_down()
                try:
                        self.received = await self.client.readline()
                except Exception as err:
                        self.log(err, write=True)
                        self.log(b"Reset connection")
                        self.quit = True

                if len(self.received) <= 0:
                        self.quit = True
                else:
                        self.received = self.received.rstrip(b"\r\n")
                        if tools.strings.tobytes(self.received[:4]) == b"PASS":
                                message = b"PASS ????"
                        else:
                                message = self.received
                        self.command = self.received.split(b" ")[0].upper()
                        self.payload = self.received[len(self.command):].lstrip()
                        self.path = tools.filesystem.abspathbytes(self.cwd, self.payload)
                        self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path))

        async def treat_command(self):
                """ Treat ftp command """
                tools.tasking.Tasks.slow_down()
                if self.quit is False:
                        try:
                                command = tools.strings.tostrings(self.command)
                                if hasattr(self, command):
                                        callback = getattr(self, command)

                                        if self.command not in [b"USER",b"PASS"]:
                                                if server.user.User.check(self.user, self.password):
                                                        await callback()
                                                else:
                                                        await self.send_response(430, b"Invalid username or password")
                                        else:
                                                await callback()
                                else:
                                        await self.unsupported_command()
                        except Exception as err:
                                self.log(err, write=True)
                                await self.send_error(err)

        async def on_connection(self, reader, writer):
                """ Asyncio on ftp connection method """
                tools.tasking.Tasks.slow_down()
                self.remoteaddr = tools.strings.tobytes(writer.get_extra_info('peername')[0])
                self.addr = self.get_ip()
                self.log("Connected from %s"%tools.strings.tostrings(self.remoteaddr), write=True)
                self.client = server.stream.Stream(reader, writer)
                try:
                        await self.send_response(220, b"Ftp " + tools.strings.tobytes(os.uname()[4]) + b".")
                        self.quit = False
                        while self.quit is False:
                                await self.receive_command()
                                await self.treat_command()
                except Exception as err:
                        self.log(err, write=True)
                        await self.send_error(err)
                finally:
                        self.close_pasv()
                        await self.client.close()
                self.log("Disconnected", write=True)

Classes

class FtpServerCore

Ftp implementation server core

Ftp constructor method

Expand source code
class FtpServerCore:
        """ Ftp implementation server core """
        portbase = [12345]
        def __init__(self):
                """ Ftp constructor method """
                self.portbase[0] += 1
                self.dataport = self.portbase[0]
                self.pasvsocket = None
                self.addr = b""
                self.user = b""
                self.password = b""
                self.path = b""
                self.cwd = b"/"
                self.fromname = None
                if tools.filesystem.ismicropython():
                        self.root = b""
                        self.path_length = 64
                else:
                        self.root = tools.strings.tobytes(os.getcwd() + "/ftp")
                        self.path_length = 256
                self.command = b""
                self.payload = b""

                self.datasocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.datasocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                self.datasocket.bind(socket.getaddrinfo("0.0.0.0", self.dataport)[0][4])
                self.datasocket.listen(1)
                self.datasocket.settimeout(None)
                self.data_addr = None
                self.quit = None
                self.received = None
                self.remoteaddr = None
                self.client = None
                self.log(b"Open data %d"%self.dataport)

        def log(self, err, msg="", write=False):
                """ Log message """
                if write:
                        tools.logger.syslog(err, msg=msg, write=write)

        def get_ip(self):
                """ Get the ip address of the board """
                if wifi.station.Station.is_ip_on_interface(self.remoteaddr):
                        result = tools.strings.tobytes(wifi.station.Station.get_info()[0])
                else:
                        result = tools.strings.tobytes(wifi.accesspoint.AccessPoint.get_info()[0])
                return result

        def close(self):
                """ Close all ftp connections """
                self.close_pasv()
                if self.datasocket:
                        self.datasocket.close()
                        self.datasocket = None

        def __del__(self):
                """ Destroy ftp instance """
                self.close()

        def get_file_description(self, filename, typ, size, current_date, now, full):
                """ Build list of file description """
                if full:
                        file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--"

                        d = tools.date.local_time(current_date)
                        year,month,day,hour,minute,_,_,_ = d[:8]

                        if year != now[0] and month != now[1]:
                                file_date = b"%s %2d  %4d"%(MONTHS[month-1], day, year)
                        else:
                                file_date = b"%s %2d %02d:%02d"%(MONTHS[month-1], day, hour, minute)
                        description = b"%s    1 owner group %10d %s %s\r\n"%(file_permissions, size, file_date, tools.strings.tobytes(filename))
                else:
                        description = tools.strings.tobytes(filename) + b"\r\n"
                return description

        def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None):
                """ Send the list of file with pattern """
                description = b""
                quantity = 0
                counter = 0
                for fileinfo in tools.filesystem.list_directory(tools.strings.tostrings(path)):
                        filename = fileinfo[0]
                        typ = fileinfo[1]
                        if len(fileinfo) > 3:
                                size = fileinfo[3]
                        else:
                                size = 0
                        if pattern is None:
                                accepted = True
                        else:
                                accepted = tools.fnmatch.fnmatch(tools.strings.tostrings(filename), tools.strings.tostrings(pattern))
                        if accepted:
                                if quantity > 100:
                                        current_date = 0
                                else:
                                        sta = (0,0,0,0,0,0,0,0,0)
                                        try:
                                                # If it is a file
                                                if not (typ & 0xF000 == 0x4000):
                                                        sta = tools.filesystem.fileinfo(tools.strings.tostrings(tools.filesystem.abspathbytes(path,tools.strings.tobytes(filename))))
                                        except Exception:
                                                pass
                                        current_date = sta[8]

                                description += self.get_file_description(filename, typ, size, current_date, now, full)
                                counter += 1
                                if counter == 20:
                                        counter = 0
                                        stream_.write(description)
                                        description = b""
                        quantity += 1
                if description != b"":
                        stream_.write(description)

        def send_file_list(self, path, stream_, full):
                """ Send the list of file """
                now = tools.date.local_time()
                try:
                        self.send_file_list_with_pattern(path, stream_, full, now)
                except Exception as err:
                        self.log(err, write=True)
                        pattern = path.split(b"/")[-1]
                        path = path[:-(len(pattern) + 1)]
                        if path == b"":
                                path = b"/"
                        self.send_file_list_with_pattern(path, stream_, full, now, pattern)

        async def send_ok(self):
                """ Send ok to ftp client """
                await self.send_response(250,b"OK")

        async def send_response(self, code, message):
                """ Send response to ftp client """
                self.log(b"%d %s"%(code, message))
                await self.client.write(b'%d %s\r\n'%(code,message))

        async def send_error(self, err):
                """ Send error to ftp client """
                showError = False
                if type(err) != type(b""):
                        if tools.filesystem.ismicropython():
                                if type(err) != type(OSError):
                                        showError = True
                        else:
                                if isinstance(err,FileNotFoundError) or isinstance(err,NotADirectoryError):
                                        showError = False
                                else:
                                        showError = True
                if showError:
                        self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload))
                await self.send_response(550, b"Failed")

        async def USER(self):
                """ Ftp command USER """
                if server.user.User.get_user() == b"":
                        await self.send_response(230, b"User Logged In.")
                else:
                        self.user = self.path[1:]
                        await self.send_response(331, b"User known, enter password")

        async def PASS(self):
                """ Ftp command PASS """
                self.password = self.path[1:]
                if server.user.User.check(self.user, self.password, False):
                        await self.send_response(230, b"Logged in.")
                else:
                        await self.send_response(430, b"Invalid username or password")

        async def SYST(self):
                """ Ftp command SYST """
                await self.send_response(215, b"UNIX Type: L8")

        async def NOOP(self):
                """ Ftp command NOOP """
                await self.send_response(200, b"OK")

        async def FEAT(self):
                """ Ftp command FEAT """
                await self.send_response(211, b"no-features")

        async def XPWD(self):
                """ Ftp command XPWD """
                await self.PWD()

        async def PWD(self):
                """ Ftp command PWD """
                await self.send_response(257,b'"%s" is current directory.'%self.cwd)

        async def XCWD(self):
                """ Ftp command XCWD """
                await self.CWD()

        async def CWD(self):
                """ Ftp command CWD """
                if len(self.path) <= self.path_length:
                        try:
                                dd = os.listdir(tools.strings.tostrings(self.root + self.path))
                                self.cwd = self.path
                                await self.send_response(250,b"CWD command successful.")
                        except Exception as err:
                                self.log(err, write=True)
                                await self.send_error(b"Path not existing")
                else:
                        await self.send_error(b"Path too long")

        async def CDUP(self):
                """ Ftp command CDUP """
                self.cwd = tools.filesystem.abspathbytes(self.cwd, b"..")
                await self.send_ok()

        async def TYPE(self):
                """ Ftp command TYPE """
                await self.send_response(200, b"Binary transfer mode active.")

        async def SIZE(self):
                """ Ftp command SIZE """
                size = tools.filesystem.filesize(tools.strings.tostrings(self.root + self.path))
                await self.send_response(213, b"%d"%(size))

        async def PASV(self):
                """ Ftp command PASV """
                await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256))
                self.close_pasv()
                self.pasvsocket, self.data_addr = self.datasocket.accept()
                self.log(b"PASV Accepted")

        async def PORT(self):
                """ Ftp command PORT """
                items = self.payload.split(b",")
                if len(items) >= 6:
                        self.data_addr = b'.'.join(items[:4])
                        if self.data_addr == b"127.0.1.1":
                                self.data_addr = self.remoteaddr
                        self.dataport = int(items[4]) * 256 + int(items[5])
                        self.close_pasv()
                        self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                        self.pasvsocket.settimeout(1000)
                        self.pasvsocket.connect((self.data_addr, self.dataport))
                        self.log("Data connection with: %s"%tools.strings.tostrings(self.data_addr))
                        await self.send_response(200, b"OK")
                else:
                        await self.send_response(504, b"Fail")

        async def NLST(self):
                """ Ftp command NLST """
                await self.LIST()

        async def LIST(self):
                """ Ftp command LIST """
                if not self.payload.startswith(b"-"):
                        place = self.path
                else:
                        place = self.cwd
                await self.send_response(150, b"Connection accepted.") # Start list files
                listsocket = server.stream.Socket(self.pasvsocket)
                self.log("List %s"%(tools.strings.tostrings(self.root+place)))
                self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l")
                listsocket.close()
                await self.send_response(226, b"Transfert complete.") # End list files
                self.close_pasv()

        async def STAT(self):
                """ Ftp command STAT """
                if self.payload == b"":
                        await self.send_response(211, b"Connected to (%s)"%self.remoteaddr[0])
                        await self.send_response(211, b"Data address (%s)"%self.addr)
                        await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream")
                else:
                        await self.send_response(213,b"Directory listing:")
                        self.log("List %s"%tools.strings.tostrings(self.root+self.path))
                        self.send_file_list(self.root + self.path, self.client, True)
                        await self.send_response(213, b"Stat end")

        async def RETR(self):
                """ Ftp command RETR """
                await self.send_response(150, b"Start send file")
                self.log("Send %s"%tools.strings.tostrings(self.root+self.path), write=True)
                filename = self.root + self.path

                if tools.filesystem.ismicropython():
                        buffer_size = 1440
                        chunk = bytearray(buffer_size)
                        with open(tools.strings.tostrings(filename), "r") as file:
                                length = file.readinto(chunk)
                                while length > 0:
                                        # pylint: disable=no-member
                                        sent = self.pasvsocket.write(chunk[:length])
                                        length = file.readinto(chunk)
                else:
                        with open(tools.strings.tostrings(filename), "rb") as file:
                                self.pasvsocket.sendall(file.read())
                await self.send_response(226, b"End send file")
                self.close_pasv()

        def close_pasv(self):
                """ Close PASV connection """
                if self.pasvsocket is not None:
                        self.log(b"Close PASV")
                        self.pasvsocket.close()
                        self.pasvsocket = None

        def write_file(self, path, dataclient):
                """ Write ftp received """
                chunk = bytearray(1440)
                with open(tools.strings.tostrings(path), "wb") as file:
                        length = dataclient.readinto(chunk)
                        while length > 0:
                                file.write(chunk, length)
                                length = dataclient.readinto(chunk)

        async def STOR(self):
                """ Ftp command STOR """
                await self.send_response(150, b"Start receive file")
                self.log("Receive %s"%tools.strings.tostrings(self.root + self.path), write=True)
                filename = self.root + self.path

                if tools.filesystem.ismicropython():
                        try:
                                self.write_file(filename, self.pasvsocket)
                        except Exception as err:
                                self.log(err, write=True)
                                directory, file = tools.filesystem.split(tools.strings.tostrings(filename))
                                tools.filesystem.makedir(directory, True)
                                self.write_file(filename, self.pasvsocket)
                else:
                        with open(filename, "wb") as file:
                                data = b" "
                                while len(data) > 0:
                                        data = self.pasvsocket.recv(1440)
                                        file.write(data)
                                data = b""
                await self.send_response(226, b"End receive file")
                self.close_pasv()

        async def DELE(self):
                """ Ftp command DELE """
                self.log("Delete %s"%tools.strings.tostrings(self.root + self.path), write=True)
                os.remove(tools.strings.tostrings(self.root + self.path))
                await self.send_ok()

        async def XRMD(self):
                """ Ftp command XRMD """
                await self.RMD()

        async def RMD(self):
                """ Ftp command RMD """
                os.rmdir(tools.strings.tostrings(self.root + self.path))
                await self.send_ok()

        async def XMKD(self):
                """ Ftp command XMKD """
                await self.MKD()

        async def MKD(self):
                """ Ftp command MKD """
                os.mkdir(tools.strings.tostrings(self.root + self.path))
                await self.send_ok()

        async def RNFR(self):
                """ Ftp command RNFR """
                self.fromname = self.path
                await self.send_response(350, b"Rename from")

        async def RNTO(self):
                """ Ftp command RNTO """
                if self.fromname is not None:
                        self.log("Rename %s to %s"%(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)), write=True)
                        os.rename(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path))
                        await self.send_ok()
                else:
                        await self.send_error(self.fromname)
                self.fromname = None

        async def QUIT(self):
                """ Ftp command QUIT """
                self.quit = True
                await self.send_response(221, b"Bye.")

        async def unsupported_command(self):
                """ Ftp unknown command """
                await self.send_response(502, b"Unsupported command")

        async def receive_command(self):
                """ Ftp command reception """
                tools.tasking.Tasks.slow_down()
                try:
                        self.received = await self.client.readline()
                except Exception as err:
                        self.log(err, write=True)
                        self.log(b"Reset connection")
                        self.quit = True

                if len(self.received) <= 0:
                        self.quit = True
                else:
                        self.received = self.received.rstrip(b"\r\n")
                        if tools.strings.tobytes(self.received[:4]) == b"PASS":
                                message = b"PASS ????"
                        else:
                                message = self.received
                        self.command = self.received.split(b" ")[0].upper()
                        self.payload = self.received[len(self.command):].lstrip()
                        self.path = tools.filesystem.abspathbytes(self.cwd, self.payload)
                        self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path))

        async def treat_command(self):
                """ Treat ftp command """
                tools.tasking.Tasks.slow_down()
                if self.quit is False:
                        try:
                                command = tools.strings.tostrings(self.command)
                                if hasattr(self, command):
                                        callback = getattr(self, command)

                                        if self.command not in [b"USER",b"PASS"]:
                                                if server.user.User.check(self.user, self.password):
                                                        await callback()
                                                else:
                                                        await self.send_response(430, b"Invalid username or password")
                                        else:
                                                await callback()
                                else:
                                        await self.unsupported_command()
                        except Exception as err:
                                self.log(err, write=True)
                                await self.send_error(err)

        async def on_connection(self, reader, writer):
                """ Asyncio on ftp connection method """
                tools.tasking.Tasks.slow_down()
                self.remoteaddr = tools.strings.tobytes(writer.get_extra_info('peername')[0])
                self.addr = self.get_ip()
                self.log("Connected from %s"%tools.strings.tostrings(self.remoteaddr), write=True)
                self.client = server.stream.Stream(reader, writer)
                try:
                        await self.send_response(220, b"Ftp " + tools.strings.tobytes(os.uname()[4]) + b".")
                        self.quit = False
                        while self.quit is False:
                                await self.receive_command()
                                await self.treat_command()
                except Exception as err:
                        self.log(err, write=True)
                        await self.send_error(err)
                finally:
                        self.close_pasv()
                        await self.client.close()
                self.log("Disconnected", write=True)

Class variables

var portbase

Methods

async def CDUP(self)

Ftp command CDUP

Expand source code
async def CDUP(self):
        """ Ftp command CDUP """
        self.cwd = tools.filesystem.abspathbytes(self.cwd, b"..")
        await self.send_ok()
async def CWD(self)

Ftp command CWD

Expand source code
async def CWD(self):
        """ Ftp command CWD """
        if len(self.path) <= self.path_length:
                try:
                        dd = os.listdir(tools.strings.tostrings(self.root + self.path))
                        self.cwd = self.path
                        await self.send_response(250,b"CWD command successful.")
                except Exception as err:
                        self.log(err, write=True)
                        await self.send_error(b"Path not existing")
        else:
                await self.send_error(b"Path too long")
async def DELE(self)

Ftp command DELE

Expand source code
async def DELE(self):
        """ Ftp command DELE """
        self.log("Delete %s"%tools.strings.tostrings(self.root + self.path), write=True)
        os.remove(tools.strings.tostrings(self.root + self.path))
        await self.send_ok()
async def FEAT(self)

Ftp command FEAT

Expand source code
async def FEAT(self):
        """ Ftp command FEAT """
        await self.send_response(211, b"no-features")
async def LIST(self)

Ftp command LIST

Expand source code
async def LIST(self):
        """ Ftp command LIST """
        if not self.payload.startswith(b"-"):
                place = self.path
        else:
                place = self.cwd
        await self.send_response(150, b"Connection accepted.") # Start list files
        listsocket = server.stream.Socket(self.pasvsocket)
        self.log("List %s"%(tools.strings.tostrings(self.root+place)))
        self.send_file_list(self.root + place, listsocket, self.command == b"LIST" or self.payload == b"-l")
        listsocket.close()
        await self.send_response(226, b"Transfert complete.") # End list files
        self.close_pasv()
async def MKD(self)

Ftp command MKD

Expand source code
async def MKD(self):
        """ Ftp command MKD """
        os.mkdir(tools.strings.tostrings(self.root + self.path))
        await self.send_ok()
async def NLST(self)

Ftp command NLST

Expand source code
async def NLST(self):
        """ Ftp command NLST """
        await self.LIST()
async def NOOP(self)

Ftp command NOOP

Expand source code
async def NOOP(self):
        """ Ftp command NOOP """
        await self.send_response(200, b"OK")
async def PASS(self)

Ftp command PASS

Expand source code
async def PASS(self):
        """ Ftp command PASS """
        self.password = self.path[1:]
        if server.user.User.check(self.user, self.password, False):
                await self.send_response(230, b"Logged in.")
        else:
                await self.send_response(430, b"Invalid username or password")
async def PASV(self)

Ftp command PASV

Expand source code
async def PASV(self):
        """ Ftp command PASV """
        await self.send_response(227, b"Entering Passive Mode (%s,%d,%d)"%(self.addr.replace(b'.',b','), self.dataport>>8, self.dataport%256))
        self.close_pasv()
        self.pasvsocket, self.data_addr = self.datasocket.accept()
        self.log(b"PASV Accepted")
async def PORT(self)

Ftp command PORT

Expand source code
async def PORT(self):
        """ Ftp command PORT """
        items = self.payload.split(b",")
        if len(items) >= 6:
                self.data_addr = b'.'.join(items[:4])
                if self.data_addr == b"127.0.1.1":
                        self.data_addr = self.remoteaddr
                self.dataport = int(items[4]) * 256 + int(items[5])
                self.close_pasv()
                self.pasvsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                self.pasvsocket.settimeout(1000)
                self.pasvsocket.connect((self.data_addr, self.dataport))
                self.log("Data connection with: %s"%tools.strings.tostrings(self.data_addr))
                await self.send_response(200, b"OK")
        else:
                await self.send_response(504, b"Fail")
async def PWD(self)

Ftp command PWD

Expand source code
async def PWD(self):
        """ Ftp command PWD """
        await self.send_response(257,b'"%s" is current directory.'%self.cwd)
async def QUIT(self)

Ftp command QUIT

Expand source code
async def QUIT(self):
        """ Ftp command QUIT """
        self.quit = True
        await self.send_response(221, b"Bye.")
async def RETR(self)

Ftp command RETR

Expand source code
async def RETR(self):
        """ Ftp command RETR """
        await self.send_response(150, b"Start send file")
        self.log("Send %s"%tools.strings.tostrings(self.root+self.path), write=True)
        filename = self.root + self.path

        if tools.filesystem.ismicropython():
                buffer_size = 1440
                chunk = bytearray(buffer_size)
                with open(tools.strings.tostrings(filename), "r") as file:
                        length = file.readinto(chunk)
                        while length > 0:
                                # pylint: disable=no-member
                                sent = self.pasvsocket.write(chunk[:length])
                                length = file.readinto(chunk)
        else:
                with open(tools.strings.tostrings(filename), "rb") as file:
                        self.pasvsocket.sendall(file.read())
        await self.send_response(226, b"End send file")
        self.close_pasv()
async def RMD(self)

Ftp command RMD

Expand source code
async def RMD(self):
        """ Ftp command RMD """
        os.rmdir(tools.strings.tostrings(self.root + self.path))
        await self.send_ok()
async def RNFR(self)

Ftp command RNFR

Expand source code
async def RNFR(self):
        """ Ftp command RNFR """
        self.fromname = self.path
        await self.send_response(350, b"Rename from")
async def RNTO(self)

Ftp command RNTO

Expand source code
async def RNTO(self):
        """ Ftp command RNTO """
        if self.fromname is not None:
                self.log("Rename %s to %s"%(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path)), write=True)
                os.rename(tools.strings.tostrings(self.root + self.fromname), tools.strings.tostrings(self.root + self.path))
                await self.send_ok()
        else:
                await self.send_error(self.fromname)
        self.fromname = None
async def SIZE(self)

Ftp command SIZE

Expand source code
async def SIZE(self):
        """ Ftp command SIZE """
        size = tools.filesystem.filesize(tools.strings.tostrings(self.root + self.path))
        await self.send_response(213, b"%d"%(size))
async def STAT(self)

Ftp command STAT

Expand source code
async def STAT(self):
        """ Ftp command STAT """
        if self.payload == b"":
                await self.send_response(211, b"Connected to (%s)"%self.remoteaddr[0])
                await self.send_response(211, b"Data address (%s)"%self.addr)
                await self.send_response(211, b"TYPE: Binary STRU: File MODE: Stream")
        else:
                await self.send_response(213,b"Directory listing:")
                self.log("List %s"%tools.strings.tostrings(self.root+self.path))
                self.send_file_list(self.root + self.path, self.client, True)
                await self.send_response(213, b"Stat end")
async def STOR(self)

Ftp command STOR

Expand source code
async def STOR(self):
        """ Ftp command STOR """
        await self.send_response(150, b"Start receive file")
        self.log("Receive %s"%tools.strings.tostrings(self.root + self.path), write=True)
        filename = self.root + self.path

        if tools.filesystem.ismicropython():
                try:
                        self.write_file(filename, self.pasvsocket)
                except Exception as err:
                        self.log(err, write=True)
                        directory, file = tools.filesystem.split(tools.strings.tostrings(filename))
                        tools.filesystem.makedir(directory, True)
                        self.write_file(filename, self.pasvsocket)
        else:
                with open(filename, "wb") as file:
                        data = b" "
                        while len(data) > 0:
                                data = self.pasvsocket.recv(1440)
                                file.write(data)
                        data = b""
        await self.send_response(226, b"End receive file")
        self.close_pasv()
async def SYST(self)

Ftp command SYST

Expand source code
async def SYST(self):
        """ Ftp command SYST """
        await self.send_response(215, b"UNIX Type: L8")
async def TYPE(self)

Ftp command TYPE

Expand source code
async def TYPE(self):
        """ Ftp command TYPE """
        await self.send_response(200, b"Binary transfer mode active.")
async def USER(self)

Ftp command USER

Expand source code
async def USER(self):
        """ Ftp command USER """
        if server.user.User.get_user() == b"":
                await self.send_response(230, b"User Logged In.")
        else:
                self.user = self.path[1:]
                await self.send_response(331, b"User known, enter password")
async def XCWD(self)

Ftp command XCWD

Expand source code
async def XCWD(self):
        """ Ftp command XCWD """
        await self.CWD()
async def XMKD(self)

Ftp command XMKD

Expand source code
async def XMKD(self):
        """ Ftp command XMKD """
        await self.MKD()
async def XPWD(self)

Ftp command XPWD

Expand source code
async def XPWD(self):
        """ Ftp command XPWD """
        await self.PWD()
async def XRMD(self)

Ftp command XRMD

Expand source code
async def XRMD(self):
        """ Ftp command XRMD """
        await self.RMD()
def close(self)

Close all ftp connections

Expand source code
def close(self):
        """ Close all ftp connections """
        self.close_pasv()
        if self.datasocket:
                self.datasocket.close()
                self.datasocket = None
def close_pasv(self)

Close PASV connection

Expand source code
def close_pasv(self):
        """ Close PASV connection """
        if self.pasvsocket is not None:
                self.log(b"Close PASV")
                self.pasvsocket.close()
                self.pasvsocket = None
def get_file_description(self, filename, typ, size, current_date, now, full)

Build list of file description

Expand source code
def get_file_description(self, filename, typ, size, current_date, now, full):
        """ Build list of file description """
        if full:
                file_permissions = b"drwxr-xr-x" if (typ & 0xF000 == 0x4000) else b"-rw-r--r--"

                d = tools.date.local_time(current_date)
                year,month,day,hour,minute,_,_,_ = d[:8]

                if year != now[0] and month != now[1]:
                        file_date = b"%s %2d  %4d"%(MONTHS[month-1], day, year)
                else:
                        file_date = b"%s %2d %02d:%02d"%(MONTHS[month-1], day, hour, minute)
                description = b"%s    1 owner group %10d %s %s\r\n"%(file_permissions, size, file_date, tools.strings.tobytes(filename))
        else:
                description = tools.strings.tobytes(filename) + b"\r\n"
        return description
def get_ip(self)

Get the ip address of the board

Expand source code
def get_ip(self):
        """ Get the ip address of the board """
        if wifi.station.Station.is_ip_on_interface(self.remoteaddr):
                result = tools.strings.tobytes(wifi.station.Station.get_info()[0])
        else:
                result = tools.strings.tobytes(wifi.accesspoint.AccessPoint.get_info()[0])
        return result
def log(self, err, msg='', write=False)

Log message

Expand source code
def log(self, err, msg="", write=False):
        """ Log message """
        if write:
                tools.logger.syslog(err, msg=msg, write=write)
async def on_connection(self, reader, writer)

Asyncio on ftp connection method

Expand source code
async def on_connection(self, reader, writer):
        """ Asyncio on ftp connection method """
        tools.tasking.Tasks.slow_down()
        self.remoteaddr = tools.strings.tobytes(writer.get_extra_info('peername')[0])
        self.addr = self.get_ip()
        self.log("Connected from %s"%tools.strings.tostrings(self.remoteaddr), write=True)
        self.client = server.stream.Stream(reader, writer)
        try:
                await self.send_response(220, b"Ftp " + tools.strings.tobytes(os.uname()[4]) + b".")
                self.quit = False
                while self.quit is False:
                        await self.receive_command()
                        await self.treat_command()
        except Exception as err:
                self.log(err, write=True)
                await self.send_error(err)
        finally:
                self.close_pasv()
                await self.client.close()
        self.log("Disconnected", write=True)
async def receive_command(self)

Ftp command reception

Expand source code
async def receive_command(self):
        """ Ftp command reception """
        tools.tasking.Tasks.slow_down()
        try:
                self.received = await self.client.readline()
        except Exception as err:
                self.log(err, write=True)
                self.log(b"Reset connection")
                self.quit = True

        if len(self.received) <= 0:
                self.quit = True
        else:
                self.received = self.received.rstrip(b"\r\n")
                if tools.strings.tobytes(self.received[:4]) == b"PASS":
                        message = b"PASS ????"
                else:
                        message = self.received
                self.command = self.received.split(b" ")[0].upper()
                self.payload = self.received[len(self.command):].lstrip()
                self.path = tools.filesystem.abspathbytes(self.cwd, self.payload)
                self.log(b"'%s' id=%08X cwd='%s' payload='%s' path='%s'"%(message, id(self), self.cwd, self.payload, self.path))
async def send_error(self, err)

Send error to ftp client

Expand source code
async def send_error(self, err):
        """ Send error to ftp client """
        showError = False
        if type(err) != type(b""):
                if tools.filesystem.ismicropython():
                        if type(err) != type(OSError):
                                showError = True
                else:
                        if isinstance(err,FileNotFoundError) or isinstance(err,NotADirectoryError):
                                showError = False
                        else:
                                showError = True
        if showError:
                self.log(err, msg=b"cmd='%s' cwd='%s' root='%s' path='%s' payload='%s'"%(self.command, self.cwd, self.root, self.path, self.payload))
        await self.send_response(550, b"Failed")
def send_file_list(self, path, stream_, full)

Send the list of file

Expand source code
def send_file_list(self, path, stream_, full):
        """ Send the list of file """
        now = tools.date.local_time()
        try:
                self.send_file_list_with_pattern(path, stream_, full, now)
        except Exception as err:
                self.log(err, write=True)
                pattern = path.split(b"/")[-1]
                path = path[:-(len(pattern) + 1)]
                if path == b"":
                        path = b"/"
                self.send_file_list_with_pattern(path, stream_, full, now, pattern)
def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None)

Send the list of file with pattern

Expand source code
def send_file_list_with_pattern(self, path, stream_, full, now, pattern=None):
        """ Send the list of file with pattern """
        description = b""
        quantity = 0
        counter = 0
        for fileinfo in tools.filesystem.list_directory(tools.strings.tostrings(path)):
                filename = fileinfo[0]
                typ = fileinfo[1]
                if len(fileinfo) > 3:
                        size = fileinfo[3]
                else:
                        size = 0
                if pattern is None:
                        accepted = True
                else:
                        accepted = tools.fnmatch.fnmatch(tools.strings.tostrings(filename), tools.strings.tostrings(pattern))
                if accepted:
                        if quantity > 100:
                                current_date = 0
                        else:
                                sta = (0,0,0,0,0,0,0,0,0)
                                try:
                                        # If it is a file
                                        if not (typ & 0xF000 == 0x4000):
                                                sta = tools.filesystem.fileinfo(tools.strings.tostrings(tools.filesystem.abspathbytes(path,tools.strings.tobytes(filename))))
                                except Exception:
                                        pass
                                current_date = sta[8]

                        description += self.get_file_description(filename, typ, size, current_date, now, full)
                        counter += 1
                        if counter == 20:
                                counter = 0
                                stream_.write(description)
                                description = b""
                quantity += 1
        if description != b"":
                stream_.write(description)
async def send_ok(self)

Send ok to ftp client

Expand source code
async def send_ok(self):
        """ Send ok to ftp client """
        await self.send_response(250,b"OK")
async def send_response(self, code, message)

Send response to ftp client

Expand source code
async def send_response(self, code, message):
        """ Send response to ftp client """
        self.log(b"%d %s"%(code, message))
        await self.client.write(b'%d %s\r\n'%(code,message))
async def treat_command(self)

Treat ftp command

Expand source code
async def treat_command(self):
        """ Treat ftp command """
        tools.tasking.Tasks.slow_down()
        if self.quit is False:
                try:
                        command = tools.strings.tostrings(self.command)
                        if hasattr(self, command):
                                callback = getattr(self, command)

                                if self.command not in [b"USER",b"PASS"]:
                                        if server.user.User.check(self.user, self.password):
                                                await callback()
                                        else:
                                                await self.send_response(430, b"Invalid username or password")
                                else:
                                        await callback()
                        else:
                                await self.unsupported_command()
                except Exception as err:
                        self.log(err, write=True)
                        await self.send_error(err)
async def unsupported_command(self)

Ftp unknown command

Expand source code
async def unsupported_command(self):
        """ Ftp unknown command """
        await self.send_response(502, b"Unsupported command")
def write_file(self, path, dataclient)

Write ftp received

Expand source code
def write_file(self, path, dataclient):
        """ Write ftp received """
        chunk = bytearray(1440)
        with open(tools.strings.tostrings(path), "wb") as file:
                length = dataclient.readinto(chunk)
                while length > 0:
                        file.write(chunk, length)
                        length = dataclient.readinto(chunk)