Skip to content

Commit 990cc2e

Browse files
committed
Merge branch 'socks'
2 parents 2aae2a2 + f148081 commit 990cc2e

21 files changed

+380
-65
lines changed

src/components/common/duration-pill.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { TimingEvents } from '../../types';
55
import { observableClock } from '../../util/observable';
66

77
import { Pill } from './pill';
8+
import { formatDuration } from '../../util/text';
89

910
function sigFig(num: number, figs: number): string {
1011
return num.toFixed(figs);
@@ -41,11 +42,5 @@ export const DurationPill = observer((p: DurationPillProps) => {
4142

4243
if (duration === undefined) return null;
4344

44-
return <Pill className={p.className}>{
45-
duration < 100 ? sigFig(duration, 1) + 'ms' : // 22.3ms
46-
duration < 1000 ? sigFig(duration, 0) + 'ms' : // 999ms
47-
duration < 5000 ? sigFig(duration / 1000, 2) + ' seconds' : // 3.04 seconds
48-
duration < 9900 ? sigFig(duration / 1000, 1) + ' seconds' : // 8.2 seconds
49-
sigFig(duration / 1000, 0) + ' seconds' // 30 seconds
50-
}</Pill>;
45+
return <Pill className={p.className}>{formatDuration(duration)}</Pill>;
5146
});

src/components/intercept/config/frida-config.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,8 @@ class FridaConfig extends React.Component<{
394394
this.props.activateInterceptor({
395395
action: 'intercept',
396396
hostId: host.id,
397-
targetId
397+
targetId,
398+
enableSocks: this.props.accountStore!.featureFlags.includes('raw-tunnels')
398399
})
399400
.catch((e) => alertActivationError(`intercept ${targetId}`, e))
400401
.then(() => {

src/components/view/http/http-details-pane.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ export class HttpDetailsPane extends React.Component<{
462462
// reset when we switch between exchanges:
463463
key={`${this.cardProps.webSocketMessages.key}-${this.props.exchange.id}`}
464464
streamId={this.props.exchange.id}
465-
streamType='WebSocket'
465+
cardHeading='WebSocket Messages'
466466

467467
editorNode={this.props.streamMessageEditor}
468468

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import * as _ from 'lodash';
2+
import * as React from 'react';
3+
import * as portals from 'react-reverse-portal';
4+
import { inject, observer } from 'mobx-react';
5+
6+
import { getReadableIP } from '../../model/network';
7+
import { RawTunnel } from '../../model/raw-tunnel';
8+
import { UiStore } from '../../model/ui/ui-store';
9+
10+
import { formatDuration } from '../../util/text';
11+
import { getReadableSize } from '../../util/buffer';
12+
13+
import { MediumCard } from '../common/card';
14+
15+
import { ContentLabelBlock, Content, CopyableMonoValue } from '../common/text-content';
16+
import { PaneScrollContainer } from './view-details-pane';
17+
import { StreamMessageListCard } from './stream-message-list-card';
18+
import { SelfSizedEditor } from '../editor/base-editor';
19+
20+
@inject('uiStore')
21+
@observer
22+
export class RawTunnelDetailsPane extends React.Component<{
23+
tunnel: RawTunnel,
24+
streamMessageEditor: portals.HtmlPortalNode<typeof SelfSizedEditor>,
25+
isPaidUser: boolean,
26+
uiStore?: UiStore
27+
}> {
28+
render() {
29+
const { tunnel } = this.props;
30+
31+
const sourceDetailParts = getReadableIP(tunnel.remoteIpAddress).split(' ');
32+
const sourceIp = sourceDetailParts[0];
33+
const sourceDetails = sourceDetailParts.slice(1).join(' ');
34+
35+
const packetCardProps = this.props.uiStore!.viewCardProps['rawTunnelPackets'];
36+
37+
const packetsListCard = <StreamMessageListCard
38+
isPaidUser={this.props.isPaidUser}
39+
{...packetCardProps}
40+
41+
streamId={tunnel.id}
42+
cardHeading='Raw Data'
43+
streamLabel={tunnel.upstreamHostname}
44+
editorNode={this.props.streamMessageEditor}
45+
filenamePrefix={`Raw Tunnel ${tunnel.upstreamHostname} ${tunnel.upstreamPort} ${tunnel.id}`}
46+
messages={tunnel.packets}
47+
/>;
48+
49+
if (packetCardProps.expanded) return packetsListCard;
50+
51+
return <PaneScrollContainer>
52+
<MediumCard>
53+
<header>
54+
<h1>Raw Tunnel</h1>
55+
</header>
56+
57+
<ContentLabelBlock>Details</ContentLabelBlock>
58+
<Content>
59+
<p>
60+
This connection was not intercepted by HTTP Toolkit, as it contains
61+
an unrecognized non-HTTP protocol, so was tunnelled directly to its destination.
62+
</p>
63+
</Content>
64+
<Content>
65+
<p>
66+
The connection was made from <CopyableMonoValue>{
67+
sourceIp
68+
}:{
69+
tunnel.remotePort
70+
}</CopyableMonoValue> { sourceDetails } to <CopyableMonoValue>{
71+
tunnel.upstreamHostname
72+
}:{
73+
tunnel.upstreamPort
74+
}</CopyableMonoValue>.
75+
</p>
76+
</Content>
77+
</MediumCard>
78+
{ packetsListCard }
79+
{
80+
!tunnel.isOpen() &&
81+
<MediumCard>
82+
<header>
83+
<h1>Connection Closed</h1>
84+
</header>
85+
<Content>
86+
This tunnel was closed {
87+
tunnel.timingEvents.disconnectTimestamp
88+
? <>after {formatDuration(
89+
tunnel.timingEvents.disconnectTimestamp - tunnel.timingEvents.connectTimestamp
90+
)}</> : <></>
91+
}.
92+
</Content>
93+
</MediumCard>
94+
}
95+
</PaneScrollContainer>;
96+
}
97+
}

src/components/view/rtc/rtc-data-channel-card.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const RTCDataChannelCard = observer(({
2929
// reset when we switch between traffic:
3030
key={dataChannel.id}
3131
streamId={dataChannel.id}
32-
streamType='DataChannel'
32+
cardHeading='DataChannel Messages'
3333
streamLabel={dataChannel.label}
3434

3535
editorNode={streamMessageEditor}

src/components/view/stream-message-list-card.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,12 @@ function getFilename(
3434
}.${extension}`;
3535
}
3636

37-
export type StreamType = 'WebSocket' | 'DataChannel';
38-
3937
@observer
4038
export class StreamMessageListCard extends React.Component<ExpandableCardProps & {
4139
isPaidUser: boolean,
4240
filenamePrefix: string,
4341
streamId: string,
44-
streamType: StreamType,
42+
cardHeading: string,
4543
streamLabel?: string,
4644
messages: ReadonlyArray<StreamMessage>,
4745
editorNode: portals.HtmlPortalNode<typeof SelfSizedEditor>
@@ -59,7 +57,7 @@ export class StreamMessageListCard extends React.Component<ExpandableCardProps &
5957
render() {
6058
const {
6159
streamId,
62-
streamType,
60+
cardHeading,
6361
streamLabel,
6462
messages,
6563
isPaidUser,
@@ -88,7 +86,7 @@ export class StreamMessageListCard extends React.Component<ExpandableCardProps &
8886
icon={['fas', 'download']}
8987
title={
9088
isPaidUser
91-
? "Save these message as a file"
89+
? "Save these messages as a file"
9290
: "With Pro: Save these messages as a file"
9391
}
9492
disabled={!isPaidUser}
@@ -115,7 +113,7 @@ export class StreamMessageListCard extends React.Component<ExpandableCardProps &
115113
<CollapsibleCardHeading
116114
onCollapseToggled={onCollapseToggled}
117115
>
118-
{ streamType } messages
116+
{ cardHeading }
119117
</CollapsibleCardHeading>
120118
</header>
121119
<StreamMessagesList expanded={!!expanded}>

src/components/view/stream-message-rows.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@ export const StreamMessageCollapsedRow = React.memo((p: {
6666
)
6767
}
6868
</CollapsedStreamContent>
69-
{
70-
p.message.isBinary &&
71-
<Pill color={warningColor}>Binary</Pill>
72-
}
73-
<Pill>
74-
{ getReadableSize(p.message.content.byteLength) }
75-
</Pill>
69+
<RowTags>
70+
{
71+
p.message.isBinary &&
72+
<Pill color={warningColor}>Binary</Pill>
73+
}
74+
<Pill>
75+
{ getReadableSize(p.message.content.byteLength) }
76+
</Pill>
77+
</RowTags>
7678
</CollapsedStreamRowContainer>);
7779

7880
const MessageArrow = styled(React.memo((p: {
@@ -141,6 +143,12 @@ const CollapsedStreamContent = styled(ContentMonoValue)`
141143
padding: 3px 0 4px;
142144
`;
143145

146+
const RowTags = styled.div`
147+
display: flex;
148+
gap: 8px;
149+
flex-direction: row;
150+
`;
151+
144152
interface MessageEditorRowProps {
145153
streamId: string,
146154
message: StreamMessage,

src/components/view/view-event-list.tsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import {
1515
RTCStream,
1616
FailedTlsConnection,
1717
RTCConnection,
18-
TlsTunnel
18+
TlsTunnel,
19+
RawTunnel
1920
} from '../../types';
2021
import { UiStore } from '../../model/ui/ui-store';
2122

@@ -381,6 +382,13 @@ const EventRow = observer((props: EventRowProps) => {
381382
style={style}
382383
event={event}
383384
/>;
385+
} else if (event.isRawTunnel()) {
386+
return <RawTunnelRow
387+
index={index}
388+
isSelected={isSelected}
389+
style={style}
390+
event={event}
391+
/>;
384392
} else {
385393
throw new UnreachableCheck(event);
386394
}
@@ -708,6 +716,35 @@ const BuiltInApiRow = observer((p: {
708716
</TrafficEventListRow>
709717
});
710718

719+
const RawTunnelRow = observer((p: {
720+
index: number,
721+
event: RawTunnel,
722+
isSelected: boolean,
723+
style: {}
724+
}) => {
725+
const { event } = p;
726+
727+
const connectionTarget = event.upstreamHostname
728+
? `${event.upstreamHostname}:${event.upstreamPort}`
729+
: 'unknown destination';
730+
731+
return <TlsListRow
732+
role="row"
733+
aria-label={`Non-HTTP connection to ${connectionTarget}`}
734+
aria-rowindex={p.index + 1}
735+
data-event-id={event.id}
736+
tabIndex={p.isSelected ? 0 : -1}
737+
738+
className={p.isSelected ? 'selected' : ''}
739+
style={p.style}
740+
>
741+
{
742+
event.isOpen() &&
743+
<ConnectedSpinnerIcon />
744+
} Non-HTTP connection to { connectionTarget }
745+
</TlsListRow>
746+
});
747+
711748
const TlsRow = observer((p: {
712749
index: number,
713750
tlsEvent: FailedTlsConnection | TlsTunnel,

src/components/view/view-page.tsx

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ import {
1313
import { observer, disposeOnUnmount, inject } from 'mobx-react';
1414
import * as portals from 'react-reverse-portal';
1515

16-
import { WithInjected, CollectedEvent, HttpExchangeView } from '../../types';
16+
import { WithInjected, CollectedEvent, HttpExchangeView, RawTunnel } from '../../types';
1717
import { NARROW_LAYOUT_BREAKPOINT, styled } from '../../styles';
1818
import { useHotkeys, isEditable, windowSize, AriaCtrlCmd, Ctrl } from '../../util/ui';
1919
import { debounceComputed } from '../../util/observable';
2020
import { UnreachableCheck, unreachableCheck } from '../../util/error';
2121

2222
import { SERVER_SEND_API_SUPPORTED, serverVersion, versionSatisfies } from '../../services/service-versions';
2323

24-
import { UiStore } from '../../model/ui/ui-store';
24+
import { ExpandableViewCardKey, UiStore } from '../../model/ui/ui-store';
2525
import { ProxyStore } from '../../model/proxy-store';
2626
import { EventsStore } from '../../model/events/events-store';
2727
import { RulesStore } from '../../model/rules/rules-store';
@@ -45,6 +45,7 @@ import { TlsTunnelDetailsPane } from './tls/tls-tunnel-details-pane';
4545
import { RTCDataChannelDetailsPane } from './rtc/rtc-data-channel-details-pane';
4646
import { RTCMediaDetailsPane } from './rtc/rtc-media-details-pane';
4747
import { RTCConnectionDetailsPane } from './rtc/rtc-connection-details-pane';
48+
import { RawTunnelDetailsPane } from './raw-tunnel-details-pane';
4849

4950
interface ViewPageProps {
5051
className?: string;
@@ -142,6 +143,19 @@ const EDITOR_KEYS = [
142143
] as const;
143144
type EditorKey = typeof EDITOR_KEYS[number];
144145

146+
const paneExpansionRequirements: { [key in ExpandableViewCardKey]: (event: CollectedEvent) => boolean } = {
147+
requestBody: (event: CollectedEvent) =>
148+
event.isHttp() &&
149+
(event.hasRequestBody() || !!event.downstream.requestBreakpoint),
150+
responseBody: (event: CollectedEvent) =>
151+
event.isHttp() &&
152+
(event.hasResponseBody() || !!event.downstream.responseBreakpoint),
153+
webSocketMessages: (event: CollectedEvent) =>
154+
event.isWebSocket() && event.wasAccepted,
155+
rawTunnelPackets: (event: CollectedEvent) =>
156+
event.isRawTunnel()
157+
};
158+
145159
@inject('eventsStore')
146160
@inject('proxyStore')
147161
@inject('uiStore')
@@ -279,33 +293,14 @@ class ViewPage extends React.Component<ViewPageProps> {
279293
}
280294

281295
const { expandedViewCard } = this.props.uiStore;
282-
if (!expandedViewCard) return;
283-
284-
const selectedHttpExchange = this.selectedExchange;
285-
286-
// If you have a pane expanded, and select an event with no data
287-
// for that pane, then disable the expansion:
288-
if (
289-
!selectedHttpExchange ||
290-
(
291-
expandedViewCard === 'requestBody' &&
292-
!selectedHttpExchange.hasRequestBody() &&
293-
!selectedHttpExchange.downstream.requestBreakpoint
294-
) ||
295-
(
296-
expandedViewCard === 'responseBody' &&
297-
!selectedHttpExchange.hasResponseBody() &&
298-
!selectedHttpExchange.downstream.responseBreakpoint
299-
) ||
300-
(
301-
expandedViewCard === 'webSocketMessages' &&
302-
!(selectedHttpExchange.isWebSocket() && selectedHttpExchange.wasAccepted)
303-
)
304-
) {
305-
runInAction(() => {
306-
this.props.uiStore.expandedViewCard = undefined;
307-
});
308-
return;
296+
if (expandedViewCard) {
297+
// If you have a pane expanded, and select an event with no data
298+
// for that pane, then disable the expansion:
299+
if (!paneExpansionRequirements[expandedViewCard](selectedEvent)) {
300+
runInAction(() => {
301+
this.props.uiStore.expandedViewCard = undefined;
302+
});
303+
}
309304
}
310305
}));
311306

@@ -404,6 +399,12 @@ class ViewPage extends React.Component<ViewPageProps> {
404399
answerEditor={this.editors.response}
405400
navigate={this.props.navigate}
406401
/>
402+
} else if (this.selectedEvent.isRawTunnel()) {
403+
rightPane = <RawTunnelDetailsPane
404+
tunnel={this.selectedEvent}
405+
streamMessageEditor={this.editors.streamMessage}
406+
isPaidUser={isPaidUser}
407+
/>
407408
} else {
408409
throw new UnreachableCheck(this.selectedEvent);
409410
}

0 commit comments

Comments
 (0)