Commit 2d5245a3 authored by slashrsm's avatar slashrsm
Browse files

Merge pull request #4 from primsi/implove_file_uploads

by primsi: Improve file upload handling.
parents 8675311e 3f03dbe7
......@@ -24,11 +24,15 @@
var dropzoneInstance = new Dropzone("#" + selector.attr("id"), $.extend({}, instanceConfig, config));
// React on add file. Add only accepted files.
dropzoneInstance.on("addedfile", function(file) {
dropzoneInstance.on("success", function(file, response) {
var uploadedFilesElement = selector.siblings(':hidden');
var currentValue = uploadedFilesElement.attr('value');
uploadedFilesElement.attr('value', currentValue + file.name + ';');
// The file is transliterated on upload. The element has to reflect
// the real filename.
file.processedName = response.result;
uploadedFilesElement.attr('value', currentValue + response.result + ';');
});
// React on file removing.
......@@ -40,7 +44,7 @@
if (currentValue.length) {
var fileNames = currentValue.split(";");
for (var i in fileNames) {
if (fileNames[i] == file.name) {
if (fileNames[i] == file.processedName) {
fileNames.splice(i,1);
break;
}
......
......@@ -10,6 +10,7 @@ namespace Drupal\dropzonejs\Controller;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Transliteration\PhpTransliteration;
use Drupal\dropzonejs\UploadException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
......@@ -20,6 +21,11 @@ use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
/**
* Handles requests that dropzone issues when uploading files.
*
* The uploaded file will be stored in the configured tmp folder and will be
* added a tmp extension. Further filename processing will be done in
* Drupal\dropzonejs\Element::valueCallback. This means that the final
* filename will be provided only after that callback.
*/
class UploadController extends ControllerBase {
......@@ -49,6 +55,13 @@ class UploadController extends ControllerBase {
*/
protected $filename;
/**
* Transliteration service.
*
* @var \Drupal\Core\Transliteration\PhpTransliteration
*/
protected $transliteration;
/**
* Constructs dropzone upload controller route controller.
*
......@@ -56,11 +69,14 @@ class UploadController extends ControllerBase {
* Request object.
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* Config factory.
* @param \Drupal\Core\Transliteration\PhpTransliteration $transliteration
* Transliteration service.
*/
public function __construct(Request $request, ConfigFactoryInterface $config) {
public function __construct(Request $request, ConfigFactoryInterface $config, PhpTransliteration $transliteration) {
$this->request = $request;
$tmp_override = $config->get('dropzonejs.settings')->get('tmp_dir');
$this->temporaryUploadLocation = ($tmp_override) ? $tmp_override : $config->get('system.file')->get('path.temporary');
$this->trasliteration = $transliteration;
}
/**
......@@ -69,7 +85,8 @@ class UploadController extends ControllerBase {
public static function create(ContainerInterface $container) {
return new static(
$container->get('request_stack')->getCurrentRequest(),
$container->get('config.factory')
$container->get('config.factory'),
$container->get('transliteration')
);
}
......@@ -90,7 +107,7 @@ class UploadController extends ControllerBase {
// Controllers should return a response.
return new JsonResponse([
'jsonrpc' => '2.0',
'result' => NULL,
'result' => $this->filename,
'id' => 'id',
], 200);
}
......@@ -123,13 +140,23 @@ class UploadController extends ControllerBase {
*/
protected function getFilename(UploadedFile $file) {
if (empty($this->filename)) {
$this->filename = $file->getClientOriginalName();
$original_name = $file->getClientOriginalName();
// Check the file name for security reasons; it must contain letters,
// numbers and underscores.
if (!preg_match('/[\w\.]/', $this->filename)) {
// There should be a filename and it should not contain a semicolon,
// which we use to separate filenames.
if (!isset($original_name)) {
throw new UploadException(UploadException::FILENAME_ERROR);
}
// Transliterate.
$processed_filename = \Drupal::transliteration()->transliterate($original_name);
// For security reasons append the txt extension. It will be removed in
// Drupal\dropzonejs\Element::valueCallback when we will know the valid
// extension and we will be abble to properly sanitaze the filename.
$processed_filename = $processed_filename . '.txt';
$this->filename = $processed_filename;
}
return $this->filename;
......
......@@ -25,6 +25,8 @@ use Drupal\Core\Render\Element\FormElement;
* - #max_filesize
* Used by dropzonejs and expressed in MB. See
* http://www.dropzonejs.com/#config-maxFilesize
* - #extensions
* A string of valid extensions separated by a space.
*
* When submitted the element returns an array of temporary file locations. It's
* the duty of the environment that implements this element to handle the
......@@ -33,6 +35,12 @@ use Drupal\Core\Render\Element\FormElement;
* @FormElement("dropzonejs")
*/
class DropzoneJs extends FormElement {
/**
* A defualut set of valid extensions.
*/
const DEFAULT_VALID_EXTENSIONS = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
/**
* {@inheritdoc}
*/
......@@ -80,9 +88,12 @@ class DropzoneJs extends FormElement {
$element['#attached']['drupalSettings']['dropzonejs'] = [
'upload_path' => \Drupal::url('dropzonejs.upload'),
'instances' => [
// Configuration keys are matched with DropzoneJS configuration
// options.
$element['#id'] => [
'maxFilesize' => $element['#max_filesize'],
'dictDefaultMessage' => $element['#dropzone_description']
'dictDefaultMessage' => $element['#dropzone_description'],
'acceptedFiles' => '.' . str_replace(' ', ',.', self::getValidExtensions()),
],
],
];
......@@ -106,7 +117,26 @@ class DropzoneJs extends FormElement {
$temp_path = \Drupal::config('system.file')->get('path.temporary');
foreach ($file_names as $name) {
$return['uploaded_files'][] = "$temp_path/$name";
// The upload handler appended the txt extension to the file for
// security reasons. We will remove it in this callback.
$old_filepath = "$temp_path/$name";
// The upload handler appended the txt extension to the file for
// security reasons. Because here we know the acceptable extensions
// we can remove that extension and sanitize the filename.
$name = self::fixTmpFilename($name);
$name = file_munge_filename($name, self::getValidExtensions());
// Finaly rename the file and add it to results.
$new_filepath = "$temp_path/$name";
$move_result = file_unmanaged_move($old_filepath, $new_filepath);
if ($move_result) {
$return['uploaded_files'][] = $move_result;
}
else {
drupal_set_message(t('There was a problem while processing the file named @name', ['@name' => $name]), 'error');
}
}
}
$form_state->setValueForElement($element, $return);
......@@ -114,4 +144,32 @@ class DropzoneJs extends FormElement {
return $return;
}
}
/**
* Gets valid file extensions for this element.
*
* @return string
* A space separated list of extensions.
*/
public static function getValidExtensions() {
return isset($element['#extensions']) ? $element['#extensions'] : self::DEFAULT_VALID_EXTENSIONS;
}
/**
* Fix temporary filename.
*
* The upload handler appended the txt extension to the file for
* security reasons.
*
* @param string $filename
* The filename we need to fix.
*
* @return string
* The fixed filename.
*/
public static function fixTmpFilename($filename) {
$parts = explode('.', $filename);
array_pop($parts);
return implode('.', $parts);
}
}
......@@ -2,14 +2,14 @@
/**
* @file
* Contains \Drupal\dropzonejs\DropzoneJsUploadException.
* Contains \Drupal\dropzonejs\UploadException.
*/
namespace Drupal\dropzonejs;
use Symfony\Component\HttpFoundation\JsonResponse;
class UploadExceptionn extends \Exception {
class UploadException extends \Exception {
/**
* Error with input stream.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment