diff --git a/typo3/sysext/core/Documentation/Changelog/master/Breaking-69227-StringsForLikeAreNotProperlyEscaped.rst b/typo3/sysext/core/Documentation/Changelog/master/Breaking-69227-StringsForLikeAreNotProperlyEscaped.rst new file mode 100644 index 0000000000000000000000000000000000000000..353e5ebf17ab36642556de09d451f9dbc4c93165 --- /dev/null +++ b/typo3/sysext/core/Documentation/Changelog/master/Breaking-69227-StringsForLikeAreNotProperlyEscaped.rst @@ -0,0 +1,34 @@ +============================================================ +Breaking: #69227 - Strings for like are not properly escaped +============================================================ + +Description +=========== + +The following constants are removed: + +* ``\TYPO3\CMS\IndexedSearch\Controller\SearchFormController::WILDCARD_LEFT`` +* ``\TYPO3\CMS\IndexedSearch\Controller\SearchFormController::WILDCARD_RIGHT`` +* ``\TYPO3\CMS\IndexedSearch\Domain\Repository\IndexSearchRepository::WILDCARD_LEFT`` +* ``\TYPO3\CMS\IndexedSearch\Domain\Repository\IndexSearchRepository::WILDCARD_RIGHT`` + + +Impact +====== + +The mentioned constants don't exist anymore. + + +Affected Installations +====================== + +Third party code using the mentioned constants + + +Migration +========= + +Use the new constants: + +* ``\TYPO3\CMS\IndexedSearch\Utility\LikeWildcard::LEFT`` +* ``\TYPO3\CMS\IndexedSearch\Utility\LikeWildcard::RIGHT`` \ No newline at end of file diff --git a/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php b/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php index 4b42ec08d4ba1463cb973f82ca466fb15b3ea266..003c4b8005fe37bb8c2cccfa162486e3fb277c7f 100755 --- a/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php +++ b/typo3/sysext/indexed_search/Classes/Controller/SearchFormController.php @@ -16,6 +16,7 @@ namespace TYPO3\CMS\IndexedSearch\Controller; use TYPO3\CMS\Core\Html\HtmlParser; use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\IndexedSearch\Utility; /** * Index search frontend @@ -169,8 +170,6 @@ class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin { */ public $lexerObj; - const WILDCARD_LEFT = 1; - const WILDCARD_RIGHT = 2; /** * Main function, called from TypoScript as a USER_INT object. * @@ -807,15 +806,15 @@ class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin { switch ($theType) { case '1': // Part of word - $res = $this->searchWord($sWord, self::WILDCARD_LEFT | self::WILDCARD_RIGHT); + $res = $this->searchWord($sWord, Utility\LikeWildcard::BOTH); break; case '2': // First part of word - $res = $this->searchWord($sWord, self::WILDCARD_RIGHT); + $res = $this->searchWord($sWord, Utility\LikeWildcard::RIGHT); break; case '3': // Last part of word - $res = $this->searchWord($sWord, self::WILDCARD_LEFT); + $res = $this->searchWord($sWord, Utility\LikeWildcard::LEFT); break; case '10': // Sounds like @@ -892,13 +891,17 @@ class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin { * Search for a word * * @param string $sWord Word to search for - * @param int $mode Bit-field which can contain WILDCARD_LEFT and/or WILDCARD_RIGHT + * @param int $wildcard Bit-field of Utility\LikeWildcard * @return bool|\mysqli_result SQL result pointer */ - public function searchWord($sWord, $mode) { - $wildcard_left = $mode & self::WILDCARD_LEFT ? '%' : ''; - $wildcard_right = $mode & self::WILDCARD_RIGHT ? '%' : ''; - $wSel = 'IW.baseword LIKE \'' . $wildcard_left . $this->databaseConnection->quoteStr($sWord, 'index_words') . $wildcard_right . '\''; + public function searchWord($sWord, $wildcard) { + $likeWildcard = Utility\LikeWildcard::cast($wildcard); + $wSel = $likeWildcard->getLikeQueryPart( + 'index_words', + 'IW.baseword', + $sWord + ); + $this->wSelClauses[] = $wSel; $res = $this->execPHashListQuery($wSel, ' AND is_stopword=0'); return $res; @@ -924,11 +927,18 @@ class SearchFormController extends \TYPO3\CMS\Frontend\Plugin\AbstractPlugin { * @return bool|\mysqli_result SQL result pointer */ public function searchSentence($sSentence) { - $res = $this->databaseConnection->exec_SELECTquery('ISEC.phash', 'index_section ISEC, index_fulltext IFT', 'IFT.fulltextdata LIKE \'%' . $this->databaseConnection->quoteStr($sSentence, 'index_fulltext') . '%\' AND - ISEC.phash = IFT.phash - ' . $this->sectionTableWhere(), 'ISEC.phash'); $this->wSelClauses[] = '1=1'; - return $res; + $likeWildcard = Utility\LikeWildcard::cast(Utility\LikeWildcard::BOTH); + $likePart = $likeWildcard->getLikeQueryPart( + 'index_fulltext', + 'IFT.fulltextdata', + $sSentence + ); + + return $this->databaseConnection->exec_SELECTquery('ISEC.phash', + 'index_section ISEC, index_fulltext IFT', + $likePart . ' AND ISEC.phash = IFT.phash' . $this->sectionTableWhere(), 'ISEC.phash' + ); } /** diff --git a/typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php b/typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php index f2a3d78252239f5ae97d147923b975c0a7d4172d..55a7e7d473d1c6ea6dbe3cf66161200d85a46a53 100644 --- a/typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php +++ b/typo3/sysext/indexed_search/Classes/Domain/Repository/IndexSearchRepository.php @@ -17,7 +17,7 @@ namespace TYPO3\CMS\IndexedSearch\Domain\Repository; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; use TYPO3\CMS\IndexedSearch\Indexer; -use TYPO3\CMS\IndexedSearch\Utility\IndexedSearchUtility; +use TYPO3\CMS\IndexedSearch\Utility; /** * Index search abstraction to search through the index @@ -156,10 +156,6 @@ class IndexSearchRepository { */ protected $displayForbiddenRecords = FALSE; - // constants to help where to use wildcards in SQL like queries - const WILDCARD_LEFT = 1; - const WILDCARD_RIGHT = 2; - /** * initialize all options that are necessary for the search * @@ -345,15 +341,15 @@ class IndexSearchRepository { switch ($theType) { case '1': // Part of word - $res = $this->searchWord($sWord, self::WILDCARD_LEFT | self::WILDCARD_RIGHT); + $res = $this->searchWord($sWord, Utility\LikeWildcard::BOTH); break; case '2': // First part of word - $res = $this->searchWord($sWord, self::WILDCARD_RIGHT); + $res = $this->searchWord($sWord, Utility\LikeWildcard::RIGHT); break; case '3': // Last part of word - $res = $this->searchWord($sWord, self::WILDCARD_LEFT); + $res = $this->searchWord($sWord, Utility\LikeWildcard::LEFT); break; case '10': // Sounds like @@ -434,13 +430,16 @@ class IndexSearchRepository { * Search for a word * * @param string $sWord the search word - * @param int $mode constant from this class to see if the wildcard should be left and/or right of the search string + * @param int $wildcard Bit-field of Utility\LikeWildcard * @return bool|\mysqli_result SQL result pointer */ - protected function searchWord($sWord, $mode) { - $wildcard_left = $mode & self::WILDCARD_LEFT ? '%' : ''; - $wildcard_right = $mode & self::WILDCARD_RIGHT ? '%' : ''; - $wSel = 'IW.baseword LIKE \'' . $wildcard_left . $this->getDatabaseConnection()->quoteStr($sWord, 'index_words') . $wildcard_right . '\''; + protected function searchWord($sWord, $wildcard) { + $likeWildcard = Utility\LikeWildcard::cast($wildcard); + $wSel = $likeWildcard->getLikeQueryPart( + 'index_words', + 'IW.baseword', + $sWord + ); $this->wSelClauses[] = $wSel; return $this->execPHashListQuery($wSel, ' AND is_stopword=0'); } @@ -465,16 +464,17 @@ class IndexSearchRepository { */ protected function searchSentence($sWord) { $this->wSelClauses[] = '1=1'; - $sWord = $this->getDatabaseConnection()->quoteStr( - $this->getDatabaseConnection()->escapeStrForLike($sWord, 'index_fulltext'), - 'index_fulltext' + $likeWildcard = Utility\LikeWildcard::cast(Utility\LikeWildcard::BOTH); + $likePart = $likeWildcard->getLikeQueryPart( + 'index_fulltext', + 'IFT.fulltextdata', + $sWord ); + return $this->getDatabaseConnection()->exec_SELECTquery( 'ISEC.phash', 'index_section ISEC, index_fulltext IFT', - 'IFT.fulltextdata LIKE \'%' . $sWord - . '%\' AND ISEC.phash = IFT.phash' - . $this->sectionTableWhere(), + $likePart . ' AND ISEC.phash = IFT.phash' . $this->sectionTableWhere(), 'ISEC.phash' ); } @@ -839,7 +839,7 @@ class IndexSearchRepository { * @return int Integer intepretation of the md5 hash of input string. */ protected function md5inthash($str) { - return IndexedSearchUtility::md5inthash($str); + return Utility\IndexedSearchUtility::md5inthash($str); } /** @@ -851,7 +851,7 @@ class IndexSearchRepository { * @return bool TRUE if given tables are enabled */ protected function isTableUsed($table_list) { - return IndexedSearchUtility::isTableUsed($table_list); + return Utility\IndexedSearchUtility::isTableUsed($table_list); } /** diff --git a/typo3/sysext/indexed_search/Classes/Utility/LikeWildcard.php b/typo3/sysext/indexed_search/Classes/Utility/LikeWildcard.php new file mode 100644 index 0000000000000000000000000000000000000000..91af8bf268b8d61f552c1145141d3dc54f011b7c --- /dev/null +++ b/typo3/sysext/indexed_search/Classes/Utility/LikeWildcard.php @@ -0,0 +1,59 @@ +<?php +namespace TYPO3\CMS\IndexedSearch\Utility; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +/** + * Enumeration object for LikeWildcard + */ +class LikeWildcard extends \TYPO3\CMS\Core\Type\Enumeration { + + const __default = self::BOTH; + + /** @var int Do not use any wildcard */ + const NONE = 0; + + /** @var int Use wildcard on left side */ + const LEFT = 1; + + /** @var int Use wildcard on right side */ + const RIGHT = 2; + + /** @var int Use wildcard on both sides */ + const BOTH = 3; + + /** + * Returns a LIKE clause for sql queries. + * + * @param string $tableName The name of the table to query. + * @param string $fieldName The name of the field to query with LIKE. + * @param string $likeValue The value for the LIKE clause operation. + * @return string + * @throws \TYPO3\CMS\Core\Type\Exception\InvalidEnumerationValueException + */ + public function getLikeQueryPart($tableName, $fieldName, $likeValue) { + $databaseConnection = $GLOBALS['TYPO3_DB']; + + $likeValue = $databaseConnection->quoteStr( + $databaseConnection->escapeStrForLike($likeValue, $tableName), + $tableName + ); + + return $fieldName . ' LIKE \'' + . ($this->value & self::LEFT ? '%' : '') + . $likeValue + . ($this->value & self::RIGHT ? '%' : '') + . '\''; + } +} diff --git a/typo3/sysext/indexed_search/Tests/Unit/Utility/LikeWildcardTest.php b/typo3/sysext/indexed_search/Tests/Unit/Utility/LikeWildcardTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fc01eb56916447b1e221e5477dfa12c72c77c07f --- /dev/null +++ b/typo3/sysext/indexed_search/Tests/Unit/Utility/LikeWildcardTest.php @@ -0,0 +1,112 @@ +<?php +namespace TYPO3\CMS\IndexedSearch\Tests\Unit\Utility; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ +use TYPO3\CMS\IndexedSearch\Utility\LikeWildcard; + +/** + * This class contains unit tests for the LikeQueryUtility + */ +class LikeWildcardTest extends \TYPO3\CMS\Core\Tests\UnitTestCase { + + /** + * Sets up this test case. + */ + protected function setUp() { + /** @var $databaseConnectionMock \TYPO3\CMS\Core\Database\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject */ + $databaseConnectionMock = $this->getMock(\TYPO3\CMS\Core\Database\DatabaseConnection::class, array('quoteStr')); + $databaseConnectionMock->method('quoteStr') + ->will($this->returnArgument(0)); + $GLOBALS['TYPO3_DB'] = $databaseConnectionMock; + } + + /** + * @test + * @param string $tableName + * @param string $fieldName + * @param string $likeValue + * @param int $wildcard + * @param string $expected + * @dataProvider getLikeQueryPartDataProvider + */ + public function getLikeQueryPart($tableName, $fieldName, $likeValue, $wildcard, $expected) { + $subject = \TYPO3\CMS\IndexedSearch\Utility\LikeWildcard::cast($wildcard); + $this->assertSame($expected, $subject->getLikeQueryPart($tableName, $fieldName, $likeValue)); + } + + /** + * Returns data sets for the test getLikeQueryPart + * Each dataset is an array with the following elements: + * - the table name + * - the field name + * - the search value + * - the wildcard mode + * - the expected result + * + * @return array + */ + public function getLikeQueryPartDataProvider() { + return [ + 'no placeholders and no wildcard mode' => [ + 'tt_content', + 'body', + 'searchstring', + LikeWildcard::NONE, + 'body LIKE \'searchstring\'' + ], + 'no placeholders and left wildcard mode' => [ + 'tt_content', + 'body', + 'searchstring', + LikeWildcard::LEFT, + 'body LIKE \'%searchstring\'' + ], + 'no placeholders and right wildcard mode' => [ + 'tt_content', + 'body', + 'searchstring', + LikeWildcard::RIGHT, + 'body LIKE \'searchstring%\'' + ], + 'no placeholders and both wildcards mode' => [ + 'tt_content', + 'body', + 'searchstring', + LikeWildcard::BOTH, + 'body LIKE \'%searchstring%\'' + ], + 'underscore placeholder and left wildcard mode' => [ + 'tt_content', + 'body', + 'search_string', + LikeWildcard::LEFT, + 'body LIKE \'%search\\_string\'' + ], + 'percent placeholder and right wildcard mode' => [ + 'tt_content', + 'body', + 'search%string', + LikeWildcard::RIGHT, + 'body LIKE \'search\\%string%\'' + ], + 'percent and underscore placeholder and both wildcards mode' => [ + 'tt_content', + 'body', + '_search%string_', + LikeWildcard::RIGHT, + 'body LIKE \'\\_search\\%string\\_%\'' + ], + ]; + } +}