如何在不同的时间重新创建具有相同sha1sums的PHAR文件?

时间:2014-10-15 15:48:01

标签: php phar

我正在开发一个命令行PHP项目,并希望能够重新创建作为我的部署工件的PHAR文件。挑战在于我无法创建两个具有相同sha1sum并且彼此间隔超过1秒的PHAR。如果输入文件相同(即来自同一个git提交),我希望能够完全重新创建我的PHAR文件。

以下代码段演示了此问题:

#!/usr/bin/php
<?php
$hashes = array();
$file_names = array('file1.phar','file2.phar');

foreach ($file_names as $name) {
  if (file_exists($name)) {
    unlink($name);
  }
  $phar = new Phar($name);
  $phar->addFromString('cli.php', "cli\n");
  $hashes[]=sha1_file($name);
  // remove the sleep and the PHAR's are identical.
  sleep(1);
}
if ($hashes[0]==$hashes[1]) {
  echo "match\n";
} else {
  echo "do not match\n";
}

据我所知,PHAR清单中每个文件的“修改时间”字段始终设置为当前时间,似乎没有办法或覆盖它。即使touch("phar://file1.phar/cli.php", 1413387555)也会出错:

touch(): Can not call touch() for a non-standard stream

我在PHP 5.5.9上运行上面的代码在ubuntu trusty和RHEL5上的PHP 5.3上,两个版本的行为方式相同,无法创建相同的PHAR文件。

我正在尝试这样做,以便遵循Jez Humble和David Farley所着的持续部署一书中的建议

感谢任何帮助。

1 个答案:

答案 0 :(得分:2)

Phar类目前不允许用户更改甚至访问修改时间。我想把你的字符串存储到一个临时文件中并使用touch来改变mtime,但这似乎没有任何影响。因此,您必须手动更改已创建文件中的时间戳,然后重新生成存档签名。以下是使用当前PHP版本的方法:

<?php
    $filename = "file1.phar";

    $archive = file_get_contents($filename);

    # Search for the start of the archive header
    # See http://php.net/manual/de/phar.fileformat.phar.php
    # This isn't the only valid way to write a PHAR archive, but it is what the Phar class
    # currently does, so you should be fine (The docs say that the end-of-PHP-tag is optional)
    $magic = "__HALT_COMPILER(); ?" . ">";
    $end_of_code = strpos($archive, $magic) + strlen($magic);
    $data_pos = $end_of_code;

    # Skip that header
    $data = unpack("Vmanifest_length/Vnumber_of_files/vapi_version/Vglobal_flags/Valias_length", substr($archive, $end_of_code, 18));
    $data_pos += 18 + $data["alias_length"];
    $metadata = unpack("Vlength", substr($archive, $data_pos, 4));
    $data_pos += 4 + $metadata["length"];

    for($i=0; $i<$data["number_of_files"]; $i++) {
        # Now $data_pos points to the first file
        # Files are explained here: http://php.net/manual/de/phar.fileformat.manifestfile.php
        $filename_data = unpack("Vfilename_length", substr($archive, $data_pos, 4));
        $data_pos += 4 + $filename_data["filename_length"];
        $file_data = unpack("Vuncompressed_size/Vtimestamp/Vcompressed_size/VCRC32/Vflags/Vmetadata_length", substr($archive, $data_pos, 24));
        # Change the timestamp to zeros (You can also use some other time here using pack("V", time()) instead of the zeros)
        $archive = substr($archive, 0, $data_pos + 4) . "\0\0\0\0" . substr($archive, $data_pos + 8);
        # Skip to the next file (it's _all_ the headers first, then file data)
        $data_pos += 24 + $file_data["metadata_length"];
    }

    # Regenerate the file's signature
    $sig_data = unpack("Vsigflags/C4magic", substr($archive, strlen($archive) - 8));
    if($sig_data["magic1"] == ord("G") && $sig_data["magic2"] == ord("B") && $sig_data["magic3"] == ord("M") && $sig_data["magic4"] == ord("B")) {
        if($sig_data["sigflags"] == 1) {
            # MD5
            $sig_pos = strlen($archive) - 8 - 16;
            $archive = substr($archive, 0, $sig_pos) . pack("H32", md5(substr($archive, 0, $sig_pos))) . substr($archive, $sig_pos + 16);
        }
        else {
            # SHA1
            $sig_pos = strlen($archive) - 8 - 20;
            $archive = substr($archive, 0, $sig_pos) . pack("H40", sha1(substr($archive, 0, $sig_pos))) . substr($archive, $sig_pos + 20);
        }
        # Note: The manual talks about SHA256/SHA512 support, but the according flags aren't documented yet. Currently,
        # PHAR uses SHA1 by default, so there's nothing to worry about. You still might have to add those sometime.
    }

    file_put_contents($filename, $archive);

我已经为我的本地PHP 5.5.9版本以及上面的示例编写了这个ad-hoc。该脚本适用于与上面的示例代码类似的文件。该文档暗示了与此格式的一些有效偏差。代码中的相应行有注释;如果你想支持一般的Phar文件,你可能需要在那里添加一些东西。