[BUGFIX] Single quote-enclosed strings are not properly parsed
authorTizian Schmidlin <st@cabag.ch>
Sun, 7 Aug 2011 09:25:14 +0000 (11:25 +0200)
committerXavier Perseguers <xavier@typo3.org>
Tue, 11 Oct 2011 08:57:39 +0000 (10:57 +0200)
When using a MSSQL database, the parser fails when simple quotes are escaped by
the ADOdb mssql driver.

Since MSSQL escapes simple quotes with another simple quote, the parser seems to
think that a new statement begins and fatally crashes because no operator is set.

Change-Id: I857aebb00efd1dc0752d42d436e6e9950a336370
Fixes: #27858
Releases: 4.5, 4.6

typo3/sysext/dbal/class.ux_t3lib_sqlparser.php
typo3/sysext/dbal/tests/dbMssqlTest.php
typo3/sysext/dbal/tests/sqlParserGeneralTest.php

index f1ea9a1..5851b41 100644 (file)
 class ux_t3lib_sqlparser extends t3lib_sqlparser {
 
        /**
+        * Gets value in quotes from $parseString.
+        *
+        * @param string $parseString String from which to find value in quotes. Notice that $parseString is passed by reference and is shortened by the output of this function.
+        * @param string $quote The quote used; input either " or '
+        * @return string The value, passed through parseStripslashes()!
+        */
+       protected function getValueInQuotes(&$parseString, $quote) {
+               switch ((string) $GLOBALS['TYPO3_DB']->handlerCfg[$GLOBALS['TYPO3_DB']->lastHandlerKey]['type']) {
+                       case 'adodb':
+                               if ($GLOBALS['TYPO3_DB']->runningADOdbDriver('mssql')) {
+                                       $value = $this->getValueInQuotesMssql($parseString, $quote);
+                               } else {
+                                       $value = parent::getValueInQuotes($parseString, $quote);
+                               }
+                               break;
+                       default:
+                               $value = parent::getValueInQuotes($parseString, $quote);
+                               break;
+               }
+
+               return $value;
+       }
+
+       /**
+        * Gets value in quotes from $parseString. This method targets MSSQL exclusively.
+        *
+        * @param string $parseString String from which to find value in quotes. Notice that $parseString is passed by reference and is shortened by the output of this function.
+        * @param $quote The quote used; input either " or '
+        * @return string
+        */
+       protected function getValueInQuotesMssql(&$parseString, $quote) {
+               $previousIsQuote = FALSE;
+               $inQuote = FALSE;
+
+               // Go through the whole string
+               for ($c = 0; $c < strlen($parseString); $c++) {
+                       // If the parsed string character is the quote string
+                       if ($parseString{$c} === $quote) {
+                               // If we are already in a quote
+                               if ($inQuote) {
+                                       // Was the previous a quote?
+                                       if ($previousIsQuote) {
+                                               // If yes, replace it by a \
+                                               $parseString{$c - 1} = '\\';
+                                       }
+                                       // Invert the state
+                                       $previousIsQuote = !$previousIsQuote;
+                               } else {
+                                       // So we are in a quote since now
+                                       $inQuote = TRUE;
+                               }
+                               // So the parsed character is not a quote char and the previous char was a quote? so we are not in a quote anymore
+                       } elseif ($inQuote && $previousIsQuote) {
+                               $inQuote = FALSE;
+                               $previousIsQuote = FALSE;
+                               // So we are still in a quote and the previous is not a quote
+                       } else {
+                               $previousIsQuote = FALSE;
+                       }
+               }
+
+               $parts = explode($quote, substr($parseString, 1));
+
+               $buffer = '';
+               foreach ($parts as $k => $v) {
+                       $buffer .= $v;
+
+                       $reg = array();
+                       preg_match('/\\\\$/', $v, $reg);
+                       if ($reg && strlen($reg[0]) % 2) {
+                               $buffer .= $quote;
+                       } else {
+                               $parseString = ltrim(substr($parseString, strlen($buffer) + 2));
+                               return $this->parseStripslashes($buffer);
+                       }
+               }
+       }
+
+       /**
         * Compiles a "SELECT [output] FROM..:" field list based on input array (made with ->parseFieldList())
         * Can also compile field lists for ORDER BY and GROUP BY.
         *
index 88b4930..56ef499 100644 (file)
@@ -139,6 +139,18 @@ class dbMssqlTest extends BaseTestCase {
                $this->assertEquals($expected, $query);
        }
 
+       /**
+        * @test
+        * @see http://forge.typo3.org/issues/27858
+        */
+       public function canParseSingleQuote() {
+               $parseString = 'SELECT * FROM pages WHERE title=\'1\'\'\' AND deleted=0';
+               $components = $GLOBALS['TYPO3_DB']->SQLparser->_callRef('parseSELECT', $parseString);
+
+               $this->assertTrue(is_array($components), $components);
+               $this->assertTrue(empty($components['parseString']), 'parseString is not empty');
+       }
+
        ///////////////////////////////////////
        // Tests concerning remapping with
        // external (non-TYPO3) databases
index 37d6164..6ddd86e 100644 (file)
@@ -379,6 +379,18 @@ class sqlParserGeneralTest extends BaseTestCase {
                $this->assertEquals($expected, $selectTable);
        }
 
+       /**
+        * @test
+        * @see http://forge.typo3.org/issues/27858
+        */
+       public function canParseSingleQuote() {
+               $parseString = 'SELECT * FROM pages WHERE title=\'1\\\'\' AND deleted=0';
+               $components = $this->fixture->_callRef('parseSELECT', $parseString);
+
+               $this->assertTrue(is_array($components), $components);
+               $this->assertTrue(empty($components['parseString']), 'parseString is not empty');
+       }
+
        ///////////////////////////////////////
        // Tests concerning JOINs
        ///////////////////////////////////////