diff --git a/lib/Sabberworm/CSS/Parsing/ParserState.php b/lib/Sabberworm/CSS/Parsing/ParserState.php index ad79820a..2271d03e 100644 --- a/lib/Sabberworm/CSS/Parsing/ParserState.php +++ b/lib/Sabberworm/CSS/Parsing/ParserState.php @@ -48,8 +48,30 @@ public function getSettings() { return $this->oParserSettings; } - public function parseIdentifier($bIgnoreCase = true) { - $sResult = $this->parseCharacter(true); + public function parseIdentifier($bIgnoreCase = true, $bNameStartCodePoint = true) { + $sResult = null; + $bCanParseCharacter = true; + + if ( $bNameStartCodePoint ) { + // Check if 3 code points would start an identifier. See . + $sNameStartCodePoint = '[a-zA-Z_]|[\x80-\xFF]'; + $sEscapeCode = '\\[^\r\n\f]'; + + if ( + ! ( + preg_match("/^-([-${sNameStartCodePoint}]|${sEscapeCode})/isSu", $this->peek(3)) || + preg_match("/^${sNameStartCodePoint}/isSu", $this->peek()) || + preg_match("/^${sEscapeCode}/isS", $this->peek(2)) + ) + ) { + $bCanParseCharacter = false; + } + } + + if ( $bCanParseCharacter ) { + $sResult = $this->parseCharacter(true); + } + if ($sResult === null) { throw new UnexpectedTokenException($sResult, $this->peek(5), 'identifier', $this->iLineNo); } @@ -97,13 +119,13 @@ public function parseCharacter($bIsForIdentifier) { } if ($bIsForIdentifier) { $peek = ord($this->peek()); - // Ranges: a-z A-Z 0-9 - _ + // Matches a name code point. See . if (($peek >= 97 && $peek <= 122) || ($peek >= 65 && $peek <= 90) || ($peek >= 48 && $peek <= 57) || ($peek === 45) || ($peek === 95) || - ($peek > 0xa1)) { + ($peek > 0x81)) { return $this->consume(1); } } else { @@ -261,22 +283,22 @@ public function strlen($sString) { return mb_strlen($sString, $this->sCharset); } else { return strlen($sString); - } - } + } + } private function substr($iStart, $iLength) { if ($iLength < 0) { $iLength = $this->iLength - $iStart + $iLength; - } + } if ($iStart + $iLength > $this->iLength) { $iLength = $this->iLength - $iStart; - } + } $sResult = ''; while ($iLength > 0) { $sResult .= $this->aText[$iStart]; $iStart++; $iLength--; - } + } return $sResult; } diff --git a/lib/Sabberworm/CSS/Value/Color.php b/lib/Sabberworm/CSS/Value/Color.php index c6ed9b18..f02777f3 100644 --- a/lib/Sabberworm/CSS/Value/Color.php +++ b/lib/Sabberworm/CSS/Value/Color.php @@ -14,7 +14,7 @@ public static function parse(ParserState $oParserState) { $aColor = array(); if ($oParserState->comes('#')) { $oParserState->consume('#'); - $sValue = $oParserState->parseIdentifier(false); + $sValue = $oParserState->parseIdentifier(false, false); if ($oParserState->strlen($sValue) === 3) { $sValue = $sValue[0] . $sValue[0] . $sValue[1] . $sValue[1] . $sValue[2] . $sValue[2]; } else if ($oParserState->strlen($sValue) === 4) { diff --git a/tests/Sabberworm/CSS/ParserTest.php b/tests/Sabberworm/CSS/ParserTest.php index ea34f2e7..ff8c5c92 100644 --- a/tests/Sabberworm/CSS/ParserTest.php +++ b/tests/Sabberworm/CSS/ParserTest.php @@ -214,7 +214,7 @@ function testManipulation() { $this->assertSame('#header {margin: 10px 2em 1cm 2%;color: red !important;frequency: 30Hz;} body {color: green;}', $oDoc->render()); } - + function testRuleGetters() { $oDoc = $this->parsedStructureForFile('values'); $aBlocks = $oDoc->getAllDeclarationBlocks(); @@ -319,7 +319,7 @@ function testNamespaces() { |test {gaga: 2;}'; $this->assertSame($sExpected, $oDoc->render()); } - + function testInnerColors() { $oDoc = $this->parsedStructureForFile('inner-color'); $sExpected = 'test {background: -webkit-gradient(linear,0 0,0 bottom,from(#006cad),to(hsl(202,100%,49%)));}'; @@ -359,7 +359,7 @@ function testListValueRemoval() { $this->assertSame('@media screen {html {some: -test(val2);}} #unrelated {other: yes;}', $oDoc->render()); } - + /** * @expectedException Sabberworm\CSS\Parsing\OutputException */ @@ -766,4 +766,24 @@ function testLonelyImport() { $sExpected = "@import url(\"example.css\") only screen and (max-width: 600px);"; $this->assertSame($sExpected, $oDoc->render()); } + + function getInvalidIdentifiers() { + return array( + array('body { -0-transition: all .3s ease-in-out; }'), + array('body { 4-o-transition: all .3s ease-in-out; }'), + ); + } + + /** + * @dataProvider getInvalidIdentifiers + * + * @param string $css CSS text. + */ + function testInvalidIdentifier($css) { + $this->setExpectedException( 'Sabberworm\CSS\Parsing\UnexpectedTokenException' ); + + $oSettings = Settings::create()->withLenientParsing(false); + $oParser = new Parser($css, $oSettings); + $oParser->parse(); + } }