Skip to content

Commit 7f4cbdc

Browse files
authored
Combine sanitization functions (#1397)
* Combine sanitization functions * Add host_list sanitization
1 parent 339ab1d commit 7f4cbdc

File tree

5 files changed

+278
-43
lines changed

5 files changed

+278
-43
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
* Upgrade script to fix Follower json representations with unescaped backslashes.
13+
* Centralized place for sanitization functions.
1314

1415
### Changed
1516

includes/class-sanitize.php

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
/**
3+
* Sanitization file.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub;
9+
10+
use Activitypub\Model\Blog;
11+
12+
/**
13+
* Sanitization class.
14+
*/
15+
class Sanitize {
16+
/**
17+
* Sanitize a list of URLs.
18+
*
19+
* @param string|array $value The value to sanitize.
20+
* @return array The sanitized list of URLs.
21+
*/
22+
public static function url_list( $value ) {
23+
if ( ! \is_array( $value ) ) {
24+
$value = \explode( PHP_EOL, $value );
25+
}
26+
27+
$value = \array_map( 'trim', $value );
28+
$value = \array_filter( $value );
29+
$value = \array_map( 'sanitize_url', $value );
30+
$value = \array_unique( $value );
31+
32+
return \array_values( $value );
33+
}
34+
35+
/**
36+
* Sanitize a list of hosts.
37+
*
38+
* @param string $value The value to sanitize.
39+
* @return string The sanitized list of hosts.
40+
*/
41+
public static function host_list( $value ) {
42+
$value = \explode( PHP_EOL, $value );
43+
$value = \array_map(
44+
function ( $host ) {
45+
$host = \trim( $host );
46+
$host = \strtolower( $host );
47+
$host = \set_url_scheme( $host );
48+
$host = \sanitize_url( $host, array( 'http', 'https' ) );
49+
50+
// Remove protocol.
51+
if ( \str_contains( $host, 'http' ) ) {
52+
$host = \wp_parse_url( $host, PHP_URL_HOST );
53+
}
54+
55+
return \filter_var( $host, FILTER_VALIDATE_DOMAIN );
56+
},
57+
$value
58+
);
59+
60+
return \implode( PHP_EOL, \array_filter( $value ) );
61+
}
62+
63+
/**
64+
* Sanitize a blog identifier.
65+
*
66+
* @param string $value The value to sanitize.
67+
* @return string The sanitized blog identifier.
68+
*/
69+
public static function blog_identifier( $value ) {
70+
// Hack to allow dots in the username.
71+
$parts = \explode( '.', $value );
72+
$sanitized = \array_map( 'sanitize_title', $parts );
73+
$sanitized = \implode( '.', $sanitized );
74+
75+
// Check for login or nicename.
76+
$user = new \WP_User_Query(
77+
array(
78+
'search' => $sanitized,
79+
'search_columns' => array( 'user_login', 'user_nicename' ),
80+
'number' => 1,
81+
'hide_empty' => true,
82+
'fields' => 'ID',
83+
)
84+
);
85+
86+
if ( $user->get_results() ) {
87+
\add_settings_error(
88+
'activitypub_blog_identifier',
89+
'activitypub_blog_identifier',
90+
\esc_html__( 'You cannot use an existing author&#8217;s name for the blog profile ID.', 'activitypub' )
91+
);
92+
93+
return Blog::get_default_username();
94+
}
95+
96+
return $sanitized;
97+
}
98+
}

includes/wp-admin/class-settings.php

Lines changed: 3 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use Activitypub\Collection\Actors;
1111
use Activitypub\Model\Blog;
12+
use Activitypub\Sanitize;
1213
use function Activitypub\is_user_disabled;
1314

1415
/**
@@ -136,14 +137,7 @@ public static function register_settings() {
136137
'type' => 'string',
137138
'description' => \__( 'Websites allowed to credit you.', 'activitypub' ),
138139
'default' => \Activitypub\home_host(),
139-
'sanitize_callback' => function ( $value ) {
140-
$value = explode( PHP_EOL, $value );
141-
$value = array_filter( array_map( 'trim', $value ) );
142-
$value = array_filter( array_map( 'esc_attr', $value ) );
143-
$value = implode( PHP_EOL, $value );
144-
145-
return $value;
146-
},
140+
'sanitize_callback' => array( Sanitize::class, 'host_list' ),
147141
)
148142
);
149143

@@ -197,41 +191,7 @@ public static function register_settings() {
197191
'description' => \esc_html__( 'The Identifier of the Blog-User', 'activitypub' ),
198192
'show_in_rest' => true,
199193
'default' => Blog::get_default_username(),
200-
'sanitize_callback' => function ( $value ) {
201-
// Hack to allow dots in the username.
202-
$parts = explode( '.', $value );
203-
$sanitized = array();
204-
205-
foreach ( $parts as $part ) {
206-
$sanitized[] = \sanitize_title( $part );
207-
}
208-
209-
$sanitized = implode( '.', $sanitized );
210-
211-
// Check for login or nicename.
212-
$user = new \WP_User_Query(
213-
array(
214-
'search' => $sanitized,
215-
'search_columns' => array( 'user_login', 'user_nicename' ),
216-
'number' => 1,
217-
'hide_empty' => true,
218-
'fields' => 'ID',
219-
)
220-
);
221-
222-
if ( $user->results ) {
223-
add_settings_error(
224-
'activitypub_blog_identifier',
225-
'activitypub_blog_identifier',
226-
\esc_html__( 'You cannot use an existing author\'s name for the blog profile ID.', 'activitypub' ),
227-
'error'
228-
);
229-
230-
return Blog::get_default_username();
231-
}
232-
233-
return $sanitized;
234-
},
194+
'sanitize_callback' => array( Sanitize::class, 'blog_identifier' ),
235195
)
236196
);
237197

readme.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ For reasons of data protection, it is not possible to see the followers of other
132132
= Unreleased =
133133

134134
* Added: Upgrade script to fix Follower json representations with unescaped backslashes.
135+
* Added: Centralized place for sanitization functions.
135136
* Changed: Bumped minimum required WordPress version to 6.4.
136137
* Changed: Use a later hook for Posts to get published to the Outbox, to get sure all `post_meta`s and `taxonomy`s are set stored properly.
137138
* Changed: Use webfinger as author email for comments from the Fediverse.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<?php
2+
/**
3+
* Test file for Sanitize class.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Tests;
9+
10+
use Activitypub\Sanitize;
11+
12+
/**
13+
* Test class for Sanitize.
14+
*
15+
* @coversDefaultClass \Activitypub\Sanitize
16+
*/
17+
class Test_Sanitize extends \WP_UnitTestCase {
18+
19+
/**
20+
* Data provider for URL list tests.
21+
*
22+
* @return array Test data.
23+
*/
24+
public function url_list_provider() {
25+
return array(
26+
'duplicate_urls' => array(
27+
array(
28+
'https://example.com',
29+
'https://example.com',
30+
'not-a-url',
31+
'https://wordpress.org',
32+
),
33+
array(
34+
'https://example.com',
35+
'http://not-a-url',
36+
'https://wordpress.org',
37+
),
38+
),
39+
'mixed_urls_in_string_whitespace' => array(
40+
"https://example.com\nnot-a-url\nhttps://wordpress.org ",
41+
array(
42+
'https://example.com',
43+
'http://not-a-url',
44+
'https://wordpress.org',
45+
),
46+
),
47+
'special_characters' => array(
48+
array(
49+
'https://example.com/path with spaces ',
50+
'https://example.com/über/path',
51+
'https://example.com/path?param=value&param2=value2#section',
52+
),
53+
array(
54+
'https://example.com/path%20with%20spaces',
55+
'https://example.com/über/path',
56+
'https://example.com/path?param=value&param2=value2#section',
57+
),
58+
),
59+
'empty_array' => array( array(), array() ),
60+
);
61+
}
62+
63+
/**
64+
* Test url_list with various inputs.
65+
*
66+
* @dataProvider url_list_provider
67+
* @covers ::url_list
68+
*
69+
* @param mixed $input Input value.
70+
* @param array $expected Expected output.
71+
*/
72+
public function test_url_list( $input, $expected ) {
73+
$this->assertEquals( $expected, Sanitize::url_list( $input ) );
74+
}
75+
76+
/**
77+
* Data provider for host list tests.
78+
*
79+
* @return array Test data.
80+
*/
81+
public function host_list_provider() {
82+
return array(
83+
'single_valid_host' => array(
84+
'example.com',
85+
'example.com',
86+
),
87+
'multiple_valid_hosts' => array(
88+
"ftp://example.com\nhttp://wordpress.org\nhttps://test.example.com",
89+
"example.com\nwordpress.org\ntest.example.com",
90+
),
91+
'mixed_case_hosts' => array(
92+
"ExAmPlE.cOm\nWoRdPrEsS.oRg",
93+
"example.com\nwordpress.org",
94+
),
95+
'invalid_hosts' => array(
96+
" not-a-domain\n\nexample.com\n\t@invalid.com",
97+
"not-a-domain\nexample.com\ninvalid.com",
98+
),
99+
'empty_string' => array(
100+
'',
101+
'',
102+
),
103+
);
104+
}
105+
106+
/**
107+
* Test host_list with various inputs.
108+
*
109+
* @dataProvider host_list_provider
110+
* @covers ::host_list
111+
*
112+
* @param string $input Input value.
113+
* @param string $expected Expected output.
114+
*/
115+
public function test_host_list( $input, $expected ) {
116+
$this->assertEquals( $expected, Sanitize::host_list( $input ) );
117+
}
118+
119+
/**
120+
* Data provider for blog identifier tests.
121+
*
122+
* @return array Test data.
123+
*/
124+
public function blog_identifier_provider() {
125+
return array(
126+
'simple_string' => array( 'test-Blog', 'test-blog' ),
127+
'with_spaces' => array( 'test blog', 'test-blog' ),
128+
'with_dots' => array( 'test.blog', 'test.blog' ),
129+
'special_chars' => array( 'test@#$%^&*blog', 'testblog' ),
130+
'multiple_dots' => array( 'test.blog.name', 'test.blog.name' ),
131+
);
132+
}
133+
134+
/**
135+
* Test blog_identifier with various inputs.
136+
*
137+
* @dataProvider blog_identifier_provider
138+
* @covers ::blog_identifier
139+
*
140+
* @param string $input Input value.
141+
* @param string $expected Expected output.
142+
*/
143+
public function test_blog_identifier( $input, $expected ) {
144+
$this->assertEquals( $expected, Sanitize::blog_identifier( $input ) );
145+
}
146+
147+
/**
148+
* Test blog_identifier with an existing username.
149+
*
150+
* @covers ::blog_identifier
151+
*/
152+
public function test_blog_identifier_with_existing_user() {
153+
$user_id = self::factory()->user->create(
154+
array(
155+
'user_login' => 'existing-user',
156+
'user_nicename' => 'test-nicename',
157+
)
158+
);
159+
160+
$result = Sanitize::blog_identifier( 'existing-user' );
161+
162+
$this->assertEquals( \Activitypub\Model\Blog::get_default_username(), $result );
163+
$this->assertNotEmpty( get_settings_errors( 'activitypub_blog_identifier' ) );
164+
165+
// Reset.
166+
$GLOBALS['wp_settings_errors'] = array();
167+
168+
$result = Sanitize::blog_identifier( 'test-nicename' );
169+
170+
$this->assertEquals( \Activitypub\Model\Blog::get_default_username(), $result );
171+
$this->assertNotEmpty( get_settings_errors( 'activitypub_blog_identifier' ) );
172+
173+
\wp_delete_user( $user_id );
174+
}
175+
}

0 commit comments

Comments
 (0)