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 / BackupHeader.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
BackupHeader.php
362 lines
1 <?php
2
3 namespace WPStaging\Backup;
4
5 use RuntimeException;
6 use WPStaging\Framework\Filesystem\FileObject;
7 use WPStaging\Framework\Utils\DataEncoder;
8 use WPStaging\Framework\Utils\Version;
9 use WPStaging\Backup\Service\Database\Exporter\DDLExporter;
10
11 /**
12 * Backup Header class
13 * It will generate the header of the backup file
14 * and read the header of the backup file
15 * and update the header of the backup file
16 */
17 class BackupHeader
18 {
19 /**
20 * In Length
21 * @var int
22 */
23 const HEADER_SIZE = 512;
24
25 /**
26 * @var string
27 */
28 const HEADER_IN_USE_HEX_FORMAT = '48888';
29
30 /**
31 * File magic
32 * should not exceed 8 characters
33 * @var string
34 */
35 const MAGIC = "wpstg";
36
37 /**
38 * Magic size in length
39 * @var int
40 */
41 const MAGIC_SIZE = 8;
42
43 /**
44 * Minimum Backup version that support this new header
45 *
46 * @var string
47 */
48 const MIN_BACKUP_VERSION = '2.0.0';
49
50 /**
51 * Backup version
52 * Should not exceed 4-bytes unsigned limit 4294967295
53 * In the format X.Y.Z
54 * Where X is the major version and can be upto 429495 :)
55 * Where Y is the minor version and can be upto 99
56 * Where Z is the patch version and can be upto 99
57 *
58 * @var string
59 */
60 const BACKUP_VERSION = '2.0.0';
61
62 /**
63 * Original string should not exceed 64 characters for consistency
64 * Generated from running bin2hex(str_pad("orignalString", 64 "\0", STR_PAD_RIGHT)) to 128 characters hex string
65 * To retrieve original string run hex2bin(this.constant)
66 * @var string
67 */
68 const COPYRIGHT_TEXT = '57502053746167696e672066696c6520666f726d61742062792052656e65204865726d656e617520262048617373616e20536861666971756520323032342f30';
69
70 /**
71 * Copyright text size
72 * @var int
73 */
74 const COPYRIGHT_TEXT_SIZE = 128;
75
76 /**
77 * @var string
78 */
79 private $magic;
80
81 /**
82 * @var int
83 */
84 private $backupVersion;
85
86 /**
87 * @var int
88 */
89 private $filesIndexStartOffset = 0;
90
91 /**
92 * @var int
93 */
94 private $filesIndexEndOffset = 0;
95
96 /**
97 * @var int
98 */
99 private $metadataStartOffset = 0;
100
101 /**
102 * @var int
103 */
104 private $metadataEndOffset = 0;
105
106 /**
107 * @var string
108 */
109 private $copyrightText;
110
111 /** @var DataEncoder */
112 private $encoder;
113
114 /** @var Version */
115 private $versionUtil;
116
117 /**
118 * BackupHeader constructor.
119 * @param DataEncoder $encoder
120 * @param Version $versionUtil
121 */
122 public function __construct(DataEncoder $encoder, Version $versionUtil)
123 {
124 $this->encoder = $encoder;
125 $this->versionUtil = $versionUtil;
126 $this->backupVersion = $this->versionUtil->convertStringFormatToIntFormat(self::BACKUP_VERSION);
127 }
128
129 /**
130 * Get backup version in XYYZZ integer format
131 *
132 * Where ZZ is the patch version from 00 to 99
133 * Where YY is the minor version from 00 to 99
134 * Where X is the major version from 0 to 429495
135 *
136 * @return int
137 */
138 public function getBackupVersion(): int
139 {
140 return $this->backupVersion;
141 }
142
143 /**
144 * Get backup version in X.Y.Z string format
145 *
146 * Where Z is the patch version from 0 to 99
147 * Where Y is the minor version from 0 to 99
148 * Where X is the major version from 0 to 429495
149 *
150 * @return string
151 */
152 public function getFormattedBackupVersion(): string
153 {
154 return $this->versionUtil->convertIntFormatToStringFormat($this->backupVersion);
155 }
156
157 public function getMetadataStartOffset(): int
158 {
159 return $this->metadataStartOffset;
160 }
161
162 public function setMetadataStartOffset(int $metadataStartOffset): BackupHeader
163 {
164 $this->metadataStartOffset = $metadataStartOffset;
165 return $this;
166 }
167
168 public function getMetadataEndOffset(): int
169 {
170 return $this->metadataEndOffset;
171 }
172
173 public function setMetadataEndOffset(int $metadataEndOffset): BackupHeader
174 {
175 $this->metadataEndOffset = $metadataEndOffset;
176 return $this;
177 }
178
179 public function getFilesIndexStartOffset(): int
180 {
181 return $this->filesIndexStartOffset;
182 }
183
184 public function setFilesIndexStartOffset(int $filesIndexStartOffset): BackupHeader
185 {
186 $this->filesIndexStartOffset = $filesIndexStartOffset;
187 return $this;
188 }
189
190 public function getFilesIndexEndOffset(): int
191 {
192 return $this->filesIndexEndOffset;
193 }
194
195 public function setFilesIndexEndOffset(int $filesIndexEndOffset): BackupHeader
196 {
197 $this->filesIndexEndOffset = $filesIndexEndOffset;
198 return $this;
199 }
200
201 /**
202 * @param string $backupFilePath
203 * @return BackupHeader
204 *
205 * @throws RuntimeException
206 */
207 public function readFromPath(string $backupFilePath): BackupHeader
208 {
209 if (!file_exists($backupFilePath)) {
210 throw new RuntimeException('Backup file not found');
211 }
212
213 $file = new FileObject($backupFilePath, FileObject::MODE_READ);
214 return $this->readFromFileObject($file);
215 }
216
217 /**
218 * @param FileObject $file
219 * @return BackupHeader
220 *
221 * @throws RuntimeException
222 */
223 public function readFromFileObject(FileObject $file): BackupHeader
224 {
225 if ($file->getSize() < self::HEADER_SIZE) {
226 throw new RuntimeException('Invalid v2 format backup file');
227 }
228
229 $file->seek(0);
230 $rawHeader = $file->fread(self::HEADER_SIZE);
231
232 return $this->setupBackupHeaderFromRaw($rawHeader);
233 }
234
235 /**
236 * @param string $rawHeader
237 *
238 * @throws InvalidArgumentException
239 * @return BackupHeader
240 */
241 public function setupBackupHeaderFromRaw(string $rawHeader): BackupHeader
242 {
243 $this->magic = rtrim(substr($rawHeader, 0, self::MAGIC_SIZE));
244 $this->copyrightText = substr($rawHeader, self::HEADER_SIZE - self::COPYRIGHT_TEXT_SIZE, self::COPYRIGHT_TEXT_SIZE); // Don't trim this, because it's fixed length with null characters
245
246 // Dynamic part of header currently in use
247 $dynamicHeader = substr($rawHeader, self::MAGIC_SIZE, $this->getHeaderInUseSize());
248 $headerIntData = $this->encoder->hexToIntArray(self::HEADER_IN_USE_HEX_FORMAT, $dynamicHeader);
249 // Change the below code into [$a, $b, $c, $d, $e] = $array format when min php is 7.1
250 $this->backupVersion = $headerIntData[0];
251 $this->filesIndexStartOffset = $headerIntData[1];
252 $this->filesIndexEndOffset = $headerIntData[2];
253 $this->metadataStartOffset = $headerIntData[3];
254 $this->metadataEndOffset = $headerIntData[4];
255
256 return $this;
257 }
258
259 public function isValidBackupHeader(): bool
260 {
261 if ($this->magic !== self::MAGIC) {
262 return false;
263 }
264
265 if ($this->copyrightText !== self::COPYRIGHT_TEXT) {
266 return false;
267 }
268
269 return version_compare($this->getFormattedBackupVersion(), self::MIN_BACKUP_VERSION, '>=');
270 }
271
272 public function getHeader(): string
273 {
274 return sprintf(
275 '%s%s%s%s',
276 str_pad(self::MAGIC, self::MAGIC_SIZE, "\0", STR_PAD_RIGHT), // let write magic as it is without converting to hex
277 $this->encoder->intArrayToHex(
278 self::HEADER_IN_USE_HEX_FORMAT, // 36-bytes of hex data
279 [
280 $this->backupVersion,
281 $this->filesIndexStartOffset,
282 $this->filesIndexEndOffset,
283 $this->metadataStartOffset,
284 $this->metadataEndOffset
285 ]
286 ),
287 bin2hex(str_pad("", $this->getUnusedBytesSize(), "\0", STR_PAD_RIGHT)),
288 self::COPYRIGHT_TEXT // 64-bytes of fixed hex data
289 );
290 }
291
292 /**
293 * @param string $backupFilePath
294 * @return void
295 */
296 public function updateHeader(string $backupFilePath)
297 {
298 $header = $this->getHeader();
299 $file = new FileObject($backupFilePath, 'r+');
300 $file->seek(0);
301 $file->fwrite($header);
302 $file = null;
303 }
304
305 /**
306 * Validate Old Backup Header
307 * @param string $content
308 * @return bool
309 */
310 public function verifyV1FormatHeader(string $content): bool
311 {
312 if (empty($content)) {
313 return false;
314 }
315
316 $wpstgBackupHeaderFileContent = DDLExporter::WPSTG_SQL_BACKUP_DUMP_HEADER;
317 $headerToVerifyLength = strlen($wpstgBackupHeaderFileContent);
318 if (substr($wpstgBackupHeaderFileContent, 0, $headerToVerifyLength) === substr($content, 0, $headerToVerifyLength)) {
319 return true;
320 }
321
322 $wpstgBackupHeaderFile = WPSTG_PLUGIN_DIR . 'Backup/wpstgBackupHeader.txt';
323 if (!file_exists($wpstgBackupHeaderFile)) {
324 return true;
325 }
326
327 $wpstgBackupHeaderFileContent = file_get_contents($wpstgBackupHeaderFile);
328 $headerToVerifyLength = self::HEADER_SIZE;
329 if (!empty($wpstgBackupHeaderFileContent) && substr($wpstgBackupHeaderFileContent, 0, $headerToVerifyLength) === substr($content, 0, $headerToVerifyLength)) {
330 return true;
331 }
332
333 return false;
334 }
335
336 public function getV1FormatHeader(): string
337 {
338 $wpstgBackupHeaderFile = WPSTG_PLUGIN_DIR . 'Backup/wpstgBackupHeader.txt';
339 // Should not happen
340 if (!file_exists($wpstgBackupHeaderFile)) {
341 return "";
342 }
343
344 return file_get_contents($wpstgBackupHeaderFile);
345 }
346
347 private function getHeaderInUseSize(): int
348 {
349 $size = 0;
350 for ($i = 0; $i < strlen(self::HEADER_IN_USE_HEX_FORMAT); $i++) {
351 $size += intval(substr(self::HEADER_IN_USE_HEX_FORMAT, $i, 1));
352 }
353
354 return $size * 2;
355 }
356
357 private function getUnusedBytesSize(): int
358 {
359 return (self::HEADER_SIZE - $this->getHeaderInUseSize() - self::MAGIC_SIZE - self::COPYRIGHT_TEXT_SIZE) / 2;
360 }
361 }
362