Allow external web links in market rich text

This commit is contained in:
gitea_admin committed 2026-09-21 15:33:38 +02:00
1 parent c25d3f1b3c
commit 9f10344fa7
4 files changed
+20 -12

No files matched your search

@@ -47,8 +47,9 @@ Link: <a href="https://erpnaurua.imhochrain.ch/files.php?path=Marktmanagement%2F
Leerzeichen, Umlaute und sonstige Sonderzeichen im URL-Parameter müssen
vollständig URL-encodiert werden. Im Feld `notes` sind ausschließlich `<a>`
für solche internen Dokumentlinks sowie `<br>` und `<strong>` erlaubt. Andere
HTML-Tags, externe Links und nicht erlaubte URL-Schemata werden entfernt. Der
Link ist schreibgeschützt und erfordert eine aktive ERP-Anmeldung.
HTML-Tags und nicht erlaubte URL-Schemata werden entfernt. Externe `http://`-
und `https://`-Links sind erlaubt und öffnen in einem neuen Tab. Der Link ist
schreibgeschützt und erfordert bei internen Dokumenten eine aktive ERP-Anmeldung.
Die Route benötigt auf dem ERP-Webserver die Konfiguration
`HERMES_DOCUMENT_ROOT=/volume1/Hermes/Hermes Naurua`. Dieser lokale Pfad ist
@@ -72,7 +72,8 @@ Modulberechtigung.
Marktdurchführung speichert, muss die Quelldatei als interner `<a>`-Link im
Feld `notes` referenziert werden. Zusätzlich sind in mehrzeiligen Feldern
`<br>` und `<strong>` erlaubt. Die Position im bestehenden Freitext ist frei
wählbar; andere HTML-Tags und externe Links sind nicht zulässig.
wählbar. Externe `http://`- und `https://`-Links sind zulässig und öffnen in
einem neuen Tab; andere HTML-Tags und URL-Schemata sind nicht zulässig.
- Unsichere Erkennung wird nicht durch eine zusätzliche App-Freigabelogik
behandelt; die fachliche Qualitätssicherung liegt im Hermes-Training.
+14 -8
View File
@@ -26,15 +26,21 @@ const MARKET_MANAGEMENT_MULTILINE_FIELDS = [
'description',
];
function market_management_is_allowed_document_url(string $url): bool
function market_management_is_allowed_link_url(string $url): bool
{
$parts = parse_url($url);
if (!is_array($parts)
|| ($parts['scheme'] ?? '') !== 'https'
|| ($parts['host'] ?? '') !== 'erpnaurua.imhochrain.ch'
|| ($parts['path'] ?? '') !== '/files.php'
|| isset($parts['port'], $parts['user'], $parts['pass'], $parts['fragment'])
|| !preg_match('/^path=([^&]+)$/', (string) ($parts['query'] ?? ''), $matches)) {
|| !in_array(($parts['scheme'] ?? ''), ['http', 'https'], true)
|| ($parts['host'] ?? '') === ''
|| isset($parts['user'], $parts['pass'])) {
return false;
}
if (($parts['host'] ?? '') !== 'erpnaurua.imhochrain.ch' || ($parts['path'] ?? '') !== '/files.php') {
return true;
}
if (isset($parts['port'], $parts['fragment']) || !preg_match('/^path=([^&]+)$/', (string) ($parts['query'] ?? ''), $matches)) {
return false;
}
@@ -88,12 +94,12 @@ function market_management_sanitize_multiline_html(?string $value): ?string
continue;
}
if (preg_match('/^<a\b[^>]*\bhref\s*=\s*(["\'])(.*?)\1[^>]*>$/isu', $token, $matches)
&& market_management_is_allowed_document_url($matches[2])) {
&& market_management_is_allowed_link_url($matches[2])) {
if ($anchorOpen) {
$result .= '</a>';
}
$href = htmlspecialchars($matches[2], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$result .= '<a class="sg-hyperlink" href="' . $href . '" target="_blank" rel="noopener">';
$result .= '<a class="sg-hyperlink" href="' . $href . '" target="_blank" rel="noopener noreferrer">';
$anchorOpen = true;
continue;
}
+1 -1
View File
@@ -1152,7 +1152,7 @@ function render_auth_home_page(array $user, array $otcProducts = [], array $otcM
echo " const getCurrentLimit = () => parseInt(getTable()?.dataset.marketsLimit || '20', 10);";
echo " const getPageSize = () => parseInt(getTable()?.dataset.marketsPageSize || '20', 10);";
echo " const marketEscape = (value) => String(value ?? '').replace(/[&<>\"']/g, (character) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[character] || character));";
echo " const marketDisplay = (value) => { if (value === null || value === undefined || value === '') { return 'Nicht hinterlegt'; } const escaped = marketEscape(value); const safeMarkup = escaped.replace(/&lt;br&gt;/g, '<br>').replace(/&lt;strong&gt;/g, '<strong>').replace(/&lt;\\/strong&gt;/g, '</strong>').replace(/&lt;a class=&quot;sg-hyperlink&quot; href=&quot;(https:\\/\\/erpnaurua\\.imhochrain\\.ch\\/files\\.php\\?path=[^&quot;]+)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;/g, '<a class=\"sg-hyperlink\" href=\"$1\" target=\"_blank\" rel=\"noopener\">').replace(/&lt;\\/a&gt;/g, '</a>'); return safeMarkup.replace(/\\r?\\n/g, '<br>'); };";
echo " const marketDisplay = (value) => { if (value === null || value === undefined || value === '') { return 'Nicht hinterlegt'; } const escaped = marketEscape(value); const safeMarkup = escaped.replace(/&lt;br&gt;/g, '<br>').replace(/&lt;strong&gt;/g, '<strong>').replace(/&lt;\\/strong&gt;/g, '</strong>').replace(/&lt;a class=&quot;sg-hyperlink&quot; href=&quot;(https?:\\/\\/.*?)&quot; target=&quot;_blank&quot; rel=&quot;noopener noreferrer&quot;&gt;/g, '<a class=\"sg-hyperlink\" href=\"$1\" target=\"_blank\" rel=\"noopener noreferrer\">').replace(/&lt;\\/a&gt;/g, '</a>'); return safeMarkup.replace(/\\r?\\n/g, '<br>'); };";
echo " const marketBoolean = (value) => value === true || value === 'true' || value === 1 || value === '1' ? 'Ja' : value === false || value === 'false' || value === 0 || value === '0' ? 'Nein' : 'Nicht hinterlegt';";
echo " const marketMoney = (value) => value === null || value === undefined || value === '' ? 'Nicht hinterlegt' : 'CHF ' + Number(value).toFixed(2);";
echo " const marketContactName = (contact) => { if (!contact) { return 'Nicht hinterlegt'; } return marketDisplay([contact.first_name, contact.last_name].filter(Boolean).join(' ')); };";