mirror of
https://github.com/erusev/parsedown.git
synced 2023-08-10 21:13:06 +03:00
Limit recursion depth by configurable
Fixes https://github.com/erusev/parsedown/issues/681
This commit is contained in:
parent
b9b75dbcea
commit
9eb6a02334
51
src/Configurables/RecursionLimiter.php
Normal file
51
src/Configurables/RecursionLimiter.php
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Erusev\Parsedown\Configurables;
|
||||||
|
|
||||||
|
use Erusev\Parsedown\Configurable;
|
||||||
|
|
||||||
|
final class RecursionLimiter implements Configurable
|
||||||
|
{
|
||||||
|
/** @var int */
|
||||||
|
private $maxDepth;
|
||||||
|
|
||||||
|
/** @var int */
|
||||||
|
private $currentDepth;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $maxDepth
|
||||||
|
* @param int $currentDepth
|
||||||
|
*/
|
||||||
|
private function __construct($maxDepth, $currentDepth)
|
||||||
|
{
|
||||||
|
$this->maxDepth = $maxDepth;
|
||||||
|
$this->currentDepth = $currentDepth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return self */
|
||||||
|
public static function initial()
|
||||||
|
{
|
||||||
|
return self::maxDepth(256);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $maxDepth
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function maxDepth($maxDepth)
|
||||||
|
{
|
||||||
|
return new self($maxDepth, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return self */
|
||||||
|
public function increment()
|
||||||
|
{
|
||||||
|
return new self($this->maxDepth, $this->currentDepth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return bool */
|
||||||
|
public function isDepthExceeded()
|
||||||
|
{
|
||||||
|
return ($this->maxDepth < $this->currentDepth);
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ use Erusev\Parsedown\Components\Inlines\PlainText;
|
|||||||
use Erusev\Parsedown\Components\StateUpdatingBlock;
|
use Erusev\Parsedown\Components\StateUpdatingBlock;
|
||||||
use Erusev\Parsedown\Configurables\BlockTypes;
|
use Erusev\Parsedown\Configurables\BlockTypes;
|
||||||
use Erusev\Parsedown\Configurables\InlineTypes;
|
use Erusev\Parsedown\Configurables\InlineTypes;
|
||||||
|
use Erusev\Parsedown\Configurables\RecursionLimiter;
|
||||||
use Erusev\Parsedown\Html\Renderable;
|
use Erusev\Parsedown\Html\Renderable;
|
||||||
use Erusev\Parsedown\Html\Renderables\Text;
|
use Erusev\Parsedown\Html\Renderables\Text;
|
||||||
use Erusev\Parsedown\Parsing\Excerpt;
|
use Erusev\Parsedown\Parsing\Excerpt;
|
||||||
@ -82,6 +83,14 @@ final class Parsedown
|
|||||||
*/
|
*/
|
||||||
public static function blocks(Lines $Lines, State $State)
|
public static function blocks(Lines $Lines, State $State)
|
||||||
{
|
{
|
||||||
|
$RecursionLimiter = $State->get(RecursionLimiter::class)->increment();
|
||||||
|
|
||||||
|
if ($RecursionLimiter->isDepthExceeded()) {
|
||||||
|
$State = $State->setting(new BlockTypes([], []));
|
||||||
|
}
|
||||||
|
|
||||||
|
$State = $State->setting($RecursionLimiter);
|
||||||
|
|
||||||
/** @var Block[] */
|
/** @var Block[] */
|
||||||
$Blocks = [];
|
$Blocks = [];
|
||||||
/** @var Block|null */
|
/** @var Block|null */
|
||||||
@ -180,6 +189,14 @@ final class Parsedown
|
|||||||
# standardize line breaks
|
# standardize line breaks
|
||||||
$text = \str_replace(["\r\n", "\r"], "\n", $text);
|
$text = \str_replace(["\r\n", "\r"], "\n", $text);
|
||||||
|
|
||||||
|
$RecursionLimiter = $State->get(RecursionLimiter::class)->increment();
|
||||||
|
|
||||||
|
if ($RecursionLimiter->isDepthExceeded()) {
|
||||||
|
return [Plaintext::build(new Excerpt($text, 0), $State)];
|
||||||
|
}
|
||||||
|
|
||||||
|
$State = $State->setting($RecursionLimiter);
|
||||||
|
|
||||||
/** @var Inline[] */
|
/** @var Inline[] */
|
||||||
$Inlines = [];
|
$Inlines = [];
|
||||||
|
|
||||||
|
54
tests/src/Configurables/RecursionLimiterTest.php
Normal file
54
tests/src/Configurables/RecursionLimiterTest.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Erusev\Parsedown\Tests\Configurables;
|
||||||
|
|
||||||
|
use Erusev\Parsedown\Configurables\RecursionLimiter;
|
||||||
|
use Erusev\Parsedown\Parsedown;
|
||||||
|
use Erusev\Parsedown\State;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class RecursionLimiterTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return void
|
||||||
|
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||||
|
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testDepthLimit()
|
||||||
|
{
|
||||||
|
$State = new State([RecursionLimiter::maxDepth(3)]);
|
||||||
|
|
||||||
|
$Parsedown = new Parsedown($State);
|
||||||
|
|
||||||
|
$borderline = '>>> foo';
|
||||||
|
$exceeded = '>>>> foo';
|
||||||
|
$exceededByInline = '>>> fo*o*';
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
(
|
||||||
|
\str_repeat("<blockquote>\n", 3)
|
||||||
|
. '<p>foo</p>'
|
||||||
|
. \str_repeat("\n</blockquote>", 3)
|
||||||
|
),
|
||||||
|
$Parsedown->text($borderline)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
(
|
||||||
|
\str_repeat("<blockquote>\n", 3)
|
||||||
|
. '<p>> foo</p>'
|
||||||
|
. \str_repeat("\n</blockquote>", 3)
|
||||||
|
),
|
||||||
|
$Parsedown->text($exceeded)
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertSame(
|
||||||
|
(
|
||||||
|
\str_repeat("<blockquote>\n", 3)
|
||||||
|
. '<p>fo*o*</p>'
|
||||||
|
. \str_repeat("\n</blockquote>", 3)
|
||||||
|
),
|
||||||
|
$Parsedown->text($exceededByInline)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user