GHSA-pqwm-q9pv-ph8r - Fix CWE-78 [skip ci]

This commit is contained in:
Shivam Mathur
2026-05-14 03:56:37 +05:30
parent 0dc33069a3
commit eeef37e059
8 changed files with 199 additions and 67 deletions

View File

@@ -2,14 +2,18 @@ import * as config from '../src/config';
describe('Config tests', () => { describe('Config tests', () => {
it.each` it.each`
ini_values | os | output ini_values | os | output
${'a=b, c=d'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b\nc=d"'} ${'a=b, c=d'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b\nc=d"'}
${'a=b, c=d'} | ${'linux'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'} ${'a=b, c=d'} | ${'linux'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'}
${'a=b, c=d'} | ${'darwin'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'} ${'a=b, c=d'} | ${'darwin'} | ${'echo "a=b\nc=d" | sudo tee -a "${pecl_file:-${ini_file[@]}}"'}
${'a=b & ~c'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'b & ~c\'"'} ${'a=b & ~c'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'b & ~c\'"'}
${'a="~(b)"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'~(b)\'"'} ${'a="~(b)"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'~(b)\'"'}
${'a="b, c"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b, c"'} ${'a="b, c"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=b, c"'}
${'a=b, c=d'} | ${'openbsd'} | ${'Platform openbsd is not supported'} ${'disable_functions="exec,system"'} | ${'linux'} | ${'echo "disable_functions=exec,system" | sudo tee -a'}
${'disable_functions="exec,system"'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "disable_functions=exec,system"'}
${'a=$(id)'} | ${'linux'} | ${'echo "a=\'\\$(id)\'"'}
${'a=$(id)'} | ${'win32'} | ${'Add-Content "$php_dir\\php.ini" "a=\'`$(id)\'"'}
${'a=b, c=d'} | ${'openbsd'} | ${'Platform openbsd is not supported'}
`('checking addINIValues on $os', async ({ini_values, os, output}) => { `('checking addINIValues on $os', async ({ini_values, os, output}) => {
expect(await config.addINIValues(ini_values, os)).toContain(output); expect(await config.addINIValues(ini_values, os)).toContain(output);
}); });

View File

@@ -187,6 +187,7 @@ describe('Tools tests', () => {
${'1.2.3-dev'} | ${'tool'} | ${'phar'} | ${'1.2.3-dev'} ${'1.2.3-dev'} | ${'tool'} | ${'phar'} | ${'1.2.3-dev'}
${'1.2.3-alpha1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha1'} ${'1.2.3-alpha1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha1'}
${'1.2.3-alpha.1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha.1'} ${'1.2.3-alpha.1'} | ${'tool'} | ${'phar'} | ${'1.2.3-alpha.1'}
${'1.>=0'} | ${'tool'} | ${'phar'} | ${'1.0'}
`( `(
'checking getVersion: $version, $tool, $type', 'checking getVersion: $version, $tool, $type',
async ({version, tool, type, expected}) => { async ({version, tool, type, expected}) => {
@@ -304,22 +305,30 @@ describe('Tools tests', () => {
}); });
it.each` it.each`
os | script | scope os | release | scope | script
${'linux'} | ${'add_composer_tool tool tool:1.2.3 user/ global'} | ${'global'} ${'linux'} | ${'tool:1.2.3'} | ${'global'} | ${'add_composer_tool tool tool:1.2.3 user/ global'}
${'darwin'} | ${'add_composer_tool tool tool:1.2.3 user/ scoped'} | ${'scoped'} ${'darwin'} | ${'tool:1.2.3'} | ${'scoped'} | ${'add_composer_tool tool tool:1.2.3 user/ scoped'}
${'win32'} | ${'Add-ComposerTool tool tool:1.2.3 user/ scoped'} | ${'scoped'} ${'win32'} | ${'tool:1.2.3'} | ${'scoped'} | ${'Add-ComposerTool tool tool:1.2.3 user/ scoped'}
${'openbsd'} | ${'Platform openbsd is not supported'} | ${'global'} ${'linux'} | ${'tool:>=1.2'} | ${'global'} | ${'add_composer_tool tool "tool:>=1.2" user/ global'}
`('checking addPackage: $os, $scope', async ({os, script, scope}) => { ${'win32'} | ${'tool:>=1.2'} | ${'global'} | ${'Add-ComposerTool tool "tool:>=1.2" user/ global'}
const data = getData({ ${'linux'} | ${'tool:1.*'} | ${'global'} | ${'add_composer_tool tool "tool:1.*" user/ global'}
tool: 'tool', ${'linux'} | ${'psalm:^5||^6'} | ${'global'} | ${'add_composer_tool tool "psalm:^5||^6" user/ global'}
version: '1.2.3', ${'linux'} | ${'psalm:>=5,<6'} | ${'global'} | ${'add_composer_tool tool "psalm:>=5,<6" user/ global'}
repository: 'user/tool', ${'openbsd'} | ${'tool:1.2.3'} | ${'global'} | ${'Platform openbsd is not supported'}
os: os, `(
scope: scope 'checking addPackage: $os, $release',
}); async ({os, release, scope, script}) => {
data['release'] = [data['tool'], data['version']].join(':'); const data = getData({
expect(await tools.addPackage(data)).toContain(script); tool: 'tool',
}); version: '1.2.3',
repository: 'user/tool',
os,
scope
});
data['release'] = release;
expect(await tools.addPackage(data)).toContain(script);
}
);
it.each` it.each`
version | php_version | os | script version | php_version | os | script
@@ -651,7 +660,7 @@ describe('Tools tests', () => {
'add_devtools phpize', 'add_devtools phpize',
'add_tool https://github.com/phpmd/phpmd/releases/latest/download/phpmd.phar phpmd "--version"', 'add_tool https://github.com/phpmd/phpmd/releases/latest/download/phpmd.phar phpmd "--version"',
'add_tool https://github.com/phpspec/phpspec/releases/latest/download/phpspec.phar phpspec "-V"', 'add_tool https://github.com/phpspec/phpspec/releases/latest/download/phpspec.phar phpspec "-V"',
'add_composer_tool phpunit-bridge phpunit-bridge:5.6.* symfony/ global', 'add_composer_tool phpunit-bridge "phpunit-bridge:5.6.*" symfony/ global',
'add_composer_tool phpunit-polyfills phpunit-polyfills:1.0.1 yoast/ global', 'add_composer_tool phpunit-polyfills phpunit-polyfills:1.0.1 yoast/ global',
'add_protoc 1.2.3', 'add_protoc 1.2.3',
'add_tool https://github.com/vimeo/psalm/releases/latest/download/psalm.phar psalm "-v"', 'add_tool https://github.com/vimeo/psalm/releases/latest/download/psalm.phar psalm "-v"',
@@ -711,7 +720,7 @@ describe('Tools tests', () => {
'Add-ComposerTool codeception codeception codeception/ global', 'Add-ComposerTool codeception codeception codeception/ global',
'Add-ComposerTool prestissimo prestissimo hirak/ global', 'Add-ComposerTool prestissimo prestissimo hirak/ global',
'Add-ComposerTool automatic-composer-prefetcher automatic-composer-prefetcher narrowspark/ global', 'Add-ComposerTool automatic-composer-prefetcher automatic-composer-prefetcher narrowspark/ global',
'Add-ComposerTool phinx phinx:1.2.* robmorgan/ scoped', 'Add-ComposerTool phinx "phinx:1.2.*" robmorgan/ scoped',
'Add-ComposerTool phinx phinx:^1.2 robmorgan/ global', 'Add-ComposerTool phinx phinx:^1.2 robmorgan/ global',
'Add-ComposerTool tool tool:1.2.3 user/ global', 'Add-ComposerTool tool tool:1.2.3 user/ global',
'Add-ComposerTool tool tool:~1.2 user/ global' 'Add-ComposerTool tool tool:~1.2 user/ global'

View File

@@ -40,7 +40,19 @@ describe('Utils tests', () => {
expect(await utils.parseVersion('7')).toBe('7.0'); expect(await utils.parseVersion('7')).toBe('7.0');
expect(await utils.parseVersion('7.4')).toBe('7.4'); expect(await utils.parseVersion('7.4')).toBe('7.4');
expect(await utils.parseVersion('5.x')).toBe('5.6'); expect(await utils.parseVersion('5.x')).toBe('5.6');
expect(await utils.parseVersion('4.x')).toBe(undefined); expect(await utils.parseVersion('pre')).toBe('pre');
expect(await utils.parseVersion('pre-installed')).toBe('pre');
await expect(utils.parseVersion('4.x')).rejects.toThrow(
'Invalid PHP version: 4.x'
);
await expect(utils.parseVersion('foo')).rejects.toThrow(
'Invalid PHP version:'
);
fetchSpy.mockResolvedValue({data: '{ "latest": "8.1.0" }'});
await expect(utils.parseVersion('latest')).rejects.toThrow(
'Invalid PHP version in manifest:'
);
fetchSpy.mockReset(); fetchSpy.mockReset();
fetchSpy.mockResolvedValueOnce({}).mockResolvedValueOnce({}); fetchSpy.mockResolvedValueOnce({}).mockResolvedValueOnce({});
@@ -56,6 +68,12 @@ describe('Utils tests', () => {
expect(await utils.parseIniFile('none')).toBe('none'); expect(await utils.parseIniFile('none')).toBe('none');
expect(await utils.parseIniFile('php.ini-production')).toBe('production'); expect(await utils.parseIniFile('php.ini-production')).toBe('production');
expect(await utils.parseIniFile('php.ini-development')).toBe('development'); expect(await utils.parseIniFile('php.ini-development')).toBe('development');
expect(await utils.parseIniFile('/etc/php.ini-production')).toBe(
'production'
);
expect(await utils.parseIniFile('/a-b/php.ini-development')).toBe(
'development'
);
expect(await utils.parseIniFile('invalid')).toBe('production'); expect(await utils.parseIniFile('invalid')).toBe('production');
}); });
@@ -91,6 +109,22 @@ describe('Utils tests', () => {
).toEqual(['apcu', 'mbstring', 'pdo_pgsql', 'posix', 'session']); ).toEqual(['apcu', 'mbstring', 'pdo_pgsql', 'posix', 'session']);
}); });
it('checking shell helpers', () => {
expect(utils.escapeForShell('a$b`c\\d"e', 'linux')).toBe(
'a\\$b\\`c\\\\d\\"e'
);
expect(utils.escapeForShell('a$b`c"d', 'win32')).toBe('a`$b``c`"d');
expect(utils.safeArg('vendor-pkg/repo@v1.0.0', 'linux')).toBe(
'vendor-pkg/repo@v1.0.0'
);
expect(utils.safeArg('phpcs:>=3.0', 'linux')).toBe('"phpcs:>=3.0"');
expect(utils.safeArg('foo$bar', 'win32')).toBe('"foo`$bar"');
expect(utils.sanitizeShellInput('foo;$(`ls`)bar')).toBe('foolsbar');
expect(utils.sanitizeShellInput('vendor/foo:1.*', true)).toBe(
'vendor/foo:1.'
);
});
it('checking INIArray', async () => { it('checking INIArray', async () => {
expect(await utils.CSVArray('a=1, b=2, c=3')).toEqual([ expect(await utils.CSVArray('a=1, b=2, c=3')).toEqual([
'a=1', 'a=1',
@@ -282,6 +316,9 @@ describe('Utils tests', () => {
process.env['php-version'] = '8.2'; process.env['php-version'] = '8.2';
expect(await utils.readPHPVersion()).toBe('8.2'); expect(await utils.readPHPVersion()).toBe('8.2');
process.env['php-version'] = 'pre-installed';
expect(await utils.readPHPVersion()).toBe('pre-installed');
delete process.env['php-version-file']; delete process.env['php-version-file'];
delete process.env['php-version']; delete process.env['php-version'];
@@ -291,7 +328,7 @@ describe('Utils tests', () => {
existsSync.mockReturnValue(true); existsSync.mockReturnValue(true);
readFileSync.mockReturnValue('setup-php'); readFileSync.mockReturnValue('setup-php');
expect(await utils.readPHPVersion()).toBe('setup-php'); await expect(utils.readPHPVersion()).rejects.toThrow('Invalid PHP version');
existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true); existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true);
readFileSync.mockReturnValue( readFileSync.mockReturnValue(
@@ -312,6 +349,37 @@ describe('Utils tests', () => {
readFileSync.mockClear(); readFileSync.mockClear();
}); });
it('readPHPVersion rejects unsupported values from each source', async () => {
const existsSync = jest.spyOn(fs, 'existsSync').mockImplementation();
const readFileSync = jest.spyOn(fs, 'readFileSync').mockImplementation();
process.env['php-version'] = 'bogus';
await expect(utils.readPHPVersion()).rejects.toThrow('php-version input');
delete process.env['php-version'];
existsSync.mockReturnValue(true);
readFileSync.mockReturnValue('bogus');
await expect(utils.readPHPVersion()).rejects.toThrow('.php-version');
existsSync.mockReturnValueOnce(false).mockReturnValueOnce(true);
readFileSync.mockReturnValue('{"platform-overrides":{"php":"bogus"}}');
await expect(utils.readPHPVersion()).rejects.toThrow(
'composer.lock platform-overrides.php'
);
existsSync
.mockReturnValueOnce(false)
.mockReturnValueOnce(false)
.mockReturnValueOnce(true);
readFileSync.mockReturnValue('{"config":{"platform":{"php":"bogus"}}}');
await expect(utils.readPHPVersion()).rejects.toThrow(
'composer.json config.platform.php'
);
existsSync.mockClear();
readFileSync.mockClear();
});
it('checking setVariable', async () => { it('checking setVariable', async () => {
let script: string = await utils.setVariable('var', 'command', 'linux'); let script: string = await utils.setVariable('var', 'command', 'linux');
expect(script).toEqual('\nvar="$(command)"\n'); expect(script).toEqual('\nvar="$(command)"\n');

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -16,7 +16,7 @@ export async function addINIValuesUnix(
}); });
return ( return (
'echo "' + 'echo "' +
ini_values.join('\n') + ini_values.map(v => utils.escapeForShell(v, 'linux')).join('\n') +
'" | sudo tee -a "${pecl_file:-${ini_file[@]}}" >/dev/null 2>&1' + '" | sudo tee -a "${pecl_file:-${ini_file[@]}}" >/dev/null 2>&1' +
script script
); );
@@ -37,7 +37,10 @@ export async function addINIValuesWindows(
(await utils.addLog('$tick', line, 'Added to php.ini', 'win32')) + '\n'; (await utils.addLog('$tick', line, 'Added to php.ini', 'win32')) + '\n';
}); });
return ( return (
'Add-Content "$php_dir\\php.ini" "' + ini_values.join('\n') + '"' + script 'Add-Content "$php_dir\\php.ini" "' +
ini_values.map(v => utils.escapeForShell(v, 'win32')).join('\n') +
'"' +
script
); );
} }

View File

@@ -18,7 +18,10 @@ export async function getScript(os: string): Promise<string> {
const filename = os + (await utils.scriptExtension(os)); const filename = os + (await utils.scriptExtension(os));
const script_path = path.join(__dirname, '../src/scripts', filename); const script_path = path.join(__dirname, '../src/scripts', filename);
const run_path = script_path.replace(os, 'run'); const run_path = script_path.replace(os, 'run');
const extension_csv: string = await utils.getInput('extensions', false); const extension_csv: string = utils.sanitizeShellInput(
await utils.getInput('extensions', false),
true
);
const ini_values_csv: string = await utils.getInput('ini-values', false); const ini_values_csv: string = await utils.getInput('ini-values', false);
const coverage_driver: string = await utils.getInput('coverage', false); const coverage_driver: string = await utils.getInput('coverage', false);
const tools_csv: string = await utils.getInput('tools', false); const tools_csv: string = await utils.getInput('tools', false);
@@ -28,7 +31,7 @@ export async function getScript(os: string): Promise<string> {
const ini_file: string = await utils.parseIniFile( const ini_file: string = await utils.parseIniFile(
await utils.getInput('ini-file', false) await utils.getInput('ini-file', false)
); );
let script = await utils.joins('.', script_path, version, ini_file); let script = await utils.joins('.', script_path, `'${version}'`, ini_file);
if (extension_csv) { if (extension_csv) {
script += await extensions.addExtension(extension_csv, version, os); script += await extensions.addExtension(extension_csv, version, os);
} }

View File

@@ -231,7 +231,7 @@ export async function getVersion(
case !!data.repository && major_minor_regex.test(data.version): case !!data.repository && major_minor_regex.test(data.version):
return await getSemverVersion(data); return await getSemverVersion(data);
default: default:
return data.version.replace(/[><=^~]*/, ''); return data.version.replace(/[^a-zA-Z0-9_.:@+,/-]/g, '');
} }
} }
@@ -347,12 +347,9 @@ export async function addArchive(data: ToolData): Promise<string> {
export async function addPackage(data: ToolData): Promise<string> { export async function addPackage(data: ToolData): Promise<string> {
const command = await utils.getCommand(data.os, 'composer_tool'); const command = await utils.getCommand(data.os, 'composer_tool');
const parts: string[] = data.repository.split('/'); const parts: string[] = data.repository.split('/');
const args: string = await utils.joins( const args = [parts[1], data.release, parts[0] + '/', data.scope]
parts[1], .map(a => utils.safeArg(a, data.os))
data.release, .join(' ');
parts[0] + '/',
data.scope
);
return command + args; return command + args;
} }

View File

@@ -62,15 +62,31 @@ export async function getManifestURLS(): Promise<string[]> {
*/ */
export async function parseVersion(version: string): Promise<string> { export async function parseVersion(version: string): Promise<string> {
switch (true) { switch (true) {
case /^pre(-installed)?$/.test(version):
return 'pre';
case /^(latest|lowest|highest|nightly|master|\d+\.x)$/.test(version): case /^(latest|lowest|highest|nightly|master|\d+\.x)$/.test(version):
for (const manifestURL of await getManifestURLS()) { for (const manifestURL of await getManifestURLS()) {
const fetchResult = await fetch.fetch(manifestURL); const fetchResult = await fetch.fetch(manifestURL);
if (fetchResult['data'] ?? false) { if (fetchResult['data'] ?? false) {
return JSON.parse(fetchResult['data'])[version]; const resolved: string | undefined = JSON.parse(fetchResult['data'])[
version
];
if (resolved === undefined) {
throw new Error(`Invalid PHP version: ${version.slice(0, 20)}`);
}
if (!/^\d+\.\d+$/.test(resolved)) {
throw new Error(
`Invalid PHP version in manifest: ${resolved.slice(0, 10)}`
);
}
return resolved;
} }
} }
throw new Error(`Could not fetch the PHP version manifest.`); throw new Error(`Could not fetch the PHP version manifest.`);
default: default:
if (!/^\d+(\.\d+){0,2}$/.test(version)) {
throw new Error(`Invalid PHP version: ${version.slice(0, 20)}`);
}
switch (true) { switch (true) {
case version.length > 1: case version.length > 1:
return version.slice(0, 3); return version.slice(0, 3);
@@ -86,14 +102,11 @@ export async function parseVersion(version: string): Promise<string> {
* @param ini_file * @param ini_file
*/ */
export async function parseIniFile(ini_file: string): Promise<string> { export async function parseIniFile(ini_file: string): Promise<string> {
switch (true) { if (/^(production|development|none)$/.test(ini_file)) {
case /^(production|development|none)$/.test(ini_file): return ini_file;
return ini_file;
case /php\.ini-(production|development)$/.test(ini_file):
return ini_file.split('-')[1];
default:
return 'production';
} }
const match = ini_file.match(/php\.ini-(production|development)$/);
return match ? match[1] : 'production';
} }
/** /**
@@ -172,10 +185,10 @@ export async function log(
export async function stepLog(message: string, os: string): Promise<string> { export async function stepLog(message: string, os: string): Promise<string> {
switch (os) { switch (os) {
case 'win32': case 'win32':
return 'Step-Log "' + message + '"'; return 'Step-Log "' + escapeForShell(message, os) + '"';
case 'linux': case 'linux':
case 'darwin': case 'darwin':
return 'step_log "' + message + '"'; return 'step_log "' + escapeForShell(message, os) + '"';
default: default:
return await log('Platform ' + os + ' is not supported', os, 'error'); return await log('Platform ' + os + ' is not supported', os, 'error');
} }
@@ -194,17 +207,40 @@ export async function addLog(
message: string, message: string,
os: string os: string
): Promise<string> { ): Promise<string> {
const sub = escapeForShell(subject, os);
const msg = escapeForShell(message, os);
switch (os) { switch (os) {
case 'win32': case 'win32':
return 'Add-Log "' + mark + '" "' + subject + '" "' + message + '"'; return `Add-Log "${mark}" "${sub}" "${msg}"`;
case 'linux': case 'linux':
case 'darwin': case 'darwin':
return 'add_log "' + mark + '" "' + subject + '" "' + message + '"'; return `add_log "${mark}" "${sub}" "${msg}"`;
default: default:
return await log('Platform ' + os + ' is not supported', os, 'error'); return await log('Platform ' + os + ' is not supported', os, 'error');
} }
} }
export function escapeForShell(value: string, os: string): string {
if (os === 'win32') {
return value.replace(/[`$"]/g, '`$&');
}
return value.replace(/[\\`$"]/g, '\\$&');
}
export function safeArg(value: string, os: string): string {
if (!/^[a-zA-Z0-9_./:@+,~^-]*$/.test(value)) {
return '"' + escapeForShell(value, os) + '"';
}
return value;
}
export function sanitizeShellInput(value: string, strict = false): string {
const pattern = strict
? /[$`"';|&(){}[\]\\<>*?\n\r\t]/g
: /[$`"';|&(){}[\]\\\n\r\t]/g;
return value.replace(pattern, '');
}
/** /**
* Function to break extension csv into an array * Function to break extension csv into an array
* *
@@ -431,22 +467,35 @@ export async function parseExtensionSource(
); );
} }
const VERSION_INPUT_REGEX =
/^(latest|lowest|highest|nightly|master|pre|pre-installed|\d+\.x|\d+(\.\d+){0,2})$/;
function validatePHPVersionInput(version: string, source: string): string {
if (!VERSION_INPUT_REGEX.test(version)) {
throw new Error(
`Invalid PHP version in ${source}: ${version.slice(0, 20)}`
);
}
return version;
}
/** /**
* Read php version from input or file * Read php version from input or file
*/ */
export async function readPHPVersion(): Promise<string> { export async function readPHPVersion(): Promise<string> {
const version = await getInput('php-version', false); const version = await getInput('php-version', false);
if (version) { if (version) {
return version; return validatePHPVersionInput(version, 'php-version input');
} }
const versionFile = const versionFile =
(await getInput('php-version-file', false)) || '.php-version'; (await getInput('php-version-file', false)) || '.php-version';
if (fs.existsSync(versionFile)) { if (fs.existsSync(versionFile)) {
const contents: string = fs.readFileSync(versionFile, 'utf8'); const contents: string = fs.readFileSync(versionFile, 'utf8');
const match: RegExpMatchArray | null = contents.match( const match = contents.match(/^(?:php\s)?(\d+\.\d+\.\d+)$/m);
/^(?:php\s)?(\d+\.\d+\.\d+)$/m return validatePHPVersionInput(
match ? match[1] : contents.trim(),
versionFile
); );
return match ? match[1] : contents.trim();
} else if (versionFile !== '.php-version') { } else if (versionFile !== '.php-version') {
throw new Error(`Could not find '${versionFile}' file.`); throw new Error(`Could not find '${versionFile}' file.`);
} }
@@ -456,11 +505,11 @@ export async function readPHPVersion(): Promise<string> {
if (fs.existsSync(composerLock)) { if (fs.existsSync(composerLock)) {
const lockFileContents = JSON.parse(fs.readFileSync(composerLock, 'utf8')); const lockFileContents = JSON.parse(fs.readFileSync(composerLock, 'utf8'));
/* istanbul ignore next */ /* istanbul ignore next */
if ( if (lockFileContents['platform-overrides']?.['php']) {
lockFileContents['platform-overrides'] && return validatePHPVersionInput(
lockFileContents['platform-overrides']['php'] lockFileContents['platform-overrides']['php'],
) { 'composer.lock platform-overrides.php'
return lockFileContents['platform-overrides']['php']; );
} }
} }
@@ -470,12 +519,11 @@ export async function readPHPVersion(): Promise<string> {
fs.readFileSync(composerJson, 'utf8') fs.readFileSync(composerJson, 'utf8')
); );
/* istanbul ignore next */ /* istanbul ignore next */
if ( if (composerFileContents['config']?.['platform']?.['php']) {
composerFileContents['config'] && return validatePHPVersionInput(
composerFileContents['config']['platform'] && composerFileContents['config']['platform']['php'],
composerFileContents['config']['platform']['php'] 'composer.json config.platform.php'
) { );
return composerFileContents['config']['platform']['php'];
} }
} }