Fix security breaches
modifié: core/lib/Thelia/Controller/Admin/FileController.php nouveau fichier: core/lib/Thelia/Tests/Tools/MimeTypesToolsTest.php nouveau fichier: core/lib/Thelia/Tools/MimeTypeTools.php nouveau fichier: local/config/mime.types
This commit is contained in:
@@ -26,6 +26,7 @@ use Thelia\Files\FileModelInterface;
|
|||||||
use Thelia\Form\Exception\FormValidationException;
|
use Thelia\Form\Exception\FormValidationException;
|
||||||
use Thelia\Log\Tlog;
|
use Thelia\Log\Tlog;
|
||||||
use Thelia\Model\Lang;
|
use Thelia\Model\Lang;
|
||||||
|
use Thelia\Tools\MimeTypeTools;
|
||||||
use Thelia\Tools\Rest\ResponseRest;
|
use Thelia\Tools\Rest\ResponseRest;
|
||||||
use Thelia\Tools\URL;
|
use Thelia\Tools\URL;
|
||||||
|
|
||||||
@@ -60,12 +61,20 @@ class FileController extends BaseAdminController
|
|||||||
* @param string $parentType Parent Type owning files being saved (product, category, content, etc.)
|
* @param string $parentType Parent Type owning files being saved (product, category, content, etc.)
|
||||||
* @param string $objectType Object type, e.g. image or document
|
* @param string $objectType Object type, e.g. image or document
|
||||||
* @param array $validMimeTypes an array of valid mime types. If empty, any mime type is allowed.
|
* @param array $validMimeTypes an array of valid mime types. If empty, any mime type is allowed.
|
||||||
*
|
* @param array $blackList an array of blacklisted mime types.
|
||||||
* @return Response
|
* @return Response
|
||||||
*/
|
*/
|
||||||
public function saveFileAjaxAction($parentId, $parentType, $objectType, $validMimeTypes = array())
|
public function saveFileAjaxAction(
|
||||||
{
|
$parentId,
|
||||||
$this->checkAuth(AdminResources::retrieve($parentType), array(), AccessManager::UPDATE);
|
$parentType,
|
||||||
|
$objectType,
|
||||||
|
$validMimeTypes = array(),
|
||||||
|
$blackList = array()
|
||||||
|
) {
|
||||||
|
if (null !== $response = $this->checkAuth(AdminResources::retrieve($parentType), array(), AccessManager::UPDATE)) {
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
$this->checkXmlHttpRequest();
|
$this->checkXmlHttpRequest();
|
||||||
|
|
||||||
if ($this->getRequest()->isMethod('POST')) {
|
if ($this->getRequest()->isMethod('POST')) {
|
||||||
@@ -87,13 +96,32 @@ class FileController extends BaseAdminController
|
|||||||
return new ResponseRest($message, 'text', 403);
|
return new ResponseRest($message, 'text', 403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$mimeType = $fileBeingUploaded->getMimeType();
|
||||||
|
$mimeTypeTools = MimeTypeTools::getInstance();
|
||||||
|
$validateMimeType = $mimeTypeTools
|
||||||
|
->validateMimeTypeExtension(
|
||||||
|
$mimeType,
|
||||||
|
$fileBeingUploaded->getClientOriginalName()
|
||||||
|
);
|
||||||
|
|
||||||
|
$message = null;
|
||||||
|
|
||||||
|
if ($validateMimeType === $mimeTypeTools::TYPE_NOT_MATCH) {
|
||||||
|
$message = $this->getTranslator()
|
||||||
|
->trans(
|
||||||
|
"There's a conflict between your file extension \"%ext\" and the mime type \"%mime\"",
|
||||||
|
[
|
||||||
|
'%mime' => $mimeType,
|
||||||
|
'%ext' => $fileBeingUploaded->getClientOriginalExtension()
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (! empty($validMimeTypes)) {
|
if (! empty($validMimeTypes)) {
|
||||||
|
|
||||||
// Check if we have the proper file type
|
// Check if we have the proper file type
|
||||||
$isValid = false;
|
$isValid = false;
|
||||||
|
|
||||||
$mimeType = $fileBeingUploaded->getMimeType();
|
|
||||||
|
|
||||||
if (in_array($mimeType, $validMimeTypes)) {
|
if (in_array($mimeType, $validMimeTypes)) {
|
||||||
$isValid = true;
|
$isValid = true;
|
||||||
}
|
}
|
||||||
@@ -104,11 +132,23 @@ class FileController extends BaseAdminController
|
|||||||
'Only files having the following mime type are allowed: %types%',
|
'Only files having the following mime type are allowed: %types%',
|
||||||
[ '%types%' => implode(', ', $validMimeTypes)]
|
[ '%types%' => implode(', ', $validMimeTypes)]
|
||||||
);
|
);
|
||||||
|
|
||||||
return new ResponseRest($message, 'text', 415);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($blackList)) {
|
||||||
|
if (in_array($mimeType, $blackList)) {
|
||||||
|
$message = $this->getTranslator()
|
||||||
|
->trans(
|
||||||
|
'Files with the following mime type are not allowed: %type, please do an archive of the file if you want to upload it',
|
||||||
|
[ '%type' => $mimeType]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($message !== null) {
|
||||||
|
return new ResponseRest($message, 'text', 415);
|
||||||
|
}
|
||||||
|
|
||||||
$fileModel = $fileManager->getModelInstance($objectType, $parentType);
|
$fileModel = $fileManager->getModelInstance($objectType, $parentType);
|
||||||
|
|
||||||
$parentModel = $fileModel->getParentFileModel();
|
$parentModel = $fileModel->getParentFileModel();
|
||||||
@@ -182,7 +222,19 @@ class FileController extends BaseAdminController
|
|||||||
*/
|
*/
|
||||||
public function saveDocumentAjaxAction($parentId, $parentType)
|
public function saveDocumentAjaxAction($parentId, $parentType)
|
||||||
{
|
{
|
||||||
return $this->saveFileAjaxAction($parentId, $parentType, 'document');
|
return $this->saveFileAjaxAction(
|
||||||
|
$parentId,
|
||||||
|
$parentType,
|
||||||
|
'document',
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
'text/x-php',
|
||||||
|
'application/x-httpd-php',
|
||||||
|
'application/x-httpd-php3',
|
||||||
|
'application/x-httpd-php4',
|
||||||
|
'application/x-httpd-php5',
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
108
core/lib/Thelia/Tests/Tools/MimeTypesToolsTest.php
Normal file
108
core/lib/Thelia/Tests/Tools/MimeTypesToolsTest.php
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
<?php
|
||||||
|
/*************************************************************************************/
|
||||||
|
/* This file is part of the Thelia package. */
|
||||||
|
/* */
|
||||||
|
/* Copyright (c) OpenStudio */
|
||||||
|
/* email : dev@thelia.net */
|
||||||
|
/* web : http://www.thelia.net */
|
||||||
|
/* */
|
||||||
|
/* For the full copyright and license information, please view the LICENSE.txt */
|
||||||
|
/* file that was distributed with this source code. */
|
||||||
|
/*************************************************************************************/
|
||||||
|
|
||||||
|
namespace Thelia\Tests\Type;
|
||||||
|
use Symfony\Component\DependencyInjection\Container;
|
||||||
|
use Thelia\Core\Translation\Translator;
|
||||||
|
use Thelia\Tools\MimeTypeTools;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MimeTypesToolsTest
|
||||||
|
* @package Thelia\Tests\Type
|
||||||
|
* @author Benjamin Perche <bperche@openstudio.fr>
|
||||||
|
*/
|
||||||
|
class MimeTypesToolsTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/** @var MimeTypeTools */
|
||||||
|
protected $tool;
|
||||||
|
|
||||||
|
public function setUp()
|
||||||
|
{
|
||||||
|
new Translator(new Container());
|
||||||
|
|
||||||
|
$this->tool = MimeTypeTools::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testTrim()
|
||||||
|
{
|
||||||
|
$this->assertEquals(
|
||||||
|
"foo",
|
||||||
|
$this->tool->realTrim(" foo ")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"foo bar",
|
||||||
|
$this->tool->realTrim(" foo bar ")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"foo bar",
|
||||||
|
$this->tool->realTrim(" foo bar ")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"# foO/x-bar",
|
||||||
|
$this->tool->realTrim(" # foO/x-bar ")
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(
|
||||||
|
"foO/x-bar bar baz",
|
||||||
|
$this->tool->realTrim(" foO/x-bar \t\t bar baz ")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testParseFile()
|
||||||
|
{
|
||||||
|
$array = $this->tool->parseFile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check the format
|
||||||
|
*/
|
||||||
|
|
||||||
|
foreach ($array as $key => $value) {
|
||||||
|
$this->assertTrue(is_string($key));
|
||||||
|
|
||||||
|
$this->assertTrue(is_array($value));
|
||||||
|
|
||||||
|
foreach ($value as $entry) {
|
||||||
|
$this->assertTrue(is_string($entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \Thelia\Exception\FileException
|
||||||
|
*/
|
||||||
|
public function testParseFileFail()
|
||||||
|
{
|
||||||
|
$this->tool->parseFile("foo.bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testValidation()
|
||||||
|
{
|
||||||
|
$this->assertTrue($this->tool->validateMimeTypeExtension(
|
||||||
|
"image/png", "foo.png"
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertTrue($this->tool->validateMimeTypeExtension(
|
||||||
|
"image/png", "foo.PNg"
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertFalse($this->tool->validateMimeTypeExtension(
|
||||||
|
"image/png", "foo.jpeg"
|
||||||
|
));
|
||||||
|
|
||||||
|
$this->assertFalse($this->tool->validateMimeTypeExtension(
|
||||||
|
"thismimetype/doesntexists", "foo.bar"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
157
core/lib/Thelia/Tools/MimeTypeTools.php
Normal file
157
core/lib/Thelia/Tools/MimeTypeTools.php
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
<?php
|
||||||
|
/*************************************************************************************/
|
||||||
|
/* This file is part of the Thelia package. */
|
||||||
|
/* */
|
||||||
|
/* Copyright (c) OpenStudio */
|
||||||
|
/* email : dev@thelia.net */
|
||||||
|
/* web : http://www.thelia.net */
|
||||||
|
/* */
|
||||||
|
/* For the full copyright and license information, please view the LICENSE.txt */
|
||||||
|
/* file that was distributed with this source code. */
|
||||||
|
/*************************************************************************************/
|
||||||
|
|
||||||
|
namespace Thelia\Tools;
|
||||||
|
use Thelia\Core\Translation\Translator;
|
||||||
|
use Thelia\Exception\FileException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class MimeTypeTools
|
||||||
|
* @package Thelia\Tools
|
||||||
|
* @author Benjamin Perche <bperche@openstudio.fr>
|
||||||
|
*/
|
||||||
|
class MimeTypeTools
|
||||||
|
{
|
||||||
|
const TYPES_FILE = "local/config/mime.types";
|
||||||
|
|
||||||
|
const TYPE_UNKNOWN = 0;
|
||||||
|
const TYPE_NOT_MATCH = 1;
|
||||||
|
const TYPE_MATCH = 2;
|
||||||
|
|
||||||
|
protected static $instance;
|
||||||
|
|
||||||
|
protected static $typesCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public static function getInstance()
|
||||||
|
{
|
||||||
|
if (null === static::$instance) {
|
||||||
|
static::$instance = new static();
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $mimeType
|
||||||
|
* @return array|bool
|
||||||
|
*/
|
||||||
|
public function guessExtensionsFromMimeType($mimeType)
|
||||||
|
{
|
||||||
|
if (null === static::$typesCache) {
|
||||||
|
static::$typesCache = $this->parseFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_scalar($mimeType) || !isset(static::$typesCache[$mimeType])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static::$typesCache[$mimeType];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $mimeType
|
||||||
|
* @param $fileName
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function validateMimeTypeExtension($mimeType, $fileName) {
|
||||||
|
$mimeType = strtolower($mimeType);
|
||||||
|
|
||||||
|
$extensions = $this->guessExtensionsFromMimeType($mimeType);
|
||||||
|
|
||||||
|
if (false === $extensions || !is_scalar($fileName)) {
|
||||||
|
return static::TYPE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oneMatch = true;
|
||||||
|
foreach ($extensions as $extension) {
|
||||||
|
$oneMatch &= !!preg_match("#\.$extension$#i", $fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool)$oneMatch ? static::TYPE_MATCH : static::TYPE_NOT_MATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param null $filePath
|
||||||
|
* @return array
|
||||||
|
* @throws \Thelia\Exception\FileException
|
||||||
|
*/
|
||||||
|
public function parseFile($filePath = null)
|
||||||
|
{
|
||||||
|
if (null === $filePath) {
|
||||||
|
$filePath = THELIA_ROOT . static::TYPES_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fileHandle = @fopen($filePath, "r");
|
||||||
|
|
||||||
|
if ($fileHandle === false) {
|
||||||
|
throw new FileException(
|
||||||
|
Translator::getInstance()->trans(
|
||||||
|
"The file %file could not be opened",
|
||||||
|
[
|
||||||
|
"%file" => $filePath,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$typesArray = [];
|
||||||
|
|
||||||
|
while (false !== $line = fgets($fileHandle)) {
|
||||||
|
$line = $this->realTrim($line);
|
||||||
|
|
||||||
|
$line = preg_replace("#\#.*$#", "", $line);
|
||||||
|
|
||||||
|
$table = explode(" ", $line);
|
||||||
|
|
||||||
|
$mime = array_shift($table);
|
||||||
|
|
||||||
|
if (!empty($table) && !empty($mime)) {
|
||||||
|
$typesArray[$mime] = $table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!feof($fileHandle)) {
|
||||||
|
throw new FileException(
|
||||||
|
Translator::getInstance()->trans(
|
||||||
|
"An error occurred while reading the file %file",
|
||||||
|
[
|
||||||
|
"%file" => $filePath,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $typesArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $string
|
||||||
|
* @param string $characterMask
|
||||||
|
* @return mixed|string
|
||||||
|
*/
|
||||||
|
public function realTrim($string, $characterMask = "\t\n\r ")
|
||||||
|
{
|
||||||
|
$string = trim($string, $characterMask);
|
||||||
|
$charLen = strlen($characterMask);
|
||||||
|
|
||||||
|
$string = preg_replace(
|
||||||
|
"#[$characterMask]+#",
|
||||||
|
" ",
|
||||||
|
$string
|
||||||
|
);
|
||||||
|
|
||||||
|
return $string;
|
||||||
|
}
|
||||||
|
}
|
||||||
1595
local/config/mime.types
Normal file
1595
local/config/mime.types
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user