fix: StoredNodeGuard checks URLs, value keys and library ids strictly (P17)
URLs must be sanitizer-stable, keys inside values follow ValueCleaner::VALUE_KEY, instances need a digits-only globalModule; state keys reuse AttrWriter::STATES. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,8 @@ defined('ABSPATH') || exit;
|
||||
*/
|
||||
final class AttrWriter
|
||||
{
|
||||
private const STATES = ['value', 'hover', 'sticky'];
|
||||
/** State keys of a breakpoint entry (StoredNodeGuard uses them too). */
|
||||
public const STATES = ['value', 'hover', 'sticky'];
|
||||
|
||||
/**
|
||||
* Structure attributes (ruling S41), refused for everyone and whatever the catalog says:
|
||||
|
||||
@@ -22,13 +22,15 @@ defined('ABSPATH') || exit;
|
||||
* - a block outside the divi/ namespace, or non-whitespace text between blocks (freeform HTML,
|
||||
* Divi 4 shortcodes),
|
||||
* - an opaque block other than a readable divi/global-layout instance (undecodable attributes,
|
||||
* a globalModule reference on a plain block, an instance without a usable blockName/localAttrs),
|
||||
* a globalModule reference on a plain block, an instance without a digits-only globalModule or
|
||||
* a usable blockName/localAttrs),
|
||||
* - a code module,
|
||||
* - a builder-only path (ValueCleaner::guardPath(): own HTML, HTML attributes, custom CSS – S7/S26),
|
||||
* also when the builder-only key sits below the state value,
|
||||
* - a string with control characters (S32), rich text or code that the sanitizer would change
|
||||
* (not kses-stable), a plain-text string with markup that is not kses-stable, or a URL the
|
||||
* sanitizer refuses – routed exactly like ValueCleaner (kindAt()),
|
||||
* (not kses-stable), a plain-text string with markup that is not kses-stable, a URL the
|
||||
* sanitizer would change, or a key inside a value that is no plain identifier (S13) – routed
|
||||
* exactly like ValueCleaner (kindAt(), VALUE_KEY),
|
||||
* - a $variable(…)$ reference VariableRef::canonical() refuses for the target post, or whose
|
||||
* settings canonical() would clean (e.g. before/after with markup such a user may not write).
|
||||
* Users with unfiltered_html may move anything (the same line as the S7 gate). Pure PHP.
|
||||
@@ -37,8 +39,8 @@ final class StoredNodeGuard
|
||||
{
|
||||
public const CODE_MODULES = ['divi/code', 'divi/fullwidth-code'];
|
||||
|
||||
/** Keys of a breakpoint map entry that are states, no sub-field names. */
|
||||
private const STATE_KEYS = ['value', 'hover', 'sticky'];
|
||||
/** An instance's globalModule: the library post ID, digits only (GlobalInstance::of()). */
|
||||
private const LIBRARY_ID = '/^[1-9][0-9]*$/D';
|
||||
|
||||
private const BUILDER_ONLY = 'nur im Divi-Builder übertragbar (oder mit dem Recht unfiltered_html)';
|
||||
|
||||
@@ -119,6 +121,7 @@ final class StoredNodeGuard
|
||||
|| ! str_starts_with($name, 'divi/')
|
||||
|| $name === GlobalInstance::BLOCK
|
||||
|| ! is_string($attrs['globalModule'] ?? null)
|
||||
|| preg_match(self::LIBRARY_ID, $attrs['globalModule']) !== 1
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@@ -158,7 +161,7 @@ final class StoredNodeGuard
|
||||
continue;
|
||||
}
|
||||
foreach ($states as $state => $item) {
|
||||
$field = in_array($state, self::STATE_KEYS, true) ? null : (string) $state;
|
||||
$field = in_array($state, AttrWriter::STATES, true) ? null : (string) $state;
|
||||
$where = $field === null ? $path : $path . '.' . $field;
|
||||
$this->node($module, $path, $where, $item, $field, $field, $problems);
|
||||
}
|
||||
@@ -190,7 +193,13 @@ final class StoredNodeGuard
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $childKey => $item) {
|
||||
if (is_string($childKey)) {
|
||||
if (is_string($childKey) && preg_match(ValueCleaner::VALUE_KEY, $childKey) !== 1) {
|
||||
$problems[] = sprintf(
|
||||
'%s: ungültiger Schlüssel im Wert (erlaubt: Buchstaben, Ziffern, "_" und "-") – %s.',
|
||||
$where,
|
||||
self::BUILDER_ONLY
|
||||
);
|
||||
} elseif (is_string($childKey)) {
|
||||
$below = $where . '.' . $childKey;
|
||||
$this->node($module, $path, $below, $item, $subName ?? $childKey, $childKey, $problems);
|
||||
} else {
|
||||
@@ -238,7 +247,7 @@ final class StoredNodeGuard
|
||||
ValueKind::Html => $this->sanitizer->html($value) === $value,
|
||||
ValueKind::Code => $this->sanitizer->code($value) === $value,
|
||||
ValueKind::Text => ! str_contains($value, '<') || $this->sanitizer->html($value) === $value,
|
||||
ValueKind::Url => trim($value) === '' || $this->sanitizer->url($value) !== '',
|
||||
ValueKind::Url => $this->sanitizer->url($value) === trim($value),
|
||||
};
|
||||
|
||||
return $stable ? null : 'Inhalt, den dieses Konto so nicht schreiben darf (HTML, Skript oder Link) – '
|
||||
|
||||
@@ -45,8 +45,8 @@ final class ValueCleaner
|
||||
/** Keys whose string values are URLs when the catalog names no kind (ruling S3). */
|
||||
private const URL_KEY = '/(url|src|link|href|image)$/i';
|
||||
|
||||
/** Keys inside values: plain identifiers (ruling S13); list indexes are ints. */
|
||||
private const VALUE_KEY = '/^[A-Za-z0-9_-]+$/D';
|
||||
/** Keys inside values: plain identifiers (ruling S13); list indexes are ints. StoredNodeGuard uses it too. */
|
||||
public const VALUE_KEY = '/^[A-Za-z0-9_-]+$/D';
|
||||
|
||||
/** Last path segment naming a URL when neither kind nor key does (ruling S24): redirectUrl. */
|
||||
private const URL_SEGMENT = '/url$/i';
|
||||
|
||||
Reference in New Issue
Block a user