Skip to content

Commit 0de1534

Browse files
authored
Fix infinite ssh config file include processing. (#13971)
* Fix infinite ssh config file processing.
1 parent b75cbb3 commit 0de1534

File tree

1 file changed

+53
-16
lines changed

1 file changed

+53
-16
lines changed

Extension/src/SSH/sshHosts.ts

Lines changed: 53 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -99,28 +99,64 @@ export async function getSshConfiguration(configurationPath: string, resolveIncl
9999
return config;
100100
}
101101

102-
async function resolveConfigIncludes(config: Configuration, configPath: string): Promise<void> {
103-
for (const entry of config) {
104-
if (isDirective(entry) && entry.param === 'Include') {
105-
let includePath: string = resolveHome(entry.value);
106-
if (isWindows && !!includePath.match(/^\/[a-z]:/i)) {
107-
includePath = includePath.substr(1);
108-
}
109-
110-
if (!path.isAbsolute(includePath)) {
111-
includePath = path.resolve(path.dirname(configPath), includePath);
112-
}
113-
114-
const pathsToGetFilesFrom: string[] = await globAsync(includePath);
102+
function getProcessedPathKey(filePath: string): string {
103+
const absolutePath: string = path.resolve(filePath);
104+
const normalizedPath: string = path.normalize(absolutePath);
105+
return isWindows ? normalizedPath.toLowerCase() : normalizedPath;
106+
}
115107

116-
for (const filePath of pathsToGetFilesFrom) {
117-
await getIncludedConfigFile(config, filePath);
108+
async function resolveConfigIncludes(
109+
config: Configuration,
110+
configPath: string,
111+
processedIncludePaths?: Set<string>,
112+
processedIncludeEntries?: WeakSet<ConfigurationDirective>
113+
): Promise<void> {
114+
processedIncludePaths = processedIncludePaths ?? new Set<string>();
115+
processedIncludeEntries = processedIncludeEntries ?? new WeakSet<ConfigurationDirective>();
116+
const configKey: string = getProcessedPathKey(configPath);
117+
if (processedIncludePaths.has(configKey)) {
118+
return;
119+
}
120+
processedIncludePaths.add(configKey);
121+
try {
122+
for (const entry of config) {
123+
if (isDirective(entry) && entry.param === 'Include') {
124+
// Prevent duplicate expansion of the same Include directive within a single resolution pass.
125+
if (processedIncludeEntries.has(entry)) {
126+
continue;
127+
}
128+
processedIncludeEntries.add(entry);
129+
let includePath: string = resolveHome(entry.value);
130+
if (isWindows && !!includePath.match(/^\/[a-z]:/i)) {
131+
includePath = includePath.slice(1);
132+
}
133+
134+
if (!path.isAbsolute(includePath)) {
135+
includePath = path.resolve(path.dirname(configPath), includePath);
136+
}
137+
138+
const pathsToGetFilesFrom: string[] = await globAsync(includePath);
139+
140+
for (const filePath of pathsToGetFilesFrom) {
141+
const includeKey: string = getProcessedPathKey(filePath);
142+
if (processedIncludePaths.has(includeKey)) {
143+
continue;
144+
}
145+
await getIncludedConfigFile(config, filePath, processedIncludePaths, processedIncludeEntries);
146+
}
118147
}
119148
}
149+
} finally {
150+
processedIncludePaths.delete(configKey);
120151
}
121152
}
122153

123-
async function getIncludedConfigFile(config: Configuration, includePath: string): Promise<void> {
154+
async function getIncludedConfigFile(
155+
config: Configuration,
156+
includePath: string,
157+
processedIncludePaths: Set<string>,
158+
processedIncludeEntries: WeakSet<ConfigurationDirective>
159+
): Promise<void> {
124160
let includedContents: string;
125161
try {
126162
includedContents = (await fs.readFile(includePath)).toString();
@@ -136,6 +172,7 @@ async function getIncludedConfigFile(config: Configuration, includePath: string)
136172
getSshChannel().appendLine(localize("failed.to.parse.SSH.config", "Failed to parse SSH configuration file {0}: {1}", includePath, (err as Error).message));
137173
return;
138174
}
175+
await resolveConfigIncludes(parsedIncludedContents, includePath, processedIncludePaths, processedIncludeEntries);
139176
config.push(...parsedIncludedContents);
140177
}
141178

0 commit comments

Comments
 (0)