Skip to content

Commit

Permalink
Compiler: changed $context from tuple to string, enumerated all contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed Jun 27, 2016
1 parent 2ad6933 commit 5fda762
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 71 deletions.
80 changes: 42 additions & 38 deletions src/Latte/Compiler/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class Compiler
/** @var string */
private $contentType = self::CONTENT_HTML;

/** @var array [context, subcontext] */
/** @var string|NULL */
private $context;

/** @var mixed */
Expand Down Expand Up @@ -72,17 +72,23 @@ class Compiler

/** @internal Context-aware escaping HTML contexts */
const
// for CONTENT_HTML, CONTENT_XHTML
CONTEXT_HTML_TAG = 'tag',
CONTEXT_HTML_ATTRIBUTE = 'attr',
CONTEXT_HTML_ATTRIBUTE_URL = 'attrurl',
CONTEXT_HTML_COMMENT = 'comment',
CONTEXT_HTML_BOGUS_COMMENT = 'bogus',
CONTEXT_HTML_CSS = 'css',
CONTEXT_HTML_JS = 'js',

CONTEXT_HTML_TEXT = NULL,
CONTEXT_HTML_TAG = 'Tag',
CONTEXT_HTML_ATTRIBUTE = 'Attr',
CONTEXT_HTML_ATTRIBUTE_JS = 'AttrJs',
CONTEXT_HTML_ATTRIBUTE_CSS = 'AttrCss',
CONTEXT_HTML_ATTRIBUTE_URL = 'AttrUrl',
CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL = 'AttrUnquotedUrl',
CONTEXT_HTML_COMMENT = 'Comment',
CONTEXT_HTML_BOGUS_COMMENT = 'Bogus',
CONTEXT_HTML_CSS = 'Css',
CONTEXT_HTML_JS = 'Js',

CONTEXT_XML_TEXT = self::CONTEXT_HTML_TEXT,
CONTEXT_XML_TAG = self::CONTEXT_HTML_TAG,
CONTEXT_XML_COMMENT = self::CONTEXT_HTML_COMMENT;
CONTEXT_XML_ATTRIBUTE = self::CONTEXT_HTML_ATTRIBUTE,
CONTEXT_XML_COMMENT = self::CONTEXT_HTML_COMMENT,
CONTEXT_XML_BOGUS_COMMENT = self::CONTEXT_HTML_BOGUS_COMMENT;


/**
Expand Down Expand Up @@ -203,9 +209,10 @@ public function getContentType()
/**
* @internal
*/
public function setContext($context, $sub = NULL)
public function setContext($context)
{
$this->context = [$context, $sub];
trigger_error(__METHOD__ . ' is deprecated.', E_USER_DEPRECATED);
$this->context = $context;
return $this;
}

Expand Down Expand Up @@ -301,7 +308,7 @@ public function expandTokens($s)

private function processText(Token $token)
{
if ($this->context[0] === self::CONTEXT_HTML_ATTRIBUTE && $this->lastAttrValue === '') {
if ($this->lastAttrValue === '' && Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE)) {
$this->lastAttrValue = $token->text;
}
$this->output .= $this->escape($token->text);
Expand All @@ -310,7 +317,7 @@ private function processText(Token $token)

private function processMacroTag(Token $token)
{
if (in_array($this->context[0], [self::CONTEXT_HTML_ATTRIBUTE, self::CONTEXT_HTML_TAG], TRUE)) {
if ($this->context === self::CONTEXT_HTML_TAG || Helpers::startsWith($this->context, self::CONTEXT_HTML_ATTRIBUTE)) {
$this->lastAttrValue = TRUE;
}

Expand Down Expand Up @@ -355,20 +362,20 @@ private function processHtmlTagBegin(Token $token)
}
$this->htmlNode->closing = TRUE;
$this->htmlNode->endLine = $this->getLine();
$this->setContext(NULL);
$this->context = self::CONTEXT_HTML_TEXT;

} elseif ($token->text === '<!--') {
$this->setContext(self::CONTEXT_HTML_COMMENT);
$this->context = self::CONTEXT_HTML_COMMENT;

} elseif ($token->text === '<?' || $token->text === '<!') {
$this->setContext(self::CONTEXT_HTML_BOGUS_COMMENT);
$this->context = self::CONTEXT_HTML_BOGUS_COMMENT;
$this->output .= $token->text === '<?' ? '<<?php ?>?' : '<!'; // bypass error in escape()
return;

} else {
$this->htmlNode = new HtmlNode($token->name, $this->htmlNode);
$this->htmlNode->startLine = $this->getLine();
$this->setContext(self::CONTEXT_HTML_TAG);
$this->context = self::CONTEXT_HTML_TAG;
}
$this->tagOffset = strlen($this->output);
$this->output .= $token->text;
Expand All @@ -377,9 +384,9 @@ private function processHtmlTagBegin(Token $token)

private function processHtmlTagEnd(Token $token)
{
if (in_array($this->context[0], [self::CONTEXT_HTML_COMMENT, self::CONTEXT_HTML_BOGUS_COMMENT], TRUE)) {
if (in_array($this->context, [self::CONTEXT_HTML_COMMENT, self::CONTEXT_HTML_BOGUS_COMMENT], TRUE)) {
$this->output .= $token->text;
$this->setContext(NULL);
$this->context = self::CONTEXT_HTML_TEXT;
return;
}

Expand Down Expand Up @@ -418,15 +425,15 @@ private function processHtmlTagEnd(Token $token)
}
}

$this->setContext(NULL);
$this->context = self::CONTEXT_HTML_TEXT;

if ($htmlNode->closing) {
$this->htmlNode = $this->htmlNode->parentNode;

} elseif ((($lower = strtolower($htmlNode->name)) === 'script' || $lower === 'style')
&& (!isset($htmlNode->attrs['type']) || preg_match('#(java|j|ecma|live)script|json|css#i', $htmlNode->attrs['type']))
) {
$this->setContext($lower === 'script' ? self::CONTEXT_HTML_JS : self::CONTEXT_HTML_CSS);
$this->context = $lower === 'script' ? self::CONTEXT_HTML_JS : self::CONTEXT_HTML_CSS;
}
}

Expand All @@ -448,37 +455,34 @@ private function processHtmlAttributeBegin(Token $token)
$this->lastAttrValue = & $this->htmlNode->attrs[$token->name];
$this->output .= $this->escape($token->text);

$context = NULL;
$lower = strtolower($token->name);
if (in_array($token->value, ['"', "'"], TRUE)) {
$this->lastAttrValue = '';
$contextMain = self::CONTEXT_HTML_ATTRIBUTE;
$this->context = self::CONTEXT_HTML_ATTRIBUTE;
if (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], TRUE)) {
if (Helpers::startsWith($lower, 'on')) {
$context = self::CONTEXT_HTML_JS;
$this->context = self::CONTEXT_HTML_ATTRIBUTE_JS;
} elseif ($lower === 'style') {
$context = self::CONTEXT_HTML_CSS;
$this->context = self::CONTEXT_HTML_ATTRIBUTE_CSS;
}
}
} else {
$this->lastAttrValue = $token->value;
$contextMain = self::CONTEXT_HTML_TAG;
$this->context = self::CONTEXT_HTML_TAG;
}

if (in_array($this->contentType, [self::CONTENT_HTML, self::CONTENT_XHTML], TRUE)
&& (in_array($lower, ['href', 'src', 'action', 'formaction'], TRUE)
|| ($lower === 'data' && strtolower($this->htmlNode->name) === 'object'))
) {
$context = self::CONTEXT_HTML_ATTRIBUTE_URL;
$this->context = $this->context === self::CONTEXT_HTML_TAG ? self::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL : self::CONTEXT_HTML_ATTRIBUTE_URL;
}

$this->setContext($contextMain, $context);
}


private function processHtmlAttributeEnd(Token $token)
{
$this->setContext(self::CONTEXT_HTML_TAG);
$this->context = self::CONTEXT_HTML_TAG;
$this->output .= $token->text;
}

Expand Down Expand Up @@ -728,7 +732,7 @@ public function writeAttrsMacro($html)
*/
public function expandMacro($name, $args, $modifiers = NULL, $nPrefix = NULL)
{
$inScript = in_array($this->context[0], [self::CONTEXT_HTML_JS, self::CONTEXT_HTML_CSS], TRUE);
$inScript = in_array($this->context, [self::CONTEXT_HTML_JS, self::CONTEXT_HTML_CSS], TRUE);

if (empty($this->macros[$name])) {
$hint = ($t = Helpers::getSuggestion(array_keys($this->macros), $name)) ? ", did you mean {{$t}}?" : '';
Expand All @@ -742,7 +746,7 @@ public function expandMacro($name, $args, $modifiers = NULL, $nPrefix = NULL)
}

if (strpbrk($name, '=~%^&_')) {
if ($this->context[1] === self::CONTEXT_HTML_ATTRIBUTE_URL) {
if (in_array($this->context, [self::CONTEXT_HTML_ATTRIBUTE_URL, self::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL], TRUE)) {
if (!Helpers::removeFilter($modifiers, 'nosafeurl|nocheck') && !preg_match('#\|datastream(?=\s|\||\z)#i', $modifiers)) {
$modifiers .= '|checkurl';
}
Expand All @@ -757,13 +761,13 @@ public function expandMacro($name, $args, $modifiers = NULL, $nPrefix = NULL)
}

if ($nPrefix === MacroNode::PREFIX_INNER && !strcasecmp($this->htmlNode->name, 'script')) {
$context = [$this->contentType, self::CONTEXT_HTML_JS, NULL];
$context = [$this->contentType, self::CONTEXT_HTML_JS];
} elseif ($nPrefix === MacroNode::PREFIX_INNER && !strcasecmp($this->htmlNode->name, 'style')) {
$context = [$this->contentType, self::CONTEXT_HTML_CSS, NULL];
$context = [$this->contentType, self::CONTEXT_HTML_CSS];
} elseif ($nPrefix) {
$context = [$this->contentType, NULL, NULL];
$context = [$this->contentType, self::CONTEXT_HTML_TEXT];
} else {
$context = [$this->contentType, $this->context[0], $this->context[1]];
$context = [$this->contentType, $this->context];
}

foreach (array_reverse($this->macros[$name]) as $macro) {
Expand Down
2 changes: 1 addition & 1 deletion src/Latte/Compiler/MacroNode.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class MacroNode
/** @var HtmlNode closest HTML node */
public $htmlNode;

/** @var array [contentType, context, subcontext] */
/** @var array [contentType, context] */
public $context;

/** @var string indicates n:attribute macro and type of prefix (PREFIX_INNER, PREFIX_TAG, PREFIX_NONE) */
Expand Down
39 changes: 22 additions & 17 deletions src/Latte/Compiler/PhpWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ public function modifierPass(MacroTokens $tokens, $var, $isContent = FALSE)
if ($tokens->isCurrent($tokens::T_SYMBOL)) {
if ($tokens->isCurrent('escape')) {
if ($isContent) {
$res->prepend('LR\Filters::convertTo($_fi, ' . var_export($this->context[0] . $this->context[1], TRUE) . ', ')
$res->prepend('LR\Filters::convertTo($_fi, ' . var_export(implode($this->context), TRUE) . ', ')
->append(')');
} else {
$res = $this->escapePass($res);
Expand Down Expand Up @@ -469,24 +469,23 @@ public function modifierPass(MacroTokens $tokens, $var, $isContent = FALSE)
public function escapePass(MacroTokens $tokens)
{
$tokens = clone $tokens;
list($contentType, $context, $subContext) = $this->context;
list($contentType, $context) = $this->context;
switch ($contentType) {
case Compiler::CONTENT_XHTML:
case Compiler::CONTENT_HTML:
switch ($context) {
case Compiler::CONTEXT_HTML_ATTRIBUTE:
case Compiler::CONTEXT_HTML_TEXT:
return $tokens->prepend('LR\Filters::escapeHtmlText(')->append(')');
case Compiler::CONTEXT_HTML_TAG:
if ($subContext === Compiler::CONTEXT_HTML_JS) {
$tokens->prepend('LR\Filters::escapeJs(')->append(')');
} elseif ($subContext === Compiler::CONTEXT_HTML_CSS) {
$tokens->prepend('LR\Filters::escapeCss(')->append(')');
}
if ($context === Compiler::CONTEXT_HTML_TAG) {
$tokens->prepend('LR\Filters::escapeHtmlAttrUnquoted(')->append(')');
} else {
$tokens->prepend('LR\Filters::escapeHtmlAttr(')->append(')');
}
return $tokens;
case Compiler::CONTEXT_HTML_ATTRIBUTE_UNQUOTED_URL:
return $tokens->prepend('LR\Filters::escapeHtmlAttrUnquoted(')->append(')');
case Compiler::CONTEXT_HTML_ATTRIBUTE:
case Compiler::CONTEXT_HTML_ATTRIBUTE_URL:
return $tokens->prepend('LR\Filters::escapeHtmlAttr(')->append(')');
case Compiler::CONTEXT_HTML_ATTRIBUTE_JS:
return $tokens->prepend('LR\Filters::escapeHtmlAttr(LR\Filters::escapeJs(')->append('))');
case Compiler::CONTEXT_HTML_ATTRIBUTE_CSS:
return $tokens->prepend('LR\Filters::escapeHtmlAttr(LR\Filters::escapeCss(')->append('))');
case Compiler::CONTEXT_HTML_COMMENT:
return $tokens->prepend('LR\Filters::escapeHtmlComment(')->append(')');
case Compiler::CONTEXT_HTML_BOGUS_COMMENT:
Expand All @@ -495,17 +494,21 @@ public function escapePass(MacroTokens $tokens)
case Compiler::CONTEXT_HTML_CSS:
return $tokens->prepend('LR\Filters::escape' . ucfirst($context) . '(')->append(')');
default:
return $tokens->prepend('LR\Filters::escapeHtmlText(')->append(')');
throw new CompileException("Unknown context $contentType, $context.");
}

case Compiler::CONTENT_XML:
switch ($context) {
case Compiler::CONTEXT_XML_TEXT:
case Compiler::CONTEXT_XML_ATTRIBUTE:
case Compiler::CONTEXT_XML_BOGUS_COMMENT:
return $tokens->prepend('LR\Filters::escapeXml(')->append(')');
case Compiler::CONTEXT_XML_COMMENT:
return $tokens->prepend('LR\Filters::escapeHtmlComment(')->append(')');
case Compiler::CONTEXT_XML_TAG:
return $tokens->prepend('LR\Filters::escapeXmlAttrUnquoted(')->append(')');
default:
return $tokens->prepend('LR\Filters::escapeXml(')->append(')');
throw new CompileException("Unknown context $contentType, $context.");
}

case Compiler::CONTENT_JS:
Expand All @@ -514,8 +517,10 @@ public function escapePass(MacroTokens $tokens)
return $tokens->prepend('LR\Filters::escape' . ucfirst($contentType) . '(')->append(')');
case Compiler::CONTENT_TEXT:
return $tokens;
default:
case NULL:
return $tokens->prepend('call_user_func($this->filters->escape, ')->append(')');
default:
throw new CompileException("Unknown context $contentType.");
}
}

Expand Down
12 changes: 6 additions & 6 deletions src/Latte/Macros/BlockMacros.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public function macroInclude(MacroNode $node, PhpWriter $writer)
. (isset($this->namedBlocks[$destination]) || $parent ? 'get_defined_vars()' : '$this->params')
. ($node->modifiers
? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }'
: ($noEscape || $parent ? '' : ', ' . var_export($node->context[0] . $node->context[1], TRUE)))
: ($noEscape || $parent ? '' : ', ' . var_export(implode($node->context), TRUE)))
. ');'
);
}
Expand All @@ -147,7 +147,7 @@ public function macroIncludeBlock(MacroNode $node, PhpWriter $writer)
}
return $writer->write(
'ob_start(function () {}); $this->createTemplate(%node.word, %node.array? + get_defined_vars(), "includeblock")->renderToContentType(%var); echo rtrim(ob_get_clean());',
$node->context[0] . $node->context[1]
implode($node->context)
);
}

Expand Down Expand Up @@ -245,7 +245,7 @@ public function macroBlock(MacroNode $node, PhpWriter $writer)
$node->data->func = $this->generateMethodName($name);
$fname = $writer->formatWord($name);
$node->closingCode = '<?php ' . ($node->name === 'define' ? '' : "\$this->renderBlock($fname, get_defined_vars());") . ' ?>';
$blockType = var_export($node->context[0] . $node->context[1], TRUE);
$blockType = var_export(implode($node->context), TRUE);
$this->checkExtraArgs($node);
return "\$this->checkBlockContentType($blockType, $fname);"
. "\$this->blockQueue[$fname][] = [\$this, '{$node->data->func}'];";
Expand All @@ -269,13 +269,13 @@ public function macroBlock(MacroNode $node, PhpWriter $writer)
if (Helpers::removeFilter($node->modifiers, 'escape')) {
trigger_error('Macro ' . $node->getNotation() . ' provides auto-escaping, remove |escape.');
}
if ($node->context[1] === 'attr') {
$node->context[1] = NULL;
if (Helpers::startsWith($node->context[1], Latte\Compiler::CONTEXT_HTML_ATTRIBUTE)) {
$node->context[1] = '';
$node->modifiers .= '|escape';
} elseif ($node->modifiers) {
$node->modifiers .= '|escape';
}
$this->blockTypes[$name] = $node->context[0] . $node->context[1];
$this->blockTypes[$name] = implode($node->context);

$include = '$this->renderBlock(%var, ' . (($node->name === 'snippet' || $node->name === 'snippetArea') ? '$this->params' : 'get_defined_vars()')
. ($node->modifiers ? ', function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }' : '') . ')';
Expand Down
2 changes: 1 addition & 1 deletion src/Latte/Macros/CoreMacros.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ public function macroInclude(MacroNode $node, PhpWriter $writer)
$this->createTemplate(%node.word, %node.array? + $this->params, "include")->renderToContentType(%raw);',
$node->modifiers
? $writer->write('function ($s, $type) { $_fi = new LR\FilterInfo($type); return %modifyContent($s); }')
: var_export($noEscape ? NULL : $node->context[0] . $node->context[1], TRUE)
: var_export($noEscape ? NULL : implode($node->context), TRUE)
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Latte/Macros/MacroSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public function nodeOpened(MacroNode $node)
} elseif (!$node->attrCode) {
$node->attrCode = "<?php $res ?>";
}
$node->context[1] = NULL;
$node->context[1] = Latte\Compiler::CONTEXT_HTML_TEXT;

} elseif ($begin) {
$res = $this->compile($node, $begin);
Expand Down
Loading

0 comments on commit 5fda762

Please sign in to comment.