PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backups, Restore, Migration & Clone / 4.11.1
WP STAGING – WordPress Backups, Restore, Migration & Clone v4.11.1
4.11.1 4.11.0 4.10.0 4.9.5 4.9.4 4.9.3 4.9.2 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 / BackupHeader.php
wp-staging / Backup Last commit date
Ajax 6 days ago BackgroundProcessing 1 week ago Dto 6 days ago Entity 1 week ago Exceptions 1 week ago FileHeader 1 week ago Interfaces 1 week ago Job 6 days ago Request 1 week ago Service 6 days ago Storage 1 week ago Task 6 days ago Traits 1 week ago Utils 1 week ago AfterRestore.php 1 week ago BackupDeleter.php 1 week ago BackupDownload.php 1 week ago BackupFileIndex.php 1 week ago BackupGlitchReason.php 1 week ago BackupHeader.php 6 days ago BackupNextOffer.php 1 week ago BackupRepairer.php 1 week ago BackupRetentionHandler.php 1 week ago BackupScheduler.php 6 days ago BackupServiceProvider.php 1 week ago BackupValidator.php 6 days ago BeforeUpdateRowStatus.php 1 week ago FileHeader.php 1 week ago FileHeaderAttribute.php 1 week ago UpdateProtectionPausedNotice.php 1 week ago WithBackupIdentifier.php 1 week ago
BackupHeader.php
453 lines
1 <?php
2
3 namespace WPStaging\Backup;
4
5 use WPStaging\Backup\Traits\EncodingErrorHandler;
6 use WPStaging\Framework\Filesystem\FileObject;
7 use WPStaging\Framework\Utils\DataEncoder;
8 use WPStaging\Framework\Utils\Version;
9
10
11
12
13
14
15
16 class BackupHeader
17 {
18 use EncodingErrorHandler;
19
20
21 const WPSTG_SQL_BACKUP_DUMP_HEADER = "-- WP Staging SQL Backup Dump\n";
22
23
24
25
26
27 const LINE_TERMINATOR = "\n";
28
29
30
31
32
33 const HEADER_SIZE = 512;
34
35
36
37
38 const HEADER_IN_USE_HEX_FORMAT = '48888';
39
40
41
42
43
44
45 const MAGIC = "wpstg";
46
47
48
49
50
51 const MAGIC_SIZE = 8;
52
53
54
55
56
57
58 const MIN_BACKUP_VERSION = '2.0.0';
59
60
61
62
63
64
65
66
67
68
69
70 const BACKUP_VERSION = '2.1.0';
71
72
73
74
75
76
77
78 const COPYRIGHT_TEXT = '57502053746167696e672066696c6520666f726d61742062792052656e65204865726d656e617520262048617373616e20536861666971756520323032342f30';
79
80 /**
81 * Copyright text size
82 * @var int
83 */
84 const COPYRIGHT_TEXT_SIZE = 128;
85
86
87
88
89 private $magic;
90
91
92
93
94 private $backupVersion;
95
96
97
98
99 private $filesIndexStartOffset = 0;
100
101
102
103
104 private $filesIndexEndOffset = 0;
105
106
107
108
109 private $metadataStartOffset = 0;
110
111
112
113
114 private $metadataEndOffset = 0;
115
116
117
118
119 private $copyrightText;
120
121
122 private $encoder;
123
124
125 private $versionUtil;
126
127
128
129
130
131
132 public function __construct(DataEncoder $encoder, Version $versionUtil)
133 {
134 $this->encoder = $encoder;
135 $this->versionUtil = $versionUtil;
136 $this->backupVersion = $this->versionUtil->convertStringFormatToIntFormat(self::BACKUP_VERSION);
137 }
138
139
140
141
142
143
144
145 private function logBackupHeaderEncodingError(string $errorMessage)
146 {
147 $context = [
148 'backupVersion' => $this->backupVersion,
149 'filesIndexStartOffset' => $this->filesIndexStartOffset,
150 'filesIndexEndOffset' => $this->filesIndexEndOffset,
151 'metadataStartOffset' => $this->metadataStartOffset,
152 'metadataEndOffset' => $this->metadataEndOffset,
153 ];
154
155 $this->logEncodingErrorWithContext(
156 $errorMessage,
157 $context,
158 'DataEncoder error in BackupHeader::getHeader(): %s. Using fallback values to continue backup.'
159 );
160 }
161
162
163
164
165
166
167 private function applyBackupHeaderFallbackValues()
168 {
169 if ($this->backupVersion === null) {
170 $this->backupVersion = $this->versionUtil->convertStringFormatToIntFormat(self::BACKUP_VERSION);
171 }
172
173 if ($this->filesIndexStartOffset === null) {
174 $this->filesIndexStartOffset = 0;
175 }
176
177 if ($this->filesIndexEndOffset === null) {
178 $this->filesIndexEndOffset = 0;
179 }
180
181 if ($this->metadataStartOffset === null) {
182 $this->metadataStartOffset = 0;
183 }
184
185 if ($this->metadataEndOffset === null) {
186 $this->metadataEndOffset = 0;
187 }
188 }
189
190
191
192
193
194
195
196
197
198
199 public function getBackupVersion(): int
200 {
201 return $this->backupVersion;
202 }
203
204
205
206
207
208
209
210
211
212
213 public function getFormattedBackupVersion(): string
214 {
215 return $this->versionUtil->convertIntFormatToStringFormat($this->backupVersion);
216 }
217
218 public function getMetadataStartOffset(): int
219 {
220 return $this->metadataStartOffset;
221 }
222
223 public function setMetadataStartOffset(int $metadataStartOffset): BackupHeader
224 {
225 $this->metadataStartOffset = $metadataStartOffset;
226 return $this;
227 }
228
229 public function getMetadataEndOffset(): int
230 {
231 return $this->metadataEndOffset;
232 }
233
234 public function setMetadataEndOffset(int $metadataEndOffset): BackupHeader
235 {
236 $this->metadataEndOffset = $metadataEndOffset;
237 return $this;
238 }
239
240 public function getFilesIndexStartOffset(): int
241 {
242 return $this->filesIndexStartOffset;
243 }
244
245 public function setFilesIndexStartOffset(int $filesIndexStartOffset): BackupHeader
246 {
247 $this->filesIndexStartOffset = $filesIndexStartOffset;
248 return $this;
249 }
250
251 public function getFilesIndexEndOffset(): int
252 {
253 return $this->filesIndexEndOffset;
254 }
255
256 public function setFilesIndexEndOffset(int $filesIndexEndOffset): BackupHeader
257 {
258 $this->filesIndexEndOffset = $filesIndexEndOffset;
259 return $this;
260 }
261
262
263
264
265
266
267
268 public function readFromPath(string $backupFilePath): BackupHeader
269 {
270 if (!file_exists($backupFilePath)) {
271 throw new \RuntimeException('Backup file not found');
272 }
273
274 $file = new FileObject($backupFilePath, FileObject::MODE_READ);
275 return $this->readFromFileObject($file);
276 }
277
278
279
280
281
282
283
284 public function readFromFileObject(FileObject $file): BackupHeader
285 {
286 if ($file->getSize() < self::HEADER_SIZE) {
287 throw new \RuntimeException('Invalid v2 format backup file');
288 }
289
290 $file->seek(0);
291 $rawHeader = $file->fread(self::HEADER_SIZE);
292
293 return $this->setupBackupHeaderFromRaw($rawHeader);
294 }
295
296
297
298
299
300
301
302 public function setupBackupHeaderFromRaw(string $rawHeader): BackupHeader
303 {
304 $this->magic = rtrim(substr($rawHeader, 0, self::MAGIC_SIZE));
305 $this->copyrightText = substr($rawHeader, self::HEADER_SIZE - self::COPYRIGHT_TEXT_SIZE, self::COPYRIGHT_TEXT_SIZE);
306
307
308 $dynamicHeader = substr($rawHeader, self::MAGIC_SIZE, $this->getHeaderInUseSize());
309 $headerIntData = $this->encoder->hexToIntArray(self::HEADER_IN_USE_HEX_FORMAT, $dynamicHeader);
310
311 $this->backupVersion = $headerIntData[0];
312 $this->filesIndexStartOffset = $headerIntData[1];
313 $this->filesIndexEndOffset = $headerIntData[2];
314 $this->metadataStartOffset = $headerIntData[3];
315 $this->metadataEndOffset = $headerIntData[4];
316
317 return $this;
318 }
319
320 public function isValidBackupHeader(): bool
321 {
322 if ($this->magic !== self::MAGIC) {
323 return false;
324 }
325
326 if ($this->copyrightText !== self::COPYRIGHT_TEXT) {
327 return false;
328 }
329
330 return version_compare($this->getFormattedBackupVersion(), self::MIN_BACKUP_VERSION, '>=');
331 }
332
333 public function getHeader(): string
334 {
335 try {
336 $encodedData = $this->encoder->intArrayToHex(
337 self::HEADER_IN_USE_HEX_FORMAT,
338 [
339 $this->backupVersion,
340 $this->filesIndexStartOffset,
341 $this->filesIndexEndOffset,
342 $this->metadataStartOffset,
343 $this->metadataEndOffset,
344 ]
345 );
346 } catch (\InvalidArgumentException $e) {
347
348 $this->logBackupHeaderEncodingError($e->getMessage());
349
350
351 $this->applyBackupHeaderFallbackValues();
352
353
354 $encodedData = $this->encoder->intArrayToHex(
355 self::HEADER_IN_USE_HEX_FORMAT,
356 [
357 $this->backupVersion,
358 $this->filesIndexStartOffset,
359 $this->filesIndexEndOffset,
360 $this->metadataStartOffset,
361 $this->metadataEndOffset,
362 ]
363 );
364 }
365
366 return sprintf(
367 '%s%s%s%s',
368 str_pad(self::MAGIC, self::MAGIC_SIZE, "\0", STR_PAD_RIGHT),
369 $encodedData,
370 bin2hex(str_pad("", $this->getUnusedBytesSize(), "\0", STR_PAD_RIGHT)),
371 self::COPYRIGHT_TEXT
372 );
373 }
374
375
376
377
378
379 public function updateHeader(string $backupFilePath)
380 {
381 $header = $this->getHeader();
382 $file = new FileObject($backupFilePath, 'r+');
383 $file->seek(0);
384 $file->fwrite($header);
385 $file = null;
386 }
387
388
389
390
391
392
393 public function verifyV1FormatHeader(string $content): bool
394 {
395 if (empty($content)) {
396 return false;
397 }
398
399 $wpstgBackupHeaderFileContent = self::WPSTG_SQL_BACKUP_DUMP_HEADER;
400 $headerToVerifyLength = strlen($wpstgBackupHeaderFileContent);
401 if (substr($wpstgBackupHeaderFileContent, 0, $headerToVerifyLength) === substr($content, 0, $headerToVerifyLength)) {
402 return true;
403 }
404
405 $wpstgBackupHeaderFile = WPSTG_RESOURCES_DIR . 'wpstgBackupHeader.txt';
406 if (!file_exists($wpstgBackupHeaderFile)) {
407 return true;
408 }
409
410 $wpstgBackupHeaderFileContent = file_get_contents($wpstgBackupHeaderFile);
411 $headerToVerifyLength = self::HEADER_SIZE;
412 if (!empty($wpstgBackupHeaderFileContent) && substr($wpstgBackupHeaderFileContent, 0, $headerToVerifyLength) === substr($content, 0, $headerToVerifyLength)) {
413 return true;
414 }
415
416 return false;
417 }
418
419
420
421
422 public function getV1FormatHeader(): string
423 {
424 $wpstgBackupHeaderFile = WPSTG_RESOURCES_DIR . 'wpstgBackupHeader.txt';
425
426 if (!file_exists($wpstgBackupHeaderFile)) {
427 return "";
428 }
429
430 $content = file_get_contents($wpstgBackupHeaderFile);
431 if ($content === false) {
432 return "";
433 }
434
435 return str_replace("\r\n", self::LINE_TERMINATOR, $content);
436 }
437
438 private function getHeaderInUseSize(): int
439 {
440 $size = 0;
441 for ($i = 0; $i < strlen(self::HEADER_IN_USE_HEX_FORMAT); $i++) {
442 $size += intval(substr(self::HEADER_IN_USE_HEX_FORMAT, $i, 1));
443 }
444
445 return $size * 2;
446 }
447
448 private function getUnusedBytesSize(): int
449 {
450 return (self::HEADER_SIZE - $this->getHeaderInUseSize() - self::MAGIC_SIZE - self::COPYRIGHT_TEXT_SIZE) / 2;
451 }
452 }
453