Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(REF) FileTest - Prevent test-interactions from testIsDirWithOpenBasedir #24476

Merged
merged 1 commit into from
Oct 21, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 88 additions & 16 deletions tests/phpunit/CRM/Utils/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -315,10 +315,9 @@ public function testIsDirLinks() {
* @param bool $expected
*/
public function testIsDirWithOpenBasedir(?string $input, bool $expected) {
$originalOpenBasedir = ini_get('open_basedir');

// This might not always be under cms root, but let's see how it goes.
$a_dir = \Civi::paths()->getPath('[civicrm.compile]/');

if (file_exists("{$a_dir}/isDirTest/ok/ok.txt")) {
unlink("{$a_dir}/isDirTest/ok/ok.txt");
}
Expand All @@ -336,36 +335,109 @@ public function testIsDirWithOpenBasedir(?string $input, bool $expected) {
// For now let's try this, assuming a drupal 7 structure where we know
// where this file is:
$cms_root = realpath(__DIR__ . '/../../../../../../../..');

// This test requires tightening `open_basedir` settings, which is a one-way change.
// Therefore, we have to do that part of the test in a sub-process.
// If you run directly in the same-process, then (eg) `phpunit` will have trouble writing error-data to JUnit XML files.
[$exitCode, $stdout, $stderr] = $this->runStaticMethodAsScript(__CLASS__, 'doIsDirWithOpenBasedir', [$a_dir, $cms_root, $input, $expected]);
$this->assertEquals('"OK"', trim($stdout), 'doIsDirWithOpenBasedir() should return OK');
$this->assertEquals('', trim($stderr), 'doIsDirWithOpenBasedir() should not generate warnings');
$this->assertEquals(0, $exitCode, 'doIsDirWithOpenBasedir() should exit normally');

unlink("{$a_dir}/isDirTest/ok/ok.txt");
rmdir("{$a_dir}/isDirTest/ok");
rmdir("{$a_dir}/isDirTest");
}

/**
* @param string $a_dir
* @param string $cms_root
* @param string|null $input
* @param bool $expected
* @return string
* @throws \ErrorException
*/
public static function doIsDirWithOpenBasedir(string $a_dir, string $cms_root, ?string $input, bool $expected): string {
// We also need temp dir because phpunit creates files in there as it does stuff before we can reset basedir.
ini_set('open_basedir', $cms_root . PATH_SEPARATOR . sys_get_temp_dir());

$this->assertTrue(mkdir("{$a_dir}/isDirTest"));
$this->assertTrue(mkdir("{$a_dir}/isDirTest/ok"));
if (!mkdir("{$a_dir}/isDirTest")) {
return 'Failed to make isDirTest';
}

if (!mkdir("{$a_dir}/isDirTest/ok")) {
return 'Failed to make isDirTest/ok';
}

file_put_contents("{$a_dir}/isDirTest/ok/ok.txt", 'Hello World!');
// hmm the "bad" isn't going to work the same way php's own tests work. We
// need to find a directory outside both cms_root and the sys temp dir.
// Let's just use some known unix files that always exist instead.
// mkdir("{$a_dir}/isDirTest/bad");

$old_cwd = getcwd();
$this->assertTrue(chdir("{$a_dir}/isDirTest/ok"));
if (!chdir("{$a_dir}/isDirTest/ok")) {
return 'Failed to chdir to isDirTest/ok';
}

clearstatcache();
if ($expected) {
$this->assertTrue(CRM_Utils_File::isDir($input));
}
else {
$actual = CRM_Utils_File::isDir($input); /* Should pass */
// $actual = is_dir($input); /* Should fail */
if ($expected !== $actual) {
return sprintf("isDir returned incorrect result. Expected=%s Actual=%s", json_encode($expected), json_encode($actual));
// Note that except for 'ok.txt', the real is_dir() would give an
// error for these. For 'ok.txt' it would return false, but no error.
// So this is what we are changing about the real function.
$this->assertFalse(CRM_Utils_File::isDir($input));
}

ini_set('open_basedir', $originalOpenBasedir);
$this->assertTrue(chdir($old_cwd));
unlink("{$a_dir}/isDirTest/ok/ok.txt");
rmdir("{$a_dir}/isDirTest/ok");
rmdir("{$a_dir}/isDirTest");
return 'OK';
}

protected function runStaticMethodAsScript(string $class, string $method, $args = []) {
$func = new ReflectionMethod($class, $method);
$outClass = 'Wrapper' . time();
$ignoreStdErr = ';^Xdebug:;';

$inFile = $func->getFileName();
$inFileLines = explode("\n", file_get_contents($inFile));

$lines = array_merge(
[
'<' . '?php',
"class $outClass {",
],
array_slice($inFileLines, $func->getStartLine() - 1, $func->getEndLine() - $func->getStartLine() + 1),
[
'}',
sprintf('$args = %s;', var_export($args, 1)),
sprintf('return %s::%s(...$args);', $outClass, $method),
]
);
$tmpSrc = implode("\n", $lines);

$outFile = tempnam(sys_get_temp_dir(), 'test-script-') . '.php';
file_put_contents($outFile, $tmpSrc);

try {
$cmd = 'cv ev -v ' . escapeshellarg("return require \"$outFile\";");
$descriptorSpec = array(0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']);
$oldOutput = getenv('CV_OUTPUT');
putenv("CV_OUTPUT=json");
$process = proc_open($cmd, $descriptorSpec, $pipes, __DIR__);
putenv("CV_OUTPUT=$oldOutput");
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[1]);
fclose($pipes[2]);

$stderr = implode("\n", preg_grep($ignoreStdErr, explode("\n", $stderr), PREG_GREP_INVERT));
$exitCode = proc_close($process);

return [$exitCode, $stdout, $stderr];
}
finally {
unlink($outFile);
}
}

/**
Expand Down