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