Downloading file with a space in filename using ftplib - python

I'm downloading two files from a C-More industrial HMI FTP server. I don't know what OS the HMI is running but I suspect that its FTP server has some quirks. Using Jython 2.7, one file can be read without difficulty but the other has a space in the file name and the normal wrap-in-quotes solution doesn't work - yet.
The following works in the Windows 10 FTP client.
ftp> get NO_SPACES.csv
200 PORT command successful.
150 Opening ASCII mode data connection for NO_SPACES.csv.
226 Transfer complete.
ftp: 12774 bytes received in 0.27Seconds 47.66Kbytes/sec.
ftp> get "WITH SPACE.csv"
200 PORT command successful.
150 Opening ASCII mode data connection for WITH SPACE.csv.
226 Transfer complete.
ftp: 6328 bytes received in 0.02Seconds 316.40Kbytes/sec.
So far, so good. Now try it in Python:
ftp = FTP(myIP) # Connect.
ftp.login(userName, password) # Login.
ftp.set_pasv(False) # Required by the C-More panel for some reason.
with io.BytesIO() as binary_buffer:
# read all of products into a binary buffer
# ftp.retrbinary("RETR NO_SPACES.csv", binary_buffer.write) # This line works.
ftp.retrbinary('RETR "WITH SPACE.csv"', binary_buffer.write) # This one doesn't.
The script console in my development system reports:
ftplib.error_perm: 550 "WITH SPACE.csv": Requested action not taken.
Filenames have been changed to protect the innocent.
Windows FTP likes the get command. Python seems to favour RETR.
I've tried 'RETR "WITH SPACE.csv"' and "RETR 'WITH SPACE.csv'". Same result.
If I have to I can rename the files in the HMI but that will require some validation and paperwork and that's no fun.
I'm developing this on the latest version of Inductive Automation's Ignition! SCADA system which uses Jython 2.7.
Has anyone got any ideas for me to try?

The ftplib has no issue with spaces. The problem are the quotes you add to the RETR command. There should be no quotes:
ftp.retrbinary('RETR WITH SPACE.csv', binary_buffer.write)
If you enable the debug mode in ftp using the -d switch, you will see that it also sends no quotes to the FTP server in the RETR command:
ftp> get "WITH SPACE.csv"
---> PORT 127,0,0,1,15,145
200 Port command successful
---> RETR WITH SPACE.csv
150 Opening data channel for file download from server of "/WITH SPACE.csv"
226 Successfully transferred "/WITH SPACE.csv"
ftp: 12 bytes received in 0.00Seconds 12000.00Kbytes/sec.
Note that the get is a commandline ftp client user command that translates to the FTP protocol RETR command.

Related

Bad gate way Error while using ftp in server

Actually we are using python3.6.8 in our server, we are trying to connect the ftp server and pushing files to the ftp server through an api call, here when we try to push the files from local it is running fine and files are being pushed but when calling api to the server it is redirecting to a 502 bad gateway error after 14.8s time when tried with postman. the server we use is AWS EC2
ftp = ftplib.FTP()
host = config.FTP_HOST
port = 21
ftp.connect(host, port)
try:
ftp.login(config.FTP_USERNAME, config.FTP_PASSWORD)
file = open(path_image, 'rb')
ftp.cwd("/DailyDump/target/")
ftp.storbinary("STOR sample_file_name" + str(yesterday_date) + ".csv", file)
file.close()
ftp.close()
except:
pass
This problem was caused due to the maximum timeout reached on the API call, hence I transferred the codes from API to stand alone code to make it run for longer time period without giving error. so there is no error in ftp logging.

Uploading to an FTP server on the same wifi network (python)

I am trying to upload a file to an ftp server on my same wifi network to get a picture on to a digital picture frame. I succeeded in uploading through file explorer, but when uploading using a python script I get a 530 response.
Here is the code so far
import ftplib
ftp = ftplib.FTP()
ftp.connect("111.111.1.11", 1111) #dummy host and port
file = open('C:/path/to/file/test1.png','rb')
ftp.storbinary('test.png', file)
file.close()
ftp.quit()
The server does not requre me to log in with a username and password on file explorer, is there some sort of default I need?
530 error code means Authentication failed error so you are missing the authentication piece. Maybe you can do something like this:
ftp = FTP(source_address=("111.111.1.11", 1111))
ftp.login(user, password)
Note that if you don't provide a user and password it will login with:
user anonymous
password anonymous
as described here
Also I would recommend you reading about S-FTP (Secure FTP) because in FTP the credentials are passed in clear text in the login request.
S-FTP is a communication protocol similar to FTP but built on top of ssh.
Hope this helped you !

FTP: 421 Data timeout on Azure VM

I have a simple script that successfully downloads a 75MB file over FTP:
try:
ftp = ftplib.FTP(host)
ftp.login(username, password)
ftp.cwd(source_dir)
except ftplib.all_errors as e:
print('Ftp error = ', e)
return False
# Check filename exists
if filename in ftp.nlst():
local_filename = os.path.join(dest_dir, filename)
lf = open(local_filename, "wb")
ftp.retrbinary("RETR " + filename, lf.write)
lf.close()
print(filename, ' successfully downloaded')
else:
print(filename, ' not found in the path ', source_dir)
ftp.quit()
This script works fine on both my home and work laptops when run from Spyder IDE or a Windows scheduled task.
I have deployed the exact same script to a Windows Virtual Machine on Azure.
Files less than 10MB seem to download ok.
Files larger than 30MB return an exception:
421 Data timeout. Reconnect. Sorry.
I get around 700 Mbps on Azure and only around 8Mbps on my home network.
It looks like a timeout. I can see the file is partially downloaded.
I tried setting ftp.set_pasv(False), but this then returns me 500 Illegal Port, which is to be expected. I understand passive is the preferred approach anyhow.
What else can I do to troubleshoot and resolve this issue?
Just some suggestions for you.
According to the wiki page for File Transfer Protocol, FTP may run in active or passive mode, as the figure below. In active mode, the client requires a listening port for incoming data from the server. However, due to the listening port of client for FTP server is random assigned, you can not prepare in advance to add the port in NSG inbound rules. So you should use passive mode in the client side on Azure VM with FTP.set_pasv(True) or without FTP.set_pasv(False).
For the issue 421 Data timeout. Reconnect. Sorry., please check the timeout setting in your FTP server, such as the data_connection_timeout property of vsftpd.conf file of vftp, to set enough long value of time out
Try to set a timeout value longer then the global default setting for ftplib.FTP(host='', user='', passwd='', acct='', timeout=None, source_address=None) function.
Try to use function FTP.set_debuglevel(level) to debug output more details for your script to find out the possible reason.
Hope it helps.

Restore Wildcard Support in pyftpdlib

Wildcard support was removed from the giampaolo/pyftpdlib library. Globbing functionality was available until r358. We could go back to r357 to see what was removed and reimplement it. https://github.com/giampaolo/pyftpdlib/issues/106#issuecomment-44425620
Globbing of wildcards is not supported (for example, NLST *.txt will not work). ” - From the compliance doc https://github.com/giampaolo/pyftpdlib/blob/64d629aa480be4340cbab4ced1213211b4eee8db/docs/rfc-compliance.rst
Furthermore, he suggests using MLSD over NLST as a possible solution. https://github.com/giampaolo/pyftpdlib/issues/106#issuecomment-44425620 . My guess is that with modifications to the library the wildcard functionality can be restored.
Does anyone have any clue how to achieve this?
ADDITIONAL DETAILS:
We followed this tutorial and created an FTP server on Ubuntu 14.04 using the awesome pyftpdlib Python library. It was easy to setup the server, add users, and create hooks.
Some clients communicate with our FTP server directly from their server using mget. The mget command works if you specify the filename directly, but passing a wildcard fails. Here is a sample response:
ftp> dir
229 Entering extended passive mode (|||26607|).
125 Data connection already open. Transfer starting.
-rw-r--r-- 1 serverpilot serverpilot 914 Oct 06 19:05 index.php
226 Transfer complete.
ftp> mget *.php
No such file or directory.
ftp> glob
Globbing off.
ftp> mget *.php
mget *.php [anpqy?]? y
229 Entering extended passive mode (|||60975|).
550 No such file or directory.
ftp> mget index.php
mget index.php [anpqy?]? y
229 Entering extended passive mode (|||17945|).
125 Data connection already open. Transfer starting.
100% |***************************************************************************************************************************************************************| 914 763.53 KiB/s 00:00 ETA
226 Transfer complete.
914 bytes received in 00:00 (692.99 KiB/s)
Our script looks like this:
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.servers import FTPServer
# The port the FTP server will listen on.
# This must be greater than 1023 unless you run this script as root.
FTP_PORT = 2121
# The name of the FTP user that can log in.
FTP_USER = "myuser"
# The FTP user's password.
FTP_PASSWORD = "change_this_password"
# The directory the FTP user will have full read/write access to.
FTP_DIRECTORY = "/srv/users/serverpilot/apps/APPNAME/public/"
def main():
authorizer = DummyAuthorizer()
# Define a new user having full r/w permissions.
authorizer.add_user(FTP_USER, FTP_PASSWORD, FTP_DIRECTORY, perm='elradfmw')
handler = FTPHandler
handler.authorizer = authorizer
# Define a customized banner (string returned when client connects)
handler.banner = "pyftpdlib based ftpd ready."
# Optionally specify range of ports to use for passive connections.
#handler.passive_ports = range(60000, 65535)
address = ('', FTP_PORT)
server = FTPServer(address, handler)
server.max_cons = 256
server.max_cons_per_ip = 5
server.serve_forever()
if __name__ == '__main__':
main()
It looks like wildcards were intentionally left out of the library based upon this issue. For reference, here is my own issue as well.
Can anyone provide more insight or guide me to reenabling wildcards?

Python EOF in FTP Transfer over PASV Connection

I'm writting a FTP Server in Python 2.7 over Sockets.
In FTP protocol, connection has to be closed by sender to define the End Of the File (EOF).
The code below is a brief example of what I am doing. Server sends file.mp4, since the server is the sender of the file, he will have to close the connection to define the end of the file.
C->S : PASV
S->Bind Port()
S->C : Entering Passive Mode (ip1,ip2,ip3,ip4,port1,port2)
C->Connect PASV (ip, port)
C->S : RETR file.mp4
S->C : Transfer file content via PASV connection....
S->Close PASV connection() = End Of File.
S->C : 226 File successfully transferred.
So far so good, but here is the error I need to handle.
If the Server has more UPLOAD speed than Client's DOWNLOAD speed (most likely).
Then the server would have already sent the file and closed connection = EOF before Client could have finished downloading it and saving it. Result, Client gets a malformatted file.
How could I figure out this?

Categories