PluginProbe
ActivityPub / 8.0.2
ActivityPub v8.0.2
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / includes / class-sanitize.php

class-sanitize.php in ActivityPub 8.0.2, at includes/class-sanitize.php

294 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sanitization file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Collection\Remote_Actors;
11 use Activitypub\Model\Blog;
12
13 /**
14 * Sanitization class.
15 */
16 class Sanitize {
17 /**
18 * Sanitize a list of URLs.
19 *
20 * @param string|array $value The value to sanitize.
21 * @return array The sanitized list of URLs.
22 */
23 public static function url_list( $value ) {
24 if ( ! \is_array( $value ) ) {
25 $value = \explode( PHP_EOL, (string) $value );
26 }
27
28 $value = \array_filter( $value );
29 $value = \array_map( 'trim', $value );
30 $value = \array_map( 'sanitize_url', $value );
31 $value = \array_unique( $value );
32
33 return \array_values( $value );
34 }
35
36 /**
37 * Sanitize and normalize a list of account identifiers to ActivityPub IDs.
38 *
39 * This function processes various identifier formats, such as URLs and
40 * webfinger identifiers, and normalizes them into a consistent format.
41 *
42 * @param string|array $value The value to sanitize.
43 *
44 * @return array The sanitized and normalized list of account identifiers.
45 */
46 public static function identifier_list( $value ) {
47 if ( ! \is_array( $value ) ) {
48 $value = \explode( PHP_EOL, (string) $value );
49 }
50
51 $value = \array_filter( $value );
52 $uris = array();
53
54 foreach ( $value as $uri ) {
55 $uri = \trim( $uri );
56 $uri = \ltrim( $uri, '@' );
57
58 if ( \is_email( $uri ) ) {
59 $_uri = Webfinger::resolve( $uri );
60 if ( \is_wp_error( $_uri ) ) {
61 $uris[] = $uri;
62 continue;
63 }
64
65 $uri = $_uri;
66 }
67
68 $uri = \sanitize_url( $uri );
69 $actor = Remote_Actors::fetch_by_uri( $uri );
70 if ( \is_wp_error( $actor ) ) {
71 $uris[] = $uri;
72 } else {
73 $uris[] = \sanitize_url( $actor->guid );
74 }
75 }
76
77 return \array_values( \array_unique( $uris ) );
78 }
79
80 /**
81 * Sanitize a list of hosts.
82 *
83 * @param string $value The value to sanitize.
84 * @return string The sanitized list of hosts.
85 */
86 public static function host_list( $value ) {
87 $value = \explode( PHP_EOL, (string) $value );
88 $value = \array_map(
89 static function ( $host ) {
90 $host = \trim( $host );
91 $host = \strtolower( $host );
92 $host = \set_url_scheme( $host );
93 $host = \sanitize_url( $host, array( 'http', 'https' ) );
94
95 // Remove protocol.
96 if ( \str_contains( $host, 'http' ) ) {
97 $host = \wp_parse_url( $host, PHP_URL_HOST );
98 }
99
100 return \filter_var( $host, FILTER_VALIDATE_DOMAIN );
101 },
102 $value
103 );
104
105 return \implode( PHP_EOL, \array_filter( $value ) );
106 }
107
108 /**
109 * Sanitize a blog identifier.
110 *
111 * @param string $value The value to sanitize.
112 * @return string The sanitized blog identifier.
113 */
114 public static function blog_identifier( $value ) {
115 // Hack to allow dots in the username.
116 $parts = \explode( '.', (string) $value );
117 $sanitized = \array_map( 'sanitize_title', $parts );
118 $sanitized = \implode( '.', $sanitized );
119
120 if ( empty( $sanitized ) ) {
121 return Blog::get_default_username();
122 }
123
124 // Check for login or nicename.
125 $user = new \WP_User_Query(
126 array(
127 'search' => $sanitized,
128 'search_columns' => array( 'user_login', 'user_nicename' ),
129 'number' => 1,
130 'hide_empty' => true,
131 'fields' => 'ID',
132 )
133 );
134
135 if ( $user->get_results() ) {
136 \add_settings_error(
137 'activitypub_blog_identifier',
138 'activitypub_blog_identifier',
139 \esc_html__( 'You cannot use an existing author&#8217;s name for the blog profile ID.', 'activitypub' )
140 );
141
142 return Blog::get_default_username();
143 }
144
145 return $sanitized;
146 }
147
148 /**
149 * Get the sanitized value of a constant.
150 *
151 * @param mixed $value The constant value.
152 *
153 * @return string The sanitized value.
154 */
155 public static function constant_value( $value ) {
156 if ( is_bool( $value ) ) {
157 return $value ? 'true' : 'false';
158 }
159
160 if ( is_string( $value ) ) {
161 return esc_attr( $value );
162 }
163
164 if ( is_array( $value ) ) {
165 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
166 return print_r( $value, true );
167 }
168
169 return $value;
170 }
171
172 /**
173 * Sanitize a webfinger identifier.
174 *
175 * @param string $value The value to sanitize.
176 *
177 * @return string The sanitized webfinger identifier.
178 */
179 public static function webfinger( $value ) {
180 $value = \str_replace( 'acct:', '', $value );
181 $value = \trim( $value, '@' );
182
183 return $value;
184 }
185
186 /**
187 * Sanitize content for ActivityPub.
188 *
189 * @param string $content The content to convert.
190 *
191 * @return string The converted content.
192 */
193 public static function content( $content ) {
194 // Only make URLs clickable if no anchor tags exist, to avoid corrupting existing links.
195 if ( false === \strpos( $content, '<a ' ) ) {
196 $content = \make_clickable( $content );
197 }
198
199 $content = \wpautop( $content );
200 $content = \wp_kses_post( $content );
201
202 return $content;
203 }
204
205 /**
206 * Strip whitespace between HTML tags.
207 *
208 * Removes newlines, carriage returns, and tabs that appear between HTML tags,
209 * preserving whitespace within text content and preformatted elements.
210 *
211 * @param string $content The content to process.
212 *
213 * @return string The content with whitespace between tags removed.
214 */
215 public static function strip_whitespace( $content ) {
216 return \trim( \preg_replace( '/>[\n\r\t]+</', '><', $content ) );
217 }
218
219 /**
220 * Clean HTML for ActivityPub federation.
221 *
222 * Keeps all WordPress allowed tags but removes global attributes like
223 * class, id, style, data-*, aria-* that increase payload size.
224 *
225 * @see https://github.com/Automattic/wordpress-activitypub/issues/2619
226 *
227 * @param string $content The HTML content to clean.
228 *
229 * @return string The cleaned HTML content.
230 */
231 public static function clean_html( $content ) {
232 if ( empty( $content ) ) {
233 return $content;
234 }
235
236 // Start with all WordPress allowed post tags.
237 $allowed_html = \wp_kses_allowed_html( 'post' );
238
239 // Global attributes to remove from all elements.
240 $remove_attrs = array(
241 'aria-controls',
242 'aria-current',
243 'aria-describedby',
244 'aria-details',
245 'aria-expanded',
246 'aria-hidden',
247 'aria-label',
248 'aria-labelledby',
249 'aria-live',
250 'class',
251 'data-*',
252 'decoding',
253 'dir',
254 'hidden',
255 'id',
256 'lang',
257 'loading',
258 'role',
259 'style',
260 'tabindex',
261 'title',
262 'xml:lang',
263 );
264
265 /**
266 * Filter the global attributes to remove from all elements.
267 *
268 * @param array $remove_attrs Global attributes to remove.
269 */
270 $remove_attrs = \apply_filters( 'activitypub_remove_html_attributes', $remove_attrs );
271
272 // Remove global attributes from all tags.
273 foreach ( $allowed_html as $tag => $attrs ) {
274 $allowed_html[ $tag ] = \array_diff_key( $attrs, \array_flip( $remove_attrs ) );
275 }
276
277 // Re-add class and title for anchors (needed for microformats).
278 $allowed_html['a']['class'] = true;
279 $allowed_html['a']['title'] = true;
280
281 // Re-add class for spans (needed for microformats).
282 $allowed_html['span']['class'] = true;
283
284 /**
285 * Filter the final allowed HTML for ActivityPub content.
286 *
287 * @param array $allowed_html The allowed HTML structure for wp_kses.
288 */
289 $allowed_html = \apply_filters( 'activitypub_allowed_html', $allowed_html );
290
291 return \wp_kses( $content, $allowed_html, \wp_allowed_protocols() );
292 }
293 }
294