Allow safe rich text in market multiline fields
This commit is contained in:
1 parent
73e2144571
commit
c25d3f1b3c
4 files changed
+141
-17
No files matched your search
@@ -18,6 +18,120 @@ const MARKET_OCCURRENCE_DAY_INHERITED_FIELDS = [
|
||||
'delivery_start_time', 'delivery_end_time',
|
||||
];
|
||||
|
||||
const MARKET_MANAGEMENT_MULTILINE_FIELDS = [
|
||||
'address', 'notes', 'other_infrastructure', 'own_stand_requirements',
|
||||
'travel_address', 'parking_options', 'delivery_access', 'storage_options',
|
||||
'travel_notes', 'cancellation_terms', 'revenue_share_terms',
|
||||
'other_participation_conditions', 'observations', 'cost_items_text',
|
||||
'description',
|
||||
];
|
||||
|
||||
function market_management_is_allowed_document_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)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$relativePath = rawurldecode($matches[1]);
|
||||
if ($relativePath === '' || str_contains($relativePath, "\0") || str_contains($relativePath, '\\') || str_starts_with($relativePath, '/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (explode('/', $relativePath) as $segment) {
|
||||
if ($segment === '' || $segment === '.' || $segment === '..') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function market_management_sanitize_multiline_html(?string $value): ?string
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$tokens = preg_split('/(<\/?a\b[^>]*>|<br\s*\/?>|<\/?strong\s*>)/iu', $value, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
if ($tokens === false) {
|
||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
$result = '';
|
||||
$anchorOpen = false;
|
||||
foreach ($tokens as $token) {
|
||||
if (preg_match('/^<br\s*\/?>$/iu', $token)) {
|
||||
$result .= '<br>';
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^<strong\s*>$/iu', $token)) {
|
||||
$result .= '<strong>';
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^<\/strong\s*>$/iu', $token)) {
|
||||
$result .= '</strong>';
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^<\/a\s*>$/iu', $token)) {
|
||||
if ($anchorOpen) {
|
||||
$result .= '</a>';
|
||||
$anchorOpen = false;
|
||||
} else {
|
||||
$result .= htmlspecialchars($token, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^<a\b[^>]*\bhref\s*=\s*(["\'])(.*?)\1[^>]*>$/isu', $token, $matches)
|
||||
&& market_management_is_allowed_document_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">';
|
||||
$anchorOpen = true;
|
||||
continue;
|
||||
}
|
||||
$result .= htmlspecialchars($token, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
if ($anchorOpen) {
|
||||
$result .= '</a>';
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function market_management_sanitize_multiline_fields(array &$record): void
|
||||
{
|
||||
foreach (MARKET_MANAGEMENT_MULTILINE_FIELDS as $field) {
|
||||
if (array_key_exists($field, $record) && is_string($record[$field])) {
|
||||
$record[$field] = market_management_sanitize_multiline_html($record[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function market_management_sanitize_occurrence_payload(array &$occurrence): void
|
||||
{
|
||||
market_management_sanitize_multiline_fields($occurrence);
|
||||
foreach ($occurrence['days'] ?? [] as &$day) {
|
||||
if (is_array($day)) {
|
||||
market_management_sanitize_multiline_fields($day);
|
||||
}
|
||||
}
|
||||
unset($day);
|
||||
foreach ($occurrence['cost_items'] ?? [] as &$costItem) {
|
||||
if (is_array($costItem)) {
|
||||
market_management_sanitize_multiline_fields($costItem);
|
||||
}
|
||||
}
|
||||
unset($costItem);
|
||||
}
|
||||
|
||||
function market_management_list_organizers(PDO $pdo): array
|
||||
{
|
||||
return $pdo->query(
|
||||
@@ -482,6 +596,7 @@ function market_management_get_market(PDO $pdo, int $marketId, bool $includeArch
|
||||
$costs = $pdo->prepare('SELECT * FROM market_occurrence_cost_item WHERE occurrence_id = :occurrence_id ORDER BY id ASC');
|
||||
$costs->execute([':occurrence_id' => $occurrenceId]);
|
||||
$occurrence['cost_items'] = $costs->fetchAll();
|
||||
market_management_sanitize_occurrence_payload($occurrence);
|
||||
}
|
||||
unset($occurrence);
|
||||
return $market;
|
||||
@@ -721,6 +836,9 @@ function market_management_write_fields(array $input, array $definitions): array
|
||||
'nullable_id' => market_management_optional_id($input, $field),
|
||||
default => throw new LogicException('Unbekannter Feldtyp.'),
|
||||
};
|
||||
if ($type === 'string' && in_array($field, MARKET_MANAGEMENT_MULTILINE_FIELDS, true)) {
|
||||
$value = market_management_sanitize_multiline_html($value);
|
||||
}
|
||||
$fields[] = $field . ' = :' . $field;
|
||||
$params[':' . $field] = $value;
|
||||
}
|
||||
@@ -982,6 +1100,7 @@ function market_management_get_occurrence(PDO $pdo, int $occurrenceId, bool $inc
|
||||
break;
|
||||
}
|
||||
}
|
||||
market_management_sanitize_occurrence_payload($occurrence);
|
||||
$costSummary = $pdo->prepare(
|
||||
'SELECT COALESCE(SUM(gross_amount), 0) AS gross_amount,
|
||||
COALESCE(SUM(net_amount), 0) AS net_amount,
|
||||
@@ -1330,7 +1449,7 @@ function market_management_create_cost_item(PDO $pdo, array $input): array
|
||||
$stmt->execute([
|
||||
':occurrence_id' => $occurrenceId,
|
||||
':cost_type_id' => $costTypeId,
|
||||
':description' => market_management_optional_string($input, 'description'),
|
||||
':description' => market_management_sanitize_multiline_html(market_management_optional_string($input, 'description')),
|
||||
':gross_amount' => $grossAmount,
|
||||
':tax_status' => $taxStatus,
|
||||
':tax_rate' => $taxRate,
|
||||
@@ -1381,7 +1500,9 @@ function market_management_update_cost_item(PDO $pdo, int $costItemId, array $in
|
||||
$sets[] = $field . ' = :' . $field;
|
||||
$params[':' . $field] = $type === 'number'
|
||||
? market_management_optional_number($input, $field)
|
||||
: market_management_optional_string($input, $field);
|
||||
: ($field === 'description'
|
||||
? market_management_sanitize_multiline_html(market_management_optional_string($input, $field))
|
||||
: market_management_optional_string($input, $field));
|
||||
}
|
||||
if (isset($params[':tax_status']) && !in_array($params[':tax_status'], ['tax_free', 'taxable'], true)) {
|
||||
throw new InvalidArgumentException('Ungültiger Steuerstatus.');
|
||||
@@ -1418,7 +1539,7 @@ function market_management_update_occurrence_simple_costs(PDO $pdo, int $occurre
|
||||
$item = market_management_create_cost_item($pdo, [
|
||||
'occurrence_id' => $occurrenceId,
|
||||
'cost_type_id' => market_management_get_cost_type_id($pdo, 'stand_fee'),
|
||||
'description' => array_key_exists('cost_items_text', $input) ? market_management_optional_string($input, 'cost_items_text') : null,
|
||||
'description' => array_key_exists('cost_items_text', $input) ? market_management_sanitize_multiline_html(market_management_optional_string($input, 'cost_items_text')) : null,
|
||||
'gross_amount' => $grossAmount,
|
||||
'tax_status' => $taxStatus,
|
||||
'tax_rate' => $taxRate,
|
||||
@@ -1429,7 +1550,7 @@ function market_management_update_occurrence_simple_costs(PDO $pdo, int $occurre
|
||||
$costFields['gross_amount'] = market_management_optional_number($input, 'gross_cost');
|
||||
}
|
||||
if (array_key_exists('cost_items_text', $input)) {
|
||||
$costFields['description'] = market_management_optional_string($input, 'cost_items_text');
|
||||
$costFields['description'] = market_management_sanitize_multiline_html(market_management_optional_string($input, 'cost_items_text'));
|
||||
}
|
||||
if (array_key_exists('tax_status', $input)) {
|
||||
$costFields['tax_status'] = trim((string) $input['tax_status']);
|
||||
|
||||
Reference in new issue
Block a user