diff --git a/phpBB/config/default/container/services.yml b/phpBB/config/default/container/services.yml index a7a7703982..11bc6728a6 100644 --- a/phpBB/config/default/container/services.yml +++ b/phpBB/config/default/container/services.yml @@ -202,6 +202,9 @@ services: template_context: class: phpbb\template\context + upload_imagesize: + class: phpbb\upload\imagesize + version_helper: class: phpbb\version_helper scope: prototype diff --git a/phpBB/config/default/container/services_avatar.yml b/phpBB/config/default/container/services_avatar.yml index e80d57cb59..c74bef3d66 100644 --- a/phpBB/config/default/container/services_avatar.yml +++ b/phpBB/config/default/container/services_avatar.yml @@ -17,6 +17,7 @@ services: class: phpbb\avatar\driver\gravatar arguments: - @config + - @upload_imagesize - %core.root_path% - %core.php_ext% - @path_helper @@ -30,6 +31,7 @@ services: class: phpbb\avatar\driver\local arguments: - @config + - @upload_imagesize - %core.root_path% - %core.php_ext% - @path_helper @@ -43,6 +45,7 @@ services: class: phpbb\avatar\driver\remote arguments: - @config + - @upload_imagesize - %core.root_path% - %core.php_ext% - @path_helper diff --git a/phpBB/includes/functions_upload.php b/phpBB/includes/functions_upload.php index 21a6de7a41..f605f89d4d 100644 --- a/phpBB/includes/functions_upload.php +++ b/phpBB/includes/functions_upload.php @@ -400,28 +400,28 @@ class filespec { $this->width = $this->height = 0; - if (($this->image_info = @getimagesize($this->destination_file)) !== false) - { - $this->width = $this->image_info[0]; - $this->height = $this->image_info[1]; + // Get imagesize class + $imagesize = new \phpbb\upload\imagesize(); - if (!empty($this->image_info['mime'])) - { - $this->mimetype = $this->image_info['mime']; - } + $this->image_info = $imagesize->get_imagesize($this->destination_file, $this->mimetype); + + if ($this->image_info !== false) + { + $this->width = $this->image_info['width']; + $this->height = $this->image_info['height']; // Check image type $types = fileupload::image_types(); - if (!isset($types[$this->image_info[2]]) || !in_array($this->extension, $types[$this->image_info[2]])) + if (!isset($types[$this->image_info['type']]) || !in_array($this->extension, $types[$this->image_info['type']])) { - if (!isset($types[$this->image_info[2]])) + if (!isset($types[$this->image_info['type']])) { - $this->error[] = sprintf($user->lang['IMAGE_FILETYPE_INVALID'], $this->image_info[2], $this->mimetype); + $this->error[] = sprintf($user->lang['IMAGE_FILETYPE_INVALID'], $this->image_info['type'], $this->mimetype); } else { - $this->error[] = sprintf($user->lang['IMAGE_FILETYPE_MISMATCH'], $types[$this->image_info[2]][0], $this->extension); + $this->error[] = sprintf($user->lang['IMAGE_FILETYPE_MISMATCH'], $types[$this->image_info['type']][0], $this->extension); } } diff --git a/phpBB/includes/message_parser.php b/phpBB/includes/message_parser.php index 8353ae6843..3d263748cb 100644 --- a/phpBB/includes/message_parser.php +++ b/phpBB/includes/message_parser.php @@ -339,22 +339,23 @@ class bbcode_firstpass extends bbcode if ($config['max_' . $this->mode . '_img_height'] || $config['max_' . $this->mode . '_img_width']) { - $stats = @getimagesize(htmlspecialchars_decode($in)); + $imagesize = new \phpbb\upload\imagesize(); + $size_info = $imagesize->get_imagesize(htmlspecialchars_decode($in)); - if ($stats === false) + if ($size_info === false) { $error = true; $this->warn_msg[] = $user->lang['UNABLE_GET_IMAGE_SIZE']; } else { - if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $stats[1]) + if ($config['max_' . $this->mode . '_img_height'] && $config['max_' . $this->mode . '_img_height'] < $size_info['height']) { $error = true; $this->warn_msg[] = $user->lang('MAX_IMG_HEIGHT_EXCEEDED', (int) $config['max_' . $this->mode . '_img_height']); } - if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $stats[0]) + if ($config['max_' . $this->mode . '_img_width'] && $config['max_' . $this->mode . '_img_width'] < $size_info['width']) { $error = true; $this->warn_msg[] = $user->lang('MAX_IMG_WIDTH_EXCEEDED', (int) $config['max_' . $this->mode . '_img_width']); diff --git a/phpBB/phpbb/avatar/driver/driver.php b/phpBB/phpbb/avatar/driver/driver.php index b3ced7edf7..aa92ba2012 100644 --- a/phpBB/phpbb/avatar/driver/driver.php +++ b/phpBB/phpbb/avatar/driver/driver.php @@ -30,6 +30,9 @@ abstract class driver implements \phpbb\avatar\driver\driver_interface */ protected $config; + /** @var \phpbb\upload\imagesize */ + protected $imagesize; + /** * Current $phpbb_root_path * @var string @@ -73,14 +76,16 @@ abstract class driver implements \phpbb\avatar\driver\driver_interface * Construct a driver object * * @param \phpbb\config\config $config phpBB configuration + * @param \phpbb\upload\imagesize $imagesize phpBB imagesize class * @param string $phpbb_root_path Path to the phpBB root * @param string $php_ext PHP file extension * @param \phpbb\path_helper $path_helper phpBB path helper * @param \phpbb\cache\driver\driver_interface $cache Cache driver */ - public function __construct(\phpbb\config\config $config, $phpbb_root_path, $php_ext, \phpbb\path_helper $path_helper, \phpbb\cache\driver\driver_interface $cache = null) + public function __construct(\phpbb\config\config $config, \phpbb\upload\imagesize $imagesize, $phpbb_root_path, $php_ext, \phpbb\path_helper $path_helper, \phpbb\cache\driver\driver_interface $cache = null) { $this->config = $config; + $this->imagesize = $imagesize; $this->phpbb_root_path = $phpbb_root_path; $this->php_ext = $php_ext; $this->path_helper = $path_helper; diff --git a/phpBB/phpbb/avatar/driver/gravatar.php b/phpBB/phpbb/avatar/driver/gravatar.php index 2082e0fd02..73effadc18 100644 --- a/phpBB/phpbb/avatar/driver/gravatar.php +++ b/phpBB/phpbb/avatar/driver/gravatar.php @@ -98,8 +98,8 @@ class gravatar extends \phpbb\avatar\driver\driver return false; } - // Make sure getimagesize works... - if (function_exists('getimagesize') && ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0)) + // Get image dimensions if they are not set + if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) { /** * default to the minimum of the maximum allowed avatar size if the size @@ -108,20 +108,20 @@ class gravatar extends \phpbb\avatar\driver\driver $row['avatar_width'] = $row['avatar_height'] = min($this->config['avatar_max_width'], $this->config['avatar_max_height']); $url = $this->get_gravatar_url($row); - if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = getimagesize($url)) === false)) + if (($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) && (($image_data = $this->imagesize->get_imagesize($url)) === false)) { $error[] = 'UNABLE_GET_IMAGE_SIZE'; return false; } - if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) + if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['width'] <= 0)) { $error[] = 'AVATAR_NO_SIZE'; return false; } - $row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data[0]; - $row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data[1]; + $row['avatar_width'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_width'] : $image_data['width']; + $row['avatar_height'] = ($row['avatar_width'] && $row['avatar_height']) ? $row['avatar_height'] : $image_data['height']; } if ($row['avatar_width'] <= 0 || $row['avatar_height'] <= 0) diff --git a/phpBB/phpbb/avatar/driver/local.php b/phpBB/phpbb/avatar/driver/local.php index 36087f8ba0..abb07469de 100644 --- a/phpBB/phpbb/avatar/driver/local.php +++ b/phpBB/phpbb/avatar/driver/local.php @@ -172,13 +172,15 @@ class local extends \phpbb\avatar\driver\driver // Match all images in the gallery folder if (preg_match('#^[^&\'"<>]+\.(?:' . implode('|', $this->allowed_extensions) . ')$#i', $image) && is_file($file_path . '/' . $image)) { - if (function_exists('getimagesize')) + $dims = $this->imagesize->get_imagesize($file_path . '/' . $image); + + if ($dims === false) { - $dims = getimagesize($file_path . '/' . $image); + $dims = array(0, 0); } else { - $dims = array(0, 0); + $dims = array($dims['width'], $dims['height']); } $cat = ($path == $file_path) ? $user->lang['NO_AVATAR_CATEGORY'] : str_replace("$path/", '', $file_path); $avatar_list[$cat][$image] = array( diff --git a/phpBB/phpbb/avatar/driver/remote.php b/phpBB/phpbb/avatar/driver/remote.php index 4b0ee3f06f..d04f95905d 100644 --- a/phpBB/phpbb/avatar/driver/remote.php +++ b/phpBB/phpbb/avatar/driver/remote.php @@ -92,25 +92,22 @@ class remote extends \phpbb\avatar\driver\driver return false; } - // Make sure getimagesize works... - if (function_exists('getimagesize')) + // Get image dimensions + if (($width <= 0 || $height <= 0) && (($image_data = $this->imagesize->get_imagesize($url)) === false)) { - if (($width <= 0 || $height <= 0) && (($image_data = @getimagesize($url)) === false)) - { - $error[] = 'UNABLE_GET_IMAGE_SIZE'; - return false; - } - - if (!empty($image_data) && ($image_data[0] <= 0 || $image_data[1] <= 0)) - { - $error[] = 'AVATAR_NO_SIZE'; - return false; - } - - $width = ($width && $height) ? $width : $image_data[0]; - $height = ($width && $height) ? $height : $image_data[1]; + $error[] = 'UNABLE_GET_IMAGE_SIZE'; + return false; } + if (!empty($image_data) && ($image_data['width'] <= 0 || $image_data['height'] <= 0)) + { + $error[] = 'AVATAR_NO_SIZE'; + return false; + } + + $width = ($width && $height) ? $width : $image_data['width']; + $height = ($width && $height) ? $height : $image_data['height']; + if ($width <= 0 || $height <= 0) { $error[] = 'AVATAR_NO_SIZE'; @@ -172,15 +169,15 @@ class remote extends \phpbb\avatar\driver\driver return false; } - if (!empty($image_data) && (!isset($types[$image_data[2]]) || !in_array($extension, $types[$image_data[2]]))) + if (!empty($image_data) && (!isset($types[$image_data['type']]) || !in_array($extension, $types[$image_data['type']]))) { - if (!isset($types[$image_data[2]])) + if (!isset($types[$image_data['type']])) { $error[] = 'UNABLE_GET_IMAGE_SIZE'; } else { - $error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data[2]][0], $extension); + $error[] = array('IMAGE_FILETYPE_MISMATCH', $types[$image_data['type']][0], $extension); } return false; diff --git a/phpBB/phpbb/upload/imagesize.php b/phpBB/phpbb/upload/imagesize.php new file mode 100644 index 0000000000..3ef258f0a2 --- /dev/null +++ b/phpBB/phpbb/upload/imagesize.php @@ -0,0 +1,549 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +namespace phpbb\upload; + +/** + * This class handles the retrieval of image dimensions + */ +class imagesize +{ + /** @var int 4-byte long size */ + const LONG_SIZE = 4; + + /** @var int 2-byte short size */ + const SHORT_SIZE = 2; + + /** @var string PNG header */ + const PNG_HEADER = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; + + /** @var int PNG IHDR offset */ + const PNG_IHDR_OFFSET = 12; + + /** @var string GIF87a header */ + const GIF87A_HEADER = "\x47\x49\x46\x38\x37\x61"; + + /** @var string GIF89a header */ + const GIF89A_HEADER = "\x47\x49\x46\x38\x39\x61"; + + /** @var int GIF header size */ + const GIF_HEADER_SIZE = 6; + + /** @var int JPG max header size. Headers can be bigger, but we'll abort + * going throught he header after this */ + const JPG_MAX_HEADER_SIZE = 24576; + + /** @var string PSD signature */ + const PSD_SIGNATURE = "8BPS"; + + /** @var int PSD header size */ + const PSD_HEADER_SIZE = 22; + + /** @var int PSD dimensions info offset */ + const PSD_DIMENSIONS_OFFSET = 14; + + /** @var int BMP header size needed for retrieving dimensions */ + const BMP_HEADER_SIZE = 26; + + /** @var string BMP signature */ + const BMP_SIGNATURE = "\x42\x4D"; + + /** qvar int BMP dimensions offset */ + const BMP_DIMENSIONS_OFFSET = 18; + + /** @var int TIF header size. The header might be larger but the dimensions + * should be in the first 512 bytes */ + const TIF_HEADER_SIZE = 512; + + /** @var int TIF tag for image height */ + const TIF_TAG_IMAGE_HEIGHT = 257; + + /** @var int TIF tag for image width */ + const TIF_TAG_IMAGE_WIDTH = 256; + + /** @var int TIF tag type for short */ + const TIF_TAG_TYPE_SHORT = 3; + + /** @var int TIF IFD entry size */ + const TIF_IFD_ENTRY_SIZE = 12; + + /** @var int IFF header size. Grab more than what should be needed to make + * sure we have the necessary data */ + const IFF_HEADER_SIZE = 32; + + /** @var string JPEG 2000 signature */ + const JPEG_2000_SIGNATURE = "\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A"; + + /** @var array Size info that is returned */ + protected $size = array(); + + /** @var string Data retrieved from remote */ + protected $data = ''; + + /** + * Get image dimensions of supplied image + * + * @param string $file Path to image that should be checked + * @param string $type Mimetype of image + * @return array|bool Array with image dimensions if successful, false if not + */ + public function get_imagesize($file, $type = '') + { + // Reset values + $this->reset_values(); + + // Treat image type as unknown if extension or mime type is unknown + if (!preg_match('/\.([a-z0-9]+)$/i', $file, $match) && empty($type)) + { + $this->get_imagesize_unknown_type($file); + } + else + { + $extension = (isset($match[1])) ? $match[1] : preg_replace('/.+\/([a-z0-9-.]+)$/i', '$1', $type); + + // Reset size info + $this->size = array(); + + switch ($extension) + { + case 'png': + $this->get_png_size($file); + break; + + case 'gif': + $this->get_gif_size($file); + break; + + case 'jpeg': + case 'jpg': + case 'jpe': + case 'jif': + case 'jfif': + case 'jfi': + $this->get_jpeg_size($file); + break; + + case 'jp2': + case 'j2k': + case 'jpf': + case 'jpg2': + case 'jpx': + case 'jpm': + $this->get_jp2_size($file); + break; + + case 'psd': + case 'photoshop': + $this->get_psd_size($file); + break; + + case 'bmp': + $this->get_bmp_size($file); + break; + + case 'tif': + case 'tiff': + // get_tif_size() sets mime type + $this->get_tif_size($file); + break; + + case 'wbm': + case 'wbmp': + case 'vnd.wap.wbmp': + $this->get_wbmp_size($file); + break; + + case 'iff': + case 'x-iff': + $this->get_iff_size($file); + break; + + default: + return false; + } + } + + return sizeof($this->size) > 1 ? $this->size : false; + } + + /** + * Get dimensions of image if type is unknown + * + * @param string $filename Path to file + */ + protected function get_imagesize_unknown_type($filename) + { + // Grab the maximum amount of bytes we might need + $data = $this->get_image($filename, 0, self::JPG_MAX_HEADER_SIZE, false); + + if ($data !== false) + { + $class_methods = preg_grep('/get_([a-z0-9]+)_size/i', get_class_methods($this)); + + foreach ($class_methods as $method) + { + call_user_func_array(array($this, $method), array($filename)); + + if (sizeof($this->size) > 1) + { + break; + } + } + } + } + + /** + * Reset values to default + */ + protected function reset_values() + { + $this->size = array(); + $this->data = ''; + } + + /** + * Set mime type based on supplied image + * + * @param int $type Type of image + */ + protected function set_image_type($type) + { + $this->size['type'] = $type; + } + + /** + * Get image from specified path/source + * + * @param string $filename Path to image + * @param int $offset Offset at which reading of the image should start + * @param int $length Maximum length that should be read + * @param bool $force_length True if the length needs to be the specified + * length, false if not. Default: true + * + * @return bool|string Image data or false if result was empty + */ + protected function get_image($filename, $offset, $length, $force_length = true) + { + if (empty($this->data)) + { + $this->data = @file_get_contents($filename, null, null, $offset, $length); + } + + // Force length to expected one. Return false if data length + // is smaller than expected length + if ($force_length === true) + { + return (strlen($this->data) < $length) ? false : substr($this->data, $offset, $length) ; + } + + return empty($this->data) ? false : $this->data; + } + + /** + * Get dimensions of PNG image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_png_size($filename) + { + // Retrieve image data including the header, the IHDR tag, and the + // following 2 chunks for the image width and height + $data = $this->get_image($filename, 0, self::PNG_IHDR_OFFSET + 3 * self::LONG_SIZE); + + // Check if header fits expected format specified by RFC 2083 + if (substr($data, 0, self::PNG_IHDR_OFFSET - self::LONG_SIZE) !== self::PNG_HEADER || substr($data, self::PNG_IHDR_OFFSET, self::LONG_SIZE) !== 'IHDR') + { + return; + } + + $this->size = unpack('Nwidth/Nheight', substr($data, self::PNG_IHDR_OFFSET + self::LONG_SIZE, self::LONG_SIZE * 2)); + + $this->set_image_type(IMAGETYPE_PNG); + } + + /** + * Get dimensions of GIF image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_gif_size($filename) + { + // Get data needed for reading image dimensions as outlined by GIF87a + // and GIF89a specifications + $data = $this->get_image($filename, 0, self::GIF_HEADER_SIZE + self::SHORT_SIZE * 2); + + $type = substr($data, 0, self::GIF_HEADER_SIZE); + if ($type !== self::GIF87A_HEADER && $type !== self::GIF89A_HEADER) + { + return; + } + + $this->size = unpack('vwidth/vheight', substr($data, self::GIF_HEADER_SIZE, self::SHORT_SIZE * 2)); + + $this->set_image_type(IMAGETYPE_GIF); + } + + /** + * Get dimensions of JPG image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_jpeg_size($filename) + { + // Do not force the data length + $data = $this->get_image($filename, 0, self::JPG_MAX_HEADER_SIZE, false); + + // Check if file is jpeg + if ($data[0] !== "\xFF" || $data[1] !== "\xD8") + { + return; + } + + // Look through file for SOF marker + for ($i = 2 * self::SHORT_SIZE; $i < strlen($data); $i++) + { + if ($data[$i] === "\xFF" && in_array($data[$i+1], array("\xC0", "\xC1", "\xC2", "\xC3", "\xC5", "\xC6", "\xC7", "\xC8", "\xC9", "\xCA", "\xCB", "\xCD", "\xCE", "\xCF"))) + { + // Extract size info from SOF marker + list(, $unpacked) = unpack("H*", substr($data, $i + self::SHORT_SIZE, 7)); + + // Get width and height from unpacked size info + $this->size = array( + 'width' => hexdec(substr($unpacked, 10, 4)), + 'height' => hexdec(substr($unpacked, 6, 4)), + ); + + break; + } + } + + $this->set_image_type(IMAGETYPE_JPEG); + } + + /** + * Get dimensions of PSD image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_psd_size($filename) + { + $data = $this->get_image($filename, 0, self::PSD_HEADER_SIZE); + + if ($data === false) + { + return; + } + + // Offset for version info is length of header but version is only a + // 16-bit unsigned value + $version = unpack('n', substr($data, self::LONG_SIZE, 2)); + + // Check if supplied file is a PSD file + if (substr($data, 0, self::LONG_SIZE) !== self::PSD_SIGNATURE || $version[1] !== 1) + { + return; + } + + $this->size = unpack('Nheight/Nwidth', substr($data, self::PSD_DIMENSIONS_OFFSET, 2 * self::LONG_SIZE)); + + $this->set_image_type(IMAGETYPE_PSD); + } + + /** + * Get dimensions of BMP image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_bmp_size($filename) + { + $data = $this->get_image($filename, 0, self::BMP_HEADER_SIZE); + + // Check if supplied file is a BMP file + if (substr($data, 0, 2) !== self::BMP_SIGNATURE) + { + return; + } + + $this->size = unpack('lwidth/lheight', substr($data, self::BMP_DIMENSIONS_OFFSET, 2 * self::LONG_SIZE)); + + $this->set_image_type(IMAGETYPE_BMP); + } + + /** + * Get dimensions of TIF/TIFF image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_tif_size($filename) + { + // Do not force length of header + $data = $this->get_image($filename, 0, self::TIF_HEADER_SIZE, false); + + $signature = substr($data, 0, self::SHORT_SIZE); + + if ($signature !== "II" && $signature !== "MM") + { + return; + } + + if ($signature === "II") + { + $type_long = 'V'; + $type_short = 'v'; + $this->set_image_type(IMAGETYPE_TIFF_II); + } + else + { + $type_long = 'N'; + $type_short = 'n'; + $this->set_image_type(IMAGETYPE_TIFF_MM); + } + + // Get offset of IFD + list(, $offset) = unpack($type_long, substr($data, self::LONG_SIZE, self::LONG_SIZE)); + + // Get size of IFD + list(, $size_ifd) = unpack($type_short, substr($data, $offset, self::SHORT_SIZE)); + + // Skip 2 bytes that define the IFD size + $offset += self::SHORT_SIZE; + + // Filter through IFD + for ($i = 0; $i < $size_ifd; $i++) + { + // Get IFD tag + $type = unpack($type_short, substr($data, $offset, self::SHORT_SIZE)); + + // Get field type of tag + $field_type = unpack($type_short . 'type', substr($data, $offset + self::SHORT_SIZE, self::SHORT_SIZE)); + + // Get IFD entry + $ifd_value = substr($data, $offset + 2 * self::LONG_SIZE, self::LONG_SIZE); + + // Get actual dimensions from IFD + if ($type[1] === self::TIF_TAG_IMAGE_HEIGHT) + { + $this->size = array_merge($this->size, ($field_type['type'] === self::TIF_TAG_TYPE_SHORT) ? unpack($type_short . 'height', $ifd_value) : unpack($type_long . 'height', $ifd_value)); + } + else if ($type[1] === self::TIF_TAG_IMAGE_WIDTH) + { + $this->size = array_merge($this->size, ($field_type['type'] === self::TIF_TAG_TYPE_SHORT) ? unpack($type_short .'width', $ifd_value) : unpack($type_long . 'width', $ifd_value)); + } + + $offset += self::TIF_IFD_ENTRY_SIZE; + } + } + + /** + * Get dimensions of WBMP image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_wbmp_size($filename) + { + $data = $this->get_image($filename, 0, self::LONG_SIZE); + + // Check if image is WBMP + if (ord($data[0]) !== 0 || ord($data[1]) !== 0 || $data === substr(self::JPEG_2000_SIGNATURE, 0, 4)) + { + return; + } + + $this->size = unpack('Cwidth/Cheight', substr($data, self::SHORT_SIZE, self::SHORT_SIZE)); + + $this->set_image_type(IMAGETYPE_WBMP); + } + + /** + * Get dimensions of IFF image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_iff_size($filename) + { + $data = $this->get_image($filename, 0, self::IFF_HEADER_SIZE); + + $signature = substr($data, 0, self::LONG_SIZE ); + + // Check if image is IFF + if ($signature !== 'FORM' && $signature !== 'FOR4') + { + return; + } + + // Amiga version of IFF + if ($signature === 'FORM') + { + $btmhd_position = strpos($data, 'BMHD'); + $this->size = unpack('nwidth/nheight', substr($data, $btmhd_position + 2 * self::LONG_SIZE, self::LONG_SIZE)); + } + // Maya version + else + { + $btmhd_position = strpos($data, 'BHD'); + $this->size = unpack('Nwidth/Nheight', substr($data, $btmhd_position + 2 * self::LONG_SIZE - 1, self::LONG_SIZE * 2)); + } + + $this->set_image_type(IMAGETYPE_IFF); + } + + /** + * Get dimensions of JPEG 2000 image + * + * @param string $filename Filename of image + * + * @return array|bool Array with image dimensions if successful, false if not + */ + protected function get_jp2_size($filename) + { + $data = $this->get_image($filename, 0, self::JPG_MAX_HEADER_SIZE, false); + + // Check if file is jpeg 2000 + if (substr($data, 0, strlen(self::JPEG_2000_SIGNATURE)) !== self::JPEG_2000_SIGNATURE) + { + return; + } + + // Get SOC position before starting to search for SIZ + $soc_position = strpos($data, "\xFF\x4F"); + + // Make sure we do not get SIZ before SOC + $data = substr($data, $soc_position); + + $siz_position = strpos($data, "\xFF\x51"); + + // Remove SIZ and everything before + $data = substr($data, $siz_position + self::SHORT_SIZE); + + // Acquire size info from data + $this->size = unpack('Nwidth/Nheight', substr($data, self::LONG_SIZE, self::LONG_SIZE * 2)); + + $this->set_image_type(IMAGETYPE_JPEG2000); + } +} diff --git a/tests/avatar/manager_test.php b/tests/avatar/manager_test.php index 4befbfc1fc..14c88c8da5 100644 --- a/tests/avatar/manager_test.php +++ b/tests/avatar/manager_test.php @@ -57,9 +57,10 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case new \phpbb\mimetype\content_guesser, ); $guesser = new \phpbb\mimetype\guesser($guessers); + $imagesize = new \phpbb\upload\imagesize(); // $this->avatar_foobar will be needed later on - $this->avatar_foobar = $this->getMock('\phpbb\avatar\driver\foobar', array('get_name'), array($this->config, $phpbb_root_path, $phpEx, $path_helper, $cache)); + $this->avatar_foobar = $this->getMock('\phpbb\avatar\driver\foobar', array('get_name'), array($this->config, $imagesize, $phpbb_root_path, $phpEx, $path_helper, $cache)); $this->avatar_foobar->expects($this->any()) ->method('get_name') ->will($this->returnValue('avatar.driver.foobar')); @@ -74,7 +75,7 @@ class phpbb_avatar_manager_test extends \phpbb_database_test_case { if ($driver !== 'upload') { - $cur_avatar = $this->getMock('\phpbb\avatar\driver\\' . $driver, array('get_name'), array($this->config, $phpbb_root_path, $phpEx, $path_helper, $cache)); + $cur_avatar = $this->getMock('\phpbb\avatar\driver\\' . $driver, array('get_name'), array($this->config, $imagesize, $phpbb_root_path, $phpEx, $path_helper, $cache)); } else { diff --git a/tests/upload/fixture/bmp b/tests/upload/fixture/bmp new file mode 100644 index 0000000000..04bff561ab Binary files /dev/null and b/tests/upload/fixture/bmp differ diff --git a/tests/upload/fixture/iff b/tests/upload/fixture/iff new file mode 100644 index 0000000000..24eda8f593 Binary files /dev/null and b/tests/upload/fixture/iff differ diff --git a/tests/upload/fixture/iff_maya b/tests/upload/fixture/iff_maya new file mode 100644 index 0000000000..b6fb85101b Binary files /dev/null and b/tests/upload/fixture/iff_maya differ diff --git a/tests/upload/fixture/jp2 b/tests/upload/fixture/jp2 new file mode 100644 index 0000000000..adca6ecf0e Binary files /dev/null and b/tests/upload/fixture/jp2 differ diff --git a/tests/upload/fixture/jpx b/tests/upload/fixture/jpx new file mode 100644 index 0000000000..adca6ecf0e Binary files /dev/null and b/tests/upload/fixture/jpx differ diff --git a/tests/upload/fixture/psd b/tests/upload/fixture/psd new file mode 100644 index 0000000000..d1bc9a6a70 Binary files /dev/null and b/tests/upload/fixture/psd differ diff --git a/tests/upload/fixture/tif_compressed b/tests/upload/fixture/tif_compressed new file mode 100644 index 0000000000..133b50c4f0 Binary files /dev/null and b/tests/upload/fixture/tif_compressed differ diff --git a/tests/upload/fixture/tif_msb b/tests/upload/fixture/tif_msb new file mode 100644 index 0000000000..32eb8abfbb Binary files /dev/null and b/tests/upload/fixture/tif_msb differ diff --git a/tests/upload/fixture/wbmp b/tests/upload/fixture/wbmp new file mode 100644 index 0000000000..708c86ccee Binary files /dev/null and b/tests/upload/fixture/wbmp differ diff --git a/tests/upload/imagesize_test.php b/tests/upload/imagesize_test.php new file mode 100644 index 0000000000..2ce712e5c1 --- /dev/null +++ b/tests/upload/imagesize_test.php @@ -0,0 +1,99 @@ + + * @license GNU General Public License, version 2 (GPL-2.0) + * + * For full copyright and license information, please see + * the docs/CREDITS.txt file. + * + */ + +require_once(__DIR__ . '/../../phpBB/includes/functions.php'); + +class phpbb_upload_imagesize_test extends \phpbb_test_case +{ + /** @var \phpbb\upload\imagesize */ + protected $imagesize; + + /** @var string Path to fixtures */ + protected $path; + + public function setUp() + { + parent::setUp(); + $this->imagesize = new \phpbb\upload\imagesize(); + $this->path = __DIR__ . '/fixture/'; + } + + public function data_get_imagesize() + { + return array( + array('foobar', 'image/bmp', false), + array('png', 'image/png', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_PNG)), + array('gif', 'image/png', false), + array('png', '', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_PNG)), + array('gif', 'image/gif', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_GIF)), + array('jpg', 'image/gif', false), + array('gif', '', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_GIF)), + array('jpg', 'image/jpg', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_JPEG)), + array('jpg', 'image/jpeg', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_JPEG)), + array('png', 'image/jpg', false), + array('jpg', '', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_JPEG)), + array('psd', 'image/psd', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_PSD)), + array('psd', 'image/photoshop', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_PSD)), + array('jpg', 'image/psd', false), + array('psd', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_PSD)), + array('bmp', 'image/bmp', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_BMP)), + array('png', 'image/bmp', false), + array('bmp', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_BMP)), + array('tif', 'image/tif', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_TIFF_II)), + array('png', 'image/tif', false), + array('tif', '', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_TIFF_II)), + array('tif_compressed', 'image/tif', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_TIFF_II)), + array('png', 'image/tiff', false), + array('tif_compressed', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_TIFF_II)), + array('tif_msb', 'image/tif', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_TIFF_MM)), + array('tif_msb', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_TIFF_MM)), + array('wbmp', 'image/wbmp', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_WBMP)), + array('wbmp', 'image/vnd.wap.wbmp', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_WBMP)), + array('png', 'image/vnd.wap.wbmp', false), + array('wbmp', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_WBMP)), + array('iff', 'image/iff', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_IFF)), + array('iff', 'image/x-iff', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_IFF)), + array('iff_maya', 'iamge/iff', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_IFF)), + array('png', 'image/iff', false), + array('png', 'image/x-iff', false), + array('iff', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_IFF)), + array('iff_maya', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_IFF)), + array('jp2', 'image/jp2', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_JPEG2000)), + array('jp2', 'image/jpx', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_JPEG2000)), + array('jp2', 'image/jpm', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_JPEG2000)), + array('jpg', 'image/jp2', false), + array('jpx', 'image/jpx', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_JPEG2000)), + array('jp2', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_JPEG2000)), + array('jpx', '', array('width' => 2, 'height' => 1, 'type' => IMAGETYPE_JPEG2000)), + ); + } + + /** + * @dataProvider data_get_imagesize + */ + public function test_get_imagesize($file, $mime_type, $expected) + { + $this->assertEquals($expected, $this->imagesize->get_imagesize($this->path . $file, $mime_type)); + } + + public function test_get_imagesize_remote() + { + $this->assertSame(array( + 'width' => 80, + 'height' => 80, + 'type' => IMAGETYPE_JPEG, + ), + $this->imagesize->get_imagesize('https://secure.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0.jpg')); + } +}