• 技术文章 >Python技术 >Python基础教程

    Python实现的简易FTP

    PythonPython2019-06-26 09:33:26原创3540
    Python版本

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

    1、继续支持多用户

    2、继续支持虚拟目录

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

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

    xxftp的特点

    1、开源、跨平台

    2、简单、易用

    3、不需要数据库

    4、可扩展性超强

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

    匿名帐号可以使用!

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

    使用方法

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

    FTP服务器目录结构

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    -/root

    -xxftp.welcome

    -xxftp.goodbye

    -user1

    -.xxftp

    -password

    -...

    -user2

    -.xxftp

    -password

    -...

    -anonymous源代码

    代码如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    68

    69

    70

    71

    72

    73

    74

    75

    76

    77

    78

    79

    80

    81

    82

    83

    84

    85

    86

    87

    88

    89

    90

    91

    92

    93

    94

    95

    96

    97

    98

    99

    100

    101

    102

    103

    104

    105

    106

    107

    108

    109

    110

    111

    112

    113

    114

    115

    116

    117

    118

    119

    120

    121

    122

    123

    124

    125

    126

    127

    128

    129

    130

    131

    132

    133

    134

    135

    136

    137

    138

    139

    140

    141

    142

    143

    144

    145

    146

    147

    148

    149

    150

    151

    152

    153

    154

    155

    156

    157

    158

    159

    160

    161

    162

    163

    164

    165

    166

    167

    168

    169

    170

    171

    172

    173

    174

    175

    176

    177

    178

    179

    180

    181

    182

    183

    184

    185

    186

    187

    188

    189

    190

    191

    192

    193

    194

    195

    196

    197

    198

    199

    200

    201

    202

    203

    204

    205

    206

    207

    208

    209

    210

    211

    212

    213

    214

    215

    216

    217

    218

    219

    220

    221

    222

    223

    224

    225

    226

    227

    228

    229

    230

    231

    232

    233

    234

    235

    236

    237

    238

    239

    240

    241

    242

    243

    244

    245

    246

    247

    248

    249

    250

    251

    252

    253

    254

    255

    256

    257

    258

    259

    260

    261

    262

    263

    264

    265

    266

    267

    268

    269

    270

    271

    272

    273

    274

    275

    276

    277

    278

    279

    280

    281

    282

    283

    284

    285

    286

    287

    288

    289

    290

    291

    292

    293

    294

    295

    296

    297

    298

    299

    300

    301

    302

    303

    304

    305

    306

    307

    308

    309

    310

    311

    312

    313

    314

    315

    316

    317

    318

    319

    320

    321

    322

    323

    324

    325

    326

    327

    328

    329

    330

    331

    332

    333

    334

    335

    336

    337

    338

    339

    340

    341

    342

    343

    344

    345

    346

    347

    348

    349

    350

    351

    352

    353

    354

    355

    356

    357

    358

    359

    360

    361

    362

    363

    364

    365

    366

    367

    368

    369

    370

    371

    372

    373

    374

    375

    376

    377

    378

    379

    380

    381

    382

    383

    384

    385

    386

    387

    388

    389

    390

    391

    392

    393

    394

    395

    396

    397

    398

    399

    400

    401

    402

    403

    404

    405

    406

    407

    408

    409

    410

    411

    412

    413

    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()

    专题推荐:python
    上一篇:Python中and、or用法实例 下一篇:Python的基本数据类型有哪些

    相关文章推荐

    • Python的函数式编程• Python中的__name__怎么用?• Python中的偏函数怎么用?

    全部评论我要评论

    © 2021 Python学习网 苏ICP备2021003149号-1

  • 取消发布评论
  • 

    Python学习网