Automattic\WooCommerce\EmailEditor\Integrations\Utils

Html_Processing_Helper::validate_caption_attributepublic staticWC 1.0

Validate and sanitize specific caption attributes for security.

Method of the class: Html_Processing_Helper{}

No Hooks.

Returns

null. Nothing (null).

Usage

$result = Html_Processing_Helper::validate_caption_attribute( $html, $attr_name ): void;
$html(WP_HTML_Tag_Processor) (required)
HTML tag processor.
$attr_name(string) (required)
Attribute name to validate.

Html_Processing_Helper::validate_caption_attribute() code WC 10.4.3

public static function validate_caption_attribute( \WP_HTML_Tag_Processor $html, string $attr_name ): void {
	$attr_value = $html->get_attribute( $attr_name );
	if ( null === $attr_value ) {
		return;
	}

	// Block all event handler attributes (on*) - Critical security fix.
	if ( str_starts_with( $attr_name, 'on' ) ) {
		$html->remove_attribute( $attr_name );
		return;
	}

	switch ( $attr_name ) {
		case 'href':
			// Only allow http, https, mailto, and tel protocols.
			if ( ! preg_match( '/^(https?:\/\/|mailto:|tel:)/i', (string) $attr_value ) ) {
				$html->remove_attribute( $attr_name );
				break;
			}

			// Sanitize and normalize the URL using WordPress's esc_url_raw.
			$sanitized_url = esc_url_raw( (string) $attr_value );
			if ( empty( $sanitized_url ) ) {
				// If esc_url_raw returns empty, the URL was invalid - remove the attribute.
				$html->remove_attribute( $attr_name );
			} else {
				// Set the attribute to the sanitized/normalized value.
				$html->set_attribute( $attr_name, $sanitized_url );
			}
			break;

		case 'target':
			// Allow only common safe targets.
			$allowed_targets = array( '_blank', '_self' );
			$target_value    = strtolower( (string) $attr_value );
			if ( ! in_array( $target_value, $allowed_targets, true ) ) {
				$html->remove_attribute( $attr_name );
			} elseif ( '_blank' === $target_value ) {
				// When target is "_blank", ensure rel attribute has noopener and noreferrer.
				$current_rel    = $html->get_attribute( 'rel' );
				$rel_value      = is_string( $current_rel ) ? $current_rel : null;
				$normalized_rel = self::normalize_rel_attribute( $rel_value, true );
				$html->set_attribute( 'rel', $normalized_rel );
			}
			break;

		case 'rel':
			// Normalize rel attribute: lowercase, deduplicate, preserve safe tokens.
			$rel_value      = is_string( $attr_value ) ? $attr_value : null;
			$normalized_rel = self::normalize_rel_attribute( $rel_value, false );
			if ( empty( $normalized_rel ) ) {
				$html->remove_attribute( $attr_name );
			} else {
				$html->set_attribute( $attr_name, $normalized_rel );
			}
			break;

		case 'style':
			// Only allow safe CSS properties for typography and basic styling.
			$safe_properties  = self::get_safe_css_properties();
			$sanitized_styles = array();
			$style_parts      = explode( ';', (string) $attr_value );

			foreach ( $style_parts as $style_part ) {
				$style_part = trim( $style_part );
				if ( empty( $style_part ) ) {
					continue;
				}

				$property_parts = explode( ':', $style_part, 2 );
				if ( count( $property_parts ) !== 2 ) {
					continue;
				}

				$property = trim( strtolower( $property_parts[0] ) );
				$value    = trim( $property_parts[1] );

				// Only allow safe properties.
				if ( in_array( $property, $safe_properties, true ) ) {
					// Use centralized CSS value sanitization.
					$sanitized_value = self::sanitize_css_value( $value );
					if ( ! empty( $sanitized_value ) ) {
						$sanitized_styles[] = $property . ': ' . $sanitized_value;
					}
				}
			}

			if ( empty( $sanitized_styles ) ) {
				$html->remove_attribute( $attr_name );
			} else {
				$html->set_attribute( $attr_name, implode( '; ', $sanitized_styles ) );
			}
			break;

		case 'class':
			// Only allow alphanumeric characters, hyphens, and underscores.
			if ( ! preg_match( '/^[a-zA-Z0-9\s\-_]+$/', (string) $attr_value ) ) {
				$html->remove_attribute( $attr_name );
			}
			break;

		case 'data-type':
		case 'data-id':
			// Only allow alphanumeric characters, hyphens, and underscores.
			if ( ! preg_match( '/^[a-zA-Z0-9\-_]+$/', (string) $attr_value ) ) {
				$html->remove_attribute( $attr_name );
			}
			break;

		default:
			// Handle data-* attributes with strict validation.
			if ( str_starts_with( $attr_name, 'data-' ) ) {
				if ( ! preg_match( '/^[a-zA-Z0-9\-_]+$/', (string) $attr_value ) ) {
					$html->remove_attribute( $attr_name );
				}
				break;
			}
			// Default deny policy: Remove any attribute not explicitly allowed.
			$html->remove_attribute( $attr_name );
			break;
	}
}