wake-up-neo.net

Verwenden von MultipartPostHandler to POST Formulardaten mit Python

Problem: Beim POST von Daten mit Pythons urllib2 werden alle Daten URL-codiert und als Content-Type gesendet: application/x-www-form-urlencoded. Beim Hochladen von Dateien sollte stattdessen der Content-Type auf multipart/form-data gesetzt und der Inhalt MIME-codiert sein. Eine Diskussion dieses Problems ist hier: http://code.activestate.com/recipes/146306/

Um diese Einschränkung zu umgehen, wurde von einigen scharfen Codierern eine Bibliothek mit dem Namen MultipartPostHandler erstellt, die einen OpenerDirector erstellt, den Sie mit urllib2 verwenden können, um sie automatisch POST mit Multipart-/Formulardaten zu verwenden. Eine Kopie dieser Bibliothek finden Sie hier: http://peerit.blogspot.com/2007/07/multipartposthandler-doesnt-work-for.html

Ich bin neu in Python und kann diese Bibliothek nicht zum Laufen bringen. Ich habe im Wesentlichen folgenden Code geschrieben. Wenn ich sie in einem lokalen HTTP-Proxy aufzeichne, kann ich feststellen, dass die Daten immer noch URL-kodiert und nicht mehrteilig MIME-kodiert sind. Bitte helfen Sie mir herauszufinden, was ich falsch mache, oder eine bessere Möglichkeit, dies zu erreichen. Vielen Dank :-)

FROM_ADDR = '[email protected]'

try:
    data = open(file, 'rb').read()
except:
    print "Error: could not open file %s for reading" % file
    print "Check permissions on the file or folder it resides in"
    sys.exit(1)

# Build the POST request
url = "http://somedomain.com/?action=analyze"       
post_data = {}
post_data['analysisType'] = 'file'
post_data['executable'] = data
post_data['notification'] = 'email'
post_data['email'] = FROM_ADDR

# MIME encode the POST payload
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)
urllib2.install_opener(opener)
request = urllib2.Request(url, post_data)
request.set_proxy('127.0.0.1:8080', 'http') # For testing with Burp Proxy

# Make the request and capture the response
try:
    response = urllib2.urlopen(request)
    print response.geturl()
except urllib2.URLError, e:
    print "File upload failed..."

EDIT1: Danke für deine Antwort. Mir ist die ActiveState-Lösung von httplib bekannt (die ich oben verlinkt habe). Ich möchte das Problem lieber abstrahieren und so wenig wie möglich mit urllib2 weiterverwenden. Irgendeine Idee, warum der Öffner nicht installiert und verwendet wird?

45
Dan

Es scheint, dass der einfachste und kompatibelste Weg, dieses Problem zu umgehen, die Verwendung des Postermoduls ist.

# test_client.py
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers
import urllib2

# Register the streaming http handlers with urllib2
register_openers()

# Start the multipart/form-data encoding of the file "DSC0001.jpg"
# "image1" is the name of the parameter, which is normally set
# via the "name" parameter of the HTML <input> tag.

# headers contains the necessary Content-Type and Content-Length
# datagen is a generator object that yields the encoded parameters
datagen, headers = multipart_encode({"image1": open("DSC0001.jpg")})

# Create the Request object
request = urllib2.Request("http://localhost:5000/upload_image", datagen, headers)
# Actually do the request, and get the response
print urllib2.urlopen(request).read()

Das hat perfekt funktioniert und ich musste mich nicht mit httplib herumschlagen. Das Modul ist hier verfügbar: http://atlee.ca/software/poster/index.html

57
Dan

Fand dieses Rezept, um Multipart mit httplib direkt zu posten (keine externen Bibliotheken beteiligt)

import httplib
import mimetypes

def post_multipart(Host, selector, fields, files):
    content_type, body = encode_multipart_formdata(fields, files)
    h = httplib.HTTP(Host)
    h.putrequest('POST', selector)
    h.putheader('content-type', content_type)
    h.putheader('content-length', str(len(body)))
    h.endheaders()
    h.send(body)
    errcode, errmsg, headers = h.getreply()
    return h.file.read()

def encode_multipart_formdata(fields, files):
    LIMIT = '----------lImIt_of_THE_fIle_eW_$'
    CRLF = '\r\n'
    L = []
    for (key, value) in fields:
        L.append('--' + LIMIT)
        L.append('Content-Disposition: form-data; name="%s"' % key)
        L.append('')
        L.append(value)
    for (key, filename, value) in files:
        L.append('--' + LIMIT)
        L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (key, filename))
        L.append('Content-Type: %s' % get_content_type(filename))
        L.append('')
        L.append(value)
    L.append('--' + LIMIT + '--')
    L.append('')
    body = CRLF.join(L)
    content_type = 'multipart/form-data; boundary=%s' % LIMIT
    return content_type, body

def get_content_type(filename):
    return mimetypes.guess_type(filename)[0] or 'application/octet-stream'
37
nosklo

Verwenden Sie einfach python-request , es werden die richtigen Header gesetzt und für Sie hochgeladen:

import requests 
files = {"form_input_field_name": open("filename", "rb")}
requests.post("http://httpbin.org/post", files=files)
32
Pawel Miech

Ich hatte das gleiche Problem und musste ein mehrteiliges Formular erstellen, ohne externe Bibliotheken verwenden zu müssen. Ich habe einen ganzen blogpost über die Probleme geschrieben, auf die ich gestoßen bin.

Am Ende habe ich eine modifizierte Version von http://code.activestate.com/recipes/146306/ verwendet. Der Code in dieser URL fügt den Inhalt der Datei eigentlich nur als Zeichenfolge an, was Probleme mit Binärdateien verursachen kann. Hier ist mein Arbeitscode.

import mimetools
import mimetypes
import io
import http
import json


form = MultiPartForm()
form.add_field("form_field", "my awesome data")

# Add a fake file     
form.add_file(key, os.path.basename(filepath),
    fileHandle=codecs.open("/path/to/my/file.Zip", "rb"))

# Build the request
url = "http://www.example.com/endpoint"
schema, netloc, url, params, query, fragments = urlparse.urlparse(url)

try:
    form_buffer =  form.get_binary().getvalue()
    http = httplib.HTTPConnection(netloc)
    http.connect()
    http.putrequest("POST", url)
    http.putheader('Content-type',form.get_content_type())
    http.putheader('Content-length', str(len(form_buffer)))
    http.endheaders()
    http.send(form_buffer)
except socket.error, e:
    raise SystemExit(1)

r = http.getresponse()
if r.status == 200:
    return json.loads(r.read())
else:
    print('Upload failed (%s): %s' % (r.status, r.reason))

class MultiPartForm(object):
    """Accumulate the data to be used when posting a form."""

    def __init__(self):
        self.form_fields = []
        self.files = []
        self.boundary = mimetools.choose_boundary()
        return

    def get_content_type(self):
        return 'multipart/form-data; boundary=%s' % self.boundary

    def add_field(self, name, value):
        """Add a simple field to the form data."""
        self.form_fields.append((name, value))
        return

    def add_file(self, fieldname, filename, fileHandle, mimetype=None):
        """Add a file to be uploaded."""
        body = fileHandle.read()
        if mimetype is None:
            mimetype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
        self.files.append((fieldname, filename, mimetype, body))
        return

    def get_binary(self):
        """Return a binary buffer containing the form data, including attached files."""
        part_boundary = '--' + self.boundary

        binary = io.BytesIO()
        needsCLRF = False
        # Add the form fields
        for name, value in self.form_fields:
            if needsCLRF:
                binary.write('\r\n')
            needsCLRF = True

            block = [part_boundary,
              'Content-Disposition: form-data; name="%s"' % name,
              '',
              value
            ]
            binary.write('\r\n'.join(block))

        # Add the files to upload
        for field_name, filename, content_type, body in self.files:
            if needsCLRF:
                binary.write('\r\n')
            needsCLRF = True

            block = [part_boundary,
              str('Content-Disposition: file; name="%s"; filename="%s"' % \
              (field_name, filename)),
              'Content-Type: %s' % content_type,
              ''
              ]
            binary.write('\r\n'.join(block))
            binary.write('\r\n')
            binary.write(body)


        # add closing boundary marker,
        binary.write('\r\n--' + self.boundary + '--\r\n')
        return binary
1
Jason Kulatunga

Um die Frage des OP zu beantworten, warum der ursprüngliche Code nicht funktionierte, war der übergebene Handler keine Instanz einer Klasse. Die Linie

# MIME encode the POST payload
opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler)

sollte lesen

opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler())
0
Daryl Tester

Was für ein Zufall, vor 2 Jahren, 6 Monaten erstelle ich das Projekt 

https://pypi.python.org/pypi/MultipartPostHandler2 , die MultipartPostHandler für utf-8-Systeme beheben. Ich habe auch ein paar kleinere Verbesserungen vorgenommen, die Sie gerne testen können :) 

0
Sérgio