Skip to content

Commit

Permalink
Issue #7: Security fix using sub dir in original file area
Browse files Browse the repository at this point in the history
  • Loading branch information
gthomas2 committed Jan 21, 2018
1 parent d38d3d7 commit 11261e0
Show file tree
Hide file tree
Showing 19 changed files with 843 additions and 222 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.DS_store
2 changes: 1 addition & 1 deletion amd/build/appear.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion amd/build/imageopt.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions amd/src/appear.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ define(['jquery'],
function(jQuery) {

var $ = jQuery;
$(); // This is here to stop linter moaning about unuse.

/*
* jQuery appear plugin
Expand Down
23 changes: 17 additions & 6 deletions amd/src/imageopt.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,27 @@ define(['filter_imageopt/appear'],
function($) {
return {
init:function() {

/**
* Load optimised image.
* @param {Element} el
* @param {string} imgurl
* @returns {void}
*/
var loadOptimisedImg = function(el, imgurl) {
$(el).attr('src', imgurl);
$(el).removeAttr('data-loadonvisible');
$(el).addClass('imageopt_loading');
$(el).on('load', function() {
$(el).removeClass('imageopt_loading');
});
};

$(document).ready(function() {
$(document.body).on('appear', 'img[data-loadonvisible]', function(e, appeared) {
appeared.each(function() {
var imgurl = $(this).data('loadonvisible');
$(this).attr('src', imgurl);
$(this).removeAttr('data-loadonvisible');
$(this).addClass('imageopt_loading');
$(this).on('load', function() {
$(this).removeClass('imageopt_loading');
});
loadOptimisedImg(this, imgurl);
});
});
// Appear configuration - start loading images when they are out of the view port by 400px.
Expand Down
51 changes: 51 additions & 0 deletions classes/componentsupport/base_component.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace filter_imageopt\componentsupport;

defined('MOODLE_INTERNAL') || die;

abstract class base_component {
/**
* Get the image file for the specified file path components.
* @param array $pathcomponents - path split by /.
* @return \stored_file | null
*/
public static function get_img_file(array $pathcomponents) {
return null;
}

/**
* Get the optimised path for specified file path.
* @param array $pathcomponents - path split by /.
* @param int $maxwidth
* @return string | null;
*/
public static function get_optimised_path(array $pathcomponents, $maxwidth) {
return null;
}

/**
* Return the optimised url for the specfied file and original src.
* @param \filter_imageopt\componentsupport\stored_file $file
* @param type $originalsrc
* @return \moodle_url
*/
public static function get_optimised_src(\stored_file $file, $originalsrc) {
return null;
}
}


79 changes: 79 additions & 0 deletions classes/componentsupport/question.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Image optimiser support for question component.
* @package filter_imageopt
* @author Guy Thomas <brudinie@gmail.com>
* @copyright Copyright (c) 2018 Guy Thomas.
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace filter_imageopt\componentsupport;

defined('MOODLE_INTERNAL') || die;

use filter_imageopt\local;

class question extends base_component {

public static function get_img_file(array $pathcomponents) {
if ($pathcomponents[1] !== 'question') {
throw new \coding_exception('Component is not a question ('.$pathcomponents[2].')');
}
if (count($pathcomponents) === 7) {
array_splice($pathcomponents, 3, 2);
} else {
return null;
}

$path = '/'.implode('/', $pathcomponents);

$fs = get_file_storage();
return $fs->get_file_by_hash(sha1($path));
}

public static function get_optimised_path(array $pathcomponents, $maxwidth) {
if ($pathcomponents[1] !== 'question') {
throw new \coding_exception('Component is not a question ('.$pathcomponents[2].')');
}
if (count($pathcomponents) === 7) {
array_splice($pathcomponents, 3, 2);
} else {
return null;
}
$pathcomponents[count($pathcomponents)-1] = 'imageopt/'.$maxwidth.'/'.$pathcomponents[count($pathcomponents)-1];
$optimisedpath = implode('/', $pathcomponents);
if (substr($optimisedpath, 0, 1) !== '/') {
$optimisedpath = '/'.$optimisedpath;
}
return $optimisedpath;
}

public static function get_optimised_src(\stored_file $file, $originalsrc) {
global $CFG;

$maxwidth = get_config('filter_imageopt', 'maxwidth');

$urlpath = local::get_img_path_from_src($originalsrc);
$urlpathcomponents = local::explode_img_path($urlpath);

array_splice($urlpathcomponents, 6, 0, ['imageopt', $maxwidth]);

$opturl = new \moodle_url($CFG->wwwroot.'/pluginfile.php/'.implode('/', $urlpathcomponents));

return $opturl;
}
}
88 changes: 54 additions & 34 deletions classes/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,27 @@ class image {
* Creates a resized version of image and stores copy in file area
*
* @param stored_file $originalfile
* @param string $resizefilepath
* @param bool | string $resizefilename
* @param int $newwidth;
* @param int $newheight;
* @return stored_file
*/
public static function resize (
stored_file $originalfile,
$resizefilepath,
$resizefilename = false,
$newwidth = false,
$newheight = false,
$jpgquality = 90
) {

raise_memory_limit(MEMORY_EXTRA);

if (substr($resizefilepath, -1) !== '/') {
$resizefilepath .= '/';
}

if ($resizefilename === false) {
$resizefilename = $originalfile->get_filename();
}
Expand All @@ -91,7 +100,7 @@ public static function resize (

// Create temporary image for processing.
$tmpimage = tempnam(sys_get_temp_dir(), 'tmpimg');
\file_put_contents($tmpimage, $originalfile->get_content());
file_put_contents($tmpimage, $originalfile->get_content());

if (!$newheight) {
$m = $imageinfo->height / $imageinfo->width; // Multiplier to work out $newheight.
Expand All @@ -103,47 +112,47 @@ public static function resize (
$t = null;
switch ($imageinfo->mimetype) {
case 'image/gif':
if (\function_exists('imagecreatefromgif')) {
$im = \imagecreatefromgif($tmpimage);
if (function_exists('imagecreatefromgif')) {
$im = imagecreatefromgif($tmpimage);
} else {
\debugging('GIF not supported on this server');
debugging('GIF not supported on this server');
unlink ($tmpimage);
return false;
}
// Guess transparent colour from GIF.
$transparent = \imagecolortransparent($im);
$transparent = imagecolortransparent($im);
if ($transparent != -1) {
$t = \imagecolorsforindex($im, $transparent);
$t = imagecolorsforindex($im, $transparent);
}
break;
case 'image/jpeg':
if (\function_exists('imagecreatefromjpeg')) {
$im = \imagecreatefromjpeg($tmpimage);
if (function_exists('imagecreatefromjpeg')) {
$im = imagecreatefromjpeg($tmpimage);
} else {
\debugging('JPEG not supported on this server');
debugging('JPEG not supported on this server');
unlink ($tmpimage);
return false;
}
// If the user uploads a jpeg them we should process as a jpeg if possible.
if (\function_exists('imagejpeg')) {
if (function_exists('imagejpeg')) {
$imagefnc = 'imagejpeg';
$filters = null; // Not used.
$quality = $jpgquality;
} else if (\function_exists('imagepng')) {
} else if (function_exists('imagepng')) {
$imagefnc = 'imagepng';
$filters = PNG_NO_FILTER;
$quality = 1;
} else {
\debugging('Jpeg and png not supported on this server, please fix server configuration');
debugging('Jpeg and png not supported on this server, please fix server configuration');
unlink ($tmpimage);
return false;
}
break;
case 'image/png':
if (\function_exists('imagecreatefrompng')) {
$im = \imagecreatefrompng($tmpimage);
if (function_exists('imagecreatefrompng')) {
$im = imagecreatefrompng($tmpimage);
} else {
\debugging('PNG not supported on this server');
debugging('PNG not supported on this server');
unlink ($tmpimage);
return false;
}
Expand All @@ -156,59 +165,70 @@ public static function resize (

// The default for all images other than jpegs is to try imagepng first.
if (empty($imagefnc)) {
if (\function_exists('imagepng')) {
if (function_exists('imagepng')) {
$imagefnc = 'imagepng';
$filters = PNG_NO_FILTER;
$quality = 1;
} else if (\function_exists('imagejpeg')) {
} else if (function_exists('imagejpeg')) {
$imagefnc = 'imagejpeg';
$filters = null; // Not used.
$quality = $jpgquality;
} else {
\debugging('Jpeg and png not supported on this server, please fix server configuration');
debugging('Jpeg and png not supported on this server, please fix server configuration');
return false;
}
}

if (\function_exists('imagecreatetruecolor')) {
$newimage = \imagecreatetruecolor($newwidth, $newheight);
if (function_exists('imagecreatetruecolor')) {
$newimage = imagecreatetruecolor($newwidth, $newheight);
if ($imageinfo->mimetype != 'image/jpeg' and $imagefnc === 'imagepng') {
if ($t) {
// Transparent GIF hacking...
$transparentcolour = \imagecolorallocate($newimage , $t['red'] , $t['green'] , $t['blue']);
\imagecolortransparent($newimage , $transparentcolour);
$transparentcolour = imagecolorallocate($newimage , $t['red'] , $t['green'] , $t['blue']);
imagecolortransparent($newimage , $transparentcolour);
}

\imagealphablending($newimage, false);
$color = \imagecolorallocatealpha($newimage, 0, 0, 0, 127);
\imagefill($newimage, 0, 0, $color);
\imagesavealpha($newimage, true);
imagealphablending($newimage, false);
$color = imagecolorallocatealpha($newimage, 0, 0, 0, 127);
imagefill($newimage, 0, 0, $color);
imagesavealpha($newimage, true);

}
} else {
$newimage = \imagecreate($newwidth, $newheight);
$newimage = imagecreate($newwidth, $newheight);
}

\imagecopybicubic($newimage, $im, 0, 0, 0, 0, $newwidth, $newheight, $imageinfo->width, $imageinfo->height);
imagecopybicubic($newimage, $im, 0, 0, 0, 0, $newwidth, $newheight, $imageinfo->width, $imageinfo->height);

$fs = \get_file_storage();
$fs = get_file_storage();
$newimageparams = array(
'contextid' => $contextid,
'component' => $component,
'filearea' => $filearea,
'itemid' => $itemid,
'filepath' => '/'
'filepath' => $resizefilepath
);

\ob_start();
$dirs = explode('/', $resizefilepath);
$dirpath = '/';

foreach ($dirs as $dir) {
if (empty($dir)) {
continue;
}
$dirpath .= $dir.'/';
$fs->create_directory($contextid, $component, $filearea, $itemid, $dirpath);
}

ob_start();
if (!$imagefnc($newimage, null, $quality, $filters)) {
return false;
}

$data = \ob_get_clean();
\imagedestroy($newimage);
$data = ob_get_clean();
imagedestroy($newimage);
$newimageparams['filename'] = $resizefilename;
if ($resizefilename == $originalfile->get_filename()) {
if ($resizefilename === $originalfile->get_filename() && $resizefilepath === $originalfile->get_filepath()) {
$originalfile->delete();
}
$file1 = $fs->create_file_from_string($newimageparams, $data);
Expand Down
Loading

0 comments on commit 11261e0

Please sign in to comment.