Merge pull request #3420 from rjt-gupta/multipart-fix

multipart-fix
This commit is contained in:
Maximilian Hils 2019-11-15 19:04:47 +01:00 committed by GitHub
commit 698f7e2e17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 114 additions and 20 deletions

View File

@ -1,8 +1,43 @@
import re import re
import mimetypes
from urllib.parse import quote
from mitmproxy.net.http import headers from mitmproxy.net.http import headers
def encode(head, l):
k = head.get("content-type")
if k:
k = headers.parse_content_type(k)
if k is not None:
try:
boundary = k[2]["boundary"].encode("ascii")
boundary = quote(boundary)
except (KeyError, UnicodeError):
return b""
hdrs = []
for key, value in l:
file_type = mimetypes.guess_type(str(key))[0] or "text/plain; charset=utf-8"
if key:
hdrs.append(b"--%b" % boundary.encode('utf-8'))
disposition = b'form-data; name="%b"' % key
hdrs.append(b"Content-Disposition: %b" % disposition)
hdrs.append(b"Content-Type: %b" % file_type.encode('utf-8'))
hdrs.append(b'')
hdrs.append(value)
hdrs.append(b'')
if value is not None:
# If boundary is found in value then raise ValueError
if re.search(rb"^--%b$" % re.escape(boundary.encode('utf-8')), value):
raise ValueError(b"boundary found in encoded string")
hdrs.append(b"--%b--\r\n" % boundary.encode('utf-8'))
temp = b"\r\n".join(hdrs)
return temp
def decode(hdrs, content): def decode(hdrs, content):
""" """
Takes a multipart boundary encoded string and returns list of (key, value) tuples. Takes a multipart boundary encoded string and returns list of (key, value) tuples.
@ -19,7 +54,7 @@ def decode(hdrs, content):
rx = re.compile(br'\bname="([^"]+)"') rx = re.compile(br'\bname="([^"]+)"')
r = [] r = []
if content is not None:
for i in content.split(b"--" + boundary): for i in content.split(b"--" + boundary):
parts = i.splitlines() parts = i.splitlines()
if len(parts) > 1 and parts[0][0:2] != b"--": if len(parts) > 1 and parts[0][0:2] != b"--":

View File

@ -472,7 +472,8 @@ class Request(message.Message):
return () return ()
def _set_multipart_form(self, value): def _set_multipart_form(self, value):
raise NotImplementedError() self.content = mitmproxy.net.http.multipart.encode(self.headers, value)
self.headers["content-type"] = "multipart/form-data"
@property @property
def multipart_form(self): def multipart_form(self):

View File

@ -381,7 +381,8 @@ class ConsoleAddon:
""" """
return [ return [
"cookies", "cookies",
"form", "urlencoded form",
"multipart form",
"path", "path",
"method", "method",
"query", "query",
@ -416,8 +417,10 @@ class ConsoleAddon:
flow.response = http.HTTPResponse.make() flow.response = http.HTTPResponse.make()
if part == "cookies": if part == "cookies":
self.master.switch_view("edit_focus_cookies") self.master.switch_view("edit_focus_cookies")
elif part == "form": elif part == "urlencoded form":
self.master.switch_view("edit_focus_form") self.master.switch_view("edit_focus_urlencoded_form")
elif part == "multipart form":
self.master.switch_view("edit_focus_multipart_form")
elif part == "path": elif part == "path":
self.master.switch_view("edit_focus_path") self.master.switch_view("edit_focus_path")
elif part == "query": elif part == "query":

View File

@ -53,14 +53,30 @@ class ResponseHeaderEditor(HeaderEditor):
flow.response.headers = Headers(vals) flow.response.headers = Headers(vals)
class RequestFormEditor(base.FocusEditor): class RequestMultipartEditor(base.FocusEditor):
title = "Edit URL-encoded Form" title = "Edit Multipart Form"
columns = [ columns = [
col_text.Column("Key"), col_text.Column("Key"),
col_text.Column("Value") col_text.Column("Value")
] ]
def get_data(self, flow): def get_data(self, flow):
return flow.request.multipart_form.items(multi=True)
def set_data(self, vals, flow):
flow.request.multipart_form = vals
class RequestUrlEncodedEditor(base.FocusEditor):
title = "Edit UrlEncoded Form"
columns = [
col_text.Column("Key"),
col_text.Column("Value")
]
def get_data(self, flow):
return flow.request.urlencoded_form.items(multi=True) return flow.request.urlencoded_form.items(multi=True)
def set_data(self, vals, flow): def set_data(self, vals, flow):

View File

@ -64,7 +64,8 @@ class WindowStack:
edit_focus_cookies = grideditor.CookieEditor(master), edit_focus_cookies = grideditor.CookieEditor(master),
edit_focus_setcookies = grideditor.SetCookieEditor(master), edit_focus_setcookies = grideditor.SetCookieEditor(master),
edit_focus_setcookie_attrs = grideditor.CookieAttributeEditor(master), edit_focus_setcookie_attrs = grideditor.CookieAttributeEditor(master),
edit_focus_form = grideditor.RequestFormEditor(master), edit_focus_multipart_form=grideditor.RequestMultipartEditor(master),
edit_focus_urlencoded_form=grideditor.RequestUrlEncodedEditor(master),
edit_focus_path = grideditor.PathEditor(master), edit_focus_path = grideditor.PathEditor(master),
edit_focus_request_headers = grideditor.RequestHeaderEditor(master), edit_focus_request_headers = grideditor.RequestHeaderEditor(master),
edit_focus_response_headers = grideditor.ResponseHeaderEditor(master), edit_focus_response_headers = grideditor.ResponseHeaderEditor(master),

View File

@ -1,5 +1,6 @@
from mitmproxy.net.http import Headers from mitmproxy.net.http import Headers
from mitmproxy.net.http import multipart from mitmproxy.net.http import multipart
import pytest
def test_decode(): def test_decode():
@ -22,3 +23,39 @@ def test_decode():
assert len(form) == 2 assert len(form) == 2
assert form[0] == (b"field1", b"value1") assert form[0] == (b"field1", b"value1")
assert form[1] == (b"field2", b"value2") assert form[1] == (b"field2", b"value2")
boundary = 'boundary茅莽'
headers = Headers(
content_type='multipart/form-data; boundary=' + boundary
)
result = multipart.decode(headers, content)
assert result == []
headers = Headers(
content_type=''
)
assert multipart.decode(headers, content) == []
def test_encode():
data = [("file".encode('utf-8'), "shell.jpg".encode('utf-8')),
("file_size".encode('utf-8'), "1000".encode('utf-8'))]
headers = Headers(
content_type='multipart/form-data; boundary=127824672498'
)
content = multipart.encode(headers, data)
assert b'Content-Disposition: form-data; name="file"' in content
assert b'Content-Type: text/plain; charset=utf-8\r\n\r\nshell.jpg\r\n\r\n--127824672498\r\n' in content
assert b'1000\r\n\r\n--127824672498--\r\n'
assert len(content) == 252
with pytest.raises(ValueError, match=r"boundary found in encoded string"):
multipart.encode(headers, [("key".encode('utf-8'), "--127824672498".encode('utf-8'))])
boundary = 'boundary茅莽'
headers = Headers(
content_type='multipart/form-data; boundary=' + boundary
)
result = multipart.encode(headers, data)
assert result == b''

View File

@ -371,6 +371,7 @@ class TestRequestUtils:
assert list(request.multipart_form.items()) == [] assert list(request.multipart_form.items()) == []
def test_set_multipart_form(self): def test_set_multipart_form(self):
request = treq(content=b"foobar") request = treq()
with pytest.raises(NotImplementedError): request.multipart_form = [("file", "shell.jpg"), ("file_size", "1000")]
request.multipart_form = "foobar" assert request.headers["Content-Type"] == 'multipart/form-data'
assert request.content is None