Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ private function scan_tokens(array $tokens): void
}
}

private static function encoded_payload_could_decode_to_http_scheme(string $payload): bool
public static function encoded_payload_could_decode_to_http_scheme(string $payload): bool
{
return strpos($payload, 'aHR0') !== false
|| strpos($payload, 'dHA6') !== false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,34 @@
*
* @method string get_modifiable_text()
* @method bool set_modifiable_text(string $plaintext_content)
* @method string|null get_tag()
* @method string|true|null get_attribute(string $name)
* @property array<string, WP_HTML_Text_Replacement> $lexical_updates
*/
// phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound
class CautiousTextBlockMarkupUrlProcessor extends BlockMarkupUrlProcessor {
/**
* Replace configured URL bases in the current raw text token.
* Replace the current text token without passing it through the HTML
* encoder. The caller has already rewritten a nested data value and must
* preserve the surrounding shortcode or builder bytes verbatim.
*/
public function replace_raw_current_text(string $updated_text): bool
{
if ('#text' !== $this->get_token_type()) {
return false;
}

$this->get_updated_html();
if (!$this->set_modifiable_text('')) {
return false;
}

$this->lexical_updates['modifiable text']->text = $updated_text;
return true;
}

/**
* Replace configured URL bases in the current raw modifiable text.
*
* WP_HTML_Tag_Processor exposes decoded text through get_modifiable_text()
* and HTML-encodes the complete replacement in set_modifiable_text(). The
Expand All @@ -50,7 +72,7 @@ class CautiousTextBlockMarkupUrlProcessor extends BlockMarkupUrlProcessor {
*/
public function replace_url_bases_in_current_text(array $url_mapping): bool
{
if ('#text' !== $this->get_token_type()) {
if ($this->get_token_type() !== '#text' && $this->get_token_type() !== '#tag') {
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,34 +287,63 @@ private function create_url_candidate_pattern(
string $source_path
): string
{
$escaped_separator = '(?:\\\\{1}|\\\\{3})?';
// A builder can store the URL syntax in JSON, HTML entities, percent
// escapes, or CSS hexadecimal escapes. The authority itself remains
// plain text, so replace only that raw span after recognizing these
// spelling variants of `:` and `/`.
$backslash = '(?:\\\\{1}|\\\\{3})?';
$css_colon = '\\\\(?:0{0,5}3[aA])(?:[ \\t\\r\\n\\f])?';
$css_slash = '\\\\(?:0{0,5}2[fF])(?:[ \\t\\r\\n\\f])?';
$json_colon = '\\\\u00(?:3[aA])';
$json_slash = '\\\\u00(?:2[fF])';
$colon = '(?:' . $backslash . ':|(?i:%3a)|&\#(?:0*58|[xX]0*3[aA]);|' . $css_colon . '|' . $json_colon . ')';
$slash = '(?:' . $backslash . '/|(?i:%2f)|&\#(?:0*47|[xX]0*2[fF]);|' . $css_slash . '|' . $json_slash . ')';
$source_authority_pattern = $this->create_css_escaped_text_pattern($source_authority);
$source_path_pattern = str_replace(
'/',
$escaped_separator . '/',
$slash,
preg_quote($source_path, '~')
);

return '~
(?<![A-Za-z0-9._%+\\/@-])
(?:(?<![A-Za-z0-9._%+=\\/@-])|(?<=%22)|(?<=%27))
(?:
(?i:' . preg_quote($source_scheme, '~') . ')
' . $escaped_separator . ':
' . $escaped_separator . '/
' . $escaped_separator . '/
' . $colon . '
' . $slash . '
' . $slash . '
(?:[^\s<>@/\\\\]+@)?
)?
(?<base>
(?<authority>(?i:' . preg_quote($source_authority, '~') . '))
(?<authority>' . $source_authority_pattern . ')
' . $source_path_pattern . '
)
(?=
$
| ' . $escaped_separator . '/
| ' . $slash . '
| [/?# \t\r\n,!;)\]}>"\']
)
~x';
}

/**
* Match an ASCII authority as literal bytes or CSS hexadecimal escapes.
* The complete authority remains one captured span, allowing the caller
* to replace it without selecting an escape spelling for the target host.
*/
private function create_css_escaped_text_pattern(string $text): string
{
$pattern = '';
$length = strlen($text);
for ($offset = 0; $offset < $length; ++$offset) {
$byte = $text[$offset];
$hex = dechex(ord($byte));
$pattern .= '(?:(?i:' . preg_quote($byte, '~') . ')|\\\\0{0,5}' . $hex . '(?:[ \\t\\r\\n\\f])?)';
}

return $pattern;
}

/**
* @return array{
* source_authority: string,
Expand Down
Loading
Loading