Python如何实现FTP功能


Posted in Python onMay 28, 2020

Python版本

实现了比之前的xxftp更多更完善的功能

1、继续支持多用户

2、继续支持虚拟目录

3、增加支持用户根目录以及映射虚拟目录的权限设置

4、增加支持限制用户根目录或者虚拟目录的空间大小

xxftp的特点

1、开源、跨平台

2、简单、易用

3、不需要数据库

4、可扩展性超强

5、你可以免费使用xxftp假设自己的私人FTP服务器

匿名帐号可以使用!

匿名根目录只读,映射了一个虚拟目录,可以上传文件但不允许更改!

使用方法

跟用C语言写的xxftp使用方法一样

FTP服务器目录结构

-/root 
-xxftp.welcome 
-xxftp.goodbye 
-user1 
-.xxftp 
-password 
-... 
-user2 
-.xxftp 
-password 
-... 
-anonymous源代码

代码如下:

import socket, threading, os, sys, time 
import hashlib, platform, stat 
listen_ip = "localhost" 
listen_port = 21 
conn_list = [] 
root_dir = "./home" 
max_connections = 500 
conn_timeout = 120 
class FtpConnection(threading.Thread): 
def __init__(self, fd): 
threading.Thread.__init__(self) 
self.fd = fd 
self.running = True 
self.setDaemon(True) 
self.alive_time = time.time() 
self.option_utf8 = False 
self.identified = False 
self.option_pasv = True 
self.username = "" 
def process(self, cmd, arg): 
cmd = cmd.upper(); 
if self.option_utf8: 
arg = unicode(arg, "utf8").encode(sys.getfilesystemencoding()) 
print "<<", cmd, arg, self.fd 
# Ftp Command 
if cmd == "BYE" or cmd == "QUIT": 
if os.path.exists(root_dir + "/xxftp.goodbye"): 
self.message(221, open(root_dir + "/xxftp.goodbye").read()) 
else: 
self.message(221, "Bye!") 
self.running = False 
return 
elif cmd == "USER": 
# Set Anonymous User 
if arg == "": arg = "anonymous" 
for c in arg: 
if not c.isalpha() and not c.isdigit() and c!="_": 
self.message(530, "Incorrect username.") 
return 
self.username = arg 
self.home_dir = root_dir + "/" + self.username 
self.curr_dir = "/" 
self.curr_dir, self.full_path, permission, self.vdir_list, \ 
limit_size, is_virtual = self.parse_path("/") 
if not os.path.isdir(self.home_dir): 
self.message(530, "User " + self.username + " not exists.") 
return 
self.pass_path = self.home_dir + "/.xxftp/password" 
if os.path.isfile(self.pass_path): 
self.message(331, "Password required for " + self.username) 
else: 
self.message(230, "Identified!") 
self.identified = True 
return 
elif cmd == "PASS": 
if open(self.pass_path).read() == hashlib.md5(arg).hexdigest(): 
self.message(230, "Identified!") 
self.identified = True 
else: 
self.message(530, "Not identified!") 
self.identified = False 
return 
elif not self.identified: 
self.message(530, "Please login with USER and PASS.") 
return 
self.alive_time = time.time() 
finish = True 
if cmd == "NOOP": 
self.message(200, "ok") 
elif cmd == "TYPE": 
self.message(200, "ok") 
elif cmd == "SYST": 
self.message(200, "UNIX") 
elif cmd == "EPSV" or cmd == "PASV": 
self.option_pasv = True 
try: 
self.data_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
self.data_fd.bind((listen_ip, 0)) 
self.data_fd.listen(1) 
ip, port = self.data_fd.getsockname() 
if cmd == "EPSV": 
self.message(229, "Entering Extended Passive Mode (|||" + str(port) + "|)") 
else: 
ipnum = socket.inet_aton(ip) 
self.message(227, "Entering Passive Mode (%s,%u,%u)." % 
(",".join(ip.split(".")), (port>>8&0xff), (port&0xff))) 
except: 
self.message(500, "failed to create data socket.") 
elif cmd == "EPRT": 
self.message(500, "implement EPRT later...") 
elif cmd == "PORT": 
self.option_pasv = False 
self.data_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s = arg.split(",") 
self.data_ip = ".".join(s[:4]) 
self.data_port = int(s[4])*256 + int(s[5]) 
self.message(200, "ok") 
elif cmd == "PWD" or cmd == "XPWD": 
if self.curr_dir == "": self.curr_dir = "/" 
self.message(257, '"' + self.curr_dir + '"') 
elif cmd == "LIST" or cmd == "NLST": 
if arg != "" and arg[0] == "-": arg = "" # omit parameters 
remote, local, perm, vdir_list, limit_size, is_virtual = self.parse_path(arg) 
if not os.path.exists(local): 
self.message(550, "failed.") 
return 
if not self.establish(): return 
self.message(150, "ok") 
for v in vdir_list: 
f = v[0] 
if self.option_utf8: 
f = unicode(f, sys.getfilesystemencoding()).encode("utf8") 
if cmd == "NLST": 
info = f + "\r\n" 
else: 
info = "d%s%s------- %04u %8s %8s %8lu %s %s\r\n" % ( 
"r" if "read" in perm else "-", 
"w" if "write" in perm else "-", 
1, "0", "0", 0, 
time.strftime("%b %d %Y", time.localtime(time.time())), 
f) 
self.data_fd.send(info) 
for f in os.listdir(local): 
if f[0] == ".": continue 
path = local + "/" + f 
if self.option_utf8: 
f = unicode(f, sys.getfilesystemencoding()).encode("utf8") 
if cmd == "NLST": 
info = f + "\r\n" 
else: 
st = os.stat(path) 
info = "%s%s%s------- %04u %8s %8s %8lu %s %s\r\n" % ( 
"-" if os.path.isfile(path) else "d", 
"r" if "read" in perm else "-", 
"w" if "write" in perm else "-", 
1, "0", "0", st[stat.ST_SIZE], 
time.strftime("%b %d %Y", time.localtime(st[stat.ST_MTIME])), 
f) 
self.data_fd.send(info) 
self.message(226, "Limit size: " + str(limit_size)) 
self.data_fd.close() 
self.data_fd = 0 
elif cmd == "REST": 
self.file_pos = int(arg) 
self.message(250, "ok") 
elif cmd == "FEAT": 
features = "211-Features:\r\nSITES\r\nEPRT\r\nEPSV\r\nMDTM\r\nPASV\r\n"\ 
"REST STREAM\r\nSIZE\r\nUTF8\r\n211 End\r\n" 
self.fd.send(features) 
elif cmd == "OPTS": 
arg = arg.upper() 
if arg == "UTF8 ON": 
self.option_utf8 = True 
self.message(200, "ok") 
elif arg == "UTF8 OFF": 
self.option_utf8 = False 
self.message(200, "ok") 
else: 
self.message(500, "unrecognized option") 
elif cmd == "CDUP": 
finish = False 
arg = ".." 
else: 
finish = False 
if finish: return 
# Parse argument ( It's a path ) 
if arg == "": 
self.message(500, "where's my argument?") 
return 
remote, local, permission, vdir_list, limit_size, is_virtual = \ 
self.parse_path(arg) 
# can not do anything to virtual directory 
if is_virtual: permission = "none" 
can_read, can_write, can_modify = "read" in permission, "write" in permission, "modify" in permission 
newpath = local 
try: 
if cmd == "CWD": 
if(os.path.isdir(newpath)): 
self.curr_dir = remote 
self.full_path = newpath 
self.message(250, '"' + remote + '"') 
else: 
self.message(550, "failed") 
elif cmd == "MDTM": 
if os.path.exists(newpath): 
self.message(213, time.strftime("%Y%m%d%I%M%S", time.localtime( 
os.path.getmtime(newpath)))) 
else: 
self.message(550, "failed") 
elif cmd == "SIZE": 
self.message(231, os.path.getsize(newpath)) 
elif cmd == "XMKD" or cmd == "MKD": 
if not can_modify: 
self.message(550, "permission denied.") 
return 
os.mkdir(newpath) 
self.message(250, "ok") 
elif cmd == "RNFR": 
if not can_modify: 
self.message(550, "permission denied.") 
return 
self.temp_path = newpath 
self.message(350, "rename from " + remote) 
elif cmd == "RNTO": 
os.rename(self.temp_path, newpath) 
self.message(250, "RNTO to " + remote) 
elif cmd == "XRMD" or cmd == "RMD": 
if not can_modify: 
self.message(550, "permission denied.") 
return 
os.rmdir(newpath) 
self.message(250, "ok") 
elif cmd == "DELE": 
if not can_modify: 
self.message(550, "permission denied.") 
return 
os.remove(newpath) 
self.message(250, "ok") 
elif cmd == "RETR": 
if not os.path.isfile(newpath): 
self.message(550, "failed") 
return 
if not can_read: 
self.message(550, "permission denied.") 
return 
if not self.establish(): return 
self.message(150, "ok") 
f = open(newpath, "rb") 
while self.running: 
self.alive_time = time.time() 
data = f.read(8192) 
if len(data) == 0: break 
self.data_fd.send(data) 
f.close() 
self.data_fd.close() 
self.data_fd = 0 
self.message(226, "ok") 
elif cmd == "STOR" or cmd == "APPE": 
if not can_write: 
self.message(550, "permission denied.") 
return 
if os.path.exists(newpath) and not can_modify: 
self.message(550, "permission denied.") 
return 
# Check space size remained! 
used_size = 0 
if limit_size > 0: 
used_size = self.get_dir_size(os.path.dirname(newpath)) 
if not self.establish(): return 
self.message(150, "ok") 
f = open(newpath, ("ab" if cmd == "APPE" else "wb") ) 
while self.running: 
self.alive_time = time.time() 
data = self.data_fd.recv(8192) 
if len(data) == 0: break 
if limit_size > 0: 
used_size = used_size + len(data) 
if used_size > limit_size: break 
f.write(data) 
f.close() 
self.data_fd.close() 
self.data_fd = 0 
if limit_size > 0 and used_size > limit_size: 
self.message(550, "Exceeding user space limit: " + str(limit_size) + " bytes") 
else: 
self.message(226, "ok") 
else: 
self.message(500, cmd + " not implemented") 
except: 
self.message(550, "failed.") 
def establish(self): 
if self.data_fd == 0: 
self.message(500, "no data connection") 
return False 
if self.option_pasv: 
fd = self.data_fd.accept()[0] 
self.data_fd.close() 
self.data_fd = fd 
else: 
try: 
self.data_fd.connect((self.data_ip, self.data_port)) 
except: 
self.message(500, "failed to establish data connection") 
return False 
return True 
def read_virtual(self, path): 
vdir_list = [] 
path = path + "/.xxftp/virtual" 
if os.path.isfile(path): 
for v in open(path, "r").readlines(): 
items = v.split() 
items[1] = items[1].replace("$root", root_dir) 
vdir_list.append(items) 
return vdir_list 
def get_dir_size(self, folder): 
size = 0 
for path, dirs, files in os.walk(folder): 
for f in files: 
size += os.path.getsize(os.path.join(path, f)) 
return size 
def read_size(self, path): 
size = 0 
path = path + "/.xxftp/size" 
if os.path.isfile(path): 
size = int(open(path, "r").readline()) 
return size 
def read_permission(self, path): 
permission = "read,write,modify" 
path = path + "/.xxftp/permission" 
if os.path.isfile(path): 
permission = open(path, "r").readline() 
return permission 
def parse_path(self, path): 
if path == "": path = "." 
if path[0] != "/": 
path = self.curr_dir + "/" + path 
s = os.path.normpath(path).replace("\\", "/").split("/") 
local = self.home_dir 
# reset directory permission 
vdir_list = self.read_virtual(local) 
limit_size = self.read_size(local) 
permission = self.read_permission(local) 
remote = "" 
is_virtual = False 
for name in s: 
name = name.lstrip(".") 
if name == "": continue 
remote = remote + "/" + name 
is_virtual = False 
for v in vdir_list: 
if v[0] == name: 
permission = v[2] 
local = v[1] 
limit_size = self.read_size(local) 
is_virtual = True 
if not is_virtual: local = local + "/" + name 
vdir_list = self.read_virtual(local) 
return (remote, local, permission, vdir_list, limit_size, is_virtual) 
def run(self): 
''' Connection Process ''' 
try: 
if len(conn_list) > max_connections: 
self.message(500, "too many connections!") 
self.fd.close() 
self.running = False 
return 
# Welcome Message 
if os.path.exists(root_dir + "/xxftp.welcome"): 
self.message(220, open(root_dir + "/xxftp.welcome").read()) 
else: 
self.message(220, "xxftp(Python) www.xiaoxia.org") 
# Command Loop 
line = "" 
while self.running: 
data = self.fd.recv(4096) 
if len(data) == 0: break 
line += data 
if line[-2:] != "\r\n": continue 
line = line[:-2] 
space = line.find(" ") 
if space == -1: 
self.process(line, "") 
else: 
self.process(line[:space], line[space+1:]) 
line = "" 
except: 
print "error", sys.exc_info() 
self.running = False 
self.fd.close() 
print "connection end", self.fd, "user", self.username 
def message(self, code, s): 
''' Send Ftp Message ''' 
s = str(s).replace("\r", "") 
ss = s.split("\n") 
if len(ss) > 1: 
r = (str(code) + "-") + ("\r\n" + str(code) + "-").join(ss[:-1]) 
r += "\r\n" + str(code) + " " + ss[-1] + "\r\n" 
else: 
r = str(code) + " " + ss[0] + "\r\n" 
if self.option_utf8: 
r = unicode(r, sys.getfilesystemencoding()).encode("utf8") 
self.fd.send(r) 
def server_listen(): 
global conn_list 
listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
listen_fd.bind((listen_ip, listen_port)) 
listen_fd.listen(1024) 
conn_lock = threading.Lock() 
print "ftpd is listening on ", listen_ip + ":" + str(listen_port) 
while True: 
conn_fd, remote_addr = listen_fd.accept() 
print "connection from ", remote_addr, "conn_list", len(conn_list) 
conn = FtpConnection(conn_fd) 
conn.start() 
conn_lock.acquire() 
conn_list.append(conn) 
# check timeout 
try: 
curr_time = time.time() 
for conn in conn_list: 
if int(curr_time - conn.alive_time) > conn_timeout: 
if conn.running == True: 
conn.fd.shutdown(socket.SHUT_RDWR) 
conn.running = False 
conn_list = [conn for conn in conn_list if conn.running] 
except: 
print sys.exc_info() 
conn_lock.release() 
def main(): 
server_listen() 
if __name__ == "__main__": 
main()

内容扩展:

FTP服务器端代码:

import socket,os,time
import hashlib
  
server =socket.socket()
server.bind(('0.0.0.0',6666))
server.listen()
print("等待....")
while True:
 conn,addr = server.accept()
 print("new conn:",conn)
 while True:
 data = conn.recv(1024)
 if not data:
 print("client is disconnection")
 break
 cmd,filename = data.decode().split() #记录指令和文件名
 print(filename)
 #判断当前目录是否存在该文件,而且必须是文件,而不是目录
 if os.path.isfile(filename):
 f = open(filename,'rb')
 #m = hashlib.md5() # 创建md5
 file_size = os.stat(filename).st_size #stat() 可以返回文件的大小值
 conn.send((str(file_size)).encode()) # 发送文件大小
 conn.recv(1024) #等待返回信息
 for line in f:
 # m.updata(line) 
 conn.send(line)
 #print("file md5",m.hexdigest()) #打印md5值
 f.close()

到此这篇关于Python如何实现FTP功能的文章就介绍到这了,更多相关Python实现的简易FTP内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
一个计算身份证号码校验位的Python小程序
Aug 15 Python
python多线程编程中的join函数使用心得
Sep 02 Python
如何安装多版本python python2和python3共存以及pip共存
Sep 18 Python
Python Django的安装配置教程图文详解
Jul 17 Python
Python实现语音识别和语音合成功能
Sep 20 Python
对pytorch的函数中的group参数的作用介绍
Feb 18 Python
在服务器上安装python3.8.2环境的教程详解
Apr 26 Python
在Keras中CNN联合LSTM进行分类实例
Jun 29 Python
Python如何给你的程序做性能测试
Jul 29 Python
解决Pycharm双击图标启动不了的问题(JetBrains全家桶通用)
Aug 07 Python
Python reversed反转序列并生成可迭代对象
Oct 22 Python
python math模块的基本使用教程
Jan 16 Python
python基于socket函数实现端口扫描
May 28 #Python
Python中and和or如何使用
May 28 #Python
Python如何安装第三方模块
May 28 #Python
python使用多线程+socket实现端口扫描
May 28 #Python
Python如何实现定时器功能
May 28 #Python
python实点云分割k-means(sklearn)详解
May 28 #Python
Python脚本实现监听服务器的思路代码详解
May 28 #Python
You might like
PHP使用正则表达式清除超链接文本
2013/11/12 PHP
再Docker中架设完整的WordPress站点全攻略
2015/07/29 PHP
php微信公众号js-sdk开发应用
2016/11/28 PHP
jQuery.Validate 使用笔记(jQuery Validation范例 )
2010/06/25 Javascript
JavaScript arguments 多参传值函数
2010/10/24 Javascript
自写的一个jQuery圆角插件
2010/10/26 Javascript
关于jquery.validate1.9.0前台验证的使用介绍
2013/04/26 Javascript
动态创建script标签实现跨域资源访问的方法介绍
2014/02/28 Javascript
jquery中one()方法的用法实例
2015/01/16 Javascript
jQuery中triggerHandler()方法用法实例
2015/01/19 Javascript
jQuery实现简单的间隔向上滚动效果
2015/03/09 Javascript
jQuery插件Tmpl的简单使用方法
2015/04/27 Javascript
jQuery实现点击按钮弹出可关闭层的浮动层插件
2015/09/19 Javascript
BootStrap树状图显示功能
2016/11/24 Javascript
浅谈jQuery hover(over, out)事件函数
2016/12/03 Javascript
javascript深拷贝和浅拷贝详解
2017/02/14 Javascript
react redux入门示例
2018/04/19 Javascript
了解重排与重绘
2019/05/29 Javascript
[14:51]DOTA2 HEROS教学视频教你分分钟做大人-卓尔游侠
2014/06/13 DOTA
python实现随机密码字典生成器示例
2014/04/09 Python
python实现指定字符串补全空格的方法
2015/04/30 Python
Python logging管理不同级别log打印和存储实例
2018/01/19 Python
实例分析python3实现并发访问水平切分表
2018/09/29 Python
Python3.5文件读与写操作经典实例详解
2019/05/01 Python
python获取当前文件路径以及父文件路径的方法
2019/07/10 Python
python之pymysql模块简单应用示例代码
2019/12/16 Python
Python使用Pandas库常见操作详解
2020/01/16 Python
python递归调用中的坑:打印有值, 返回却None
2020/03/16 Python
如何在python中执行另一个py文件
2020/04/30 Python
python实现杨辉三角的几种方法代码实例
2021/03/02 Python
详解Canvas 跨域脱坑实践
2018/11/07 HTML / CSS
化学系大学生自荐信范文
2014/03/01 职场文书
企业诚信承诺书
2014/05/23 职场文书
党员四风自我剖析材料思想汇报
2014/09/13 职场文书
建立共青团委员会的请示
2019/04/02 职场文书
Java Optional<Foo>转换成List<Bar>的实例方法
2021/06/20 Java/Android