Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flow support to relative paths #6562

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
([#6559](https://github.com/mitmproxy/mitmproxy/pull/6559), @mhils)
* `ignore_hosts` now waits for the entire HTTP headers if it suspects the connection to be HTTP.
([#6559](https://github.com/mitmproxy/mitmproxy/pull/6559), @mhils)

* Fix root-relative URLs for flow and other APIs so that mitmweb can run in subdirectories.
([#6562](https://github.com/mitmproxy/mitmproxy/pull/6562), @davet2001)

## 14 December 2023: mitmproxy 10.1.6

Expand Down
8 changes: 4 additions & 4 deletions mitmproxy/tools/web/static/app.js

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions test/mitmproxy/tools/web/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,18 @@ def test_index(self):
response.body
), "HTML content should not contain root-relative paths"

def test_app_js(self):
response: httpclient.HTTPResponse = self.fetch("/static/app.js")
assert response.code == 200
result = (
'"/flows' not in str(response.body)
and '"/commands' not in str(response.body)
and '"/filter' not in str(response.body)
and '"/clear' not in str(response.body)
and '"/options' not in str(response.body)
)
assert result, "app.js content should not contain root-relative paths"

def test_filter_help(self):
assert self.fetch("/filter-help").code == 200

Expand Down
18 changes: 9 additions & 9 deletions web/src/js/__tests__/ducks/ui/keyboardSpec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ describe("onKeyDown", () => {

it("should handle delete action", () => {
store.dispatch(createKeyEvent("d"));
expect(fetchApi).toBeCalledWith("/flows/1", { method: "DELETE" });
expect(fetchApi).toBeCalledWith("flows/1", { method: "DELETE" });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calls to fetchApi should still be prefixed with /, fetchApi has explicit logic to make them relative.

});

it("should handle create action", () => {
Expand All @@ -138,48 +138,48 @@ describe("onKeyDown", () => {

it("should handle duplicate action", () => {
store.dispatch(createKeyEvent("D"));
expect(fetchApi).toBeCalledWith("/flows/1/duplicate", {
expect(fetchApi).toBeCalledWith("flows/1/duplicate", {
method: "POST",
});
});

it("should handle resume action", () => {
// resume all
store.dispatch(createKeyEvent("A"));
expect(fetchApi).toBeCalledWith("/flows/resume", { method: "POST" });
expect(fetchApi).toBeCalledWith("flows/resume", { method: "POST" });
// resume
store.getState().flows.byId[
store.getState().flows.selected[0]
].intercepted = true;
store.dispatch(createKeyEvent("a"));
expect(fetchApi).toBeCalledWith("/flows/1/resume", { method: "POST" });
expect(fetchApi).toBeCalledWith("flows/1/resume", { method: "POST" });
});

it("should handle replay action", () => {
store.dispatch(createKeyEvent("r"));
expect(fetchApi).toBeCalledWith("/flows/1/replay", { method: "POST" });
expect(fetchApi).toBeCalledWith("flows/1/replay", { method: "POST" });
});

it("should handle revert action", () => {
store.getState().flows.byId[
store.getState().flows.selected[0]
].modified = true;
store.dispatch(createKeyEvent("v"));
expect(fetchApi).toBeCalledWith("/flows/1/revert", { method: "POST" });
expect(fetchApi).toBeCalledWith("flows/1/revert", { method: "POST" });
});

it("should handle kill action", () => {
// kill all
store.dispatch(createKeyEvent("X"));
expect(fetchApi).toBeCalledWith("/flows/kill", { method: "POST" });
expect(fetchApi).toBeCalledWith("flows/kill", { method: "POST" });
// kill
store.dispatch(createKeyEvent("x"));
expect(fetchApi).toBeCalledWith("/flows/1/kill", { method: "POST" });
expect(fetchApi).toBeCalledWith("flows/1/kill", { method: "POST" });
});

it("should handle clear action", () => {
store.dispatch(createKeyEvent("z"));
expect(fetchApi).toBeCalledWith("/clear", { method: "POST" });
expect(fetchApi).toBeCalledWith("clear", { method: "POST" });
});

it("should stop on some action with no flow is selected", () => {
Expand Down
2 changes: 1 addition & 1 deletion web/src/js/components/CommandBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export default function CommandBar() {
const [currentPos, setCurrentPos] = useState<number | undefined>(undefined);

useEffect(() => {
fetchApi("/commands", { method: "GET" })
fetchApi("commands", { method: "GET" })
.then((response) => response.json())
.then((data: AllCommands) => {
setAllCommands(data);
Expand Down
4 changes: 2 additions & 2 deletions web/src/js/components/Header/FileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ export default React.memo(function FileMenu() {
}}
/>
</li>
<MenuItem onClick={() => location.replace("/flows/dump")}>
<MenuItem onClick={() => location.replace("flows/dump")}>
<i className="fa fa-fw fa-floppy-o" />
&nbsp;Save
</MenuItem>
<MenuItem
onClick={() => location.replace("/flows/dump?filter=" + filter)}
onClick={() => location.replace("flows/dump?filter=" + filter)}
>
<i className="fa fa-fw fa-floppy-o" />
&nbsp;Save filtered
Expand Down
2 changes: 1 addition & 1 deletion web/src/js/components/Header/FilterDocs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default class FilterDocs extends Component<

componentDidMount() {
if (!FilterDocs.xhr) {
FilterDocs.xhr = fetchApi("/filter-help").then((response) =>
FilterDocs.xhr = fetchApi("filter-help").then((response) =>
response.json()
);
FilterDocs.xhr.catch(() => {
Expand Down
24 changes: 12 additions & 12 deletions web/src/js/ducks/flows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,63 +177,63 @@ export function selectRelative(flows, shift) {

export function resume(flow: Flow) {
return (dispatch) =>
fetchApi(`/flows/${flow.id}/resume`, { method: "POST" });
fetchApi(`flows/${flow.id}/resume`, { method: "POST" });
}

export function resumeAll() {
return (dispatch) => fetchApi("/flows/resume", { method: "POST" });
return (dispatch) => fetchApi("flows/resume", { method: "POST" });
}

export function kill(flow: Flow) {
return (dispatch) => fetchApi(`/flows/${flow.id}/kill`, { method: "POST" });
return (dispatch) => fetchApi(`flows/${flow.id}/kill`, { method: "POST" });
}

export function killAll() {
return (dispatch) => fetchApi("/flows/kill", { method: "POST" });
return (dispatch) => fetchApi("flows/kill", { method: "POST" });
}

export function remove(flow: Flow) {
return (dispatch) => fetchApi(`/flows/${flow.id}`, { method: "DELETE" });
return (dispatch) => fetchApi(`flows/${flow.id}`, { method: "DELETE" });
}

export function duplicate(flow: Flow) {
return (dispatch) =>
fetchApi(`/flows/${flow.id}/duplicate`, { method: "POST" });
fetchApi(`flows/${flow.id}/duplicate`, { method: "POST" });
}

export function replay(flow: Flow) {
return (dispatch) =>
fetchApi(`/flows/${flow.id}/replay`, { method: "POST" });
fetchApi(`flows/${flow.id}/replay`, { method: "POST" });
}

export function revert(flow: Flow) {
return (dispatch) =>
fetchApi(`/flows/${flow.id}/revert`, { method: "POST" });
fetchApi(`flows/${flow.id}/revert`, { method: "POST" });
}

export function update(flow: Flow, data) {
return (dispatch) => fetchApi.put(`/flows/${flow.id}`, data);
return (dispatch) => fetchApi.put(`flows/${flow.id}`, data);
}

export function uploadContent(flow: Flow, file, type) {
const body = new FormData();
file = new window.Blob([file], { type: "plain/text" });
body.append("file", file);
return (dispatch) =>
fetchApi(`/flows/${flow.id}/${type}/content.data`, {
fetchApi(`flows/${flow.id}/${type}/content.data`, {
method: "POST",
body,
});
}

export function clear() {
return (dispatch) => fetchApi("/clear", { method: "POST" });
return (dispatch) => fetchApi("clear", { method: "POST" });
}

export function upload(file) {
const body = new FormData();
body.append("file", file);
return (dispatch) => fetchApi("/flows/dump", { method: "POST", body });
return (dispatch) => fetchApi("flows/dump", { method: "POST", body });
}

export function select(id?: string) {
Expand Down
4 changes: 2 additions & 2 deletions web/src/js/ducks/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default function reducer(state = defaultState, action): OptionsState {

export async function pureSendUpdate(option: Option, value, dispatch) {
try {
const response = await fetchApi.put("/options", { [option]: value });
const response = await fetchApi.put("options", { [option]: value });
if (response.status === 200) {
dispatch(optionsEditorActions.updateSuccess(option));
} else {
Expand All @@ -55,7 +55,7 @@ export function update(name: Option, value: any): AppThunk {
}

export function save() {
return (dispatch) => fetchApi("/options/save", { method: "POST" });
return (dispatch) => fetchApi("options/save", { method: "POST" });
}

export function addInterceptFilter(example) {
Expand Down
4 changes: 2 additions & 2 deletions web/src/js/urlState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ export function updateUrlFromStore(store) {

let url;
if (state.flows.selected.length > 0) {
url = `/flows/${state.flows.selected[0]}/${state.ui.flow.tab}`;
url = `flows/${state.flows.selected[0]}/${state.ui.flow.tab}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This breaks all existing links to mitmweb instances, which I'd like to avoid. location.hash does not need relative URLs.

} else {
url = "/flows";
url = "flows";
}

if (queryStr) {
Expand Down
Loading