Skip to content

Commit 7faae09

Browse files
committed
Add tests for glob metacharacter sanitization in emoji URLs
Verifies that sanitize_file_name() prevents glob patterns like [abc].png, test*.png, tes?.png, and {foo,bar}.png from matching unintended cached files.
1 parent b2413bc commit 7faae09

File tree

1 file changed

+107
-0
lines changed

1 file changed

+107
-0
lines changed

tests/phpunit/tests/includes/class-test-attachments.php

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ class Test_Attachments extends \WP_UnitTestCase {
3131
*/
3232
protected static $author_id;
3333

34+
/**
35+
* Emoji test directory path.
36+
*
37+
* @var string
38+
*/
39+
protected static $emoji_dir;
40+
3441
/**
3542
* Set up before class.
3643
*/
@@ -51,6 +58,25 @@ public static function set_up_before_class() {
5158
'post_author' => self::$author_id,
5259
)
5360
);
61+
62+
// Create emoji test directory.
63+
$upload_dir = \wp_upload_dir();
64+
self::$emoji_dir = $upload_dir['basedir'] . Attachments::$emoji_dir;
65+
\wp_mkdir_p( self::$emoji_dir );
66+
}
67+
68+
/**
69+
* Clean up after all tests.
70+
*/
71+
public static function tear_down_after_class() {
72+
global $wp_filesystem;
73+
\WP_Filesystem();
74+
75+
if ( $wp_filesystem->is_dir( self::$emoji_dir ) ) {
76+
$wp_filesystem->rmdir( self::$emoji_dir, true );
77+
}
78+
79+
parent::tear_down_after_class();
5480
}
5581

5682
/**
@@ -1325,4 +1351,85 @@ public function test_import_emoji_returns_false_on_download_failure() {
13251351

13261352
$this->assertFalse( $result );
13271353
}
1354+
1355+
/**
1356+
* Call private get_emoji_url method via reflection.
1357+
*
1358+
* @param string $emoji_url The emoji URL.
1359+
* @return string|false The local URL or false.
1360+
*/
1361+
private function call_get_emoji_url( $emoji_url ) {
1362+
$method = new \ReflectionMethod( Attachments::class, 'get_emoji_url' );
1363+
$method->setAccessible( true );
1364+
1365+
return $method->invoke( null, $emoji_url );
1366+
}
1367+
1368+
/**
1369+
* Test that glob metacharacters in emoji URLs don't match unintended files.
1370+
*
1371+
* Without sanitization, glob patterns could match unintended files:
1372+
* - '[abc].*' would match files starting with a, b, or c
1373+
* - '*.*' would match all files
1374+
* - '?.*' would match any single-character filename
1375+
*
1376+
* With sanitization, these metacharacters are removed.
1377+
*
1378+
* @covers ::get_emoji_url
1379+
* @dataProvider data_glob_metacharacter_urls
1380+
*/
1381+
public function test_get_emoji_url_sanitizes_glob_metacharacters( $malicious_filename, $files_to_create ) {
1382+
global $wp_filesystem;
1383+
\WP_Filesystem();
1384+
1385+
$domain_dir = self::$emoji_dir . 'glob-test.example.com';
1386+
$wp_filesystem->mkdir( $domain_dir, FS_CHMOD_DIR );
1387+
1388+
// Create files that would match the unsanitized glob pattern.
1389+
foreach ( $files_to_create as $filename ) {
1390+
$wp_filesystem->put_contents( $domain_dir . '/' . $filename, 'test' );
1391+
}
1392+
1393+
// URL with glob metacharacters.
1394+
$url = 'https://glob-test.example.com/emoji/' . $malicious_filename;
1395+
$result = $this->call_get_emoji_url( $url );
1396+
1397+
// Should NOT match any files because metacharacters are sanitized.
1398+
$this->assertFalse( $result, sprintf( 'Glob pattern "%s" should not match existing files', $malicious_filename ) );
1399+
}
1400+
1401+
/**
1402+
* Data provider for glob metacharacter tests.
1403+
*
1404+
* @return array Test cases: [malicious_filename, files_that_would_match_if_not_sanitized].
1405+
*/
1406+
public function data_glob_metacharacter_urls() {
1407+
return array(
1408+
'brackets' => array( '[abc].png', array( 'a.png', 'b.png', 'c.png' ) ),
1409+
'asterisk' => array( 'test*.png', array( 'test1.png', 'test2.png', 'testing.png' ) ),
1410+
'question_mark' => array( 'tes?.png', array( 'test.png', 'tess.png', 'tesx.png' ) ),
1411+
'curly_braces' => array( '{foo,bar}.png', array( 'foo.png', 'bar.png' ) ),
1412+
);
1413+
}
1414+
1415+
/**
1416+
* Test that get_emoji_url finds cached files for normal URLs.
1417+
*
1418+
* @covers ::get_emoji_url
1419+
*/
1420+
public function test_get_emoji_url_finds_cached_file() {
1421+
global $wp_filesystem;
1422+
\WP_Filesystem();
1423+
1424+
$domain_dir = self::$emoji_dir . 'cache-test.example.com';
1425+
$wp_filesystem->mkdir( $domain_dir, FS_CHMOD_DIR );
1426+
$wp_filesystem->put_contents( $domain_dir . '/normal-emoji.png', 'test' );
1427+
1428+
// Normal URL should find the cached file.
1429+
$url = 'https://cache-test.example.com/emoji/normal-emoji.png';
1430+
$result = $this->call_get_emoji_url( $url );
1431+
1432+
$this->assertNotFalse( $result );
1433+
$this->assertStringContainsString( 'normal-emoji.png', $result );
1434+
}
13281435
}

0 commit comments

Comments
 (0)