Python如何实现FTP功能

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如何实现FTP功能 的全部内容, 来源链接: utcz.com/z/328009.html

回到顶部