From a8374b25f15c1c7545748bae26dda5e3c190573c Mon Sep 17 00:00:00 2001 From: zotlabs Date: Mon, 26 Jun 2017 17:32:38 -0700 Subject: upgrade blueimp from 9.8 to 9.18 --- library/blueimp_upload/server/php/Dockerfile | 38 ++++ .../blueimp_upload/server/php/UploadHandler.php | 250 ++++++++++++--------- .../blueimp_upload/server/php/docker-compose.yml | 6 + library/blueimp_upload/server/php/files/.htaccess | 14 +- library/blueimp_upload/server/php/index.php | 4 +- 5 files changed, 207 insertions(+), 105 deletions(-) create mode 100644 library/blueimp_upload/server/php/Dockerfile create mode 100644 library/blueimp_upload/server/php/docker-compose.yml (limited to 'library/blueimp_upload/server/php') diff --git a/library/blueimp_upload/server/php/Dockerfile b/library/blueimp_upload/server/php/Dockerfile new file mode 100644 index 000000000..ca88d3d0d --- /dev/null +++ b/library/blueimp_upload/server/php/Dockerfile @@ -0,0 +1,38 @@ +FROM php:7.0-apache + +# Enable the Apache Headers module: +RUN ln -s /etc/apache2/mods-available/headers.load \ + /etc/apache2/mods-enabled/headers.load + +# Enable the Apache Rewrite module: +RUN ln -s /etc/apache2/mods-available/rewrite.load \ + /etc/apache2/mods-enabled/rewrite.load + +# Install GD, Imagick and ImageMagick as image conversion options: +RUN DEBIAN_FRONTEND=noninteractive \ + apt-get update && apt-get install -y --no-install-recommends \ + libpng-dev \ + libjpeg-dev \ + libmagickwand-dev \ + imagemagick \ + && pecl install \ + imagick \ + && docker-php-ext-enable \ + imagick \ + && docker-php-ext-configure \ + gd --with-jpeg-dir=/usr/include/ \ + && docker-php-ext-install \ + gd \ + # Uninstall obsolete packages: + && apt-get autoremove -y \ + libpng-dev \ + libjpeg-dev \ + libmagickwand-dev \ + # Remove obsolete files: + && apt-get clean \ + && rm -rf \ + /tmp/* \ + /usr/share/doc/* \ + /var/cache/* \ + /var/lib/apt/lists/* \ + /var/tmp/* diff --git a/library/blueimp_upload/server/php/UploadHandler.php b/library/blueimp_upload/server/php/UploadHandler.php index fb77be1d0..1380d4739 100755 --- a/library/blueimp_upload/server/php/UploadHandler.php +++ b/library/blueimp_upload/server/php/UploadHandler.php @@ -1,13 +1,13 @@ response = array(); $this->options = array( - 'script_url' => $this->get_full_url().'/', + 'script_url' => $this->get_full_url().'/'.$this->basename($this->get_server_var('SCRIPT_NAME')), 'upload_dir' => dirname($this->get_server_var('SCRIPT_FILENAME')).'/files/', 'upload_url' => $this->get_full_url().'/files/', + 'input_stream' => 'php://input', 'user_dirs' => false, 'mkdir_mode' => 0755, 'param_name' => 'files', @@ -67,6 +69,14 @@ class UploadHandler 'Content-Range', 'Content-Disposition' ), + // By default, allow redirects to the referer protocol+host: + 'redirect_allow_target' => '/^'.preg_quote( + parse_url($this->get_server_var('HTTP_REFERER'), PHP_URL_SCHEME) + .'://' + .parse_url($this->get_server_var('HTTP_REFERER'), PHP_URL_HOST) + .'/', // Trailing slash to not match subdomains by mistake + '/' // preg_quote delimiter param + ).'/', // Enable to provide file downloads via GET requests to the PHP script: // 1. Set to 1 to download files via readfile method through PHP // 2. Set to 2 to send a X-Sendfile header for lighttpd/Apache @@ -147,7 +157,8 @@ class UploadHandler 'max_width' => 80, 'max_height' => 80 ) - ) + ), + 'print_response' => true ); if ($options) { $this->options = $options + $this->options; @@ -167,15 +178,15 @@ class UploadHandler $this->head(); break; case 'GET': - $this->get(); + $this->get($this->options['print_response']); break; case 'PATCH': case 'PUT': case 'POST': - $this->post(); + $this->post($this->options['print_response']); break; case 'DELETE': - $this->delete(); + $this->delete($this->options['print_response']); break; default: $this->header('HTTP/1.1 405 Method Not Allowed'); @@ -300,7 +311,7 @@ class UploadHandler $this->get_upload_path($file_name) ); $file->url = $this->get_download_url($file->name); - foreach($this->options['image_versions'] as $version => $options) { + foreach ($this->options['image_versions'] as $version => $options) { if (!empty($version)) { if (is_file($this->get_upload_path($file_name, $version))) { $file->{$version.'Url'} = $this->get_download_url( @@ -332,14 +343,15 @@ class UploadHandler } protected function get_error_message($error) { - return array_key_exists($error, $this->error_messages) ? + return isset($this->error_messages[$error]) ? $this->error_messages[$error] : $error; } - function get_config_bytes($val) { + public function get_config_bytes($val) { $val = trim($val); $last = strtolower($val[strlen($val)-1]); - switch($last) { + $val = (int)$val; + switch ($last) { case 'g': $val *= 1024; case 'm': @@ -355,9 +367,9 @@ class UploadHandler $file->error = $this->get_error_message($error); return false; } - $content_length = $this->fix_integer_overflow(intval( - $this->get_server_var('CONTENT_LENGTH') - )); + $content_length = $this->fix_integer_overflow( + (int)$this->get_server_var('CONTENT_LENGTH') + ); $post_max_size = $this->get_config_bytes(ini_get('post_max_size')); if ($post_max_size && ($content_length > $post_max_size)) { $file->error = $this->get_error_message('post_max_size'); @@ -398,6 +410,21 @@ class UploadHandler if (($max_width || $max_height || $min_width || $min_height) && preg_match($this->options['image_file_types'], $file->name)) { list($img_width, $img_height) = $this->get_image_size($uploaded_file); + + // If we are auto rotating the image by default, do the checks on + // the correct orientation + if ( + @$this->options['image_versions']['']['auto_orient'] && + function_exists('exif_read_data') && + ($exif = @exif_read_data($uploaded_file)) && + (((int) @$exif['Orientation']) >= 5) + ) { + $tmp = $img_width; + $img_width = $img_height; + $img_height = $tmp; + unset($tmp); + } + } if (!empty($img_width)) { if ($max_width && $img_width > $max_width) { @@ -421,7 +448,7 @@ class UploadHandler } protected function upcount_name_callback($matches) { - $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1; + $index = isset($matches[1]) ? ((int)$matches[1]) + 1 : 1; $ext = isset($matches[2]) ? $matches[2] : ''; return ' ('.$index.')'.$ext; } @@ -441,8 +468,8 @@ class UploadHandler $name = $this->upcount_name($name); } // Keep an existing filename if this is part of a chunked upload: - $uploaded_bytes = $this->fix_integer_overflow(intval($content_range[1])); - while(is_file($this->get_upload_path($name))) { + $uploaded_bytes = $this->fix_integer_overflow((int)$content_range[1]); + while (is_file($this->get_upload_path($name))) { if ($uploaded_bytes === $this->get_file_size( $this->get_upload_path($name))) { break; @@ -461,7 +488,7 @@ class UploadHandler } if ($this->options['correct_image_extensions'] && function_exists('exif_imagetype')) { - switch(@exif_imagetype($file_path)){ + switch (@exif_imagetype($file_path)){ case IMAGETYPE_JPEG: $extensions = array('jpg', 'jpeg'); break; @@ -491,7 +518,7 @@ class UploadHandler // Remove path information and dots around the filename, to prevent uploading // into different directories or replacing hidden system files. // Also remove control characters and spaces (\x00..\x20) around the filename: - $name = trim(basename(stripslashes($name)), ".\x00..\x20"); + $name = trim($this->basename(stripslashes($name)), ".\x00..\x20"); // Use a timestamp for empty filenames: if (!$name) { $name = str_replace('.', '-', microtime(true)); @@ -515,10 +542,6 @@ class UploadHandler ); } - protected function handle_form_data($file, $index) { - // Handle form data, e.g. $_REQUEST['description'][$index] - } - protected function get_scaled_image_file_paths($file_name, $version) { $file_path = $this->get_upload_path($file_name); if (!empty($version)) { @@ -601,7 +624,7 @@ class UploadHandler if ($exif === false) { return false; } - $orientation = intval(@$exif['Orientation']); + $orientation = (int)@$exif['Orientation']; if ($orientation < 2 || $orientation > 8) { return false; } @@ -825,7 +848,7 @@ class UploadHandler $this->get_scaled_image_file_paths($file_name, $version); $image = $this->imagick_get_image_object( $file_path, - !empty($options['no_cache']) + !empty($options['crop']) || !empty($options['no_cache']) ); if ($image->getImageFormat() === 'GIF') { // Handle animated GIFs: @@ -955,7 +978,7 @@ class UploadHandler return $dimensions; } return false; - } catch (Exception $e) { + } catch (\Exception $e) { error_log($e->getMessage()); } } @@ -965,7 +988,7 @@ class UploadHandler exec($cmd, $output, $error); if (!$error && !empty($output)) { // image.jpg JPEG 1920x1080 1920x1080+0+0 8-bit sRGB 465KB 0.000u 0:00.000 - $infos = preg_split('/\s+/', $output[0]); + $infos = preg_split('/\s+/', substr($output[0], strlen($file_path))); $dimensions = preg_split('/x/', $infos[2]); return $dimensions; } @@ -1008,7 +1031,7 @@ class UploadHandler protected function handle_image_file($file_path, $file) { $failed_versions = array(); - foreach($this->options['image_versions'] as $version => $options) { + foreach ($this->options['image_versions'] as $version => $options) { if ($this->create_scaled_image($file->name, $version, $options)) { if (!empty($version)) { $file->{$version.'Url'} = $this->get_download_url( @@ -1024,7 +1047,7 @@ class UploadHandler } if (count($failed_versions)) { $file->error = $this->get_error_message('image_resize') - .' ('.implode($failed_versions,', ').')'; + .' ('.implode($failed_versions, ', ').')'; } // Free memory: $this->destroy_image_object($file_path); @@ -1035,7 +1058,7 @@ class UploadHandler $file = new \stdClass(); $file->name = $this->get_file_name($uploaded_file, $name, $size, $type, $error, $index, $content_range); - $file->size = $this->fix_integer_overflow(intval($size)); + $file->size = $this->fix_integer_overflow((int)$size); $file->type = $type; if ($this->validate($uploaded_file, $file, $error, $index)) { $this->handle_form_data($file, $index); @@ -1061,7 +1084,7 @@ class UploadHandler // Non-multipart uploads (PUT method support) file_put_contents( $file_path, - fopen('php://input', 'r'), + fopen($this->options['input_stream'], 'r'), $append_file ? FILE_APPEND : 0 ); } @@ -1102,41 +1125,33 @@ class UploadHandler protected function body($str) { echo $str; } - + protected function header($str) { header($str); } + protected function get_upload_data($id) { + return @$_FILES[$id]; + } + + protected function get_post_param($id) { + return @$_POST[$id]; + } + + protected function get_query_param($id) { + return @$_GET[$id]; + } + protected function get_server_var($id) { - return isset($_SERVER[$id]) ? $_SERVER[$id] : ''; + return @$_SERVER[$id]; } - protected function generate_response($content, $print_response = true) { - if ($print_response) { - $json = json_encode($content); - $redirect = isset($_REQUEST['redirect']) ? - stripslashes($_REQUEST['redirect']) : null; - if ($redirect) { - $this->header('Location: '.sprintf($redirect, rawurlencode($json))); - return; - } - $this->head(); - if ($this->get_server_var('HTTP_CONTENT_RANGE')) { - $files = isset($content[$this->options['param_name']]) ? - $content[$this->options['param_name']] : null; - if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) { - $this->header('Range: 0-'.( - $this->fix_integer_overflow(intval($files[0]->size)) - 1 - )); - } - } - $this->body($json); - } - return $content; + protected function handle_form_data($file, $index) { + // Handle form data, e.g. $_POST['description'][$index] } protected function get_version_param() { - return isset($_GET['version']) ? basename(stripslashes($_GET['version'])) : null; + return $this->basename(stripslashes($this->get_query_param('version'))); } protected function get_singular_param_name() { @@ -1145,14 +1160,16 @@ class UploadHandler protected function get_file_name_param() { $name = $this->get_singular_param_name(); - return isset($_REQUEST[$name]) ? basename(stripslashes($_REQUEST[$name])) : null; + return $this->basename(stripslashes($this->get_query_param($name))); } protected function get_file_names_params() { - $params = isset($_REQUEST[$this->options['param_name']]) ? - $_REQUEST[$this->options['param_name']] : array(); + $params = $this->get_query_param($this->options['param_name']); + if (!$params) { + return null; + } foreach ($params as $key => $value) { - $params[$key] = basename(stripslashes($value)); + $params[$key] = $this->basename(stripslashes($value)); } return $params; } @@ -1232,6 +1249,34 @@ class UploadHandler .implode(', ', $this->options['access_control_allow_headers'])); } + public function generate_response($content, $print_response = true) { + $this->response = $content; + if ($print_response) { + $json = json_encode($content); + $redirect = stripslashes($this->get_post_param('redirect')); + if ($redirect && preg_match($this->options['redirect_allow_target'], $redirect)) { + $this->header('Location: '.sprintf($redirect, rawurlencode($json))); + return; + } + $this->head(); + if ($this->get_server_var('HTTP_CONTENT_RANGE')) { + $files = isset($content[$this->options['param_name']]) ? + $content[$this->options['param_name']] : null; + if ($files && is_array($files) && is_object($files[0]) && $files[0]->size) { + $this->header('Range: 0-'.( + $this->fix_integer_overflow((int)$files[0]->size) - 1 + )); + } + } + $this->body($json); + } + return $content; + } + + public function get_response () { + return $this->response; + } + public function head() { $this->header('Pragma: no-cache'); $this->header('Cache-Control: no-store, no-cache, must-revalidate'); @@ -1245,7 +1290,7 @@ class UploadHandler } public function get($print_response = true) { - if ($print_response && isset($_GET['download'])) { + if ($print_response && $this->get_query_param('download')) { return $this->download(); } $file_name = $this->get_file_name_param(); @@ -1262,58 +1307,59 @@ class UploadHandler } public function post($print_response = true) { - if (isset($_REQUEST['_method']) && $_REQUEST['_method'] === 'DELETE') { + if ($this->get_query_param('_method') === 'DELETE') { return $this->delete($print_response); } - $upload = isset($_FILES[$this->options['param_name']]) ? - $_FILES[$this->options['param_name']] : null; + $upload = $this->get_upload_data($this->options['param_name']); // Parse the Content-Disposition header, if available: - $file_name = $this->get_server_var('HTTP_CONTENT_DISPOSITION') ? + $content_disposition_header = $this->get_server_var('HTTP_CONTENT_DISPOSITION'); + $file_name = $content_disposition_header ? rawurldecode(preg_replace( '/(^[^"]+")|("$)/', '', - $this->get_server_var('HTTP_CONTENT_DISPOSITION') + $content_disposition_header )) : null; // Parse the Content-Range header, which has the following form: // Content-Range: bytes 0-524287/2000000 - $content_range = $this->get_server_var('HTTP_CONTENT_RANGE') ? - preg_split('/[^0-9]+/', $this->get_server_var('HTTP_CONTENT_RANGE')) : null; + $content_range_header = $this->get_server_var('HTTP_CONTENT_RANGE'); + $content_range = $content_range_header ? + preg_split('/[^0-9]+/', $content_range_header) : null; $size = $content_range ? $content_range[3] : null; $files = array(); - if ($upload && is_array($upload['tmp_name'])) { - // param_name is an array identifier like "files[]", - // $_FILES is a multi-dimensional array: - foreach ($upload['tmp_name'] as $index => $value) { + if ($upload) { + if (is_array($upload['tmp_name'])) { + // param_name is an array identifier like "files[]", + // $upload is a multi-dimensional array: + foreach ($upload['tmp_name'] as $index => $value) { + $files[] = $this->handle_file_upload( + $upload['tmp_name'][$index], + $file_name ? $file_name : $upload['name'][$index], + $size ? $size : $upload['size'][$index], + $upload['type'][$index], + $upload['error'][$index], + $index, + $content_range + ); + } + } else { + // param_name is a single object identifier like "file", + // $upload is a one-dimensional array: $files[] = $this->handle_file_upload( - $upload['tmp_name'][$index], - $file_name ? $file_name : $upload['name'][$index], - $size ? $size : $upload['size'][$index], - $upload['type'][$index], - $upload['error'][$index], - $index, + isset($upload['tmp_name']) ? $upload['tmp_name'] : null, + $file_name ? $file_name : (isset($upload['name']) ? + $upload['name'] : null), + $size ? $size : (isset($upload['size']) ? + $upload['size'] : $this->get_server_var('CONTENT_LENGTH')), + isset($upload['type']) ? + $upload['type'] : $this->get_server_var('CONTENT_TYPE'), + isset($upload['error']) ? $upload['error'] : null, + null, $content_range ); } - } else { - // param_name is a single object identifier like "file", - // $_FILES is a one-dimensional array: - $files[] = $this->handle_file_upload( - isset($upload['tmp_name']) ? $upload['tmp_name'] : null, - $file_name ? $file_name : (isset($upload['name']) ? - $upload['name'] : null), - $size ? $size : (isset($upload['size']) ? - $upload['size'] : $this->get_server_var('CONTENT_LENGTH')), - isset($upload['type']) ? - $upload['type'] : $this->get_server_var('CONTENT_TYPE'), - isset($upload['error']) ? $upload['error'] : null, - null, - $content_range - ); } - return $this->generate_response( - array($this->options['param_name'] => $files), - $print_response - ); + $response = array($this->options['param_name'] => $files); + return $this->generate_response($response, $print_response); } public function delete($print_response = true) { @@ -1322,11 +1368,11 @@ class UploadHandler $file_names = array($this->get_file_name_param()); } $response = array(); - foreach($file_names as $file_name) { + foreach ($file_names as $file_name) { $file_path = $this->get_upload_path($file_name); $success = is_file($file_path) && $file_name[0] !== '.' && unlink($file_path); if ($success) { - foreach($this->options['image_versions'] as $version => $options) { + foreach ($this->options['image_versions'] as $version => $options) { if (!empty($version)) { $file = $this->get_upload_path($file_name, $version); if (is_file($file)) { @@ -1340,4 +1386,8 @@ class UploadHandler return $this->generate_response($response, $print_response); } + protected function basename($filepath, $suffix = null) { + $splited = preg_split('/\//', rtrim ($filepath, '/ ')); + return substr(basename('X'.$splited[count($splited)-1], $suffix), 1); + } } diff --git a/library/blueimp_upload/server/php/docker-compose.yml b/library/blueimp_upload/server/php/docker-compose.yml new file mode 100644 index 000000000..691ea9caa --- /dev/null +++ b/library/blueimp_upload/server/php/docker-compose.yml @@ -0,0 +1,6 @@ +apache: + build: ./ + ports: + - "80:80" + volumes: + - "../../:/var/www/html" diff --git a/library/blueimp_upload/server/php/files/.htaccess b/library/blueimp_upload/server/php/files/.htaccess index 56689f0bb..6f454afb9 100644 --- a/library/blueimp_upload/server/php/files/.htaccess +++ b/library/blueimp_upload/server/php/files/.htaccess @@ -1,8 +1,16 @@ -# The following directives force the content-type application/octet-stream -# and force browsers to display a download dialog for non-image files. -# This prevents the execution of script files in the context of the website: +# To enable the Headers module, execute the following command and reload Apache: +# sudo a2enmod headers + +# The following directives prevent the execution of script files +# in the context of the website. +# They also force the content-type application/octet-stream and +# force browsers to display a download dialog for non-image files. +SetHandler default-handler ForceType application/octet-stream Header set Content-Disposition attachment + +# The following unsets the forced type and Content-Disposition headers +# for known image files: ForceType none Header unset Content-Disposition diff --git a/library/blueimp_upload/server/php/index.php b/library/blueimp_upload/server/php/index.php index 3ae1295ef..6caabb710 100644 --- a/library/blueimp_upload/server/php/index.php +++ b/library/blueimp_upload/server/php/index.php @@ -1,13 +1,13 @@