Przeglądaj źródła

Add ForkBB\url() function

This function checks the url (partially) and normalizes it (also partially at the moment).
Visman 3 lat temu
rodzic
commit
75df01aa8e

+ 1 - 1
app/Models/BBCodeList/Generate.php

@@ -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()) {

+ 23 - 22
app/config/defaultBBCode.php

@@ -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;
-    }
 }
 
-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);
-}
+$fUrl = url($fUrl);
 
-return "<a href=\"{$fUrl}\" rel=\"ugc\">{$body}</a>";
+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);
+    }
+
+    return "<a href=\"{$fUrl}\" rel=\"ugc\">{$body}</a>";
+}
 HANDLER,
     ],
     [

+ 157 - 0
app/functions.php

@@ -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;
+}

+ 5 - 0
public/style/ForkBB/style.css

@@ -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,