mirror of
https://github.com/Neetpone/ponepaste.git
synced 2025-03-12 22:50:07 +01:00
338 lines
No EOL
9.8 KiB
PHP
338 lines
No EOL
9.8 KiB
PHP
<?php
|
|
|
|
namespace Aidantwoods\SecureParsedown;
|
|
|
|
class SecureParsedown extends \Parsedown {
|
|
const version = '1.0.1';
|
|
|
|
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:',
|
|
);
|
|
|
|
protected function blockCodeComplete($Block) {
|
|
$text = $Block['element']['text']['text'];
|
|
|
|
$Block['element']['text']['text'] = $text;
|
|
|
|
return $Block;
|
|
}
|
|
|
|
protected function blockComment($Line) {
|
|
if ($this->markupEscaped or $this->safeMode) {
|
|
return;
|
|
}
|
|
|
|
if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') {
|
|
$Block = array(
|
|
'markup' => $Line['body'],
|
|
);
|
|
|
|
if (preg_match('/-->$/', $Line['text'])) {
|
|
$Block['closed'] = true;
|
|
}
|
|
|
|
return $Block;
|
|
}
|
|
}
|
|
|
|
protected function blockFencedCodeComplete($Block) {
|
|
$text = $Block['element']['text']['text'];
|
|
|
|
$Block['element']['text']['text'] = $text;
|
|
|
|
return $Block;
|
|
}
|
|
|
|
protected function blockMarkup($Line) {
|
|
if ($this->markupEscaped or $this->safeMode) {
|
|
return;
|
|
}
|
|
|
|
if (preg_match('/^<(\w*)(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*(\/)?>/', $Line['text'], $matches)) {
|
|
$element = strtolower($matches[1]);
|
|
|
|
if (in_array($element, $this->textLevelElements)) {
|
|
return;
|
|
}
|
|
|
|
$Block = array(
|
|
'name' => $matches[1],
|
|
'depth' => 0,
|
|
'markup' => $Line['text'],
|
|
);
|
|
|
|
$length = strlen($matches[0]);
|
|
|
|
$remainder = substr($Line['text'], $length);
|
|
|
|
if (trim($remainder) === '') {
|
|
if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) {
|
|
$Block['closed'] = true;
|
|
|
|
$Block['void'] = true;
|
|
}
|
|
} else {
|
|
if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) {
|
|
return;
|
|
}
|
|
|
|
if (preg_match('/<\/' . $matches[1] . '>[ ]*$/i', $remainder)) {
|
|
$Block['closed'] = true;
|
|
}
|
|
}
|
|
|
|
return $Block;
|
|
}
|
|
}
|
|
|
|
protected function inlineCode($Excerpt) {
|
|
$marker = $Excerpt['text'][0];
|
|
|
|
if (preg_match('/^(' . $marker . '+)[ ]*(.+?)[ ]*(?<!' . $marker . ')\1(?!' . $marker . ')/s', $Excerpt['text'], $matches)) {
|
|
$text = $matches[2];
|
|
$text = preg_replace("/[ ]*\n/", ' ', $text);
|
|
|
|
return array(
|
|
'extent' => strlen($matches[0]),
|
|
'element' => array(
|
|
'name' => 'code',
|
|
'text' => $text,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function inlineLink($Excerpt) {
|
|
$Element = array(
|
|
'name' => 'a',
|
|
'handler' => 'line',
|
|
'text' => null,
|
|
'attributes' => array(
|
|
'href' => null,
|
|
'title' => null,
|
|
),
|
|
);
|
|
|
|
$extent = 0;
|
|
|
|
$remainder = $Excerpt['text'];
|
|
|
|
if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) {
|
|
$Element['text'] = $matches[1];
|
|
|
|
$extent += strlen($matches[0]);
|
|
|
|
$remainder = substr($remainder, $extent);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches)) {
|
|
$Element['attributes']['href'] = $matches[1];
|
|
|
|
if (isset($matches[2])) {
|
|
$Element['attributes']['title'] = substr($matches[2], 1, -1);
|
|
}
|
|
|
|
$extent += strlen($matches[0]);
|
|
} else {
|
|
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) {
|
|
$definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
|
|
$definition = strtolower($definition);
|
|
|
|
$extent += strlen($matches[0]);
|
|
} else {
|
|
$definition = strtolower($Element['text']);
|
|
}
|
|
|
|
if (!isset($this->DefinitionData['Reference'][$definition])) {
|
|
return;
|
|
}
|
|
|
|
$Definition = $this->DefinitionData['Reference'][$definition];
|
|
|
|
$Element['attributes']['href'] = $Definition['url'];
|
|
$Element['attributes']['title'] = $Definition['title'];
|
|
}
|
|
|
|
return array(
|
|
'extent' => $extent,
|
|
'element' => $Element,
|
|
);
|
|
}
|
|
|
|
protected function inlineMarkup($Excerpt) {
|
|
if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) {
|
|
return;
|
|
}
|
|
|
|
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) {
|
|
return array(
|
|
'markup' => $matches[0],
|
|
'extent' => strlen($matches[0]),
|
|
);
|
|
}
|
|
|
|
if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches)) {
|
|
return array(
|
|
'markup' => $matches[0],
|
|
'extent' => strlen($matches[0]),
|
|
);
|
|
}
|
|
|
|
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*' . $this->regexHtmlAttribute . ')*[ ]*\/?>/s', $Excerpt['text'], $matches)) {
|
|
return array(
|
|
'markup' => $matches[0],
|
|
'extent' => strlen($matches[0]),
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function inlineUrl($Excerpt) {
|
|
if ($this->urlsLinked !== true or !isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') {
|
|
return;
|
|
}
|
|
|
|
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' => $url,
|
|
'attributes' => array(
|
|
'href' => $url,
|
|
),
|
|
),
|
|
);
|
|
|
|
return $Inline;
|
|
}
|
|
}
|
|
|
|
protected function inlineUrlTag($Excerpt) {
|
|
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) {
|
|
$url = $matches[1];
|
|
|
|
return array(
|
|
'extent' => strlen($matches[0]),
|
|
'element' => array(
|
|
'name' => 'a',
|
|
'text' => $url,
|
|
'attributes' => array(
|
|
'href' => $url,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
protected function element(array $Element) {
|
|
if ($this->safeMode) {
|
|
$Element = $this->sanitiseElement($Element);
|
|
}
|
|
|
|
$markup = '<' . $Element['name'];
|
|
|
|
if (isset($Element['attributes'])) {
|
|
foreach ($Element['attributes'] as $name => $value) {
|
|
if ($value === null) {
|
|
continue;
|
|
}
|
|
|
|
$markup .= ' ' . $name . '="' . self::escape($value) . '"';
|
|
}
|
|
}
|
|
|
|
if (isset($Element['text'])) {
|
|
$markup .= '>';
|
|
|
|
if (isset($Element['handler'])) {
|
|
$markup .= $this->{$Element['handler']}($Element['text']);
|
|
} else {
|
|
$markup .= self::escape($Element['text'], true);
|
|
}
|
|
|
|
$markup .= '</' . $Element['name'] . '>';
|
|
} else {
|
|
$markup .= ' />';
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
} |