PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.8.1
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.8.1
4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Backup / FileHeader.php
wp-staging / Backup Last commit date
Ajax 2 years ago BackgroundProcessing 2 years ago Dto 2 years ago Entity 2 years ago Exceptions 2 years ago Interfaces 2 years ago Job 2 years ago Request 2 years ago Service 2 years ago Storage 2 years ago Task 2 years ago AfterRestore.php 3 years ago BackupDeleter.php 2 years ago BackupDownload.php 2 years ago BackupFileIndex.php 2 years ago BackupHeader.php 2 years ago BackupProcessLock.php 2 years ago BackupRepairer.php 3 years ago BackupRetentionHandler.php 2 years ago BackupScheduler.php 2 years ago BackupServiceProvider.php 2 years ago BackupValidator.php 2 years ago FileHeader.php 2 years ago FileHeaderAttribute.php 2 years ago WithBackupIdentifier.php 2 years ago wpstgBackupHeader.txt 3 years ago
FileHeader.php
508 lines
1 <?php
2
3 namespace WPStaging\Backup;
4
5 use SplFileInfo;
6 use WPStaging\Backup\Exceptions\FileValidationException;
7 use WPStaging\Backup\Interfaces\IndexLineInterface;
8 use WPStaging\Framework\Utils\Cache\BufferedCache;
9 use WPStaging\Framework\Utils\DataEncoder;
10 use WPStaging\Framework\Filesystem\PathIdentifier;
11 use WPStaging\Framework\Traits\EndOfLinePlaceholderTrait;
12 use WPStaging\Core\WPStaging;
13
14 class FileHeader implements IndexLineInterface
15 {
16 use EndOfLinePlaceholderTrait;
17
18 /**
19 * ASCII INT of `WPSTG`
20 * @var int
21 */
22 const START_SIGNATURE = 8780838471;
23
24 /** @var int */
25 const FILE_HEADER_FIXED_SIZE = 72;
26
27 /** @var int */
28 const INDEX_HEADER_FIXED_SIZE = 72;
29
30 /** @var string */
31 const FILE_HEADER_FORMAT = '644552424';
32
33 /** @var string */
34 const INDEX_HEADER_FORMAT = '644552424';
35
36 /** @var string */
37 const CRC32_CHECKSUM_ALGO = 'crc32b';
38
39 /** @var string */
40 private $startSignature;
41
42 /** @var int */
43 private $modifiedTime;
44
45 /** @var string */
46 private $crc32Checksum;
47
48 /** @var int */
49 private $crc32;
50
51 /** @var int */
52 private $compressedSize;
53
54 /** @var int */
55 private $uncompressedSize;
56
57 /** @var int */
58 private $attributes;
59
60 /** @var int */
61 private $extraFieldLength;
62
63 /** @var int */
64 private $fileNameLength;
65
66 /** @var int */
67 private $filePathLength;
68
69 /** @var int */
70 private $startOffset;
71
72 /** @var string */
73 private $filePath;
74
75 /** @var string */
76 private $fileName;
77
78 /** @var string */
79 private $extraField;
80
81 /** @var DataEncoder */
82 private $encoder;
83
84 public function __construct(DataEncoder $encoder)
85 {
86 $this->encoder = $encoder;
87 $this->resetHeader();
88 }
89
90 /**
91 * @param string $filePath
92 * @param string $identifiablePath
93 * @return void
94 */
95 public function readFile(string $filePath, string $identifiablePath)
96 {
97 $fileInfo = new SplFileInfo($filePath);
98 $this->setFileName($fileInfo->getFilename());
99
100 $pathIdentifier = WPStaging::make(PathIdentifier::class);
101 $convertedPath = $pathIdentifier->transformIdentifiableToPath($identifiablePath);
102 $convertedPathName = basename($convertedPath);
103
104 $path = substr($identifiablePath, 0, -strlen($convertedPathName));
105 $this->setFilePath($path);
106 $this->setExtraField("");
107 $this->setUncompressedSize($fileInfo->getSize());
108 $this->setCompressedSize($fileInfo->getSize());
109 $this->setModifiedTime($fileInfo->getMTime());
110 $this->setAttributes(0);
111 $this->setCrc32Checksum(hash_file(self::CRC32_CHECKSUM_ALGO, $filePath));
112 }
113
114 /**
115 * @param string $index
116 * @return void
117 */
118 public function decodeFileHeader(string $index)
119 {
120 $index = rtrim($index);
121 $fixedHeader = substr($index, 0, self::FILE_HEADER_FIXED_SIZE);
122 $dynamicHeader = substr($index, self::FILE_HEADER_FIXED_SIZE);
123 $header = $this->encoder->hexToIntArray(self::FILE_HEADER_FORMAT, $fixedHeader);
124 $this->setStartSignature($header[0]);
125 $this->setModifiedTime($header[1]);
126 $this->setCrc32($header[2]);
127 $this->setCompressedSize($header[3]);
128 $this->setUncompressedSize($header[4]);
129 $this->setAttributes($header[5]);
130 $this->filePathLength = $header[6];
131 $this->fileNameLength = $header[7];
132 $this->extraFieldLength = $header[8];
133 $this->setFilePath(substr($dynamicHeader, 0, $this->filePathLength));
134 $this->setFileName(substr($dynamicHeader, $this->filePathLength, $this->fileNameLength));
135 $this->setExtraField(substr($dynamicHeader, $this->filePathLength + $this->fileNameLength, $this->extraFieldLength));
136 }
137
138 /**
139 * @param string $index
140 * @return void
141 */
142 public function decodeIndexHeader(string $index)
143 {
144 $index = rtrim($index);
145 $fixedHeader = substr($index, 0, self::INDEX_HEADER_FIXED_SIZE);
146 $dynamicHeader = substr($index, self::INDEX_HEADER_FIXED_SIZE);
147 $header = $this->encoder->hexToIntArray(self::INDEX_HEADER_FORMAT, $fixedHeader);
148
149 $this->setStartOffset($header[0]);
150 $this->setModifiedTime($header[1]);
151 $this->setCrc32($header[2]);
152 $this->setCompressedSize($header[3]);
153 $this->setUncompressedSize($header[4]);
154 $this->setAttributes($header[5]);
155 $this->filePathLength = $header[6];
156 $this->fileNameLength = $header[7];
157 $this->extraFieldLength = $header[8];
158 $this->setFilePath(substr($dynamicHeader, 0, $this->filePathLength));
159 $this->setFileName(substr($dynamicHeader, $this->filePathLength, $this->fileNameLength));
160 $this->setExtraField(substr($dynamicHeader, $this->filePathLength + $this->fileNameLength, $this->extraFieldLength));
161 }
162
163 /**
164 * For compatibility with IndexLineInterface
165 * @param string $indexLine
166 * @return IndexLineInterface
167 */
168 public function readIndexLine(string $indexLine): IndexLineInterface
169 {
170 $this->decodeIndexHeader($indexLine);
171
172 return $this;
173 }
174
175 /**
176 * For compatibility with IndexLineInterface
177 * @param string $indexLine
178 * @return bool
179 */
180 public function isIndexLine(string $indexLine): bool
181 {
182 if (strlen($indexLine) <= self::INDEX_HEADER_FIXED_SIZE) {
183 return false;
184 }
185
186 return true;
187 }
188
189 public function writeFileHeader(BufferedCache $backupCache): int
190 {
191 return $backupCache->append($this->getFileHeader());
192 }
193
194 public function writeIndexHeader(BufferedCache $filesIndexCache): int
195 {
196 return $filesIndexCache->append($this->getIndexHeader());
197 }
198
199 public function getFileHeader(): string
200 {
201 $fixedHeader = $this->encoder->intArrayToHex(self::FILE_HEADER_FORMAT, [
202 self::START_SIGNATURE,
203 $this->modifiedTime,
204 $this->crc32,
205 $this->compressedSize,
206 $this->uncompressedSize,
207 $this->attributes,
208 $this->filePathLength,
209 $this->fileNameLength,
210 $this->extraFieldLength
211 ]);
212 $fileHeader = $fixedHeader . $this->filePath . $this->fileName . $this->extraField;
213 $fileHeader = $this->replaceEOLsWithPlaceholders($fileHeader);
214
215 return $fileHeader;
216 }
217
218 public function getIndexHeader(): string
219 {
220 $fixedHeader = $this->encoder->intArrayToHex(self::INDEX_HEADER_FORMAT, [
221 $this->startOffset,
222 $this->modifiedTime,
223 $this->crc32,
224 $this->compressedSize,
225 $this->uncompressedSize,
226 $this->attributes,
227 $this->filePathLength,
228 $this->fileNameLength,
229 $this->extraFieldLength
230 ]);
231
232 $fixedHeader = $fixedHeader . $this->filePath . $this->fileName . $this->extraField;
233 $fixedHeader = $this->replaceEOLsWithPlaceholders($fixedHeader);
234
235 return $fixedHeader;
236 }
237
238 /**
239 * @return void
240 */
241 public function resetHeader()
242 {
243 $this->startSignature = '';
244 $this->modifiedTime = 0;
245 $this->crc32 = 0;
246 $this->crc32Checksum = '';
247 $this->compressedSize = 0;
248 $this->uncompressedSize = 0;
249 $this->setAttributes(0);
250 $this->extraFieldLength = 0;
251 $this->fileNameLength = 0;
252 $this->filePathLength = 0;
253 $this->startOffset = 0;
254 $this->filePath = '';
255 $this->fileName = '';
256 $this->extraField = '';
257 }
258
259 public function getStartSignature(): string
260 {
261 return $this->startSignature;
262 }
263
264 /**
265 * @return void
266 */
267 public function setStartSignature(string $startSignature)
268 {
269 $this->startSignature = $startSignature;
270 }
271
272 public function getModifiedTime(): int
273 {
274 return $this->modifiedTime;
275 }
276
277 /**
278 * @return void
279 */
280 public function setModifiedTime(int $modifiedTime)
281 {
282 $this->modifiedTime = $modifiedTime;
283 }
284
285 public function getCrc32(): int
286 {
287 return $this->crc32;
288 }
289
290 /**
291 * @return void
292 */
293 public function setCrc32(int $crc32)
294 {
295 $this->crc32 = $crc32;
296 $this->crc32Checksum = bin2hex(pack('N', $crc32));
297 }
298
299 public function getCrc32Checksum(): string
300 {
301 return $this->crc32Checksum;
302 }
303
304 /**
305 * @return void
306 */
307 public function setCrc32Checksum(string $crc32Checksum)
308 {
309 $this->crc32Checksum = $crc32Checksum;
310 $this->crc32 = unpack('N', pack('H*', $this->crc32Checksum))[1];
311 }
312
313 public function getCompressedSize(): int
314 {
315 return $this->compressedSize;
316 }
317
318 /**
319 * @return void
320 */
321 public function setCompressedSize(int $compressedSize)
322 {
323 $this->compressedSize = $compressedSize;
324 }
325
326 public function getUncompressedSize(): int
327 {
328 return $this->uncompressedSize;
329 }
330
331 /**
332 * @return void
333 */
334 public function setUncompressedSize(int $uncompressedSize)
335 {
336 $this->uncompressedSize = $uncompressedSize;
337 }
338
339 public function getAttributes(): int
340 {
341 return $this->attributes;
342 }
343
344 /**
345 * @return void
346 */
347 public function setAttributes(int $attributes)
348 {
349 $this->attributes = $attributes;
350 }
351
352 public function getIsCompressed(): bool
353 {
354 if ($this->attributes & FileHeaderAttribute::COMPRESSED) {
355 return true;
356 }
357
358 return false;
359 }
360
361 /**
362 * @return void
363 */
364 public function setIsCompressed(bool $isCompressed)
365 {
366 $isCompressed ?
367 $this->attributes |= FileHeaderAttribute::COMPRESSED :
368 $this->attributes &= ~FileHeaderAttribute::COMPRESSED;
369 }
370
371 public function getIsPreviousPartRequired(): bool
372 {
373 if ($this->attributes & FileHeaderAttribute::REQUIRE_PREVIOUS_PART) {
374 return true;
375 }
376
377 return false;
378 }
379
380 /**
381 * @return void
382 */
383 public function setIsPreviousPartRequired(bool $isPreviousPartRequired)
384 {
385 $isPreviousPartRequired ?
386 $this->attributes |= FileHeaderAttribute::REQUIRE_PREVIOUS_PART :
387 $this->attributes &= ~FileHeaderAttribute::REQUIRE_PREVIOUS_PART;
388 }
389
390 public function getIsNextPartRequired(): bool
391 {
392 if ($this->attributes & FileHeaderAttribute::REQUIRE_NEXT_PART) {
393 return true;
394 }
395
396 return false;
397 }
398
399 /**
400 * @return void
401 */
402 public function setIsNextPartRequired(bool $isNextPartRequired)
403 {
404 $isNextPartRequired ?
405 $this->attributes |= FileHeaderAttribute::REQUIRE_NEXT_PART :
406 $this->attributes &= ~FileHeaderAttribute::REQUIRE_NEXT_PART;
407 }
408
409 public function getStartOffset(): int
410 {
411 return $this->startOffset;
412 }
413
414 /**
415 * @return void
416 */
417 public function setStartOffset(int $startOffset)
418 {
419 $this->startOffset = $startOffset;
420 }
421
422 public function getFilePath(): string
423 {
424 return $this->filePath;
425 }
426
427 /**
428 * @return void
429 */
430 public function setFilePath(string $filePath)
431 {
432 $this->filePath = $filePath;
433 $filePathRenamed = $this->replaceEOLsWithPlaceholders($filePath);
434 $this->filePathLength = strlen($filePathRenamed);
435 }
436
437 public function getFileName(): string
438 {
439 return $this->fileName;
440 }
441
442 /**
443 * @return void
444 */
445 public function setFileName(string $fileName)
446 {
447 $this->fileName = $fileName;
448 $renamedFile = $this->replaceEOLsWithPlaceholders($fileName);
449 $this->fileNameLength = strlen($renamedFile);
450 }
451
452 public function getExtraField(): string
453 {
454 return $this->extraField;
455 }
456
457 /**
458 * @return void
459 */
460 public function setExtraField(string $extraField)
461 {
462 $this->extraField = $extraField;
463 $this->extraFieldLength = strlen($extraField);
464 }
465
466 public function getIdentifiablePath(): string
467 {
468 return $this->filePath . $this->fileName;
469 }
470
471 public function getDynamicHeaderLength(): int
472 {
473 return $this->filePathLength + $this->fileNameLength + $this->extraFieldLength;
474 }
475
476 public function getContentStartOffset(): int
477 {
478 return $this->startOffset + self::FILE_HEADER_FIXED_SIZE + $this->getDynamicHeaderLength() + 1;
479 }
480
481 /**
482 * @param string $filePath
483 * @param string $pathForErrorLogging
484 * @return void
485 * @throws FileValidationException
486 */
487 public function validateFile(string $filePath, string $pathForErrorLogging = '')
488 {
489 if (empty($pathForErrorLogging)) {
490 $pathForErrorLogging = $filePath;
491 }
492
493 if (!file_exists($filePath)) {
494 throw new FileValidationException(sprintf('File doesn\'t exist: %s.', $pathForErrorLogging));
495 }
496
497 $fileSize = filesize($filePath);
498 if ($this->getUncompressedSize() !== $fileSize) {
499 throw new FileValidationException(sprintf('Filesize validation failed for file %s. Expected: %s. Actual: %s', $pathForErrorLogging, size_format($this->getUncompressedSize(), 2), size_format($fileSize, 2)));
500 }
501
502 $crc32Checksum = hash_file(self::CRC32_CHECKSUM_ALGO, $filePath);
503 if ($this->crc32Checksum !== $crc32Checksum) {
504 throw new FileValidationException(sprintf('CRC32 Checksum validation failed for file %s. Expected: %s. Actual: %s', $pathForErrorLogging, $this->getCrc32Checksum(), $crc32Checksum));
505 }
506 }
507 }
508