From c6a774761dc75f992797eef35ae89e2c215a1dae Mon Sep 17 00:00:00 2001 From: Mark Challoner Date: Thu, 7 May 2015 19:37:53 +0100 Subject: [PATCH 1/5] [Filesystem] added tempnam() stream wrapper aware version of PHP's native tempnam() and fixed dumpFile to allow dumping to streams --- src/Symfony/Component/Filesystem/CHANGELOG.md | 5 + .../Component/Filesystem/Filesystem.php | 64 ++++- src/Symfony/Component/Filesystem/README.md | 4 + .../Filesystem/Tests/FilesystemTest.php | 134 +++++++++++ .../Tests/Fixtures/MockStream/MockStream.php | 226 ++++++++++++++++++ 5 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php diff --git a/src/Symfony/Component/Filesystem/CHANGELOG.md b/src/Symfony/Component/Filesystem/CHANGELOG.md index a4c0479f7d9a..aee6e804b0d0 100644 --- a/src/Symfony/Component/Filesystem/CHANGELOG.md +++ b/src/Symfony/Component/Filesystem/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.8.0 +----- + + * added tempnam() a stream aware version of PHP's native tempnam() + 2.6.0 ----- diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 93a1f0de4c53..b10a264118d8 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -452,6 +452,53 @@ public function isAbsolutePath($file) ); } + /** + * Creates a temporary file with support for custom stream wrappers. + * + * @param string $dir The directory where the temporary filename will be created. + * @param string $prefix The prefix of the generated temporary filename. + * Note: Windows uses only the first three characters of prefix. + * + * @return string The new temporary filename (with path), or false on failure. + */ + public function tempnam($dir, $prefix) + { + $limit = 10; + list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir); + + // If no scheme or scheme is "file" create temp file in local filesystem + if (null === $scheme || 'file' === $scheme) { + $tmpFile = tempnam($hierarchy, $prefix); + + // If tempnam failed or no scheme return the filename otherwise prepend the scheme + return false === $tmpFile || null === $scheme ? $tmpFile : $scheme.'://'.$tmpFile; + } + + // Loop until we create a valid temp file or have reached $limit attempts + for ($i = 0; $i < $limit; $i++) { + + // Create a unique filename + $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); + + // Use fopen instead of file_exists as some streams do not support stat + // Use mode 'x' to atomically check existence and create to avoid a TOCTOU vulnerability + $handle = @fopen($tmpFile, 'x'); + + // If unsuccessful restart the loop + if (false === $handle) { + continue; + } + + // Close the file if it was successfully opened + @fclose($handle); + + return $tmpFile; + + } + + return false; + } + /** * Atomically dumps content into a file. * @@ -472,7 +519,7 @@ public function dumpFile($filename, $content, $mode = 0666) throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir); } - $tmpFile = tempnam($dir, basename($filename)); + $tmpFile = $this->tempnam($dir, basename($filename)); if (false === @file_put_contents($tmpFile, $content)) { throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename); @@ -501,4 +548,19 @@ private function toIterator($files) return $files; } + + /** + * Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)). + * + * @param string $filename The filename to be parsed. + * + * @return array The filename scheme and hierarchical part + */ + private function getSchemeAndHierarchy($filename) + { + $components = explode('://', $filename, 2); + + return count($components) >= 2 ? array($components[0], $components[1]) : array(null, $components[0]); + } + } diff --git a/src/Symfony/Component/Filesystem/README.md b/src/Symfony/Component/Filesystem/README.md index df09f93dce72..466924f3b7d0 100644 --- a/src/Symfony/Component/Filesystem/README.md +++ b/src/Symfony/Component/Filesystem/README.md @@ -30,11 +30,15 @@ $filesystem->rename($origin, $target); $filesystem->symlink($originDir, $targetDir, $copyOnWindows = false); +$filesystem->tempnam($dir, $prefix); + $filesystem->makePathRelative($endPath, $startPath); $filesystem->mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()); $filesystem->isAbsolutePath($file); + +$filesystem->dumpFile($file, $content); ``` Resources diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 45254c3c712e..7eceacc27302 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Filesystem\Tests; +use Phar; /** * Test class for Filesystem. */ @@ -946,6 +947,116 @@ public function providePathsForIsAbsolutePath() ); } + public function testTempnam() + { + $dirname = $this->workspace; + + $filename = $this->filesystem->tempnam($dirname, 'foo'); + + $this->assertNotFalse($filename); + $this->assertFileExists($filename); + } + + public function testTempnamWithFileScheme() + { + $scheme = 'file://'; + $dirname = $scheme.$this->workspace; + + $filename = $this->filesystem->tempnam($dirname, 'foo'); + + $this->assertNotFalse($filename); + $this->assertStringStartsWith($scheme, $filename); + $this->assertFileExists($filename); + } + + public function testTempnamWithMockScheme() + { + // We avoid autoloading via ClassLoader as stream_wrapper_register creates the object + if (!@include __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'MockStream'.DIRECTORY_SEPARATOR.'MockStream.php') { + $this->markTestSkipped('Unable to load mock:// stream.'); + } + + stream_wrapper_register('mock', 'MockStream\MockStream'); + + $scheme = 'mock://'; + $dirname = $scheme.$this->workspace; + + $filename = $this->filesystem->tempnam($dirname, 'foo'); + + $this->assertNotFalse($filename); + $this->assertStringStartsWith($scheme, $filename); + $this->assertFileExists($filename); + } + + public function testTempnamWithZlibSchemeFails() + { + $scheme = 'compress.zlib://'; + $dirname = $scheme.$this->workspace; + + $filename = $this->filesystem->tempnam($dirname, 'bar'); + + // The compress.zlib:// stream does not support mode x: creates the file, errors "failed to open stream: operation failed" and returns false + $this->assertFalse($filename); + } + + public function testTempnamWithPHPTempSchemeFails() + { + $scheme = 'php://temp'; + $dirname = $scheme; + + $filename = $this->filesystem->tempnam($dirname, 'bar'); + + $this->assertNotFalse($filename); + $this->assertStringStartsWith($scheme, $filename); + + // The php://temp stream deletes the file after close + $this->assertFileNotExists($filename); + } + + public function testTempnamWithPharSchemeFails() + { + // Skip test if Phar disabled phar.readonly must be 0 in php.ini + if (!Phar::canWrite()) { + $this->markTestSkipped('This test cannot run when phar.readonly is 1.'); + } + + $scheme = 'phar://'; + $dirname = $scheme.$this->workspace; + $pharname = 'foo.phar'; + + $p = new Phar($this->workspace.'/'.$pharname, 0, $pharname); + $filename = $this->filesystem->tempnam($dirname, $pharname.'/bar'); + + // The phar:// stream does not support mode x: fails to create file, errors "failed to open stream: phar error: "$filename" is not a file in phar "$pharname"" and returns false + $this->assertFalse($filename); + } + + public function testTempnamWithHTTPSchemeFails() + { + $scheme = 'http://'; + $dirname = $scheme.$this->workspace; + + $filename = $this->filesystem->tempnam($dirname, 'bar'); + + // The http:// scheme is read-only + $this->assertFalse($filename); + } + + public function testTempnamOnUnwritableFallsBackToSysTmp() + { + $scheme = 'file://'; + $dirname = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'does_not_exist'; + + $filename = $this->filesystem->tempnam($dirname, 'bar'); + + $this->assertNotFalse($filename); + $this->assertStringStartsWith(rtrim($scheme.sys_get_temp_dir(), DIRECTORY_SEPARATOR), $filename); + $this->assertFileExists($filename); + + // Tear down + unlink($filename); + } + public function testDumpFile() { $filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt'; @@ -1000,6 +1111,29 @@ public function testDumpFileOverwritesAnExistingFile() $this->assertSame('bar', file_get_contents($filename)); } + public function testDumpFileWithFileScheme() + { + $scheme = 'file://'; + $filename = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt'; + + $this->filesystem->dumpFile($filename, 'bar', null); + + $this->assertFileExists($filename); + $this->assertSame('bar', file_get_contents($filename)); + } + + public function testDumpFileWithZlibScheme() + { + $scheme = 'compress.zlib://'; + $filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt'; + + $this->filesystem->dumpFile($filename, 'bar', null); + + // Zlib stat uses file:// wrapper so remove scheme + $this->assertFileExists(str_replace($scheme, '', $filename)); + $this->assertSame('bar', file_get_contents($filename)); + } + public function testCopyShouldKeepExecutionPermission() { $this->markAsSkippedIfChmodIsMissing(); diff --git a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php new file mode 100644 index 000000000000..ac1a840d2354 --- /dev/null +++ b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php @@ -0,0 +1,226 @@ +. + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * This class is based on VariableStream from the PHP Manual, which is licenced + * under Creative Commons Attribution 3.0 Licence copyright (c) the PHP + * Documentation Group + * + * @url http://php.net/manual/en/stream.streamwrapper.example-1.php + * @url http://php.net/license/ + * @url http://creativecommons.org/licenses/by/3.0/legalcode + */ +namespace MockStream; + +/** + * Mock stream class to be used with stream_wrapper_register. + * + * stream_wrapper_register('mock', 'MockStream\MockStream') + */ +class MockStream { + private $str_overloaded; + private $content; + private $position; + private $atime; + private $mtime; + private $ctime; + private $path; + + /** + * Opens file or URL. + * + * @param string $path Specifies the URL that was passed to the original function + * @param string $mode The mode used to open the file, as detailed for fopen() + * @param int $options Holds additional flags set by the streams API + * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, opened_path should be set to the full path of the file/resource that was actually opened + * + * @return bool + */ + public function stream_open($path, $mode, $options, &$opened_path) { + // Is mbstring.func_overload applied to string functions (bit 2 set) + $this->str_overloaded = (bool) (ini_get('mbstring.func_overload') & (1 << 2)); + $this->path = $path; + $this->content = ''; + $this->position = 0; + $this->atime = 0; + $this->mtime = 0; + + return true; + } + + /** + * Read from stream. + * + * @param int $count How many bytes of data from the current position should be returned + * + * @return string The data + */ + public function stream_read($count) { + $ret = $this->substr($this->varname, $this->position, $count); + $this->position += $this->strlen($ret); + $this->atime = time(); + + return $ret; + } + + /** + * Write to stream. + * + * @param string $data Data to write to the stream + * + * @return int Number of bytes that were successfully stored, or 0 if none could be stored + */ + public function stream_write($data) { + $left = $this->substr($this->content, 0, $this->position); + $right = $this->substr($this->content, $this->position + $this->strlen($data)); + $this->content = $left.$data.$right; + $this->position += $this->strlen($data); + $this->mtime = time(); + $this->ctime = time(); + + return $this->strlen($data); + } + + /** + * Retrieve the current position of a stream. + * + * @return int The current position of the stream + */ + public function stream_tell() { + return $this->position; + } + + /** + * Tests for end-of-file on a file pointer. + * + * @return bool Return true if the read/write position is at the end of the stream and if no more data is available to be read, or false otherwise + */ + public function stream_eof() { + return $this->position >= $this->strlen($this->content); + } + + /** + * Seeks to specific location in a stream. + * + * @param string $offset The stream offset to seek to + * @param int $whence Set position based on value + * + * @return bool Return true if the position was updated, false otherwise + */ + public function stream_seek($offset, $whence) { + switch ($whence) { + case SEEK_SET: + if ($offset < $this->strlen($this->content) && 0 <= $offset) { + $this->position = $offset; + + return true; + } + break; + + case SEEK_CUR: + if (0 <= $offset) { + $this->position += $offset; + + return true; + } + break; + + case SEEK_END: + if (0 <= $this->strlen($this->content) + $offset) { + $this->position = $this->strlen($this->content) + $offset; + + return true; + } + break; + } + + return false; + } + + /** + * Change stream options, only touch is supported. + * + * @param string $path The file path or URL to set metadata + * @param array $option + * @param array $value Additional arguments for the option + * + * @return bool Return true on success or fale on failure or if option is not implemented + */ + public function stream_metadata($path, $option, $value) { + if (STREAM_META_TOUCH === $option) { + $now = array_key_exists(0, $value) ? $value[0] : time(); + $this->atime = array_key_exists(1, $value) ? $value[1] : $now; + $this->mtime = $now; + $this->ctime = $now; + + return true; + } + + return false; + } + + /** + * Retrieve information about a stream. + * + * @return array Stream stats + */ + public function stream_stat() { + return array( + 'dev' => 0, + 'ino' => 0, + 'mode' => 33188, // 100644 + 'nlink' => 1, + 'uid' => 0, + 'gid' => 0, + 'rdev' => 0, + 'size' => $this->strlen($this->content), + 'atime' => $this->atime, + 'mtime' => $this->mtime, + 'ctime' => $this->ctime, + 'blksize' => 4096, + 'blocks' => 8, + ); + } + + /** + * Retrieve information about a url, added as called by PHP's builtin functions. + * + * @param string $path The file path or URL to stat + * @param array $flags Holds additional flags set by the streams API + * + * @return array File stats + */ + public function url_stat($path, $flags) { + return $this->stream_stat(); + } + + /** + * Returns the number of bytes of the given string even when strlen is overloaded to mb_strlen. + * + * @param string $string The string being measured for bytes + * + * @return int The number of bytes in the string on success, and 0 if the string is empty + */ + protected function strlen($string) { + return function_exists('mb_strlen') && $this->str_overloaded ? mb_strlen($string, '8bit') : strlen($string); + } + + /** + * Returns the portion of string specified by the start and length parameters even when substr is overloaded to mb_substr. + * + * @param string $string The input string which must be one character or longer + * @param start $int Starting position in bytes + * @param length $int Length in bytes which if omitted or NULL is passed, extracts all bytes to the end of the string + * + * @return string + */ + protected function substr($string, $start, $length = null) { + return function_exists('mb_substr') && $this->str_overloaded ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); + } + +} From 247266cdef84e6352173f564de06151027550c8c Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Wed, 7 Oct 2015 00:13:52 +0200 Subject: [PATCH 2/5] Update coding standard for MockStream --- .../Component/Filesystem/Filesystem.php | 16 +++- src/Symfony/Component/Filesystem/README.md | 4 - .../Tests/Fixtures/MockStream/MockStream.php | 91 ++++++++++--------- 3 files changed, 62 insertions(+), 49 deletions(-) diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index b10a264118d8..f9e462a6bd77 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -468,14 +468,24 @@ public function tempnam($dir, $prefix) // If no scheme or scheme is "file" create temp file in local filesystem if (null === $scheme || 'file' === $scheme) { + $tmpFile = tempnam($hierarchy, $prefix); // If tempnam failed or no scheme return the filename otherwise prepend the scheme - return false === $tmpFile || null === $scheme ? $tmpFile : $scheme.'://'.$tmpFile; + + if (null !== $scheme) { + return $scheme.'://'.$tmpFile; + } + + if (false !== $tmpFile) { + return $tmpFile; + } + + throw new IOException('A temporary file could not be created'); } // Loop until we create a valid temp file or have reached $limit attempts - for ($i = 0; $i < $limit; $i++) { + for ($i = 0; $i < $limit; ++$i) { // Create a unique filename $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); @@ -560,7 +570,7 @@ private function getSchemeAndHierarchy($filename) { $components = explode('://', $filename, 2); - return count($components) >= 2 ? array($components[0], $components[1]) : array(null, $components[0]); + return count($components) === 2 ? array($components[0], $components[1]) : array(null, $components[0]); } } diff --git a/src/Symfony/Component/Filesystem/README.md b/src/Symfony/Component/Filesystem/README.md index 466924f3b7d0..df09f93dce72 100644 --- a/src/Symfony/Component/Filesystem/README.md +++ b/src/Symfony/Component/Filesystem/README.md @@ -30,15 +30,11 @@ $filesystem->rename($origin, $target); $filesystem->symlink($originDir, $targetDir, $copyOnWindows = false); -$filesystem->tempnam($dir, $prefix); - $filesystem->makePathRelative($endPath, $startPath); $filesystem->mirror($originDir, $targetDir, \Traversable $iterator = null, $options = array()); $filesystem->isAbsolutePath($file); - -$filesystem->dumpFile($file, $content); ``` Resources diff --git a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php index ac1a840d2354..7dabbfd80522 100644 --- a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php +++ b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php @@ -1,29 +1,23 @@ . + * + * (c) Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. - * - * This class is based on VariableStream from the PHP Manual, which is licenced - * under Creative Commons Attribution 3.0 Licence copyright (c) the PHP - * Documentation Group - * - * @url http://php.net/manual/en/stream.streamwrapper.example-1.php - * @url http://php.net/license/ - * @url http://creativecommons.org/licenses/by/3.0/legalcode */ + namespace MockStream; /** * Mock stream class to be used with stream_wrapper_register. - * - * stream_wrapper_register('mock', 'MockStream\MockStream') + * stream_wrapper_register('mock', 'MockStream\MockStream'). */ -class MockStream { - private $str_overloaded; +class MockStream +{ + private $strOverloaded; private $content; private $position; private $atime; @@ -37,13 +31,16 @@ class MockStream { * @param string $path Specifies the URL that was passed to the original function * @param string $mode The mode used to open the file, as detailed for fopen() * @param int $options Holds additional flags set by the streams API - * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, opened_path should be set to the full path of the file/resource that was actually opened + * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, + * opened_path should be set to the full path of the file/resource that was actually + * opened * * @return bool */ - public function stream_open($path, $mode, $options, &$opened_path) { + public function stream_open($path, $mode, $options, &$opened_path) + { // Is mbstring.func_overload applied to string functions (bit 2 set) - $this->str_overloaded = (bool) (ini_get('mbstring.func_overload') & (1 << 2)); + $this->strOverloaded = (bool) (ini_get('mbstring.func_overload') & (1 << 2)); $this->path = $path; $this->content = ''; $this->position = 0; @@ -60,8 +57,9 @@ public function stream_open($path, $mode, $options, &$opened_path) { * * @return string The data */ - public function stream_read($count) { - $ret = $this->substr($this->varname, $this->position, $count); + public function stream_read($count) + { + $ret = $this->substr($this->content, $this->position, $count); $this->position += $this->strlen($ret); $this->atime = time(); @@ -75,7 +73,8 @@ public function stream_read($count) { * * @return int Number of bytes that were successfully stored, or 0 if none could be stored */ - public function stream_write($data) { + public function stream_write($data) + { $left = $this->substr($this->content, 0, $this->position); $right = $this->substr($this->content, $this->position + $this->strlen($data)); $this->content = $left.$data.$right; @@ -91,50 +90,54 @@ public function stream_write($data) { * * @return int The current position of the stream */ - public function stream_tell() { + public function stream_tell() + { return $this->position; } /** * Tests for end-of-file on a file pointer. * - * @return bool Return true if the read/write position is at the end of the stream and if no more data is available to be read, or false otherwise + * @return bool Return true if the read/write position is at the end of the stream and if no more data is available + * to be read, or false otherwise */ - public function stream_eof() { + public function stream_eof() + { return $this->position >= $this->strlen($this->content); } /** * Seeks to specific location in a stream. * - * @param string $offset The stream offset to seek to - * @param int $whence Set position based on value + * @param int $offset The stream offset to seek to + * @param int $whence Set position based on value * * @return bool Return true if the position was updated, false otherwise */ - public function stream_seek($offset, $whence) { + public function stream_seek($offset, $whence) + { switch ($whence) { case SEEK_SET: if ($offset < $this->strlen($this->content) && 0 <= $offset) { - $this->position = $offset; + $this->position = $offset; - return true; + return true; } break; case SEEK_CUR: if (0 <= $offset) { - $this->position += $offset; + $this->position += $offset; - return true; + return true; } break; case SEEK_END: if (0 <= $this->strlen($this->content) + $offset) { - $this->position = $this->strlen($this->content) + $offset; + $this->position = $this->strlen($this->content) + $offset; - return true; + return true; } break; } @@ -151,7 +154,8 @@ public function stream_seek($offset, $whence) { * * @return bool Return true on success or fale on failure or if option is not implemented */ - public function stream_metadata($path, $option, $value) { + public function stream_metadata($path, $option, $value) + { if (STREAM_META_TOUCH === $option) { $now = array_key_exists(0, $value) ? $value[0] : time(); $this->atime = array_key_exists(1, $value) ? $value[1] : $now; @@ -169,7 +173,8 @@ public function stream_metadata($path, $option, $value) { * * @return array Stream stats */ - public function stream_stat() { + public function stream_stat() + { return array( 'dev' => 0, 'ino' => 0, @@ -195,7 +200,8 @@ public function stream_stat() { * * @return array File stats */ - public function url_stat($path, $flags) { + public function url_stat($path, $flags) + { return $this->stream_stat(); } @@ -206,21 +212,22 @@ public function url_stat($path, $flags) { * * @return int The number of bytes in the string on success, and 0 if the string is empty */ - protected function strlen($string) { - return function_exists('mb_strlen') && $this->str_overloaded ? mb_strlen($string, '8bit') : strlen($string); + protected function strlen($string) + { + return function_exists('mb_strlen') && $this->strOverloaded ? mb_strlen($string, '8bit') : strlen($string); } /** * Returns the portion of string specified by the start and length parameters even when substr is overloaded to mb_substr. * * @param string $string The input string which must be one character or longer - * @param start $int Starting position in bytes - * @param length $int Length in bytes which if omitted or NULL is passed, extracts all bytes to the end of the string + * @param int $start Starting position in bytes + * @param int $length Length in bytes which if omitted or NULL is passed, extracts all bytes to the end of the string * * @return string */ - protected function substr($string, $start, $length = null) { - return function_exists('mb_substr') && $this->str_overloaded ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); + protected function substr($string, $start, $length = null) + { + return function_exists('mb_substr') && $this->strOverloaded ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); } - } From 61a3afd829e3b76e39a18dca7635921a9148c51b Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Thu, 8 Oct 2015 12:58:45 +0200 Subject: [PATCH 3/5] Removed unused logic in MockStream --- .../Component/Filesystem/Filesystem.php | 26 +-- .../Filesystem/Tests/FilesystemTest.php | 39 ++-- .../Tests/Fixtures/MockStream/MockStream.php | 195 +----------------- 3 files changed, 30 insertions(+), 230 deletions(-) diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index f9e462a6bd77..194771ea083d 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -459,34 +459,30 @@ public function isAbsolutePath($file) * @param string $prefix The prefix of the generated temporary filename. * Note: Windows uses only the first three characters of prefix. * - * @return string The new temporary filename (with path), or false on failure. + * @return string The new temporary filename (with path), or throw an exception on failure. */ public function tempnam($dir, $prefix) { - $limit = 10; list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir); // If no scheme or scheme is "file" create temp file in local filesystem if (null === $scheme || 'file' === $scheme) { - $tmpFile = tempnam($hierarchy, $prefix); // If tempnam failed or no scheme return the filename otherwise prepend the scheme - - if (null !== $scheme) { - return $scheme.'://'.$tmpFile; - } - if (false !== $tmpFile) { + if (null !== $scheme) { + return $scheme.'://'.$tmpFile; + } + return $tmpFile; } - throw new IOException('A temporary file could not be created'); + throw new IOException('A temporary file could not be created.'); } - // Loop until we create a valid temp file or have reached $limit attempts - for ($i = 0; $i < $limit; ++$i) { - + // Loop until we create a valid temp file or have reached 10 attempts + for ($i = 0; $i < 10; ++$i) { // Create a unique filename $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); @@ -503,10 +499,9 @@ public function tempnam($dir, $prefix) @fclose($handle); return $tmpFile; - } - return false; + throw new IOException('A temporary file could not be created.'); } /** @@ -570,7 +565,6 @@ private function getSchemeAndHierarchy($filename) { $components = explode('://', $filename, 2); - return count($components) === 2 ? array($components[0], $components[1]) : array(null, $components[0]); + return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]); } - } diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index 7eceacc27302..b9ad6c26f5b6 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\Filesystem\Tests; -use Phar; /** * Test class for Filesystem. */ @@ -953,7 +952,6 @@ public function testTempnam() $filename = $this->filesystem->tempnam($dirname, 'foo'); - $this->assertNotFalse($filename); $this->assertFileExists($filename); } @@ -964,39 +962,34 @@ public function testTempnamWithFileScheme() $filename = $this->filesystem->tempnam($dirname, 'foo'); - $this->assertNotFalse($filename); $this->assertStringStartsWith($scheme, $filename); $this->assertFileExists($filename); } public function testTempnamWithMockScheme() { - // We avoid autoloading via ClassLoader as stream_wrapper_register creates the object - if (!@include __DIR__.DIRECTORY_SEPARATOR.'Fixtures'.DIRECTORY_SEPARATOR.'MockStream'.DIRECTORY_SEPARATOR.'MockStream.php') { - $this->markTestSkipped('Unable to load mock:// stream.'); - } - - stream_wrapper_register('mock', 'MockStream\MockStream'); + stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream'); $scheme = 'mock://'; $dirname = $scheme.$this->workspace; $filename = $this->filesystem->tempnam($dirname, 'foo'); - $this->assertNotFalse($filename); $this->assertStringStartsWith($scheme, $filename); $this->assertFileExists($filename); } + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ public function testTempnamWithZlibSchemeFails() { $scheme = 'compress.zlib://'; $dirname = $scheme.$this->workspace; - $filename = $this->filesystem->tempnam($dirname, 'bar'); - // The compress.zlib:// stream does not support mode x: creates the file, errors "failed to open stream: operation failed" and returns false - $this->assertFalse($filename); + $this->filesystem->tempnam($dirname, 'bar'); + } public function testTempnamWithPHPTempSchemeFails() @@ -1006,17 +999,19 @@ public function testTempnamWithPHPTempSchemeFails() $filename = $this->filesystem->tempnam($dirname, 'bar'); - $this->assertNotFalse($filename); $this->assertStringStartsWith($scheme, $filename); // The php://temp stream deletes the file after close $this->assertFileNotExists($filename); } + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ public function testTempnamWithPharSchemeFails() { // Skip test if Phar disabled phar.readonly must be 0 in php.ini - if (!Phar::canWrite()) { + if (!\Phar::canWrite()) { $this->markTestSkipped('This test cannot run when phar.readonly is 1.'); } @@ -1024,22 +1019,21 @@ public function testTempnamWithPharSchemeFails() $dirname = $scheme.$this->workspace; $pharname = 'foo.phar'; - $p = new Phar($this->workspace.'/'.$pharname, 0, $pharname); - $filename = $this->filesystem->tempnam($dirname, $pharname.'/bar'); - + new \Phar($this->workspace.'/'.$pharname, 0, $pharname); // The phar:// stream does not support mode x: fails to create file, errors "failed to open stream: phar error: "$filename" is not a file in phar "$pharname"" and returns false - $this->assertFalse($filename); + $this->filesystem->tempnam($dirname, $pharname.'/bar'); } + /** + * @expectedException \Symfony\Component\Filesystem\Exception\IOException + */ public function testTempnamWithHTTPSchemeFails() { $scheme = 'http://'; $dirname = $scheme.$this->workspace; - $filename = $this->filesystem->tempnam($dirname, 'bar'); - // The http:// scheme is read-only - $this->assertFalse($filename); + $this->filesystem->tempnam($dirname, 'bar'); } public function testTempnamOnUnwritableFallsBackToSysTmp() @@ -1049,7 +1043,6 @@ public function testTempnamOnUnwritableFallsBackToSysTmp() $filename = $this->filesystem->tempnam($dirname, 'bar'); - $this->assertNotFalse($filename); $this->assertStringStartsWith(rtrim($scheme.sys_get_temp_dir(), DIRECTORY_SEPARATOR), $filename); $this->assertFileExists($filename); diff --git a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php index 7dabbfd80522..f14420fb60a1 100644 --- a/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php +++ b/src/Symfony/Component/Filesystem/Tests/Fixtures/MockStream/MockStream.php @@ -9,22 +9,14 @@ * file that was distributed with this source code. */ -namespace MockStream; +namespace Symfony\Component\Filesystem\Tests\Fixtures\MockStream; /** * Mock stream class to be used with stream_wrapper_register. - * stream_wrapper_register('mock', 'MockStream\MockStream'). + * stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream'). */ class MockStream { - private $strOverloaded; - private $content; - private $position; - private $atime; - private $mtime; - private $ctime; - private $path; - /** * Opens file or URL. * @@ -32,169 +24,16 @@ class MockStream * @param string $mode The mode used to open the file, as detailed for fopen() * @param int $options Holds additional flags set by the streams API * @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options, - * opened_path should be set to the full path of the file/resource that was actually - * opened + * opened_path should be set to the full path of the file/resource that was actually opened * * @return bool */ public function stream_open($path, $mode, $options, &$opened_path) { - // Is mbstring.func_overload applied to string functions (bit 2 set) - $this->strOverloaded = (bool) (ini_get('mbstring.func_overload') & (1 << 2)); - $this->path = $path; - $this->content = ''; - $this->position = 0; - $this->atime = 0; - $this->mtime = 0; - return true; } /** - * Read from stream. - * - * @param int $count How many bytes of data from the current position should be returned - * - * @return string The data - */ - public function stream_read($count) - { - $ret = $this->substr($this->content, $this->position, $count); - $this->position += $this->strlen($ret); - $this->atime = time(); - - return $ret; - } - - /** - * Write to stream. - * - * @param string $data Data to write to the stream - * - * @return int Number of bytes that were successfully stored, or 0 if none could be stored - */ - public function stream_write($data) - { - $left = $this->substr($this->content, 0, $this->position); - $right = $this->substr($this->content, $this->position + $this->strlen($data)); - $this->content = $left.$data.$right; - $this->position += $this->strlen($data); - $this->mtime = time(); - $this->ctime = time(); - - return $this->strlen($data); - } - - /** - * Retrieve the current position of a stream. - * - * @return int The current position of the stream - */ - public function stream_tell() - { - return $this->position; - } - - /** - * Tests for end-of-file on a file pointer. - * - * @return bool Return true if the read/write position is at the end of the stream and if no more data is available - * to be read, or false otherwise - */ - public function stream_eof() - { - return $this->position >= $this->strlen($this->content); - } - - /** - * Seeks to specific location in a stream. - * - * @param int $offset The stream offset to seek to - * @param int $whence Set position based on value - * - * @return bool Return true if the position was updated, false otherwise - */ - public function stream_seek($offset, $whence) - { - switch ($whence) { - case SEEK_SET: - if ($offset < $this->strlen($this->content) && 0 <= $offset) { - $this->position = $offset; - - return true; - } - break; - - case SEEK_CUR: - if (0 <= $offset) { - $this->position += $offset; - - return true; - } - break; - - case SEEK_END: - if (0 <= $this->strlen($this->content) + $offset) { - $this->position = $this->strlen($this->content) + $offset; - - return true; - } - break; - } - - return false; - } - - /** - * Change stream options, only touch is supported. - * - * @param string $path The file path or URL to set metadata - * @param array $option - * @param array $value Additional arguments for the option - * - * @return bool Return true on success or fale on failure or if option is not implemented - */ - public function stream_metadata($path, $option, $value) - { - if (STREAM_META_TOUCH === $option) { - $now = array_key_exists(0, $value) ? $value[0] : time(); - $this->atime = array_key_exists(1, $value) ? $value[1] : $now; - $this->mtime = $now; - $this->ctime = $now; - - return true; - } - - return false; - } - - /** - * Retrieve information about a stream. - * - * @return array Stream stats - */ - public function stream_stat() - { - return array( - 'dev' => 0, - 'ino' => 0, - 'mode' => 33188, // 100644 - 'nlink' => 1, - 'uid' => 0, - 'gid' => 0, - 'rdev' => 0, - 'size' => $this->strlen($this->content), - 'atime' => $this->atime, - 'mtime' => $this->mtime, - 'ctime' => $this->ctime, - 'blksize' => 4096, - 'blocks' => 8, - ); - } - - /** - * Retrieve information about a url, added as called by PHP's builtin functions. - * * @param string $path The file path or URL to stat * @param array $flags Holds additional flags set by the streams API * @@ -202,32 +41,6 @@ public function stream_stat() */ public function url_stat($path, $flags) { - return $this->stream_stat(); - } - - /** - * Returns the number of bytes of the given string even when strlen is overloaded to mb_strlen. - * - * @param string $string The string being measured for bytes - * - * @return int The number of bytes in the string on success, and 0 if the string is empty - */ - protected function strlen($string) - { - return function_exists('mb_strlen') && $this->strOverloaded ? mb_strlen($string, '8bit') : strlen($string); - } - - /** - * Returns the portion of string specified by the start and length parameters even when substr is overloaded to mb_substr. - * - * @param string $string The input string which must be one character or longer - * @param int $start Starting position in bytes - * @param int $length Length in bytes which if omitted or NULL is passed, extracts all bytes to the end of the string - * - * @return string - */ - protected function substr($string, $start, $length = null) - { - return function_exists('mb_substr') && $this->strOverloaded ? mb_substr($string, $start, $length, '8bit') : substr($string, $start, $length); + return array(); } } From a17aa5e091585ef0599e941a1cbc0d786833d85b Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Mon, 12 Oct 2015 17:04:38 +0200 Subject: [PATCH 4/5] Fixed failing test for HHVM --- src/Symfony/Component/Filesystem/Filesystem.php | 2 +- src/Symfony/Component/Filesystem/Tests/FilesystemTest.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index 194771ea083d..ff2a66746cd7 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -565,6 +565,6 @@ private function getSchemeAndHierarchy($filename) { $components = explode('://', $filename, 2); - return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]); + return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]); } } diff --git a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php index b9ad6c26f5b6..ea38e17df567 100644 --- a/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php +++ b/src/Symfony/Component/Filesystem/Tests/FilesystemTest.php @@ -1047,7 +1047,7 @@ public function testTempnamOnUnwritableFallsBackToSysTmp() $this->assertFileExists($filename); // Tear down - unlink($filename); + @unlink($filename); } public function testDumpFile() @@ -1106,6 +1106,10 @@ public function testDumpFileOverwritesAnExistingFile() public function testDumpFileWithFileScheme() { + if (defined('HHVM_VERSION')) { + $this->markTestSkipped('HHVM does not handle the file:// scheme correctly'); + } + $scheme = 'file://'; $filename = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt'; From 5ca7dee2fd024681da037b9a3ec3f3c8c2c34f8c Mon Sep 17 00:00:00 2001 From: Pierre du Plessis Date: Mon, 19 Oct 2015 10:45:30 +0200 Subject: [PATCH 5/5] Fix mode --- src/Symfony/Component/Filesystem/Filesystem.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Filesystem/Filesystem.php b/src/Symfony/Component/Filesystem/Filesystem.php index ff2a66746cd7..7b19706e6ced 100644 --- a/src/Symfony/Component/Filesystem/Filesystem.php +++ b/src/Symfony/Component/Filesystem/Filesystem.php @@ -487,8 +487,8 @@ public function tempnam($dir, $prefix) $tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true); // Use fopen instead of file_exists as some streams do not support stat - // Use mode 'x' to atomically check existence and create to avoid a TOCTOU vulnerability - $handle = @fopen($tmpFile, 'x'); + // Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability + $handle = @fopen($tmpFile, 'x+'); // If unsuccessful restart the loop if (false === $handle) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy