Skip to content

Commit

Permalink
[web] fix query editing
Browse files Browse the repository at this point in the history
  • Loading branch information
yesmeck committed Oct 26, 2022
1 parent 201651c commit f481d96
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 12 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Expand Up @@ -56,6 +56,8 @@
([#5658](https://github.com/mitmproxy/mitmproxy/issues/5658), [#5661](https://github.com/mitmproxy/mitmproxy/issues/5661), @LIU-shuyi, @mhils)
* Added Docs for Transparent Mode on Windows.
([#5402](https://github.com/mitmproxy/mitmproxy/issues/5402), @stephenspol)
* Fix query editing on mitmweb.
([#5574](https://github.com/mitmproxy/mitmproxy/pull/5574), @yesmeck)


## 28 June 2022: mitmproxy 8.1.1
Expand Down
22 changes: 22 additions & 0 deletions mitmproxy/tools/web/app.py
Expand Up @@ -24,6 +24,7 @@
from mitmproxy import version
from mitmproxy.dns import DNSFlow
from mitmproxy.http import HTTPFlow
from mitmproxy.net.http import url
from mitmproxy.tcp import TCPFlow, TCPMessage
from mitmproxy.udp import UDPFlow, UDPMessage
from mitmproxy.utils.emoji import emoji
Expand Down Expand Up @@ -403,6 +404,8 @@ def put(self, flow_id):
request.trailers.add(*trailer)
elif k == "content":
request.text = v
elif k == "query":
request.query = [tuple(i) for i in v]
else:
raise APIError(400, f"Unknown update request.{k}: {v}")

Expand Down Expand Up @@ -485,6 +488,21 @@ def get(self, flow_id, message):
self.write(message.get_content(strict=False))


class FlowQuery(RequestHandler):
def post(self, flow_id, message):
self.flow.backup()
message = getattr(self.flow, message)
message.query = url.decode(b"&".join(self.filecontents.strip().splitlines()))
self.view.update([self.flow])

def get(self, flow_id, message):
message = getattr(self.flow, message)
self.set_header("Content-Type", "application/text")
self.set_header("X-Content-Type-Options", "nosniff")
self.set_header("X-Frame-Options", "DENY")
self.write("\n".join("=".join(field) for field in message.query.fields))


class FlowContentView(RequestHandler):
def message_to_json(
self,
Expand Down Expand Up @@ -674,6 +692,10 @@ def __init__(
r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response|messages)/content.data",
FlowContent,
),
(
r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response|messages)/query.data",
FlowQuery,
),
(
r"/flows/(?P<flow_id>[0-9a-f\-]+)/(?P<message>request|response|messages)/"
r"content/(?P<content_view>[0-9a-zA-Z\-\_%]+)(?:\.json)?",
Expand Down
15 changes: 15 additions & 0 deletions test/mitmproxy/tools/web/test_app.py
Expand Up @@ -306,13 +306,15 @@ def test_flow_update(self):
upd = {
"request": {
"trailers": [("foo", "baz")],
"query": [("foo", "2")]
},
"response": {
"trailers": [("foo", "baz")],
},
}
assert self.put_json("/flows/42", upd).code == 200
assert f.request.trailers["foo"] == "baz"
assert f.request.query["foo"] == "2"

f.revert()

Expand Down Expand Up @@ -390,6 +392,19 @@ def test_flow_content_returns_raw_content_when_decoding_fails(self):

f.revert()

def test_flow_query(self):
f = self.view.get_by_id("42")
f.backup()

f.request.query = (("foo", "1"), ("bar", "2"))

r = self.fetch("/flows/42/request/query.data")

assert r.code == 200
assert r.body == b"foo=1\nbar=2"

f.revert()

def test_update_flow_content(self):
assert (
self.fetch("/flows/42/request/content.data", method="POST", body="new").code
Expand Down
65 changes: 64 additions & 1 deletion web/src/js/__tests__/components/contentviews/HttpMessageSpec.tsx
Expand Up @@ -4,7 +4,20 @@ import HttpMessage, {ViewImage} from '../../../components/contentviews/HttpMessa
import {fireEvent, render, screen, waitFor} from "../../test-utils"
import fetchMock, {enableFetchMocks} from "jest-fetch-mock";

jest.mock("../../../contrib/CodeMirror")
jest.mock("../../../contrib/CodeMirror", () => {
const React = require("react");
return {
__esModule: true,
default: React.forwardRef((props, ref) => {
React.useImperativeHandle(ref, () => ({
codeMirror: {
getValue: () => props.value
}
}));
return <div>{props.value}</div>
})
}
})

enableFetchMocks();

Expand All @@ -25,13 +38,19 @@ test("HttpMessage", async () => {
description: "Raw",
}),
"raw content",
JSON.stringify({
lines: Array(5).fill([["text", "rawdata"]]),
description: "Raw",
}),
"",
JSON.stringify({
lines: Array(5).fill([["text", "rawdata"]]),
description: "Raw",
})
);

const tflow = TFlow();
tflow.request.method = "POST";
const {asFragment} = render(<HttpMessage flow={tflow} message={tflow.request}/>);
await waitFor(() => screen.getAllByText("data"));
expect(screen.queryByText('additional')).toBeNull();
Expand All @@ -50,6 +69,50 @@ test("HttpMessage", async () => {

await waitFor(() => screen.getAllByText("rawdata"));
expect(asFragment()).toMatchSnapshot();

fireEvent.click(screen.getByText("Edit"));
fireEvent.click(screen.getByText("Done"));
await waitFor(() => screen.getAllByText("rawdata"));
expect(asFragment()).toMatchSnapshot();
});

test("HttpMessage edit query string", async () => {
const lines = [
[
["header", "foo"],
["text", "1"],
],
[
["header", "bar"],
["text", "2"],
],
];

fetchMock.mockResponses(
JSON.stringify({
lines: lines,
description: "Query",
}),
"foo=1\nbar=2",
'',
JSON.stringify({
lines,
description: "Query",
})
);

const tflow = TFlow();
tflow.request.path = "/path?foo=1&bar=2";
const { asFragment, debug } = render(
<HttpMessage flow={tflow} message={tflow.request} />
);
fireEvent.click(screen.getByText("Edit"));
await waitFor(() => screen.getAllByText(/foo/));
expect(asFragment()).toMatchSnapshot();
fireEvent.click(screen.getByText("Done"));

await waitFor(() => screen.getAllByText("foo"));
expect(asFragment()).toMatchSnapshot();
});

test("ViewImage", async () => {
Expand Down
Expand Up @@ -126,7 +126,11 @@ exports[`HttpMessage 2`] = `
</div>
<div
class="codeeditor"
/>
>
<div>
{"lines":[[["text","rawdata"]],[["text","rawdata"]],[["text","rawdata"]],[["text","rawdata"]],[["text","rawdata"]]],"description":"Raw"}
</div>
</div>
</div>
</DocumentFragment>
`;
Expand Down Expand Up @@ -226,6 +230,189 @@ exports[`HttpMessage 3`] = `
</DocumentFragment>
`;

exports[`HttpMessage 4`] = `
<DocumentFragment>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
Loading...
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-edit"
/>
 Edit
</button>
 
<a
class="btn btn-default btn-xs"
href="#"
title="Upload a file to replace the content."
>
<i
class="fa fa-fw fa-upload"
/>
Replace
<input
class="hidden"
type="file"
/>
</a>
 
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
raw
<span
class="caret"
/>
</span>
</a>
</div>
</div>
</DocumentFragment>
`;

exports[`HttpMessage edit query string 1`] = `
<DocumentFragment>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
[Editing]
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-check text-success"
/>
 Done
</button>
 
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-times text-danger"
/>
 Cancel
</button>
</div>
<div
class="codeeditor"
>
<div>
foo=1
bar=2
</div>
</div>
</div>
</DocumentFragment>
`;

exports[`HttpMessage edit query string 2`] = `
<DocumentFragment>
<div
class="contentview"
>
<div
class="controls"
>
<h5>
Query
</h5>
<button
class="btn-xs btn btn-default"
>
<i
class="fa fa-edit"
/>
 Edit
</button>
 
<a
class="btn btn-default btn-xs"
href="#"
title="Upload a file to replace the content."
>
<i
class="fa fa-fw fa-upload"
/>
Replace
<input
class="hidden"
type="file"
/>
</a>
 
<a
class="btn btn-default btn-xs"
href="#"
>
<span>
<i
class="fa fa-fw fa-files-o"
/>
 
<b>
View:
</b>
auto
<span
class="caret"
/>
</span>
</a>
</div>
<pre>
<div>
<span
class="header"
>
foo
</span>
<span
class="text"
>
1
</span>
</div>
<div>
<span
class="header"
>
bar
</span>
<span
class="text"
>
2
</span>
</div>
</pre>
</div>
</DocumentFragment>
`;

exports[`ViewImage 1`] = `
<DocumentFragment>
<div
Expand Down

0 comments on commit f481d96

Please sign in to comment.