Large diffs are not rendered by default.

@@ -0,0 +1,332 @@
/**
* @fileoverview A rule to choose between single and double quote marks
* @author Matt DuVall <http://www.mattduvall.com/>, Brandon Payton
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const astUtils = require("./utils/ast-utils");

//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------

const QUOTE_SETTINGS = {
double: {
quote: "\"",
alternateQuote: "'",
description: "doublequote"
},
single: {
quote: "'",
alternateQuote: "\"",
description: "singlequote"
},
backtick: {
quote: "`",
alternateQuote: "\"",
description: "backtick"
}
};

// An unescaped newline is a newline preceded by an even number of backslashes.
const UNESCAPED_LINEBREAK_PATTERN = new RegExp(String.raw`(^|[^\\])(\\\\)*[${Array.from(astUtils.LINEBREAKS).join("")}]`, "u");

/**
* Switches quoting of javascript string between ' " and `
* escaping and unescaping as necessary.
* Only escaping of the minimal set of characters is changed.
* Note: escaping of newlines when switching from backtick to other quotes is not handled.
* @param {string} str A string to convert.
* @returns {string} The string with changed quotes.
* @private
*/
QUOTE_SETTINGS.double.convert =
QUOTE_SETTINGS.single.convert =
QUOTE_SETTINGS.backtick.convert = function(str) {
const newQuote = this.quote;
const oldQuote = str[0];

if (newQuote === oldQuote) {
return str;
}
return newQuote + str.slice(1, -1).replace(/\\(\$\{|\r\n?|\n|.)|["'`]|\$\{|(\r\n?|\n)/gu, (match, escaped, newline) => {
if (escaped === oldQuote || oldQuote === "`" && escaped === "${") {
return escaped; // unescape
}
if (match === newQuote || newQuote === "`" && match === "${") {
return `\\${match}`; // escape
}
if (newline && oldQuote === "`") {
return "\\n"; // escape newlines
}
return match;
}) + newQuote;
};

const AVOID_ESCAPE = "avoid-escape";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
type: "layout",

docs: {
description: "enforce the consistent use of either backticks, double, or single quotes",
category: "Stylistic Issues",
recommended: false,
url: "https://eslint.org/docs/rules/quotes"
},

fixable: "code",

schema: [
{
enum: ["single", "double", "backtick"]
},
{
anyOf: [
{
enum: ["avoid-escape"]
},
{
type: "object",
properties: {
avoidEscape: {
type: "boolean"
},
allowTemplateLiterals: {
type: "boolean"
}
},
additionalProperties: false
}
]
}
],

messages: {
wrongQuotes: "Strings must use {{description}}."
}
},

create(context) {

const quoteOption = context.options[0],
settings = QUOTE_SETTINGS[quoteOption || "double"],
options = context.options[1],
allowTemplateLiterals = options && options.allowTemplateLiterals === true,
sourceCode = context.getSourceCode();
let avoidEscape = options && options.avoidEscape === true;

// deprecated
if (options === AVOID_ESCAPE) {
avoidEscape = true;
}

/**
* Determines if a given node is part of JSX syntax.
*
* This function returns `true` in the following cases:
*
* - `<div className="foo"></div>` ... If the literal is an attribute value, the parent of the literal is `JSXAttribute`.
* - `<div>foo</div>` ... If the literal is a text content, the parent of the literal is `JSXElement`.
* - `<>foo</>` ... If the literal is a text content, the parent of the literal is `JSXFragment`.
*
* In particular, this function returns `false` in the following cases:
*
* - `<div className={"foo"}></div>`
* - `<div>{"foo"}</div>`
*
* In both cases, inside of the braces is handled as normal JavaScript.
* The braces are `JSXExpressionContainer` nodes.
* @param {ASTNode} node The Literal node to check.
* @returns {boolean} True if the node is a part of JSX, false if not.
* @private
*/
function isJSXLiteral(node) {
return node.parent.type === "JSXAttribute" || node.parent.type === "JSXElement" || node.parent.type === "JSXFragment";
}

/**
* Checks whether or not a given node is a directive.
* The directive is a `ExpressionStatement` which has only a string literal.
* @param {ASTNode} node A node to check.
* @returns {boolean} Whether or not the node is a directive.
* @private
*/
function isDirective(node) {
return (
node.type === "ExpressionStatement" &&
node.expression.type === "Literal" &&
typeof node.expression.value === "string"
);
}

/**
* Checks whether or not a given node is a part of directive prologues.
* See also: http://www.ecma-international.org/ecma-262/6.0/#sec-directive-prologues-and-the-use-strict-directive
* @param {ASTNode} node A node to check.
* @returns {boolean} Whether or not the node is a part of directive prologues.
* @private
*/
function isPartOfDirectivePrologue(node) {
const block = node.parent.parent;

if (block.type !== "Program" && (block.type !== "BlockStatement" || !astUtils.isFunction(block.parent))) {
return false;
}

// Check the node is at a prologue.
for (let i = 0; i < block.body.length; ++i) {
const statement = block.body[i];

if (statement === node.parent) {
return true;
}
if (!isDirective(statement)) {
break;
}
}

return false;
}

/**
* Checks whether or not a given node is allowed as non backtick.
* @param {ASTNode} node A node to check.
* @returns {boolean} Whether or not the node is allowed as non backtick.
* @private
*/
function isAllowedAsNonBacktick(node) {
const parent = node.parent;

switch (parent.type) {

// Directive Prologues.
case "ExpressionStatement":
return isPartOfDirectivePrologue(node);

// LiteralPropertyName.
case "Property":
case "MethodDefinition":
return parent.key === node && !parent.computed;

// ModuleSpecifier.
case "ImportDeclaration":
case "ExportNamedDeclaration":
case "ExportAllDeclaration":
return parent.source === node;

// Others don't allow.
default:
return false;
}
}

/**
* Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings.
* @param {ASTNode} node A TemplateLiteral node to check.
* @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings.
* @private
*/
function isUsingFeatureOfTemplateLiteral(node) {
const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi;

if (hasTag) {
return true;
}

const hasStringInterpolation = node.expressions.length > 0;

if (hasStringInterpolation) {
return true;
}

const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);

if (isMultilineString) {
return true;
}

return false;
}

return {

Literal(node) {
const val = node.value,
rawVal = node.raw;

if (settings && typeof val === "string") {
let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
isJSXLiteral(node) ||
astUtils.isSurroundedBy(rawVal, settings.quote);

if (!isValid && avoidEscape) {
isValid = astUtils.isSurroundedBy(rawVal, settings.alternateQuote) && rawVal.indexOf(settings.quote) >= 0;
}

if (!isValid) {
context.report({
node,
messageId: "wrongQuotes",
data: {
description: settings.description
},
fix(fixer) {
if (quoteOption === "backtick" && astUtils.hasOctalEscapeSequence(rawVal)) {

// An octal escape sequence in a template literal would produce syntax error, even in non-strict mode.
return null;
}

return fixer.replaceText(node, settings.convert(node.raw));
}
});
}
}
},

TemplateLiteral(node) {
// Don't throw an error if backticks are expected or a template literal feature is in use.
if (
allowTemplateLiterals ||
quoteOption === "backtick" ||
isUsingFeatureOfTemplateLiteral(node) ||
settings && avoidEscape && node.quasis[0].value.raw.indexOf(settings.quote) >= 0
) {
return;
}

context.report({
node,
messageId: "wrongQuotes",
data: {
description: settings.description
},
fix(fixer) {
if (isPartOfDirectivePrologue(node)) {

/*
* TemplateLiterals in a directive prologue aren't actually directives, but if they're
* in the directive prologue, then fixing them might turn them into directives and change
* the behavior of the code.
*/
return null;
}
return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
}
});
}
};

}
};
@@ -0,0 +1,4 @@
node_modules
lib/protocol/crypto/poly1305.js
.eslint-plugins
!.eslintrc.js
@@ -0,0 +1,212 @@
'use strict';

/* eslint-env node */

const Module = require('module');
const path = require('path');

const ModuleFindPath = Module._findPath;
const hacks = [
'eslint-plugin-mscdex',
];
const eslintRulesPath =
path.join(path.dirname(process.mainModule.filename), '..', 'lib', 'rules');
Module._findPath = (request, paths, isMain) => {
const r = ModuleFindPath(request, paths.concat(eslintRulesPath), isMain);
if (!r) {
if (hacks.includes(request))
return path.join(__dirname, '.eslint-plugins', request);
}
return r;
};

module.exports = {
root: true,
env: { node: true, es6: true },
plugins: ['mscdex'],
parserOptions: { sourceType: 'script', ecmaVersion: '2020' },
rules: {
// ESLint built-in rules
// https://eslint.org/docs/rules/
'accessor-pairs': 'error',
'array-callback-return': 'error',
'arrow-parens': ['error', 'always'],
'arrow-spacing': ['error', { before: true, after: true }],
'block-scoped-var': 'error',
'block-spacing': 'error',
'brace-style': ['error', '1tbs', { allowSingleLine: true }],
'capitalized-comments': ['error', 'always', {
line: {
// Ignore all lines that have less characters than 20 and all lines that
// start with something that looks like a variable name or code.
// eslint-disable-next-line max-len
ignorePattern: '.{0,20}$|[a-z]+ ?[0-9A-Z_.(/=:[#-]|std|http|ssh|ftp|(let|var|const) [a-z_A-Z0-9]+ =|[b-z] |[a-z]*[0-9].* ',
ignoreInlineComments: true,
ignoreConsecutiveComments: true,
},
block: {
ignorePattern: '.*',
},
}],
'comma-dangle': ['error', 'only-multiline'],
'comma-spacing': 'error',
'comma-style': 'error',
'computed-property-spacing': 'error',
'constructor-super': 'error',
'default-case-last': 'error',
'dot-location': ['error', 'property'],
'dot-notation': 'error',
'eol-last': 'error',
'eqeqeq': ['error', 'smart'],
'for-direction': 'error',
'func-call-spacing': 'error',
'func-name-matching': 'error',
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'getter-return': 'error',
'key-spacing': ['error', { mode: 'strict' }],
'keyword-spacing': 'error',
'linebreak-style': ['error', 'unix'],
'max-len': ['error', {
code: 80,
ignorePattern: '^// Flags:',
ignoreRegExpLiterals: true,
ignoreUrls: true,
tabWidth: 2,
}],
'new-parens': 'error',
'no-async-promise-executor': 'error',
'no-class-assign': 'error',
'no-confusing-arrow': 'error',
'no-const-assign': 'error',
'no-constructor-return': 'error',
'no-control-regex': 'error',
'no-debugger': 'error',
'no-delete-var': 'error',
'no-dupe-args': 'error',
'no-dupe-class-members': 'error',
'no-dupe-keys': 'error',
'no-dupe-else-if': 'error',
'no-duplicate-case': 'error',
'no-duplicate-imports': 'error',
'no-else-return': ['error', { allowElseIf: true }],
'no-empty-character-class': 'error',
'no-ex-assign': 'error',
'no-extra-boolean-cast': 'error',
'no-extra-parens': ['error', 'functions'],
'no-extra-semi': 'error',
'no-fallthrough': 'error',
'no-func-assign': 'error',
'no-global-assign': 'error',
'no-invalid-regexp': 'error',
'no-irregular-whitespace': 'error',
'no-lonely-if': 'error',
'no-misleading-character-class': 'error',
'no-mixed-requires': 'error',
'no-mixed-spaces-and-tabs': 'error',
'no-multi-spaces': ['error', { ignoreEOLComments: true }],
'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0, maxBOF: 0 }],
'no-new-require': 'error',
'no-new-symbol': 'error',
'no-obj-calls': 'error',
'no-octal': 'error',
'no-path-concat': 'error',
'no-proto': 'error',
'no-redeclare': 'error',
/* eslint-disable max-len */
'no-restricted-syntax': [
'error',
{
selector: "CallExpression[callee.name='setTimeout'][arguments.length<2]",
message: '`setTimeout()` must be invoked with at least two arguments.',
},
{
selector: "CallExpression[callee.name='setInterval'][arguments.length<2]",
message: '`setInterval()` must be invoked with at least two arguments.',
},
{
selector: 'ThrowStatement > CallExpression[callee.name=/Error$/]',
message: 'Use `new` keyword when throwing an `Error`.',
}
],
/* eslint-enable max-len */
'no-return-await': 'error',
'no-self-assign': 'error',
'no-self-compare': 'error',
'no-setter-return': 'error',
'no-shadow-restricted-names': 'error',
'no-tabs': 'error',
'no-template-curly-in-string': 'error',
'no-this-before-super': 'error',
'no-throw-literal': 'error',
'no-trailing-spaces': 'error',
'no-undef': ['error', { typeof: true }],
'no-undef-init': 'error',
'no-unexpected-multiline': 'error',
'no-unreachable': 'error',
'no-unsafe-finally': 'error',
'no-unsafe-negation': 'error',
'no-unused-labels': 'error',
'no-unused-vars': ['error', { args: 'none', caughtErrors: 'all' }],
'no-use-before-define': ['error', {
classes: true,
functions: false,
variables: false,
}],
'no-useless-backreference': 'error',
'no-useless-call': 'error',
'no-useless-catch': 'error',
'no-useless-concat': 'error',
'no-useless-constructor': 'error',
'no-useless-escape': 'error',
'no-useless-return': 'error',
'no-var': 'error',
'no-void': 'error',
'no-whitespace-before-property': 'error',
'no-with': 'error',
'object-curly-spacing': ['error', 'always'],
'one-var': ['error', { initialized: 'never' }],
'one-var-declaration-per-line': 'error',
'operator-linebreak': ['error', 'before', { overrides: { '=': 'after' } }],
'padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: 'function', next: 'function' },
],
'prefer-const': ['error', { ignoreReadBeforeAssign: true }],
'quote-props': ['error', 'consistent'],
'rest-spread-spacing': 'error',
'semi': 'error',
'semi-spacing': 'error',
'space-before-blocks': ['error', 'always'],
'space-before-function-paren': ['error', {
anonymous: 'never',
named: 'never',
asyncArrow: 'always',
}],
'space-in-parens': ['error', 'never'],
'space-infix-ops': 'error',
'space-unary-ops': 'error',
'spaced-comment': ['error', 'always', {
'block': { 'balanced': true },
'exceptions': ['-'],
}],
'strict': ['error', 'global'],
'symbol-description': 'error',
'template-curly-spacing': 'error',
'unicode-bom': 'error',
'use-isnan': 'error',
'valid-typeof': 'error',

// Custom rules
'mscdex/curly': ['error', 'multi-or-nest', 'consistent'],
'mscdex/quotes': ['error', 'single', { avoidEscape: true }],
},
globals: {
Atomics: 'readable',
BigInt: 'readable',
BigInt64Array: 'readable',
BigUint64Array: 'readable',
TextEncoder: 'readable',
TextDecoder: 'readable',
globalThis: 'readable',
},
};
@@ -4,11 +4,9 @@ notifications:
email: false
env:
matrix:
- TRAVIS_NODE_VERSION="6"
- TRAVIS_NODE_VERSION="8"
- TRAVIS_NODE_VERSION="10"
- TRAVIS_NODE_VERSION="12"
- TRAVIS_NODE_VERSION="13"
- TRAVIS_NODE_VERSION="14"
install:
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION
- node --version
652 README.md

Large diffs are not rendered by default.

403 SFTP.md

Large diffs are not rendered by default.

@@ -4,34 +4,35 @@
// terminal types of client connections
// 2. Install `blessed`: `npm install blessed`
// 3. Create a server host key in this same directory and name it `host.key`
'use strict';

var fs = require('fs');
const { readFileSync } = require('fs');

var blessed = require('blessed');
var Server = require('ssh2').Server;
const blessed = require('blessed');
const { Server } = require('ssh2');

var RE_SPECIAL = /[\x00-\x1F\x7F]+|(?:\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K])/g;
var MAX_MSG_LEN = 128;
var MAX_NAME_LEN = 10;
var PROMPT_NAME = 'Enter a nickname to use (max ' + MAX_NAME_LEN + ' chars): ';
const RE_SPECIAL =
// eslint-disable-next-line no-control-regex
/[\x00-\x1F\x7F]+|(?:\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K])/g;
const MAX_MSG_LEN = 128;
const MAX_NAME_LEN = 10;
const PROMPT_NAME = `Enter a nickname to use (max ${MAX_NAME_LEN} chars): `;

var users = [];
const users = [];

function formatMessage(msg, output) {
var output = output;
output.parseTags = true;
msg = output._parseTags(msg);
output.parseTags = false;
return msg;
}

function userBroadcast(msg, source) {
var sourceMsg = '> ' + msg;
var name = '{cyan-fg}{bold}' + source.name + '{/}';
msg = ': ' + msg;
for (var i = 0; i < users.length; ++i) {
var user = users[i];
var output = user.output;
const sourceMsg = `> ${msg}`;
const name = `{cyan-fg}{bold}${source.name}{/}`;
msg = `: ${msg}`;
for (const user of users) {
const output = user.output;
if (source === user)
output.add(sourceMsg);
else
@@ -40,41 +41,43 @@ function userBroadcast(msg, source) {
}

function localMessage(msg, source) {
var output = source.output;
const output = source.output;
output.add(formatMessage(msg, output));
}

function noop(v) {}

new Server({
hostKeys: [fs.readFileSync('host.key')],
}, function(client) {
var stream;
var name;

client.on('authentication', function(ctx) {
var nick = ctx.username;
var prompt = PROMPT_NAME;
var lowered;
hostKeys: [readFileSync('host.key')],
}, (client) => {
let stream;
let name;

client.on('authentication', (ctx) => {
let nick = ctx.username;
let prompt = PROMPT_NAME;
let lowered;

// Try to use username as nickname
if (nick.length > 0 && nick.length <= MAX_NAME_LEN) {
lowered = nick.toLowerCase();
var ok = true;
for (var i = 0; i < users.length; ++i) {
if (users[i].name.toLowerCase() === lowered) {
let ok = true;
for (const user of users) {
if (user.name.toLowerCase() === lowered) {
ok = false;
prompt = 'That nickname is already in use.\n' + PROMPT_NAME;
prompt = `That nickname is already in use.\n${PROMPT_NAME}`;
break;
}
}
if (ok) {
name = nick;
return ctx.accept();
}
} else if (nick.length === 0)
} else if (nick.length === 0) {
prompt = 'A nickname is required.\n' + PROMPT_NAME;
else
} else {
prompt = 'That nickname is too long.\n' + PROMPT_NAME;
}

if (ctx.method !== 'keyboard-interactive')
return ctx.reject(['keyboard-interactive']);
@@ -84,33 +87,33 @@ new Server({
return ctx.reject(['keyboard-interactive']);
nick = answers[0];
if (nick.length > MAX_NAME_LEN) {
return ctx.prompt('That nickname is too long.\n' + PROMPT_NAME,
return ctx.prompt(`That nickname is too long.\n${PROMPT_NAME}`,
retryPrompt);
} else if (nick.length === 0) {
return ctx.prompt('A nickname is required.\n' + PROMPT_NAME,
return ctx.prompt(`A nickname is required.\n${PROMPT_NAME}`,
retryPrompt);
}
lowered = nick.toLowerCase();
for (var i = 0; i < users.length; ++i) {
if (users[i].name.toLowerCase() === lowered) {
return ctx.prompt('That nickname is already in use.\n' + PROMPT_NAME,
for (const user of users) {
if (user.name.toLowerCase() === lowered) {
return ctx.prompt(`That nickname is already in use.\n${PROMPT_NAME}`,
retryPrompt);
}
}
name = nick;
ctx.accept();
});
}).on('ready', function() {
var rows;
var cols;
var term;
client.once('session', function(accept, reject) {
accept().once('pty', function(accept, reject, info) {
}).on('ready', () => {
let rows;
let cols;
let term;
client.once('session', (accept, reject) => {
accept().once('pty', (accept, reject, info) => {
rows = info.rows;
cols = info.cols;
term = info.term;
accept && accept();
}).on('window-change', function(accept, reject, info) {
}).on('window-change', (accept, reject, info) => {
rows = info.rows;
cols = info.cols;
if (stream) {
@@ -119,7 +122,7 @@ new Server({
stream.emit('resize');
}
accept && accept();
}).once('shell', function(accept, reject) {
}).once('shell', (accept, reject) => {
stream = accept();
users.push(stream);

@@ -130,7 +133,7 @@ new Server({
stream.setRawMode = noop;
stream.on('error', noop);

var screen = new blessed.screen({
const screen = new blessed.screen({
autoPadding: true,
smartCSR: true,
program: new blessed.program({
@@ -144,14 +147,14 @@ new Server({
// Disable local echo
screen.program.attr('invisible', true);

var output = stream.output = new blessed.log({
const output = stream.output = new blessed.log({
screen: screen,
top: 0,
left: 0,
width: '100%',
bottom: 2,
scrollOnInput: true
})
});
screen.append(output);

screen.append(new blessed.box({
@@ -164,7 +167,7 @@ new Server({
ch: '='
}));

var input = new blessed.textbox({
const input = new blessed.textbox({
screen: screen,
bottom: 0,
height: 1,
@@ -184,9 +187,8 @@ new Server({
stream);

// Let everyone else know that this user just joined
for (var i = 0; i < users.length; ++i) {
var user = users[i];
var output = user.output;
for (const user of users) {
const output = user.output;
if (user === stream)
continue;
output.add(formatMessage('{green-fg}*** {bold}', output)
@@ -200,7 +202,7 @@ new Server({
screen.program.emit('resize');

// Read a line of input from the user
input.on('submit', function(line) {
input.on('submit', (line) => {
input.clearValue();
screen.render();
if (!input.focused)
@@ -217,27 +219,20 @@ new Server({
});
});
});
}).on('end', function() {
}).on('close', () => {
if (stream !== undefined) {
spliceOne(users, users.indexOf(stream));
users.splice(users.indexOf(stream), 1);
// Let everyone else know that this user just left
for (var i = 0; i < users.length; ++i) {
var user = users[i];
var output = user.output;
for (const user of users) {
const output = user.output;
output.add(formatMessage('{magenta-fg}*** {bold}', output)
+ name
+ formatMessage('{/bold} has left the chat{/}', output));
}
}
}).on('error', function(err) {
}).on('error', (err) => {
// Ignore errors
});
}).listen(0, function() {
console.log('Listening on port ' + this.address().port);
});

function spliceOne(list, index) {
for (var i = index, k = i + 1, n = list.length; k < n; i += 1, k += 1)
list[i] = list[k];
list.pop();
}
@@ -1,113 +1,134 @@
var crypto = require('crypto');
var constants = require('constants');
var fs = require('fs');
'use strict';

var ssh2 = require('ssh2');
var OPEN_MODE = ssh2.SFTP_OPEN_MODE;
var STATUS_CODE = ssh2.SFTP_STATUS_CODE;
const { timingSafeEqual } = require('crypto');
const { constants, readFileSync } = require('fs');

var allowedUser = Buffer.from('foo');
var allowedPassword = Buffer.from('bar');
const { Server, sftp: { OPEN_MODE, STATUS_CODE } } = require('ssh2');

new ssh2.Server({
hostKeys: [fs.readFileSync('host.key')]
}, function(client) {
const allowedUser = Buffer.from('foo');
const allowedPassword = Buffer.from('bar');

function checkValue(input, allowed) {
const autoReject = (input.length !== allowed.length);
if (autoReject) {
// Prevent leaking length information by always making a comparison with the
// same input when lengths don't match what we expect ...
allowed = input;
}
const isMatch = timingSafeEqual(input, allowed);
return (!autoReject && isMatch);
}

new Server({
hostKeys: [readFileSync('host.key')]
}, (client) => {
console.log('Client connected!');

client.on('authentication', function(ctx) {
var user = Buffer.from(ctx.username);
if (user.length !== allowedUser.length
|| !crypto.timingSafeEqual(user, allowedUser)) {
return ctx.reject(['password']);
}
client.on('authentication', (ctx) => {
let allowed = true;
if (!checkValue(Buffer.from(ctx.username), allowedUser))
allowed = false;

switch (ctx.method) {
case 'password':
var password = Buffer.from(ctx.password);
if (password.length !== allowedPassword.length
|| !crypto.timingSafeEqual(password, allowedPassword)) {
return ctx.reject(['password']);
}
if (!checkValue(Buffer.from(ctx.password), allowedPassword))
return ctx.reject();
break;
default:
return ctx.reject(['password']);
return ctx.reject();
}

ctx.accept();
}).on('ready', function() {
if (allowed)
ctx.accept();
else
ctx.reject();
}).on('ready', () => {
console.log('Client authenticated!');

client.on('session', function(accept, reject) {
var session = accept();
session.on('sftp', function(accept, reject) {
client.on('session', (accept, reject) => {
const session = accept();
session.on('sftp', (accept, reject) => {
console.log('Client SFTP session');
var openFiles = {};
var handleCount = 0;
// `sftpStream` is an `SFTPStream` instance in server mode
// see: https://github.com/mscdex/ssh2-streams/blob/master/SFTPStream.md
var sftpStream = accept();
sftpStream.on('OPEN', function(reqid, filename, flags, attrs) {
console.log('OPEN', filename);
// only allow opening /tmp/foo.txt for writing

const openFiles = new Map();
let handleCount = 0;
const sftp = accept();
sftp.on('OPEN', (reqid, filename, flags, attrs) => {
// Only allow opening /tmp/foo.txt for writing
if (filename !== '/tmp/foo.txt' || !(flags & OPEN_MODE.READ))
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
// create a fake handle to return to the client, this could easily
return sftp.status(reqid, STATUS_CODE.FAILURE);

// Create a fake handle to return to the client, this could easily
// be a real file descriptor number for example if actually opening
// the file on the disk
var handle = new Buffer(4);
openFiles[handleCount] = { read: false };
const handle = Buffer.alloc(4);
openFiles.set(handleCount, { read: false });
handle.writeUInt32BE(handleCount++, 0, true);
sftpStream.handle(reqid, handle);
console.log('Opening file for read')
}).on('READ', function(reqid, handle, offset, length) {
if (handle.length !== 4 || !openFiles[handle.readUInt32BE(0, true)])
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
// fake the read
var state = openFiles[handle.readUInt32BE(0, true)];
if (state.read)
sftpStream.status(reqid, STATUS_CODE.EOF);
else {

console.log('Opening file for read');
sftp.handle(reqid, handle);
}).on('READ', (reqid, handle, offset, length) => {
let fnum;
if (handle.length !== 4
|| !openFiles.has(fnum = handle.readUInt32BE(0, true))) {
return sftp.status(reqid, STATUS_CODE.FAILURE);
}

// Fake the read
const state = openFiles.get(fnum);
if (state.read) {
sftp.status(reqid, STATUS_CODE.EOF);
} else {
state.read = true;
sftpStream.data(reqid, 'bar');
console.log('Read from file at offset %d, length %d', offset, length);

console.log(
'Read from file at offset %d, length %d', offset, length
);
sftp.data(reqid, 'bar');
}
}).on('CLOSE', function(reqid, handle) {
var fnum;
if (handle.length !== 4 || !openFiles[(fnum = handle.readUInt32BE(0, true))])
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
delete openFiles[fnum];
sftpStream.status(reqid, STATUS_CODE.OK);
}).on('CLOSE', (reqid, handle) => {
let fnum;
if (handle.length !== 4
|| !openFiles.has(fnum = handle.readUInt32BE(0))) {
return sftp.status(reqid, STATUS_CODE.FAILURE);
}

openFiles.delete(fnum);

console.log('Closing file');
sftp.status(reqid, STATUS_CODE.OK);
}).on('REALPATH', function(reqid, path) {
var name = [{
const name = [{
filename: '/tmp/foo.txt',
longname: '-rwxrwxrwx 1 foo foo 3 Dec 8 2009 foo.txt',
attrs: {}
}];
sftpStream.name(reqid, name);
sftp.name(reqid, name);
}).on('STAT', onSTAT)
.on('LSTAT', onSTAT);

function onSTAT(reqid, path) {
if (path !== '/tmp/foo.txt')
return sftpStream.status(reqid, STATUS_CODE.FAILURE);
var mode = constants.S_IFREG; // Regular file
mode |= constants.S_IRWXU; // read, write, execute for user
mode |= constants.S_IRWXG; // read, write, execute for group
mode |= constants.S_IRWXO; // read, write, execute for other
sftpStream.attrs(reqid, {
return sftp.status(reqid, STATUS_CODE.FAILURE);

let mode = constants.S_IFREG; // Regular file
mode |= constants.S_IRWXU; // Read, write, execute for user
mode |= constants.S_IRWXG; // Read, write, execute for group
mode |= constants.S_IRWXO; // Read, write, execute for other
sftp.attrs(reqid, {
mode: mode,
uid: 0,
gid: 0,
size: 3,
atime: Date.now(),
mtime: Date.now()
mtime: Date.now(),
});
}
});
});
}).on('end', function() {
}).on('close', () => {
console.log('Client disconnected');
});
}).listen(0, '127.0.0.1', function() {
console.log('Listening on port ' + this.address().port);
console.log(`Listening on port ${this.address().port}`);
});
@@ -0,0 +1,17 @@
'use strict';

const { spawnSync } = require('child_process');

// Attempt to build the bundled optional binding
const result = spawnSync('node-gyp', ['rebuild'], {
cwd: 'lib/protocol/crypto',
encoding: 'utf8',
shell: true,
stdio: 'inherit',
windowsHide: true,
});
if (result.error || result.status !== 0)
console.log('Failed to build optional crypto binding');
else
console.log('Succeeded in building optional crypto binding');
process.exit(0);

Large diffs are not rendered by default.

This file was deleted.

Large diffs are not rendered by default.

This file was deleted.

Large diffs are not rendered by default.

@@ -1,63 +1,66 @@
var HttpAgent = require('http').Agent;
var HttpsAgent = require('https').Agent;
var inherits = require('util').inherits;
'use strict';

var Client;
const { Agent: HttpAgent } = require('http');
const { Agent: HttpsAgent } = require('https');
const { connect: tlsConnect } = require('tls');

[HttpAgent, HttpsAgent].forEach((ctor) => {
function SSHAgent(connectCfg, agentOptions) {
if (!(this instanceof SSHAgent))
return new SSHAgent(connectCfg, agentOptions);
let Client;

ctor.call(this, agentOptions);
for (const ctor of [HttpAgent, HttpsAgent]) {
class SSHAgent extends ctor {
constructor(connectCfg, agentOptions) {
super(agentOptions);

this._connectCfg = connectCfg;
this._defaultSrcIP = (agentOptions && agentOptions.srcIP) || 'localhost';
}
inherits(SSHAgent, ctor);
this._connectCfg = connectCfg;
this._defaultSrcIP = (agentOptions && agentOptions.srcIP) || 'localhost';
}

createConnection(options, cb) {
const srcIP = (options && options.localAddress) || this._defaultSrcIP;
const srcPort = (options && options.localPort) || 0;
const dstIP = options.host;
const dstPort = options.port;

if (Client === undefined)
({ Client } = require('./client.js'));

SSHAgent.prototype.createConnection = createConnection;
const client = new Client();
let triedForward = false;
client.on('ready', () => {
client.forwardOut(srcIP, srcPort, dstIP, dstPort, (err, stream) => {
triedForward = true;
if (err) {
client.end();
return cb(err);
}
stream.once('close', () => client.end());
cb(null, decorateStream(stream, ctor, options));
});
}).on('error', cb).on('close', () => {
if (!triedForward)
cb(new Error('Unexpected connection close'));
}).connect(this._connectCfg);
}
}

exports[ctor === HttpAgent ? 'SSHTTPAgent' : 'SSHTTPSAgent'] = SSHAgent;
});

function createConnection(options, cb) {
var srcIP = (options && options.localAddress) || this._defaultSrcIP;
var srcPort = (options && options.localPort) || 0;
var dstIP = options.host;
var dstPort = options.port;

if (Client === undefined)
Client = require('./client').Client;

var client = new Client();
var triedForward = false;
client.on('ready', () => {
client.forwardOut(srcIP, srcPort, dstIP, dstPort, (err, stream) => {
triedForward = true;
if (err) {
client.end();
return cb(err);
}
stream.once('close', () => {
client.end();
});
cb(null, decorateStream(stream));
});
}).on('error', cb).on('close', () => {
if (!triedForward)
cb(new Error('Unexpected connection loss'));
}).connect(this._connectCfg);
}

function noop() {}

function decorateStream(stream) {
stream.setKeepAlive = noop;
stream.setNoDelay = noop;
stream.setTimeout = noop;
stream.ref = noop;
stream.unref = noop;
stream.destroySoon = stream.destroy;
return stream;
function decorateStream(stream, ctor, options) {
if (ctor === HttpAgent) {
// HTTP
stream.setKeepAlive = noop;
stream.setNoDelay = noop;
stream.setTimeout = noop;
stream.ref = noop;
stream.unref = noop;
stream.destroySoon = stream.destroy;
return stream;
}

// HTTPS
options.socket = stream;
return tlsConnect(options);
}
@@ -0,0 +1,26 @@
'use strict';

const HTTPAgents = require('./http-agents.js');
const { parseKey } = require('./protocol/keyParser.js');
const {
flagsToString,
OPEN_MODE,
STATUS_CODE,
stringToFlags,
} = require('./protocol/SFTP.js');

module.exports = {
Client: require('./client.js'),
HTTPAgent: HTTPAgents.SSHTTPAgent,
HTTPSAgent: HTTPAgents.SSHTTPSAgent,
Server: require('./server.js'),
utils: {
parseKey,
sftp: {
flagsToString,
OPEN_MODE,
STATUS_CODE,
stringToFlags,
},
},
};

This file was deleted.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -0,0 +1,345 @@
// TODO: support server host key format: rsa-sha2-256/512
'use strict';

const crypto = require('crypto');

let cpuInfo;
try {
cpuInfo = require('cpu-features')();
} catch {}

const { bindingAvailable } = require('./crypto.js');

const eddsaSupported = (() => {
if (typeof crypto.sign === 'function'
&& typeof crypto.verify === 'function') {
const key =
'-----BEGIN PRIVATE KEY-----\r\nMC4CAQAwBQYDK2VwBCIEIHKj+sVa9WcD'
+ '/q2DJUJaf43Kptc8xYuUQA4bOFj9vC8T\r\n-----END PRIVATE KEY-----';
const data = Buffer.from('a');
let sig;
let verified;
try {
sig = crypto.sign(null, data, key);
verified = crypto.verify(null, data, key, sig);
} catch {}
return (Buffer.isBuffer(sig) && sig.length === 64 && verified === true);
}

return false;
})();

const curve25519Supported = (typeof crypto.diffieHellman === 'function'
&& typeof crypto.generateKeyPairSync === 'function'
&& typeof crypto.createPublicKey === 'function');

const DEFAULT_KEX = [
// https://tools.ietf.org/html/rfc5656#section-10.1
'ecdh-sha2-nistp256',
'ecdh-sha2-nistp384',
'ecdh-sha2-nistp521',

// https://tools.ietf.org/html/rfc4419#section-4
'diffie-hellman-group-exchange-sha256',

// https://tools.ietf.org/html/rfc8268
'diffie-hellman-group14-sha256',
'diffie-hellman-group15-sha512',
'diffie-hellman-group16-sha512',
'diffie-hellman-group17-sha512',
'diffie-hellman-group18-sha512',
];
if (curve25519Supported) {
DEFAULT_KEX.unshift('curve25519-sha256');
DEFAULT_KEX.unshift('curve25519-sha256@libssh.org');
}
const SUPPORTED_KEX = DEFAULT_KEX.concat([
// https://tools.ietf.org/html/rfc4419#section-4
'diffie-hellman-group-exchange-sha1',

'diffie-hellman-group14-sha1', // REQUIRED
'diffie-hellman-group1-sha1', // REQUIRED
]);


const DEFAULT_SERVER_HOST_KEY = [
'ecdsa-sha2-nistp256',
'ecdsa-sha2-nistp384',
'ecdsa-sha2-nistp521',
'rsa-sha2-512', // RFC 8332
'rsa-sha2-256', // RFC 8332
'ssh-rsa',
];
if (eddsaSupported)
DEFAULT_SERVER_HOST_KEY.unshift('ssh-ed25519');
const SUPPORTED_SERVER_HOST_KEY = DEFAULT_SERVER_HOST_KEY.concat([
'ssh-dss',
]);


const DEFAULT_CIPHER = [
// http://tools.ietf.org/html/rfc5647
'aes128-gcm',
'aes128-gcm@openssh.com',
'aes256-gcm',
'aes256-gcm@openssh.com',

// http://tools.ietf.org/html/rfc4344#section-4
'aes128-ctr',
'aes192-ctr',
'aes256-ctr',
];
if (cpuInfo && cpuInfo.flags && !cpuInfo.flags.aes) {
// We know for sure the CPU does not support AES acceleration
if (bindingAvailable)
DEFAULT_CIPHER.unshift('chacha20-poly1305@openssh.com');
else
DEFAULT_CIPHER.push('chacha20-poly1305@openssh.com');
} else if (bindingAvailable && cpuInfo && cpuInfo.arch === 'x86') {
// Places chacha20-poly1305 immediately after GCM ciphers since GCM ciphers
// seem to outperform it on x86, but it seems to be faster than CTR ciphers
DEFAULT_CIPHER.splice(4, 0, 'chacha20-poly1305@openssh.com');
} else {
DEFAULT_CIPHER.push('chacha20-poly1305@openssh.com');
}
const SUPPORTED_CIPHER = DEFAULT_CIPHER.concat([
'aes256-cbc',
'aes192-cbc',
'aes128-cbc',
'blowfish-cbc',
'3des-cbc',

// http://tools.ietf.org/html/rfc4345#section-4:
'arcfour256',
'arcfour128',

'cast128-cbc',
'arcfour',
]);


const DEFAULT_MAC = [
'hmac-sha2-256-etm@openssh.com',
'hmac-sha2-512-etm@openssh.com',
'hmac-sha1-etm@openssh.com',
'hmac-sha2-256',
'hmac-sha2-512',
'hmac-sha1',
];
const SUPPORTED_MAC = DEFAULT_MAC.concat([
'hmac-md5',
'hmac-sha2-256-96', // first 96 bits of HMAC-SHA256
'hmac-sha2-512-96', // first 96 bits of HMAC-SHA512
'hmac-ripemd160',
'hmac-sha1-96', // first 96 bits of HMAC-SHA1
'hmac-md5-96', // first 96 bits of HMAC-MD5
]);

const DEFAULT_COMPRESSION = [
'none',
'zlib@openssh.com', // ZLIB (LZ77) compression, except
// compression/decompression does not start until after
// successful user authentication
'zlib', // ZLIB (LZ77) compression
];
const SUPPORTED_COMPRESSION = DEFAULT_COMPRESSION.concat([
]);


const COMPAT = {
BAD_DHGEX: 1 << 0,
OLD_EXIT: 1 << 1,
DYN_RPORT_BUG: 1 << 2,
BUG_DHGEX_LARGE: 1 << 3,
};

module.exports = {
MESSAGE: {
// Transport layer protocol -- generic (1-19)
DISCONNECT: 1,
IGNORE: 2,
UNIMPLEMENTED: 3,
DEBUG: 4,
SERVICE_REQUEST: 5,
SERVICE_ACCEPT: 6,

// Transport layer protocol -- algorithm negotiation (20-29)
KEXINIT: 20,
NEWKEYS: 21,

// Transport layer protocol -- key exchange method-specific (30-49)
KEXDH_INIT: 30,
KEXDH_REPLY: 31,

KEXDH_GEX_GROUP: 31,
KEXDH_GEX_INIT: 32,
KEXDH_GEX_REPLY: 33,
KEXDH_GEX_REQUEST: 34,

KEXECDH_INIT: 30,
KEXECDH_REPLY: 31,

// User auth protocol -- generic (50-59)
USERAUTH_REQUEST: 50,
USERAUTH_FAILURE: 51,
USERAUTH_SUCCESS: 52,
USERAUTH_BANNER: 53,

// User auth protocol -- user auth method-specific (60-79)
USERAUTH_PASSWD_CHANGEREQ: 60,

USERAUTH_PK_OK: 60,

USERAUTH_INFO_REQUEST: 60,
USERAUTH_INFO_RESPONSE: 61,

// Connection protocol -- generic (80-89)
GLOBAL_REQUEST: 80,
REQUEST_SUCCESS: 81,
REQUEST_FAILURE: 82,

// Connection protocol -- channel-related (90-127)
CHANNEL_OPEN: 90,
CHANNEL_OPEN_CONFIRMATION: 91,
CHANNEL_OPEN_FAILURE: 92,
CHANNEL_WINDOW_ADJUST: 93,
CHANNEL_DATA: 94,
CHANNEL_EXTENDED_DATA: 95,
CHANNEL_EOF: 96,
CHANNEL_CLOSE: 97,
CHANNEL_REQUEST: 98,
CHANNEL_SUCCESS: 99,
CHANNEL_FAILURE: 100

// Reserved for client protocols (128-191)

// Local extensions (192-155)
},
DISCONNECT_REASON: {
HOST_NOT_ALLOWED_TO_CONNECT: 1,
PROTOCOL_ERROR: 2,
KEY_EXCHANGE_FAILED: 3,
RESERVED: 4,
MAC_ERROR: 5,
COMPRESSION_ERROR: 6,
SERVICE_NOT_AVAILABLE: 7,
PROTOCOL_VERSION_NOT_SUPPORTED: 8,
HOST_KEY_NOT_VERIFIABLE: 9,
CONNECTION_LOST: 10,
BY_APPLICATION: 11,
TOO_MANY_CONNECTIONS: 12,
AUTH_CANCELED_BY_USER: 13,
NO_MORE_AUTH_METHODS_AVAILABLE: 14,
ILLEGAL_USER_NAME: 15,
},
DISCONNECT_REASON_STR: undefined,
CHANNEL_OPEN_FAILURE: {
ADMINISTRATIVELY_PROHIBITED: 1,
CONNECT_FAILED: 2,
UNKNOWN_CHANNEL_TYPE: 3,
RESOURCE_SHORTAGE: 4
},
TERMINAL_MODE: {
TTY_OP_END: 0, // Indicates end of options.
VINTR: 1, // Interrupt character; 255 if none. Similarly for the
// other characters. Not all of these characters are
// supported on all systems.
VQUIT: 2, // The quit character (sends SIGQUIT signal on POSIX
// systems).
VERASE: 3, // Erase the character to left of the cursor.
VKILL: 4, // Kill the current input line.
VEOF: 5, // End-of-file character (sends EOF from the
// terminal).
VEOL: 6, // End-of-line character in addition to carriage
// return and/or linefeed.
VEOL2: 7, // Additional end-of-line character.
VSTART: 8, // Continues paused output (normally control-Q).
VSTOP: 9, // Pauses output (normally control-S).
VSUSP: 10, // Suspends the current program.
VDSUSP: 11, // Another suspend character.
VREPRINT: 12, // Reprints the current input line.
VWERASE: 13, // Erases a word left of cursor.
VLNEXT: 14, // Enter the next character typed literally, even if
// it is a special character
VFLUSH: 15, // Character to flush output.
VSWTCH: 16, // Switch to a different shell layer.
VSTATUS: 17, // Prints system status line (load, command, pid,
// etc).
VDISCARD: 18, // Toggles the flushing of terminal output.
IGNPAR: 30, // The ignore parity flag. The parameter SHOULD be 0
// if this flag is FALSE, and 1 if it is TRUE.
PARMRK: 31, // Mark parity and framing errors.
INPCK: 32, // Enable checking of parity errors.
ISTRIP: 33, // Strip 8th bit off characters.
INLCR: 34, // Map NL into CR on input.
IGNCR: 35, // Ignore CR on input.
ICRNL: 36, // Map CR to NL on input.
IUCLC: 37, // Translate uppercase characters to lowercase.
IXON: 38, // Enable output flow control.
IXANY: 39, // Any char will restart after stop.
IXOFF: 40, // Enable input flow control.
IMAXBEL: 41, // Ring bell on input queue full.
ISIG: 50, // Enable signals INTR, QUIT, [D]SUSP.
ICANON: 51, // Canonicalize input lines.
XCASE: 52, // Enable input and output of uppercase characters by
// preceding their lowercase equivalents with "\".
ECHO: 53, // Enable echoing.
ECHOE: 54, // Visually erase chars.
ECHOK: 55, // Kill character discards current line.
ECHONL: 56, // Echo NL even if ECHO is off.
NOFLSH: 57, // Don't flush after interrupt.
TOSTOP: 58, // Stop background jobs from output.
IEXTEN: 59, // Enable extensions.
ECHOCTL: 60, // Echo control characters as ^(Char).
ECHOKE: 61, // Visual erase for line kill.
PENDIN: 62, // Retype pending input.
OPOST: 70, // Enable output processing.
OLCUC: 71, // Convert lowercase to uppercase.
ONLCR: 72, // Map NL to CR-NL.
OCRNL: 73, // Translate carriage return to newline (output).
ONOCR: 74, // Translate newline to carriage return-newline
// (output).
ONLRET: 75, // Newline performs a carriage return (output).
CS7: 90, // 7 bit mode.
CS8: 91, // 8 bit mode.
PARENB: 92, // Parity enable.
PARODD: 93, // Odd parity, else even.
TTY_OP_ISPEED: 128, // Specifies the input baud rate in bits per second.
TTY_OP_OSPEED: 129, // Specifies the output baud rate in bits per second.
},
CHANNEL_EXTENDED_DATATYPE: {
STDERR: 1,
},

SIGNALS: [
'ABRT', 'ALRM', 'FPE', 'HUP', 'ILL', 'INT', 'QUIT', 'SEGV', 'TERM', 'USR1',
'USR2', 'KILL', 'PIPE'
].reduce((cur, val) => ({ ...cur, [val]: 1 }), {}),

COMPAT,
COMPAT_CHECKS: [
[ 'Cisco-1.25', COMPAT.BAD_DHGEX ],
[ /^Cisco-1\./, COMPAT.BUG_DHGEX_LARGE ],
[ /^[0-9.]+$/, COMPAT.OLD_EXIT ], // old SSH.com implementations
[ /^OpenSSH_5\.\d+/, COMPAT.DYN_RPORT_BUG ],
],

// KEX proposal-related
DEFAULT_KEX,
SUPPORTED_KEX,
DEFAULT_SERVER_HOST_KEY,
SUPPORTED_SERVER_HOST_KEY,
DEFAULT_CIPHER,
SUPPORTED_CIPHER,
DEFAULT_MAC,
SUPPORTED_MAC,
DEFAULT_COMPRESSION,
SUPPORTED_COMPRESSION,

curve25519Supported,
eddsaSupported,
};

module.exports.DISCONNECT_REASON_BY_VALUE =
Array.from(Object.entries(module.exports.DISCONNECT_REASON))
.reduce((obj, [key, value]) => ({ ...obj, [value]: key }), {});

Large diffs are not rendered by default.

@@ -0,0 +1,14 @@
{
'targets': [
{
'target_name': 'sshcrypto',
'include_dirs': [
"<!(node -e \"require('nan')\")",
],
'sources': [
'src/binding.cc'
],
'cflags': [ '-O3' ],
},
],
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -0,0 +1,16 @@
'use strict';

const MESSAGE_HANDLERS = new Array(256);
[
require('./kex.js').HANDLERS,
require('./handlers.misc.js'),
].forEach((handlers) => {
// eslint-disable-next-line prefer-const
for (let [type, handler] of Object.entries(handlers)) {
type = +type;
if (isFinite(type) && type >= 0 && type < MESSAGE_HANDLERS.length)
MESSAGE_HANDLERS[type] = handler;
}
});

module.exports = MESSAGE_HANDLERS;

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -0,0 +1,115 @@
'use strict';

const assert = require('assert');
const { inspect } = require('util');

// Only use this for integers! Decimal numbers do not work with this function.
function addNumericalSeparator(val) {
let res = '';
let i = val.length;
const start = val[0] === '-' ? 1 : 0;
for (; i >= start + 4; i -= 3)
res = `_${val.slice(i - 3, i)}${res}`;
return `${val.slice(0, i)}${res}`;
}

function oneOf(expected, thing) {
assert(typeof thing === 'string', '`thing` has to be of type string');
if (Array.isArray(expected)) {
const len = expected.length;
assert(len > 0, 'At least one expected value needs to be specified');
expected = expected.map((i) => String(i));
if (len > 2) {
return `one of ${thing} ${expected.slice(0, len - 1).join(', ')}, or `
+ expected[len - 1];
} else if (len === 2) {
return `one of ${thing} ${expected[0]} or ${expected[1]}`;
}
return `of ${thing} ${expected[0]}`;
}
return `of ${thing} ${String(expected)}`;
}


exports.ERR_INTERNAL_ASSERTION = class ERR_INTERNAL_ASSERTION extends Error {
constructor(message) {
super();
Error.captureStackTrace(this, ERR_INTERNAL_ASSERTION);

const suffix = 'This is caused by either a bug in ssh2 '
+ 'or incorrect usage of ssh2 internals.\n'
+ 'Please open an issue with this stack trace at '
+ 'https://github.com/mscdex/ssh2/issues\n';

this.message = (message === undefined ? suffix : `${message}\n${suffix}`);
}
};

const MAX_32BIT_INT = 2 ** 32;
const MAX_32BIT_BIGINT = (() => {
try {
return new Function('return 2n ** 32n')();
} catch {}
})();
exports.ERR_OUT_OF_RANGE = class ERR_OUT_OF_RANGE extends RangeError {
constructor(str, range, input, replaceDefaultBoolean) {
super();
Error.captureStackTrace(this, ERR_OUT_OF_RANGE);

assert(range, 'Missing "range" argument');
let msg = (replaceDefaultBoolean
? str
: `The value of "${str}" is out of range.`);
let received;
if (Number.isInteger(input) && Math.abs(input) > MAX_32BIT_INT) {
received = addNumericalSeparator(String(input));
} else if (typeof input === 'bigint') {
received = String(input);
if (input > MAX_32BIT_BIGINT || input < -MAX_32BIT_BIGINT)
received = addNumericalSeparator(received);
received += 'n';
} else {
received = inspect(input);
}
msg += ` It must be ${range}. Received ${received}`;

this.message = msg;
}
};

class ERR_INVALID_ARG_TYPE extends TypeError {
constructor(name, expected, actual) {
super();
Error.captureStackTrace(this, ERR_INVALID_ARG_TYPE);

assert(typeof name === 'string', `'name' must be a string`);

// determiner: 'must be' or 'must not be'
let determiner;
if (typeof expected === 'string' && expected.startsWith('not ')) {
determiner = 'must not be';
expected = expected.replace(/^not /, '');
} else {
determiner = 'must be';
}

let msg;
if (name.endsWith(' argument')) {
// For cases like 'first argument'
msg = `The ${name} ${determiner} ${oneOf(expected, 'type')}`;
} else {
const type = (name.includes('.') ? 'property' : 'argument');
msg = `The "${name}" ${type} ${determiner} ${oneOf(expected, 'type')}`;
}

msg += `. Received type ${typeof actual}`;

this.message = msg;
}
}
exports.ERR_INVALID_ARG_TYPE = ERR_INVALID_ARG_TYPE;

exports.validateNumber = function validateNumber(value, name) {
if (typeof value !== 'number')
throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
};
@@ -0,0 +1,376 @@
'use strict';

const { timingSafeEqual: timingSafeEqual_ } = require('crypto');

const Ber = require('asn1').Ber;

let DISCONNECT_REASON;

const FastBuffer = Buffer[Symbol.species];
const TypedArrayFill = Object.getPrototypeOf(Uint8Array.prototype).fill;

const EMPTY_BUFFER = Buffer.alloc(0);

function readUInt32BE(buf, offset) {
return (buf[offset++] * 16777216)
+ (buf[offset++] * 65536)
+ (buf[offset++] * 256)
+ buf[offset];
}

function bufferCopy(src, dest, srcStart, srcEnd, destStart) {
if (!destStart)
destStart = 0;
if (srcEnd > src.length)
srcEnd = src.length;
let nb = srcEnd - srcStart;
const destLeft = (dest.length - destStart);
if (nb > destLeft)
nb = destLeft;
dest.set(new Uint8Array(src.buffer, src.byteOffset + srcStart, nb),
destStart);
return nb;
}

function bufferSlice(buf, start, end) {
if (end === undefined)
end = buf.length;
return new FastBuffer(buf.buffer, buf.byteOffset + start, end - start);
}

function makeBufferParser() {
let pos = 0;
let buffer;

const self = {
init: (buf, start) => {
buffer = buf;
pos = (typeof start === 'number' ? start : 0);
},
pos: () => pos,
length: () => (buffer ? buffer.length : 0),
avail: () => (buffer && pos < buffer.length ? buffer.length - pos : 0),
clear: () => {
buffer = undefined;
},
readUInt32BE: () => {
if (!buffer || pos + 3 >= buffer.length)
return;
return (buffer[pos++] * 16777216)
+ (buffer[pos++] * 65536)
+ (buffer[pos++] * 256)
+ buffer[pos++];
},
readUInt64BE: (behavior) => {
if (!buffer || pos + 7 >= buffer.length)
return;
switch (behavior) {
case 'always':
return BigInt(`0x${buffer.hexSlice(pos, pos += 8)}`);
case 'maybe':
if (buffer[pos] > 0x1F)
return BigInt(`0x${buffer.hexSlice(pos, pos += 8)}`);
// FALLTHROUGH
default:
return (buffer[pos++] * 72057594037927940)
+ (buffer[pos++] * 281474976710656)
+ (buffer[pos++] * 1099511627776)
+ (buffer[pos++] * 4294967296)
+ (buffer[pos++] * 16777216)
+ (buffer[pos++] * 65536)
+ (buffer[pos++] * 256)
+ buffer[pos++];
}
},
skip: (n) => {
if (buffer && n > 0)
pos += n;
},
skipString: () => {
const len = self.readUInt32BE();
if (len === undefined)
return;
pos += len;
return (pos <= buffer.length ? len : undefined);
},
readByte: () => {
if (buffer && pos < buffer.length)
return buffer[pos++];
},
readBool: () => {
if (buffer && pos < buffer.length)
return !!buffer[pos++];
},
readList: () => {
const list = self.readString(true);
if (list === undefined)
return;
return (list ? list.split(',') : []);
},
readString: (dest, maxLen) => {
if (typeof dest === 'number') {
maxLen = dest;
dest = undefined;
}

const len = self.readUInt32BE();
if (len === undefined)
return;

if ((buffer.length - pos) < len
|| (typeof maxLen === 'number' && len > maxLen)) {
return;
}

if (dest) {
if (Buffer.isBuffer(dest))
return bufferCopy(buffer, dest, pos, pos += len);
return buffer.utf8Slice(pos, pos += len);
}
return bufferSlice(buffer, pos, pos += len);
},
readRaw: (len) => {
if (!buffer)
return;
if (typeof len !== 'number')
return bufferSlice(buffer, pos, pos += (buffer.length - pos));
if ((buffer.length - pos) >= len)
return bufferSlice(buffer, pos, pos += len);
},
};

return self;
}

function makeError(msg, level, fatal) {
const err = new Error(msg);
if (typeof level === 'boolean') {
fatal = level;
err.level = 'protocol';
} else {
err.level = level || 'protocol';
}
err.fatal = !!fatal;
return err;
}

function writeUInt32BE(buf, value, offset) {
buf[offset++] = (value >>> 24);
buf[offset++] = (value >>> 16);
buf[offset++] = (value >>> 8);
buf[offset++] = value;
return offset;
}

const utilBufferParser = makeBufferParser();

module.exports = {
bufferCopy,
bufferSlice,
FastBuffer,
bufferFill: (buf, value, start, end) => {
return TypedArrayFill.call(buf, value, start, end);
},
makeError,
doFatalError: (protocol, msg, level, reason) => {
let err;
if (DISCONNECT_REASON === undefined)
({ DISCONNECT_REASON } = require('./utils.js'));
if (msg instanceof Error) {
// doFatalError(protocol, err[, reason])
err = msg;
if (typeof level !== 'number')
reason = DISCONNECT_REASON.PROTOCOL_ERROR;
else
reason = level;
} else {
// doFatalError(protocol, msg[, level[, reason]])
err = makeError(msg, level, true);
}
if (typeof reason !== 'number')
reason = DISCONNECT_REASON.PROTOCOL_ERROR;
protocol.disconnect(reason);
protocol._destruct();
protocol._onError(err);
return Infinity;
},
getBinaryList: (list) => {
if (Buffer.isBuffer(list))
return list;
if (typeof list === 'string')
return (list.length === 0 ? EMPTY_BUFFER : Buffer.from(list));
if (Array.isArray(list))
return (list.length === 0 ? EMPTY_BUFFER : Buffer.from(list.join(',')));
throw new Error(`Invalid list type: ${typeof list}`);
},
timingSafeEquals: (a, b) => {
if (a.length !== b.length) {
timingSafeEqual_(a, a);
return false;
}
return timingSafeEqual_(a, b);
},
readUInt32BE,
writeUInt32BE,
writeUInt32LE: (buf, value, offset) => {
buf[offset++] = value;
buf[offset++] = (value >>> 8);
buf[offset++] = (value >>> 16);
buf[offset++] = (value >>> 24);
return offset;
},
makeBufferParser,
bufferParser: makeBufferParser(),
readString: (buffer, start, dest, maxLen) => {
if (typeof dest === 'number') {
maxLen = dest;
dest = undefined;
}

if (start === undefined)
start = 0;

const left = (buffer.length - start);
if (start < 0 || start >= buffer.length || left < 4)
return;

const len = readUInt32BE(buffer, start);
if (left < (4 + len) || (typeof maxLen === 'number' && len > maxLen))
return;

start += 4;
const end = start + len;
buffer._pos = end;

if (dest) {
if (Buffer.isBuffer(dest))
return bufferCopy(buffer, dest, start, end);
return buffer.utf8Slice(start, end);
}
return bufferSlice(buffer, start, end);
},
sigSSHToASN1: (sig, type) => {
switch (type) {
case 'ssh-dss': {
if (sig.length > 40)
return sig;
// Change bare signature r and s values to ASN.1 BER values for OpenSSL
const asnWriter = new Ber.Writer();
asnWriter.startSequence();
let r = sig.slice(0, 20);
let s = sig.slice(20);
if (r[0] & 0x80) {
const rNew = Buffer.allocUnsafe(21);
rNew[0] = 0x00;
r.copy(rNew, 1);
r = rNew;
} else if (r[0] === 0x00 && !(r[1] & 0x80)) {
r = r.slice(1);
}
if (s[0] & 0x80) {
const sNew = Buffer.allocUnsafe(21);
sNew[0] = 0x00;
s.copy(sNew, 1);
s = sNew;
} else if (s[0] === 0x00 && !(s[1] & 0x80)) {
s = s.slice(1);
}
asnWriter.writeBuffer(r, Ber.Integer);
asnWriter.writeBuffer(s, Ber.Integer);
asnWriter.endSequence();
return asnWriter.buffer;
}
case 'ecdsa-sha2-nistp256':
case 'ecdsa-sha2-nistp384':
case 'ecdsa-sha2-nistp521': {
utilBufferParser.init(sig, 0);
const r = utilBufferParser.readString();
const s = utilBufferParser.readString();
utilBufferParser.clear();
if (r === undefined || s === undefined)
return;

const asnWriter = new Ber.Writer();
asnWriter.startSequence();
asnWriter.writeBuffer(r, Ber.Integer);
asnWriter.writeBuffer(s, Ber.Integer);
asnWriter.endSequence();
return asnWriter.buffer;
}
default:
return sig;
}
},
convertSignature: (signature, keyType) => {
switch (keyType) {
case 'ssh-dss': {
if (signature.length <= 40)
return signature;
// This is a quick and dirty way to get from BER encoded r and s that
// OpenSSL gives us, to just the bare values back to back (40 bytes
// total) like OpenSSH (and possibly others) are expecting
const asnReader = new Ber.Reader(signature);
asnReader.readSequence();
let r = asnReader.readString(Ber.Integer, true);
let s = asnReader.readString(Ber.Integer, true);
let rOffset = 0;
let sOffset = 0;
if (r.length < 20) {
const rNew = Buffer.allocUnsafe(20);
rNew.set(r, 1);
r = rNew;
r[0] = 0;
}
if (s.length < 20) {
const sNew = Buffer.allocUnsafe(20);
sNew.set(s, 1);
s = sNew;
s[0] = 0;
}
if (r.length > 20 && r[0] === 0)
rOffset = 1;
if (s.length > 20 && s[0] === 0)
sOffset = 1;
const newSig =
Buffer.allocUnsafe((r.length - rOffset) + (s.length - sOffset));
bufferCopy(r, newSig, rOffset, r.length, 0);
bufferCopy(s, newSig, sOffset, s.length, r.length - rOffset);
return newSig;
}
case 'ecdsa-sha2-nistp256':
case 'ecdsa-sha2-nistp384':
case 'ecdsa-sha2-nistp521': {
if (signature[0] === 0)
return signature;
// Convert SSH signature parameters to ASN.1 BER values for OpenSSL
const asnReader = new Ber.Reader(signature);
asnReader.readSequence();
const r = asnReader.readString(Ber.Integer, true);
const s = asnReader.readString(Ber.Integer, true);
if (r === null || s === null)
return;
const newSig = Buffer.allocUnsafe(4 + r.length + 4 + s.length);
writeUInt32BE(newSig, r.length, 0);
newSig.set(r, 4);
writeUInt32BE(newSig, s.length, 4 + r.length);
newSig.set(s, 4 + 4 + r.length);
return newSig;
}
}

return signature;
},
sendPacket: (proto, packet, bypass) => {
if (!bypass && proto._kexinit !== undefined) {
// We're currently in the middle of a handshake

if (proto._queue === undefined)
proto._queue = [];
proto._queue.push(packet);
proto._debug && proto._debug('Outbound: ... packet queued');
return false;
}
proto._cipher.encrypt(packet);
return true;
},
};
@@ -0,0 +1,255 @@
'use strict';

const { kMaxLength } = require('buffer');
const {
createInflate,
constants: {
DEFLATE,
INFLATE,
Z_DEFAULT_CHUNK,
Z_DEFAULT_COMPRESSION,
Z_DEFAULT_MEMLEVEL,
Z_DEFAULT_STRATEGY,
Z_DEFAULT_WINDOWBITS,
Z_PARTIAL_FLUSH,
}
} = require('zlib');
const ZlibHandle = createInflate()._handle.constructor;

function processCallback() {
throw new Error('Should not get here');
}

function zlibOnError(message, errno, code) {
const self = this._owner;
// There is no way to cleanly recover.
// Continuing only obscures problems.

const error = new Error(message);
error.errno = errno;
error.code = code;
self._err = error;
}

function _close(engine) {
// Caller may invoke .close after a zlib error (which will null _handle).
if (!engine._handle)
return;

engine._handle.close();
engine._handle = null;
}

class Zlib {
constructor(mode) {
const windowBits = Z_DEFAULT_WINDOWBITS;
const level = Z_DEFAULT_COMPRESSION;
const memLevel = Z_DEFAULT_MEMLEVEL;
const strategy = Z_DEFAULT_STRATEGY;
const dictionary = undefined;

this._err = undefined;
this._writeState = new Uint32Array(2);
this._chunkSize = Z_DEFAULT_CHUNK;
this._maxOutputLength = kMaxLength;
this._outBuffer = Buffer.allocUnsafe(this._chunkSize);
this._outOffset = 0;

this._handle = new ZlibHandle(mode);
this._handle._owner = this;
this._handle.onerror = zlibOnError;
this._handle.init(windowBits,
level,
memLevel,
strategy,
this._writeState,
processCallback,
dictionary);
}

writeSync(chunk, retChunks) {
const handle = this._handle;
if (!handle)
throw new Error('Invalid Zlib instance');

let availInBefore = chunk.length;
let availOutBefore = this._chunkSize - this._outOffset;
let inOff = 0;
let availOutAfter;
let availInAfter;

let buffers;
let nread = 0;
const state = this._writeState;
let buffer = this._outBuffer;
let offset = this._outOffset;
const chunkSize = this._chunkSize;

while (true) {
handle.writeSync(Z_PARTIAL_FLUSH,
chunk, // in
inOff, // in_off
availInBefore, // in_len
buffer, // out
offset, // out_off
availOutBefore); // out_len
if (this._err)
throw this._err;

availOutAfter = state[0];
availInAfter = state[1];

const inDelta = availInBefore - availInAfter;
const have = availOutBefore - availOutAfter;

if (have > 0) {
const out = (offset === 0 && have === buffer.length
? buffer
: buffer.slice(offset, offset + have));
offset += have;
if (!buffers)
buffers = out;
else if (buffers.push === undefined)
buffers = [buffers, out];
else
buffers.push(out);
nread += out.byteLength;

if (nread > this._maxOutputLength) {
_close(this);
throw new Error(
`Output length exceeded maximum of ${this._maxOutputLength}`
);
}
} else if (have !== 0) {
throw new Error('have should not go down');
}

// Exhausted the output buffer, or used all the input create a new one.
if (availOutAfter === 0 || offset >= chunkSize) {
availOutBefore = chunkSize;
offset = 0;
buffer = Buffer.allocUnsafe(chunkSize);
}

if (availOutAfter === 0) {
// Not actually done. Need to reprocess.
// Also, update the availInBefore to the availInAfter value,
// so that if we have to hit it a third (fourth, etc.) time,
// it'll have the correct byte counts.
inOff += inDelta;
availInBefore = availInAfter;
} else {
break;
}
}

this._outBuffer = buffer;
this._outOffset = offset;

if (nread === 0)
buffers = Buffer.alloc(0);

if (retChunks) {
buffers.totalLen = nread;
return buffers;
}

if (buffers.push === undefined)
return buffers;

const output = Buffer.allocUnsafe(nread);
for (let i = 0, p = 0; i < buffers.length; ++i) {
const buf = buffers[i];
output.set(buf, p);
p += buf.length;
}
return output;
}
}

class ZlibPacketWriter {
constructor(protocol) {
this.allocStart = 0;
this.allocStartKEX = 0;
this._protocol = protocol;
this._zlib = new Zlib(DEFLATE);
}

cleanup() {
if (this._zlib)
_close(this._zlib);
}

alloc(payloadSize, force) {
return Buffer.allocUnsafe(payloadSize);
}

finalize(payload, force) {
if (this._protocol._kexinit === undefined || force) {
const output = this._zlib.writeSync(payload, true);
const packet = this._protocol._cipher.allocPacket(output.totalLen);
if (output.push === undefined) {
packet.set(output, 5);
} else {
for (let i = 0, p = 5; i < output.length; ++i) {
const chunk = output[i];
packet.set(chunk, p);
p += chunk.length;
}
}
return packet;
}
return payload;
}
}

class PacketWriter {
constructor(protocol) {
this.allocStart = 5;
this.allocStartKEX = 5;
this._protocol = protocol;
}

cleanup() {}

alloc(payloadSize, force) {
if (this._protocol._kexinit === undefined || force)
return this._protocol._cipher.allocPacket(payloadSize);
return Buffer.allocUnsafe(payloadSize);
}

finalize(packet, force) {
return packet;
}
}

class ZlibPacketReader {
constructor() {
this._zlib = new Zlib(INFLATE);
}

cleanup() {
if (this._zlib)
_close(this._zlib);
}

read(data) {
return this._zlib.writeSync(data, false);
}
}

class PacketReader {
cleanup() {}

read(data) {
return data;
}
}

module.exports = {
PacketReader,
PacketWriter,
ZlibPacketReader,
ZlibPacketWriter,
};

Large diffs are not rendered by default.

@@ -0,0 +1,316 @@
'use strict';

const { SFTP } = require('./protocol/SFTP.js');

const MAX_CHANNEL = 2 ** 32 - 1;

function onChannelOpenFailure(self, recipient, info, cb) {
self._chanMgr.remove(recipient);
if (typeof cb !== 'function')
return;

let err;
if (info instanceof Error) {
err = info;
} else if (typeof info === 'object' && info !== null) {
err = new Error(`(SSH) Channel open failure: ${info.description}`);
err.reason = info.reason;
} else {
err = new Error(
'(SSH) Channel open failure: server closed channel unexpectedly'
);
err.reason = '';
}

cb(err);
}

function onCHANNEL_CLOSE(self, recipient, channel, err, dead) {
if (typeof channel === 'function') {
// We got CHANNEL_CLOSE instead of CHANNEL_OPEN_FAILURE when
// requesting to open a channel
onChannelOpenFailure(self, recipient, err, channel);
return;
}
if (typeof channel !== 'object'
|| channel === null
|| channel.incoming.state === 'closed') {
return;
}

channel.incoming.state = 'closed';

if (channel.readable)
channel.push(null);
if (channel.server) {
if (channel.stderr.writable)
channel.stderr.end();
} else if (channel.stderr.readable) {
channel.stderr.push(null);
}

if (channel.constructor !== SFTP
&& (channel.outgoing.state === 'open'
|| channel.outgoing.state === 'eof')
&& !dead) {
channel.close();
}
if (channel.outgoing.state === 'closing')
channel.outgoing.state = 'closed';

self._chanMgr.remove(recipient);

const state = channel._writableState;
if (state && !state.ending && !state.finished && !dead)
channel.end();

// Take care of any outstanding channel requests
const chanCallbacks = channel._callbacks;
channel._callbacks = [];
for (let i = 0; i < chanCallbacks.length; ++i)
chanCallbacks[i](true);

if (channel.server) {
if (!channel.readable) {
channel.emit('close');
} else {
channel.once('end', () => {
channel.emit('close');
});
}
} else {
const exit = channel._exit;
// Align more with node child processes, where the close event gets
// the same arguments as the exit event
if (!channel.readable) {
if (exit.code === null) {
channel.emit('close', exit.code, exit.signal, exit.dump,
exit.desc, exit.lang);
} else {
channel.emit('close', exit.code);
}
} else {
channel.once('end', () => {
if (exit.code === null) {
channel.emit('close', exit.code, exit.signal, exit.dump,
exit.desc, exit.lang);
} else {
channel.emit('close', exit.code);
}
});
}

if (!channel.stderr.readable) {
channel.stderr.emit('close');
} else {
channel.stderr.once('end', () => {
channel.stderr.emit('close');
});
}
}
}

class ChannelManager {
constructor(client) {
this._client = client;
this._channels = {};
this._cur = -1;
this._count = 0;
}
add(val) {
// Attempt to reserve an id

let id;
// Optimized paths
if (this._cur < MAX_CHANNEL) {
id = ++this._cur;
} else if (this._count === 0) {
// Revert and reset back to fast path once we no longer have any channels
// open
this._cur = 0;
id = 0;
} else {
// Slower lookup path

// This path is triggered we have opened at least MAX_CHANNEL channels
// while having at least one channel open at any given time, so we have
// to search for a free id.
const channels = this._channels;
for (let i = 0; i < MAX_CHANNEL; ++i) {
if (channels[i] === undefined) {
id = i;
break;
}
}
}

if (id === undefined)
return -1;

this._channels[id] = (val || true);
++this._count;

return id;
}
update(id, val) {
if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
throw new Error(`Invalid channel id: ${id}`);

if (val && this._channels[id])
this._channels[id] = val;
}
get(id) {
if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
throw new Error(`Invalid channel id: ${id}`);

return this._channels[id];
}
remove(id) {
if (typeof id !== 'number' || id < 0 || id >= MAX_CHANNEL || !isFinite(id))
throw new Error(`Invalid channel id: ${id}`);

if (this._channels[id]) {
delete this._channels[id];
if (this._count)
--this._count;
}
}
cleanup(err) {
const channels = this._channels;
this._channels = {};
this._cur = -1;
this._count = 0;

const chanIDs = Object.keys(channels);
const client = this._client;
for (let i = 0; i < chanIDs.length; ++i) {
const id = +chanIDs[i];
onCHANNEL_CLOSE(client, id, channels[id], err, true);
}
}
}

const isRegExp = (() => {
const toString = Object.prototype.toString;
return (val) => toString.call(val) === '[object RegExp]';
})();

function generateAlgorithmList(algoList, defaultList, supportedList) {
if (Array.isArray(algoList) && algoList.length > 0) {
// Exact list
for (let i = 0; i < algoList.length; ++i) {
if (supportedList.indexOf(algoList[i]) === -1)
throw new Error(`Unsupported algorithm: ${algoList[i]}`);
}
return algoList;
}

if (typeof algoList === 'object' && algoList !== null) {
// Operations based on the default list
const keys = Object.keys(algoList);
let list = defaultList;
for (let i = 0; i < keys.length; ++i) {
const key = keys[i];
let val = algoList[key];
switch (key) {
case 'append':
if (!Array.isArray(val))
val = [val];
if (Array.isArray(val)) {
for (let j = 0; j < val.length; ++j) {
const append = val[j];
if (typeof append === 'string') {
if (!append || list.indexOf(append) !== -1)
continue;
if (supportedList.indexOf(append) === -1)
throw new Error(`Unsupported algorithm: ${append}`);
if (list === defaultList)
list = list.slice();
list.push(append);
} else if (isRegExp(append)) {
for (let k = 0; k < supportedList.length; ++k) {
const algo = list[k];
if (append.test(algo)) {
if (list.indexOf(algo) !== -1)
continue;
if (list === defaultList)
list = list.slice();
list.push(algo);
}
}
}
}
}
break;
case 'prepend':
if (!Array.isArray(val))
val = [val];
if (Array.isArray(val)) {
for (let j = val.length; j >= 0; --j) {
const prepend = val[j];
if (typeof prepend === 'string') {
if (!prepend || list.indexOf(prepend) !== -1)
continue;
if (supportedList.indexOf(prepend) === -1)
throw new Error(`Unsupported algorithm: ${prepend}`);
if (list === defaultList)
list = list.slice();
list.unshift(prepend);
} else if (isRegExp(prepend)) {
for (let k = supportedList.length; k >= 0; --k) {
const algo = list[k];
if (prepend.test(algo)) {
if (list.indexOf(algo) !== -1)
continue;
if (list === defaultList)
list = list.slice();
list.unshift(algo);
}
}
}
}
}
break;
case 'remove':
if (!Array.isArray(val))
val = [val];
if (Array.isArray(val)) {
for (let j = 0; j < val.length; ++j) {
const search = val[j];
if (typeof search === 'string') {
if (!search)
continue;
const idx = list.indexOf(search);
if (idx === -1)
continue;
if (list === defaultList)
list = list.slice();
list.splice(idx, 1);
} else if (isRegExp(search)) {
for (let k = 0; k < list.length; ++k) {
if (search.test(list[k])) {
if (list === defaultList)
list = list.slice();
list.splice(k, 1);
--k;
}
}
}
}
}
break;
}
}

return list;
}

return defaultList;
}

module.exports = {
ChannelManager,
generateAlgorithmList,
onChannelOpenFailure,
onCHANNEL_CLOSE,
};
@@ -1,16 +1,43 @@
{ "name": "ssh2",
"version": "0.8.9",
{
"name": "ssh2",
"version": "1.0.0-beta.0",
"author": "Brian White <mscdex@mscdex.net>",
"description": "SSH2 client and server modules written in pure JavaScript for node.js",
"main": "./lib/client",
"engines": { "node": ">=5.2.0" },
"engines": {
"node": ">=10.16.0"
},
"dependencies": {
"ssh2-streams": "~0.4.10"
"asn1": "^0.2.4",
"bcrypt-pbkdf": "^1.0.2"
},
"optionalDependencies": {
"cpu-features": "0.0.2",
"nan": "^2.14.1"
},
"scripts": {
"install": "node install.js",
"rebuild": "node install.js",
"test": "node test/test.js"
},
"keywords": [ "ssh", "ssh2", "sftp", "secure", "shell", "exec", "remote", "client" ],
"licenses": [ { "type": "MIT", "url": "http://github.com/mscdex/ssh2/raw/master/LICENSE" } ],
"repository" : { "type": "git", "url": "http://github.com/mscdex/ssh2.git" }
"keywords": [
"ssh",
"ssh2",
"sftp",
"secure",
"shell",
"exec",
"remote",
"client"
],
"licenses": [
{
"type": "MIT",
"url": "http://github.com/mscdex/ssh2/raw/master/LICENSE"
}
],
"repository": {
"type": "git",
"url": "http://github.com/mscdex/ssh2.git"
}
}
Loading