Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
125 changes: 119 additions & 6 deletions apps/emdash-desktop/src/main/core/ssh/config/sshConfigParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ import {
parseSshConfigFileAt,
} from './sshConfigParser';

const itWindows = process.platform === 'win32' ? it : it.skip;
const itPosix = process.platform === 'win32' ? it.skip : it;
const pathSuffix = (suffix: string) => {
const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replaceAll('/', '[\\\\/]');
return new RegExp(`[\\\\/]${escapedSuffix}$`);
};

describe('parseSshConfigContent', () => {
it('lists concrete SSH config aliases with proxy and forwarding preview fields', () => {
const hosts = parseSshConfigContent(`
Expand Down Expand Up @@ -35,8 +42,8 @@ Host plain
hostname: 'dev.internal',
user: 'alice',
port: 2222,
identityFile: expect.stringContaining('/.ssh/dev_ed25519'),
identityAgent: expect.stringContaining('/.1password/agent.sock'),
identityFile: expect.stringMatching(pathSuffix('.ssh/dev_ed25519')),
identityAgent: expect.stringMatching(pathSuffix('.1password/agent.sock')),
proxyCommand: 'cloudflared access ssh --hostname %h',
forwardAgent: true,
forwardAgentValue: '$SSH_AUTH_SOCK',
Expand All @@ -46,8 +53,8 @@ Host plain
hostname: 'dev.internal',
user: 'alice',
port: 2222,
identityFile: expect.stringContaining('/.ssh/dev_ed25519'),
identityAgent: expect.stringContaining('/.1password/agent.sock'),
identityFile: expect.stringMatching(pathSuffix('.ssh/dev_ed25519')),
identityAgent: expect.stringMatching(pathSuffix('.1password/agent.sock')),
proxyCommand: 'cloudflared access ssh --hostname %h',
forwardAgent: true,
forwardAgentValue: '$SSH_AUTH_SOCK',
Expand Down Expand Up @@ -130,6 +137,112 @@ Host included-alias
]);
});

it('expands quoted and unquoted Include glob patterns', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-quoted-'));
await mkdir(join(dir, 'team config'));
await mkdir(join(dir, 'conf.d'));
await writeFile(
join(dir, 'config'),
`
Include "team config/*.conf" conf.d/*.conf
`,
'utf-8'
);
await writeFile(
join(dir, 'team config', 'primary.conf'),
`
Host quoted-include
HostName quoted.internal
`,
'utf-8'
);
await writeFile(
join(dir, 'conf.d', 'secondary.conf'),
`
Host unquoted-include
HostName unquoted.internal
`,
'utf-8'
);

expect(await parseSshConfigFileAt(join(dir, 'config'))).toEqual([
{
host: 'unquoted-include',
hostname: 'unquoted.internal',
},
{
host: 'quoted-include',
hostname: 'quoted.internal',
},
]);
});
Comment thread
janburzinski marked this conversation as resolved.
Outdated

itWindows('expands Include glob patterns with Windows path separators', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-windows-'));
await mkdir(join(dir, 'absolute'));
await mkdir(join(dir, 'relative'));
await writeFile(
join(dir, 'config'),
`
Include relative\\*.conf "${join(dir, 'absolute', '*.conf')}"
`,
'utf-8'
);
await writeFile(
join(dir, 'absolute', 'team.conf'),
`
Host absolute-windows-include
HostName absolute.internal
`,
'utf-8'
);
await writeFile(
join(dir, 'relative', 'team.conf'),
`
Host relative-windows-include
HostName relative.internal
`,
'utf-8'
);

expect(await parseSshConfigFileAt(join(dir, 'config'))).toEqual([
{
host: 'absolute-windows-include',
hostname: 'absolute.internal',
},
{
host: 'relative-windows-include',
hostname: 'relative.internal',
},
]);
});

itPosix('preserves backslash escaping in tilde Include patterns', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-posix-'));
await writeFile(
join(dir, 'config'),
`
Include ~\\*.conf
`,
'utf-8'
);
await writeFile(
join(dir, '~*.conf'),
`
Host posix-escaped-include
HostName posix.internal
`,
'utf-8'
);

expect(await parseSshConfigFileAt(join(dir, 'config'))).toEqual([
{
host: 'posix-escaped-include',
hostname: 'posix.internal',
},
]);
});
Comment thread
janburzinski marked this conversation as resolved.
Outdated

it('applies the same included snippet at each Include occurrence', async () => {
const dir = await mkdtemp(join(tmpdir(), 'emdash-ssh-config-repeat-'));
await writeFile(
Expand Down Expand Up @@ -159,13 +272,13 @@ Host second
host: 'first',
hostname: 'first.internal',
user: 'shared',
identityAgent: expect.stringContaining('/.ssh/shared-agent.sock'),
identityAgent: expect.stringMatching(pathSuffix('.ssh/shared-agent.sock')),
},
{
host: 'second',
hostname: 'second.internal',
user: 'shared',
identityAgent: expect.stringContaining('/.ssh/shared-agent.sock'),
identityAgent: expect.stringMatching(pathSuffix('.ssh/shared-agent.sock')),
},
]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ function stripQuotes(value: string): string {
}

/**
* Expands a leading `~` or `~/` to the user's home directory.
* Expands a leading `~`, `~/`, or Windows `~\` to the user's home directory.
*/
function expandTilde(filePath: string): string {
if (filePath === '~') {
return homedir();
}
if (filePath.startsWith('~/')) {
if (filePath.startsWith('~/') || (process.platform === 'win32' && filePath.startsWith('~\\'))) {
return join(homedir(), filePath.slice(2));
}
return filePath;
Expand Down Expand Up @@ -83,7 +83,10 @@ async function resolveIncludePaths(value: string, configDir: string): Promise<st
const absolutePattern = isAbsolute(expandedPattern)
? expandedPattern
: resolve(configDir, expandedPattern);
return await glob(absolutePattern, { nodir: true });
return await glob(absolutePattern, {
nodir: true,
windowsPathsNoEscape: process.platform === 'win32',
});
})
);

Expand Down
Loading