Using MultipartPostHandler for POST form data using Python

Problem: when placing data using Python urllib2, all data is encoded as a URL and sent as Content-Type: application / x-www-form-urlencoded. When downloading files, the content type should instead be set to multipart / form-data, and the content should be encoded in MIME.

To get around this limitation, some astute coders have created a library called MultipartPostHandler that creates an OpenerDirector that you can use with urllib2 to, in most cases, automatically execute POST with multipart / form-data. A copy of this library is here: MultipartPostHandler does not work for Unicode files

I am new to Python and cannot get this library to work. I wrote essentially the following code. When I write it to a local HTTP proxy, I see that the data is still URL-encoded rather than multi-part MIME-encoded. Please help me understand what I'm doing wrong, or the best way to do this. Thank :-)

FROM_ADDR = 'my@email.com' 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: Thanks for your reply. I am aware of the ActiveState httplib solution for this (I contacted him above). I would prefer to ignore the problem and use a minimal amount of code to continue using urllib2, as I was. Any ideas why the knife is not installed and not used?

+47
python file upload multipartform-data urllib2
Mar 25 '09 at 5:14
source share
6 answers

It seems that the easiest and most compatible way to get around this problem is to use the poster module.

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

This worked fine, and I didn't have to guess with httplib. The module is available here: http://atlee.ca/software/poster/index.html

+57
Mar 27 '09 at 1:31
source share

Found this recipe to send multipart using httplib directly (without the involvement of external libraries)

 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' 
+38
Mar 25 '09 at 11:29
source share

Just use python-requests , it will set the correct headers and download for you:

 import requests files = {"form_input_field_name": open("filename", "rb")} requests.post("http://httpbin.org/post", files=files) 
+33
Aug 08 '13 at 8:04 on
source share

I ran into the same problem, and I needed to do a multi-page form entry without using external libraries. I wrote a whole blog about the problems I encountered .

I ended up using a modified version of http://code.activestate.com/recipes/146306/ . The code in this url actually just adds the contents of the file as a string, which can cause binary problems. Here is my working code.

 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
Mar 29 '15 at 17:49
source share

I coincide, 2 years, 6 months ago I create a project

https://pypi.python.org/pypi/MultipartPostHandler2 which fix MultipartPostHandler for utf-8 systems. I also made some minor improvements, you can test it :)

0
Sep 25 '14 at 1:18
source share

To answer the OP question about why the source code did not work, the handler did not pass as an instance of the class. Line

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

must read

 opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler()) 
0
Nov 27 '16 at 13:27
source share



All Articles