Python實現簡易文件服務器 包含上傳、下載、新建目錄、執行命令功能,去掉刪除功能

#!/usr/bin/python
# -*- coding: UTF-8 -*-

'''
kysec_set -n exectl -v verified ./HttpServerWithUpload.py
nohup python HttpServerWithUpload.py > /dev/null 2>&1 &
'''

__version__ = "0.6"
__all__ = ["SimpleHTTPRequestHandler"]

import os, sys, platform, socket, struct, json
import posixpath
import BaseHTTPServer
import urllib, urllib2
import urlparse
import cgi
import shutil
import mimetypes
   
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO
import time
from SocketServer import ThreadingMixIn
import re
import threading
import md5, hashlib
import commands

class ToDo:
    #獲取IP地址
    def get_ip_address(ifname=None):
        if sys.platform == 'win32':
            return socket.getaddrinfo(socket.gethostname(), None, socket.AF_INET, socket.SOCK_DGRAM)[-1][4][0]
        else:
            import fcntl
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            return socket.inet_ntoa(fcntl.ioctl(
                s.fileno(),
                0x8915,  # SIOCGIFADDR
                struct.pack('256s', ifname[:15])
            )[20:24])
    def getip(self):
        try:
            myip = get_ip_address(ifname="eth0")
        except Exception, e:
            #print "eth0"+str(e)
            myip = "127.0.0.1"
        return myip
    #獲取系統時間
    def getTimeNow(self):
        return time.strftime("%Y-%m-%d %H:%M:%S",time.localtime())+"-->"
    #獲取port
    def showTips(self):
        port = 1235
        try:
            print ToDo().getTimeNow()+'Please visit files or dirs use Chrome Browser:http://'+ToDo().getip()+':'+str(port)
        except Exception, e:
            print ToDo().getTimeNow()+'You have not give a port, plase use Chrome Browser:http://'+ToDo().getip()+':'+str(port)
        if not 1024 < port < 65535:  port = 1235
        return ('', port)
    #處理文件大小
    def sizeof_fmt(self,num):
        if num==0:
            return '0.0bytes'
        for x in ['bytes', 'KB', 'MB', 'GB']:
            if num < 1024.0:
                return "%3.1f%s" % (num, x)
            num /= 1024.0
        return "%3.1f%s" % (num, 'TB')
    #處理文件時間
    def modification_date(self,filename):
        if os.path.isfile(filename):
            return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(filename)))
        elif os.path.isdir(filename):
            return time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(filename)))
        else:
            return ""
    #處理文件MD5值
    def sumfile(self,fobj):
        m = md5.new()
        while True:
            d = fobj.read(8096)
            if not d:
                break
            m.update(d)
        return m.hexdigest()
    def md5sum(self,fname):
        print fname
        if fname == '-':
            ret = self.sumfile(sys.stdin)
        else:
            try:
                f = file(fname, 'rb')
            except:
                return ''
            ret = self.sumfile(f)
            f.close()
        return ret
    
    #轉碼
    def do_utf82gbk(self,sts):
        osType = platform.system()
        if osType == "Windows":
            sts = sts.decode('utf-8').encode('gbk')
            print "do_utf82gbk char:" + sts
            return sts
        else:
            sts = sts
            return sts

    def do_gbk2utf8(self,sts):
        osType = platform.system()
        if osType == "Windows":
            sts = sts.decode('gbk').encode('utf-8')
            #print "do_gbk2utf8 char:" + sts
            return sts
        else:
            sts = sts
            return sts

class View:
    def do_buildHeadMessage(self,f,title):
        f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html>\n<head><STYLE><!--H1 {font-family: Tahoma, Arial, sans-serif;color: white;background-color: #525D76;font-size: 22px;}H2 {font-family: Tahoma, Arial, sans-serif;color: white;background-color: #525D76;font-size: 16px;}H3 {font-family: Tahoma, Arial, sans-serif;color: white;background-color: #525D76;font-size: 14px;}BODY {font-family: Tahoma, Arial, sans-serif;color: black;background-color: white;}B {font-family: Tahoma, Arial, sans-serif;color: white;background-color: #525D76;}P {font-family: Tahoma, Arial, sans-serif;background: white;color: black;font-size: 12px;}A {color: black;}A.name {color: black;}HR {color: #525D76;}--></STYLE>')
        f.write('<title>%s</title>\n' % title)
        self.do_buildJS(f)
        f.write('</head>\n')
    def do_buildJS(self,f):
        f.write('<script type="text/javascript">')
        f.write("function callBackMD5(murl,domId){http.get({url:murl,timeout:100000},function(err,result){document.getElementById(domId).innerHTML=result;});}\n")
        f.write("var http = {};\n")
        f.write("http.quest = function (option, callback) {\n")
        f.write("    var url = option.url;\n")
        f.write("    var method = option.method;\n")
        f.write("    var data = option.data;\n")
        f.write("    var timeout = option.timeout || 0;\n")
        f.write("    var xhr = new XMLHttpRequest();\n")
        f.write("    (timeout > 0) && (xhr.timeout = timeout);\n")
        f.write("    xhr.onreadystatechange = function () {\n")
        f.write("        if (xhr.readyState == 4) {\n")
        f.write("            if (xhr.status >= 200 && xhr.status < 400) {\n")
        f.write("            var result = xhr.responseText;\n")
        f.write("            try {result = JSON.parse(xhr.responseText);} catch (e) {}\n")
        f.write("                callback && callback(null, result);\n")
        f.write("            } else {\n")
        f.write("                callback && callback('status: ' + xhr.status);\n")
        f.write("            }\n")
        f.write("        }\n")
        f.write("    }.bind(this);\n")
        f.write("    xhr.open(method, url, true);\n")
        f.write("    if(typeof data === 'object'){\n")
        f.write("        try{\n")
        f.write("            data = JSON.stringify(data);\n")
        f.write("        }catch(e){}\n")
        f.write("    }\n")
        f.write("    xhr.send(data);\n")
        f.write("    xhr.ontimeout = function () {\n")
        f.write("        callback && callback('timeout');\n")
        f.write("        console.log('%c連%c接%c超%c時', 'color:red', 'color:orange', 'color:purple', 'color:green');\n")
        f.write("    };\n")
        f.write("};\n")
        f.write("http.get = function (url, callback) {\n")
        f.write("    var option = url.url ? url : { url: url };\n")
        f.write("    option.method = 'get';\n")
        f.write("    this.quest(option, callback);\n")
        f.write("};\n")
        f.write("http.post = function (option, callback) {\n")
        f.write("    option.method = 'post';\n")
        f.write("    this.quest(option, callback);\n")
        f.write("};\n")
        f.write('</script>')
class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    server_version = "SimpleHTTP/" + __version__
    def returnMessage(self,r,info):
        f = StringIO()
        View().do_buildHeadMessage(f,'消息提示')
        f.write('<body>\n<h1>消息提示</h1>\n<HR size="1" noshade="noshade">\n')
        if r:
            f.write('<strong>成功:</strong>\n<font color="GREEN">'+info+'</font>\n')
        else:
            f.write('<strong>失敗:</strong>\n<font color="RED">'+info+'</font>\n')
        f.write(' <a href="javascript:" onclick="self.location=document.referrer;">返回</a>\n')
        f.write(" <input type=\"button\" value=\"返回首頁\" onClick=\"location='/'\">\n<HR size=\"1\" noshade=\"noshade\">\n<h2>Powered By [email protected]</h2>\n</body>\n</html>")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=%s" % "utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            self.copyfile(f, self.wfile)
            f.close()
            
    def returnalert(self,r,info):
        f = StringIO()
        f.write(info)
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=%s" % "utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            self.copyfile(f, self.wfile)
            f.close()
        
    def do_GET(self):
        """Serve a GET request."""
        if("?delete=" in self.path):
            r, info = self.deal_delFile(self.path)
            self.returnMessage(r,info)
        elif("?getMD5=" in self.path):
            r, info = self.deal_getMD5(self.path)
            self.returnalert(r,info)
        elif("?docmd=" in self.path):
            r,info = self.deal_docmd(self.path)
            self.returnMessage(r,info.replace("\n","<br>"))
        elif("?makedir=" in self.path):
            r,info = self.deal_makeDir(self.path)
            self.returnMessage(r,info)
        else:
            f = self.send_head()
            if f:
                try:
                    self.copyfile(f, self.wfile)
                finally:
                    f.close()
    def do_POST(self):
        r, info = self.deal_post_data()
        self.returnMessage(r,info)
        
    def do_HEAD(self):
        f = self.send_head()
        if f:
            f.close()

    def deal_docmd(self,path):
        dirname = path.split('?docmd=',1)[1]
        dirpath = self.translate_path(self.path)
        path = posixpath.normpath(urllib.unquote(ToDo().do_utf82gbk(urllib.unquote(dirname))))
        path=path.strip()
        path=path.rstrip("\\")
        backstr=commands.getstatusoutput(path)
        if(backstr[0]==0):
            return (True,ToDo().do_gbk2utf8(path)+" 執行成功:"+str(backstr[0])+"\n"+backstr[1]+"\n")
        else:
            return (False,ToDo().do_gbk2utf8(path)+" 執行失敗:"+str(backstr[0])+"\n"+backstr[1]+"\n")   

    def deal_makeDir(self,path):
        dirname = path.split('?makedir=',1)[1]
        dirpath = self.translate_path(self.path)
        path = posixpath.normpath(urllib.unquote(dirpath+ToDo().do_utf82gbk(urllib.unquote(dirname))))
        path=path.strip()
        path=path.rstrip("\\")
        isExists=os.path.exists(path)
        if not isExists:
            os.makedirs(path)
            return (True,ToDo().do_gbk2utf8(path)+" 創建成功")
        else:
            return (False,ToDo().do_gbk2utf8(path)+" 目錄已存在")

    def deal_delFile(self,path):
        if("?delete=" in path):
            dirname = path.split('?delete=',1)[1]
            dirpath = self.translate_path(self.path)
            path = posixpath.normpath(urllib.unquote(dirpath+dirname))
            path=path.strip()
            path=path.rstrip("\\")
        if os.path.isfile(path):
            try:
                os.remove(path)
                return (True, "文件刪除成功.")
            except Exception, e:
                return (False, "文件刪除失敗.\n"+e)
        elif os.path.isdir(path):
            for item in os.listdir(path):
                itempath = os.path.join(path,item)
                self.deal_delFile(itempath)
            try:
                os.rmdir(path)
                return (True, "文件刪除成功.")
            except Exception,e:
                return (False, "文件刪除失敗.\n"+e)
    def deal_getMD5(self,path):
        if("?getMD5=" in path):
            dirname = path.split('?getMD5=',1)[1]
            dirpath = self.translate_path(self.path)
            path = posixpath.normpath(urllib.unquote(dirpath+dirname))
            path=path.strip()
            path=path.rstrip("\\")
        if os.path.isfile(path):
            try:
                return (True, ""+ToDo().md5sum(path))
            except Exception, e:
                return (False, "文件MD5值計算失敗."+e)
        elif os.path.isdir(path):
            return (False, "文件MD5值計算失敗."+e)
            
    def deal_post_data(self):
        boundary = self.headers.plisttext.split("=")[1]
        remainbytes = int(self.headers['content-length'])
        line = self.rfile.readline()
        remainbytes -= len(line)
        if not boundary in line:
            return (False, "Content NOT begin with boundary")
        line = self.rfile.readline()
        remainbytes -= len(line)
        fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line)
        if not fn:
            return (False, "Can't find out file name...")
        path = self.translate_path(self.path)
        try:
            fn = os.path.join(path, ToDo().do_utf82gbk(fn[0]))
        except Exception, e:
            return (False, "文件名請不要用中文,或者使用IE上傳中文名的文件。"+e)
        while os.path.exists(fn):
            fn += "_"
        line = self.rfile.readline()
        remainbytes -= len(line)
        line = self.rfile.readline()
        remainbytes -= len(line)
        try:
            out = open(fn,'wb')
        except IOError:
            return (False, "Can't create file to write, do you have permission to write?")
        preline = self.rfile.readline()
        remainbytes -= len(preline)
        while remainbytes > 0:
            line = self.rfile.readline()
            remainbytes -= len(line)
            if boundary in line:
                preline = preline[0:-1]
                if preline.endswith('\r'):
                    preline = preline[0:-1]
                out.write(preline)
                out.close()
                return (True, "File %s upload success!" % ToDo().do_gbk2utf8(fn))
            else:
                out.write(preline)
                preline = line
        return (False, "Unexpect Ends of data.")

    def send_head(self):
        path = self.translate_path(self.path)
        f = None
        if os.path.isdir(path):
            parts = urlparse.urlsplit(self.path)
            if not parts.path.endswith('/'):
                # redirect browser - doing basically what apache does
                self.send_response(301)
                new_parts = (parts[0], parts[1], parts[2] + '/',
                             parts[3], parts[4])
                new_url = urlparse.urlunsplit(new_parts)
                self.send_header("Location", new_url)
                self.end_headers()
                return None
            for index in "index.html", "index.htm":
                index = os.path.join(path, index)
                if os.path.exists(index):
                    path = index
                    break
            else:
                return self.list_directory(path)
        ctype = self.guess_type(path)
        try:
            # Always read in binary mode. Opening files in text mode may cause
            # newline translations, making the actual size of the content
            # transmitted *less* than the content-length!
            f = open(path, 'rb')
        except IOError:
            self.send_error(404, "File not found")
            return None
        try:
            self.send_response(200)
            self.send_header("Content-type", ctype)
            fs = os.fstat(f.fileno())
            self.send_header("Content-Length", str(fs[6]))
            self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
            self.end_headers()
            return f
        except:
            f.close()
            raise

    def list_directory(self, path):
        try:
            list = os.listdir(path)
        except os.error:
            self.send_error(404, "No permission to list directory")
            return None
        list.sort(key=lambda a: a.lower())
        f = StringIO()
        displaypath = cgi.escape(urllib.unquote(self.path))
        View().do_buildHeadMessage(f,'文件列表 '+ToDo().do_gbk2utf8(displaypath))
        f.write("<body>\n<h1>文件列表 %s</h1>\n" % ToDo().do_gbk2utf8(displaypath))
        f.write('<HR size="1" noshade="noshade">\n')
        f.write('<table><tr><td>')
        f.write("<input type=\"button\" value=\"返回\" onClick=\"javascript:history.back(-1);\"></td><td>")
        f.write("<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
        f.write("<input name=\"file\" type=\"file\"/>")
        f.write("<input type=\"submit\" value=\"上傳\"/>")
        f.write("</form></td><td>")
        f.write('<form method="get">')
        f.write('<input type="text" name="makedir" />')
        f.write('<input type="submit" value="新建文件夾" />  ')
        f.write("<input type=\"button\" value=\"返回首頁\" onClick=\"location='/'\">")
        f.write('</form></td></tr></table>')
        f.write('<HR size="1" noshade="noshade">')
        f.write('<table border=0 width="100%" cellspacing="0" cellpadding="5" align="center" style="overflow: scroll;word-break: keep-all"><tr bgcolor="#00DB00"><td>序號</td><td>文件名</td><td>文件大小</td><td>文件創建時間</td><td>MD5</td></tr>')
        idn=1
        for name in list:
            fullname = os.path.join(path, name)
            colorName = linkname = name
            # Append / for directories or @ for symbolic links
            filename = os.getcwd() + displaypath + name
            print filename
            filesize = 0
            if os.path.isfile(filename):
                filesize = os.path.getsize(filename)   
            elif os.path.isdir(filename):
                filesize = os.path.getsize(filename)
            else:
                print ToDo().getTimeNow()+'The file can`t read it:'+filename
            if(idn%2==0):
                f.write('<tr>')
            else:
                f.write('<tr bgcolor="#eeeeee">')
            emd5 = "<a id=a_%d onclick=callBackMD5('?getMD5=%s','a_%d')>MD5</a>" % (idn,urllib.quote(linkname),idn)
            if os.path.isdir(fullname):
                colorName = '<span style="background-color: #CEFFCE;">' + name + '/</span>'
                linkname = name + "/"
                emd5 = ''
            if os.path.islink(fullname):
                colorName = '<span style="background-color: #FFBFFF;">' + name + '@</span>'
                emd5 = ''
                # Note: a link to a directory displays with @ and links with /
            f.write(
                '<td width="10%%">%d</td><td width="30%%"><a href="%s">%s</a></td><td width="20%%">%s</td><td width="20%%">%s</td><td width="50%%">%s</td></tr>\n'
                % (idn,urllib.quote(linkname), ToDo().do_gbk2utf8(colorName),
                   ToDo().sizeof_fmt(filesize), ToDo().modification_date(filename),emd5))
            idn = idn+1
        list = []
        f.write('</table>\n<HR size="1" noshade="noshade">\n<h2>Powered By [email protected]</h2>\n</body>\n</html>\n')
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/html; charset=%s" % "utf-8")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        return f

    def translate_path(self, path):
        # abandon query parameters
        path = path.split('?',1)[0]
        path = path.split('#',1)[0]
        # Don't forget explicit trailing slash when normalizing. Issue17324
        trailing_slash = path.rstrip().endswith('/')
        path = posixpath.normpath(urllib.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        path = os.getcwd()
        for word in words:
            if os.path.dirname(word) or word in (os.curdir, os.pardir):
                # Ignore components that are not a simple file/directory name
                continue
            path = os.path.join(path, word)
        if trailing_slash:
            path += '/'
        return path

    def copyfile(self, source, outputfile):
        shutil.copyfileobj(source, outputfile)

    def guess_type(self, path):
        base, ext = posixpath.splitext(path)
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        ext = ext.lower()
        if ext in self.extensions_map:
            return self.extensions_map[ext]
        else:
            return self.extensions_map['']

    if not mimetypes.inited:
        mimetypes.init() # try to read system mime.types
    extensions_map = mimetypes.types_map.copy()
    extensions_map.update({
        '': 'application/octet-stream', # Default
        '.py': 'text/plain',
        '.c': 'text/plain',
        '.h': 'text/plain',
        })

class ThreadingServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
    pass

def test(HandlerClass = SimpleHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)

if __name__ == '__main__':
    # test()
    # 單線程
    # srvr = BaseHTTPServer.HTTPServer(serveraddr, SimpleHTTPRequestHandler)
    # 多線程
    srvr = ThreadingServer(ToDo().showTips(), SimpleHTTPRequestHandler)
    srvr.serve_forever()

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章