When rendering the output as a web page or API response, you must escape the output. This is also a protective measure to avoid rendering malicious code, causing XSS attacks, and preventing users of the application from inadvertently executing malicious code.
We can use the previously mentioned htmlentities
function to transfer the output. The second parameter of the function must be used ENT_QUOTES
. This function escapes single and double quotes, and also specifies the appropriate character encoding in the third parameter (usually UTF-8), the following example demonstrates how to escape HTML output before rendering:
<?php
$output = '<p><script>alert("Welcome to Morioh!")</script></p>';
echo htmlentities($output, ENT_QUOTES, 'UTF-8');
If you output without escaping, a prompt box will pop up. The output after escaping becomes:
<p><script>alert("Welcome to Morioh!");</script></p>
Modern PHP supports many template engines. These template engines have been escaped in order to do a good job at the bottom. For example, the popular twig/twig and smarty/smarty will automatically escape the output. This default processing method is very good, which provides a strong security guarantee for PHP Web applications.
The template engine used by Laravel is Blade. Here we briefly discuss how to escape the output of the underlying Laravel.
Generally we do this when we return the view content in Laravel:
return view('test', ['data'=>$data]);
This is a very simple example, which means that we will resources/views
find the test.blade.php
view file in the directory , then pass the $data
variables into it, and return the final rendered result to the user as the content of the response. So what are the underlying source code processes that go through this process, and if the $data
variables contain script code (such as JavaScript scripts), how do you deal with them? Let’s take a closer look.
First we start with auxiliary functions view
, of course we can also use them here View:make
, but for simplicity, we generally use view
functions, which are defined in the Illuminate\Foundation\helpers.php
file:
function view($view = null, $data = [], $mergeData = [])
{
$factory = app(ViewFactory::class);
if (func_num_args() === 0) {
return $factory;
}
return $factory->make($view, $data, $mergeData);
}
The logic function is removed from the container the factory interface view ViewFactory
corresponding instance $factory
(the binding relationship in Illuminate\View\ViewServiceProvider
the register
process register, in addition there is also a registered template parser engine EngineResolver
, comprising PhpEngine
and loading BladeCompiler
of CompilerEngine
, and a view finder file FileViewFinder
, a Then, all services required for view resolution are registered here). If parameters are passed in, the method $factory
on is called make
:
public function make($view, $data = [], $mergeData = [])
{
if (isset($this->aliases[$view])) {
$view = $this->aliases[$view];
}
$view = $this->normalizeName($view);
$path = $this->finder->find($view);
$data = array_merge($mergeData, $this->parseData($data));
$this->callCreator($view = new View($this, $this->getEngineFromPath($path), $view, $path, $data));
return $view;
}
This method is in Illuminate\View\Factory
here doing things is to get a view of the full path to the file, merge incoming variables, $this->getEngineFromPath
you will obtain the corresponding view template engine by file extension, such as we use .blade.php
at the end of the file view to get that CompilerEngine
(ie Blade template engine ), Otherwise we will get it PhpEngine
, and then we instantiate the View (Illuminate\View\View
) object according to the corresponding parameters and return. Note that methods View
are overridden in the class __toString
:
public function __toString()
{
return $this->render();
}
So when we print the $view
instance, View
the render
method of the class is actually called , so we should study render
what the method does next:
public function render(callable $callback = null)
{
try {
$contents = $this->renderContents();
$response = isset($callback) ? call_user_func($callback, $this, $contents) : null;
// Once we have the contents of the view, we will flush the sections if we are
// done rendering all views so that there is nothing left hanging over when
// another view gets rendered in the future by the application developer.
$this->factory->flushSectionsIfDoneRendering();
return ! is_null($response) ? $response : $contents;
} catch (Exception $e) {
$this->factory->flushSections();
throw $e;
} catch (Throwable $e) {
$this->factory->flushSections();
throw $e;
}
}
The focus here is on $this->renderContents()
methods, and we continue to delve into methods in View
classes renderContents
:
protected function renderContents()
{
// We will keep track of the amount of views being rendered so we can flush
// the section after the complete rendering operation is done. This will
// clear out the sections for any separate views that may be rendered.
$this->factory->incrementRender();
$this->factory->callComposer($this);
$contents = $this->getContents();
// Once we've finished rendering the view, we'll decrement the render count
// so that each sections get flushed out next time a view is created and
// no old sections are staying around in the memory of an environment.
$this->factory->decrementRender();
return $contents;
}
We focus on $this->getContents()
here, and enter the getContents
method:
protected function getContents()
{
return $this->engine->get($this->path, $this->gatherData());
}
We already mentioned earlier that here $this->engine
corresponds to CompilerEngine (Illuminate\View\Engines\CompilerEngine
), so we enter CompilerEngine
the get
method:
public function get($path, array $data = [])
{
$this->lastCompiled[] = $path;
// If this given view has expired, which means it has simply been edited since
// it was last compiled, we will re-compile the views so we can evaluate a
// fresh copy of the view. We'll pass the compiler the path of the view.
if ($this->compiler->isExpired($path)) {
$this->compiler->compile($path);
}
$compiled = $this->compiler->getCompiledPath($path);
// Once we have the path to the compiled file, we will evaluate the paths with
// typical PHP just like any other templates. We also keep a stack of views
// which have been rendered for right exception messages to be generated.
$results = $this->evaluatePath($compiled, $data);
array_pop($this->lastCompiled);
return $results;
}
Similarly, we mentioned earlier that the compiler used by CompilerEngine
is BladeCompiler
, so $this->compiler
is also the Blade compiler. Let’s look at $this->compiler->compile($path);
this line (first run or compile well The view template has expired and will enter here), enter BladeCompiler
of compile method:
public function compile($path = null)
{
if ($path) {
$this->setPath($path);
}
if (! is_null($this->cachePath)) {
$contents = $this->compileString($this->files->get($this->getPath()));
$this->files->put($this->getCompiledPath($this->getPath()), $contents);
}
}
What we do here is to compile the contents of the view file first, and then store the compiled content in the view compilation path (storage\framework\views
) the corresponding file (one compile and multiple runs to improve performance), here we focus on the $this->compileString
method, which uses the token_get_all function to split the view file code into multiple fragments If the fragment is an array, the $this->parseToken
method is called in a loop:
protected function parseToken($token)
{
list($id, $content) = $token;
if ($id == T_INLINE_HTML) {
foreach ($this->compilers as $type) {
$content = $this->{"compile{$type}"}($content);
}
}
return $content;
}
Here, we are very close to the truth, and for the HTML code (including Blade instruction code), the cycle call compileExtensions
, compileStatements
, compileComments
and compileEchos
methods, we focus on the output method compileEchos
, Blade engine provides a default compileRawEchos
, compileEscapedEchos
and compileRegularEchos
three output methods, respectively corresponding instruction Yes {!! !!}
, {{{ }}}
and {{ }}
, as the name suggests, compileRawEchos
correspond to native output:
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) : 'compileEchoDefaults($matches[2]).'; ?>'.$whitespace;
};
return preg_replace_callback($pattern, $callback, $value);
}
That is {!! !!}
, variables wrapped in the Blade view will output HTML natively. If you want to display pictures and links, this method is recommended.
{{{}}}
Corresponding CompileEscapedEchos
, in this Laravel 4.2 and previous versions used to escape, now replaced with {{}}
that call compileRegularEchos
method:
protected function compileRegularEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]);
$callback = function ($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
$wrapped = sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2]));
return $matches[1] ? substr($matches[0], 1) : ''.$whitespace;
};
return preg_replace_callback($pattern, $callback, $value);
}
Which $this->echoFormat
corresponds to e(%s)
, coincidentally, compileEscapedEchos
is also used in this method:
protected function compileEscapedEchos($value)
{
$pattern = sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]);
$callback = function ($matches) {
$whitespace = empty($matches[3]) ? '' : $matches[3].$matches[3];
return $matches[1] ? $matches[0] : 'compileEchoDefaults($matches[2]).'); ?>'.$whitespace;
};
return preg_replace_callback($pattern, $callback, $value);
}
Helper functions are e()
defined in Illuminate\Support\helpers.php
:
function e($value)
{
if ($value instanceof Htmlable) {
return $value->toHtml();
}
return htmlentities($value, ENT_QUOTES, 'UTF-8', false);
}
Its role is to escape the input value.
After such escaping, the view may {{ $data }}
be compiled into <?php echo $data?>
, and finally how to $dataoutput the incoming view, we return to CompilerEngine
the get
method, see this paragraph:
$results = $this->evaluatePath($compiled, $data);
evaluatePath
The compiled view file path and the passed variables are passed in $data
. The method is defined as follows:
protected function evaluatePath($__path, $__data)
{
$obLevel = ob_get_level();ob_start();
extract($__data, EXTR_SKIP);
// We'll evaluate the contents of the view inside a try/catch block so we can
// flush out any stray output that might get out before an error occurs or
// an exception is thrown. This prevents any partial views from leaking.
try {
include $__path;
} catch (Exception $e) {
$this->handleViewException($e, $obLevel);
} catch (Throwable $e) {
$this->handleViewException(new FatalThrowableError($e), $obLevel);
}
return ltrim(ob_get_clean());
}
This calls the PHP system function extract to import the incoming variables from the array into the current symbol table (through include $__path
import), and its role is to replace all the variables in the compiled view file with the incoming variable values (via key name mapping). .
Well, this is the basic process of the Blade view template from rendering to output. You can see that we {{}}
escape the output by using it to avoid the XSS attack.
#php #laravel #excapes