Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[Filesystem] Changed dumpFile to allow dumping to streams #16156

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

Merged
merged 5 commits into from
Nov 9, 2015
Merged
Show file tree
Hide file tree
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
5 changes: 5 additions & 0 deletions src/Symfony/Component/Filesystem/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
CHANGELOG
=========

2.8.0
-----

* added tempnam() a stream aware version of PHP's native tempnam()

2.6.0
-----

Expand Down
68 changes: 67 additions & 1 deletion src/Symfony/Component/Filesystem/Filesystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,58 @@ 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 throw an exception on failure.
*/
public function tempnam($dir, $prefix)
{
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 (false !== $tmpFile) {
if (null !== $scheme) {
return $scheme.'://'.$tmpFile;
}

return $tmpFile;
}

throw new IOException('A temporary file could not be created.');
}

// 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);

// 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;
}

throw new IOException('A temporary file could not be created.');
}

/**
* Atomically dumps content into a file.
*
Expand All @@ -472,7 +524,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);
Expand Down Expand Up @@ -501,4 +553,18 @@ 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 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldnt this be simlified to

return 2 === count($components) ? $components : array(null, $components[0]);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems so

}
}
131 changes: 131 additions & 0 deletions src/Symfony/Component/Filesystem/Tests/FilesystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,110 @@ public function providePathsForIsAbsolutePath()
);
}

public function testTempnam()
{
$dirname = $this->workspace;

$filename = $this->filesystem->tempnam($dirname, 'foo');

$this->assertFileExists($filename);
}

public function testTempnamWithFileScheme()
{
$scheme = 'file://';
$dirname = $scheme.$this->workspace;

$filename = $this->filesystem->tempnam($dirname, 'foo');

$this->assertStringStartsWith($scheme, $filename);
$this->assertFileExists($filename);
}

public function testTempnamWithMockScheme()
{
stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream');

$scheme = 'mock://';
$dirname = $scheme.$this->workspace;

$filename = $this->filesystem->tempnam($dirname, 'foo');

$this->assertStringStartsWith($scheme, $filename);
$this->assertFileExists($filename);
}

/**
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
*/
public function testTempnamWithZlibSchemeFails()
{
$scheme = 'compress.zlib://';
$dirname = $scheme.$this->workspace;

// The compress.zlib:// stream does not support mode x: creates the file, errors "failed to open stream: operation failed" and returns false
$this->filesystem->tempnam($dirname, 'bar');

}

public function testTempnamWithPHPTempSchemeFails()
{
$scheme = 'php://temp';
$dirname = $scheme;

$filename = $this->filesystem->tempnam($dirname, 'bar');

$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()) {
$this->markTestSkipped('This test cannot run when phar.readonly is 1.');
}

$scheme = 'phar://';
$dirname = $scheme.$this->workspace;
$pharname = 'foo.phar';

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->filesystem->tempnam($dirname, $pharname.'/bar');
}

/**
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
*/
public function testTempnamWithHTTPSchemeFails()
{
$scheme = 'http://';
$dirname = $scheme.$this->workspace;

// The http:// scheme is read-only
$this->filesystem->tempnam($dirname, 'bar');
}

public function testTempnamOnUnwritableFallsBackToSysTmp()
{
$scheme = 'file://';
$dirname = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'does_not_exist';

$filename = $this->filesystem->tempnam($dirname, 'bar');

$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';
Expand Down Expand Up @@ -1000,6 +1104,33 @@ public function testDumpFileOverwritesAnExistingFile()
$this->assertSame('bar', file_get_contents($filename));
}

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';

$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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\Filesystem\Tests\Fixtures\MockStream;

/**
* Mock stream class to be used with stream_wrapper_register.
* stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream').
*/
class MockStream
{
/**
* 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)
{
return true;
}

/**
* @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 array();
}
}