Skip to content

Instantly share code, notes, and snippets.

@takase1121
Created November 16, 2020 14:39
Show Gist options
  • Save takase1121/64b09df8336a9ca23aa1db3cb62de016 to your computer and use it in GitHub Desktop.
Save takase1121/64b09df8336a9ca23aa1db3cb62de016 to your computer and use it in GitHub Desktop.
Generate multipart data in Python3

Wrote this. Turns out I don't need it at all since urllib3 has the exact same function. welp.

import unittest
from os import urandom
from io import BytesIO
from binascii import b2a_base64, hexlify
def is_bytes_like(input):
"""Returns true of input is a byte-like object"""
return hasattr(input, "decode")
def create_multipart(multi):
"""
Creates a multipart form data and its MIME type
data is a dictionary {name: (data, type, filename)}
name is written to the name field on Content-Disposition subheader
data is data as byte-like object or string. Byte-like object is encoded to base64 while strings are encoded as utf8.
type is Content-Type of the data
filename is written to the filename field on Content-Disposition subheader
All fields in tuple are optional except data. However, they must be filled with a falsy value such as None
returns (mime_type, multipart_data)
mime_type is a string you use for request's Content-Type header
multipart_data is a byte-like object that contains the multipart data
"""
boundary = hexlify(urandom(16))
output = BytesIO()
for key, (data, type, filename) in multi.items():
output.write(b"--")
output.write(boundary)
output.write(b"\r\n")
output.write(b'Content-Disposition:form-data;name="')
output.write(key.encode("ascii"))
output.write(b'";')
if filename:
output.write(b'filename="')
output.write(filename.encode("ascii"))
output.write(b'"')
output.write(b"\r\n")
if type:
output.write(b"Content-Type:")
output.write(type.encode("ascii"))
output.write(b"\r\n")
output.write(
b2a_base64(data, newline=False)
if is_bytes_like(data)
else data.encode("utf-8")
)
output.write(b"\r\n")
output.write(b"--")
output.write(boundary)
output.write(b"--")
content_type = b"Content-Type:multipart/form-data;boundary=" + boundary
return content_type.decode("ascii"), output.getvalue()
class MultipartTest(unittest.TestCase):
"""A little unit test for multipart data"""
def test_general(self):
mime, output = create_multipart(
{
"name": ("John Doe", None, None),
"result": ("Lorem ipsum...", "text/plain", None),
"attachment": (b"FILE", None, "file.txt"),
}
)
# get boundary
import re
boundary = re.search(r"boundary=([0-9a-f]+)$", mime)[1]
boundary = "--" + boundary
self.maxDiff = None
self.assertMultiLineEqual(
output.decode("utf-8"),
(
boundary
+ "\r\n"
+ 'Content-Disposition:form-data;name="name";\r\n'
+ "John Doe"
+ "\r\n"
+ boundary
+ "\r\n"
+ 'Content-Disposition:form-data;name="result";\r\n'
+ "Content-Type:text/plain\r\n"
+ "Lorem ipsum..."
+ "\r\n"
+ boundary
+ "\r\n"
+ 'Content-Disposition:form-data;name="attachment";filename="file.txt"\r\n'
+ "RklMRQ=="
+ "\r\n"
+ boundary
+ "--"
),
)
if __name__ == "__main__":
unittest.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment