xref: /plugin/sitebackup/PatchedTar.php (revision c874c2c0a464f5afa1b4711c21d732aad51b8080)
1<?php
2
3namespace dokuwiki\plugin\sitebackup;
4
5use splitbrain\PHPArchive\Tar as UpstreamTar;
6
7/**
8 * Tar with the upstream mtime bug patched.
9 *
10 * DokuWiki 2025-05-14b "Librarian" ships an old version of
11 * splitbrain/php-archive whose Tar::writeRawFileHeader() contains:
12 *     $size  = self::numberEncode($size, 12);
13 *     $mtime = self::numberEncode($size, 12);   // <-- $size, not $mtime
14 * So every file's mtime field ends up holding its size, octal-encoded. GNU tar
15 * dutifully reads the value as a Unix timestamp and shows 1970-01-01 with a
16 * size-derived seconds offset.
17 *
18 * Fixed upstream in splitbrain/php-archive PR #38. This subclass copies the
19 * method verbatim and corrects the one line so existing DokuWiki installs
20 * don't have to wait for the vendored library to be bumped.
21 */
22class PatchedTar extends UpstreamTar
23{
24    /**
25     * @inheritdoc
26     */
27    protected function writeRawFileHeader($name, $uid, $gid, $perm, $size, $mtime, $typeflag = '')
28    {
29        // handle filename length restrictions
30        $prefix  = '';
31        $namelen = strlen($name);
32        if ($namelen > 100) {
33            $file = basename($name);
34            $dir  = dirname($name);
35            if (strlen($file) > 100 || strlen($dir) > 155) {
36                // we're still too large, let's use GNU longlink
37                $this->writeRawFileHeader('././@LongLink', 0, 0, 0, $namelen, 0, 'L');
38                for ($s = 0; $s < $namelen; $s += 512) {
39                    $this->writebytes(pack("a512", substr($name, $s, 512)));
40                }
41                $name = substr($name, 0, 100); // cut off name
42            } else {
43                // we're fine when splitting, use POSIX ustar
44                $prefix = $dir;
45                $name   = $file;
46            }
47        }
48
49        // values are needed in octal
50        $uid   = sprintf("%6s ", decoct($uid));
51        $gid   = sprintf("%6s ", decoct($gid));
52        $perm  = sprintf("%6s ", decoct($perm));
53        $size  = self::numberEncode($size, 12);
54        $mtime = self::numberEncode($mtime, 12);   // patched: was numberEncode($size, 12)
55
56        $data_first = pack("a100a8a8a8a12A12", $name, $perm, $uid, $gid, $size, $mtime);
57        $data_last  = pack("a1a100a6a2a32a32a8a8a155a12", $typeflag, '', 'ustar', '', '', '', '', '', $prefix, "");
58
59        for ($i = 0, $chks = 0; $i < 148; $i++) {
60            $chks += ord($data_first[$i]);
61        }
62
63        for ($i = 156, $chks += 256, $j = 0; $i < 512; $i++, $j++) {
64            $chks += ord($data_last[$j]);
65        }
66
67        $this->writebytes($data_first);
68
69        $chks = pack("a8", sprintf("%6s ", decoct($chks)));
70        $this->writebytes($chks.$data_last);
71    }
72}
73