OwlCyberSecurity - MANAGER
Edit File: ImageParser.php
<?php namespace FasterImage; use WillWashburn\Stream\Exception\StreamBufferTooSmallException; use WillWashburn\Stream\Stream; use WillWashburn\Stream\StreamableInterface; /** * Parses the stream of the image and determines the size and type of the image * * @package FasterImage */ class ImageParser { /** * The type of image we've determined this is * * @var string */ protected $type; /** * @var StreamableInterface $stream */ private $stream; /** * ImageParser constructor. * * @param StreamableInterface $stream */ public function __construct(StreamableInterface & $stream) { $this->stream = $stream; } /** * @return array|bool|null */ public function parseSize() { $this->stream->resetPointer(); switch ( $this->type ) { case 'png': return $this->parseSizeForPNG(); case 'ico': case 'cur': return $this->parseSizeForIco(); case 'gif': return $this->parseSizeForGIF(); case 'bmp': return $this->parseSizeForBMP(); case 'jpeg': return $this->parseSizeForJPEG(); case 'tiff': return $this->parseSizeForTiff(); case 'psd': return $this->parseSizeForPSD(); case 'webp': return $this->parseSizeForWebp(); case 'svg': return $this->parseSizeForSvg(); } return null; } /** * @return array */ public function parseSizeForIco() { $this->stream->read(6); $b1 = $this->getByte(); $b2 = $this->getByte(); return [ $b1 == 0 ? 256 : $b1, $b2 == 0 ? 256 : $b2 ]; } /** * @return array */ protected function parseSizeForPSD() { $this->stream->read(14); $sizes = unpack("N*",$this->stream->read(12)); return [ $sizes[2], $sizes[1] ]; } /** * Reads and returns the type of the image * * @return bool|string */ public function parseType() { if ( ! $this->type ) { $this->stream->resetPointer(); switch ( $this->stream->read(2) ) { case "BM": return $this->type = 'bmp'; case "GI": return $this->type = 'gif'; case chr(0xFF) . chr(0xd8): return $this->type = 'jpeg'; case "\0\0": switch ( $this->readByte($this->stream->peek(1)) ) { case 1: return $this->type = 'ico'; case 2: return $this->type = 'cur'; } return false; case chr(0x89) . 'P': return $this->type = 'png'; case "RI": if ( substr($this->stream->read(10), 6, 4) == 'WEBP' ) { return $this->type = 'webp'; } return false; case'8B': return $this->type = 'psd'; case "II": case "MM": return $this->type = 'tiff'; default: $this->stream->resetPointer(); // Keep reading bytes until we find '<svg'. while ( true ) { $byte = $this->stream->read( 1 ); if ( '<' === $byte && 'svg' === $this->stream->peek( 3 ) ) { $this->type = 'svg'; return $this->type; } } return false; } } return $this->type; } /** * @return array */ protected function parseSizeForBMP() { $chars = $this->stream->read(29); $chars = substr($chars, 14, 14); $type = unpack('C', $chars); $size = (reset($type) == 40) ? unpack('l*', substr($chars, 4)) : unpack('l*', substr($chars, 4, 8)); return [ current($size), abs(next($size)) ]; } /** * @return array */ protected function parseSizeForGIF() { $chars = $this->stream->read(11); $size = unpack("S*", substr($chars, 6, 4)); return [ current($size), next($size) ]; } /** * @return array|bool */ protected function parseSizeForJPEG() { $state = null; while ( true ) { switch ( $state ) { default: $this->stream->read(2); $state = 'started'; break; case 'started': $b = $this->getByte(); if ( $b === false ) return false; $state = $b == 0xFF ? 'sof' : 'started'; break; case 'sof': $b = $this->getByte(); if ( $b === 0xe1 ) { $data = $this->stream->read($this->readInt($this->stream->read(2)) - 2); $stream = new Stream; $stream->write($data); if ( $stream->read(4) === 'Exif' ) { $stream->read(2); // Some Exif data is broken/wrong so we'll ignore // any exceptions here try { $exif = new ExifParser($stream); } catch (\Exception $e) {} } break; } if ( in_array($b, range(0xe0, 0xef)) ) { $state = 'skipframe'; break; } if ( in_array($b, array_merge(range(0xC0, 0xC3), range(0xC5, 0xC7), range(0xC9, 0xCB), range(0xCD, 0xCF))) ) { $state = 'readsize'; break; } if ( $b == 0xFF ) { $state = 'sof'; break; } $state = 'skipframe'; break; case 'skipframe': $skip = $this->readInt($this->stream->read(2)) - 2; $this->stream->read($skip); $state = 'started'; break; case 'readsize': $c = $this->stream->read(7); $size = array($this->readInt(substr($c, 5, 2)), $this->readInt(substr($c, 3, 2))); if ( isset($exif) && $exif->isRotated() ) { return array_reverse($size); } return $size; } } return false; } /** * @return array */ protected function parseSizeForPNG() { $chars = $this->stream->read(25); $size = unpack("N*", substr($chars, 16, 8)); return [ current($size), next($size) ]; } /** * @return array|bool * @throws \FasterImage\Exception\InvalidImageException * @throws StreamBufferTooSmallException */ protected function parseSizeForTiff() { $exif = new ExifParser($this->stream); if ( $exif->isRotated() ) { return [$exif->getHeight(), $exif->getWidth()]; } return [$exif->getWidth(), $exif->getHeight()]; } /** * @return null * @throws StreamBufferTooSmallException */ protected function parseSizeForWebp() { $vp8 = substr($this->stream->read(16), 12, 4); $len = unpack("V", $this->stream->read(4)); switch ( trim($vp8) ) { case 'VP8': $this->stream->read(6); $width = current(unpack("v", $this->stream->read(2))); $height = current(unpack("v", $this->stream->read(2))); return [ $width & 0x3fff, $height & 0x3fff ]; case 'VP8L': $this->stream->read(1); $b1 = $this->getByte(); $b2 = $this->getByte(); $b3 = $this->getByte(); $b4 = $this->getByte(); $width = 1 + ((($b2 & 0x3f) << 8) | $b1); $height = 1 + ((($b4 & 0xf) << 10) | ($b3 << 2) | (($b2 & 0xc0) >> 6)); return [$width, $height]; case 'VP8X': $flags = current(unpack("C", $this->stream->read(4))); $b1 = $this->getByte(); $b2 = $this->getByte(); $b3 = $this->getByte(); $b4 = $this->getByte(); $b5 = $this->getByte(); $b6 = $this->getByte(); $width = 1 + $b1 + ($b2 << 8) + ($b3 << 16); $height = 1 + $b4 + ($b5 << 8) + ($b6 << 16); return [$width, $height]; default: return null; } } /** * Parse size for SVG. * * @return array|null Size or null. */ protected function parseSizeForSvg() { $this->stream->resetPointer(); // Keep reading bytes until we find the complete <svg> start tag. $inside = false; $markup = ''; while ( true ) { $byte = $this->stream->read(1); // Open a tag if not in a tag and the byte is '<'. if ( ! $inside && '<' === $byte ) { $inside = true; $markup .= $byte; } // Close the current tag if the tag is open, the byte is '>', and the last characters weren't a comment token. elseif ( $inside && '>' === $byte && '--' !== substr( $markup, -2 ) ) { $inside = false; $markup .= $byte; // Break the loop if this tag started with '<svg', as we have now found the SVG start tag. if ( '<svg' === strtolower( substr( $markup, 0, 4 ) ) ) { break; } // Clear out the markup since we consumed the end of the tag/comment. $markup = ''; } // Append the bte to the current tag if the tag is open. elseif ( $inside ) { $markup .= $byte; } } $width = null; $height = null; if ( preg_match( '/\swidth=([\'"])(\d+(\.\d+)?)(px)?\1/', $markup, $matches ) ) { $width = floatval( $matches[2] ); } if ( preg_match( '/\sheight=([\'"])(\d+(\.\d+)?)(px)?\1/', $markup, $matches ) ) { $height = floatval( $matches[2] ); } if ( $width && $height ) { return [ $width, $height ]; } if ( preg_match( '/\sviewBox=([\'"])[^\1]*(?:,|\s)+(?P<width>\d+(?:\.\d+)?)(?:px)?(?:,|\s)+(?P<height>\d+(?:\.\d+)?)(?:px)?\s*\1/', $markup, $matches ) ) { return [ floatval( $matches['width'] ), floatval( $matches['height'] ) ]; } return null; } /** * @return mixed */ private function getByte() { return $this->readByte($this->stream->read(1)); } /** * @param $string * * @return mixed */ private function readByte($string) { return current(unpack("C", $string)); } /** * @param $str * * @return int */ private function readInt($str) { $size = unpack("C*", $str); return ($size[1] << 8) + $size[2]; } }