Skip to content

Commit

Permalink
Merge pull request #51201 from nextcloud/backport/51194/stable29
Browse files Browse the repository at this point in the history
[stable29] refactor(TempManager): Simplify and unify implementations and remove legacy behavior
  • Loading branch information
sorbaugh authored Mar 4, 2025
2 parents 04ae493 + 7e8ffd3 commit d8040d7
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 87 deletions.
6 changes: 0 additions & 6 deletions build/psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3189,12 +3189,6 @@
<code><![CDATA[$tag]]></code>
</MoreSpecificImplementedParamType>
</file>
<file src="lib/private/TempManager.php">
<FalsableReturnStatement>
<code><![CDATA[false]]></code>
<code><![CDATA[false]]></code>
</FalsableReturnStatement>
</file>
<file src="lib/private/Template/CSSResourceLocator.php">
<ParamNameMismatch>
<code><![CDATA[$style]]></code>
Expand Down
78 changes: 21 additions & 57 deletions lib/private/TempManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use bantu\IniGetWrapper\IniGetWrapper;
use OCP\IConfig;
use OCP\ITempManager;
use OCP\Security\ISecureRandom;
use Psr\Log\LoggerInterface;

class TempManager implements ITempManager {
Expand All @@ -58,51 +59,25 @@ public function __construct(LoggerInterface $logger, IConfig $config, IniGetWrap
$this->tmpBaseDir = $this->getTempBaseDir();
}

/**
* Builds the filename with suffix and removes potential dangerous characters
* such as directory separators.
*
* @param string $absolutePath Absolute path to the file / folder
* @param string $postFix Postfix appended to the temporary file name, may be user controlled
* @return string
*/
private function buildFileNameWithSuffix($absolutePath, $postFix = '') {
private function generateTemporaryPath(string $postFix): string {
$secureRandom = \OCP\Server::get(ISecureRandom::class);
$absolutePath = $this->tmpBaseDir . '/' . self::TMP_PREFIX . $secureRandom->generate(32, ISecureRandom::CHAR_ALPHANUMERIC);

if ($postFix !== '') {
$postFix = '.' . ltrim($postFix, '.');
$postFix = str_replace(['\\', '/'], '', $postFix);
$absolutePath .= '-';
}

return $absolutePath . $postFix;
}

/**
* Create a temporary file and return the path
*
* @param string $postFix Postfix appended to the temporary file name
* @return string
*/
public function getTemporaryFile($postFix = '') {
if (is_writable($this->tmpBaseDir)) {
// To create an unique file and prevent the risk of race conditions
// or duplicated temporary files by other means such as collisions
// we need to create the file using `tempnam` and append a possible
// postfix to it later
$file = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
$this->current[] = $file;
public function getTemporaryFile($postFix = ''): string|false {
$path = $this->generateTemporaryPath($postFix);

// If a postfix got specified sanitize it and create a postfixed
// temporary file
if ($postFix !== '') {
$fileNameWithPostfix = $this->buildFileNameWithSuffix($file, $postFix);
touch($fileNameWithPostfix);
chmod($fileNameWithPostfix, 0600);
$this->current[] = $fileNameWithPostfix;
return $fileNameWithPostfix;
}

return $file;
} else {
$old_umask = umask(0077);
$fp = fopen($path, 'x');
umask($old_umask);
if ($fp === false) {
$this->log->warning(
'Can not create a temporary file in directory {dir}. Check it exists and has correct permissions',
[
Expand All @@ -111,30 +86,16 @@ public function getTemporaryFile($postFix = '') {
);
return false;
}
}

/**
* Create a temporary folder and return the path
*
* @param string $postFix Postfix appended to the temporary folder name
* @return string
*/
public function getTemporaryFolder($postFix = '') {
if (is_writable($this->tmpBaseDir)) {
// To create an unique directory and prevent the risk of race conditions
// or duplicated temporary files by other means such as collisions
// we need to create the file using `tempnam` and append a possible
// postfix to it later
$uniqueFileName = tempnam($this->tmpBaseDir, self::TMP_PREFIX);
$this->current[] = $uniqueFileName;
fclose($fp);
$this->current[] = $path;
return $path;
}

// Build a name without postfix
$path = $this->buildFileNameWithSuffix($uniqueFileName . '-folder', $postFix);
mkdir($path, 0700);
$this->current[] = $path;
public function getTemporaryFolder($postFix = ''): string|false {
$path = $this->generateTemporaryPath($postFix) . '/';

return $path . '/';
} else {
if (mkdir($path, 0700) === false) {
$this->log->warning(
'Can not create a temporary folder in directory {dir}. Check it exists and has correct permissions',
[
Expand All @@ -143,6 +104,9 @@ public function getTemporaryFolder($postFix = '') {
);
return false;
}

$this->current[] = $path;
return $path;
}

/**
Expand Down
12 changes: 6 additions & 6 deletions lib/public/ITempManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ interface ITempManager {
/**
* Create a temporary file and return the path
*
* @param string $postFix
* @return string
* @param string $postFix Postfix appended to the temporary file name
*
* @since 8.0.0
*/
public function getTemporaryFile($postFix = '');
public function getTemporaryFile(string $postFix = ''): string|false;

/**
* Create a temporary folder and return the path
*
* @param string $postFix
* @return string
* @param string $postFix Postfix appended to the temporary folder name
*
* @since 8.0.0
*/
public function getTemporaryFolder($postFix = '');
public function getTemporaryFolder(string $postFix = ''): string|false;

/**
* Remove the temporary files and folders generated during this request
Expand Down
25 changes: 7 additions & 18 deletions tests/lib/TempManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -155,34 +155,23 @@ public function testLogCantCreateFolder() {
$this->assertFalse($manager->getTemporaryFolder());
}

public function testBuildFileNameWithPostfix() {
public function testGenerateTemporaryPathWithPostfix(): void {
$logger = $this->createMock(LoggerInterface::class);
$tmpManager = self::invokePrivate(
$this->getManager($logger),
'buildFileNameWithSuffix',
['/tmp/myTemporaryFile', 'postfix']
'generateTemporaryPath',
['postfix']
);

$this->assertEquals('/tmp/myTemporaryFile-.postfix', $tmpManager);
$this->assertStringEndsWith('.postfix', $tmpManager);
}

public function testBuildFileNameWithoutPostfix() {
public function testGenerateTemporaryPathTraversal(): void {
$logger = $this->createMock(LoggerInterface::class);
$tmpManager = self::invokePrivate(
$this->getManager($logger),
'buildFileNameWithSuffix',
['/tmp/myTemporaryFile', '']
);

$this->assertEquals('/tmp/myTemporaryFile', $tmpManager);
}

public function testBuildFileNameWithSuffixPathTraversal() {
$logger = $this->createMock(LoggerInterface::class);
$tmpManager = self::invokePrivate(
$this->getManager($logger),
'buildFileNameWithSuffix',
['foo', '../Traversal\\../FileName']
'generateTemporaryPath',
['../Traversal\\../FileName']
);

$this->assertStringEndsNotWith('./Traversal\\../FileName', $tmpManager);
Expand Down

0 comments on commit d8040d7

Please sign in to comment.