ssh-config 4.1.6 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,478 +1,23 @@
1
- 'use strict'
2
-
3
- const glob = require('./src/glob')
4
-
5
- const RE_SPACE = /\s/
6
- const RE_LINE_BREAK = /\r|\n/
7
- const RE_SECTION_DIRECTIVE = /^(Host|Match)$/i
8
- const RE_MULTI_VALUE_DIRECTIVE = /^(GlobalKnownHostsFile|Host|IPQoS|SendEnv|UserKnownHostsFile|ProxyCommand)$/i
9
- const RE_QUOTE_DIRECTIVE = /^(?:CertificateFile|IdentityFile|IdentityAgent|User)$/i
10
- const RE_SINGLE_LINE_DIRECTIVE = /^(Include|IdentityFile)$/i
11
-
12
- const DIRECTIVE = 1
13
- const COMMENT = 2
14
-
15
- const MULTIPLE_VALUE_PROPS = [
16
- 'IdentityFile',
17
- 'LocalForward',
18
- 'RemoteForward',
19
- 'DynamicForward',
20
- 'CertificateFile'
21
- ]
22
-
23
- function compare(line, opts) {
24
- return opts.hasOwnProperty(line.param) && opts[line.param] === line.value
25
- }
26
-
27
- function getIndent(config) {
28
- for (const line of config) {
29
- if (RE_SECTION_DIRECTIVE.test(line.param)) {
30
- for (const subline of line.config) {
31
- if (subline.before) {
32
- return subline.before
33
- }
34
- }
35
- }
36
- }
37
-
38
- return ' '
39
- }
40
-
41
- class SSHConfig extends Array {
42
- /**
43
- * Query ssh config by host.
44
- *
45
- * @return {Object} The applied options of current Host
46
- */
47
- compute(host) {
48
- const obj = {}
49
- const setProperty = (name, value) => {
50
- if (MULTIPLE_VALUE_PROPS.includes(name)) {
51
- const list = obj[name] || (obj[name] = [])
52
- list.push(value)
53
- }
54
- else if (obj[name] == null) {
55
- obj[name] = value
56
- }
57
- }
58
-
59
- for (const line of this) {
60
- if (line.type !== DIRECTIVE) continue
61
- if (line.param === 'Host') {
62
- if (glob(line.value, host)) {
63
- setProperty(line.param, line.value)
64
-
65
- line.config
66
- .filter(line => line.type === DIRECTIVE)
67
- .forEach(line => setProperty(line.param, line.value))
68
- }
69
- }
70
- else if (line.param === 'Match') {
71
- // TODO
72
- }
73
- else {
74
- setProperty(line.param, line.value)
75
- }
76
- }
77
-
78
- return obj
79
- }
80
-
81
- /**
82
- * find section by Host / Match or function
83
- */
84
- find(opts = {}) {
85
- if (typeof opts === 'function') return super.find(opts)
86
-
87
- if (!(opts && ('Host' in opts || 'Match' in opts))) {
88
- throw new Error('Can only find by Host or Match')
89
- }
90
-
91
- return super.find(line => compare(line, opts))
92
- }
93
-
94
- /**
95
- * Remove section by Host / Match or function
96
- */
97
- remove(opts = {}) {
98
- let index;
99
-
100
- if (typeof opts === 'function') {
101
- index = super.findIndex(opts);
102
-
103
- } else if (!(opts && ('Host' in opts || 'Match' in opts))) {
104
- throw new Error('Can only remove by Host or Match');
105
-
106
- } else {
107
- index = super.findIndex(line => compare(line, opts));
108
-
109
- }
110
-
111
- if (index >= 0) return this.splice(index, 1)
112
- }
113
-
114
- /**
115
- * toString()
116
- * @returns {string}
117
- */
118
- toString() {
119
- return this.constructor.stringify(this)
120
- }
121
-
122
-
123
- /**
124
- * Append new section to existing ssh config.
125
- * @param {Object} opts
126
- */
127
- append(opts) {
128
- const indent = getIndent(this)
129
- const lastEntry = this.length > 0 ? this[this.length - 1] : null
130
- let config = lastEntry && lastEntry.config || this
131
- let configWas = this
132
-
133
- let lastLine = config.length > 0 ? config[config.length - 1] : lastEntry
134
- if (lastLine && !lastLine.after) lastLine.after = '\n'
135
-
136
- for (const param in opts) {
137
- const line = {
138
- type: DIRECTIVE,
139
- param,
140
- separator: ' ',
141
- value: opts[param],
142
- before: '',
143
- after: '\n'
144
- }
145
-
146
- if (RE_SECTION_DIRECTIVE.test(param)) {
147
- config = configWas
148
- // separate sections with an extra newline
149
- // https://github.com/cyjake/ssh-config/issues/23#issuecomment-564768248
150
- if (lastLine && lastLine.after === '\n') lastLine.after += '\n'
151
- config.push(line)
152
- config = line.config = new SSHConfig()
153
- } else {
154
- line.before = config === configWas ? '' : indent
155
- config.push(line)
156
- }
157
- lastLine = line
158
- }
159
-
160
- return configWas
161
- }
162
-
163
- /**
164
- * Prepend new section to existing ssh config.
165
- * @param {Object} opts
166
- */
167
- prepend(opts, beforeFirstSection = false) {
168
- const indent = getIndent(this)
169
- let config = this
170
- let i = 0
171
-
172
- // insert above known sections
173
- if (beforeFirstSection) {
174
- while (i < this.length && !RE_SECTION_DIRECTIVE.test(this[i].param)) {
175
- i += 1
176
- }
177
-
178
- if (i >= this.length) { // No sections in original config
179
- return this.append(opts)
180
- }
181
- }
182
-
183
- // Prepend new section above the first section
184
- let sectionLineFound = false
185
- let processedLines = 0
186
-
187
- for (const param in opts) {
188
- processedLines += 1
189
- const line = {
190
- type: DIRECTIVE,
191
- param,
192
- separator: ' ',
193
- value: opts[param],
194
- before: '',
195
- after: '\n'
196
- }
197
-
198
- if (RE_SECTION_DIRECTIVE.test(param)) {
199
- config.splice(i, 0, line)
200
- config = line.config = new SSHConfig()
201
- sectionLineFound = true
202
- continue
203
- }
204
-
205
- // separate from previous sections with an extra newline
206
- if (processedLines === Object.keys(opts).length) {
207
- line.after += '\n'
208
- }
209
-
210
- if (!sectionLineFound) {
211
- config.splice(i, 0, line)
212
- i += 1
213
-
214
- // Add an extra newline if a single line directive like Include
215
- if (RE_SINGLE_LINE_DIRECTIVE.test(param)) {
216
- line.after += '\n'
217
- }
218
- continue
219
- }
220
-
221
- line.before = indent
222
- config.push(line)
223
- }
224
-
225
- return config
226
- }
227
-
228
- /**
229
- * Stringify structured object into ssh config text
230
- * @param {SSHConfig} config
231
- * @returns {string}
232
- */
233
- static stringify(config) {
234
- let str = ''
235
-
236
- function formatValue(value, quoted) {
237
- if (Array.isArray(value)) {
238
- return value.map(chunk => formatValue(chunk, RE_SPACE.test(chunk))).join(' ')
239
- }
240
- return quoted ? `"${value}"` : value
241
- }
242
-
243
- function formatDirective(line) {
244
- const quoted = line.quoted
245
- || (RE_QUOTE_DIRECTIVE.test(line.param) && RE_SPACE.test(line.value))
246
- const value = formatValue(line.value, quoted)
247
- return `${line.param}${line.separator}${value}`
248
- }
249
-
250
- const format = line => {
251
- str += line.before
252
-
253
- if (line.type === COMMENT) {
254
- str += line.content
255
- }
256
- else if (line.type === DIRECTIVE && MULTIPLE_VALUE_PROPS.includes(line.param)) {
257
- [].concat(line.value).forEach(function (value, i, values) {
258
- str += formatDirective({ ...line, value })
259
- if (i < values.length - 1) str += `\n${line.before}`
260
- })
261
- }
262
- else if (line.type === DIRECTIVE) {
263
- str += formatDirective(line)
264
- }
265
-
266
- str += line.after
267
-
268
- if (line.config) {
269
- line.config.forEach(format)
270
- }
271
- }
272
-
273
- config.forEach(format)
274
-
275
- return str
276
- }
277
-
278
- static get DIRECTIVE() {
279
- return DIRECTIVE
280
- }
281
-
282
- static get COMMENT() {
283
- return COMMENT
284
- }
285
-
286
- /**
287
- * Parse ssh config text into structured object.
288
- */
289
- static parse(str) {
290
- let i = 0
291
- let chr = next()
292
- let config = new SSHConfig()
293
- let configWas = config
294
-
295
- function next() {
296
- return str[i++]
297
- }
298
-
299
- function space() {
300
- let spaces = ''
301
-
302
- while (RE_SPACE.test(chr)) {
303
- spaces += chr
304
- chr = next()
305
- }
306
-
307
- return spaces
308
- }
309
-
310
- function linebreak() {
311
- let breaks = ''
312
-
313
- while (RE_LINE_BREAK.test(chr)) {
314
- breaks += chr
315
- chr = next()
316
- }
317
-
318
- return breaks
319
- }
320
-
321
- function parameter() {
322
- let param = ''
323
-
324
- while (chr && /[^ \t=]/.test(chr)) {
325
- param += chr
326
- chr = next()
327
- }
328
-
329
- return param
330
- }
331
-
332
- function separator() {
333
- let sep = space()
334
-
335
- if (chr === '=') {
336
- sep += chr
337
- chr = next()
338
- }
339
-
340
- return sep + space()
341
- }
342
-
343
- function value() {
344
- let val = ''
345
- let quoted = false
346
- let escaped = false
347
-
348
- while (chr && !RE_LINE_BREAK.test(chr)) {
349
- // backslash escapes only double quotes
350
- if (escaped) {
351
- val += chr === '"' ? chr : `\\${chr}`
352
- escaped = false
353
- }
354
- // ProxyCommand ssh -W "%h:%p" firewall.example.org
355
- else if (chr === '"' && (!val || quoted)) {
356
- quoted = !quoted
357
- }
358
- else if (chr === '\\') {
359
- escaped = true
360
- }
361
- else {
362
- val += chr
363
- }
364
- chr = next()
365
- }
366
-
367
- if (quoted || escaped) {
368
- throw new Error(`Unexpected line break at ${val}`)
369
- }
370
-
371
- return val.trim()
372
- }
373
-
374
- function comment() {
375
- const type = COMMENT
376
- let content = ''
377
-
378
- while (chr && !RE_LINE_BREAK.test(chr)) {
379
- content += chr
380
- chr = next()
381
- }
382
-
383
- return { type, content }
384
- }
385
-
386
- // Host *.co.uk
387
- // Host * !local.dev
388
- // Host "foo bar"
389
- function values() {
390
- const results = []
391
- let val = ''
392
- let quoted = false
393
- let escaped = false
394
-
395
- while (chr && !RE_LINE_BREAK.test(chr)) {
396
- if (escaped) {
397
- val += chr === '"' ? chr : `\\${chr}`
398
- escaped = false
399
- }
400
- else if (chr === '"') {
401
- quoted = !quoted
402
- }
403
- else if (chr === '\\') {
404
- escaped = true
405
- }
406
- else if (quoted) {
407
- val += chr
408
- }
409
- else if (/[ \t]/.test(chr)) {
410
- if (val) {
411
- results.push(val)
412
- val = ''
413
- }
414
- // otherwise ignore the space
415
- }
416
- else {
417
- val += chr
418
- }
419
-
420
- chr = next()
421
- }
422
-
423
- if (quoted || escaped) {
424
- throw new Error(`Unexpected line break at ${results.concat(val).join(' ')}`)
425
- }
426
- if (val) results.push(val)
427
- return results.length > 1 ? results : results[0]
428
- }
429
-
430
- function directive() {
431
- const type = DIRECTIVE
432
- const param = parameter()
433
- // Host "foo bar" baz
434
- const multiple = RE_MULTI_VALUE_DIRECTIVE.test(param)
435
- const result = {
436
- type,
437
- param,
438
- separator: separator(),
439
- quoted: !multiple && chr === '"',
440
- value: multiple ? values() : value()
441
- }
442
- if (!result.quoted) delete result.quoted
443
- return result
444
- }
445
-
446
- function line() {
447
- const before = space()
448
- const node = chr === '#' ? comment() : directive()
449
- const after = linebreak()
450
-
451
- node.before = before
452
- node.after = after
453
-
454
- return node
455
- }
456
-
457
- while (chr) {
458
- let node = line()
459
-
460
- if (node.type === DIRECTIVE && RE_SECTION_DIRECTIVE.test(node.param)) {
461
- config = configWas
462
- config.push(node)
463
- config = node.config = new SSHConfig()
464
- }
465
- else if (node.type === DIRECTIVE && !node.param) {
466
- // blank lines at file end
467
- config[config.length - 1].after += node.before
468
- }
469
- else {
470
- config.push(node)
471
- }
472
- }
473
-
474
- return configWas
475
- }
476
- }
477
-
478
- module.exports = SSHConfig
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ var __importDefault = (this && this.__importDefault) || function (mod) {
17
+ return (mod && mod.__esModule) ? mod : { "default": mod };
18
+ };
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ const ssh_config_1 = __importDefault(require("./src/ssh-config"));
21
+ __exportStar(require("./src/ssh-config"), exports);
22
+ exports.default = ssh_config_1.default;
23
+ //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ssh-config",
3
3
  "description": "SSH config parser and stringifier",
4
- "version": "4.1.6",
4
+ "version": "4.2.0",
5
5
  "author": "Chen Yangjian (https://www.cyj.me)",
6
6
  "repository": {
7
7
  "type": "git",
@@ -9,20 +9,23 @@
9
9
  },
10
10
  "files": [
11
11
  "index.js",
12
- "src",
13
- "types"
12
+ "src"
14
13
  ],
15
14
  "devDependencies": {
16
15
  "@types/mocha": "^9.1.0",
17
- "@types/node": "^17.0.23",
18
- "eslint": "^7.17.0",
16
+ "@types/node": "^17.0.45",
17
+ "@typescript-eslint/eslint-plugin": "^5.48.0",
18
+ "@typescript-eslint/parser": "^5.48.0",
19
+ "eslint": "^8.31.0",
19
20
  "heredoc": "^1.3.1",
20
21
  "mocha": "^8.2.1",
21
22
  "nyc": "^15.1.0",
22
23
  "typescript": "^4.6.3"
23
24
  },
24
25
  "scripts": {
25
- "lint": "eslint .",
26
+ "lint": "eslint --ext ts .",
27
+ "lint:fix": "eslint --ext ts --fix .",
28
+ "prepack": "tsc",
26
29
  "pretest": "tsc",
27
30
  "test": "NODE_OPTIONS=--enable-source-maps mocha --exit --recursive",
28
31
  "test:coverage": "nyc mocha --exit --recursive && nyc report --reporter=lcov"
@@ -30,6 +33,5 @@
30
33
  "engine": {
31
34
  "node": ">= 10.0.0"
32
35
  },
33
- "types": "types/index.d.ts",
34
36
  "license": "MIT"
35
37
  }
package/src/glob.js CHANGED
@@ -1,22 +1,18 @@
1
- 'use strict'
2
-
3
- function escapeChars(str, chars) {
4
- for (let char of chars) {
5
- str = str.replace(new RegExp('\\' + char, 'g'), '\\' + char)
6
- }
7
-
8
- return str
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function escapeChars(text, chars) {
4
+ for (let char of chars) {
5
+ text = text.replace(new RegExp('\\' + char, 'g'), '\\' + char);
6
+ }
7
+ return text;
9
8
  }
10
-
11
- function match(pattern, str) {
12
- pattern = escapeChars(pattern, '\\()[]{}.+^$|')
13
- pattern = pattern
14
- .replace(/\*/g, '.*')
15
- .replace(/\?/g, '.?')
16
-
17
- return new RegExp('^(?:' + pattern + ')$').test(str)
9
+ function match(pattern, text) {
10
+ pattern = escapeChars(pattern, '\\()[]{}.+^$|');
11
+ pattern = pattern
12
+ .replace(/\*/g, '.*')
13
+ .replace(/\?/g, '.?');
14
+ return new RegExp('^(?:' + pattern + ')$').test(text);
18
15
  }
19
-
20
16
  /**
21
17
  * A helper function to match input against [pattern-list](https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5#PATTERNS).
22
18
  * According to `man ssh_config`, negated patterns shall be matched first.
@@ -24,23 +20,21 @@ function match(pattern, str) {
24
20
  * @param {string|string[]} patternList
25
21
  * @param {string} str
26
22
  */
27
- function glob(patternList, str) {
28
- const patterns = Array.isArray(patternList) ? patternList : patternList.split(/,/)
29
-
30
- // > If a negated entry is matched, then the Host entry is ignored, regardless of whether any other patterns on the line match.
31
- let result = false
32
- for (const pattern of patterns) {
33
- const negate = pattern[0] == '!'
34
-
35
- if (negate && match(pattern.slice(1), str)) {
36
- return false
37
- } else if (match(pattern, str)) {
38
- // wait until all of the pattern match results because there might be a negated pattern
39
- result = true
23
+ function glob(patternList, text) {
24
+ const patterns = Array.isArray(patternList) ? patternList : patternList.split(/,/);
25
+ // > If a negated entry is matched, then the Host entry is ignored, regardless of whether any other patterns on the line match.
26
+ let result = false;
27
+ for (const pattern of patterns) {
28
+ const negate = pattern[0] == '!';
29
+ if (negate && match(pattern.slice(1), text)) {
30
+ return false;
31
+ }
32
+ else if (match(pattern, text)) {
33
+ // wait until all of the pattern match results because there might be a negated pattern
34
+ result = true;
35
+ }
40
36
  }
41
- }
42
-
43
- return result
37
+ return result;
44
38
  }
45
-
46
- module.exports = glob
39
+ exports.default = glob;
40
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["glob.ts"],"names":[],"mappings":";;AACA,SAAS,WAAW,CAAC,IAAY,EAAE,KAAa;IAC9C,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE;QACtB,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,GAAG,IAAI,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,CAAA;KAC/D;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,KAAK,CAAC,OAAe,EAAE,IAAY;IAC1C,OAAO,GAAG,WAAW,CAAC,OAAO,EAAE,eAAe,CAAC,CAAA;IAC/C,OAAO,GAAG,OAAO;SACd,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC;SACpB,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;IAEvB,OAAO,IAAI,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACvD,CAAC;AAED;;;;;;GAMG;AACH,SAAS,IAAI,CAAC,WAA8B,EAAE,IAAY;IACxD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAElF,+HAA+H;IAC/H,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;QAC9B,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,GAAG,CAAA;QAEhC,IAAI,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,EAAE;YAC3C,OAAO,KAAK,CAAA;SACb;aAAM,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE;YAC/B,uFAAuF;YACvF,MAAM,GAAG,IAAI,CAAA;SACd;KACF;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,kBAAe,IAAI,CAAA"}
package/src/glob.ts ADDED
@@ -0,0 +1,45 @@
1
+
2
+ function escapeChars(text: string, chars: string) {
3
+ for (let char of chars) {
4
+ text = text.replace(new RegExp('\\' + char, 'g'), '\\' + char)
5
+ }
6
+
7
+ return text
8
+ }
9
+
10
+ function match(pattern: string, text: string) {
11
+ pattern = escapeChars(pattern, '\\()[]{}.+^$|')
12
+ pattern = pattern
13
+ .replace(/\*/g, '.*')
14
+ .replace(/\?/g, '.?')
15
+
16
+ return new RegExp('^(?:' + pattern + ')$').test(text)
17
+ }
18
+
19
+ /**
20
+ * A helper function to match input against [pattern-list](https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5#PATTERNS).
21
+ * According to `man ssh_config`, negated patterns shall be matched first.
22
+ *
23
+ * @param {string|string[]} patternList
24
+ * @param {string} str
25
+ */
26
+ function glob(patternList: string | string[], text: string) {
27
+ const patterns = Array.isArray(patternList) ? patternList : patternList.split(/,/)
28
+
29
+ // > If a negated entry is matched, then the Host entry is ignored, regardless of whether any other patterns on the line match.
30
+ let result = false
31
+ for (const pattern of patterns) {
32
+ const negate = pattern[0] == '!'
33
+
34
+ if (negate && match(pattern.slice(1), text)) {
35
+ return false
36
+ } else if (match(pattern, text)) {
37
+ // wait until all of the pattern match results because there might be a negated pattern
38
+ result = true
39
+ }
40
+ }
41
+
42
+ return result
43
+ }
44
+
45
+ export default glob