giampaolo / pyftpdlib

Extremely fast and scalable Python FTP server library
MIT License
1.65k stars 265 forks source link

Bug: Filename can't contains Chinese characters on Windows #625

Closed znsoooo closed 1 day ago

znsoooo commented 2 weeks ago

I use the command python -m pyftpdlib -wp 21 to launch the FTP server on a Windows platform with a Chinese locale. However, I found that it does not work when the file or folder name contains Chinese characters; the file name string becomes unreadable:

image

When I made this change, it worked again:

import glob
for file in glob.glob('pyftpdlib/*.py'):
    with open(file) as f:
        text = f.read()
    with open(file, 'w') as f:
        f.write(text.replace("'utf8'", "'gbk'"))

However, I don't know if this is the minimal change needed for the bug fix, and it obviously won't work on non-Chinese locale platforms.

So, can you fix this bug in the library? Although I have already fixed this bug by changing the local source code, the solution feels like a dirty workaround. :<

Maybe hardcoding "gbk" could be replaced by using the function locale.getpreferredencoding(False) to get the setting. I don't know if this will work universally.

I can test it on a Chinese version of the Windows platform.

giampaolo commented 2 weeks ago

This happens because pyftpdlib uses UTF-8 by default, and it currently does not give the ability to change the encoding to something different. A long time has gone by, but I think I did that on purpose because https://datatracker.ietf.org/doc/html/rfc2640 mandates that. To put it another way, there is not way for the FTP client to tell the server "please use encoding XXX instead of UTF8", so the server should always assumes UTF-8.

With that said, when exactly do you experience the problem? When you LIST the files? When you download a file?

znsoooo commented 2 weeks ago

This happens because pyftpdlib uses UTF-8 by default, and it currently does not give the ability to change the encoding to something different. A long time has gone by, but I think I did that on purpose because https://datatracker.ietf.org/doc/html/rfc2640 mandates that. To put it another way, there is not way for the FTP client to tell the server "please use encoding XXX instead of UTF8", so the server should always assumes UTF-8.

To @giampaolo :

The pyftpdlib library can be extended on a standard basis, such as adding a switch to modify the default encoding.

The client is unable to tell the server of which encoding to use, but the server can decide for itself which encoding it wishes to use.

Additionally, I believe that setting up an FTP server on Windows is a fairly common requirement, making this a widely encountered issue.

With that said, when exactly do you experience the problem? When you LIST the files? When you download a file?

The problem occurs when listing files, uploading files, and downloading files. In the screenshots, the Chinese "新建文件夹" means "New Folder", which is the default name for newly created folders on Windows.

Of course, it's not just these characters; other Chinese characters in file names also encounter garbled text.

giampaolo commented 2 weeks ago

The pyftpdlib library can be extended on a standard basis, such as adding a switch to modify the default encoding.

That seems fair. We can expose a configurable option(s) to set an encoding different than UTF8. Still, I expect most clients to still send UTF-8 paths by default though, so I'm not sure how to do this exactly. It must be noted that there are 2 encodings at play here though:

This is not an easy topic. I definitively should re-read https://datatracker.ietf.org/doc/html/rfc2640. Also http://www.proftpd.org/docs/modules/mod_lang.html could be used as a guidance.

giampaolo commented 2 weeks ago

Note: proftpd indeed lets you configure TWO encodings, local and client, so my logical distinction appears correct. From http://www.proftpd.org/docs/modules/mod_lang.html:

UseEncoding Syntax: UseEncoding on|off|local-charset client-charset [...] [...] UseEncoding directive allows administrators to specify exactly which character sets to use locally (i.e. for paths on local disks) and for dealing with clients. One such usage this way might look like: UseEncoding koi8-r cp1251.

znsoooo commented 2 weeks ago

To @giampaolo :

I noticed that you are the author of pyftpdlib, and I want to express my gratitude for your contributions.

The library is already very good, but if you have any free time, I hope it can be improved better.

If you have difficulties on work time, I am willing to do a pull request, but I am not very good at coding, so it may not be very efficient.


Perhaps the AbstractedFS class should use sys.getfilesystemencoding() as the default instead of utf8, but I'm not completely sure.

I am sure it's not the sys.getfilesystemencoding(), because on my computer with bug, sys.getfilesystemencoding() returns utf-8, and I only fixed the bug by changing it to gbk.

So I suspect it should be 'cp936' = locale.getpreferredencoding(False) (the default config for "open" write file encoding), but not the 'utf-8' = sys.getfilesystemencoding().

znsoooo commented 2 weeks ago

@giampaolo

Addition: I tested setting up a server on Linux and accessing the FTP server by using Explorer on Windows, but the garbled text issue still exists. However, on Ubuntu, both methods ( "locale.getpreferredencoding(False)" and "sys.getfilesystemencoding()" ) result is "UTF-8".

So, this seems to automatically prove that proper encoding from the server side is impossible. Adding an option to set the "correct" encoding might be the only way to solve the problem.

giampaolo commented 2 weeks ago

Mmmm I doubt server-side encoding is broken on Linux. It seems more likely that it's Windows Exporer which is at fault here (wouldn't be a first). Can you try with FileZilla client?

znsoooo commented 2 weeks ago

@giampaolo

On FileZilla, both upload and download is ok:

image

giampaolo commented 2 weeks ago

Yeah, I suspected so. Another thing you can try is the following: start pyftpdlib with DEBUG logging enabled and see what commands Windows Explorer sends. See: https://pyftpdlib.readthedocs.io/en/latest/tutorial.html#debug-logging. I suspect Windows Explorer sends some non-compliant FTP commands to force the encoding somehow, that pyftpdlib will ignore. When I started writing pyftpdlib almost 20 years I was on Windows and clearly remember how bad and uncompliant Windows Explorer and Microsoft IIS FTP server were.

znsoooo commented 2 weeks ago

@giampaolo

Retrun this:

K:\git\oppo\pyftpdlib-1.5.9>python -m pyftpdlib -wp 2121 -D
K:\git\oppo\pyftpdlib-1.5.9\pyftpdlib\authorizers.py:108: RuntimeWarning: write permissions assigned to anonymous user.
  self._check_permissions(username, perm)
[I 2024-06-21 21:17:45] concurrency model: async
[I 2024-06-21 21:17:45] masquerade (NAT) address: None
[I 2024-06-21 21:17:45] passive ports: None
[D 2024-06-21 21:17:45] poller: 'pyftpdlib.ioloop.Select'
[D 2024-06-21 21:17:45] authorizer: 'pyftpdlib.authorizers.DummyAuthorizer'
[D 2024-06-21 21:17:45] handler: 'pyftpdlib.handlers.type'
[D 2024-06-21 21:17:45] max connections: 512
[D 2024-06-21 21:17:45] max connections per ip: unlimited
[D 2024-06-21 21:17:45] timeout: 300
[D 2024-06-21 21:17:45] banner: 'pyftpdlib 1.5.9 ready.'
[D 2024-06-21 21:17:45] max login attempts: 3
[I 2024-06-21 21:17:45] >>> starting FTP server on 0.0.0.0:2121, pid=17792 <<<
[I 2024-06-21 21:18:15] 127.0.0.1:53151-[] FTP session opened (connect)
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[] -> 220 pyftpdlib 1.5.9 ready.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[] <- USER anonymous
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[] -> 331 Username ok, send password.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- PASS ******
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 230 Login successful.
[I 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] USER 'anonymous' logged in.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- opts utf8 on
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 501 Invalid argument.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- syst
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 215 UNIX Type: L8
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- site help
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 214 Help SITE command successful.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- PWD
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 257 "/" is the current directory.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- TYPE A
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 200 Type set to: ASCII.
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- PASV
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 227 Entering passive mode (127,0,0,1,207,161).
[D 2024-06-21 21:18:15] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1aa7def0ef0>)
[D 2024-06-21 21:18:15] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1aa7def0ef0>)
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- LIST
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 125 Data connection already open. Transfer starting.
[D 2024-06-21 21:18:15] [debug] starting transfer using send() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:15] [debug] call: close() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 226 Transfer complete.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- TYPE A
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 Type set to: ASCII.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- PASV
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 227 Entering passive mode (127,0,0,1,207,183).
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1aa7dec5dd8>)
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1aa7dec5dd8>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- LIST
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 125 Data connection already open. Transfer starting.
[D 2024-06-21 21:18:40] [debug] starting transfer using send() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:40] [debug] call: close() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 226 Transfer complete.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- TYPE I
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 Type set to: Binary.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- PASV
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 227 Entering passive mode (127,0,0,1,207,185).
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1aa7dec5e10>)
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1aa7dec5e10>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- STOR ��Ӿ�˶�Ա�ȼ���׼.txt
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 125 Data connection already open. Transfer starting.
[D 2024-06-21 21:18:40] [debug] call: close() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous', sending-file=<_io.BufferedWriter name='K:\\git\\oppo\\pyftpdlib-1.5.9\\��Ӿ�˶�Ա�ȼ���\u05fc.txt'>, bytes-trans=3468)>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 226 Transfer complete.
[I 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] STOR K:\git\oppo\pyftpdlib-1.5.9\��Ӿ�˶�Ա�ȼ���׼.txt completed=1 bytes=3468 seconds=0.031
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- TYPE A
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 Type set to: ASCII.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- PASV
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 227 Entering passive mode (127,0,0,1,207,187).
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1aa7dec5dd8>)
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1aa7dec5dd8>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- LIST
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 125 Data connection already open. Transfer starting.
[D 2024-06-21 21:18:40] [debug] starting transfer using send() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:40] [debug] call: close() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 226 Transfer complete.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- TYPE I
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 200 Type set to: Binary.
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- PASV
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 227 Entering passive mode (127,0,0,1,207,189).
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] <- STOR �����������Ӫͼ.xlsx
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 150 File status okay. About to open data connection.
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1aa7dec5e10>)
[D 2024-06-21 21:18:40] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1aa7dec5e10>)
[D 2024-06-21 21:18:40] [debug] call: close() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous', sending-file=<_io.BufferedWriter name='K:\\git\\oppo\\pyftpdlib-1.5.9\\�����������Ӫͼ.xlsx'>, bytes-trans=3581948)>)
[D 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] -> 226 Transfer complete.
[I 2024-06-21 21:18:40] 127.0.0.1:53151-[anonymous] STOR K:\git\oppo\pyftpdlib-1.5.9\�����������Ӫͼ.xlsx completed=1 bytes=3581948 seconds=0.141
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] <- noop
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] -> 200 I successfully did nothing'.
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] <- CWD /
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] -> 250 "/" is the current directory.
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] <- TYPE A
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] -> 200 Type set to: ASCII.
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] <- PASV
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] -> 227 Entering passive mode (127,0,0,1,207,192).
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] <- LIST
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] -> 150 File status okay. About to open data connection.
[D 2024-06-21 21:18:42] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1aa7dec5dd8>)
[D 2024-06-21 21:18:42] [debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1aa7dec5dd8>)
[D 2024-06-21 21:18:42] [debug] starting transfer using send() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:42] [debug] call: close() (<DTPHandler(id=1831730189256, addr='127.0.0.1:53151', user='anonymous')>)
[D 2024-06-21 21:18:42] 127.0.0.1:53151-[anonymous] -> 226 Transfer complete.

Some lines of "STOR" became garbled text.

znsoooo commented 2 weeks ago

@giampaolo , I don't know if you noticed my previous reply, I can fix this garbled problem with a hardcoded, but I don't know if this is a minimal change.

I don't know if this will help you solve the problem:

import glob

for file in glob.glob('pyftpdlib/*.py'):
    with open(file) as f:
        text = f.read()
    with open(file, 'w') as f:
        f.write(text.replace("'utf8'", "'gbk'"))
giampaolo commented 2 weeks ago

This is what Windows Explorer sends:

[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] <- opts utf8 on
[D 2024-06-21 21:18:15] 127.0.0.1:53151-[anonymous] -> 501 Invalid argument.

opts utf8 on is not a valid command, but perhaps the fact that pyftpdlib replies with 501 makes Windows Explorer think that the encoding is ASCII or something. Can you try this for me? It forces the server to reply with 2XX:

import os

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

class Handler(FTPHandler):
    def ftp_OPTS(self, line):
        self.respond('200 OK')

def main():
    authorizer = DummyAuthorizer()
    authorizer.add_user('user', '12345', '.', perm='elradfmwMT')
    authorizer.add_anonymous(os.getcwd(), perm='elradfmwMT')
    handler = Handler
    handler.authorizer = authorizer
    address = ('', 2121)
    server = FTPServer(address, handler)
    server.serve_forever()

if __name__ == '__main__':
    main()
znsoooo commented 2 weeks ago

@giampaolo : Amazing! It worked.

By the way, to make the program work, I need to change the line:

address = ('127.0.0.1', 2121)

And I tried to reproduce the problem after I modified the line:

handler = FTPHandler  # I think this will make nothing happen

Doing so will reproduce the garbled problem.


Windows Explorer think that the encoding is ASCII or something

I think it should be ANSI encoding, ANSI encoding is explained as gbk encoding in Chinese computers, and in Japan or South Korea, it will explained as their national encodings.

znsoooo commented 2 weeks ago

@giampaolo Bad news!

I found that files containing specific Chinese nouns failed to be copied, and it has nothing to do with the content of the file.

I tested with the following code:

import os
import logging

from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer

logging.basicConfig(level=logging.DEBUG)

class Handler(FTPHandler):
    def ftp_OPTS(self, line):
        self.respond('200 OK')

def main():
    authorizer = DummyAuthorizer()
    authorizer.add_user('user', '12345', '.', perm='elradfmwMT')
    authorizer.add_anonymous(os.getcwd(), perm='elradfmwMT')
    handler = Handler
    handler.authorizer = authorizer
    address = ('127.0.0.1', 2121)
    server = FTPServer(address, handler)
    server.serve_forever()

if __name__ == '__main__':
    main()

I try to copy the following files separately, one at a time, the first three copies failed, and the last one succeeded, as a comparison:

硬件检测.txt
装机单.txt
任务管理器.txt
新建文本文档.txt

When the copy fails, it will prompt an error like this:

image

Translate:

FTP folder error
An error occurred while copying the file to the FTP server, check if you have permission to put the file on the server.
Details:
...

The debug log message is:

Warning (from warnings module):
  File "K:\git\oppo\pyftpdlib-1.5.9\pyftpdlib\authorizers.py", line 108
    self._check_permissions(username, perm)
RuntimeWarning: write permissions assigned to anonymous user.
INFO:pyftpdlib:concurrency model: async
INFO:pyftpdlib:masquerade (NAT) address: None
INFO:pyftpdlib:passive ports: None
DEBUG:pyftpdlib:poller: 'pyftpdlib.ioloop.Select'
DEBUG:pyftpdlib:authorizer: 'pyftpdlib.authorizers.DummyAuthorizer'
DEBUG:pyftpdlib:handler: '__main__.type'
DEBUG:pyftpdlib:max connections: 512
DEBUG:pyftpdlib:max connections per ip: unlimited
DEBUG:pyftpdlib:timeout: 300
DEBUG:pyftpdlib:banner: 'pyftpdlib 1.5.9 ready.'
DEBUG:pyftpdlib:max login attempts: 3
INFO:pyftpdlib:>>> starting FTP server on 127.0.0.1:2121, pid=19876 <<<
INFO:pyftpdlib:127.0.0.1:56740-[] FTP session opened (connect)
DEBUG:pyftpdlib:127.0.0.1:56740-[] -> 220 pyftpdlib 1.5.9 ready.
DEBUG:pyftpdlib:127.0.0.1:56740-[] <- USER anonymous
DEBUG:pyftpdlib:127.0.0.1:56740-[] -> 331 Username ok, send password.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASS ******
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 230 Login successful.
INFO:pyftpdlib:127.0.0.1:56740-[anonymous] USER 'anonymous' logged in.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- opts utf8 on
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 OK
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PWD
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 257 "/" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE A
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: ASCII.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,166).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- LIST
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 150 File status okay. About to open data connection.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] starting transfer using send() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 226 Transfer complete.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE I
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: Binary.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,168).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- STOR 装机�?txt
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 550 Invalid argument.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b35b0>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b35b0>)
INFO:pyftpdlib:127.0.0.1:56745-[] FTP session opened (connect)
DEBUG:pyftpdlib:127.0.0.1:56745-[] -> 220 pyftpdlib 1.5.9 ready.
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56745-[] <- USER anonymous
DEBUG:pyftpdlib:127.0.0.1:56745-[] -> 331 Username ok, send password.
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] <- PASS ******
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] -> 230 Login successful.
INFO:pyftpdlib:127.0.0.1:56745-[anonymous] USER 'anonymous' logged in.
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] <- opts utf8 on
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] -> 200 OK
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] <- PWD
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] -> 257 "/" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] <- DELE 装机单.txt
DEBUG:pyftpdlib:127.0.0.1:56745-[anonymous] -> 550 No such file or directory.
DEBUG:pyftpdlib:[debug] call: close() (<Handler(id=2164598190864, addr='127.0.0.1:56745', user='anonymous')>)
INFO:pyftpdlib:127.0.0.1:56745-[anonymous] FTP session closed (disconnect).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE A
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: ASCII.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,173).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- LIST
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 150 File status okay. About to open data connection.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] starting transfer using send() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 226 Transfer complete.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE I
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: Binary.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,175).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- STOR 硬件检�?txt
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 550 Invalid argument.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b35b0>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b35b0>)
INFO:pyftpdlib:127.0.0.1:56752-[] FTP session opened (connect)
DEBUG:pyftpdlib:127.0.0.1:56752-[] -> 220 pyftpdlib 1.5.9 ready.
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56752-[] <- USER anonymous
DEBUG:pyftpdlib:127.0.0.1:56752-[] -> 331 Username ok, send password.
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] <- PASS ******
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] -> 230 Login successful.
INFO:pyftpdlib:127.0.0.1:56752-[anonymous] USER 'anonymous' logged in.
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] <- opts utf8 on
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] -> 200 OK
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] <- PWD
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] -> 257 "/" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] <- DELE 硬件检测.txt
DEBUG:pyftpdlib:127.0.0.1:56752-[anonymous] -> 550 No such file or directory.
DEBUG:pyftpdlib:[debug] call: close() (<Handler(id=2164598190864, addr='127.0.0.1:56752', user='anonymous')>)
INFO:pyftpdlib:127.0.0.1:56752-[anonymous] FTP session closed (disconnect).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE A
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: ASCII.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,179).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- LIST
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 150 File status okay. About to open data connection.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] starting transfer using send() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 226 Transfer complete.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE I
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: Binary.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,181).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- STOR 任务管理�?txt
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 550 Invalid argument.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b35b0>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b35b0>)
INFO:pyftpdlib:127.0.0.1:56759-[] FTP session opened (connect)
DEBUG:pyftpdlib:127.0.0.1:56759-[] -> 220 pyftpdlib 1.5.9 ready.
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56759-[] <- USER anonymous
DEBUG:pyftpdlib:127.0.0.1:56759-[] -> 331 Username ok, send password.
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] <- PASS ******
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] -> 230 Login successful.
INFO:pyftpdlib:127.0.0.1:56759-[anonymous] USER 'anonymous' logged in.
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] <- opts utf8 on
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] -> 200 OK
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] <- PWD
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] -> 257 "/" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] <- DELE 任务管理器.txt
DEBUG:pyftpdlib:127.0.0.1:56759-[anonymous] -> 550 No such file or directory.
DEBUG:pyftpdlib:[debug] call: close() (<Handler(id=2164598190864, addr='127.0.0.1:56759', user='anonymous')>)
INFO:pyftpdlib:127.0.0.1:56759-[anonymous] FTP session closed (disconnect).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE A
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: ASCII.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,185).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- LIST
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 150 File status okay. About to open data connection.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b3310>)
DEBUG:pyftpdlib:[debug] starting transfer using send() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 226 Transfer complete.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- noop
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 I successfully did nothing'.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- CWD /new/
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 250 "/new" is the current directory.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- TYPE I
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 200 Type set to: Binary.
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- PASV
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 227 Entering passive mode (127,0,0,1,221,187).
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] <- STOR 新建文本文档.txt
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 150 File status okay. About to open data connection.
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP listening 127.0.0.1:0 at 0x1f7fc1b35b0>)
DEBUG:pyftpdlib:[debug] call: close() (<pyftpdlib.handlers.PassiveDTP 127.0.0.1:0 at 0x1f7fc1b35b0>)
DEBUG:pyftpdlib:[debug] call: close() (<DTPHandler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous', sending-file=<_io.BufferedWriter name='K:\\git\\oppo\\pyftpdlib-1.5.9\\new\\新建文本文档.txt'>, bytes-trans=0)>)
DEBUG:pyftpdlib:127.0.0.1:56740-[anonymous] -> 226 Transfer complete.
INFO:pyftpdlib:127.0.0.1:56740-[anonymous] STOR K:\git\oppo\pyftpdlib-1.5.9\new\新建文本文档.txt completed=1 bytes=0 seconds=0.047
INFO:pyftpdlib:127.0.0.1:56740-[anonymous] -> 421 Control connection timed out.
DEBUG:pyftpdlib:[debug] call: close() (<Handler(id=2164598190624, addr='127.0.0.1:56740', user='anonymous')>)
INFO:pyftpdlib:127.0.0.1:56740-[anonymous] FTP session closed (disconnect).

The above problem does not occur when hardcoding "utf8" to "gbk".

giampaolo commented 1 day ago

@znsoooo I have created PR https://github.com/giampaolo/pyftpdlib/issues/625 which adds a new FTPHandler.encoding attribute which you can set as such:

handler = Handler
handler.authorizer = authorizer
handler.encoding = "gbk"
address = ('127.0.0.1', 2121)
server = FTPServer(address, handler)
server.serve_forever()

This should solve your specific problem.