@@ -16,7 +16,7 @@ import {
1616 HostConfigurationDirective
1717} from 'ssh-config' ;
1818import { promisify } from 'util' ;
19- import { ISshConfigHostInfo , ISshHostInfo , isWindows , resolveHome } from "../common" ;
19+ import { ISshConfigHostInfo , isWindows , resolveHome } from "../common" ;
2020import { getSshChannel } from '../logger' ;
2121import * as glob from 'glob' ;
2222import * as vscode from 'vscode' ;
@@ -32,6 +32,11 @@ const userSshConfigurationFile: string = path.resolve(os.homedir(), '.ssh/config
3232const ProgramData : string = process . env . ALLUSERSPROFILE || process . env . PROGRAMDATA || 'C:\\ProgramData' ;
3333const systemSshConfigurationFile : string = isWindows ( ) ? `${ ProgramData } \\ssh\\ssh_config` : '/etc/ssh/ssh_config' ;
3434
35+ // Stores if the SSH config files are parsed successfully.
36+ // Only store root config files' failure status since included files are not modified by our extension.
37+ // path => successful
38+ export const parseFailures : Map < string , boolean > = new Map < string , boolean > ( ) ;
39+
3540export function getSshConfigurationFiles ( ) : string [ ] {
3641 return [ userSshConfigurationFile , systemSshConfigurationFile ] ;
3742}
@@ -64,42 +69,24 @@ function extractHostNames(parsedConfig: Configuration): { [host: string]: string
6469 return hostNames ;
6570}
6671
67- export async function getConfigurationForHost ( host : ISshHostInfo ) : Promise < ResolvedConfiguration | null > {
68- return getConfigurationForHostImpl ( host , getSshConfigurationFiles ( ) ) ;
69- }
70-
71- export async function getConfigurationForHostImpl (
72- host : ISshHostInfo ,
73- configPaths : string [ ]
74- ) : Promise < ResolvedConfiguration | null > {
75- for ( const configPath of configPaths ) {
76- const configuration : Configuration = await getSshConfiguration ( configPath ) ;
77- const config : ResolvedConfiguration = configuration . compute ( host . hostName ) ;
78-
79- if ( ! config || ! config . HostName ) {
80- // No real matching config was found
81- continue ;
82- }
83-
84- if ( config . IdentityFile ) {
85- config . IdentityFile = config . IdentityFile . map ( resolveHome ) ;
86- }
87-
88- return config ;
89- }
90-
91- return null ;
92- }
93-
9472/**
9573 * Gets parsed SSH configuration from file. Resolves Include directives as well unless specified otherwise.
9674 * @param configurationPath the location of the config file
9775 * @param resolveIncludes by default this is set to true
9876 * @returns
9977 */
10078export async function getSshConfiguration ( configurationPath : string , resolveIncludes : boolean = true ) : Promise < Configuration > {
79+ parseFailures . set ( configurationPath , false ) ;
10180 const src : string = await getSshConfigSource ( configurationPath ) ;
102- const config : Configuration = caseNormalizeConfigProps ( parse ( src ) ) ;
81+ let parsedSrc : Configuration | undefined ;
82+ try {
83+ parsedSrc = parse ( src ) ;
84+ } catch ( err ) {
85+ parseFailures . set ( configurationPath , true ) ;
86+ getSshChannel ( ) . appendLine ( localize ( "failed.to.parse.SSH.config" , "Failed to parse SSH configuration file {0}: {1}" , configurationPath , ( err as Error ) . message ) ) ;
87+ return parse ( '' ) ;
88+ }
89+ const config : Configuration = caseNormalizeConfigProps ( parsedSrc ) ;
10390 if ( resolveIncludes ) {
10491 await resolveConfigIncludes ( config , configurationPath ) ;
10592 }
@@ -128,13 +115,22 @@ async function resolveConfigIncludes(config: Configuration, configPath: string):
128115}
129116
130117async function getIncludedConfigFile ( config : Configuration , includePath : string ) : Promise < void > {
118+ let includedContents : string ;
131119 try {
132- const includedContents : string = ( await fs . readFile ( includePath ) ) . toString ( ) ;
133- const parsed : Configuration = parse ( includedContents ) ;
134- config . push ( ...parsed ) ;
120+ includedContents = ( await fs . readFile ( includePath ) ) . toString ( ) ;
135121 } catch ( e ) {
136122 getSshChannel ( ) . appendLine ( localize ( "failed.to.read.file" , "Failed to read file {0}." , includePath ) ) ;
123+ return ;
124+ }
125+
126+ let parsedIncludedContents : Configuration | undefined ;
127+ try {
128+ parsedIncludedContents = parse ( includedContents ) ;
129+ } catch ( err ) {
130+ getSshChannel ( ) . appendLine ( localize ( "failed.to.parse.SSH.config" , "Failed to parse SSH configuration file {0}: {1}" , includePath , ( err as Error ) . message ) ) ;
131+ return ;
137132 }
133+ config . push ( ...parsedIncludedContents ) ;
138134}
139135
140136export async function writeSshConfiguration ( configurationPath : string , configuration : Configuration ) : Promise < void > {
@@ -153,6 +149,7 @@ async function getSshConfigSource(configurationPath: string): Promise<string> {
153149 const buffer : Buffer = await fs . readFile ( configurationPath ) ;
154150 return buffer . toString ( 'utf8' ) ;
155151 } catch ( e ) {
152+ parseFailures . set ( configurationPath , true ) ;
156153 if ( ( e as NodeJS . ErrnoException ) . code === 'ENOENT' ) {
157154 return '' ;
158155 }
0 commit comments