Skip to content

Commit ac94d7a

Browse files
committed
Add support for intercepting existing PowerShell terminals
1 parent abaebc1 commit ac94d7a

File tree

2 files changed

+53
-9
lines changed

2 files changed

+53
-9
lines changed

src/interceptors/terminal/existing-terminal-interceptor.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,41 @@ import { Mockttp, getLocal } from 'mockttp';
44
import { Interceptor } from '..';
55
import { HtkConfig } from '../../config';
66
import { getTerminalEnvVars } from './terminal-env-overrides';
7-
import { getBashShellScript, getFishShellScript } from './terminal-scripts';
7+
import { getBashShellScript, getFishShellScript, getPowerShellScript } from './terminal-scripts';
88

99
interface ServerState {
1010
server: Mockttp;
1111
isActive: boolean;
1212
}
1313

14-
function getShellCommands(port: number) {
15-
return {
16-
'Bash': { command: `eval "$(curl -sS localhost:${port}/setup)"`, description: "Bash-compatible" },
17-
'Fish': { command: `curl -sS localhost:${port}/fish-setup | source`, description: "Fish" }
14+
type ShellDefinition = { command: string, description: string };
15+
16+
const getBashDefinition = (port: number) => ({
17+
description: "Bash-compatible",
18+
command: `eval "$(curl -sS localhost:${port}/setup)"`
19+
});
20+
21+
const getFishDefinition = (port: number) => ({
22+
description: "Fish",
23+
command: `curl -sS localhost:${port}/fish-setup | source`
24+
});
25+
26+
const getPowershellDefinition = (port: number) => ({
27+
description: "Powershell",
28+
command: `Invoke-Expression (Invoke-WebRequest http://localhost:${port}/ps-setup).Content`
29+
});
30+
31+
function getShellCommands(port: number): { [shellName: string]: ShellDefinition } {
32+
if (process.platform === 'win32') {
33+
return {
34+
'Powershell': getPowershellDefinition(port)
35+
}
36+
} else {
37+
return {
38+
'Bash': getBashDefinition(port),
39+
'Fish': getFishDefinition(port),
40+
'Powershell': getPowershellDefinition(port)
41+
};
1842
}
1943
}
2044

@@ -30,16 +54,14 @@ export class ExistingTerminalInterceptor implements Interceptor {
3054
constructor(private config: HtkConfig) { }
3155

3256
isActivable(): Promise<boolean> {
33-
// Not supported on Windows, for now. Doesn't work in cmd or powershell of course (needs bash),
34-
// and doesn't work in git bash/WSL due to path transforms. Fixable, I think, but not easily.
35-
return Promise.resolve(process.platform !== 'win32');
57+
return Promise.resolve(true);
3658
}
3759

3860
isActive(proxyPort: number): boolean {
3961
return this.servers[proxyPort]?.isActive ?? false;
4062
}
4163

42-
async activate(proxyPort: number): Promise<{ port: number, commands: { [shellName: string]: { command: string, description: string } } }> {
64+
async activate(proxyPort: number): Promise<{ port: number, commands: { [shellName: string]: ShellDefinition } }> {
4365
if (this.servers[proxyPort]) {
4466
// Reset isActive, so we wait again for a new request
4567
this.servers[proxyPort].isActive = false;
@@ -57,6 +79,7 @@ export class ExistingTerminalInterceptor implements Interceptor {
5779

5880
const serverState = { server, isActive: false };
5981

82+
// Endpoints for each of the various setup scripts:
6083
await server.forGet('/setup').thenReply(200,
6184
getBashShellScript(server.urlFor('/success'), envVars),
6285
{ "content-type": "text/x-shellscript" }
@@ -65,7 +88,12 @@ export class ExistingTerminalInterceptor implements Interceptor {
6588
getFishShellScript(server.urlFor('/success'), envVars),
6689
{ "content-type": "application/x-fish" }
6790
);
91+
await server.forGet('/ps-setup').thenReply(200,
92+
getPowerShellScript(server.urlFor('/success'), envVars),
93+
{ "content-type": "text/plain" }
94+
);
6895

96+
// A success endpoint, so we can mark this as active (which provides some helpful UX on the frontend)
6997
await server.forPost('/success').thenCallback(() => {
7098
serverState.isActive = true;
7199
return { status: 200 };

src/interceptors/terminal/terminal-scripts.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,22 @@ export const getFishShellScript = (callbackUrl: string, env: { [name: string]: s
106106
echo 'HTTP Toolkit interception enabled'
107107
`;
108108

109+
export const getPowerShellScript = (callbackUrl: string, env: { [name: string]: string }) => `${
110+
_.map(env, (value, key) => ` $Env:${key} = "${value.replace(/"/g, '`"')}"`).join('\n')
111+
}
112+
113+
# We add a few special hooks just for Invoke-WebRequest.
114+
# First, we override the default proxy to use the env var value:
115+
$PSDefaultParameterValues["invoke-webrequest:proxy"] = $Env:HTTP_PROXY
116+
# Then we disable cert checks completely - all traffic will go to us, we'll handle HTTPS upstream
117+
$PSDefaultParameterValues["invoke-webrequest:SkipCertificateCheck"] = $True
118+
119+
# Let the HTTP Toolkit app know this ran succesfully
120+
Start-Job -ScriptBlock { Invoke-WebRequest "${callbackUrl}" -NoProxy -Method 'POST' } | out-null
121+
122+
Write-Host 'HTTP Toolkit interception enabled'
123+
`;
124+
109125
// Find the relevant user shell config file, add the above line to it, so that
110126
// shells launched with HTTP_TOOLKIT_ACTIVE set use the interception PATH.
111127
export const editShellStartupScripts = async () => {

0 commit comments

Comments
 (0)