使用 phar Stream 写入 PharData 文件

Posted

技术标签:

【中文标题】使用 phar Stream 写入 PharData 文件【英文标题】:Using phar Stream to Write PharData Files 【发布时间】:2012-12-15 16:06:31 【问题描述】:

当我尝试这个时:

$phar = new PharData('./phar.tar', 0, null, Phar::TAR);
$phar->addEmptyDir('test');

file_put_contents('phar://./phar.tar/test/foo.txt', 'bar');

我收到以下错误:

警告:file_put_contents(phar://./phar.tar/test/foo.txt): 失败 打开流:无法创建 phar './phar.tar'、文件扩展名(或 组合)无法识别或目录不存在

我可以使用 file_get_contents 但shouldn't file_put_contents work too?


为了让事情变得更奇怪,我尝试了@hakre 在他的评论中提出的想法,它奏效了!

$phar = new PharData('./phar.tar', 0, null, Phar::TAR);
$phar->addEmptyDir('test');

if (file_exists('./phar.phar') !== true)

    symlink('./phar.tar', './phar.phar');


file_put_contents('phar://./phar.phar/test/foo.txt', 'bar');
var_dump(file_get_contents('phar://./phar.tar/test/foo.txt')); // string(3) "bar"

【问题讨论】:

可能是权限问题? 您能否添加指向您尝试访问的phar.tar 的链接,以便我可以尝试复制该问题 @Baba,只需使用此代码。这就是你所需要的。 @CORRUPT:我当时在 Windows 上运行代码,所以我对此表示怀疑。 @hakre:尝试了你所说的,符号链接的想法奏效了!我必须说,我真的没想到会这样。抱歉,我花了一段时间才回来,我有点忙。 【参考方案1】:

简介

我认为这基本上与您正在使用 Phar::TAR 文件这一事实有关,因为

file_put_contents("phar://./test.tar/foo.txt", "hello world");
                                  ^
                                  |+---- Note this tar

返回

警告:file_put_contents(phar://./test.tar/foo.txt):未能打开流:无法创建 phar './test.tar',文件扩展名(或组合)无法识别或目录不正确不存在

虽然

file_put_contents("phar://./test.phar/foo.txt", "hello world"); // Works file
                                  ^
                                  |+---- Note This phar

返回

//No Errors

了解问题

如果您查看 php 文档中的 detail example,这是过去 2 years 的已知问题,给出的示例如下

例子

$p = new PharData(dirname(__FILE__) . '/phartest.zip', 0, 'phartest', Phar::ZIP);
$p->addFromString('testfile.txt', 'this is just some test text');

// This works
echo file_get_contents('phar://phartest.zip/testfile.txt');

// This Fails
file_put_contents('phar://phartest.zip/testfile.txt', 'Thist is text for testfile.txt');

$context = stream_context_create(array(
        'phar' => array(
                'compress' => Phar::ZIP
        )
));

// This Fails
file_put_contents('phar://phartest.zip/testfile.txt', 'Thist is text for testfile.txt', 0, $context);

// This works but only with 'r' readonly mode.
$f = fopen('phar://C:\\Inetpub\\wwwroot\\PACT\\test\\phartest.zip\\testfile.txt', 'r');

工作选择

不要使用.tar.zip 有你的文件extension 但设置压缩如PHP DOC 所示

我是什么意思?

$context = stream_context_create(array(
        'phar' => array(
                'compress' => Phar::GZ //set compression
        )
));

file_put_contents('phar://sample.phar/somefile.txt', "Sample Data", 0, $context);
                                   ^
                                   |= Use phar not tar

建议

我真的认为你应该坚持使用PharData 是可以实现的并且被证明是有效的。制作的唯一方法

我必须使用 file_put_contents

然后编写自己的包装器并将其注册到stream_wrapper_register 这是一个简单的Prof of Concept

stream_wrapper_register("alixphar", "AlixPhar");
file_put_contents('alixphar://phar.tar/test/foo.txt', 'hey'); 
                         ^
                         |+-- Write with alixphar

echo file_get_contents('alixphar://phar.tar/test/foo.txt'),PHP_EOL;
echo file_get_contents('phar://phar.tar/test/foo.txt'),PHP_EOL;

输出

hey     <----- alixphar
hey     <----- phar

注意:这个类不应该在生产中使用......它只是一个示例,并且会失败一些测试用例

class AlixPhar 
    private $pos;
    private $stream;
    private $types;
    private $dir;
    private $pharContainer, $pharType, $pharFile, $pharDir = null;
    private $fp;

    function __construct() 
        $this->types = array(
                "tar" => Phar::TAR,
                "zip" => Phar::ZIP,
                "phar" => Phar::PHAR
        );
        // your phar dir to help avoid using path before the phar file
        $this->dir = __DIR__;
    

    private function parsePath($path) 
        $url = parse_url($path);
        $this->pharContainer = $url['host'];
        $this->pharType = $this->types[pathinfo($this->pharContainer, PATHINFO_EXTENSION)];
        if (strpos($url['path'], ".") !== false) 
            list($this->pharDir, $this->pharFile, , ) = array_values(pathinfo($url['path']));
         else 
            $this->pharDir = $url['path'];
        
    

    public function stream_open($path, $mode, $options, &$opened_path) 
        $this->parsePath($path);
        $this->stream = new PharData($this->dir . "/" . $this->pharContainer, 0, null, $this->pharType);
        if (! empty($this->pharDir))
            $this->stream->addEmptyDir($this->pharDir);

        if ($mode == "rb") 
            $this->fp = fopen("phar://" . $this->pharContainer . "/" . $this->pharDir . "/" . $this->pharFile, $mode);
        
        return true;
    

    public function stream_write($data) 
        $this->pos += strlen($data);
        $this->stream->addFromString($this->pharDir . "/" . $this->pharFile, $data);
        return $this->pos;
    

    public function stream_read($count) 
        return fread($this->fp, $count);
    

    public function stream_tell() 
        return ftell($this->fp);
    

    public function stream_eof() 
        return feof($this->fp);
    

    public function stream_stat() 
        return fstat($this->fp);
    

【讨论】:

【参考方案2】:

我有一些答案 - 希望这会对你有所帮助。

首先,对于 file_put_contents(),我注意到了一些奇怪之处。首先,我所做的是按照您发布的链接的示例进行操作。我能够添加 $context 变量,但仍然有错误。对我有用的是将文件名更改为 phar.phar。也许 phar:// 处理程序无法理解 .tar 扩展名?

无论如何,这似乎是有效的代码 - 但我不推荐它

$context = stream_context_create(array('phar' =>
                                array('compress' => Phar::GZ)),
                                array('metadata' => array('user' => 'cellog')));

file_put_contents('phar://./phar.phar/test/foo.txt', 'bar', 0, $context);

改为使用这个。

使用我 - 我被推荐

$phar = new PharData('./phar.tar', 0, null, Phar::TAR);
$phar->addEmptyDir('test');
$phar->addFile('./bar', 'foo.txt');

如果您确实需要使用 file_put_contents() 代替,也许存在另一个问题,也许我们可以提供帮助。谢谢,祝你好运!

【讨论】:

尝试使用 .phar 扩展名创建 phar。 奇怪的是,如果您使用第一种方法并且之前执行过$phar = new PharData('./phar.phar', 0, null, Phar::TAR); $phar-&gt;addEmptyDir('test');,您将得到完全相同的错误。没有它(并且只有 .phar 扩展名)它可以工作。根本不定义 $context 也可以(但是我不确定压缩方法)。我知道第二个选项是“推荐”选项,但由于我不记得的原因(我为我的问题提供了不同的解决方案),我只有 phar://... 可以使用的路径。 哦,还有一件让我烦恼的事情是file_get_contents 可以与.phar.zip.tar 扩展文件一起使用,只要它们带有前缀phar:// 流包装器,您甚至不必指定 $context!我希望file_put_contents() 保持一致并以相同的方式表现,但不是。 :(【参考方案3】:

我还有一些其他的替代方法,我认为它们更简洁一些。他们避免使用自定义包装器、符号链接或创建额外文件以使用 addFile 函数。

选项 A

使用与file_put_contents 类似的addFromString 函数。下面的代码创建一个 TAR 存档,向其中添加一个文件并对其进行压缩。

$phar = new PharData("./archive.tar", 0, null, Phar::TAR);
$phar->addFromString('out/fileA.txt','The quick brown fox jumped over the lazy dog');
$phar->compress(Phar::GZ); # Creates archive.tar.gz

选项 B

如果您出于某种原因确实需要使用file_put_contents。您可以使用它创建一个 PHAR 存档,然后重新打开它并将其转换为另一种存档类型。

file_put_contents('phar://archive2.phar/out/fileB.txt', "Colourless green ideas sleep furiously");
$tarphar = new Phar('archive2.phar');
$tar = $tarphar->convertToData(Phar::TAR); # Creates archive2.tar
$zip = $tarphar->convertToData(Phar::ZIP); # Creates archive2.zip
$tgz = $tarphar->convertToData(Phar::TAR, Phar::GZ, '.tar.gz'); # Creates archive2.tar.gz

【讨论】:

以上是关于使用 phar Stream 写入 PharData 文件的主要内容,如果未能解决你的问题,请参考以下文章

Phar反序列化漏洞原理

为啥 PHP 中的 phar 写支持特别被锁定?

使用 gridfs-stream 将字符串写入 gridfs

为啥要分块写入 Stream?

在 Flutter 中监听 Stream 并只向 DB 写入一次

在 ofstream 写入期间检测到空间不足,stream.fail() 无法工作