PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / 3.1.18.8
JetBackup – Backup, Restore & Migrate v3.1.18.8
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / SGB / Extractor.php
backup / src / JetBackup / SGB Last commit date
.htaccess 5 months ago Extractor.php 5 months ago Migration.php 5 months ago index.html 5 months ago web.config 5 months ago
Extractor.php
255 lines
1 <?php
2
3 namespace JetBackup\SGB;
4
5 use JetBackup\Exception\SGBExtractorException;
6 use JetBackup\JetBackup;
7 use JetBackup\Log\LogController;
8
9 if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');
10
11 class Extractor {
12
13 private $_fd;
14 private $_target;
15 private $_info_file;
16 private LogController $_logController;
17
18 /**
19 * @param string $file
20 * @param string $target
21 *
22 * @throws SGBExtractorException
23 */
24 public function __construct(string $file, string $target) {
25 if(!($this->_fd = fopen($file, "rb")))
26 throw new SGBExtractorException("Failed opening file");
27
28 // Move FD pointer to place
29 $this->_seek(-4, SEEK_END);
30 $footer_offset = $this->_readHex(4);
31 $this->_seek(-$footer_offset, SEEK_END);
32
33 $this->_info_file = $file . '.info';
34 $this->_target = $target;
35 $this->_logController = new LogController();
36
37 }
38
39 /**
40 * @param LogController $logController
41 *
42 * @return void
43 */
44 public function setLogController(LogController $logController) { $this->_logController = $logController; }
45
46 /**
47 * @return LogController
48 */
49 public function getLogController():LogController { return $this->_logController; }
50
51 /**
52 * @param int $length
53 *
54 * @return string|null
55 */
56 private function _read(int $length):?string {
57 $read = fread($this->_fd, $length);
58 return $read === false ? null : $read;
59 }
60
61 /**
62 * @param int $position
63 * @param int $whence
64 *
65 * @return int
66 */
67 private function _seek(int $position, int $whence=SEEK_SET):int {
68 return fseek($this->_fd, $position, $whence);
69 }
70
71 /**
72 * @param int $length
73 *
74 * @return float|int
75 */
76 private function _readHex(int $length) {
77 return hexdec(self::_unpackLittleEndian($this->_read($length), $length) );
78 }
79
80 /**
81 * @return array
82 */
83 private function _getExtra():array {
84 $version = $this->_readHex(1);
85 $extra_size = $this->_readHex(4);
86 $extra = $extra_size > 0 ? $this->_read($extra_size) : [];
87
88 if(is_string($extra)) {
89 $extra = json_decode($extra, true);
90 if(is_string($extra['tables'])) $extra['tables'] = json_decode($extra['tables'], true);
91 }
92
93 if(is_array($extra)) $extra['versions'] = $version;
94
95 return $extra;
96 }
97
98 /**
99 * @return array
100 */
101 private function _fetchFilesList():array {
102
103 $total_files = $this->_readHex(4);
104
105 $files = [];
106
107 for($i = 0; $i < $total_files; $i++) {
108
109 // we don't need the crc, just move the pointer 4 bytes
110 $this->_seek(4, SEEK_CUR);
111 //
112
113 if (($filenameLen = $this->_readHex(2)) <= 0) continue;
114
115 $file = [];
116 $file['filename'] = $this->_read($filenameLen);
117 $file['offset'] = $this->_readHex(8);
118 $file['chunks'] = [];
119 // We don't need the compressed/uncompressed length, just move the pointer 16 bytes (8 bytes for each one)
120 $this->_seek(16, SEEK_CUR);
121 //
122 $total_chunks = $this->_readHex(4);
123
124 for($j = 0; $j < $total_chunks; $j++) {
125 $file['chunks'][] = [
126 'start' => $this->_readHex(8),
127 'size' => $this->_readHex(8),
128 ];
129 }
130
131 $files[] = $file;
132 }
133
134 return $files;
135 }
136
137 /**
138 * @param array $file
139 *
140 * @return void
141 * @throws SGBExtractorException
142 */
143 private function _extractFile(array $file):void {
144
145 $target = $this->_target . JetBackup::SEP . $file['filename'];
146
147 if(!is_dir(dirname($target))) mkdir(dirname($target), 0755, true);
148
149 $target_temp = $target . '.sgbpTemFile';
150
151 $chunks = $file['chunks'];
152
153 $info = $this->_getInfo();
154
155 if($info->chunk <= 0 && file_exists($target_temp)) unlink($target_temp);
156 $fd = fopen($target_temp, "ab");
157
158 if($info->chunk > 0) $chunks = array_splice($chunks, $info->chunk);
159
160 foreach($chunks as $chunk) {
161 fseek($fd, $chunk['start']);
162
163 $this->_seek($file['offset'] + 4 + $chunk['start']);
164 $chunk_data = $this->_read($chunk['size']);
165
166 if (
167 !($file_data = gzinflate($chunk_data)) ||
168 fwrite($fd, $file_data) == false
169 ) {
170 fclose($fd);
171 unlink($target_temp);
172 throw new SGBExtractorException("Failed to extracting file $target");
173 }
174
175 $info->chunk++;
176 $this->_updateInfo($info);
177 }
178
179 fflush($fd);
180 fclose($fd);
181
182 if(!@rename($target_temp, $target)) {
183 unlink($target_temp);
184 throw new SGBExtractorException("Error when trying to move $target_temp to $target");
185 }
186 }
187
188 /**
189 * @return void
190 */
191 public function extract(?callable $callback=null):void {
192
193 // read the extra data just to move the FD pointer to place, we don't really need this data
194 $this->_getExtra();
195
196 $files = $this->_fetchFilesList();
197
198 $info = $this->_getInfo();
199
200 if($info->file > 0) $files = array_splice($files, $info->file);
201
202 foreach($files as $file) {
203 try {
204 $this->_extractFile($file);
205 } catch(SGBExtractorException $e) {
206 $this->getLogController()->logError("Failed extracting file '{$file['filename']}', skipping to next file. Error: " . $e->getMessage());
207 }
208
209 $info->file++;
210 $info->chunk = 0;
211 $this->_updateInfo($info);
212
213 if($callback) $callback();
214 }
215
216 @unlink($this->_info_file);
217 @unlink($this->_info_file . '.tmp');
218 }
219
220 /**
221 * @param string $data
222 * @param int $size
223 *
224 * @return string
225 */
226 private static function _unpackLittleEndian(string $data, int $size):string {
227 return unpack('H' . ($size * 2), strrev($data))[1];
228 }
229
230 private function _getInfo() {
231
232 if(file_exists($this->_info_file)) {
233 $data = file_get_contents($this->_info_file);
234 if($data && ($output = json_decode($data))) return $output;
235 }
236
237 $output = new \stdClass();
238 $output->file = 0;
239 $output->chunk = 0;
240 return $output;
241 }
242
243 private function _updateInfo($data) {
244 $tempFile = $this->_info_file . '.tmp';
245 $jsonData = json_encode($data);
246 if ($jsonData === false)
247 throw new SGBExtractorException("Failed to encode compress information");
248
249 if (file_put_contents($tempFile, $jsonData) === false)
250 throw new SGBExtractorException("Failed to write compress information to temporary file");
251
252 if (!rename($tempFile, $this->_info_file))
253 throw new SGBExtractorException("Failed to atomically write compress information");
254 }
255 }