[BUGFIX] Properly escape "dropzone-target" selector in DragUploader.js 25/54825/3
authorGiannis Economou <gecon@antithesis.gr>
Thu, 6 Jul 2017 11:20:21 +0000 (14:20 +0300)
committerSusanne Moog <susanne.moog@typo3.org>
Wed, 6 Dec 2017 20:00:01 +0000 (21:00 +0100)
We properly escape some characters of "dropzone-target" data attribute,
since it is being used as a CSS selector to insert the dropzone in
our DOM. The "dropzone-target" might contain characters that have a
special meaning in CSS, like for example a dot. Especially the dot
is typical for cases like flexforms fields.

This allows drag and drop file uploads even on such cases (like for
example working drag and drop file uploads in DCE content elements).

Resolves: #81812
Releases: master, 8.7
Change-Id: Ib1f5b5063e390f08436fd3a51978842754b698ef
Reviewed-on: https://review.typo3.org/54825
Tested-by: TYPO3com <no-reply@typo3.com>
Reviewed-by: Susanne Moog <susanne.moog@typo3.org>
Tested-by: Susanne Moog <susanne.moog@typo3.org>
typo3/sysext/backend/Classes/Form/Container/InlineControlContainer.php
typo3/sysext/backend/Resources/Public/JavaScript/DragUploader.js
typo3/sysext/core/Classes/Utility/StringUtility.php
typo3/sysext/core/Tests/Unit/Utility/StringUtilityTest.php

index 4b3eaad..4ef669e 100644 (file)
@@ -24,6 +24,7 @@ use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\MathUtility;
+use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Lang\LanguageService;
 
 /**
@@ -444,8 +445,9 @@ class InlineControlContainer extends AbstractContainer
 
         $foreign_table = $inlineConfiguration['foreign_table'];
         $allowed = $groupFieldConfiguration['allowed'];
-        $objectPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']) . '-' . $foreign_table;
-        $nameObject = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
+        $currentStructureDomObjectIdPrefix = $this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid']);
+        $objectPrefix = $currentStructureDomObjectIdPrefix . '-' . $foreign_table;
+        $nameObject = $currentStructureDomObjectIdPrefix;
         $mode = 'db';
         $showUpload = false;
         $elementBrowserEnabled = true;
@@ -507,7 +509,7 @@ class InlineControlContainer extends AbstractContainer
                 $maxFileSize = GeneralUtility::getMaxUploadFileSize() * 1024;
                 $item .= ' <a href="#" class="btn btn-default t3js-drag-uploader inlineNewFileUploadButton ' . $this->inlineData['config'][$nameObject]['md5'] . '"
                                        ' . $buttonStyle . '
-                                       data-dropzone-target="#' . htmlspecialchars($this->inlineStackProcessor->getCurrentStructureDomObjectIdPrefix($this->data['inlineFirstPid'])) . '"
+                                       data-dropzone-target="#' . htmlspecialchars(StringUtility::escapeCssSelector($currentStructureDomObjectIdPrefix)) . '"
                                        data-insert-dropzone-before="1"
                                        data-file-irre-object="' . htmlspecialchars($objectPrefix) . '"
                                        data-file-allowed="' . htmlspecialchars($allowed) . '"
index 9e575dd..ebc1a33 100644 (file)
@@ -57,15 +57,16 @@ define(['jquery',
                var me = this;
                me.$body = $('body');
                me.$element = $(element);
-               me.$trigger = $(me.$element.data('dropzone-trigger'));
+               me.$trigger = $(me.$element.data('dropzoneTrigger'));
                me.$dropzone = $('<div />').addClass('dropzone').hide();
-               me.irreObjectUid = me.$element.data('file-irre-object');
-               if (me.irreObjectUid && me.$element.nextAll(me.$element.data('dropzone-target')).length !== 0) {
+               me.irreObjectUid = me.$element.data('fileIrreObject');
+               var dropZoneEscapedTarget = me.$element.data('dropzoneTarget');
+               if (me.irreObjectUid && me.$element.nextAll(dropZoneEscapedTarget).length !== 0) {
                        me.dropZoneInsertBefore = true;
-                       me.$dropzone.insertBefore(me.$element.data('dropzone-target'));
+                       me.$dropzone.insertBefore(dropZoneEscapedTarget);
                } else {
                        me.dropZoneInsertBefore = false;
-                       me.$dropzone.insertAfter(me.$element.data('dropzone-target'));
+                       me.$dropzone.insertAfter(dropZoneEscapedTarget);
                }
                me.$dropzoneMask = $('<div />').addClass('dropzone-mask').appendTo(me.$dropzone);
                me.$fileInput = $('<input type="file" multiple name="files[]" />').addClass('upload-file-picker').appendTo(me.$body);
index a4800df..c9ff0c9 100644 (file)
@@ -93,4 +93,18 @@ class StringUtility
         $uniqueId = uniqid($prefix, true);
         return str_replace('.', '', $uniqueId);
     }
+
+    /**
+     * Escape a CSS selector to be used for DOM queries
+     *
+     * This method takes care to escape any CSS selector meta character.
+     * The result may be used to query the DOM like $('#' + escapedSelector)
+     *
+     * @param string $selector
+     * @return string
+     */
+    public static function escapeCssSelector(string $selector) : string
+    {
+        return preg_replace('([#:.\\[\\],=@])', '\\$1', $selector);
+    }
 }
index 73cd95d..52f14af 100644 (file)
@@ -217,4 +217,28 @@ class StringUtilityTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
     {
         $this->assertNotContains('.', StringUtility::getUniqueId());
     }
+
+    /**
+     * @param string $selector
+     * @param string $expectedValue
+     * @dataProvider escapeCssSelectorDataProvider
+     */
+    public function escapeCssSelector(string $selector, string $expectedValue)
+    {
+        $this->assertEquals($expectedValue, StringUtility::escapeCssSelector($selector));
+    }
+
+    /**
+     * @return array
+     */
+    public function escapeCssSelectorDataProvider() : array
+    {
+        return [
+            ['data.field', 'data\\.field'],
+            ['#theId', '\\#theId'],
+            ['.theId:hover', '\\.theId\\:hover'],
+            ['.theId:hover', '\\.theId\\:hover'],
+            ['input[name=foo]', 'input\\[name\\=foo\\]'],
+        ];
+    }
 }