diff --git a/libs/class.upload.php b/libs/class.upload.php index f132f0f..66d6ab1 100644 --- a/libs/class.upload.php +++ b/libs/class.upload.php @@ -2,7 +2,7 @@ // +------------------------------------------------------------------------+ // | class.upload.php | // +------------------------------------------------------------------------+ -// | Copyright (c) Colin Verot 2003-2018. All rights reserved. | +// | Copyright (c) Colin Verot 2003-2019. All rights reserved. | // | Email colin@verot.net | // | Web http://www.verot.net | // +------------------------------------------------------------------------+ @@ -25,6 +25,9 @@ // | This script is free to use, don't abuse. | // +------------------------------------------------------------------------+ +// namespace Verot\Upload; + +if (!defined('IMG_WEBP')) define('IMG_WEBP', 32); /** * Class upload @@ -33,7 +36,7 @@ * @license http://opensource.org/licenses/gpl-license.php GNU Public License * @copyright Colin Verot */ -class upload { +class Upload { /** @@ -181,7 +184,7 @@ class upload { var $image_src_pixels; /** - * Type of image (png, gif, jpg or bmp) + * Type of image (png, gif, jpg, webp or bmp) * * @access public * @var string @@ -205,7 +208,7 @@ class upload { var $image_dst_y; /** - * Destination image type (png, gif, jpg or bmp) + * Destination image type (png, gif, jpg, webp or bmp) * * @access public * @var integer @@ -496,7 +499,7 @@ class upload { /** * Set this variable to convert the file if it is an image * - * Possibles values are : ''; 'png'; 'jpeg'; 'gif'; 'bmp' + * Possibles values are : ''; 'png'; 'jpeg'; 'gif'; 'webp'; 'bmp' * * Default value is '' (no conversion)
* If {@link resize} is true, {@link convert} will be set to the source file extension @@ -754,6 +757,16 @@ class upload { */ var $jpeg_quality; + /** + * Quality of WebP created/converted destination image + * + * Default value is 85 + * + * @access public + * @var integer + */ + var $webp_quality; + /** * Determines the quality of the JPG image to fit a desired file size * @@ -804,7 +817,7 @@ class upload { * Background color, used to paint transparent areas with * * If set, it will forcibly remove transparency by painting transparent areas with the color - * This setting will fill in all transparent areas in PNG and GIF, as opposed to {@link image_default_color} + * This setting will fill in all transparent areas in PNG, WEPB and GIF, as opposed to {@link image_default_color} * which will do so only in BMP, JPEG, and alpha transparent areas in transparent GIFs * This setting overrides {@link image_default_color} * @@ -820,7 +833,7 @@ class upload { * * This setting is to be used to define a background color for semi transparent areas * of an alpha transparent when the output format doesn't support alpha transparency - * This is useful when, from an alpha transparent PNG image, or an image with alpha transparent features + * This is useful when, from an alpha transparent PNG or WEBP image, or an image with alpha transparent features * if you want to output it as a transparent GIFs for instance, you can set a blending color for transparent areas * If you output in JPEG or BMP, this color will be used to fill in the previously transparent areas * @@ -1549,7 +1562,7 @@ class upload { /** * Adds a watermark on the image * - * Value is a local image filename, relative or absolute. GIF, JPG, BMP and PNG are supported, as well as PNG alpha. + * Value is a local image filename, relative or absolute. GIF, JPG, BMP, WEBP and PNG are supported, as well as PNG and WEBP alpha. * * If set, this setting allow the use of all other settings starting with image_watermark_ * @@ -1675,6 +1688,17 @@ class upload { */ var $forbidden; + /** + * Blacklisted file extensions + * + * List of blacklisted extensions, that are enforced if {@link no_script} is true + * + * @access public + * @var array + */ + var $blacklist; + + /** * Array of translated error messages * @@ -1749,6 +1773,7 @@ class upload { $this->image_no_shrinking = false; $this->png_compression = null; + $this->webp_quality = 85; $this->jpeg_quality = 85; $this->jpeg_size = null; $this->image_interlace = false; @@ -1885,6 +1910,7 @@ class upload { 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'application/vocaltec-media-file', 'application/wordperfect', + 'application/haansoftxlsx', 'application/x-bittorrent', 'application/x-bzip', 'application/x-bzip2', @@ -1914,7 +1940,13 @@ class upload { 'text/richtext', 'text/xml', 'video/*', - 'text/csv' + 'text/csv', + 'text/x-c', + 'text/x-csv', + 'text/comma-separated-values', + 'text/x-comma-separated-values', + 'application/csv', + 'application/x-csv', ); $this->mime_types = array( @@ -1922,6 +1954,7 @@ class upload { 'jpeg' => 'image/jpeg', 'jpe' => 'image/jpeg', 'gif' => 'image/gif', + 'webp' => 'image/webp', 'png' => 'image/png', 'bmp' => 'image/bmp', 'flif' => 'image/flif', @@ -2003,6 +2036,28 @@ class upload { 'csv' => 'text/csv', ); + $this->blacklist = array( + 'php', + 'php7', + 'php6', + 'php5', + 'php4', + 'php3', + 'phtml', + 'pht', + 'phpt', + 'phtm', + 'phps', + 'inc', + 'pl', + 'py', + 'cgi', + 'asp', + 'js', + 'sh', + 'phar', + ); + } /** @@ -2038,7 +2093,7 @@ class upload { */ function upload($file, $lang = 'en_GB') { - $this->version = '0.35dev'; + $this->version = '03/08/2019'; $this->file_src_name = ''; $this->file_src_name_body = ''; @@ -2152,6 +2207,10 @@ class upload { $this->image_supported['image/png'] = 'png'; $this->image_supported['image/x-png'] = 'png'; } + if (imagetypes() & IMG_WEBP) { + $this->image_supported['image/webp'] = 'webp'; + $this->image_supported['image/x-webp'] = 'webp'; + } if (imagetypes() & IMG_WBMP) { $this->image_supported['image/bmp'] = 'bmp'; $this->image_supported['image/x-ms-bmp'] = 'bmp'; @@ -2170,6 +2229,7 @@ class upload { } $gd = $this->gdversion() ? $this->gdversion(true) : 'GD not present'; $supported = trim((in_array('png', $this->image_supported) ? 'png' : '') . ' ' . + (in_array('webp', $this->image_supported) ? 'webp' : '') . ' ' . (in_array('jpg', $this->image_supported) ? 'jpg' : '') . ' ' . (in_array('gif', $this->image_supported) ? 'gif' : '') . ' ' . (in_array('bmp', $this->image_supported) ? 'bmp' : '')); @@ -2194,6 +2254,7 @@ class upload { $this->uploaded = false; $this->error = $this->translate('file_error'); } else { + $file = (string) $file; if (substr($file, 0, 4) == 'php:' || substr($file, 0, 5) == 'data:' || substr($file, 0, 7) == 'base64:') { $data = null; @@ -2297,7 +2358,7 @@ class upload { // this is an element from $_FILE, i.e. an uploaded file $this->log .= 'source is an uploaded file
'; if ($this->uploaded) { - $this->file_src_error = trim($file['error']); + $this->file_src_error = trim((int) $file['error']); switch($this->file_src_error) { case UPLOAD_ERR_OK: // all is OK @@ -2338,8 +2399,8 @@ class upload { } if ($this->uploaded) { - $this->file_src_pathname = $file['tmp_name']; - $this->file_src_name = $file['name']; + $this->file_src_pathname = (string) $file['tmp_name']; + $this->file_src_name = (string) $file['name']; if ($this->file_src_name == '') { $this->uploaded = false; $this->error = $this->translate('try_again'); @@ -2356,8 +2417,8 @@ class upload { $this->file_src_name_ext = ''; $this->file_src_name_body = $this->file_src_name; } - $this->file_src_size = $file['size']; - $mime_from_browser = $file['type']; + $this->file_src_size = (int) $file['size']; + $mime_from_browser = (string) $file['type']; } } @@ -2492,7 +2553,8 @@ class upload { $this->file_src_mime = ($mime==IMAGETYPE_GIF ? 'image/gif' : ($mime==IMAGETYPE_JPEG ? 'image/jpeg' : ($mime==IMAGETYPE_PNG ? 'image/png' : - ($mime==IMAGETYPE_BMP ? 'image/bmp' : null)))); + ($mime==IMAGETYPE_WEBP ? 'image/webp' : + ($mime==IMAGETYPE_BMP ? 'image/bmp' : null))))); } $this->log .= '    MIME type detected as ' . $this->file_src_mime . ' by PHP getimagesize() function
'; if (preg_match("/^([\.\w-]+)\/([\.\w-]+)(.*)$/i", $this->file_src_mime)) { @@ -2775,7 +2837,7 @@ class upload { */ function getsize($size) { if ($size === null) return null; - $last = strtolower($size{strlen($size)-1}); + $last = is_string($size) ? strtolower(substr($size, -1)) : null; $size = (int) $size; switch($last) { case 'g': @@ -2847,7 +2909,7 @@ class upload { if ($this->gdversion() >= 2 && !$this->image_is_palette) { // create a true color image $dst_im = imagecreatetruecolor($x, $y); - // this preserves transparency in PNGs, in true color + // this preserves transparency in PNG and WEBP, in true color if (empty($this->image_background_color) || $trsp) { imagealphablending($dst_im, false ); imagefilledrectangle($dst_im, 0, 0, $x, $y, imagecolorallocatealpha($dst_im, 0, 0, 0, 127)); @@ -2880,15 +2942,29 @@ class upload { * @return resource Destination image */ function imagetransfer($src_im, $dst_im) { - if (is_resource($dst_im)) imagedestroy($dst_im); + $this->imageunset($dst_im); $dst_im = & $src_im; return $dst_im; } + /** + * Destroy GD ressource + * + * @access private + * @param resource $im Image + */ + function imageunset($im) { + if (is_resource($im)) { + imagedestroy($im); + } else if (is_object($im) && $im instanceOf \GdImage) { + unset($im); + } + } + /** * Merges two images * - * If the output format is PNG, then we do it pixel per pixel to retain the alpha channel + * If the output format is PNG or WEBP, then we do it pixel per pixel to retain the alpha channel * * @access private * @param resource $dst_img Destination image @@ -3044,7 +3120,7 @@ class upload { } // if the file is text based, or has a dangerous extension, we rename it as .txt if ((((substr($this->file_src_mime, 0, 5) == 'text/' && $this->file_src_mime != 'text/rtf') || strpos($this->file_src_mime, 'javascript') !== false) && (substr($file_src_name, -4) != '.txt')) - || preg_match('/\.(php|php5|php4|php3|phtml|pl|py|cgi|asp|js)$/i', $this->file_src_name) + || preg_match('/\.(' . implode('|', $this->blacklist) . ')$/i', $this->file_src_name) || $this->file_force_extension && empty($file_src_name_ext)) { $this->file_src_mime = 'text/plain'; if ($this->file_src_name_ext) $file_src_name_body = $file_src_name_body . '.' . $this->file_src_name_ext; @@ -3453,6 +3529,20 @@ class upload { } } break; + case 'webp': + if (!$this->function_enabled('imagecreatefromwebp')) { + $this->processed = false; + $this->error = $this->translate('no_create_support', array('WEBP')); + } else { + $image_src = @imagecreatefromwebp($this->file_src_pathname); + if (!$image_src) { + $this->processed = false; + $this->error = $this->translate('create_error', array('WEBP')); + } else { + $this->log .= '- source image is WEBP
'; + } + } + break; case 'gif': if (!$this->function_enabled('imagecreatefromgif')) { $this->processed = false; @@ -3503,7 +3593,7 @@ class upload { } // we set the default color to be the background color if we don't output in a transparent format - if ($this->image_convert != 'png' && $this->image_convert != 'gif' && !empty($this->image_default_color) && empty($this->image_background_color)) $this->image_background_color = $this->image_default_color; + if ($this->image_convert != 'png' && $this->image_convert != 'webp' && $this->image_convert != 'gif' && !empty($this->image_default_color) && empty($this->image_background_color)) $this->image_background_color = $this->image_default_color; if (!empty($this->image_background_color)) $this->image_default_color = $this->image_background_color; if (empty($this->image_default_color)) $this->image_default_color = '#FFFFFF'; @@ -3903,7 +3993,7 @@ class upload { imagecopyresized($filter, $image_dst, 0, 0, 0, 0, round($this->image_dst_x / $this->image_pixelate), round($this->image_dst_y / $this->image_pixelate), $this->image_dst_x, $this->image_dst_y); imagecopyresized($image_dst, $filter, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, round($this->image_dst_x / $this->image_pixelate), round($this->image_dst_y / $this->image_pixelate)); } - imagedestroy($filter); + $this->imageunset($filter); } // unsharp mask @@ -3967,8 +4057,8 @@ class upload { } } } - imagedestroy($canvas); - imagedestroy($blur); + $this->imageunset($canvas); + $this->imageunset($blur); } } @@ -3980,7 +4070,7 @@ class upload { $color = imagecolorallocate($filter, $red, $green, $blue); imagefilledrectangle($filter, 0, 0, $this->image_dst_x, $this->image_dst_y, $color); $this->imagecopymergealpha($image_dst, $filter, 0, 0, 0, 0, $this->image_dst_x, $this->image_dst_y, $this->image_overlay_opacity); - imagedestroy($filter); + $this->imageunset($filter); } // add brightness, contrast and tint, turns to greyscale and inverts colors @@ -4238,6 +4328,18 @@ class upload { $watermark_checked = true; } } + } else if ($watermark_type == IMAGETYPE_WEBP) { + if (!$this->function_enabled('imagecreatefromwebp')) { + $this->error = $this->translate('watermark_no_create_support', array('WEBP')); + } else { + $filter = @imagecreatefromwebp($this->image_watermark); + if (!$filter) { + $this->error = $this->translate('watermark_create_error', array('WEBP')); + } else { + $this->log .= '    watermark source image is WEBP
'; + $watermark_checked = true; + } + } } else if ($watermark_type == IMAGETYPE_BMP) { if (!method_exists($this, 'imagecreatefrombmp')) { $this->error = $this->translate('watermark_no_create_support', array('BMP')); @@ -4502,7 +4604,7 @@ class upload { $background_color = imagecolorallocate($filter, $red, $green, $blue); imagefilledrectangle($filter, 0, 0, $text_width, $text_height, $background_color); $this->imagecopymergealpha($image_dst, $filter, $text_x, $text_y, 0, 0, $text_width, $text_height, $this->image_text_background_opacity); - imagedestroy($filter); + $this->imageunset($filter); } else { $background_color = imagecolorallocate($image_dst ,$red, $green, $blue); imagefilledrectangle($image_dst, $text_x, $text_y, $text_x + $text_width, $text_y + $text_height, $background_color); @@ -4551,7 +4653,7 @@ class upload { $text); } $this->imagecopymergealpha($image_dst, $filter, $text_x, $text_y, 0, 0, $t_width, $t_height, $this->image_text_opacity); - imagedestroy($filter); + $this->imageunset($filter); } else { $text_color = imagecolorallocate($image_dst ,$red, $green, $blue); @@ -4833,6 +4935,24 @@ class upload { $this->log .= '    PNG image created
'; } break; + case 'webp': + imagealphablending( $image_dst, false ); + imagesavealpha( $image_dst, true ); + if (!$return_mode) { + $result = @imagewebp($image_dst, $this->file_dst_pathname, $this->webp_quality); + } else { + ob_start(); + $result = @imagewebp($image_dst, null, $this->webp_quality); + $return_content = ob_get_contents(); + ob_end_clean(); + } + if (!$result) { + $this->processed = false; + $this->error = $this->translate('file_create', array('WEBP')); + } else { + $this->log .= '    WEBP image created
'; + } + break; case 'gif': if (!$return_mode) { $result = @imagegif($image_dst, $this->file_dst_pathname); @@ -4871,8 +4991,8 @@ class upload { $this->error = $this->translate('no_conversion_type'); } if ($this->processed) { - if (is_resource($image_src)) imagedestroy($image_src); - if (is_resource($image_dst)) imagedestroy($image_dst); + $this->imageunset($image_src); + $this->imageunset($image_dst); $this->log .= '    image objects destroyed
'; } } @@ -5024,7 +5144,7 @@ class upload { if (!imageistruecolor($im)) { $tmp = imagecreatetruecolor($w, $h); imagecopy($tmp, $im, 0, 0, 0, 0, $w, $h); - imagedestroy($im); + $this->imageunset($im); $im = & $tmp; }