Skip to content

Commit

Permalink
Introduce od_store_url_metric_validity filter so Image Prioritizer ca…
Browse files Browse the repository at this point in the history
…n validate background-image URL
  • Loading branch information
westonruter committed Nov 25, 2024
1 parent a75b94f commit f2959cd
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 4 deletions.
63 changes: 61 additions & 2 deletions plugins/image-prioritizer/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* @param string $optimization_detective_version Current version of the optimization detective plugin.
*/
function image_prioritizer_init( string $optimization_detective_version ): void {
$required_od_version = '0.7.0';
$required_od_version = '0.9.0';
if ( ! version_compare( (string) strtok( $optimization_detective_version, '-' ), $required_od_version, '>=' ) ) {
add_action(
'admin_notices',
Expand Down Expand Up @@ -121,7 +121,6 @@ function image_prioritizer_filter_extension_module_urls( $extension_module_urls
* @return array<string, array{type: string}> Additional properties.
*/
function image_prioritizer_add_element_item_schema_properties( array $additional_properties ): array {
// TODO: Validation of the URL.
$additional_properties['lcpElementExternalBackgroundImage'] = array(
'type' => 'object',
'properties' => array(
Expand Down Expand Up @@ -151,3 +150,63 @@ function image_prioritizer_add_element_item_schema_properties( array $additional
);
return $additional_properties;
}

/**
* Validates that the provided background image URL is valid.
*
* @since n.e.x.t
*
* @param bool|WP_Error|mixed $validity Validity. Valid if true or a WP_Error without any errors, or invalid otherwise.
* @param OD_Strict_URL_Metric $url_metric URL Metric, already validated against the JSON Schema.
* @return bool|WP_Error Validity. Valid if true or a WP_Error without any errors, or invalid otherwise.
*/
function image_prioritizer_filter_store_url_metric_validity( $validity, OD_Strict_URL_Metric $url_metric ) {
if ( ! is_bool( $validity ) && ! ( $validity instanceof WP_Error ) ) {
$validity = (bool) $validity;
}

$data = $url_metric->get( 'lcpElementExternalBackgroundImage' );
if ( ! is_array( $data ) ) {
return $validity;
}

$r = wp_safe_remote_head(
$data['url'],
array(
'redirection' => 3, // Allow up to 3 redirects.
)
);
if ( $r instanceof WP_Error ) {
return new WP_Error(
WP_DEBUG ? $r->get_error_code() : 'head_request_failure',
__( 'HEAD request for background image URL failed.', 'image-prioritizer' ) . ( WP_DEBUG ? ' ' . $r->get_error_message() : '' ),
array(
'code' => 500,
)
);
}
$response_code = wp_remote_retrieve_response_code( $r );
if ( $response_code < 200 || $response_code >= 400 ) {
return new WP_Error(
'background_image_response_not_ok',
__( 'HEAD request for background image URL did not return with a success status code.', 'image-prioritizer' ),
array(
'code' => WP_DEBUG ? $response_code : 400,
)
);
}

$content_type = wp_remote_retrieve_header( $r, 'Content-Type' );
if ( ! is_string( $content_type ) || ! str_starts_with( $content_type, 'image/' ) ) {
return new WP_Error(
'background_image_response_not_image',
__( 'HEAD request for background image URL did not return an image Content-Type.', 'image-prioritizer' ),
array(
'code' => 400,
)
);
}

// TODO: Check for the Content-Length and return invalid if it is gigantic?
return $validity;
}
1 change: 1 addition & 0 deletions plugins/image-prioritizer/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
add_action( 'od_init', 'image_prioritizer_init' );
add_filter( 'od_extension_module_urls', 'image_prioritizer_filter_extension_module_urls' );
add_filter( 'od_url_metric_schema_root_additional_properties', 'image_prioritizer_add_element_item_schema_properties' );
add_filter( 'od_store_url_metric_validity', 'image_prioritizer_filter_store_url_metric_validity', 10, 2 );
2 changes: 1 addition & 1 deletion plugins/image-prioritizer/tests/test-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public function data_provider_to_test_image_prioritizer_init(): array {
'expected' => false,
),
'with_new_version' => array(
'version' => '0.7.0',
'version' => '99.0.0',
'expected' => true,
),
);
Expand Down
32 changes: 31 additions & 1 deletion plugins/optimization-detective/storage/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function od_register_endpoint(): void {
return new WP_Error(
'url_metric_storage_locked',
__( 'URL Metric storage is presently locked for the current IP.', 'optimization-detective' ),
array( 'status' => 403 )
array( 'status' => 403 ) // TODO: Consider 423 Locked status code.
);
}
return true;
Expand Down Expand Up @@ -152,6 +152,7 @@ function od_handle_rest_request( WP_REST_Request $request ) {
$request->get_param( 'viewport' )['width']
);
} catch ( InvalidArgumentException $exception ) {
// Note: This should never happen because an exception only occurs if a viewport width is less than zero, and the JSON Schema enforces that the viewport.width have a minimum of zero.
return new WP_Error( 'invalid_viewport_width', $exception->getMessage() );
}
if ( $url_metric_group->is_complete() ) {
Expand Down Expand Up @@ -197,6 +198,35 @@ function od_handle_rest_request( WP_REST_Request $request ) {
);
}

/**
* Filters whether a URL Metric is valid for storage.
*
* This allows for custom validation constraints to be applied beyond what can be expressed in JSON Schema. This is
* also necessary because the 'validate_callback' key in a JSON Schema is not respected when gathering the REST API
* endpoint args via the {@see rest_get_endpoint_args_for_schema()} function. Besides this, the REST API doesn't
* support 'validate_callback' for any nested arguments in any case, meaning that custom constraints would be able
* to be applied to multidimensional objects, such as the items inside 'elements'.
*
* This filter only applies when storing a URL Metric via the REST API. It does not run when a stored URL Metric
* loaded from the od_url_metric post type. This means that validation logic enforced via this filter can be more
* expensive, such as doing filesystem checks or HTTP requests.
*
* @since n.e.x.t
*
* @param bool|WP_Error $validity Validity. Valid if true or a WP_Error without any errors, or invalid otherwise.
* @param OD_Strict_URL_Metric $url_metric URL Metric, already validated against the JSON Schema.
*/
$validity = apply_filters( 'od_store_url_metric_validity', true, $url_metric );
if ( false === $validity || ( $validity instanceof WP_Error && $validity->has_errors() ) ) {
if ( false === $validity ) {
$validity = new WP_Error( 'invalid_url_metric', __( 'Validity of URL Metric was rejected by filter.', 'optimization-detective' ) );
}
if ( ! isset( $validity->error_data['code'] ) ) {
$validity->error_data['code'] = 400;
}
return $validity;
}

// TODO: This should be changed from store_url_metric($slug, $url_metric) instead be update_post( $slug, $group_collection ). As it stands, store_url_metric() is duplicating logic here.
$result = OD_URL_Metrics_Post_Type::store_url_metric(
$request->get_param( 'slug' ),
Expand Down

0 comments on commit f2959cd

Please sign in to comment.