Skip to content

Commit 0fc513e

Browse files
committed
feature #16156 [Filesystem] Changed dumpFile to allow dumping to streams (markchalloner, pierredup)
This PR was merged into the 2.8 branch. Discussion ---------- [Filesystem] Changed dumpFile to allow dumping to streams | Q | A | ------------- | --- | Bug fix? | yes | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #10018 | License | MIT | Doc PR | symfony/symfony-docs#5393 This is a follow-up of #14970 with comments addressed Commits ------- 5ca7dee Fix mode a17aa5e Fixed failing test for HHVM 61a3afd Removed unused logic in MockStream 247266c Update coding standard for MockStream c6a7747 [Filesystem] added tempnam() stream wrapper aware version of PHP's native tempnam() and fixed dumpFile to allow dumping to streams
2 parents 456e558 + 5ca7dee commit 0fc513e

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

src/Symfony/Component/Filesystem/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
2.8.0
5+
-----
6+
7+
* added tempnam() a stream aware version of PHP's native tempnam()
8+
49
2.6.0
510
-----
611

src/Symfony/Component/Filesystem/Filesystem.php

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,58 @@ public function isAbsolutePath($file)
452452
);
453453
}
454454

455+
/**
456+
* Creates a temporary file with support for custom stream wrappers.
457+
*
458+
* @param string $dir The directory where the temporary filename will be created.
459+
* @param string $prefix The prefix of the generated temporary filename.
460+
* Note: Windows uses only the first three characters of prefix.
461+
*
462+
* @return string The new temporary filename (with path), or throw an exception on failure.
463+
*/
464+
public function tempnam($dir, $prefix)
465+
{
466+
list($scheme, $hierarchy) = $this->getSchemeAndHierarchy($dir);
467+
468+
// If no scheme or scheme is "file" create temp file in local filesystem
469+
if (null === $scheme || 'file' === $scheme) {
470+
$tmpFile = tempnam($hierarchy, $prefix);
471+
472+
// If tempnam failed or no scheme return the filename otherwise prepend the scheme
473+
if (false !== $tmpFile) {
474+
if (null !== $scheme) {
475+
return $scheme.'://'.$tmpFile;
476+
}
477+
478+
return $tmpFile;
479+
}
480+
481+
throw new IOException('A temporary file could not be created.');
482+
}
483+
484+
// Loop until we create a valid temp file or have reached 10 attempts
485+
for ($i = 0; $i < 10; ++$i) {
486+
// Create a unique filename
487+
$tmpFile = $dir.'/'.$prefix.uniqid(mt_rand(), true);
488+
489+
// Use fopen instead of file_exists as some streams do not support stat
490+
// Use mode 'x+' to atomically check existence and create to avoid a TOCTOU vulnerability
491+
$handle = @fopen($tmpFile, 'x+');
492+
493+
// If unsuccessful restart the loop
494+
if (false === $handle) {
495+
continue;
496+
}
497+
498+
// Close the file if it was successfully opened
499+
@fclose($handle);
500+
501+
return $tmpFile;
502+
}
503+
504+
throw new IOException('A temporary file could not be created.');
505+
}
506+
455507
/**
456508
* Atomically dumps content into a file.
457509
*
@@ -472,7 +524,7 @@ public function dumpFile($filename, $content, $mode = 0666)
472524
throw new IOException(sprintf('Unable to write to the "%s" directory.', $dir), 0, null, $dir);
473525
}
474526

475-
$tmpFile = tempnam($dir, basename($filename));
527+
$tmpFile = $this->tempnam($dir, basename($filename));
476528

477529
if (false === @file_put_contents($tmpFile, $content)) {
478530
throw new IOException(sprintf('Failed to write file "%s".', $filename), 0, null, $filename);
@@ -501,4 +553,18 @@ private function toIterator($files)
501553

502554
return $files;
503555
}
556+
557+
/**
558+
* Gets a 2-tuple of scheme (may be null) and hierarchical part of a filename (e.g. file:///tmp -> array(file, tmp)).
559+
*
560+
* @param string $filename The filename to be parsed.
561+
*
562+
* @return array The filename scheme and hierarchical part
563+
*/
564+
private function getSchemeAndHierarchy($filename)
565+
{
566+
$components = explode('://', $filename, 2);
567+
568+
return 2 === count($components) ? array($components[0], $components[1]) : array(null, $components[0]);
569+
}
504570
}

src/Symfony/Component/Filesystem/Tests/FilesystemTest.php

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,110 @@ public function providePathsForIsAbsolutePath()
946946
);
947947
}
948948

949+
public function testTempnam()
950+
{
951+
$dirname = $this->workspace;
952+
953+
$filename = $this->filesystem->tempnam($dirname, 'foo');
954+
955+
$this->assertFileExists($filename);
956+
}
957+
958+
public function testTempnamWithFileScheme()
959+
{
960+
$scheme = 'file://';
961+
$dirname = $scheme.$this->workspace;
962+
963+
$filename = $this->filesystem->tempnam($dirname, 'foo');
964+
965+
$this->assertStringStartsWith($scheme, $filename);
966+
$this->assertFileExists($filename);
967+
}
968+
969+
public function testTempnamWithMockScheme()
970+
{
971+
stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream');
972+
973+
$scheme = 'mock://';
974+
$dirname = $scheme.$this->workspace;
975+
976+
$filename = $this->filesystem->tempnam($dirname, 'foo');
977+
978+
$this->assertStringStartsWith($scheme, $filename);
979+
$this->assertFileExists($filename);
980+
}
981+
982+
/**
983+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
984+
*/
985+
public function testTempnamWithZlibSchemeFails()
986+
{
987+
$scheme = 'compress.zlib://';
988+
$dirname = $scheme.$this->workspace;
989+
990+
// The compress.zlib:// stream does not support mode x: creates the file, errors "failed to open stream: operation failed" and returns false
991+
$this->filesystem->tempnam($dirname, 'bar');
992+
993+
}
994+
995+
public function testTempnamWithPHPTempSchemeFails()
996+
{
997+
$scheme = 'php://temp';
998+
$dirname = $scheme;
999+
1000+
$filename = $this->filesystem->tempnam($dirname, 'bar');
1001+
1002+
$this->assertStringStartsWith($scheme, $filename);
1003+
1004+
// The php://temp stream deletes the file after close
1005+
$this->assertFileNotExists($filename);
1006+
}
1007+
1008+
/**
1009+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
1010+
*/
1011+
public function testTempnamWithPharSchemeFails()
1012+
{
1013+
// Skip test if Phar disabled phar.readonly must be 0 in php.ini
1014+
if (!\Phar::canWrite()) {
1015+
$this->markTestSkipped('This test cannot run when phar.readonly is 1.');
1016+
}
1017+
1018+
$scheme = 'phar://';
1019+
$dirname = $scheme.$this->workspace;
1020+
$pharname = 'foo.phar';
1021+
1022+
new \Phar($this->workspace.'/'.$pharname, 0, $pharname);
1023+
// 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
1024+
$this->filesystem->tempnam($dirname, $pharname.'/bar');
1025+
}
1026+
1027+
/**
1028+
* @expectedException \Symfony\Component\Filesystem\Exception\IOException
1029+
*/
1030+
public function testTempnamWithHTTPSchemeFails()
1031+
{
1032+
$scheme = 'http://';
1033+
$dirname = $scheme.$this->workspace;
1034+
1035+
// The http:// scheme is read-only
1036+
$this->filesystem->tempnam($dirname, 'bar');
1037+
}
1038+
1039+
public function testTempnamOnUnwritableFallsBackToSysTmp()
1040+
{
1041+
$scheme = 'file://';
1042+
$dirname = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'does_not_exist';
1043+
1044+
$filename = $this->filesystem->tempnam($dirname, 'bar');
1045+
1046+
$this->assertStringStartsWith(rtrim($scheme.sys_get_temp_dir(), DIRECTORY_SEPARATOR), $filename);
1047+
$this->assertFileExists($filename);
1048+
1049+
// Tear down
1050+
@unlink($filename);
1051+
}
1052+
9491053
public function testDumpFile()
9501054
{
9511055
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt';
@@ -1000,6 +1104,33 @@ public function testDumpFileOverwritesAnExistingFile()
10001104
$this->assertSame('bar', file_get_contents($filename));
10011105
}
10021106

1107+
public function testDumpFileWithFileScheme()
1108+
{
1109+
if (defined('HHVM_VERSION')) {
1110+
$this->markTestSkipped('HHVM does not handle the file:// scheme correctly');
1111+
}
1112+
1113+
$scheme = 'file://';
1114+
$filename = $scheme.$this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt';
1115+
1116+
$this->filesystem->dumpFile($filename, 'bar', null);
1117+
1118+
$this->assertFileExists($filename);
1119+
$this->assertSame('bar', file_get_contents($filename));
1120+
}
1121+
1122+
public function testDumpFileWithZlibScheme()
1123+
{
1124+
$scheme = 'compress.zlib://';
1125+
$filename = $this->workspace.DIRECTORY_SEPARATOR.'foo'.DIRECTORY_SEPARATOR.'baz.txt';
1126+
1127+
$this->filesystem->dumpFile($filename, 'bar', null);
1128+
1129+
// Zlib stat uses file:// wrapper so remove scheme
1130+
$this->assertFileExists(str_replace($scheme, '', $filename));
1131+
$this->assertSame('bar', file_get_contents($filename));
1132+
}
1133+
10031134
public function testCopyShouldKeepExecutionPermission()
10041135
{
10051136
$this->markAsSkippedIfChmodIsMissing();
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Filesystem\Tests\Fixtures\MockStream;
13+
14+
/**
15+
* Mock stream class to be used with stream_wrapper_register.
16+
* stream_wrapper_register('mock', 'Symfony\Component\Filesystem\Tests\Fixtures\MockStream\MockStream').
17+
*/
18+
class MockStream
19+
{
20+
/**
21+
* Opens file or URL.
22+
*
23+
* @param string $path Specifies the URL that was passed to the original function
24+
* @param string $mode The mode used to open the file, as detailed for fopen()
25+
* @param int $options Holds additional flags set by the streams API
26+
* @param string $opened_path If the path is opened successfully, and STREAM_USE_PATH is set in options,
27+
* opened_path should be set to the full path of the file/resource that was actually opened
28+
*
29+
* @return bool
30+
*/
31+
public function stream_open($path, $mode, $options, &$opened_path)
32+
{
33+
return true;
34+
}
35+
36+
/**
37+
* @param string $path The file path or URL to stat
38+
* @param array $flags Holds additional flags set by the streams API
39+
*
40+
* @return array File stats
41+
*/
42+
public function url_stat($path, $flags)
43+
{
44+
return array();
45+
}
46+
}

0 commit comments

Comments
 (0)
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