Skip to content
Permalink
Browse files

Tweak how Blade echo escaping works for more safety.

Make both {{ }} and {{{ }}} escape values by default, new {!! !!}
construct to echo raw data.
  • Loading branch information...
taylorotwell committed Sep 8, 2014
1 parent 34f63cb commit 833d90073da404df4b5b94c182f931b5c239fa10
@@ -8,7 +8,8 @@
{"message": "Added new FormRequest class for simpler validation and authorization.", "backport": null},
{"message": "Contracts for most major components of the framework.", "backport": null},
{"message": "Flysystem integration now included with framework.", "backport": null},
{"message": "Passing an Eloquent model to a URL generator method will now automatically pull its ID.", "backport": null}
{"message": "Passing an Eloquent model to a URL generator method will now automatically pull its ID.", "backport": null},
{"message": "Make both {{ }} and {{{ }}} escape values by default, new {!! !!} construct to echo raw data.", "backport": null}
],
"4.2.*": [
{"message": "View and Pagination 'Environment' classes renamed to 'Factory'.", "backport": null},
@@ -30,6 +30,13 @@ class BladeCompiler extends Compiler implements CompilerInterface {
'Echos'
);
/**
* Array of opening and closing tags for escaped echos.
*
* @var array
*/
protected $rawTags = array('{!!', '!!}');
/**
* Array of opening and closing tags for escaped echos.
*
@@ -190,6 +197,8 @@ protected function compileComments($value)
*/
protected function compileEchos($value)
{
$value = $this->compileRawEchos($value);
$difference = strlen($this->contentTags[0]) - strlen($this->escapedTags[0]);
if ($difference > 0)
@@ -221,6 +230,26 @@ protected function compileStatements($value)
return preg_replace_callback('/\B@(\w+)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value);
}
/**
* Compile the "raw" echo statements.
*
* @param string $value
* @return string
*/
protected function compileRawEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]);
$callback = function($matches)
{
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
return $matches[1] ? substr($matches[0], 1) : '<?php echo '.$this->compileEchoDefaults($matches[2]).'; ?>'.$whitespace;
};
return preg_replace_callback($pattern, $callback, $value);
}
/**
* Compile the "regular" echo statements.
*
@@ -235,7 +264,7 @@ protected function compileRegularEchos($value)
{
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
return $matches[1] ? substr($matches[0], 1) : '<?php echo '.$this->compileEchoDefaults($matches[2]).'; ?>'.$whitespace;
return $matches[1] ? substr($matches[0], 1) : '<?php echo e('.$this->compileEchoDefaults($matches[2]).'); ?>'.$whitespace;
};
return preg_replace_callback($pattern, $callback, $value);
@@ -83,52 +83,57 @@ public function testCompileDoesntStoreFilesWhenCachePathIsNull()
public function testEchosAreCompiled()
{
$compiler = new BladeCompiler($this->getFiles(), __DIR__);
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{!!$name!!}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{!! $name !!}'));
$this->assertEquals('<?php echo isset($name) ? $name : \'foo\'; ?>', $compiler->compileString('{!! $name or \'foo\' !!}'));
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{{$name}}}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{{$name}}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{{ $name }}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{{
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{$name}}'));
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{ $name }}'));
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{
$name
}}'));
$this->assertEquals("<?php echo \$name; ?>\n\n", $compiler->compileString("{{ \$name }}\n"));
$this->assertEquals("<?php echo \$name; ?>\r\n\r\n", $compiler->compileString("{{ \$name }}\r\n"));
$this->assertEquals("<?php echo \$name; ?>\n\n", $compiler->compileString("{{ \$name }}\n"));
$this->assertEquals("<?php echo \$name; ?>\r\n\r\n", $compiler->compileString("{{ \$name }}\r\n"));
$this->assertEquals('<?php echo isset($name) ? $name : "foo"; ?>', $compiler->compileString('{{ $name or "foo" }}'));
$this->assertEquals('<?php echo isset($user->name) ? $user->name : "foo"; ?>', $compiler->compileString('{{ $user->name or "foo" }}'));
$this->assertEquals('<?php echo isset($name) ? $name : "foo"; ?>', $compiler->compileString('{{$name or "foo"}}'));
$this->assertEquals('<?php echo isset($name) ? $name : "foo"; ?>', $compiler->compileString('{{
$this->assertEquals("<?php echo e(\$name); ?>\n\n", $compiler->compileString("{{ \$name }}\n"));
$this->assertEquals("<?php echo e(\$name); ?>\r\n\r\n", $compiler->compileString("{{ \$name }}\r\n"));
$this->assertEquals("<?php echo e(\$name); ?>\n\n", $compiler->compileString("{{ \$name }}\n"));
$this->assertEquals("<?php echo e(\$name); ?>\r\n\r\n", $compiler->compileString("{{ \$name }}\r\n"));
$this->assertEquals('<?php echo e(isset($name) ? $name : "foo"); ?>', $compiler->compileString('{{ $name or "foo" }}'));
$this->assertEquals('<?php echo e(isset($user->name) ? $user->name : "foo"); ?>', $compiler->compileString('{{ $user->name or "foo" }}'));
$this->assertEquals('<?php echo e(isset($name) ? $name : "foo"); ?>', $compiler->compileString('{{$name or "foo"}}'));
$this->assertEquals('<?php echo e(isset($name) ? $name : "foo"); ?>', $compiler->compileString('{{
$name or "foo"
}}'));
$this->assertEquals('<?php echo isset($name) ? $name : \'foo\'; ?>', $compiler->compileString('{{ $name or \'foo\' }}'));
$this->assertEquals('<?php echo isset($name) ? $name : \'foo\'; ?>', $compiler->compileString('{{$name or \'foo\'}}'));
$this->assertEquals('<?php echo isset($name) ? $name : \'foo\'; ?>', $compiler->compileString('{{
$this->assertEquals('<?php echo e(isset($name) ? $name : \'foo\'); ?>', $compiler->compileString('{{ $name or \'foo\' }}'));
$this->assertEquals('<?php echo e(isset($name) ? $name : \'foo\'); ?>', $compiler->compileString('{{$name or \'foo\'}}'));
$this->assertEquals('<?php echo e(isset($name) ? $name : \'foo\'); ?>', $compiler->compileString('{{
$name or \'foo\'
}}'));
$this->assertEquals('<?php echo isset($age) ? $age : 90; ?>', $compiler->compileString('{{ $age or 90 }}'));
$this->assertEquals('<?php echo isset($age) ? $age : 90; ?>', $compiler->compileString('{{$age or 90}}'));
$this->assertEquals('<?php echo isset($age) ? $age : 90; ?>', $compiler->compileString('{{
$this->assertEquals('<?php echo e(isset($age) ? $age : 90); ?>', $compiler->compileString('{{ $age or 90 }}'));
$this->assertEquals('<?php echo e(isset($age) ? $age : 90); ?>', $compiler->compileString('{{$age or 90}}'));
$this->assertEquals('<?php echo e(isset($age) ? $age : 90); ?>', $compiler->compileString('{{
$age or 90
}}'));
$this->assertEquals('<?php echo "Hello world or foo"; ?>', $compiler->compileString('{{ "Hello world or foo" }}'));
$this->assertEquals('<?php echo "Hello world or foo"; ?>', $compiler->compileString('{{"Hello world or foo"}}'));
$this->assertEquals('<?php echo $foo + $or + $baz; ?>', $compiler->compileString('{{$foo + $or + $baz}}'));
$this->assertEquals('<?php echo "Hello world or foo"; ?>', $compiler->compileString('{{
$this->assertEquals('<?php echo e("Hello world or foo"); ?>', $compiler->compileString('{{ "Hello world or foo" }}'));
$this->assertEquals('<?php echo e("Hello world or foo"); ?>', $compiler->compileString('{{"Hello world or foo"}}'));
$this->assertEquals('<?php echo e($foo + $or + $baz); ?>', $compiler->compileString('{{$foo + $or + $baz}}'));
$this->assertEquals('<?php echo e("Hello world or foo"); ?>', $compiler->compileString('{{
"Hello world or foo"
}}'));
$this->assertEquals('<?php echo \'Hello world or foo\'; ?>', $compiler->compileString('{{ \'Hello world or foo\' }}'));
$this->assertEquals('<?php echo \'Hello world or foo\'; ?>', $compiler->compileString('{{\'Hello world or foo\'}}'));
$this->assertEquals('<?php echo \'Hello world or foo\'; ?>', $compiler->compileString('{{
$this->assertEquals('<?php echo e(\'Hello world or foo\'); ?>', $compiler->compileString('{{ \'Hello world or foo\' }}'));
$this->assertEquals('<?php echo e(\'Hello world or foo\'); ?>', $compiler->compileString('{{\'Hello world or foo\'}}'));
$this->assertEquals('<?php echo e(\'Hello world or foo\'); ?>', $compiler->compileString('{{
\'Hello world or foo\'
}}'));
$this->assertEquals('<?php echo myfunc(\'foo or bar\'); ?>', $compiler->compileString('{{ myfunc(\'foo or bar\') }}'));
$this->assertEquals('<?php echo myfunc("foo or bar"); ?>', $compiler->compileString('{{ myfunc("foo or bar") }}'));
$this->assertEquals('<?php echo myfunc("$name or \'foo\'"); ?>', $compiler->compileString('{{ myfunc("$name or \'foo\'") }}'));
$this->assertEquals('<?php echo e(myfunc(\'foo or bar\')); ?>', $compiler->compileString('{{ myfunc(\'foo or bar\') }}'));
$this->assertEquals('<?php echo e(myfunc("foo or bar")); ?>', $compiler->compileString('{{ myfunc("foo or bar") }}'));
$this->assertEquals('<?php echo e(myfunc("$name or \'foo\'")); ?>', $compiler->compileString('{{ myfunc("$name or \'foo\'") }}'));
}
@@ -156,9 +161,9 @@ public function testReversedEchosAreCompiled()
$compiler->setEscapedContentTags('{{', '}}');
$compiler->setContentTags('{{{', '}}}');
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{$name}}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{{{$name}}}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{{{ $name }}}'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('{{{
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{{$name}}}'));
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{{ $name }}}'));
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('{{{
$name
}}}'));
}
@@ -410,7 +415,7 @@ public function testCustomPhpCodeIsCorrectlyHandled()
public function testMixingYieldAndEcho()
{
$compiler = new BladeCompiler($this->getFiles(), __DIR__);
$this->assertEquals('<?php echo $__env->yieldContent(\'title\'); ?> - <?php echo Config::get(\'site.title\'); ?>', $compiler->compileString("@yield('title') - {{Config::get('site.title')}}"));
$this->assertEquals('<?php echo $__env->yieldContent(\'title\'); ?> - <?php echo e(Config::get(\'site.title\')); ?>', $compiler->compileString("@yield('title') - {{Config::get('site.title')}}"));
}
@@ -429,8 +434,8 @@ public function testConfiguringContentTags()
$compiler->setEscapedContentTags('[[[', ']]]');
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('[[[ $name ]]]'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('[[ $name ]]'));
$this->assertEquals('<?php echo $name; ?>', $compiler->compileString('[[
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('[[ $name ]]'));
$this->assertEquals('<?php echo e($name); ?>', $compiler->compileString('[[
$name
]]'));
}
@@ -446,9 +451,9 @@ public function testExpressionsOnTheSameLine()
public function testExpressionWithinHTML()
{
$compiler = new BladeCompiler($this->getFiles(), __DIR__);
$this->assertEquals('<html <?php echo $foo; ?>>', $compiler->compileString('<html {{ $foo }}>'));
$this->assertEquals('<html<?php echo $foo; ?>>', $compiler->compileString('<html{{ $foo }}>'));
$this->assertEquals('<html <?php echo $foo; ?> <?php echo \Illuminate\Support\Facades\Lang::get(\'foo\'); ?>>', $compiler->compileString('<html {{ $foo }} @lang(\'foo\')>'));
$this->assertEquals('<html <?php echo e($foo); ?>>', $compiler->compileString('<html {{ $foo }}>'));
$this->assertEquals('<html<?php echo e($foo); ?>>', $compiler->compileString('<html{{ $foo }}>'));
$this->assertEquals('<html <?php echo e($foo); ?> <?php echo \Illuminate\Support\Facades\Lang::get(\'foo\'); ?>>', $compiler->compileString('<html {{ $foo }} @lang(\'foo\')>'));
}

10 comments on commit 833d900

@xreal

This comment has been minimized.

Copy link
Contributor

replied Sep 9, 2014

Hey Taylor, we have a large Laravel Project and this change makes it mostly impossible to migrate to 4.3 because we have many Views where "{{" is used to output raw data. can you make this change optional? This would be a huge help for people who want to migrate old Projects.

Btw. the idea behind this change is good, from a security perspective!

@barryvdh

This comment has been minimized.

Copy link
Contributor

replied Sep 9, 2014

Can't you just search and replace? Perhaps with a step in between. {{{ -> |||, {{ -> {!!, ||| -> {{ or something.

@taylorotwell

This comment has been minimized.

Copy link
Member Author

replied Sep 9, 2014

Blade::setRawTags('open tag', 'close tag');

@Anahkiasen

This comment has been minimized.

Copy link
Contributor

replied Sep 9, 2014

What's the difference between {{ and {{{ now ?

@xreal

This comment has been minimized.

Copy link
Contributor

replied Sep 9, 2014

@taylorotwell Perfect! You just made my day!

@Anahkiasen The difference is that in the upcoming 4.3 there is no more difference between these two. See commit description.

@xreal

This comment has been minimized.

Copy link
Contributor

replied Sep 10, 2014

Ok i have tested the Raw tags settetr. b7611b1
But i cant set it to the old '{{' because then every '{{{' throws an Exception. Like this:

{{{ $value }}} 

gets

<?php echo { $value; ?>}

Im not sure, but i think wenn you replace

$value = $this->compileRawEchos($value);

with

$value = $this->compileRawEchos($this->compileEscapedEchos($value));

everything would work fine.

@sonamtobgay

This comment has been minimized.

Copy link

replied Jan 6, 2015

On laravel 4, {{ }} these curly braces do not work...they are displayed raw on the browser...Why?? HELP ME!!!!!!

@barryvdh

This comment has been minimized.

Copy link
Contributor

replied Jan 6, 2015

Accessing your views directly in the browser perhaps? Or wrong extension (should be yourfilename.blade.php? Also: ask in the forums.

@GrahamCampbell

This comment has been minimized.

Copy link
Member

replied Jan 6, 2015

@sonamtobgay Please ask on the forums, and as @barryvdh said, ensure you're using the correct file extension. And actually, make sure you are actually using the view component to render the file and are not manually requiring it - that would be a bad idea.

@duncan3dc

This comment has been minimized.

Copy link
Contributor

replied Jan 12, 2015

@xreal I noticed the same issue and have submitted a pr (#6978) for it

Please sign in to comment.
You can’t perform that action at this time.