Skip to content

Commit e616ff5

Browse files
authored
Merge pull request #4 from JS00001/main
add support for formdata
2 parents 82f652e + 36119b5 commit e616ff5

File tree

8 files changed

+97
-38
lines changed

8 files changed

+97
-38
lines changed

bridge/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "HTTP Interceptor",
4-
"version": "0.1.4",
4+
"version": "0.1.6",
55
"identifier": "com.http-interceptor.js00001",
66
"build": {
77
"beforeDevCommand": "npm run dev",

shared/lib/index.ts

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@ export const RED = '🔴';
55
export const GREEN = '🟢';
66
export const YELLOW = '🟡';
77

8+
type ParsedRequestParams = {
9+
queryParams: Record<string, any>;
10+
postData: Record<string, any>;
11+
postDataType: 'json' | 'form-data' | null;
12+
};
13+
814
/**
915
* Take a CDP request, and convert both its query params and post data
1016
* into a combined object
1117
*/
1218
export const getRequestParams = (request: Protocol.Network.Request) => {
13-
const reqParams = {
19+
const reqParams: ParsedRequestParams = {
1420
queryParams: {},
1521
postData: {},
22+
postDataType: null,
1623
};
1724

1825
// Get URL params and store them
@@ -22,9 +29,22 @@ export const getRequestParams = (request: Protocol.Network.Request) => {
2229

2330
// Get the post data and store it
2431
if (request.hasPostData && request.postData) {
25-
const data = parseJSON(request.postData);
26-
if (!data) return reqParams;
27-
reqParams.postData = data;
32+
const jsonPayload = parseJSON(request.postData);
33+
34+
if (jsonPayload) {
35+
reqParams.postDataType = 'json';
36+
reqParams.postData = jsonPayload;
37+
return reqParams;
38+
}
39+
40+
// We need to handle multipart form data if the body is not already a JSON payload
41+
const boundary = getBoundary(request);
42+
43+
if (boundary) {
44+
const formData = parseFormData(request.postData, boundary);
45+
reqParams.postDataType = 'form-data';
46+
reqParams.postData = formData;
47+
}
2848
}
2949

3050
return reqParams;
@@ -108,3 +128,50 @@ export const formatError = (errorText?: string) => {
108128

109129
return `(failed) ${errorText}`;
110130
};
131+
132+
/**
133+
* Parse multipart-form data
134+
*/
135+
export const parseFormData = (data: string, boundary: string) => {
136+
const fields: Record<string, string> = {};
137+
const parts = data.split(`--${boundary}`).filter((p) => p.trim() && p.trim() !== '--');
138+
139+
for (const part of parts) {
140+
const [rawHeaders, ...rest] = part.split('\r\n\r\n');
141+
if (!rawHeaders || !rest.length) continue;
142+
143+
const nameMatch = rawHeaders.match(/name="([^"]+)"/);
144+
if (!nameMatch) continue;
145+
146+
const name = nameMatch[1];
147+
const value = rest.join('\r\n\r\n').replace(/\r\n$/, ''); // remove trailing newline
148+
fields[name] = value;
149+
}
150+
151+
return fields;
152+
};
153+
154+
/**
155+
* Turn an object back into form-data
156+
*/
157+
export const assembleFormData = (fields: Record<string, string>, boundary: string) => {
158+
let body = '';
159+
160+
for (const [name, value] of Object.entries(fields)) {
161+
body += `--${boundary}\r\n`;
162+
body += `Content-Disposition: form-data; name="${name}"\r\n\r\n`;
163+
body += `${value}\r\n`;
164+
}
165+
body += `--${boundary}--\r\n`;
166+
return body;
167+
};
168+
169+
/**
170+
* Check if a request is a multipart form data request, if so, return the
171+
* boundary
172+
*/
173+
export const getBoundary = (request: Protocol.Network.Request) => {
174+
const contentType = request.headers['Content-Type'] ?? request.headers['content-type'] ?? '';
175+
const boundary = contentType.split('boundary=')[1];
176+
return boundary;
177+
};

ui/components/Sidebar.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useMemo } from "react";
22
import classNames from "classnames";
3-
import { CellTowerIcon, CommandIcon, GearIcon } from "@phosphor-icons/react";
3+
import { CellTowerIcon, GearIcon } from "@phosphor-icons/react";
44

55
import useRouter from "@ui/store/router";
66

@@ -15,12 +15,7 @@ export default function Sidebar() {
1515
route: "/intercept" as const,
1616
isActive: router.pathname.startsWith("/intercept"),
1717
},
18-
{
19-
icon: CommandIcon,
20-
label: "Shortcuts",
21-
route: "/shortcuts" as const,
22-
isActive: router.pathname.startsWith("/shortcuts"),
23-
},
18+
2419
{
2520
icon: GearIcon,
2621
label: "Settings",

ui/components/event-viewer/PayloadView.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { useMemo } from "react";
33

44
import { NetworkEvent } from "@shared/types";
55
import type { DataType } from "@shared/types";
6-
import { getRequestParams } from "@shared/lib";
6+
import { assembleFormData, getBoundary, getRequestParams } from "@shared/lib";
77
import JsonViewer from "@ui/components/json-viewer";
88
import { useNetworkEventStore } from "@shared/stores/network-event";
99

@@ -23,8 +23,11 @@ export default function PayloadView({ event, editable = false }: PayloadViewProp
2323
const showOnlyQueryParams = !hasPostData && hasQueryParams;
2424

2525
const sections = useMemo(() => {
26+
const requestPayloadTitle =
27+
requestParams.postDataType === "json" ? "Request Payload" : "Form Data";
28+
2629
if (showOnlyPostData) {
27-
return [{ title: "Request Payload", data: requestParams.postData, editable }];
30+
return [{ title: requestPayloadTitle, data: requestParams.postData, editable }];
2831
}
2932

3033
if (showOnlyQueryParams) {
@@ -39,7 +42,7 @@ export default function PayloadView({ event, editable = false }: PayloadViewProp
3942
},
4043
{
4144
editable,
42-
title: "Request Payload",
45+
title: requestPayloadTitle,
4346
data: requestParams.postData,
4447
},
4548
];
@@ -52,13 +55,18 @@ export default function PayloadView({ event, editable = false }: PayloadViewProp
5255
if (isPostData) {
5356
lodash.set(requestParams.postData, path, value);
5457

58+
const boundary = getBoundary(event.request);
59+
60+
// If this request is a multipart form data request, we need to reassemble the form data, otherwise
61+
// just use the stringified JSON
62+
const postData = boundary
63+
? assembleFormData(requestParams.postData, boundary)
64+
: JSON.stringify(requestParams.postData);
65+
5566
updateRequest({
5667
tabId: event.tabId,
5768
requestId: event.requestId,
58-
request: {
59-
...request,
60-
postData: JSON.stringify(requestParams.postData),
61-
},
69+
request: { ...request, postData },
6270
});
6371
}
6472
};

ui/components/json-viewer/Group.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export default function Group({ nodeKey, expanded, level, value }: GroupProps) {
1010

1111
return (
1212
<div style={{ paddingLeft: level * 16 }} className="w-full hover:bg-primary-50 -ml-4">
13-
<p className="truncate text-gray-800 w-full">
14-
<span className="mr-2 text-[12px]">{arrow}</span>
13+
<p className="truncate text-gray-800 w-full text-[11px]">
14+
<span className="mr-2">{arrow}</span>
1515
<span className="text-fuchsia-800 shrink-0">{nodeKey}</span>
1616
{nodeKey && <span className="mr-2">:</span>}
1717
{value}

ui/components/json-viewer/Node.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,15 @@ export default function Node({ nodeKey, path, data, level, editable, onChange }:
9494
}[dataType];
9595

9696
const containerClasses = classNames("w-full flex items-center hover:bg-primary-50");
97-
const valueClasses = classNames("truncate resize-none w-auto caret-black", textColor);
97+
const valueClasses = classNames(
98+
"truncate resize-none w-auto caret-black text-[11px]",
99+
textColor
100+
);
98101

99102
return (
100103
<div style={{ paddingLeft: level * 16 }} className={containerClasses}>
101-
<p className="text-fuchsia-800 shrink-0">{nodeKey}</p>
102-
<p className="text-gray-800 mr-2">:</p>
104+
<p className="text-fuchsia-800 shrink-0 text-[11px]">{nodeKey}</p>
105+
<p className="text-gray-800 mr-2 text-[11px]">:</p>
103106

104107
{dataType === "string" && <StringQuotation />}
105108

ui/pages/shortcuts.tsx

Lines changed: 0 additions & 12 deletions
This file was deleted.

ui/routes.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import Settings from '@ui/pages/settings';
22
import Intercept from '@ui/pages/intercept';
3-
import Shortcuts from '@ui/pages/shortcuts';
43

54
export type Route = keyof typeof Routes;
65

76
const Routes = {
87
'/intercept': Intercept,
9-
'/shortcuts': Shortcuts,
108
'/settings': Settings,
119
} as const;
1210

0 commit comments

Comments
 (0)