summaryrefslogtreecommitdiff
path: root/vendor/erusev/parsedown/Parsedown.php
diff options
context:
space:
mode:
authorFrédéric Guillot <fred@kanboard.net>2018-04-20 16:05:50 -0700
committerFrédéric Guillot <fred@kanboard.net>2018-04-20 16:05:50 -0700
commitbb406d57b1c1ad2736774be18b2f7b4d31abcf63 (patch)
treea4a89775f5519c178679f99115dd3b84efd051cb /vendor/erusev/parsedown/Parsedown.php
parent32a95ce5ea08ec5b263224865b0695c7459928b0 (diff)
Update Parsedown library
Diffstat (limited to 'vendor/erusev/parsedown/Parsedown.php')
-rw-r--r--[-rwxr-xr-x]vendor/erusev/parsedown/Parsedown.php219
1 files changed, 185 insertions, 34 deletions
diff --git a/vendor/erusev/parsedown/Parsedown.php b/vendor/erusev/parsedown/Parsedown.php
index c8c92a39..87d612ab 100755..100644
--- a/vendor/erusev/parsedown/Parsedown.php
+++ b/vendor/erusev/parsedown/Parsedown.php
@@ -17,7 +17,7 @@ class Parsedown
{
# ~
- const version = '1.6.0';
+ const version = '1.7.1';
# ~
@@ -75,6 +75,32 @@ class Parsedown
protected $urlsLinked = true;
+ function setSafeMode($safeMode)
+ {
+ $this->safeMode = (bool) $safeMode;
+
+ return $this;
+ }
+
+ protected $safeMode;
+
+ protected $safeLinksWhitelist = array(
+ 'http://',
+ 'https://',
+ 'ftp://',
+ 'ftps://',
+ 'mailto:',
+ 'data:image/png;base64,',
+ 'data:image/gif;base64,',
+ 'data:image/jpeg;base64,',
+ 'irc:',
+ 'ircs:',
+ 'git:',
+ 'ssh:',
+ 'news:',
+ 'steam:',
+ );
+
#
# Lines
#
@@ -115,7 +141,7 @@ class Parsedown
# Blocks
#
- private function lines(array $lines)
+ protected function lines(array $lines)
{
$CurrentBlock = null;
@@ -175,7 +201,7 @@ class Parsedown
}
else
{
- if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
+ if ($this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
@@ -216,7 +242,7 @@ class Parsedown
$Block['identified'] = true;
}
- if (method_exists($this, 'block'.$blockType.'Continue'))
+ if ($this->isBlockContinuable($blockType))
{
$Block['continuable'] = true;
}
@@ -245,7 +271,7 @@ class Parsedown
# ~
- if (isset($CurrentBlock['continuable']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
{
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
}
@@ -278,6 +304,16 @@ class Parsedown
return $markup;
}
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Complete');
+ }
+
#
# Code
@@ -332,8 +368,6 @@ class Parsedown
{
$text = $Block['element']['text']['text'];
- $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
-
$Block['element']['text']['text'] = $text;
return $Block;
@@ -344,7 +378,7 @@ class Parsedown
protected function blockComment($Line)
{
- if ($this->markupEscaped)
+ if ($this->markupEscaped or $this->safeMode)
{
return;
}
@@ -386,7 +420,7 @@ class Parsedown
protected function blockFencedCode($Line)
{
- if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([^`]+)?[ ]*$/', $Line['text'], $matches))
{
$Element = array(
'name' => 'code',
@@ -438,7 +472,7 @@ class Parsedown
return $Block;
}
- $Block['element']['text']['text'] .= "\n".$Line['body'];;
+ $Block['element']['text']['text'] .= "\n".$Line['body'];
return $Block;
}
@@ -447,8 +481,6 @@ class Parsedown
{
$text = $Block['element']['text']['text'];
- $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
-
$Block['element']['text']['text'] = $text;
return $Block;
@@ -505,6 +537,16 @@ class Parsedown
),
);
+ if($name === 'ol')
+ {
+ $listStart = stristr($matches[0], '.', true);
+
+ if($listStart !== '1')
+ {
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
$Block['li'] = array(
'name' => 'li',
'handler' => 'li',
@@ -527,6 +569,8 @@ class Parsedown
{
$Block['li']['text'] []= '';
+ $Block['loose'] = true;
+
unset($Block['interrupted']);
}
@@ -575,6 +619,22 @@ class Parsedown
}
}
+ protected function blockListComplete(array $Block)
+ {
+ if (isset($Block['loose']))
+ {
+ foreach ($Block['element']['text'] as &$li)
+ {
+ if (end($li['text']) !== '')
+ {
+ $li['text'] []= '';
+ }
+ }
+ }
+
+ return $Block;
+ }
+
#
# Quote
@@ -658,12 +718,12 @@ class Parsedown
protected function blockMarkup($Line)
{
- if ($this->markupEscaped)
+ if ($this->markupEscaped or $this->safeMode)
{
return;
}
- if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+ if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
{
$element = strtolower($matches[1]);
@@ -977,7 +1037,7 @@ class Parsedown
# ~
#
- public function line($text)
+ public function line($text, $nonNestables=array())
{
$markup = '';
@@ -993,6 +1053,13 @@ class Parsedown
foreach ($this->InlineTypes[$marker] as $inlineType)
{
+ # check to see if the current inline type is nestable in the current context
+
+ if ( ! empty($nonNestables) and in_array($inlineType, $nonNestables))
+ {
+ continue;
+ }
+
$Inline = $this->{'inline'.$inlineType}($Excerpt);
if ( ! isset($Inline))
@@ -1014,6 +1081,13 @@ class Parsedown
$Inline['position'] = $markerPosition;
}
+ # cause the new element to 'inherit' our non nestables
+
+ foreach ($nonNestables as $non_nestable)
+ {
+ $Inline['element']['nonNestables'][] = $non_nestable;
+ }
+
# the text that comes before the inline
$unmarkedText = substr($text, 0, $Inline['position']);
@@ -1054,7 +1128,6 @@ class Parsedown
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
{
$text = $matches[2];
- $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
$text = preg_replace("/[ ]*\n/", ' ', $text);
return array(
@@ -1173,6 +1246,7 @@ class Parsedown
$Element = array(
'name' => 'a',
'handler' => 'line',
+ 'nonNestables' => array('Url', 'Link'),
'text' => null,
'attributes' => array(
'href' => null,
@@ -1184,7 +1258,7 @@ class Parsedown
$remainder = $Excerpt['text'];
- if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{
$Element['text'] = $matches[1];
@@ -1197,7 +1271,7 @@ class Parsedown
return;
}
- if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{
$Element['attributes']['href'] = $matches[1];
@@ -1233,8 +1307,6 @@ class Parsedown
$Element['attributes']['title'] = $Definition['title'];
}
- $Element['attributes']['href'] = str_replace(array('&', '<'), array('&amp;', '&lt;'), $Element['attributes']['href']);
-
return array(
'extent' => $extent,
'element' => $Element,
@@ -1243,12 +1315,12 @@ class Parsedown
protected function inlineMarkup($Excerpt)
{
- if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
+ if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
{
return;
}
- if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*[ ]*>/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
@@ -1264,7 +1336,7 @@ class Parsedown
);
}
- if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
{
return array(
'markup' => $matches[0],
@@ -1323,14 +1395,16 @@ class Parsedown
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
{
+ $url = $matches[0][0];
+
$Inline = array(
'extent' => strlen($matches[0][0]),
'position' => $matches[0][1],
'element' => array(
'name' => 'a',
- 'text' => $matches[0][0],
+ 'text' => $url,
'attributes' => array(
- 'href' => $matches[0][0],
+ 'href' => $url,
),
),
);
@@ -1343,7 +1417,7 @@ class Parsedown
{
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
{
- $url = str_replace(array('&', '<'), array('&amp;', '&lt;'), $matches[1]);
+ $url = $matches[1];
return array(
'extent' => strlen($matches[0]),
@@ -1381,6 +1455,11 @@ class Parsedown
protected function element(array $Element)
{
+ if ($this->safeMode)
+ {
+ $Element = $this->sanitiseElement($Element);
+ }
+
$markup = '<'.$Element['name'];
if (isset($Element['attributes']))
@@ -1392,7 +1471,7 @@ class Parsedown
continue;
}
- $markup .= ' '.$name.'="'.$value.'"';
+ $markup .= ' '.$name.'="'.self::escape($value).'"';
}
}
@@ -1400,13 +1479,18 @@ class Parsedown
{
$markup .= '>';
+ if (!isset($Element['nonNestables']))
+ {
+ $Element['nonNestables'] = array();
+ }
+
if (isset($Element['handler']))
{
- $markup .= $this->{$Element['handler']}($Element['text']);
+ $markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
}
else
{
- $markup .= $Element['text'];
+ $markup .= self::escape($Element['text'], true);
}
$markup .= '</'.$Element['name'].'>';
@@ -1465,10 +1549,77 @@ class Parsedown
return $markup;
}
+ protected function sanitiseElement(array $Element)
+ {
+ static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+ static $safeUrlNameToAtt = array(
+ 'a' => 'href',
+ 'img' => 'src',
+ );
+
+ if (isset($safeUrlNameToAtt[$Element['name']]))
+ {
+ $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+ }
+
+ if ( ! empty($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $att => $val)
+ {
+ # filter out badly parsed attribute
+ if ( ! preg_match($goodAttribute, $att))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ # dump onevent attribute
+ elseif (self::striAtStart($att, 'on'))
+ {
+ unset($Element['attributes'][$att]);
+ }
+ }
+ }
+
+ return $Element;
+ }
+
+ protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+ {
+ foreach ($this->safeLinksWhitelist as $scheme)
+ {
+ if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+ {
+ return $Element;
+ }
+ }
+
+ $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+ return $Element;
+ }
+
#
# Static Methods
#
+ protected static function escape($text, $allowQuotes = false)
+ {
+ return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+ }
+
+ protected static function striAtStart($string, $needle)
+ {
+ $len = strlen($needle);
+
+ if ($len > strlen($string))
+ {
+ return false;
+ }
+ else
+ {
+ return strtolower(substr($string, 0, $len)) === strtolower($needle);
+ }
+ }
+
static function instance($name = 'default')
{
if (isset(self::$instances[$name]))
@@ -1519,10 +1670,10 @@ class Parsedown
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
'i', 'rp', 'del', 'code', 'strike', 'marquee',
'q', 'rt', 'ins', 'font', 'strong',
- 's', 'tt', 'sub', 'mark',
- 'u', 'xm', 'sup', 'nobr',
- 'var', 'ruby',
- 'wbr', 'span',
- 'time',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
);
}