PluginProbe
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses / 2.10.01
FluentCommunity – Ultra-Fast High-Performance Social Network, Community, LMS & Online Courses v2.10.01
2.11.0 2.10.0 2.10.01 2.9.1 2.9.0 2.8.1 2.8.0 2.7.7 2.7.5 2.7.0 2.6.01 2.6.0 2.5.0 2.4.01 trunk 1.0.90 1.0.91 1.0.92 1.0.93 1.0.94 1.0.95 1.0.96 1.0.97 1.0.98 1.0.99 All 78 releases
← All changes | app/Services/CustomSanitizer.php +47 -16 2.4.012.10.01 View file →
@@ -26,9 +26,9 @@
26 26 $item[$key] = sanitize_text_field($item[$key]);
27 27 }
28 28 }
29 29
30 - $item['permalink'] = sanitize_url($item['permalink']);
30 + $item['permalink'] = sanitize_url(Arr::get($item, 'permalink', ''));
31 31
32 32 if (!empty($item['shape_svg'])) {
33 33 $item['shape_svg'] = self::sanitizeSvg($item['shape_svg']);
34 34 }
@@ -119,8 +119,9 @@
119 119
120 120 private static function sanitizeNode(\DOMNode $node, array $allowed_tags)
121 121 {
122 122 if ($node->nodeType === XML_ELEMENT_NODE) {
123 + /** @var \DOMElement $node */
123 124 if (!isset($allowed_tags[$node->nodeName])) {
124 125 $node->parentNode->removeChild($node);
125 126 return;
126 127 }
@@ -168,10 +169,12 @@
168 169 }
169 170 return '';
170 171 }
171 172
172 - public static function sanitizeWelcomeBannerSettings($settings)
173 + public static function sanitizeWelcomeBannerSettings($settings, $views = ['login', 'logout'])
173 174 {
175 + $views = array_intersect($views, ['login', 'logout', 'enrolled', 'not_enrolled']);
176 +
174 177 $rules = [
175 178 'title' => 'sanitize_text_field',
176 179 'description' => 'wp_kses_post',
177 180 'mediaType' => 'sanitize_text_field',
@@ -179,9 +182,9 @@
179 182 'enabled' => 'sanitize_text_field',
180 183 ];
181 184
182 185 $sanitizedSettings = [];
183 - foreach (['login', 'logout'] as $type) {
186 + foreach ($views as $type) {
184 187 $typeSettings = Arr::get($settings, $type, []);
185 188 if (empty($typeSettings)) {
186 189 continue;
187 190 }
@@ -192,12 +195,17 @@
192 195
193 196 $sanitizedSettings[$type]['bannerVideo'] = self::sanitizeBannerVideo($bannerVideo);
194 197 $sanitizedSettings[$type]['bannerImage'] = self::sanitizeBannerImage($bannerImage);
195 198 $sanitizedSettings[$type]['ctaButtons'] = self::sanitizeCtaButtons($ctaButtons);
196 - $sanitizedSettings[$type]['description'] = Arr::get($typeSettings, 'description');
197 199
200 + $description = Arr::get($typeSettings, 'description');
201 + if (!empty($description)) {
202 + $description = wp_kses_post(self::unslashMarkdown(wp_unslash($description)));
203 + }
204 + $sanitizedSettings[$type]['description'] = $description;
205 +
198 206 foreach ($typeSettings as $key => $value) {
199 - if (isset($rules[$key]) && !in_array($key, ['bannerVideo', 'bannerImage', 'ctaButtons'])) {
207 + if (isset($rules[$key]) && !in_array($key, ['bannerVideo', 'bannerImage', 'ctaButtons', 'description'])) {
200 208 $sanitizedSettings[$type][$key] = call_user_func($rules[$key], $value);
201 209 }
202 210 }
203 211 }
@@ -214,12 +222,13 @@
214 222 return array_filter([
215 223 'type' => sanitize_text_field(Arr::get($video, 'type', '')),
216 224 'url' => sanitize_url(Arr::get($video, 'url', '')),
217 225 'content_type' => sanitize_text_field(Arr::get($video, 'content_type', '')),
218 - 'provider' => sanitize_url(Arr::get($video, 'provider', '')),
226 + 'provider' => sanitize_text_field(Arr::get($video, 'provider', '')),
219 227 'title' => sanitize_text_field(Arr::get($video, 'title', '')),
220 228 'author_name' => sanitize_text_field(Arr::get($video, 'author_name', '')),
221 229 'html' => self::sanitizeRichText(Arr::get($video, 'html', '')),
230 + 'image' => sanitize_url(Arr::get($video, 'image', '')),
222 231 ]);
223 232 }
224 233
225 234 private static function sanitizeBannerImage($imageUrl)
@@ -248,9 +257,11 @@
248 257 }
249 258
250 259 $sanitizerMap = [
251 260 'label' => 'sanitize_text_field',
252 - 'link' => 'sanitize_url',
261 + 'link' => function ($url) {
262 + return esc_url_raw($url, ['http', 'https', 'mailto']);
263 + },
253 264 'type' => 'sanitize_text_field',
254 265 'newTab' => 'sanitize_text_field'
255 266 ];
256 267
@@ -279,9 +290,9 @@
279 290
280 291 $item['emoji'] = self::sanitizeEmoji(Arr::get($item, 'emoji'));
281 292
282 293 if (empty($item['slug'])) {
283 - $item['slug'] = sanitize_title($item['title']);
294 + $item['slug'] = sanitize_title(Arr::get($item, 'title', ''));
284 295 } else {
285 296 $item['slug'] = sanitize_title($item['slug']);
286 297 }
287 298
@@ -290,9 +301,9 @@
290 301 if (isset($item[$key])) {
291 302 $item[$key] = sanitize_text_field($item[$key]);
292 303 }
293 304 }
294 - $item['permalink'] = sanitize_url($item['permalink']);
305 + $item['permalink'] = sanitize_url(Arr::get($item, 'permalink', ''));
295 306
296 307
297 308 if (!empty($item['icon_image'])) {
298 309 $media = Helper::getMediaFromUrl($item['icon_image']);
@@ -310,9 +321,13 @@
310 321
311 322 if (!empty($item['icon_svg'])) {
312 323 $item['icon_svg'] = self::sanitizeSvg($item['icon_svg']);
313 324 }
314 -
325 +
326 + if (!empty($item['shape_svg'])) {
327 + $item['shape_svg'] = self::sanitizeSvg($item['shape_svg']);
328 + }
329 +
315 330 if (Arr::get($item, 'privacy') == 'members_only') {
316 331 $item['membership_ids'] = array_map('sanitize_text_field', (array)Arr::get($item, 'membership_ids', []));
317 332 }
318 333
@@ -343,18 +358,22 @@
343 358 return $html;
344 359 }
345 360
346 361 $tags = wp_kses_allowed_html('post');
347 - $tags['style'] = [
348 - 'types' => [],
349 - ];
350 362
351 - // iframe
363 + // No <style> element: kses filters style="" attributes but never the text content
364 + // of a <style> block, so allowing it would let any role that can author this markup
365 + // persist CSS (@import, attribute-selector data exfiltration, UI redress) against
366 + // every viewer. Embed/media HTML never needs a <style> element.
367 +
368 + // iframe. Note there is deliberately no 'srcdoc' here: a srcdoc iframe without a
369 + // sandbox attribute is same-origin with the portal, so allowing it would let any
370 + // role that can author embed markup run script against every viewer. Real embed
371 + // providers only ever use src.
352 372 $tags['iframe'] = [
353 373 'width' => [],
354 374 'height' => [],
355 375 'src' => [],
356 - 'srcdoc' => [],
357 376 'title' => [],
358 377 'frameborder' => [],
359 378 'allow' => [],
360 379 'class' => [],
@@ -360,9 +379,8 @@
360 379 'class' => [],
361 380 'id' => [],
362 381 'allowfullscreen' => [],
363 382 'referrerpolicy' => [],
364 - 'style' => [],
365 383 ];
366 384
367 385 $tags = apply_filters('fluent_community/allowed_html_tags', $tags);
368 386
@@ -464,13 +482,15 @@
464 482 public static function santizeSpaceSettings($settings = [], $privacy = 'public')
465 483 {
466 484 $yesNotFields = [
467 485 'restricted_post_only',
486 + 'verified_post_only',
468 487 'can_request_join',
469 488 'show_paywalls',
470 489 'show_sidebar',
471 490 'hide_members_count',
472 491 'document_library',
492 + 'media_gallery',
473 493 'disable_post_sort_by',
474 494 'disable_layout_style'
475 495 ];
476 496
@@ -508,8 +528,19 @@
508 528
509 529 $validCommentOrderOptions = array_keys(Helper::getCommentOrderOptions());
510 530 $defaultCommentOrder = Arr::get($settings, 'default_comment_sort_by', '');
511 531 $settings['default_comment_sort_by'] = in_array($defaultCommentOrder, $validCommentOrderOptions) ? $defaultCommentOrder : '';
532 +
533 + $accessOptions = ['members_only', 'logged_in', 'everybody'];
534 + $mediaAccess = Arr::get($settings, 'media_access');
535 + $settings['media_access'] = in_array($mediaAccess, $accessOptions, true) ? $mediaAccess : 'members_only';
536 +
537 + $documentAccess = Arr::get($settings, 'document_access');
538 + $settings['document_access'] = in_array($documentAccess, $accessOptions, true) ? $documentAccess : 'members_only';
539 +
540 + $documentUploadOptions = ['admin_only', 'members_only'];
541 + $documentUpload = Arr::get($settings, 'document_upload');
542 + $settings['document_upload'] = in_array($documentUpload, $documentUploadOptions, true) ? $documentUpload : 'admin_only';
512 543
513 544 return $settings;
514 545 }
515 546