Add ForkBB\url() function

This function checks the url (partially) and normalizes it (also partially at the moment).
This commit is contained in:
Visman 2022-01-08 18:54:20 +07:00
parent b1791cf33b
commit 75df01aa8e
4 changed files with 186 additions and 23 deletions

View file

@ -24,7 +24,7 @@ class Generate extends Method
$query = 'SELECT bb_structure
FROM ::bbcode';
$content = "<?php\n\nuse function \\ForkBB\\__;\n\nreturn [\n";
$content = "<?php\n\nuse function \\ForkBB\\__;\nuse function \\ForkBB\\url;\n\nreturn [\n";
$stmt = $this->c->DB->query($query);
while ($row = $stmt->fetch()) {

View file

@ -203,16 +203,15 @@ HANDLER,
],
],
'handler' => <<<'HANDLER'
$def = $attrs['Def'] ?? $body;
$def = \implode('@',
\array_map('\\rawurlencode',
\array_map('\\rawurldecode',
\explode('@', $def)
)
)
);
$def = \htmlspecialchars_decode($attrs['Def'] ?? $body, \ENT_QUOTES | \ENT_HTML5);
$def = url("mailto:{$def}");
return "<a href=\"mailto:{$def}\">{$body}</a>";
if ('' == $def) {
return '<span class="f-bb-badlink">BAD EMAIL</span>';
} else {
$def = $parser->e($def);
return "<a href=\"{$def}\">{$body}</a>";
}
HANDLER,
],
[
@ -371,25 +370,27 @@ if (isset($attrs['Def'])) {
}
}
$fUrl = \str_replace([' ', '\'', '`', '"'], ['%20', '', '', ''], $url);
$fUrl = \htmlspecialchars_decode($url, \ENT_QUOTES | \ENT_HTML5);
if (0 === \strpos($url, 'ftp.')) {
$fUrl = 'ftp://' . $fUrl;
} elseif (! \preg_match('%^(?:\.?\.?/|#|[a-z](?:[a-z]|[a-z0-9]{1,6}):)%', $fUrl)) {
if (\preg_match('%^[^/]+@[^/]+$%', $fUrl)) {
$fUrl = 'mailto:' . $fUrl;
} else {
$fUrl = '//' . $fUrl;
}
$fUrl = url($fUrl);
if ('' == $fUrl) {
return '<span class="f-bb-badlink">BAD URL</span>';
} else {
$fUrl = $parser->e($fUrl);
if ($url === $body) {
$url = \htmlspecialchars_decode($url, \ENT_QUOTES | \ENT_HTML5);
$url = \mb_strlen($url, 'UTF-8') > 55 ? \mb_substr($url, 0, 39, 'UTF-8') . ' … ' . \mb_substr($url, -10, null, 'UTF-8') : $url;
$body = $parser->e($url);
}
}
if ($url === $body) {
$url = \htmlspecialchars_decode($url, \ENT_QUOTES | \ENT_HTML5);
$url = \mb_strlen($url, 'UTF-8') > 55 ? \mb_substr($url, 0, 39, 'UTF-8') . ' … ' . \mb_substr($url, -10, null, 'UTF-8') : $url;
$body = $parser->e($url);
return "<a href=\"{$fUrl}\" rel=\"ugc\">{$body}</a>";
}
return "<a href=\"{$fUrl}\" rel=\"ugc\">{$body}</a>";
HANDLER,
],
[

View file

@ -148,3 +148,160 @@ function size(int $size): string
return __(['%s ' . $units[$i], num($size, $decimals)]);
}
/**
* Возвращает нормализованный (частично) url или пустую строку
*/
function url(string $url): string
{
if (
! isset($url[1])
|| \preg_match('%^\s%u', $url)
) {
return '';
}
switch ($url[0]) {
case '/':
if ('/' === $url[1]) {
$schemeOn = false;
$hostOn = true;
$url = 'http:' . $url;
break;
}
case '?':
case '#':
$schemeOn = false;
$hostOn = false;
$url = 'http://a.a' . $url;
break;
default:
$hostOn = true;
if (\preg_match('%^([a-z][a-z0-9+.-]*):(\S)?%i', $url, $m)) {
if (
! isset($m[2])
|| isset($m[1][10])
) {
return '';
}
$schemeOn = true;
} else {
$schemeOn = false;
$url = 'http://' . $url;
}
break;
}
$p = \parse_url($url);
if (! \is_array($p)) {
return '';
}
$scheme = \strtolower($p['scheme'] ?? '');
$result = $schemeOn && $scheme ? $scheme . ':' : '';
switch ($scheme) {
case 'javascript':
return '';
case 'mailto':
if (
isset($p['host'])
|| ! isset($p['path'])
|| ! \preg_match('%^([^\x00-\x1F]+)@([^\x00-\x1F\s@]++)$%Du', $p['path'], $m)
) {
return '';
}
$result .= \rawurlencode(\rawurldecode($m[1])) . '@' . \rawurlencode(\rawurldecode($m[2]));
break;
case 'tel':
if (
isset($p['host'])
|| ! isset($p['path'])
|| ! \preg_match('%^\+?[0-9.()-]+$%D', $p['path'])
) {
return '';
}
$result .= $p['path'];
break;
default:
if ($hostOn && isset($p['host'])) {
$result .= '//';
if (isset($p['user'])) {
$result .= \rawurlencode(\rawurldecode($p['user']));
if (isset($p['pass'])) {
$result .= ':' . \rawurlencode(\rawurldecode($p['pass']));
}
$result .= '@';
}
if (\preg_match('%[\x80-\xFF]%', $p['host'])) {
$p['host'] = \idn_to_ascii($p['host'], 0, \INTL_IDNA_VARIANT_UTS46);
}
$host = \filter_var($p['host'], \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME);
if (false !== $host) {
$result .= $host;
} elseif (
'[' === $p['host'][0]
&& ']' === $p['host'][-1]
&& \filter_var(\substr($p['host'], 1, -1), \FILTER_VALIDATE_IP, \FILTER_FLAG_IPV6)
) {
$result .= $p['host'];
} else {
return '';
}
if (isset($p['port'])) {
$result .= ':' . $p['port'];
}
}
if (isset($p['path'])) {
$result .= \preg_replace_callback(
'%[^/\%\w.~-]|\%(?![0-9a-fA-F]{2})%',
function($m) {
return \rawurlencode($m[0]);
},
$p['path']
);
}
break;
}
if (isset($p['query'])) {
$result .= '?' . \preg_replace_callback(
'%[^=&\%\w.~-]|\%(?![0-9a-fA-F]{2})%',
function($m) {
return \rawurlencode($m[0]);
},
$p['query']
);
}
if (isset($p['fragment'])) {
$result .= '#' . \preg_replace_callback(
'%[^\%\w.~-]|\%(?![0-9a-fA-F]{2})%',
function($m) {
return \rawurlencode($m[0]);
},
$p['fragment']
);
}
return $result;
}

View file

@ -1625,6 +1625,11 @@ body,
font-style: italic;
}
#fork .f-bb-badlink {
color: #D8000C;
background-color: #FFBABA;
}
#fork .f-post-body p,
#fork .f-post-body blockquote,
#fork .f-post-body br,