Sparse
1 year ago
.htaccess
1 year ago
Header.php
1 year ago
index.html
1 year ago
web.config
1 year ago
Header.php
351 lines
| 1 | <?php |
| 2 | /* |
| 3 | * |
| 4 | * JetBackup @ package |
| 5 | * Created By Idan Ben-Ezra |
| 6 | * |
| 7 | * Copyrights @ JetApps |
| 8 | * https://www.jetapps.com |
| 9 | * |
| 10 | **/ |
| 11 | namespace JetBackup\Archive\Header; |
| 12 | |
| 13 | use JetBackup\Archive\Archive; |
| 14 | use JetBackup\Archive\Data\SetterGetter; |
| 15 | use JetBackup\Archive\Header\Sparse\Sparse; |
| 16 | use JetBackup\Archive\Header\Sparse\SparseRegion; |
| 17 | use JetBackup\Exception\ArchiveException; |
| 18 | |
| 19 | class Header extends SetterGetter { |
| 20 | |
| 21 | const REGTYPE = '0'; // regular file |
| 22 | const AREGTYPE = '\0'; // regular file |
| 23 | const LNKTYPE = '1'; // link |
| 24 | const SYMTYPE = '2'; // reserved (symlink) |
| 25 | const CHRTYPE = '3'; // character special |
| 26 | const BLKTYPE = '4'; // block special |
| 27 | const DIRTYPE = '5'; // directory |
| 28 | const FIFOTYPE = '6'; // FIFO special |
| 29 | const CONTTYPE = '7'; // reserved |
| 30 | const XHDTYPE = 'x'; // Extended header referring to the next file in the archive |
| 31 | const XGLTYPE = 'g'; // Global extended header |
| 32 | |
| 33 | /* This is a dir entry that contains the names of files that were in the |
| 34 | dir at the time the dump was made. */ |
| 35 | const GNUTYPE_DUMPDIR = 'D'; |
| 36 | |
| 37 | /* Identifies the *next* file on the tape as having a long linkname. */ |
| 38 | const GNUTYPE_LONGLINK = 'K'; |
| 39 | |
| 40 | /* Identifies the *next* file on the tape as having a long name. */ |
| 41 | const GNUTYPE_LONGNAME = 'L'; |
| 42 | |
| 43 | /* This is the continuation of a file that began on another volume. */ |
| 44 | const GNUTYPE_MULTIVOL = 'M'; |
| 45 | |
| 46 | /* This is for sparse files. */ |
| 47 | const GNUTYPE_SPARSE = 'S'; |
| 48 | |
| 49 | /* This file is a tape/volume header. Ignore it on extraction. */ |
| 50 | const GNUTYPE_VOLHDR = 'V'; |
| 51 | |
| 52 | /* Solaris extended header */ |
| 53 | const SOLARIS_XHDTYPE = 'X'; |
| 54 | |
| 55 | const LENGTH_FILENAME = 100; |
| 56 | const LENGTH_MODE = 8; |
| 57 | const LENGTH_UID = 8; |
| 58 | const LENGTH_GID = 8; |
| 59 | const LENGTH_SIZE = 12; |
| 60 | const LENGTH_MTIME = 12; |
| 61 | const LENGTH_CHECKSUM = 8; |
| 62 | const LENGTH_TYPEFLAG = 1; |
| 63 | const LENGTH_LINKNAME = 100; |
| 64 | const LENGTH_MAGIC = 6; |
| 65 | const LENGTH_VERSION = 2; |
| 66 | const LENGTH_UNAME = 32; |
| 67 | const LENGTH_GNAME = 32; |
| 68 | const LENGTH_DEVMAJOR = 8; |
| 69 | const LENGTH_DEVMINOR = 8; |
| 70 | const LENGTH_PREFIX = 155; |
| 71 | const LENGTH_PAD = 12; |
| 72 | |
| 73 | const OFFSET_FILENAME = 0; |
| 74 | const OFFSET_MODE = self::LENGTH_FILENAME; |
| 75 | const OFFSET_UID = self::LENGTH_FILENAME + self::LENGTH_MODE; |
| 76 | const OFFSET_GID = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID; |
| 77 | const OFFSET_SIZE = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID; |
| 78 | const OFFSET_MTIME = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 79 | self::LENGTH_SIZE; |
| 80 | const OFFSET_CHECKSUM = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 81 | self::LENGTH_SIZE + self::LENGTH_MTIME; |
| 82 | const OFFSET_TYPEFLAG = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 83 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM; |
| 84 | const OFFSET_LINKNAME = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 85 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG; |
| 86 | const OFFSET_MAGIC = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 87 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 88 | self::LENGTH_LINKNAME; |
| 89 | const OFFSET_VERSION = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 90 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 91 | self::LENGTH_LINKNAME + self::LENGTH_MAGIC; |
| 92 | const OFFSET_UNAME = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 93 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 94 | self::LENGTH_LINKNAME + self::LENGTH_MAGIC + self::LENGTH_VERSION; |
| 95 | const OFFSET_GNAME = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 96 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 97 | self::LENGTH_LINKNAME + self::LENGTH_MAGIC + self::LENGTH_VERSION + self::LENGTH_UNAME; |
| 98 | const OFFSET_DEVMAJOR = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 99 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 100 | self::LENGTH_LINKNAME + self::LENGTH_MAGIC + self::LENGTH_VERSION + self::LENGTH_UNAME + |
| 101 | self::LENGTH_GNAME; |
| 102 | const OFFSET_DEVMINOR = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 103 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 104 | self::LENGTH_LINKNAME + self::LENGTH_MAGIC + self::LENGTH_VERSION + self::LENGTH_UNAME + |
| 105 | self::LENGTH_GNAME + self::LENGTH_DEVMAJOR; |
| 106 | const OFFSET_PREFIX = self::LENGTH_FILENAME + self::LENGTH_MODE + self::LENGTH_UID + self::LENGTH_GID + |
| 107 | self::LENGTH_SIZE + self::LENGTH_MTIME + self::LENGTH_CHECKSUM + self::LENGTH_TYPEFLAG + |
| 108 | self::LENGTH_LINKNAME + self::LENGTH_MAGIC + self::LENGTH_VERSION + self::LENGTH_UNAME + |
| 109 | self::LENGTH_GNAME + self::LENGTH_DEVMAJOR + self::LENGTH_DEVMINOR; |
| 110 | |
| 111 | const |
| 112 | FILENAME = 'filename', |
| 113 | MODE = 'mode', |
| 114 | UID = 'uid', |
| 115 | GID = 'gid', |
| 116 | SIZE = 'size', |
| 117 | MTIME = 'mtime', |
| 118 | CHECKSUM = 'checksum', |
| 119 | TYPEFLAG = 'typeflag', |
| 120 | LINKNAME = 'linkname', |
| 121 | MAGIC = 'magic', |
| 122 | VERSION = 'version', |
| 123 | UNAME = 'uname', |
| 124 | GNAME = 'gname', |
| 125 | DEVMAJOR = 'devmajor', |
| 126 | DEVMINOR = 'devminor', |
| 127 | PREFIX = 'prefix', |
| 128 | SPARSE = 'sparse'; |
| 129 | |
| 130 | public function __construct($data=[]) { |
| 131 | parent::__construct(); |
| 132 | if(isset($data[self::FILENAME])) $this->setFilename($data[self::FILENAME]); |
| 133 | if(isset($data[self::MODE])) $this->setMode($data[self::MODE]); |
| 134 | if(isset($data[self::UID])) $this->setUid($data[self::UID]); |
| 135 | if(isset($data[self::GID])) $this->setGid($data[self::GID]); |
| 136 | if(isset($data[self::SIZE])) $this->setSize($data[self::SIZE]); |
| 137 | if(isset($data[self::MTIME])) $this->setMtime($data[self::MTIME]); |
| 138 | if(isset($data[self::CHECKSUM])) $this->setChecksum($data[self::CHECKSUM]); |
| 139 | if(isset($data[self::TYPEFLAG])) $this->setTypeFlag($data[self::TYPEFLAG]); |
| 140 | if(isset($data[self::LINKNAME])) $this->setLinkName($data[self::LINKNAME]); |
| 141 | if(isset($data[self::MAGIC])) $this->setMagic($data[self::MAGIC]); |
| 142 | if(isset($data[self::VERSION])) $this->setVersion($data[self::VERSION]); |
| 143 | if(isset($data[self::UNAME])) $this->setUname($data[self::UNAME]); |
| 144 | if(isset($data[self::GNAME])) $this->setGname($data[self::GNAME]); |
| 145 | if(isset($data[self::DEVMAJOR])) $this->setDevMajor($data[self::DEVMAJOR]); |
| 146 | if(isset($data[self::DEVMINOR])) $this->setDevMinor($data[self::DEVMINOR]); |
| 147 | if(isset($data[self::PREFIX])) $this->setPrefix($data[self::PREFIX]); |
| 148 | if(isset($data[self::SPARSE])) $this->setSparse(new Sparse($data[self::SPARSE])); |
| 149 | } |
| 150 | |
| 151 | public function setFilename($filename): void { $this->set(self::FILENAME, $filename); } |
| 152 | public function getFilename(): string { return $this->get(self::FILENAME); } |
| 153 | |
| 154 | public function setMode($mode, $octal=true): void { |
| 155 | |
| 156 | |
| 157 | $this->set(self::MODE, $octal ? octdec(trim($mode)) : (int) $mode); |
| 158 | |
| 159 | } |
| 160 | public function getMode($octal=true) { return self::_getDecOct($this->get(self::MODE), $octal, 7); } |
| 161 | |
| 162 | public function setUid($uid, $octal=true): void { $this->set(self::UID, $octal ? octdec(trim($uid)) : (int) $uid); } |
| 163 | public function getUid($octal=true) { return self::_getDecOct($this->get(self::UID), $octal, 7); } |
| 164 | |
| 165 | public function setGid($gid, $octal=true): void { $this->set(self::GID, $octal ? octdec(trim($gid)) : (int) $gid); } |
| 166 | public function getGid($octal=true) { return self::_getDecOct($this->get(self::GID), $octal, 7); } |
| 167 | |
| 168 | public function setSize($size, $octal=true): void { |
| 169 | |
| 170 | $this->set(self::SIZE, $octal ? octdec(trim($size)) : (int) $size); |
| 171 | |
| 172 | } |
| 173 | public function getSize($octal=true) { return self::_getDecOct($this->get(self::SIZE), $octal, 11); } |
| 174 | |
| 175 | public function setMtime($mtime, $octal=true): void { $this->set(self::MTIME, $octal ? octdec(trim($mtime)) : (int) $mtime); } |
| 176 | public function getMtime($octal=true) { return self::_getDecOct($this->get(self::MTIME), $octal, 11); } |
| 177 | |
| 178 | public function setChecksum($checksum, $octal=true): void { $this->set(self::CHECKSUM, $octal ? octdec(trim($checksum)) : (int) $checksum); } |
| 179 | public function getChecksum($octal=true) { return self::_getDecOct($this->get(self::CHECKSUM), $octal, 6); } |
| 180 | |
| 181 | public function setTypeFlag(string $flag): void { $this->set(self::TYPEFLAG, $flag); } |
| 182 | public function getTypeFlag(): string { return trim($this->get(self::TYPEFLAG)); } |
| 183 | |
| 184 | public function setLinkName($name): void { $this->set(self::LINKNAME, $name); } |
| 185 | public function getLinkName(): string { return trim($this->get(self::LINKNAME)); } |
| 186 | |
| 187 | public function setMagic($magic): void { $this->set(self::MAGIC, $magic); } |
| 188 | public function getMagic(): string { return trim($this->get(self::MAGIC)); } |
| 189 | |
| 190 | public function setVersion($version): void { $this->set(self::VERSION, $version); } |
| 191 | public function getVersion(): string { return trim($this->get(self::VERSION)); } |
| 192 | |
| 193 | public function setUname($name): void { $this->set(self::UNAME, $name); } |
| 194 | public function getUname(): string { return trim($this->get(self::UNAME)); } |
| 195 | |
| 196 | public function setGname($name): void { $this->set(self::GNAME, $name); } |
| 197 | public function getGname(): string { return trim($this->get(self::GNAME)); } |
| 198 | |
| 199 | public function setDevMajor($device, $octal=true): void { $this->set(self::DEVMAJOR, $octal ? octdec(trim($device)) : (int) $device); } |
| 200 | public function getDevMajor($octal=true) { return self::_getDecOct($this->get(self::DEVMAJOR), $octal, 7); } |
| 201 | |
| 202 | public function setDevMinor($device, $octal=true): void { $this->set(self::DEVMINOR, $octal ? octdec(trim($device)) : (int) $device); } |
| 203 | public function getDevMinor($octal=true) { return self::_getDecOct($this->get(self::DEVMINOR), $octal, 7); } |
| 204 | |
| 205 | public function setPrefix($prefix): void { $this->set(self::PREFIX, $prefix); } |
| 206 | public function getPrefix(): string { return $this->get(self::PREFIX); } |
| 207 | |
| 208 | public function setSparse( ?Sparse $sparse=null): void { $this->set(self::SPARSE, $sparse); } |
| 209 | public function getSparse():?Sparse { return $this->get(self::SPARSE, null); } |
| 210 | |
| 211 | /** |
| 212 | * @return void |
| 213 | */ |
| 214 | public function buildPrefix(): void { |
| 215 | if($this->getTypeFlag() != self::GNUTYPE_SPARSE || !($sparse = $this->getSparse())) return; |
| 216 | |
| 217 | $regions = $sparse->getRegions(); |
| 218 | |
| 219 | $prefix = ""; |
| 220 | $prefix .= $sparse->getAtime(); |
| 221 | $prefix .= $sparse->getCtime(); |
| 222 | $prefix .= $sparse->getOffset(); |
| 223 | $prefix .= $sparse->getLongName(); |
| 224 | $prefix .= Archive::NULL_CHAR; // pad |
| 225 | |
| 226 | // we have space only for 4 regions, more than that will be written in the next blocks (each 21 regions block) |
| 227 | for($i = 0; $i < 4; $i++) { |
| 228 | $region = array_shift($regions); |
| 229 | |
| 230 | if($region) { |
| 231 | $prefix .= $region->getOffset(); |
| 232 | $prefix .= $region->getNumbytes(); |
| 233 | } else { |
| 234 | $prefix .= str_repeat(Archive::NULL_CHAR, SparseRegion::REGION_LENGTH); |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | $prefix .= sizeof($regions) ? Archive::TRUE_CHAR : Archive::NULL_CHAR; // extended |
| 239 | $prefix .= $sparse->getRealSize(); |
| 240 | $prefix .= str_repeat(Archive::NULL_CHAR, Sparse::LENGTH_PAD_END); |
| 241 | |
| 242 | $sparse->setRegions($regions); |
| 243 | $this->setPrefix($prefix); |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * @return string |
| 248 | */ |
| 249 | public function pack(): string { |
| 250 | $pack = pack("a" . self::LENGTH_FILENAME, $this->getFilename()); //name |
| 251 | $pack .= pack("a" . self::LENGTH_MODE, $this->getMode()); // mode |
| 252 | $pack .= pack("a" . self::LENGTH_UID, $this->getUid()); // uid |
| 253 | $pack .= pack("a" . self::LENGTH_GID, $this->getGid()); // gid |
| 254 | $pack .= pack("a" . self::LENGTH_SIZE, $this->getSize()); // size |
| 255 | $pack .= pack("A" . self::LENGTH_MTIME, $this->getMtime()); // mtime |
| 256 | $pack .= pack("a" . self::LENGTH_CHECKSUM, ""); // checksum |
| 257 | $pack .= pack("a" . self::LENGTH_TYPEFLAG, $this->getTypeFlag()); // typeflag |
| 258 | $pack .= pack("a" . self::LENGTH_LINKNAME, $this->getLinkName()); // linkname |
| 259 | $pack .= pack("a" . self::LENGTH_MAGIC, $this->getMagic()); // magic |
| 260 | $pack .= pack("a" . self::LENGTH_VERSION, $this->getVersion()); // version |
| 261 | $pack .= pack("a" . self::LENGTH_UNAME, $this->getUname()); // uname |
| 262 | $pack .= pack("a" . self::LENGTH_GNAME, $this->getGname()); // gname |
| 263 | $pack .= pack("a" . self::LENGTH_DEVMAJOR, $this->getDevMajor()); // devmajor |
| 264 | $pack .= pack("a" . self::LENGTH_DEVMINOR, $this->getDevMinor()); // devminor |
| 265 | $pack .= pack("a" . self::LENGTH_PREFIX, $this->getPrefix()); // prefix |
| 266 | $pack .= pack("a" . self::LENGTH_PAD, ""); // pad |
| 267 | |
| 268 | $this->setChecksum(self::calculateChecksum($pack), false); |
| 269 | $checksum = pack('a' . self::LENGTH_CHECKSUM, $this->getChecksum()); |
| 270 | return substr_replace($pack, $checksum, self::OFFSET_CHECKSUM, self::LENGTH_CHECKSUM); |
| 271 | } |
| 272 | |
| 273 | /** |
| 274 | * @param mixed $data |
| 275 | * @param callable $readDataBlock |
| 276 | * |
| 277 | * @return Header |
| 278 | * @throws ArchiveException |
| 279 | */ |
| 280 | public static function parse($data, callable $readDataBlock, bool $debug=false): Header { |
| 281 | |
| 282 | if($data === null || $data === false) |
| 283 | throw new ArchiveException("Failed reading header"); |
| 284 | |
| 285 | if(strlen($data) < Archive::BLOCK_SIZE) |
| 286 | throw new ArchiveException("Header length is invalid"); |
| 287 | |
| 288 | $args = [ |
| 289 | "a" . self::LENGTH_FILENAME . self::FILENAME, |
| 290 | "a" . self::LENGTH_MODE . self::MODE, |
| 291 | "a" . self::LENGTH_UID . self::UID, |
| 292 | "a" . self::LENGTH_GID . self::GID, |
| 293 | "a" . self::LENGTH_SIZE . self::SIZE, |
| 294 | "A" . self::LENGTH_MTIME . self::MTIME, |
| 295 | "a" . self::LENGTH_CHECKSUM . self::CHECKSUM, |
| 296 | "a" . self::LENGTH_TYPEFLAG . self::TYPEFLAG, |
| 297 | "a" . self::LENGTH_LINKNAME . self::LINKNAME, |
| 298 | "a" . self::LENGTH_MAGIC . self::MAGIC, |
| 299 | "a" . self::LENGTH_VERSION . self::VERSION, |
| 300 | "a" . self::LENGTH_UNAME . self::UNAME, |
| 301 | "a" . self::LENGTH_GNAME . self::GNAME, |
| 302 | "a" . self::LENGTH_DEVMAJOR . self::DEVMAJOR, |
| 303 | "a" . self::LENGTH_DEVMINOR . self::DEVMINOR, |
| 304 | "a" . self::LENGTH_PREFIX . self::PREFIX |
| 305 | ]; |
| 306 | |
| 307 | if(!($header_data = unpack(implode("/", $args), $data))) |
| 308 | throw new ArchiveException("Failed parsing header"); |
| 309 | |
| 310 | if(isset($header_data[self::CHECKSUM]) && octdec(trim($header_data[self::CHECKSUM])) != self::calculateChecksum($data)) |
| 311 | throw new ArchiveException("Header does not match its checksum for account '{$header_data[self::FILENAME]}'"); |
| 312 | |
| 313 | $header = new Header($header_data); |
| 314 | |
| 315 | if($header->getTypeFlag() == Header::GNUTYPE_SPARSE) |
| 316 | $header->setSparse(Sparse::fromPrefix($header->getPrefix(), $readDataBlock)); |
| 317 | |
| 318 | if($debug) Archive::printHeader($header); |
| 319 | |
| 320 | return $header; |
| 321 | } |
| 322 | |
| 323 | /** |
| 324 | * @param string $data |
| 325 | * |
| 326 | * @return int |
| 327 | */ |
| 328 | public static function calculateChecksum(string $data): int { |
| 329 | $checksum = 256; |
| 330 | for ($i = 0; $i < Archive::BLOCK_SIZE; $i++) { |
| 331 | // skip checksum, not should be in the checksum calculation |
| 332 | if($i >= self::OFFSET_CHECKSUM && $i < (self::OFFSET_CHECKSUM + self::LENGTH_CHECKSUM)) continue; |
| 333 | $checksum += ord($data[$i]); |
| 334 | } |
| 335 | return $checksum; |
| 336 | } |
| 337 | |
| 338 | /** |
| 339 | * @param mixed $value |
| 340 | * @param bool $octal |
| 341 | * @param int $octal_length |
| 342 | * |
| 343 | * @return int|string |
| 344 | */ |
| 345 | public static function _getDecOct( $value, bool $octal, int $octal_length=0) { |
| 346 | if(!$octal_length) $octal = false; |
| 347 | if(!$octal) return (int) $value; |
| 348 | if(!is_int($value)) return str_repeat(Archive::NULL_CHAR, $octal_length); |
| 349 | return sprintf("%0{$octal_length}o", $value); |
| 350 | } |
| 351 | } |