🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

shell-quote

Package Overview
Dependencies
Maintainers
4
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

shell-quote - npm Package Compare versions

Comparing version
1.8.4
to
1.9.0
+29
eslint.config.mjs
import ljharb from '@ljharb/eslint-config/flat';
export default [
...ljharb,
{
rules: {
'array-bracket-newline': 'off',
complexity: 'off',
eqeqeq: 'warn',
'func-style': ['error', 'declaration'],
'max-depth': 'off',
'max-lines-per-function': 'off',
'max-statements': 'off',
'multiline-comment-style': 'off',
'no-extra-parens': 'off',
'no-lonely-if': 'warn',
'no-negated-condition': 'warn',
'no-param-reassign': 'warn',
'no-shadow': 'warn',
'no-template-curly-in-string': 'off',
},
},
{
files: ['example/**'],
rules: {
'no-console': 'off',
},
},
];
import quote = require('./quote');
import parse = require('./parse');
export { quote, parse };
export type ControlOperator = parse.ControlOperator;
export type GlobPattern = parse.GlobPattern;
export type Comment = parse.Comment;
export type ParseEntry = parse.ParseEntry;
export type ParseOptions = parse.ParseOptions;
type Join<T extends readonly string[], D extends string> = T extends readonly []
? ''
: T extends readonly [infer F extends string]
? F
: T extends readonly [infer F extends string, ...infer R extends string[]]
? `${F}${D}${Join<R, D>}`
: string;
declare global {
interface ReadonlyArray<T> {
join<This extends readonly string[], D extends string = ','>(this: This, separator?: D): Join<This, D>;
}
interface Array<T> {
join<This extends readonly string[], D extends string = ','>(this: This, separator?: D): Join<This, D>;
}
}
declare namespace parse {
/** A shell control operator. */
export interface ControlOperator {
op: '||' | '&&' | ';;' | '|&' | '<(' | '<<<' | '>>' | '>&' | '<&' | '&' | ';' | '(' | ')' | '|' | '<' | '>';
}
/** A glob pattern parsed from the shell command. */
export interface GlobPattern {
op: 'glob';
pattern: string;
}
/** A shell comment. */
export interface Comment {
comment: string;
}
/** A parsed token returned by {@link parse}. */
export type ParseEntry = string | ControlOperator | GlobPattern | Comment;
/** Options for the {@link parse} function. */
export interface ParseOptions {
/** Custom escape character. Defaults to `\\`. */
escape?: string;
}
export type Env =
| Record<string, string | undefined>
| ((key: string) => string | object | undefined);
}
/**
* Parses a shell command string into an array of tokens.
*
* @param s - The shell command string to parse.
* @param env - Optional environment variables for expansion, either as an object of string values or a lookup function. When the lookup function returns an object, that object is inserted into the result verbatim.
* @param opts - Optional parsing options.
* @returns An array of parsed tokens, including any objects returned by an `env` lookup function.
*/
declare function parse<T extends string | object = never>(
s: string,
env?: parse.Env,
opts?: parse.ParseOptions,
): (parse.ParseEntry | T)[];
export = parse;
import parse = require('./parse');
/**
* Quotes an array of tokens into a shell-safe string.
*
* Accepts strings and the object shapes that {@link parse} emits. Throws a
* `TypeError` for unrecognized object shapes, `op` values outside the
* allowlist, or `pattern`/`comment` values containing line terminators.
*
* @param args - Array of tokens to quote.
* @returns A shell-safe quoted string.
*/
declare function quote(args: readonly parse.ParseEntry[]): string;
export = quote;
{
"extends": "@ljharb/tsconfig",
"compilerOptions": {
"target": "ES2021"
},
"exclude": [
"coverage",
"test"
]
}
+18
-13
{
"name": "shell-quote",
"description": "quote and parse shell commands",
"version": "1.8.4",
"version": "1.9.0",
"author": {

@@ -14,14 +14,2 @@ "name": "James Halliday",

"bugs": "https://github.com/ljharb/shell-quote/issues",
"devDependencies": {
"@ljharb/eslint-config": "^22.2.3",
"auto-changelog": "^2.5.1",
"eslint": "^8.57.1",
"evalmd": "^0.0.19",
"in-publish": "^2.0.1",
"jackspeak": "=2.1.1",
"npmignore": "^0.3.5",
"nyc": "^10.3.2",
"safe-publish-latest": "^2.0.0",
"tape": "^5.9.0"
},
"homepage": "https://github.com/ljharb/shell-quote",

@@ -36,2 +24,3 @@ "keywords": [

"main": "index.js",
"types": "index.d.ts",
"repository": {

@@ -47,2 +36,3 @@ "type": "git",

"lint": "eslint --ext=js,mjs .",
"postlint": "tsc -p . && attw -P",
"pretest": "npm run lint",

@@ -55,2 +45,17 @@ "tests-only": "nyc tape 'test/**/*.js'",

},
"devDependencies": {
"@arethetypeswrong/cli": "^0.18.2",
"@ljharb/eslint-config": "^22.2.3",
"@ljharb/tsconfig": "^0.3.2",
"auto-changelog": "^2.6.0",
"eslint": "^10.5.0",
"evalmd": "^0.0.19",
"in-publish": "^2.0.1",
"jiti": "^0.0.0",
"npmignore": "^0.3.5",
"nyc": "^10.3.2",
"safe-publish-latest": "^2.0.0",
"tape": "^5.10.2",
"typescript": "next"
},
"auto-changelog": {

@@ -57,0 +62,0 @@ "output": "CHANGELOG.md",

+64
-29
'use strict';
/**
* @import {
* ControlOperator,
* Env,
* GlobPattern,
* ParseEntry,
* } from './parse' */
// '<(' is process substitution operator and
// can be parsed the same as control operator
var CONTROL = '(?:' + [
var CONTROL = /** @type {const} */ ('(?:') + /** @type {const} */ ([
'\\|\\|',

@@ -16,15 +24,15 @@ '\\&\\&',

'[&;()|<>]'
].join('|') + ')';
]).join(/** @type {const} */ ('|')) + /** @type {const} */ (')');
var controlRE = new RegExp('^' + CONTROL + '$');
var META = '|&;()<> \\t';
var SINGLE_QUOTE = '"((\\\\"|[^"])*?)"';
var DOUBLE_QUOTE = '\'((\\\\\'|[^\'])*?)\'';
var META = /** @type {const} */ ('|&;()<> \\t');
var SINGLE_QUOTE = /** @type {const} */ ('"((\\\\"|[^"])*?)"');
var DOUBLE_QUOTE = /** @type {const} */ ('\'((\\\\\'|[^\'])*?)\'');
var hash = /^#$/;
var SQ = "'";
var DQ = '"';
var DS = '$';
var SQ = /** @type {const} */ ("'");
var DQ = /** @type {const} */ ('"');
var DS = /** @type {const} */ ('$');
var TOKEN = '';
var mult = 0x100000000; // Math.pow(16, 8);
var mult = /** @type {const} */ (0x100000000); // Math.pow(16, 8);
for (var i = 0; i < 4; i++) {

@@ -35,2 +43,6 @@ TOKEN += (mult * Math.random()).toString(16);

/**
* @param {string} s
* @param {RegExp} r
*/
function matchAll(s, r) {

@@ -43,3 +55,3 @@ var origIndex = r.lastIndex;

while ((matchObj = r.exec(s))) {
matches.push(matchObj);
matches[matches.length] = matchObj;
if (r.lastIndex === matchObj.index) {

@@ -55,2 +67,7 @@ r.lastIndex += 1;

/**
* @param {Env} env
* @param {string} pre
* @param {string} key
*/
function getVar(env, pre, key) {

@@ -70,2 +87,8 @@ var r = typeof env === 'function' ? env(key) : env[key];

/**
* @param {string} string
* @param {Env} [env]
* @param {{ escape?: string }} [opts]
* @returns {ParseEntry[]}
*/
function parseInternal(string, env, opts) {

@@ -100,3 +123,3 @@ if (!opts) {

if (controlRE.test(s)) {
return { op: s };
return /** @type {ControlOperator} */ ({ op: s });
}

@@ -115,2 +138,3 @@

// "allonetoken")
/** @type {string | boolean} */
var quote = false;

@@ -120,2 +144,3 @@ var esc = false;

var isGlob = false;
/** @type {number} */
var i;

@@ -125,3 +150,5 @@

i += 1;
/** @type {number | RegExpMatchArray | null} */
var varend;
/** @type {string} */
var varname;

@@ -152,6 +179,6 @@ var char = s.charAt(i);

varname = slicedFromI.slice(0, varend.index);
i += varend.index - 1;
i += /** @type {number} */ (varend.index) - 1;
}
}
return getVar(env, '', varname);
return getVar(/** @type {NonNullable<typeof env>} */ (env), '', varname);
}

@@ -188,3 +215,3 @@

} else if (controlRE.test(c)) {
return { op: s };
return /** @type {ControlOperator} */ ({ op: s });
} else if (hash.test(c)) {

@@ -194,5 +221,5 @@ commented = true;

if (out.length) {
return [out, commentObj];
return /** @type {const} */ ([out, commentObj]);
}
return [commentObj];
return /** @type {const} */ ([commentObj]);
} else if (c === BS) {

@@ -208,3 +235,3 @@ esc = true;

if (isGlob) {
return { op: 'glob', pattern: out };
return /** @type {GlobPattern} */ ({ op: 'glob', pattern: out });
}

@@ -214,7 +241,13 @@

}).reduce(function (prev, arg) { // finalize parsed arguments
// TODO: replace this whole reduce with a concat
return typeof arg === 'undefined' ? prev : prev.concat(arg);
}, []);
if (typeof arg === 'undefined') {
return prev;
}
/** @type {ParseEntry[]} */ ([]).concat(arg).forEach(function (entry) {
prev[prev.length] = entry;
});
return prev;
}, /** @type {ParseEntry[]} */ ([]));
}
/** @type {import('./parse')} */
module.exports = function parse(s, env, opts) {

@@ -227,15 +260,17 @@ var mapped = parseInternal(s, env, opts);

if (typeof s === 'object') {
return acc.concat(s);
acc[acc.length] = s;
return acc;
}
var xs = s.split(RegExp('(' + TOKEN + '.*?' + TOKEN + ')', 'g'));
if (xs.length === 1) {
return acc.concat(xs[0]);
acc[acc.length] = xs[0];
return acc;
}
return acc.concat(xs.filter(Boolean).map(function (x) {
if (startsWithToken.test(x)) {
return JSON.parse(x.split(TOKEN)[1]);
}
return x;
}));
}, []);
xs.filter(Boolean).forEach(function (x) {
acc[acc.length] = startsWithToken.test(x)
? JSON.parse(x.split(TOKEN)[1])
: x;
});
return acc;
}, /** @type {ParseEntry[]} */ ([]));
};
'use strict';
var OPS = [
/** @import { ControlOperator } from './parse' */
/** @type {ControlOperator['op'][]} */
var OPS = /** @type {const} */ ([
'||',

@@ -20,13 +23,14 @@ '&&',

'>'
];
]);
var LINE_TERMINATORS = /[\n\r\u2028\u2029]/;
var GLOB_SHELL_SPECIAL = /[\s#!"$&'():;<=>@\\^`|]/g;
/** @type {import('./quote')} */
module.exports = function quote(xs) {
return xs.map(function (s) {
if (s === '') {
return '\'\'';
return /** @type {const} */ ('\'\'');
}
if (s && typeof s === 'object') {
if (s.op === 'glob') {
if ('op' in s && s.op === 'glob') {
if (typeof s.pattern !== 'string') {

@@ -40,3 +44,3 @@ throw new TypeError('glob token requires a string `pattern`');

}
if (typeof s.op === 'string') {
if ('op' in s && typeof s.op === 'string') {
if (OPS.indexOf(s.op) < 0) {

@@ -47,3 +51,3 @@ throw new TypeError('invalid `op` value: ' + JSON.stringify(s.op));

}
if (typeof s.comment === 'string') {
if ('comment' in s && typeof s.comment === 'string') {
if (LINE_TERMINATORS.test(s.comment)) {

@@ -62,4 +66,4 @@ throw new TypeError('`comment` must not contain line terminators');

}
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}])/g, '$1\\$2');
return String(s).replace(/([A-Za-z]:)?([#!"$&'()*,:;<=>?@[\\\]^`{|}~])/g, '$1\\$2');
}).join(' ');
};

@@ -51,1 +51,18 @@ 'use strict';

});
test('parse stays linear in token count (GHSA-395f-4hp3-45gv)', function (t) {
// the old concat-in-reduce finalizer was O(n^2): this many tokens took
// ~minutes, so under the unfixed code this test hangs rather than passes
var n = 2e5;
var input = new Array(n + 1).join('x '); // avoid String#repeat for old engines
var words = parse(input);
t.equal(words.length, n, 'every token is returned');
t.equal(words[0], 'x', 'first token is correct');
t.equal(words[n - 1], 'x', 'last token is correct');
var withEnv = parse(input, function () { return 'v'; });
t.equal(withEnv.length, n, 'env-function path returns every token');
t.end();
});

@@ -33,2 +33,14 @@ 'use strict';

test('quote tilde (escapes leading ~ to prevent shell tilde-expansion)', function (t) {
t.equal(quote(['~']), '\\~');
t.equal(quote(['~/foo']), '\\~/foo');
t.equal(quote(['~root']), '\\~root');
t.equal(quote(['~root/x']), '\\~root/x');
t.equal(quote(['~+']), '\\~+');
t.equal(quote(['~-']), '\\~-');
t.equal(quote(['a~b']), 'a\\~b');
t.equal(quote(['x~']), 'x\\~');
t.end();
});
test('quote ops', function (t) {

@@ -35,0 +47,0 @@ t.equal(quote(['a', { op: '|' }, 'b']), 'a \\| b');

{
"root": true,
"extends": "@ljharb",
"rules": {
"array-bracket-newline": 0,
"complexity": 0,
"eqeqeq": 1,
"func-style": [2, "declaration"],
"max-depth": 0,
"max-lines-per-function": 0,
"max-statements": 0,
"multiline-comment-style": 0,
"no-negated-condition": 1,
"no-param-reassign": 1,
"no-lonely-if": 1,
"no-shadow": 1,
"no-template-curly-in-string": 0,
},
"overrides": [
{
"files": "example/**",
"rules": {
"no-console": 0,
},
},
],
}