From 868b5b49eb79237ce0153fb0d66612cfbf62c62f Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 18 Jun 2014 15:36:02 -0700 Subject: [PATCH 1/5] starting triple braces without ending triple braces throws an error - previously this was swallowing an additional character --- src/Mustache/Tokenizer.php | 27 +++++++++++++++++++-------- test/Mustache/Test/TokenizerTest.php | 11 +++++++++++ 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index bf1e5d1..c4ad3ea 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -151,7 +151,7 @@ class Mustache_Tokenizer default: if ($this->tagChange($this->ctag, $this->ctagLen, $text, $i)) { - $this->tokens[] = array( + $token = array( self::TYPE => $this->tagType, self::NAME => trim($this->buffer), self::OTAG => $this->otag, @@ -160,20 +160,31 @@ class Mustache_Tokenizer self::INDEX => ($this->tagType === self::T_END_SECTION) ? $this->seenTag - $this->otagLen : $i + $this->ctagLen ); - $this->buffer = ''; - $i += $this->ctagLen - 1; - $this->state = self::IN_TEXT; if ($this->tagType === self::T_UNESCAPED) { + // Clean up `{{{ tripleStache }}}` style tokens. if ($this->ctag === '}}') { - $i++; + if (($i < $len + 1) && $text[$i+2] == '}') { + $i++; + } else { + $msg = sprintf( + 'Uneven closing tag encountered: on line %d', + $token[Mustache_Tokenizer::LINE] + ); + + throw new Mustache_Exception_SyntaxException($msg, $token); + } } else { - // Clean up `{{{ tripleStache }}}` style tokens. - $lastName = $this->tokens[count($this->tokens) - 1][self::NAME]; + $lastName = $token[self::NAME]; if (substr($lastName, -1) === '}') { - $this->tokens[count($this->tokens) - 1][self::NAME] = trim(substr($lastName, 0, -1)); + $token[self::NAME] = trim(substr($lastName, 0, -1)); } } } + + $this->buffer = ''; + $i += $this->ctagLen - 1; + $this->state = self::IN_TEXT; + $this->tokens[] = $token; } else { $this->buffer .= $text[$i]; } diff --git a/test/Mustache/Test/TokenizerTest.php b/test/Mustache/Test/TokenizerTest.php index 42df8e1..ce24f56 100644 --- a/test/Mustache/Test/TokenizerTest.php +++ b/test/Mustache/Test/TokenizerTest.php @@ -24,6 +24,17 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase $this->assertSame($expected, $tokenizer->scan($text, $delimiters)); } + /** + * @expectedException Mustache_Exception_SyntaxException + */ + public function testUnevenBracesThrowExceptions() + { + $tokenizer = new Mustache_Tokenizer; + + $text = "{{{ name }}"; + $tokenizer->scan($text, null); + } + public function getTokens() { return array( From 40e86247ade53feea897586e8d5d566408fb3fa4 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 18 Jun 2014 16:34:32 -0700 Subject: [PATCH 2/5] math is hard - check the bounds of a string properly when looking to see if a closing } exists --- src/Mustache/Tokenizer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index c4ad3ea..9e7eb4b 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -163,7 +163,7 @@ class Mustache_Tokenizer if ($this->tagType === self::T_UNESCAPED) { // Clean up `{{{ tripleStache }}}` style tokens. if ($this->ctag === '}}') { - if (($i < $len + 1) && $text[$i+2] == '}') { + if (($i+2 < $len) && $text[$i+2] == '}') { $i++; } else { $msg = sprintf( From 842b31b4778f0190610de6bb36fa5962523ae549 Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 19 Jun 2014 11:29:21 -0700 Subject: [PATCH 3/5] clean up Tokenizer->scan -- add error annotation, spaces between +, strict type checking, and self instead of Mustache_Tokenizer --- src/Mustache/Tokenizer.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index 9e7eb4b..f8b7608 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -84,6 +84,8 @@ class Mustache_Tokenizer /** * Scan and tokenize template source. * + * @throws Mustache_Exception_SyntaxException when mismatched section tags are encountered. + * * @param string $text Mustache template source to tokenize * @param string $delimiters Optionally, pass initial opening and closing delimiters (default: null) * @@ -163,12 +165,12 @@ class Mustache_Tokenizer if ($this->tagType === self::T_UNESCAPED) { // Clean up `{{{ tripleStache }}}` style tokens. if ($this->ctag === '}}') { - if (($i+2 < $len) && $text[$i+2] == '}') { + if (($i+2 < $len) && $text[$i + 2] === '}') { $i++; } else { $msg = sprintf( 'Uneven closing tag encountered: on line %d', - $token[Mustache_Tokenizer::LINE] + $token[self::LINE] ); throw new Mustache_Exception_SyntaxException($msg, $token); From c037b31d254f00820a673ceb038cefac41c1014e Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 19 Jun 2014 20:51:02 -0700 Subject: [PATCH 4/5] add test coverage to ensure that custom delimiters don't swallow trailing characters after they close --- test/Mustache/Test/TokenizerTest.php | 47 ++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/Mustache/Test/TokenizerTest.php b/test/Mustache/Test/TokenizerTest.php index ce24f56..34fa86d 100644 --- a/test/Mustache/Test/TokenizerTest.php +++ b/test/Mustache/Test/TokenizerTest.php @@ -199,6 +199,53 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase ), ) ), + + // custom delimiters don't swallow the next character, even if it is a }, }}}, or the same delimiter + array( + "<% a %>} <% b %>%> <% c %>}}}", + "<% %>", + array( + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED, + Mustache_Tokenizer::NAME => 'a', + Mustache_Tokenizer::OTAG => '<%', + Mustache_Tokenizer::CTAG => '%>', + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::INDEX => 7, + ), + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT, + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::VALUE => "} ", + ), + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED, + Mustache_Tokenizer::NAME => 'b', + Mustache_Tokenizer::OTAG => '<%', + Mustache_Tokenizer::CTAG => '%>', + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::INDEX => 16, + ), + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT, + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::VALUE => "%> ", + ), + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_ESCAPED, + Mustache_Tokenizer::NAME => 'c', + Mustache_Tokenizer::OTAG => '<%', + Mustache_Tokenizer::CTAG => '%>', + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::INDEX => 26, + ), + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_TEXT, + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::VALUE => "}}}", + ), + ) + ), ); } } From 6ba77964b8a687e67276888d2f516ff70787663c Mon Sep 17 00:00:00 2001 From: steve Date: Thu, 19 Jun 2014 23:25:46 -0700 Subject: [PATCH 5/5] uneven braces with custom delimiters throws Mustache_Exception_SyntaxException --- src/Mustache/Tokenizer.php | 8 ++++++++ test/Mustache/Test/TokenizerTest.php | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/Mustache/Tokenizer.php b/src/Mustache/Tokenizer.php index f8b7608..fa834d9 100644 --- a/src/Mustache/Tokenizer.php +++ b/src/Mustache/Tokenizer.php @@ -179,6 +179,14 @@ class Mustache_Tokenizer $lastName = $token[self::NAME]; if (substr($lastName, -1) === '}') { $token[self::NAME] = trim(substr($lastName, 0, -1)); + } else { + $msg = sprintf( + 'Uneven closing tag encountered: %s on line %d', + substr($lastName, -1), + $token[self::LINE] + ); + + throw new Mustache_Exception_SyntaxException($msg, $token); } } } diff --git a/test/Mustache/Test/TokenizerTest.php b/test/Mustache/Test/TokenizerTest.php index 34fa86d..67df941 100644 --- a/test/Mustache/Test/TokenizerTest.php +++ b/test/Mustache/Test/TokenizerTest.php @@ -35,6 +35,17 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase $tokenizer->scan($text, null); } + /** + * @expectedException Mustache_Exception_SyntaxException + */ + public function testUnevenBracesWithCustomDelimiterThrowExceptions() + { + $tokenizer = new Mustache_Tokenizer; + + $text = "<%{ name %>"; + $tokenizer->scan($text, "<% %>"); + } + public function getTokens() { return array( @@ -246,6 +257,22 @@ class Mustache_Test_TokenizerTest extends PHPUnit_Framework_TestCase ), ) ), + + // unescaped custom delimiters are properly parsed + array( + "<%{ a }%>", + "<% %>", + array( + array( + Mustache_Tokenizer::TYPE => Mustache_Tokenizer::T_UNESCAPED, + Mustache_Tokenizer::NAME => 'a', + Mustache_Tokenizer::OTAG => '<%', + Mustache_Tokenizer::CTAG => '%>', + Mustache_Tokenizer::LINE => 0, + Mustache_Tokenizer::INDEX => 9, + ) + ) + ), ); } }