openvk/Web/Models/Entities/Note.php
mr❤️🤢 9714d0f036
feat(notes): use whitelist for images sources (#1352)
Переиначенный #942, но в нём картинка скачивалась с сервера, в этом в
конфиге задаётся список разрешённых хостов и затем идёт редирект если
ссылка прошла проверку. Если не прошла то редиректает на заглушку.
Впрочем, это не поможет если в конфиге не указан cdn, но по крайней мере
не будет приколов с автозапуском методов на основном сайте

После мержа в конфиг добавьте kaslana.ovk.to


![image](https://github.com/user-attachments/assets/6f72add7-1d9f-4ca8-8938-3e00be5b61f7)

---------

Co-authored-by: n1rwana <93197434+n1rwana@users.noreply.github.com>
2025-06-15 16:55:27 +03:00

184 lines
5.1 KiB
PHP

<?php
declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use HTMLPurifier_Config;
use HTMLPurifier;
use HTMLPurifier_Filter;
class SecurityFilter extends HTMLPurifier_Filter
{
public function preFilter($html, $config, $context)
{
$html = preg_replace_callback(
'/<img[^>]*src\s*=\s*["\']([^"\']*)["\'][^>]*>/i',
function ($matches) {
$originalSrc = $matches[1];
$src = $originalSrc;
if (OPENVK_ROOT_CONF["openvk"]["preferences"]["notes"]["disableHotlinking"] ?? true) {
if (!str_contains($src, "/image.php?url=")) {
$src = '/image.php?url=' . base64_encode($originalSrc);
} /*else {
$src = preg_replace_callback('/(.*)\/image\.php\?url=(.*)/i', function ($matches) {
return base64_decode($matches[2]);
}, $src);
}*/
}
return str_replace($originalSrc, $src, $matches[0]);
},
$html
);
return $html;
}
}
class Note extends Postable
{
protected $tableName = "notes";
protected function renderHTML(?string $content = null): string
{
$config = HTMLPurifier_Config::createDefault();
$config->set("Attr.AllowedClasses", []);
$config->set("Attr.DefaultInvalidImageAlt", "Unknown image");
$config->set("AutoFormat.AutoParagraph", true);
$config->set("AutoFormat.Linkify", true);
$config->set("URI.Base", "//$_SERVER[SERVER_NAME]/");
$config->set("URI.Munge", "/away.php?xinf=%n.%m:%r&css=%p&to=%s");
$config->set("URI.MakeAbsolute", true);
$config->set("HTML.Doctype", "XHTML 1.1");
$config->set("HTML.TidyLevel", "heavy");
$config->set("HTML.AllowedElements", [
"div",
"h3",
"h4",
"h5",
"h6",
"p",
"i",
"b",
"a",
"del",
"ins",
"sup",
"sub",
"table",
"thead",
"tbody",
"tr",
"td",
"th",
"img",
"ul",
"ol",
"li",
"hr",
"br",
"acronym",
"blockquote",
"cite",
"span",
]);
$config->set("HTML.AllowedAttributes", [
"table.summary",
"td.abbr",
"th.abbr",
"a.href",
"img.src",
"img.alt",
"img.style",
"div.style",
"div.title",
"span.class",
"p.class",
]);
$config->set("CSS.AllowedProperties", [
"float",
"height",
"width",
"max-height",
"max-width",
"font-weight",
]);
$config->set("Attr.AllowedClasses", [
"underline",
]);
$config->set('Filter.Custom', [new SecurityFilter()]);
$source = $content;
if (!$source) {
if (is_null($this->getRecord())) {
if (isset($this->changes["source"])) {
$source = $this->changes["source"];
} else {
throw new \LogicException("Can't render note without content set.");
}
} else {
$source = $this->getRecord()->source;
}
}
$purifier = new HTMLPurifier($config);
return $purifier->purify($source);
}
public function getName(): string
{
return $this->getRecord()->name;
}
public function getPreview(int $length = 25): string
{
return ovk_proc_strtr(strip_tags($this->getRecord()->source), $length);
}
public function getText(): string
{
if (is_null($this->getRecord())) {
return $this->renderHTML();
}
$cached = $this->getRecord()->cached_content;
if (!$cached) {
$cached = $this->renderHTML();
$this->setCached_Content($cached);
$this->save();
}
return $this->renderHTML($cached);
}
public function getSource(): string
{
return $this->getRecord()->source;
}
public function canBeViewedBy(?User $user = null): bool
{
if ($this->isDeleted() || $this->getOwner()->isDeleted()) {
return false;
}
return $this->getOwner()->getPrivacyPermission('notes.read', $user) && $this->getOwner()->canBeViewedBy($user);
}
public function toVkApiStruct(): object
{
$res = (object) [];
$res->id = $this->getVirtualId();
$res->owner_id = $this->getOwner()->getId();
$res->title = $this->getName();
$res->text = $this->getText();
$res->date = $this->getPublicationTime()->timestamp();
$res->comments = $this->getCommentsCount();
$res->view_url = "/note" . $this->getOwner()->getId() . "_" . $this->getVirtualId();
return $res;
}
}