aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2019-11-15 19:04:47 +0100
committerGitHub <noreply@github.com>2019-11-15 19:04:47 +0100
commit698f7e2e177baf313e6af62ec0f79a26693e430b (patch)
tree3976db70da1c8f3bb1a38e4db6a3e5c400c6a7fe
parenta79e0a0868f68aa0decaf2a67dd563c68cad8da9 (diff)
parent580ba356adf4e11241725005eb79d47f3468e092 (diff)
downloadmitmproxy-698f7e2e177baf313e6af62ec0f79a26693e430b.tar.gz
mitmproxy-698f7e2e177baf313e6af62ec0f79a26693e430b.tar.bz2
mitmproxy-698f7e2e177baf313e6af62ec0f79a26693e430b.zip
Merge pull request #3420 from rjt-gupta/multipart-fix
multipart-fix
-rw-r--r--mitmproxy/net/http/multipart.py55
-rw-r--r--mitmproxy/net/http/request.py3
-rw-r--r--mitmproxy/tools/console/consoleaddons.py9
-rw-r--r--mitmproxy/tools/console/grideditor/editors.py20
-rw-r--r--mitmproxy/tools/console/window.py3
-rw-r--r--test/mitmproxy/net/http/test_multipart.py37
-rw-r--r--test/mitmproxy/net/http/test_request.py7
7 files changed, 114 insertions, 20 deletions
diff --git a/mitmproxy/net/http/multipart.py b/mitmproxy/net/http/multipart.py
index a854d47f..4edf76ac 100644
--- a/mitmproxy/net/http/multipart.py
+++ b/mitmproxy/net/http/multipart.py
@@ -1,8 +1,43 @@
import re
-
+import mimetypes
+from urllib.parse import quote
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):
"""
Takes a multipart boundary encoded string and returns list of (key, value) tuples.
@@ -19,14 +54,14 @@ def decode(hdrs, content):
rx = re.compile(br'\bname="([^"]+)"')
r = []
-
- for i in content.split(b"--" + boundary):
- parts = i.splitlines()
- if len(parts) > 1 and parts[0][0:2] != b"--":
- match = rx.search(parts[1])
- if match:
- key = match.group(1)
- value = b"".join(parts[3 + parts[2:].index(b""):])
- r.append((key, value))
+ if content is not None:
+ for i in content.split(b"--" + boundary):
+ parts = i.splitlines()
+ if len(parts) > 1 and parts[0][0:2] != b"--":
+ match = rx.search(parts[1])
+ if match:
+ key = match.group(1)
+ value = b"".join(parts[3 + parts[2:].index(b""):])
+ r.append((key, value))
return r
return []
diff --git a/mitmproxy/net/http/request.py b/mitmproxy/net/http/request.py
index 1569ea72..ba699e2a 100644
--- a/mitmproxy/net/http/request.py
+++ b/mitmproxy/net/http/request.py
@@ -472,7 +472,8 @@ class Request(message.Message):
return ()
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
def multipart_form(self):
diff --git a/mitmproxy/tools/console/consoleaddons.py b/mitmproxy/tools/console/consoleaddons.py
index b6602413..9f595b42 100644
--- a/mitmproxy/tools/console/consoleaddons.py
+++ b/mitmproxy/tools/console/consoleaddons.py
@@ -381,7 +381,8 @@ class ConsoleAddon:
"""
return [
"cookies",
- "form",
+ "urlencoded form",
+ "multipart form",
"path",
"method",
"query",
@@ -416,8 +417,10 @@ class ConsoleAddon:
flow.response = http.HTTPResponse.make()
if part == "cookies":
self.master.switch_view("edit_focus_cookies")
- elif part == "form":
- self.master.switch_view("edit_focus_form")
+ elif part == "urlencoded form":
+ self.master.switch_view("edit_focus_urlencoded_form")
+ elif part == "multipart form":
+ self.master.switch_view("edit_focus_multipart_form")
elif part == "path":
self.master.switch_view("edit_focus_path")
elif part == "query":
diff --git a/mitmproxy/tools/console/grideditor/editors.py b/mitmproxy/tools/console/grideditor/editors.py
index b4d59384..a4b46a51 100644
--- a/mitmproxy/tools/console/grideditor/editors.py
+++ b/mitmproxy/tools/console/grideditor/editors.py
@@ -53,14 +53,30 @@ class ResponseHeaderEditor(HeaderEditor):
flow.response.headers = Headers(vals)
-class RequestFormEditor(base.FocusEditor):
- title = "Edit URL-encoded Form"
+class RequestMultipartEditor(base.FocusEditor):
+ title = "Edit Multipart Form"
columns = [
col_text.Column("Key"),
col_text.Column("Value")
]
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)
def set_data(self, vals, flow):
diff --git a/mitmproxy/tools/console/window.py b/mitmproxy/tools/console/window.py
index 7669299c..fb2e8c1e 100644
--- a/mitmproxy/tools/console/window.py
+++ b/mitmproxy/tools/console/window.py
@@ -64,7 +64,8 @@ class WindowStack:
edit_focus_cookies = grideditor.CookieEditor(master),
edit_focus_setcookies = grideditor.SetCookieEditor(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_request_headers = grideditor.RequestHeaderEditor(master),
edit_focus_response_headers = grideditor.ResponseHeaderEditor(master),
diff --git a/test/mitmproxy/net/http/test_multipart.py b/test/mitmproxy/net/http/test_multipart.py
index 68ae6bbd..6d2e5017 100644
--- a/test/mitmproxy/net/http/test_multipart.py
+++ b/test/mitmproxy/net/http/test_multipart.py
@@ -1,5 +1,6 @@
from mitmproxy.net.http import Headers
from mitmproxy.net.http import multipart
+import pytest
def test_decode():
@@ -22,3 +23,39 @@ def test_decode():
assert len(form) == 2
assert form[0] == (b"field1", b"value1")
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''
diff --git a/test/mitmproxy/net/http/test_request.py b/test/mitmproxy/net/http/test_request.py
index ef581a91..71d5c7a1 100644
--- a/test/mitmproxy/net/http/test_request.py
+++ b/test/mitmproxy/net/http/test_request.py
@@ -371,6 +371,7 @@ class TestRequestUtils:
assert list(request.multipart_form.items()) == []
def test_set_multipart_form(self):
- request = treq(content=b"foobar")
- with pytest.raises(NotImplementedError):
- request.multipart_form = "foobar"
+ request = treq()
+ request.multipart_form = [("file", "shell.jpg"), ("file_size", "1000")]
+ assert request.headers["Content-Type"] == 'multipart/form-data'
+ assert request.content is None