fix: preserve invalid metadata payloads
This commit is contained in:
@@ -25,12 +25,19 @@ final class MetadataUrlTransformer {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( $this->isSerialized( $value ) ) {
|
||||
// phpcs:ignore -- Required to transform serialized metadata while preserving valid string lengths.
|
||||
$unserialized = unserialize( $value, array( 'allowed_classes' => false ) );
|
||||
if ( $this->looksSerialized( trim( $value ) ) && trim( $value ) !== $value ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( $this->looksSerialized( $value ) ) {
|
||||
$unserialized = $this->unserializeValue( $value );
|
||||
|
||||
if ( ! $unserialized['valid'] || is_object( $unserialized['value'] ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// phpcs:ignore -- Required to reserialize metadata with updated string lengths.
|
||||
return serialize( $this->transformValue( $unserialized, $mappings ) );
|
||||
return serialize( $this->transformValue( $unserialized['value'], $mappings ) );
|
||||
}
|
||||
|
||||
if ( $this->isJsonObjectOrArray( $value ) ) {
|
||||
@@ -43,6 +50,8 @@ final class MetadataUrlTransformer {
|
||||
return $encoded;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return $this->url_transformer->transformString( $value, $mappings );
|
||||
@@ -65,23 +74,43 @@ final class MetadataUrlTransformer {
|
||||
return $transformed;
|
||||
}
|
||||
|
||||
private function isSerialized( string $value ): bool {
|
||||
$trimmed = trim( $value );
|
||||
private function looksSerialized( string $value ): bool {
|
||||
return 'N;' === $value || 'b:0;' === $value || 1 === preg_match( '/^(?:a|O|s|i|d|b):/', $value );
|
||||
}
|
||||
|
||||
if ( 'N;' === $trimmed ) {
|
||||
return true;
|
||||
/**
|
||||
* @return array{valid: bool, value: mixed}
|
||||
*/
|
||||
private function unserializeValue( string $value ): array {
|
||||
if ( 'N;' === $value ) {
|
||||
return array(
|
||||
'valid' => true,
|
||||
'value' => null,
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'b:0;' === $trimmed ) {
|
||||
return true;
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.PHP.DiscouragedPHPFunctions.serialize_unserialize -- Metadata may be stored in PHP serialized format.
|
||||
$unserialized = @unserialize( $value, array( 'allowed_classes' => false ) );
|
||||
|
||||
if ( false === $unserialized && 'b:0;' !== $value ) {
|
||||
return array(
|
||||
'valid' => false,
|
||||
'value' => null,
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^(?:a|O|s|i|d|b):/', $trimmed ) ) {
|
||||
return false;
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize -- Strict validation avoids transforming malformed serialized-looking strings.
|
||||
if ( serialize( $unserialized ) !== $value ) {
|
||||
return array(
|
||||
'valid' => false,
|
||||
'value' => null,
|
||||
);
|
||||
}
|
||||
|
||||
// phpcs:ignore -- Validation must parse PHP serialized metadata.
|
||||
return false !== @unserialize( $trimmed, array( 'allowed_classes' => false ) );
|
||||
return array(
|
||||
'valid' => true,
|
||||
'value' => $unserialized,
|
||||
);
|
||||
}
|
||||
|
||||
private function isJsonObjectOrArray( string $value ): bool {
|
||||
|
||||
@@ -71,6 +71,51 @@ class MetadataUrlTransformerTest extends TestCase {
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_leaves_invalid_serialized_strings_unchanged(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
$value = 'a:1:{s:3:"url";s:37:"https://example.test/uploads/hero.jpg";} trailing';
|
||||
|
||||
self::assertSame( $value, $transformer->transformValue( $value, $this->mappings() ) );
|
||||
}
|
||||
|
||||
public function test_it_leaves_whitespace_wrapped_serialized_strings_unchanged(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
// phpcs:ignore -- Test fixture requires native serialized metadata.
|
||||
$value = ' ' . serialize( array( 'url' => 'https://example.test/uploads/hero.jpg' ) ) . ' ';
|
||||
|
||||
self::assertSame( $value, $transformer->transformValue( $value, $this->mappings() ) );
|
||||
}
|
||||
|
||||
public function test_it_preserves_serialized_false_and_null_values(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
|
||||
self::assertSame( 'b:0;', $transformer->transformValue( 'b:0;', $this->mappings() ) );
|
||||
self::assertSame( 'N;', $transformer->transformValue( 'N;', $this->mappings() ) );
|
||||
}
|
||||
|
||||
public function test_it_leaves_serialized_objects_unchanged(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
$value = 'O:8:"stdClass":1:{s:3:"url";s:28:"https://example.test/inside";}';
|
||||
|
||||
self::assertSame( $value, $transformer->transformValue( $value, $this->mappings() ) );
|
||||
}
|
||||
|
||||
public function test_it_leaves_invalid_json_strings_unchanged(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
$value = '{"url":"https://example.test/missing"';
|
||||
|
||||
self::assertSame( $value, $transformer->transformValue( $value, $this->mappings() ) );
|
||||
}
|
||||
|
||||
public function test_it_transforms_nested_json_arrays(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
|
||||
self::assertSame(
|
||||
'[{"url":"https:\/\/staging.example.test\/one"},{"count":2}]',
|
||||
$transformer->transformValue( '[{"url":"https:\/\/example.test\/one"},{"count":2}]', $this->mappings() )
|
||||
);
|
||||
}
|
||||
|
||||
public function test_it_transforms_plain_string_metadata(): void {
|
||||
$transformer = new MetadataUrlTransformer( new UrlTransformer() );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user