#!/usr/bin/env python3
 
"""
Powered by "Simple HTTP Server With Upload." 
https://gist.github.com/UniIsland/3346170
"""
 
 
__version__ = "1.0"
__all__ = ["SimpleHTTPRequestHandler"]
__author__ = "bones7456"
__home_page__ = "https://gist.github.com/UniIsland/3346170"
import getpass
import os
import posixpath
import http.server
import socketserver
import urllib.request, urllib.parse, urllib.error
import html
import shutil
import mimetypes
import re
import argparse
import base64
from io import BytesIO
import json

def jsonget(key_name):
   with  open("setting.json") as f:
        json_data = f.read()
   data = json.loads(json_data)
   f.close()
   return data[key_name]
def start():
    theport = int(jsonget('webserverport'))
    os.chdir(jsonget('webserverroot'))
    class SimpleHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
    
        """Simple HTTP request handler with GET/HEAD/POST commands.

        This serves files from the current directory and any of its
        subdirectories.  The MIME type for files is determined by
        calling the .guess_type() method. And can reveive file uploaded
        by client.

        The GET/HEAD/POST requests are identical except that the HEAD
        request omits the actual contents of the file.

        """
    
        server_version = "SimpleHTTPWithUpload/" + __version__
    
        def do_GET(self):
            """Serve a GET request."""
            f = self.send_head()
            if f:
                self.copyfile(f, self.wfile)
                f.close()
    
        def do_HEAD(self):
            """Serve a HEAD request."""
            f = self.send_head()
            if f:
                f.close()
    
        def do_POST(self):
            """Serve a POST request."""
            r, info = self.deal_post_data()
            print((r, info, "by: ", self.client_address))
            f = BytesIO()
            f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />')
            f.write(b"<html>\n<title>\xe4\xb8\x8a\xe4\xbc\xa0\xe7\xbb\x93\xe6\x9e\x9c\xe9\xa1\xb5\xe9\x9d\xa2</title>\n")
            f.write(b"<body>\n<h2>\xe4\xb8\x8a\xe4\xbc\xa0\xe7\xbb\x93\xe6\x9e\x9c\xe9\xa1\xb5\xe9\x9d\xa2</h2>\n")
            f.write(b"<hr>\n")
            if r:
                f.write(b"<strong>\xe6\x88\x90\xe5\x8a\x9f:</strong>")
            else:
                f.write(b"<strong>\xe5\xa4\xb1\xe8\xb4\xa5:</strong>")
            f.write(info.encode())
            f.write(("<br><a href=\"%s\">返回</a>" % self.headers['referer']).encode())
            f.write(b"<hr><small>\xe4\xbb\xa5LAN++\xe4\xb8\xba\xe9\xa9\xb1\xe5\x8a\xa8 | \xe6\xba\x90\xe4\xbb\xa3\xe7\xa0\x81\xe5\x8f\x96\xe8\x87\xaabones7456")
            f.write(b'<br><a href=\"http://david-ajax.github.io/LANpp/index.html">')
            f.write(b"\xe5\xae\x98\xe6\x96\xb9\xe7\xbd\x91\xe7\xab\x99</a>.</small></body>\n</html>\n")
            length = f.tell()
            f.seek(0)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header("Content-Length", str(length))
            self.end_headers()
            if f:
                self.copyfile(f, self.wfile)
                f.close()
            
        def deal_post_data(self):
            uploaded_files = []
            content_type = self.headers['content-type']
            if not content_type:
                return (False, "内容类型头不含有边界")
            boundary = content_type.split("=")[1].encode()
            remainbytes = int(self.headers['content-length'])
            line = self.rfile.readline()
            remainbytes -= len(line)
            if not boundary in line:
                return (False, "内容没有边界")
            while remainbytes > 0:
                line = self.rfile.readline()
                remainbytes -= len(line)
                fn = re.findall(r'Content-Disposition.*name="file"; filename="(.*)"', line.decode())
                if not fn:
                    return (False, "无法获取文件名...")
                path = self.translate_path(self.path)
                fn = os.path.join(path, fn[0])
                line = self.rfile.readline()
                remainbytes -= len(line)
                line = self.rfile.readline()
                remainbytes -= len(line)
                try:
                    out = open(fn, 'wb')
                except IOError:
                    return (False, "无法写入文件<br>您是否以足够的权限运行LAN++及其组件?您是否选择了文件以上传?")
                else:
                    with out:                    
                        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(b'\r'):
                                    preline = preline[0:-1]
                                out.write(preline)
                                uploaded_files.append(fn)
                                break
                            else:
                                out.write(preline)
                                preline = line
            return (True, "文件 '%s' 上传成功" % ",".join(uploaded_files))
    
        def send_head(self):
            path = self.translate_path(self.path)
            f = None
            if os.path.isdir(path):
                if not self.path.endswith('/'):
                    # redirect browser - doing basically what apache does
                    self.send_response(301)
                    self.send_header("Location", self.path + "/")
                    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, "没有找到文件 错误码:404")
                return None
            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
    
        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 = BytesIO()
            displaypath = html.escape(urllib.parse.unquote(self.path))
            f.write(b'<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><meta http-equiv="Content-Type" content="text/html; charset=utf-8" />')
            f.write(("<html>\n<title>此目录下的文件列表: %s</title>\n" % displaypath).encode())
            f.write(b'<style type="text/css">\n')
            f.write(b'a { text-decoration: none; }\n')
            f.write(b'a:link { text-decoration: none; font-weight: bold; color: #0000ff; }\n')
            f.write(b'a:visited { text-decoration: none; font-weight: bold; color: #0000ff; }\n')
            f.write(b'a:active { text-decoration: none; font-weight: bold; color: #0000ff; }\n')
            f.write(b'a:hover { text-decoration: none; font-weight: bold; color: #ff0000; }\n')
            f.write(b'</style>\n')
            f.write(("<body>\n<h2>此目录下的文件列表:" + displaypath + "</2><br/><h5>注意:<br/>1:您当前以[" + getpass.getuser() + "]用户查看文件(您或许对某些文件没有查看权限)<br>2:选择文件上传后,如文件较大,请只点击一次按钮直到页面刷新</h5>").encode())
            f.write(b"<hr>\n")
            f.write(b"<form ENCTYPE=\"multipart/form-data\" method=\"post\">")
            f.write(b"<input name=\"file\" type=\"file\" multiple/>")
            f.write(b"<input type=\"submit\" value=\"\xe4\xb8\x8a\xe4\xbc\xa0\xe9\x80\x89\xe6\x8b\xa9\xe7\x9a\x84\xe6\x96\x87\xe4\xbb\xb6\"/></form>\n")
            f.write(b"<hr>\n")
            f.write(b'<a href="../"><img src="data:image/png;base64,[encoded data, 9448 characters]" alt="[PARENTDIR]" width="24" height="24">&nbsp;&nbsp;&nbsp;\xe8\xbf\x94\xe5\x9b\x9e\xe7\x88\xb6\xe7\x9b\xae\xe5\xbd\x95</a><br />\n')
            for name in list:
                dirimage = 'data:image/png;base64,[encoded data, 4644 characters]'
                fullname = os.path.join(path, name)
                displayname = linkname = name
                # Append / for directories or @ for symbolic links
                if os.path.isdir(fullname):
                    dirimage = 'data:image/png;base64,[encoded data, 4160 characters]'
                    displayname = name + "/"
                    linkname = name + "/"
                if os.path.islink(fullname):
                    dirimage = 'data:image/png;base64,[encoded data, 9324 characters]'
                    displayname = name + "@"
                if name.endswith(('.bmp','.gif','.jpg','.png')):
                    dirimage = 'data:image/png;base64,[encoded data, 5704 characters]'
                if name.endswith(('.avi','.mpg')):
                    dirimage = 'data:image/png;base64,[encoded data, 5536 characters]'
                if name.endswith(('.idx','.srt','.sub')):
                    dirimage = name
                if name.endswith('.iso'):
                    dirimage = name
                # Note: a link to a directory displays with @ and links with /
                f.write(('<a href="%s"><img src="%s" width="24" height="24">&nbsp;&nbsp;&nbsp;%s</a><br />\n'
                        % (urllib.parse.quote(linkname), dirimage , html.escape(displayname))).encode())
            f.write(b"<hr>\n</body>\n</html>\n")
            length = f.tell()
            f.seek(0)
            self.send_response(200)
            self.send_header("Content-type", "text/html")
            self.send_header("Content-Length", str(length))
            self.end_headers()
            return f
    
        def translate_path(self, path):
            """Translate a /-separated PATH to the local filename syntax.
            Components that mean special things to the local file system
            (e.g. drive or directory names) are ignored.  (XXX They should
            probably be diagnosed.)

            """
            # abandon query parameters
            path = path.split('?',1)[0]
            path = path.split('#',1)[0]
            path = posixpath.normpath(urllib.parse.unquote(path))
            words = path.split('/')
            words = [_f for _f in words if _f]
            path = os.getcwd()
            for word in words:
                drive, word = os.path.splitdrive(word)
                head, word = os.path.split(word)
                if word in (os.curdir, os.pardir): continue
                path = os.path.join(path, word)
            return path
    
        def copyfile(self, source, outputfile):
            """Copy all data between two file objects.

            The SOURCE argument is a file object open for reading
            (or anything with a read() method) and the DESTINATION
            argument is a file object open for writing (or
            anything with a write() method).

            The only reason for overriding this would be to change
            the block size or perhaps to replace newlines by CRLF
            -- note however that this the default server uses this
            to copy binary data as well.

            """
            shutil.copyfileobj(source, outputfile)
    
        def guess_type(self, path):
            """Guess the type of a file.

            Argument is a PATH (a filename).

            Return value is a string of the form type/subtype,
            usable for a MIME Content-type header.

            The default implementation looks the file's extension
            up in the table self.extensions_map, using application/octet-stream
            as a default; however it would be permissible (if
            slow) to look inside the data to make a better guess.

            """
    
            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',
            })
    
    parser = argparse.ArgumentParser()
    parser.add_argument('--bind', '-b', default='', metavar='ADDRESS',
                            help='Specify alternate bind address '
                                '[default: all interfaces]')
    parser.add_argument('port', action='store',
                            default=theport, type=int,
                            nargs='?',
                            help='Specify alternate port [default:' + str(theport) + ']')
    args = parser.parse_args()

    PORT = args.port
    BIND = args.bind
    HOST = BIND

    if HOST == '':
        HOST = 'localhost'

    Handler = SimpleHTTPRequestHandler

    with socketserver.TCPServer((BIND, PORT), Handler) as httpd:
        serve_message = "[Webserver - 1.0]\nServing HTTP on {host} port {port} (http://{host}:{port}/) ..."
        print(serve_message.format(host=HOST, port=PORT))
        httpd.serve_forever()
if __name__ == "__main__":
    start()