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 2.7.x 和 3.x 版本的重要区别小结
Nov 28 Python
Python中使用第三方库xlutils来追加写入Excel文件示例
Apr 05 Python
Python模拟百度登录实例详解
Jan 20 Python
python中利用队列asyncio.Queue进行通讯详解
Sep 10 Python
Python虚拟环境项目实例
Nov 20 Python
用Python实现读写锁的示例代码
Nov 05 Python
导入tensorflow:ImportError: libcublas.so.9.0 报错
Jan 06 Python
Pytorch通过保存为ONNX模型转TensorRT5的实现
May 25 Python
Python实现PS滤镜中的USM锐化效果
Dec 04 Python
python保存大型 .mat 数据文件报错超出 IO 限制的操作
May 10 Python
python爬取豆瓣电影TOP250数据
May 23 Python
使用opencv-python如何打开USB或者笔记本前置摄像头
Jun 21 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编程过程中需要了解的this,self,parent的区别
2009/12/30 PHP
PHP中数组定义的几种方法
2013/09/01 PHP
php设计模式之享元模式分析【星际争霸游戏案例】
2020/03/23 PHP
CSS和JS标签style属性对照表(方便js开发的朋友)
2010/11/11 Javascript
用jquery等比例控制图片宽高的具体实现
2014/01/28 Javascript
实例分析javascript中的call()和apply()方法
2014/11/28 Javascript
ajax+jQuery实现级联显示地址的方法
2015/05/06 Javascript
javascript转换静态图片,增加粒子动画效果
2015/05/28 Javascript
一步步教大家编写酷炫的导航栏js+css实现
2016/03/14 Javascript
利用jQuery实现滑动开关按钮效果(附demo源码下载)
2017/02/07 Javascript
jQuery插件echarts去掉垂直网格线用法示例
2017/03/03 Javascript
快速掌握jquery分页插件jqPaginator的使用方法
2017/08/09 jQuery
通过seajs实现JavaScript的模块开发及按模块加载
2019/06/06 Javascript
vuecli项目构建SSR服务端渲染的实现
2020/10/30 Javascript
python中import reload __import__的区别详解
2017/10/16 Python
Python编程pygame模块实现移动的小车示例代码
2018/01/03 Python
利用python库在局域网内传输文件的方法
2018/06/04 Python
详解使用python绘制混淆矩阵(confusion_matrix)
2019/07/14 Python
Python Django 简单分页的实现代码解析
2019/08/21 Python
Windows10下 python3.7 安装 facenet的教程
2019/09/10 Python
Python字典的概念及常见应用实例详解
2019/10/30 Python
如何将tensorflow训练好的模型移植到Android (MNIST手写数字识别)
2020/04/22 Python
在pycharm中debug 实时查看数据操作(交互式)
2020/06/09 Python
python代码区分大小写吗
2020/06/17 Python
Python压缩模块zipfile实现原理及用法解析
2020/08/14 Python
浅析css3中matrix函数的使用
2016/06/06 HTML / CSS
html5-canvas中使用clip抠出一个区域的示例代码
2018/05/25 HTML / CSS
浅析HTML5:'data-'属性的作用
2018/01/23 HTML / CSS
娇韵诗法国官网:Clarins法国
2019/01/29 全球购物
幼儿园师德师风学习材料
2014/05/29 职场文书
销售竞赛活动方案
2014/08/23 职场文书
2015年乡镇扶贫工作总结
2015/04/08 职场文书
2015年业务工作总结范文
2015/04/10 职场文书
关于迟到的检讨书
2015/05/06 职场文书
撤诉书怎么写
2015/05/19 职场文书
创业计划书之甜品店
2019/09/18 职场文书