diff --git a/projects/packages/image-cdn/changelog/fix-photon-url-encoding b/projects/packages/image-cdn/changelog/fix-photon-url-encoding new file mode 100644 index 0000000000000..c23cebccb8444 --- /dev/null +++ b/projects/packages/image-cdn/changelog/fix-photon-url-encoding @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +URL encode path parts of an image diff --git a/projects/packages/image-cdn/src/class-image-cdn-core.php b/projects/packages/image-cdn/src/class-image-cdn-core.php index 377a92682976c..3b87b87845e9b 100644 --- a/projects/packages/image-cdn/src/class-image-cdn-core.php +++ b/projects/packages/image-cdn/src/class-image-cdn-core.php @@ -191,8 +191,7 @@ public static function cdn_url( $image_url, $args = array(), $scheme = null ) { } } - $image_host_path = $image_url_parts['host'] . $image_url_parts['path']; - + $image_host_path = $image_url_parts['host'] . static::escape_path( $image_url_parts['path'] ); /** * Filters the domain used by the Photon module. * @@ -262,6 +261,22 @@ public static function is_cdn_url( $url ) { || wp_parse_url( $custom_photon_url, PHP_URL_HOST ) === $parsed_url['host']; } + /** + * URL-encodes each path component. + * + * Example: + * Input: "foo/bar baz/baz" + * Output: "foo/bar%20baz/baz" + * + * @param string $path The path to escape. + * @return string The escaped path. + */ + private static function escape_path( $path ) { + $parts = explode( '/', $path ); + $parts = array_map( 'rawurlencode', $parts ); + return implode( '/', $parts ); + } + /** * Parses WP.com-hosted image args to replicate the crop. * diff --git a/projects/packages/image-cdn/tests/php/test_class.image_cdn_core.php b/projects/packages/image-cdn/tests/php/test_class.image_cdn_core.php index 66490b1d9854f..d08e3ee153e20 100644 --- a/projects/packages/image-cdn/tests/php/test_class.image_cdn_core.php +++ b/projects/packages/image-cdn/tests/php/test_class.image_cdn_core.php @@ -283,6 +283,18 @@ public function test_is_cdn_url_method() { $this->assertFalse( Image_CDN_Core::is_cdn_url( '//example.com/img.jpg' ) ); } + /** + * @covers ::Image_CDN_Core::cdn_url + * @since $$next-version$$ + * @group jetpack_photon_filter_url_encoding + */ + public function test_photon_url_filter_url_encodes_path_parts() { + // The first two spaces are not standard spaces - https://www.compart.com/en/unicode/U+202F + $url = Image_CDN_Core::cdn_url( '//example.com/narrow no-break space/name with spaces.jpg', array(), 'https' ); + + $this->assertEquals( 'https://i0.wp.com/example.com/narrow%E2%80%AFno-break%E2%80%AFspace/name%20with%20spaces.jpg', $url ); + } + /** * @author aduth * @covers ::Image_CDN_Core::cdn_url_scheme diff --git a/projects/plugins/boost/changelog/fix-photon-url-encoding b/projects/plugins/boost/changelog/fix-photon-url-encoding new file mode 100644 index 0000000000000..4f7de91427b89 --- /dev/null +++ b/projects/plugins/boost/changelog/fix-photon-url-encoding @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Image CDN: URL encode image path parts for RSS feed compatibility diff --git a/projects/plugins/jetpack/changelog/fix-photon-url-encoding b/projects/plugins/jetpack/changelog/fix-photon-url-encoding new file mode 100644 index 0000000000000..de3a486e45325 --- /dev/null +++ b/projects/plugins/jetpack/changelog/fix-photon-url-encoding @@ -0,0 +1,4 @@ +Significance: patch +Type: compat + +Image CDN: URL encode image path parts for RSS feed compatibility