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中asyncore的用法实例
Sep 29 Python
使用SAE部署Python运行环境的教程
May 05 Python
详解Python中的序列化与反序列化的使用
Jun 30 Python
python删除列表内容
Aug 04 Python
Python时间模块datetime、time、calendar的使用方法
Jan 13 Python
Python中列表元素转为数字的方法分析
Jun 14 Python
Python中 Lambda表达式全面解析
Nov 28 Python
Python 调用 zabbix api的方法示例
Jan 06 Python
Django外键(ForeignKey)操作以及related_name的作用详解
Jul 29 Python
利用pytorch实现对CIFAR-10数据集的分类
Jan 14 Python
Python3实现监控新型冠状病毒肺炎疫情的示例代码
Feb 13 Python
python中pop()函数的语法与实例
Dec 01 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可能遇到的问题“无法载入mysql扩展” 的解决方法
2007/04/16 PHP
IIS安装Apache伪静态插件的具体操作图文
2013/07/01 PHP
php+mysql查询优化简单实例
2015/01/13 PHP
PHP的文件操作与算法实现的面试题示例
2015/08/10 PHP
PHP类的自动加载与命名空间用法实例分析
2020/06/05 PHP
Spring mvc 接收json对象
2015/12/10 Javascript
浅谈jQuery为哪般去掉了浏览器检测
2016/08/29 Javascript
webpack+vue.js实现组件化详解
2016/10/12 Javascript
Javascript 正则表达式校验数字的简单实例
2016/11/02 Javascript
微信小程序 利用css实现遮罩效果实例详解
2017/01/21 Javascript
js清除浏览器缓存的几种方法
2017/03/15 Javascript
Vue 2.0 服务端渲染入门介绍
2017/03/29 Javascript
jQuery查找和过滤_动力节点节点Java学院整理
2017/07/04 jQuery
微信小程序实现点击文字页面跳转功能【附源码下载】
2017/12/12 Javascript
Vue.js实现双向数据绑定方法(表单自动赋值、表单自动取值)
2018/08/27 Javascript
webpack的tree shaking的实现方法
2019/09/18 Javascript
vue.js实现图书管理功能
2019/09/24 Javascript
解决Vue + Echarts 使用markLine标线(precision精度问题)
2020/07/20 Javascript
javascript解析json格式的数据方法详解
2020/08/07 Javascript
python读取html中指定元素生成excle文件示例
2014/04/03 Python
简介Python中用于处理字符串的center()方法
2015/05/18 Python
Python(TensorFlow框架)实现手写数字识别系统的方法
2018/05/29 Python
原来我一直安装 Python 库的姿势都不对呀
2019/11/11 Python
Python编程快速上手——strip()函数的正则表达式实现方法分析
2020/02/29 Python
Python 用__new__方法实现单例的操作
2020/12/11 Python
CSS3转换功能transform主要属性值分析及实现分享
2012/05/06 HTML / CSS
用CSS3实现瀑布流布局的示例代码
2017/11/10 HTML / CSS
出纳试用期自我鉴定范文
2014/09/16 职场文书
公司股东出资证明书
2014/11/01 职场文书
设立有限责任公司出资协议书
2014/11/01 职场文书
计算机实训报告总结
2014/11/05 职场文书
八月迷情观后感
2015/06/11 职场文书
孟佩杰观后感
2015/06/17 职场文书
办公室管理规章制度
2015/08/04 职场文书
springBoot基于webSocket实现扫码登录
2021/06/22 Java/Android
Android自定义ScrollView实现阻尼回弹
2022/04/01 Java/Android