diff --git a/src/wp-includes/kses.php b/src/wp-includes/kses.php
index d379319f83..025ef5d66b 100644
--- a/src/wp-includes/kses.php
+++ b/src/wp-includes/kses.php
@@ -272,7 +272,10 @@ if ( ! CUSTOM_TAGS ) {
'xml:lang' => true,
),
'object' => array(
- 'data' => true,
+ 'data' => array(
+ 'required' => true,
+ 'value_callback' => '_wp_kses_allow_pdf_objects',
+ ),
'type' => array(
'required' => true,
'values' => array( 'application/pdf' ),
@@ -1661,6 +1664,17 @@ function wp_kses_check_attr_val( $value, $vless, $checkname, $checkvalue ) {
$ok = false;
}
break;
+
+ case 'value_callback':
+ /*
+ * The value_callback check is used when you want to make sure that the attribute
+ * value is accepted by the callback function.
+ */
+
+ if ( ! call_user_func( $checkvalue, $value ) ) {
+ $ok = false;
+ }
+ break;
} // End switch.
return $ok;
@@ -2566,3 +2580,34 @@ function _wp_add_global_attributes( $value ) {
return $value;
}
+
+/**
+ * Helper function to check if this is a safe PDF URL.
+ *
+ * @since 5.9.0
+ * @access private
+ * @ignore
+ *
+ * @param string $url The URL to check.
+ * @return bool True if the URL is safe, false otherwise.
+ */
+function _wp_kses_allow_pdf_objects( $value ) {
+ // We're not interested in URLs that contain query strings or fragments.
+ if ( strpos( $value, '?' ) !== false || strpos( $value, '#' ) !== false ) {
+ return false;
+ }
+
+ // If it doesn't have a PDF extension, it's not safe.
+ if ( 0 !== substr_compare( $value, '.pdf', -4, 4, true ) ) {
+ return false;
+ }
+
+ // If the URL host matches the current site's media URL, it's safe.
+ $upload_info = wp_upload_dir( null, false );
+ $upload_host = wp_parse_url( $upload_info['url'], PHP_URL_HOST );
+ if ( 0 === strpos( $value, "http://$upload_host/" ) || 0 === strpos( $value, "https://$upload_host/" ) ) {
+ return true;
+ }
+
+ return false;
+}
diff --git a/tests/phpunit/tests/kses.php b/tests/phpunit/tests/kses.php
index 481e01f23b..8c857fe7d8 100644
--- a/tests/phpunit/tests/kses.php
+++ b/tests/phpunit/tests/kses.php
@@ -1517,59 +1517,83 @@ EOF;
function data_wp_kses_object_tag_allowed() {
return array(
'valid value for type' => array(
- '',
- '',
+ '',
+ '',
),
'invalid value for type' => array(
- '',
+ '',
'',
),
'multiple type attributes, last invalid' => array(
- '',
- '',
+ '',
+ '',
),
'multiple type attributes, first uppercase, last invalid' => array(
- '',
- '',
+ '',
+ '',
),
'multiple type attributes, last upper case and invalid' => array(
- '',
- '',
+ '',
+ '',
),
'multiple type attributes, first invalid' => array(
- '',
+ '',
'',
),
'multiple type attributes, first upper case and invalid' => array(
- '',
+ '',
'',
),
'multiple type attributes, first invalid, last uppercase' => array(
- '',
+ '',
'',
),
'multiple object tags, last invalid' => array(
- '',
- '',
+ '',
+ '',
),
'multiple object tags, first invalid' => array(
- '',
- '',
+ '',
+ '',
),
'type attribute with partially incorrect value' => array(
- '',
+ '',
'',
),
'type attribute with empty value' => array(
- '',
+ '',
'',
),
'type attribute with no value' => array(
- '',
+ '',
'',
),
'no type attribute' => array(
- '',
+ '',
+ '',
+ ),
+ 'different protocol in url' => array(
+ '',
+ '',
+ ),
+ 'query string on url' => array(
+ '',
+ '',
+ ),
+ 'fragment on url' => array(
+ '',
+ '',
+ ),
+ 'wrong extension' => array(
+ '',
+ '',
+ ),
+ 'protocol relative url' => array(
+ '',
+ '',
+ ),
+ 'relative url' => array(
+ '',
'',
),
);