Changeset 63006
- Timestamp:
- 08/04/2026 07:25:22 AM (5 weeks ago)
- Location:
- trunk
- Files:
-
- 2 edited
-
src/wp-includes/class-wp-xmlrpc-server.php (modified) (2 diffs)
-
tests/phpunit/tests/xmlrpc/wp/uploadFile.php (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-includes/class-wp-xmlrpc-server.php
r62933 r63006 6441 6441 * 6442 6442 * @param array $args { 6443 * Method arguments. Note: arguments must be ordered as documented.6443 * Method arguments. Note: top-level arguments must be ordered as documented. 6444 6444 * 6445 6445 * @type int $0 Blog ID (unused). 6446 6446 * @type string $1 Username. 6447 6447 * @type string $2 Password. 6448 * @type array $3 Data. 6448 * @type array $3 { 6449 * Data for the file to upload. 6450 * 6451 * @type string $name File name. Sanitized with sanitize_file_name(). 6452 * @type string $type Optional. File MIME type, stored as the attachment's 6453 * post MIME type. Default empty string. 6454 * @type string $bits Optional. File contents. Default empty string. 6455 * @type int $post_id Optional. ID of the post to attach the file to. 6456 * Default 0. 6457 * } 6449 6458 * } 6450 6459 * @return array|IXR_Error 6451 6460 */ 6452 6461 public function mw_newMediaObject( $args ) { 6462 if ( ! $this->minimum_args( $args, 4 ) ) { 6463 return $this->error; 6464 } 6465 6453 6466 $username = $this->escape( $args[1] ); 6454 6467 $password = $this->escape( $args[2] ); 6455 6468 $data = $args[3]; 6456 6469 6457 $name = sanitize_file_name( $data['name'] );6458 $type = $data['type'];6459 $bits = $data['bits'];6460 6461 6470 $user = $this->login( $username, $password ); 6462 6471 if ( ! $user ) { … … 6471 6480 return $this->error; 6472 6481 } 6482 6483 if ( 6484 ! is_array( $data ) || 6485 ! is_string( $data['name'] ?? null ) || 6486 ! is_string( $data['type'] ?? '' ) || 6487 ! is_string( $data['bits'] ?? '' ) 6488 ) { 6489 return new IXR_Error( 400, __( 'Invalid attachment data.' ) ); 6490 } 6491 6492 $name = sanitize_file_name( $data['name'] ); 6493 6494 // A name consisting only of characters the sanitizer strips leaves nothing to write to. 6495 if ( '' === $name ) { 6496 return new IXR_Error( 400, __( 'Invalid attachment data.' ) ); 6497 } 6498 6499 $type = $data['type'] ?? ''; 6500 $bits = $data['bits'] ?? ''; 6473 6501 6474 6502 if ( is_multisite() && upload_is_user_over_quota( false ) ) { -
trunk/tests/phpunit/tests/xmlrpc/wp/uploadFile.php
r52010 r63006 35 35 $this->assertIsString( $result['type'] ); 36 36 } 37 38 /** 39 * Tests that a non-array data argument returns an error instead of 40 * triggering a fatal error. 41 * 42 * The data argument (the fourth parameter) is expected to be a struct, 43 * which is passed to the method as an array. When it is any other type, 44 * the method must return an IXR_Error rather than attempting to access 45 * array offsets on a non-array value. 46 * 47 * @ticket 65611 48 * 49 * @covers wp_xmlrpc_server::mw_newMediaObject 50 */ 51 public function test_invalid_attachment_data_should_return_error() { 52 $this->make_user_by_role( 'editor' ); 53 54 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'editor', 'editor', 'not-a-struct' ) ); 55 $this->assertIXRError( $result, 'A non-array data argument should return an IXR_Error.' ); 56 $this->assertSame( 400, $result->code, 'The error code should be 400.' ); 57 } 58 59 /** 60 * Tests that an anonymous request with a non-array data argument returns 61 * the login error rather than triggering a fatal error. 62 * 63 * The reported fatal error was reached without credentials because the 64 * data struct was read before the login was attempted. The struct must 65 * only be read once the request is authenticated. 66 * 67 * @ticket 65611 68 * 69 * @covers wp_xmlrpc_server::mw_newMediaObject 70 */ 71 public function test_anonymous_request_with_invalid_attachment_data_should_return_login_error() { 72 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'not-a-user', 'not-a-password', 'not-a-struct' ) ); 73 $this->assertIXRError( $result, 'An anonymous request should return an IXR_Error.' ); 74 $this->assertSame( 403, $result->code, 'The error code should be the 403 returned for a failed login.' ); 75 } 76 77 /** 78 * Tests that a user who cannot upload files is rejected before the data is 79 * read. 80 * 81 * The capability is checked ahead of the attachment data, so a user who is 82 * not allowed to upload is told that rather than being told the data is 83 * malformed. Sending unusable data must not change which error comes back. 84 * 85 * @ticket 65611 86 * 87 * @covers wp_xmlrpc_server::mw_newMediaObject 88 */ 89 public function test_incapable_user() { 90 $this->make_user_by_role( 'subscriber' ); 91 92 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'subscriber', 'subscriber', 'not-a-struct' ) ); 93 $this->assertIXRError( $result, 'A user who cannot upload files should return an IXR_Error.' ); 94 $this->assertSame( 401, $result->code, 'The error code should be the 401 returned for a missing capability.' ); 95 } 96 97 /** 98 * Tests that too few arguments return an error instead of emitting a PHP 99 * notice for the undefined arguments. 100 * 101 * @ticket 65611 102 * 103 * @covers wp_xmlrpc_server::mw_newMediaObject 104 * 105 * @dataProvider data_insufficient_arguments 106 * 107 * @param list<mixed> $args The arguments to pass to the method. 108 */ 109 public function test_insufficient_arguments_should_return_error( array $args ) { 110 $this->make_user_by_role( 'editor' ); 111 112 $result = $this->myxmlrpcserver->mw_newMediaObject( $args ); 113 $this->assertIXRError( $result, 'Insufficient arguments should return an IXR_Error.' ); 114 $this->assertSame( 400, $result->code, 'The error code should be 400.' ); 115 } 116 117 /** 118 * Data provider. 119 * 120 * @return array<non-falsy-string, array{args: list<mixed>}> 121 */ 122 public function data_insufficient_arguments(): array { 123 return array( 124 'no arguments' => array( 125 'args' => array(), 126 ), 127 'only the blog ID' => array( 128 'args' => array( 0 ), 129 ), 130 'missing the data' => array( 131 'args' => array( 0, 'editor', 'editor' ), 132 ), 133 ); 134 } 135 136 /** 137 * Tests that a data struct without a usable file name returns an error 138 * instead of emitting a PHP notice for the undefined array key. 139 * 140 * A file name is required to write the upload, so the request cannot 141 * succeed. It must fail with an IXR_Error rather than by reading an 142 * undefined array offset. 143 * 144 * @ticket 65611 145 * 146 * @covers wp_xmlrpc_server::mw_newMediaObject 147 * 148 * @dataProvider data_attachment_data_without_name 149 * 150 * @param array<string, mixed> $data The data argument to pass to the method. 151 */ 152 public function test_attachment_data_without_name_should_return_error( array $data ) { 153 $this->make_user_by_role( 'editor' ); 154 155 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'editor', 'editor', $data ) ); 156 $this->assertIXRError( $result, 'A data argument without a name should return an IXR_Error.' ); 157 $this->assertSame( 400, $result->code, 'The error code should be 400.' ); 158 } 159 160 /** 161 * Data provider. 162 * 163 * @return array<non-falsy-string, array{data: array<string, mixed>}> 164 */ 165 public function data_attachment_data_without_name(): array { 166 return array( 167 'empty struct' => array( 168 'data' => array(), 169 ), 170 'only type and bits' => array( 171 'data' => array( 172 'type' => 'image/jpeg', 173 'bits' => 'contents', 174 ), 175 ), 176 'non-string name' => array( 177 'data' => array( 178 'name' => array( 'a2-small.jpg' ), 179 'type' => 'image/jpeg', 180 'bits' => 'contents', 181 ), 182 ), 183 ); 184 } 185 186 /** 187 * Tests that a file name left empty by sanitization returns the same error 188 * as an absent one. 189 * 190 * sanitize_file_name() strips special characters and then trims the 191 * remaining leading and trailing '.', '-' and '_' characters, so a name 192 * built only from those is reduced to an empty string. That leaves nothing 193 * to write, which is a malformed request rather than a server failure, so 194 * it must be reported as a 400 like any other unusable name instead of 195 * reaching wp_upload_bits() and surfacing as a 500. 196 * 197 * @ticket 65611 198 * 199 * @covers wp_xmlrpc_server::mw_newMediaObject 200 * 201 * @dataProvider data_attachment_data_with_unusable_name 202 * 203 * @param string $name The file name to pass to the method. 204 */ 205 public function test_attachment_data_with_unusable_name_should_return_error( string $name ) { 206 $this->make_user_by_role( 'editor' ); 207 208 $data = array( 209 'name' => $name, 210 'type' => 'image/jpeg', 211 'bits' => 'contents', 212 ); 213 214 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'editor', 'editor', $data ) ); 215 $this->assertIXRError( $result, 'A name left empty by sanitization should return an IXR_Error.' ); 216 $this->assertSame( 400, $result->code, 'The error code should be 400.' ); 217 } 218 219 /** 220 * Data provider. 221 * 222 * @return array<non-falsy-string, array{name: string}> 223 */ 224 public function data_attachment_data_with_unusable_name(): array { 225 return array( 226 'empty name' => array( 227 'name' => '', 228 ), 229 'only dots' => array( 230 'name' => '...', 231 ), 232 'only dashes' => array( 233 'name' => '---', 234 ), 235 'only underscores' => array( 236 'name' => '___', 237 ), 238 'only a space' => array( 239 'name' => ' ', 240 ), 241 'only special chars' => array( 242 'name' => '///', 243 ), 244 'only a question mark' => array( 245 'name' => '?', 246 ), 247 ); 248 } 249 250 /** 251 * Tests that a data struct with a non-string type or bits member returns an 252 * error instead of triggering a fatal error. 253 * 254 * A struct sent for either member arrives as an array. An array reaches 255 * fwrite() by way of wp_upload_bits(), which throws a TypeError, and it 256 * survives sanitize_mime_type() to reach the database as the attachment's 257 * post MIME type. Both members must be rejected before that point. 258 * 259 * @ticket 65611 260 * 261 * @covers wp_xmlrpc_server::mw_newMediaObject 262 * 263 * @dataProvider data_attachment_data_with_invalid_members 264 * 265 * @param array<string, mixed> $data The data argument to pass to the method. 266 */ 267 public function test_attachment_data_with_invalid_members_should_return_error( array $data ) { 268 $this->make_user_by_role( 'editor' ); 269 270 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'editor', 'editor', $data ) ); 271 $this->assertIXRError( $result, 'A data argument with a non-string member should return an IXR_Error.' ); 272 $this->assertSame( 400, $result->code, 'The error code should be 400.' ); 273 } 274 275 /** 276 * Data provider. 277 * 278 * @return array<non-falsy-string, array{data: array<string, mixed>}> 279 */ 280 public function data_attachment_data_with_invalid_members(): array { 281 return array( 282 'non-string bits' => array( 283 'data' => array( 284 'name' => 'a2-small.jpg', 285 'type' => 'image/jpeg', 286 'bits' => array( 'contents' ), 287 ), 288 ), 289 'non-string type' => array( 290 'data' => array( 291 'name' => 'a2-small.jpg', 292 'type' => array( 'image/jpeg' ), 293 'bits' => 'contents', 294 ), 295 ), 296 ); 297 } 298 299 /** 300 * Tests that a data struct without the optional members is still accepted. 301 * 302 * Only the name is required. The type and bits members are tolerated when 303 * absent, and must not emit a PHP notice for the undefined array keys. 304 * 305 * @ticket 65611 306 * 307 * @covers wp_xmlrpc_server::mw_newMediaObject 308 * 309 * @dataProvider data_attachment_data_with_optional_members_omitted 310 * 311 * @param array<string, mixed> $data The data argument to pass to the method. 312 */ 313 public function test_attachment_data_with_optional_members_omitted_should_be_accepted( array $data ) { 314 $this->make_user_by_role( 'editor' ); 315 316 $result = $this->myxmlrpcserver->mw_newMediaObject( array( 0, 'editor', 'editor', $data ) ); 317 $this->assertNotIXRError( $result ); 318 $this->assertIsString( $result['id'] ); 319 $this->assertStringMatchesFormat( '%d', $result['id'] ); 320 } 321 322 /** 323 * Data provider. 324 * 325 * @return array<non-falsy-string, array{data: array<string, mixed>}> 326 */ 327 public function data_attachment_data_with_optional_members_omitted(): array { 328 return array( 329 'missing type' => array( 330 'data' => array( 331 'name' => 'a2-small.jpg', 332 'bits' => file_get_contents( DIR_TESTDATA . '/images/a2-small.jpg' ), 333 ), 334 ), 335 'missing bits' => array( 336 'data' => array( 337 'name' => 'a2-small.jpg', 338 'type' => 'image/jpeg', 339 ), 340 ), 341 ); 342 } 37 343 }
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)