1 | <?php |
||
2 | /* vim: set expandtab sw=4 ts=4 sts=4: */ |
||
3 | /** |
||
4 | * file upload functions |
||
5 | * |
||
6 | * @package PhpMyAdmin |
||
7 | */ |
||
8 | declare(strict_types=1); |
||
9 | |||
10 | namespace PhpMyAdmin; |
||
11 | |||
12 | use PhpMyAdmin\Core; |
||
13 | use PhpMyAdmin\Message; |
||
14 | use PhpMyAdmin\Util; |
||
15 | use PhpMyAdmin\ZipExtension; |
||
16 | |||
17 | /** |
||
18 | * File wrapper class |
||
19 | * |
||
20 | * @todo when uploading a file into a blob field, should we also consider using |
||
21 | * chunks like in import? UPDATE `table` SET `field` = `field` + [chunk] |
||
22 | * |
||
23 | * @package PhpMyAdmin |
||
24 | */ |
||
25 | class File |
||
26 | { |
||
27 | /** |
||
28 | * @var string the temporary file name |
||
29 | * @access protected |
||
30 | */ |
||
31 | protected $_name = null; |
||
32 | |||
33 | /** |
||
34 | * @var string the content |
||
35 | * @access protected |
||
36 | */ |
||
37 | protected $_content = null; |
||
38 | |||
39 | /** |
||
40 | * @var Message|null the error message |
||
41 | * @access protected |
||
42 | */ |
||
43 | protected $_error_message = null; |
||
44 | |||
45 | /** |
||
46 | * @var bool whether the file is temporary or not |
||
47 | * @access protected |
||
48 | */ |
||
49 | protected $_is_temp = false; |
||
50 | |||
51 | /** |
||
52 | * @var string type of compression |
||
53 | * @access protected |
||
54 | */ |
||
55 | protected $_compression = null; |
||
56 | |||
57 | /** |
||
58 | * @var integer |
||
59 | */ |
||
60 | protected $_offset = 0; |
||
61 | |||
62 | /** |
||
63 | * @var integer size of chunk to read with every step |
||
64 | */ |
||
65 | protected $_chunk_size = 32768; |
||
66 | |||
67 | /** |
||
68 | * @var resource file handle |
||
69 | */ |
||
70 | protected $_handle = null; |
||
71 | |||
72 | /** |
||
73 | * @var boolean whether to decompress content before returning |
||
74 | */ |
||
75 | protected $_decompress = false; |
||
76 | |||
77 | /** |
||
78 | * @var string charset of file |
||
79 | */ |
||
80 | protected $_charset = null; |
||
81 | |||
82 | /** |
||
83 | * @var ZipExtension |
||
84 | */ |
||
85 | private $zipExtension; |
||
86 | |||
87 | /** |
||
88 | * constructor |
||
89 | * |
||
90 | * @param boolean|string $name file name or false |
||
91 | * |
||
92 | * @access public |
||
93 | */ |
||
94 | public function __construct($name = false) |
||
95 | { |
||
96 | if ($name && is_string($name)) { |
||
97 | $this->setName($name); |
||
98 | } |
||
99 | |||
100 | if (extension_loaded('zip')) { |
||
101 | $this->zipExtension = new ZipExtension(); |
||
102 | } |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * destructor |
||
107 | * |
||
108 | * @see File::cleanUp() |
||
109 | * @access public |
||
110 | */ |
||
111 | public function __destruct() |
||
112 | { |
||
113 | $this->cleanUp(); |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * deletes file if it is temporary, usually from a moved upload file |
||
118 | * |
||
119 | * @access public |
||
120 | * @return boolean success |
||
121 | */ |
||
122 | public function cleanUp(): bool |
||
123 | { |
||
124 | if ($this->isTemp()) { |
||
125 | return $this->delete(); |
||
126 | } |
||
127 | |||
128 | return true; |
||
129 | } |
||
130 | |||
131 | /** |
||
132 | * deletes the file |
||
133 | * |
||
134 | * @access public |
||
135 | * @return boolean success |
||
136 | */ |
||
137 | public function delete(): bool |
||
138 | { |
||
139 | return unlink($this->getName()); |
||
140 | } |
||
141 | |||
142 | /** |
||
143 | * checks or sets the temp flag for this file |
||
144 | * file objects with temp flags are deleted with object destruction |
||
145 | * |
||
146 | * @param boolean $is_temp sets the temp flag |
||
147 | * |
||
148 | * @return boolean File::$_is_temp |
||
149 | * @access public |
||
150 | */ |
||
151 | public function isTemp(?bool $is_temp = null): bool |
||
152 | { |
||
153 | if (null !== $is_temp) { |
||
154 | $this->_is_temp = $is_temp; |
||
155 | } |
||
156 | |||
157 | return $this->_is_temp; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * accessor |
||
162 | * |
||
163 | * @param string|null $name file name |
||
164 | * |
||
165 | * @return void |
||
166 | * @access public |
||
167 | */ |
||
168 | public function setName(?string $name): void |
||
169 | { |
||
170 | $this->_name = trim($name); |
||
171 | } |
||
172 | |||
173 | /** |
||
174 | * Gets file content |
||
175 | * |
||
176 | * @return string|false the binary file content, |
||
177 | * or false if no content |
||
178 | * |
||
179 | * @access public |
||
180 | */ |
||
181 | public function getRawContent() |
||
182 | { |
||
183 | if (null === $this->_content) { |
||
184 | if ($this->isUploaded() && ! $this->checkUploadedFile()) { |
||
185 | return false; |
||
186 | } |
||
187 | |||
188 | if (! $this->isReadable()) { |
||
189 | return false; |
||
190 | } |
||
191 | |||
192 | if (function_exists('file_get_contents')) { |
||
193 | $this->_content = file_get_contents($this->getName()); |
||
194 | } elseif ($size = filesize($this->getName())) { |
||
195 | $handle = fopen($this->getName(), 'rb'); |
||
196 | $this->_content = fread($handle, $size); |
||
197 | fclose($handle); |
||
198 | } |
||
199 | } |
||
200 | |||
201 | return $this->_content; |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * Gets file content |
||
206 | * |
||
207 | * @return string|false the binary file content as a string, |
||
208 | * or false if no content |
||
209 | * |
||
210 | * @access public |
||
211 | */ |
||
212 | public function getContent() |
||
213 | { |
||
214 | $result = $this->getRawContent(); |
||
215 | if ($result === false) { |
||
216 | return false; |
||
217 | } |
||
218 | return '0x' . bin2hex($result); |
||
219 | } |
||
220 | |||
221 | /** |
||
222 | * Whether file is uploaded. |
||
223 | * |
||
224 | * @access public |
||
225 | * |
||
226 | * @return bool |
||
227 | */ |
||
228 | public function isUploaded(): bool |
||
229 | { |
||
230 | return is_uploaded_file($this->getName()); |
||
231 | } |
||
232 | |||
233 | /** |
||
234 | * accessor |
||
235 | * |
||
236 | * @access public |
||
237 | * @return string|null File::$_name |
||
238 | */ |
||
239 | public function getName(): ?string |
||
240 | { |
||
241 | return $this->_name; |
||
242 | } |
||
243 | |||
244 | /** |
||
245 | * Initializes object from uploaded file. |
||
246 | * |
||
247 | * @param string $name name of file uploaded |
||
248 | * |
||
249 | * @return boolean success |
||
250 | * @access public |
||
251 | */ |
||
252 | public function setUploadedFile(string $name): bool |
||
253 | { |
||
254 | $this->setName($name); |
||
255 | |||
256 | if (! $this->isUploaded()) { |
||
257 | $this->setName(null); |
||
258 | $this->_error_message = Message::error(__('File was not an uploaded file.')); |
||
259 | return false; |
||
260 | } |
||
261 | |||
262 | return true; |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Loads uploaded file from table change request. |
||
267 | * |
||
268 | * @param string $key the md5 hash of the column name |
||
269 | * @param string $rownumber number of row to process |
||
270 | * |
||
271 | * @return boolean success |
||
272 | * @access public |
||
273 | */ |
||
274 | public function setUploadedFromTblChangeRequest( |
||
275 | string $key, |
||
276 | string $rownumber |
||
277 | ): bool { |
||
278 | if (! isset($_FILES['fields_upload']) |
||
279 | || empty($_FILES['fields_upload']['name']['multi_edit'][$rownumber][$key]) |
||
280 | ) { |
||
281 | return false; |
||
282 | } |
||
283 | $file = File::fetchUploadedFromTblChangeRequestMultiple( |
||
284 | $_FILES['fields_upload'], |
||
285 | $rownumber, |
||
286 | $key |
||
287 | ); |
||
288 | |||
289 | // check for file upload errors |
||
290 | switch ($file['error']) { |
||
291 | // we do not use the PHP constants here cause not all constants |
||
292 | // are defined in all versions of PHP - but the correct constants names |
||
293 | // are given as comment |
||
294 | case 0: //UPLOAD_ERR_OK: |
||
295 | return $this->setUploadedFile($file['tmp_name']); |
||
296 | case 4: //UPLOAD_ERR_NO_FILE: |
||
297 | break; |
||
298 | case 1: //UPLOAD_ERR_INI_SIZE: |
||
299 | $this->_error_message = Message::error(__( |
||
300 | 'The uploaded file exceeds the upload_max_filesize directive in ' |
||
301 | . 'php.ini.' |
||
302 | )); |
||
303 | break; |
||
304 | case 2: //UPLOAD_ERR_FORM_SIZE: |
||
305 | $this->_error_message = Message::error(__( |
||
306 | 'The uploaded file exceeds the MAX_FILE_SIZE directive that was ' |
||
307 | . 'specified in the HTML form.' |
||
308 | )); |
||
309 | break; |
||
310 | case 3: //UPLOAD_ERR_PARTIAL: |
||
311 | $this->_error_message = Message::error(__( |
||
312 | 'The uploaded file was only partially uploaded.' |
||
313 | )); |
||
314 | break; |
||
315 | case 6: //UPLOAD_ERR_NO_TMP_DIR: |
||
316 | $this->_error_message = Message::error(__('Missing a temporary folder.')); |
||
317 | break; |
||
318 | case 7: //UPLOAD_ERR_CANT_WRITE: |
||
319 | $this->_error_message = Message::error(__('Failed to write file to disk.')); |
||
320 | break; |
||
321 | case 8: //UPLOAD_ERR_EXTENSION: |
||
322 | $this->_error_message = Message::error(__('File upload stopped by extension.')); |
||
323 | break; |
||
324 | default: |
||
325 | $this->_error_message = Message::error(__('Unknown error in file upload.')); |
||
326 | } // end switch |
||
327 | |||
328 | return false; |
||
329 | } |
||
330 | |||
331 | /** |
||
332 | * strips some dimension from the multi-dimensional array from $_FILES |
||
333 | * |
||
334 | * <code> |
||
335 | * $file['name']['multi_edit'][$rownumber][$key] = [value] |
||
336 | * $file['type']['multi_edit'][$rownumber][$key] = [value] |
||
337 | * $file['size']['multi_edit'][$rownumber][$key] = [value] |
||
338 | * $file['tmp_name']['multi_edit'][$rownumber][$key] = [value] |
||
339 | * $file['error']['multi_edit'][$rownumber][$key] = [value] |
||
340 | * |
||
341 | * // becomes: |
||
342 | * |
||
343 | * $file['name'] = [value] |
||
344 | * $file['type'] = [value] |
||
345 | * $file['size'] = [value] |
||
346 | * $file['tmp_name'] = [value] |
||
347 | * $file['error'] = [value] |
||
348 | * </code> |
||
349 | * |
||
350 | * @param array $file the array |
||
351 | * @param string $rownumber number of row to process |
||
352 | * @param string $key key to process |
||
353 | * |
||
354 | * @return array |
||
355 | * @access public |
||
356 | * @static |
||
357 | */ |
||
358 | public function fetchUploadedFromTblChangeRequestMultiple( |
||
359 | array $file, |
||
360 | string $rownumber, |
||
361 | string $key |
||
362 | ): array { |
||
363 | $new_file = [ |
||
364 | 'name' => $file['name']['multi_edit'][$rownumber][$key], |
||
365 | 'type' => $file['type']['multi_edit'][$rownumber][$key], |
||
366 | 'size' => $file['size']['multi_edit'][$rownumber][$key], |
||
367 | 'tmp_name' => $file['tmp_name']['multi_edit'][$rownumber][$key], |
||
368 | 'error' => $file['error']['multi_edit'][$rownumber][$key], |
||
369 | ]; |
||
370 | |||
371 | return $new_file; |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * sets the name if the file to the one selected in the tbl_change form |
||
376 | * |
||
377 | * @param string $key the md5 hash of the column name |
||
378 | * @param string $rownumber number of row to process |
||
379 | * |
||
380 | * @return boolean success |
||
381 | * @access public |
||
382 | */ |
||
383 | public function setSelectedFromTblChangeRequest( |
||
384 | string $key, |
||
385 | ?string $rownumber = null |
||
386 | ): bool { |
||
387 | if (! empty($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]) |
||
388 | && is_string($_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key]) |
||
389 | ) { |
||
390 | // ... whether with multiple rows ... |
||
391 | return $this->setLocalSelectedFile( |
||
392 | $_REQUEST['fields_uploadlocal']['multi_edit'][$rownumber][$key] |
||
393 | ); |
||
394 | } |
||
395 | |||
396 | return false; |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Returns possible error message. |
||
401 | * |
||
402 | * @access public |
||
403 | * @return Message|null error message |
||
404 | */ |
||
405 | public function getError(): ?Message |
||
406 | { |
||
407 | return $this->_error_message; |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Checks whether there was any error. |
||
412 | * |
||
413 | * @access public |
||
414 | * @return boolean whether an error occurred or not |
||
415 | */ |
||
416 | public function isError(): bool |
||
417 | { |
||
418 | return ! is_null($this->_error_message); |
||
419 | } |
||
420 | |||
421 | /** |
||
422 | * checks the superglobals provided if the tbl_change form is submitted |
||
423 | * and uses the submitted/selected file |
||
424 | * |
||
425 | * @param string $key the md5 hash of the column name |
||
426 | * @param string $rownumber number of row to process |
||
427 | * |
||
428 | * @return boolean success |
||
429 | * @access public |
||
430 | */ |
||
431 | public function checkTblChangeForm(string $key, string $rownumber): bool |
||
432 | { |
||
433 | if ($this->setUploadedFromTblChangeRequest($key, $rownumber)) { |
||
434 | // well done ... |
||
435 | $this->_error_message = null; |
||
436 | return true; |
||
437 | } elseif ($this->setSelectedFromTblChangeRequest($key, $rownumber)) { |
||
438 | // well done ... |
||
439 | $this->_error_message = null; |
||
440 | return true; |
||
441 | } |
||
442 | // all failed, whether just no file uploaded/selected or an error |
||
443 | |||
444 | return false; |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Sets named file to be read from UploadDir. |
||
449 | * |
||
450 | * @param string $name file name |
||
451 | * |
||
452 | * @return boolean success |
||
453 | * @access public |
||
454 | */ |
||
455 | public function setLocalSelectedFile(string $name): bool |
||
456 | { |
||
457 | if (empty($GLOBALS['cfg']['UploadDir'])) { |
||
458 | return false; |
||
459 | } |
||
460 | |||
461 | $this->setName( |
||
462 | Util::userDir($GLOBALS['cfg']['UploadDir']) . Core::securePath($name) |
||
463 | ); |
||
464 | if (@is_link($this->getName())) { |
||
465 | $this->_error_message = __('File is a symbolic link'); |
||
466 | $this->setName(null); |
||
467 | return false; |
||
468 | } |
||
469 | if (! $this->isReadable()) { |
||
470 | $this->_error_message = Message::error(__('File could not be read!')); |
||
471 | $this->setName(null); |
||
472 | return false; |
||
473 | } |
||
474 | |||
475 | return true; |
||
476 | } |
||
477 | |||
478 | /** |
||
479 | * Checks whether file can be read. |
||
480 | * |
||
481 | * @access public |
||
482 | * @return boolean whether the file is readable or not |
||
483 | */ |
||
484 | public function isReadable(): bool |
||
485 | { |
||
486 | // suppress warnings from being displayed, but not from being logged |
||
487 | // any file access outside of open_basedir will issue a warning |
||
488 | return @is_readable((string) $this->getName()); |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * If we are on a server with open_basedir, we must move the file |
||
493 | * before opening it. The FAQ 1.11 explains how to create the "./tmp" |
||
494 | * directory - if needed |
||
495 | * |
||
496 | * @todo move check of $cfg['TempDir'] into Config? |
||
497 | * @access public |
||
498 | * @return boolean whether uploaded file is fine or not |
||
499 | */ |
||
500 | public function checkUploadedFile(): bool |
||
501 | { |
||
502 | if ($this->isReadable()) { |
||
503 | return true; |
||
504 | } |
||
505 | |||
506 | $tmp_subdir = $GLOBALS['PMA_Config']->getUploadTempDir(); |
||
507 | if (is_null($tmp_subdir)) { |
||
508 | // cannot create directory or access, point user to FAQ 1.11 |
||
509 | $this->_error_message = Message::error(__( |
||
510 | 'Error moving the uploaded file, see [doc@faq1-11]FAQ 1.11[/doc].' |
||
511 | )); |
||
512 | return false; |
||
513 | } |
||
514 | |||
515 | $new_file_to_upload = tempnam( |
||
516 | $tmp_subdir, |
||
517 | basename($this->getName()) |
||
518 | ); |
||
519 | |||
520 | // suppress warnings from being displayed, but not from being logged |
||
521 | // any file access outside of open_basedir will issue a warning |
||
522 | ob_start(); |
||
523 | $move_uploaded_file_result = move_uploaded_file( |
||
524 | $this->getName(), |
||
525 | $new_file_to_upload |
||
526 | ); |
||
527 | ob_end_clean(); |
||
528 | if (! $move_uploaded_file_result) { |
||
529 | $this->_error_message = Message::error(__('Error while moving uploaded file.')); |
||
530 | return false; |
||
531 | } |
||
532 | |||
533 | $this->setName($new_file_to_upload); |
||
534 | $this->isTemp(true); |
||
535 | |||
536 | if (! $this->isReadable()) { |
||
537 | $this->_error_message = Message::error(__('Cannot read uploaded file.')); |
||
538 | return false; |
||
539 | } |
||
540 | |||
541 | return true; |
||
542 | } |
||
543 | |||
544 | /** |
||
545 | * Detects what compression the file uses |
||
546 | * |
||
547 | * @todo move file read part into readChunk() or getChunk() |
||
548 | * @todo add support for compression plugins |
||
549 | * @access protected |
||
550 | * @return string|false false on error, otherwise string MIME type of |
||
551 | * compression, none for none |
||
552 | */ |
||
553 | protected function detectCompression() |
||
554 | { |
||
555 | // suppress warnings from being displayed, but not from being logged |
||
556 | // f.e. any file access outside of open_basedir will issue a warning |
||
557 | ob_start(); |
||
558 | $file = fopen($this->getName(), 'rb'); |
||
559 | ob_end_clean(); |
||
560 | |||
561 | if (! $file) { |
||
0 ignored issues
–
show
introduced
by
Loading history...
|
|||
562 | $this->_error_message = Message::error(__('File could not be read!')); |
||
563 | return false; |
||
564 | } |
||
565 | |||
566 | $this->_compression = Util::getCompressionMimeType($file); |
||
567 | return $this->_compression; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * Sets whether the content should be decompressed before returned |
||
572 | * |
||
573 | * @param boolean $decompress whether to decompress |
||
574 | * |
||
575 | * @return void |
||
576 | */ |
||
577 | public function setDecompressContent(bool $decompress): void |
||
578 | { |
||
579 | $this->_decompress = $decompress; |
||
580 | } |
||
581 | |||
582 | /** |
||
583 | * Returns the file handle |
||
584 | * |
||
585 | * @return resource file handle |
||
586 | */ |
||
587 | public function getHandle() |
||
588 | { |
||
589 | if (null === $this->_handle) { |
||
590 | $this->open(); |
||
591 | } |
||
592 | return $this->_handle; |
||
0 ignored issues
–
show
The expression
return $this->_handle could also return false which is incompatible with the documented return type resource . Did you maybe forget to handle an error condition?
If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.
Loading history...
|
|||
593 | } |
||
594 | |||
595 | /** |
||
596 | * Sets the file handle |
||
597 | * |
||
598 | * @param resource $handle file handle |
||
599 | * |
||
600 | * @return void |
||
601 | */ |
||
602 | public function setHandle($handle): void |
||
603 | { |
||
604 | $this->_handle = $handle; |
||
605 | } |
||
606 | |||
607 | |||
608 | /** |
||
609 | * Sets error message for unsupported compression. |
||
610 | * |
||
611 | * @return void |
||
612 | */ |
||
613 | public function errorUnsupported(): void |
||
614 | { |
||
615 | $this->_error_message = Message::error(sprintf( |
||
616 | __( |
||
617 | 'You attempted to load file with unsupported compression (%s). ' |
||
618 | . 'Either support for it is not implemented or disabled by your ' |
||
619 | . 'configuration.' |
||
620 | ), |
||
621 | $this->getCompression() |
||
622 | )); |
||
623 | } |
||
624 | |||
625 | /** |
||
626 | * Attempts to open the file. |
||
627 | * |
||
628 | * @return bool |
||
629 | */ |
||
630 | public function open(): bool |
||
631 | { |
||
632 | if (! $this->_decompress) { |
||
633 | $this->_handle = @fopen($this->getName(), 'r'); |
||
0 ignored issues
–
show
It seems like
@fopen($this->getName(), 'r') can also be of type false . However, the property $_handle is declared as type resource . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
Loading history...
|
|||
634 | } |
||
635 | |||
636 | switch ($this->getCompression()) { |
||
637 | case false: |
||
638 | return false; |
||
639 | case 'application/bzip2': |
||
640 | if ($GLOBALS['cfg']['BZipDump'] && function_exists('bzopen')) { |
||
641 | $this->_handle = @bzopen($this->getName(), 'r'); |
||
642 | } else { |
||
643 | $this->errorUnsupported(); |
||
644 | return false; |
||
645 | } |
||
646 | break; |
||
647 | case 'application/gzip': |
||
648 | if ($GLOBALS['cfg']['GZipDump'] && function_exists('gzopen')) { |
||
649 | $this->_handle = @gzopen($this->getName(), 'r'); |
||
650 | } else { |
||
651 | $this->errorUnsupported(); |
||
652 | return false; |
||
653 | } |
||
654 | break; |
||
655 | case 'application/zip': |
||
656 | if ($GLOBALS['cfg']['ZipDump'] && function_exists('zip_open')) { |
||
657 | return $this->openZip(); |
||
658 | } |
||
659 | |||
660 | $this->errorUnsupported(); |
||
661 | return false; |
||
662 | case 'none': |
||
663 | $this->_handle = @fopen($this->getName(), 'r'); |
||
664 | break; |
||
665 | default: |
||
666 | $this->errorUnsupported(); |
||
667 | return false; |
||
668 | } |
||
669 | |||
670 | return ($this->_handle !== false); |
||
671 | } |
||
672 | |||
673 | /** |
||
674 | * Opens file from zip |
||
675 | * |
||
676 | * @param string|null $specific_entry Entry to open |
||
677 | * |
||
678 | * @return bool |
||
679 | */ |
||
680 | public function openZip(?string $specific_entry = null): bool |
||
681 | { |
||
682 | $result = $this->zipExtension->getContents($this->getName(), $specific_entry); |
||
683 | if (! empty($result['error'])) { |
||
684 | $this->_error_message = Message::rawError($result['error']); |
||
685 | return false; |
||
686 | } |
||
687 | $this->_content = $result['data']; |
||
688 | $this->_offset = 0; |
||
689 | return true; |
||
690 | } |
||
691 | |||
692 | /** |
||
693 | * Checks whether we've reached end of file |
||
694 | * |
||
695 | * @return bool |
||
696 | */ |
||
697 | public function eof(): bool |
||
698 | { |
||
699 | if (! is_null($this->_handle)) { |
||
700 | return feof($this->_handle); |
||
701 | } |
||
702 | return $this->_offset == strlen($this->_content); |
||
703 | } |
||
704 | |||
705 | /** |
||
706 | * Closes the file |
||
707 | * |
||
708 | * @return void |
||
709 | */ |
||
710 | public function close(): void |
||
711 | { |
||
712 | if (! is_null($this->_handle)) { |
||
713 | fclose($this->_handle); |
||
714 | $this->_handle = null; |
||
715 | } else { |
||
716 | $this->_content = ''; |
||
717 | $this->_offset = 0; |
||
718 | } |
||
719 | $this->cleanUp(); |
||
720 | } |
||
721 | |||
722 | /** |
||
723 | * Reads data from file |
||
724 | * |
||
725 | * @param int $size Number of bytes to read |
||
726 | * |
||
727 | * @return string |
||
728 | */ |
||
729 | public function read(int $size): string |
||
730 | { |
||
731 | switch ($this->_compression) { |
||
732 | case 'application/bzip2': |
||
733 | return bzread($this->_handle, $size); |
||
734 | case 'application/gzip': |
||
735 | return gzread($this->_handle, $size); |
||
736 | case 'application/zip': |
||
737 | $result = mb_strcut($this->_content, $this->_offset, $size); |
||
738 | $this->_offset += strlen($result); |
||
739 | return $result; |
||
740 | case 'none': |
||
741 | default: |
||
742 | return fread($this->_handle, $size); |
||
743 | } |
||
744 | } |
||
745 | |||
746 | /** |
||
747 | * Returns the character set of the file |
||
748 | * |
||
749 | * @return string character set of the file |
||
750 | */ |
||
751 | public function getCharset(): string |
||
752 | { |
||
753 | return $this->_charset; |
||
754 | } |
||
755 | |||
756 | /** |
||
757 | * Sets the character set of the file |
||
758 | * |
||
759 | * @param string $charset character set of the file |
||
760 | * |
||
761 | * @return void |
||
762 | */ |
||
763 | public function setCharset(string $charset): void |
||
764 | { |
||
765 | $this->_charset = $charset; |
||
766 | } |
||
767 | |||
768 | /** |
||
769 | * Returns compression used by file. |
||
770 | * |
||
771 | * @return string MIME type of compression, none for none |
||
772 | * @access public |
||
773 | */ |
||
774 | public function getCompression(): string |
||
775 | { |
||
776 | if (null === $this->_compression) { |
||
777 | return $this->detectCompression(); |
||
778 | } |
||
779 | |||
780 | return $this->_compression; |
||
781 | } |
||
782 | |||
783 | /** |
||
784 | * Returns the offset |
||
785 | * |
||
786 | * @return integer the offset |
||
787 | */ |
||
788 | public function getOffset(): int |
||
789 | { |
||
790 | return $this->_offset; |
||
791 | } |
||
792 | |||
793 | /** |
||
794 | * Returns the chunk size |
||
795 | * |
||
796 | * @return integer the chunk size |
||
797 | */ |
||
798 | public function getChunkSize(): int |
||
799 | { |
||
800 | return $this->_chunk_size; |
||
801 | } |
||
802 | |||
803 | /** |
||
804 | * Sets the chunk size |
||
805 | * |
||
806 | * @param integer $chunk_size the chunk size |
||
807 | * |
||
808 | * @return void |
||
809 | */ |
||
810 | public function setChunkSize(int $chunk_size): void |
||
811 | { |
||
812 | $this->_chunk_size = $chunk_size; |
||
813 | } |
||
814 | |||
815 | /** |
||
816 | * Returns the length of the content in the file |
||
817 | * |
||
818 | * @return integer the length of the file content |
||
819 | */ |
||
820 | public function getContentLength(): int |
||
821 | { |
||
822 | return strlen($this->_content); |
||
823 | } |
||
824 | } |
||
825 |