]> git.mxchange.org Git - friendica-addons.git/blob - s3_storage/vendor/akeeba/s3/src/Input.php
Merge pull request #1236 from nupplaphil/feat/s3
[friendica-addons.git] / s3_storage / vendor / akeeba / s3 / src / Input.php
1 <?php
2 /**
3  * Akeeba Engine
4  *
5  * @package   akeebaengine
6  * @copyright Copyright (c)2006-2020 Nicholas K. Dionysopoulos / Akeeba Ltd
7  * @license   GNU General Public License version 3, or later
8  */
9
10 namespace Akeeba\Engine\Postproc\Connector\S3v4;
11
12 // Protection against direct access
13 defined('AKEEBAENGINE') or die();
14
15 /**
16  * Defines an input source for PUT/POST requests to Amazon S3
17  */
18 class Input
19 {
20         /**
21          * Input type: resource
22          */
23         const INPUT_RESOURCE = 1;
24
25         /**
26          * Input type: file
27          */
28         const INPUT_FILE = 2;
29
30         /**
31          * Input type: raw data
32          */
33         const INPUT_DATA = 3;
34
35         /**
36          * File pointer, in case we have a resource
37          *
38          * @var  resource
39          */
40         private $fp = null;
41
42         /**
43          * Absolute filename to the file
44          *
45          * @var  string
46          */
47         private $file = null;
48
49         /**
50          * Data to upload, as a string
51          *
52          * @var  string
53          */
54         private $data = null;
55
56         /**
57          * Length of the data to upload
58          *
59          * @var  int
60          */
61         private $size = -1;
62
63         /**
64          * Content type (MIME type)
65          *
66          * @var  string|null
67          */
68         private $type = '';
69
70         /**
71          * MD5 sum of the data to upload, as base64 encoded string. If it's false no MD5 sum will be returned.
72          *
73          * @var  string|null
74          */
75         private $md5sum = null;
76
77         /**
78          * SHA-256 sum of the data to upload, as lowercase hex string.
79          *
80          * @var  string|null
81          */
82         private $sha256 = null;
83
84         /**
85          * The Upload Session ID used for multipart uploads
86          *
87          * @var  string|null
88          */
89         private $UploadID = null;
90
91         /**
92          * The part number used in multipart uploads
93          *
94          * @var  int|null
95          */
96         private $PartNumber = null;
97
98         /**
99          * The list of ETags used when finalising a multipart upload
100          *
101          * @var  string[]
102          */
103         private $etags = [];
104
105         /**
106          * Create an input object from a file (also: any valid URL wrapper)
107          *
108          * @param   string       $file       Absolute file path or any valid URL fopen() wrapper
109          * @param   null|string  $md5sum     The MD5 sum. null to auto calculate, empty string to never calculate.
110          * @param   null|string  $sha256sum  The SHA256 sum. null to auto calculate.
111          *
112          * @return  Input
113          */
114         public static function createFromFile(string $file, ?string $md5sum = null, ?string $sha256sum = null): self
115         {
116                 $input = new Input();
117
118                 $input->setFile($file);
119                 $input->setMd5sum($md5sum);
120                 $input->setSha256($sha256sum);
121
122                 return $input;
123         }
124
125         /**
126          * Create an input object from a stream resource / file pointer.
127          *
128          * Please note that the contentLength cannot be calculated automatically unless you have a seekable stream resource.
129          *
130          * @param   resource     $resource       The file pointer or stream resource
131          * @param   int          $contentLength  The length of the content in bytes. Set to -1 for auto calculation.
132          * @param   null|string  $md5sum         The MD5 sum. null to auto calculate, empty string to never calculate.
133          * @param   null|string  $sha256sum      The SHA256 sum. null to auto calculate.
134          *
135          * @return  Input
136          */
137         public static function createFromResource(&$resource, int $contentLength, ?string $md5sum = null, ?string $sha256sum = null): self
138         {
139                 $input = new Input();
140
141                 $input->setFp($resource);
142                 $input->setSize($contentLength);
143                 $input->setMd5sum($md5sum);
144                 $input->setSha256($sha256sum);
145
146                 return $input;
147         }
148
149         /**
150          * Create an input object from raw data.
151          *
152          * Please bear in mind that the data is being duplicated in memory. Therefore you'll need at least 2xstrlen($data)
153          * of free memory when you are using this method. You can instantiate an object and use assignData to work around
154          * this limitation when handling large amounts of data which may cause memory outages (typically: over 10Mb).
155          *
156          * @param   string       $data       The data to use.
157          * @param   null|string  $md5sum     The MD5 sum. null to auto calculate, empty string to never calculate.
158          * @param   null|string  $sha256sum  The SHA256 sum. null to auto calculate.
159          *
160          * @return  Input
161          */
162         public static function createFromData(string &$data, ?string $md5sum = null, ?string $sha256sum = null): self
163         {
164                 $input = new Input();
165
166                 $input->setData($data);
167                 $input->setMd5sum($md5sum);
168                 $input->setSha256($sha256sum);
169
170                 return $input;
171         }
172
173         /**
174          * Destructor.
175          */
176         function __destruct()
177         {
178                 if (is_resource($this->fp))
179                 {
180                         @fclose($this->fp);
181                 }
182         }
183
184         /**
185          * Returns the input type (resource, file or data)
186          *
187          * @return  int
188          */
189         public function getInputType(): int
190         {
191                 if (!empty($this->file))
192                 {
193                         return self::INPUT_FILE;
194                 }
195
196                 if (!empty($this->fp))
197                 {
198                         return self::INPUT_RESOURCE;
199                 }
200
201                 return self::INPUT_DATA;
202         }
203
204         /**
205          * Return the file pointer to the data, or null if this is not a resource input
206          *
207          * @return  resource|null
208          */
209         public function getFp()
210         {
211                 if (!is_resource($this->fp))
212                 {
213                         return null;
214                 }
215
216                 return $this->fp;
217         }
218
219         /**
220          * Set the file pointer (or, generally, stream resource)
221          *
222          * @param   resource  $fp
223          */
224         public function setFp($fp): void
225         {
226                 if (!is_resource($fp))
227                 {
228                         throw new Exception\InvalidFilePointer('$fp is not a file resource');
229                 }
230
231                 $this->fp = $fp;
232         }
233
234         /**
235          * Get the absolute path to the input file, or null if this is not a file input
236          *
237          * @return  string|null
238          */
239         public function getFile(): ?string
240         {
241                 if (empty($this->file))
242                 {
243                         return null;
244                 }
245
246                 return $this->file;
247         }
248
249         /**
250          * Set the absolute path to the input file
251          *
252          * @param   string  $file
253          */
254         public function setFile(string $file): void
255         {
256                 $this->file = $file;
257                 $this->data = null;
258
259                 if (is_resource($this->fp))
260                 {
261                         @fclose($this->fp);
262                 }
263
264                 $this->fp = @fopen($file, 'rb');
265
266                 if ($this->fp === false)
267                 {
268                         throw new Exception\CannotOpenFileForRead($file);
269                 }
270         }
271
272         /**
273          * Return the raw input data, or null if this is a file or stream input
274          *
275          * @return  string|null
276          */
277         public function getData(): ?string
278         {
279                 if (empty($this->data) && ($this->getInputType() != self::INPUT_DATA))
280                 {
281                         return null;
282                 }
283
284                 return $this->data;
285         }
286
287         /**
288          * Set the raw input data
289          *
290          * @param   string  $data
291          */
292         public function setData(string $data): void
293         {
294                 $this->data = $data;
295
296                 if (is_resource($this->fp))
297                 {
298                         @fclose($this->fp);
299                 }
300
301                 $this->file = null;
302                 $this->fp   = null;
303         }
304
305         /**
306          * Return a reference to the raw input data
307          *
308          * @return  string|null
309          */
310         public function &getDataReference(): ?string
311         {
312                 if (empty($this->data) && ($this->getInputType() != self::INPUT_DATA))
313                 {
314                         $this->data = null;
315                 }
316
317                 return $this->data;
318         }
319
320         /**
321          * Set the raw input data by doing an assignment instead of memory copy. While this conserves memory you cannot use
322          * this with hardcoded strings, method results etc without going through a variable first.
323          *
324          * @param   string  $data
325          */
326         public function assignData(string &$data): void
327         {
328                 $this->data = $data;
329
330                 if (is_resource($this->fp))
331                 {
332                         @fclose($this->fp);
333                 }
334
335                 $this->file = null;
336                 $this->fp   = null;
337         }
338
339         /**
340          * Returns the size of the data to be uploaded, in bytes. If it's not already specified it will try to guess.
341          *
342          * @return  int
343          */
344         public function getSize(): int
345         {
346                 if ($this->size < 0)
347                 {
348                         $this->size = $this->getInputSize();
349                 }
350
351                 return $this->size;
352         }
353
354         /**
355          * Set the size of the data to be uploaded.
356          *
357          * @param   int  $size
358          */
359         public function setSize(int $size)
360         {
361                 $this->size = $size;
362         }
363
364         /**
365          * Get the MIME type of the data
366          *
367          * @return  string|null
368          */
369         public function getType(): ?string
370         {
371                 if (empty($this->type))
372                 {
373                         $this->type = 'application/octet-stream';
374
375                         if ($this->getInputType() == self::INPUT_FILE)
376                         {
377                                 $this->type = $this->getMimeType($this->file);
378                         }
379                 }
380
381                 return $this->type;
382         }
383
384         /**
385          * Set the MIME type of the data
386          *
387          * @param   string|null  $type
388          */
389         public function setType(?string $type)
390         {
391                 $this->type = $type;
392         }
393
394         /**
395          * Get the MD5 sum of the content
396          *
397          * @return  null|string
398          */
399         public function getMd5sum(): ?string
400         {
401                 if ($this->md5sum === '')
402                 {
403                         return null;
404                 }
405
406                 if (is_null($this->md5sum))
407                 {
408                         $this->md5sum = $this->calculateMd5();
409                 }
410
411                 return $this->md5sum;
412         }
413
414         /**
415          * Set the MD5 sum of the content as a base64 encoded string of the raw MD5 binary value.
416          *
417          * WARNING: Do not set a binary MD5 sum or a hex-encoded MD5 sum, it will result in an invalid signature error!
418          *
419          * Set to null to automatically calculate it from the raw data. Set to an empty string to force it to never be
420          * calculated and no value for it set either.
421          *
422          * @param   string|null  $md5sum
423          */
424         public function setMd5sum(?string $md5sum): void
425         {
426                 $this->md5sum = $md5sum;
427         }
428
429         /**
430          * Get the SHA-256 hash of the content
431          *
432          * @return  string
433          */
434         public function getSha256(): string
435         {
436                 if (empty($this->sha256))
437                 {
438                         $this->sha256 = $this->calculateSha256();
439                 }
440
441                 return $this->sha256;
442         }
443
444         /**
445          * Set the SHA-256 sum of the content. It must be a lowercase hexadecimal encoded string.
446          *
447          * Set to null to automatically calculate it from the raw data.
448          *
449          * @param   string|null  $sha256
450          */
451         public function setSha256(?string $sha256): void
452         {
453                 $this->sha256 = strtolower($sha256);
454         }
455
456         /**
457          * Get the Upload Session ID for multipart uploads
458          *
459          * @return  string|null
460          */
461         public function getUploadID(): ?string
462         {
463                 return $this->UploadID;
464         }
465
466         /**
467          * Set the Upload Session ID for multipart uploads
468          *
469          * @param   string|null  $UploadID
470          */
471         public function setUploadID(?string $UploadID): void
472         {
473                 $this->UploadID = $UploadID;
474         }
475
476         /**
477          * Get the part number for multipart uploads.
478          *
479          * Returns null if the part number has not been set yet.
480          *
481          * @return  int|null
482          */
483         public function getPartNumber(): ?int
484         {
485                 return $this->PartNumber;
486         }
487
488         /**
489          * Set the part number for multipart uploads
490          *
491          * @param   int  $PartNumber
492          */
493         public function setPartNumber(int $PartNumber): void
494         {
495                 // Clamp the part number to integers greater than zero.
496                 $this->PartNumber = max(1, (int) $PartNumber);
497         }
498
499         /**
500          * Get the list of ETags for multipart uploads
501          *
502          * @return  string[]
503          */
504         public function getEtags(): array
505         {
506                 return $this->etags;
507         }
508
509         /**
510          * Set the list of ETags for multipart uploads
511          *
512          * @param   string[]  $etags
513          */
514         public function setEtags(array $etags): void
515         {
516                 $this->etags = $etags;
517         }
518
519         /**
520          * Calculates the upload size from the input source. For data it's the entire raw string length. For a file resource
521          * it's the entire file's length. For seekable stream resources it's the remaining data from the current seek
522          * position to EOF.
523          *
524          * WARNING: You should never try to specify files or resources over 2Gb minus 1 byte otherwise 32-bit versions of
525          * PHP (anything except Linux x64 builds) will fail in unpredictable ways: the internal int representation in PHP
526          * depends on the target platform and is typically a signed 32-bit integer.
527          *
528          * @return  int
529          */
530         private function getInputSize(): int
531         {
532                 switch ($this->getInputType())
533                 {
534                         case self::INPUT_DATA:
535                                 return function_exists('mb_strlen') ? mb_strlen($this->data, '8bit') : strlen($this->data);
536                                 break;
537
538                         case self::INPUT_FILE:
539                                 clearstatcache(true, $this->file);
540
541                                 $filesize = @filesize($this->file);
542
543                                 return ($filesize === false) ? 0 : $filesize;
544                                 break;
545
546                         case self::INPUT_RESOURCE:
547                                 $meta = stream_get_meta_data($this->fp);
548
549                                 if ($meta['seekable'])
550                                 {
551                                         $pos    = ftell($this->fp);
552                                         $endPos = fseek($this->fp, 0, SEEK_END);
553                                         fseek($this->fp, $pos, SEEK_SET);
554
555                                         return $endPos - $pos + 1;
556                                 }
557
558                                 break;
559                 }
560
561                 return 0;
562         }
563
564         /**
565          * Get the MIME type of a file
566          *
567          * @param   string  $file  The absolute path to the file for which we want to get the MIME type
568          *
569          * @return  string  The MIME type of the file
570          */
571         private function getMimeType(string $file): string
572         {
573                 $type = false;
574
575                 // Fileinfo documentation says fileinfo_open() will use the
576                 // MAGIC env var for the magic file
577                 if (extension_loaded('fileinfo') && isset($_ENV['MAGIC']) &&
578                         ($finfo = finfo_open(FILEINFO_MIME, $_ENV['MAGIC'])) !== false
579                 )
580                 {
581                         if (($type = finfo_file($finfo, $file)) !== false)
582                         {
583                                 // Remove the charset and grab the last content-type
584                                 $type = explode(' ', str_replace('; charset=', ';charset=', $type));
585                                 $type = array_pop($type);
586                                 $type = explode(';', $type);
587                                 $type = trim(array_shift($type));
588                         }
589
590                         finfo_close($finfo);
591                 }
592                 elseif (function_exists('mime_content_type'))
593                 {
594                         $type = trim(mime_content_type($file));
595                 }
596
597                 if ($type !== false && strlen($type) > 0)
598                 {
599                         return $type;
600                 }
601
602                 // Otherwise do it the old fashioned way
603                 static $exts = [
604                         'jpg'  => 'image/jpeg',
605                         'gif'  => 'image/gif',
606                         'png'  => 'image/png',
607                         'tif'  => 'image/tiff',
608                         'tiff' => 'image/tiff',
609                         'ico'  => 'image/x-icon',
610                         'swf'  => 'application/x-shockwave-flash',
611                         'pdf'  => 'application/pdf',
612                         'zip'  => 'application/zip',
613                         'gz'   => 'application/x-gzip',
614                         'tar'  => 'application/x-tar',
615                         'bz'   => 'application/x-bzip',
616                         'bz2'  => 'application/x-bzip2',
617                         'txt'  => 'text/plain',
618                         'asc'  => 'text/plain',
619                         'htm'  => 'text/html',
620                         'html' => 'text/html',
621                         'css'  => 'text/css',
622                         'js'   => 'text/javascript',
623                         'xml'  => 'text/xml',
624                         'xsl'  => 'application/xsl+xml',
625                         'ogg'  => 'application/ogg',
626                         'mp3'  => 'audio/mpeg',
627                         'wav'  => 'audio/x-wav',
628                         'avi'  => 'video/x-msvideo',
629                         'mpg'  => 'video/mpeg',
630                         'mpeg' => 'video/mpeg',
631                         'mov'  => 'video/quicktime',
632                         'flv'  => 'video/x-flv',
633                         'php'  => 'text/x-php',
634                 ];
635
636                 $ext = strtolower(pathInfo($file, PATHINFO_EXTENSION));
637
638                 return isset($exts[$ext]) ? $exts[$ext] : 'application/octet-stream';
639         }
640
641         /**
642          * Calculate the MD5 sum of the input data
643          *
644          * @return  string  Base-64 encoded MD5 sum
645          */
646         private function calculateMd5(): string
647         {
648                 switch ($this->getInputType())
649                 {
650                         case self::INPUT_DATA:
651                                 return base64_encode(md5($this->data, true));
652                                 break;
653
654                         case self::INPUT_FILE:
655                                 return base64_encode(md5_file($this->file, true));
656                                 break;
657
658                         case self::INPUT_RESOURCE:
659                                 $ctx   = hash_init('md5');
660                                 $pos   = ftell($this->fp);
661                                 $size  = $this->getSize();
662                                 $done  = 0;
663                                 $batch = min(1048576, $size);
664
665                                 while ($done < $size)
666                                 {
667                                         $toRead = min($batch, $done - $size);
668                                         $data   = @fread($this->fp, $toRead);
669                                         hash_update($ctx, $data);
670                                         unset($data);
671                                 }
672
673                                 fseek($this->fp, $pos, SEEK_SET);
674
675                                 return base64_encode(hash_final($ctx, true));
676
677                                 break;
678                 }
679
680                 return '';
681         }
682
683         /**
684          * Calcualte the SHA256 data of the input data
685          *
686          * @return  string  Lowercase hex representation of the SHA-256 sum
687          */
688         private function calculateSha256(): string
689         {
690                 $inputType = $this->getInputType();
691                 switch ($inputType)
692                 {
693                         case self::INPUT_DATA:
694                                 return hash('sha256', $this->data, false);
695                                 break;
696
697                         case self::INPUT_FILE:
698                         case self::INPUT_RESOURCE:
699                                 if ($inputType == self::INPUT_FILE)
700                                 {
701                                         $filesize = @filesize($this->file);
702                                         $fPos     = @ftell($this->fp);
703
704                                         if (($filesize == $this->getSize()) && ($fPos === 0))
705                                         {
706                                                 return hash_file('sha256', $this->file, false);
707                                         }
708                                 }
709
710                                 $ctx   = hash_init('sha256');
711                                 $pos   = ftell($this->fp);
712                                 $size  = $this->getSize();
713                                 $done  = 0;
714                                 $batch = min(1048576, $size);
715
716                                 while ($done < $size)
717                                 {
718                                         $toRead = min($batch, $size - $done);
719                                         $data   = @fread($this->fp, $toRead);
720                                         $done   += $toRead;
721                                         hash_update($ctx, $data);
722                                         unset($data);
723                                 }
724
725                                 fseek($this->fp, $pos, SEEK_SET);
726
727                                 return hash_final($ctx, false);
728
729                                 break;
730                 }
731
732                 return '';
733         }
734 }