ILIAS  release_8 Revision v8.24
SVGWhitelistPreProcessor.php
Go to the documentation of this file.
1<?php
2
20
24
33{
34 use IsMimeTypeOrExtension;
35
36 private const SVG_MIME_TYPE = 'image/svg+xml';
37 private const REGEX_SCRIPT = '/<script/m';
38 private const REGEX_BASE64 = '/data:.*;base64/m';
39 private const SVG = 'svg';
40 private string $rejection_message = 'The SVG file contains possibily malicious code.';
41 private string $ok_message = 'SVG OK';
42
43 public function __construct(?string $rejection_message = null)
44 {
45 $this->rejection_message = $rejection_message ?? $this->rejection_message;
46 }
47
48 private function isSVG(Metadata $metadata): bool
49 {
50 return $this->isMimeTypeOrExtension(
51 $metadata,
52 self::SVG,
53 [self::SVG_MIME_TYPE]
54 );
55 }
56
57 public function process(FileStream $stream, Metadata $metadata): ProcessingStatus
58 {
59 if ($this->isSVG($metadata) && !$this->checkStream($stream)) {
60 return new ProcessingStatus(ProcessingStatus::DENIED, $this->rejection_message);
61 }
62 return new ProcessingStatus(ProcessingStatus::OK, $this->ok_message);
63 }
64
65 protected function checkStream(FileStream $stream): bool
66 {
67 // Check all Elements and Attributes against a whitelist
68 // Credits to https://github.com/alnorris/SVG-Sanitizer
69 $all_elements = $dom->getElementsByTagName("*");
70
71 for ($i = 0; $i < $all_elements->length; $i++) {
72 $current_node = $all_elements->item($i);
73
74 $element_name = $original_element_name = $current_node->tagName;
75 $whitelist_attr_arr = self::$whitelist[$element_name] ?? null;
76
77 if ($whitelist_attr_arr !== null) {
78 for ($x = 0; $x < $current_node->attributes->length; $x++) {
79 $attribute_name = $current_node->attributes->item($x)->name;
80 if (!in_array($attribute_name, $whitelist_attr_arr)) {
81 $this->rejection_message = $this->rejection_message
82 . ' (' . $original_element_name
83 . '/' . $attribute_name . ').';
84 return false;
85 }
86 }
87 } else {
88 $this->rejection_message = $this->rejection_message
89 . ' (' . $original_element_name . ').';
90 return false;
91 }
92 }
93
94 return true;
95 }
96
97 private static $whitelist = [
98 'a' =>
99 [
100 'class',
101 'clip-path',
102 'clip-rule',
103 'fill',
104 'fill-opacity',
105 'fill-rule',
106 'filter',
107 'id',
108 'mask',
109 'opacity',
110 'stroke',
111 'stroke-dasharray',
112 'stroke-dashoffset',
113 'stroke-linecap',
114 'stroke-linejoin',
115 'stroke-miterlimit',
116 'stroke-opacity',
117 'stroke-width',
118 'style',
119 'systemLanguage',
120 'transform',
121 'href',
122 'xlink:href',
123 'xlink:title',
124 ],
125 'animate' => [
126 'attributeName',
127 'from',
128 'to',
129 'dur',
130 'repeatCount',
131 'begin'
132 ],
133 'circle' =>
134 [
135 'class',
136 'clip-path',
137 'clip-rule',
138 'cx',
139 'cy',
140 'fill',
141 'fill-opacity',
142 'fill-rule',
143 'filter',
144 'id',
145 'mask',
146 'opacity',
147 'r',
148 'requiredFeatures',
149 'stroke',
150 'stroke-dasharray',
151 'stroke-dashoffset',
152 'stroke-linecap',
153 'stroke-linejoin',
154 'stroke-miterlimit',
155 'stroke-opacity',
156 'stroke-width',
157 'style',
158 'systemLanguage',
159 'transform',
160 ],
161 'clipPath' =>
162 [
163 'class',
164 'clipPathUnits',
165 'id',
166 ],
167 'defs' =>
168 [
169 'id',
170 ],
171 'style' =>
172 [
173 'type',
174 ],
175 'desc' =>
176 [],
177 'ellipse' =>
178 [
179 'class',
180 'clip-path',
181 'clip-rule',
182 'cx',
183 'cy',
184 'fill',
185 'fill-opacity',
186 'fill-rule',
187 'filter',
188 'id',
189 'mask',
190 'opacity',
191 'requiredFeatures',
192 'rx',
193 'ry',
194 'stroke',
195 'stroke-dasharray',
196 'stroke-dashoffset',
197 'stroke-linecap',
198 'stroke-linejoin',
199 'stroke-miterlimit',
200 'stroke-opacity',
201 'stroke-width',
202 'style',
203 'systemLanguage',
204 'transform',
205 ],
206 'feGaussianBlur' =>
207 [
208 'class',
209 'color-interpolation-filters',
210 'id',
211 'requiredFeatures',
212 'stdDeviation',
213 ],
214 'filter' =>
215 [
216 'class',
217 'color-interpolation-filters',
218 'filterRes',
219 'filterUnits',
220 'height',
221 'id',
222 'primitiveUnits',
223 'requiredFeatures',
224 'width',
225 'x',
226 'xlink:href',
227 'y',
228 ],
229 'foreignObject' =>
230 [
231 'class',
232 'font-size',
233 'height',
234 'id',
235 'opacity',
236 'requiredFeatures',
237 'style',
238 'transform',
239 'width',
240 'x',
241 'y',
242 ],
243 'g' =>
244 [
245 'class',
246 'clip-path',
247 'clip-rule',
248 'id',
249 'display',
250 'fill',
251 'fill-opacity',
252 'fill-rule',
253 'filter',
254 'mask',
255 'opacity',
256 'extraneous',
257 'requiredFeatures',
258 'stroke',
259 'stroke-dasharray',
260 'stroke-dashoffset',
261 'stroke-linecap',
262 'stroke-linejoin',
263 'stroke-miterlimit',
264 'stroke-opacity',
265 'stroke-width',
266 'style',
267 'systemLanguage',
268 'transform',
269 'font-family',
270 'font-size',
271 'font-style',
272 'font-weight',
273 'text-anchor',
274 ],
275 'image' =>
276 [
277 'class',
278 'clip-path',
279 'clip-rule',
280 'filter',
281 'height',
282 'id',
283 'mask',
284 'opacity',
285 'requiredFeatures',
286 'style',
287 'systemLanguage',
288 'transform',
289 'width',
290 'x',
291 'xlink:href',
292 'xlink:title',
293 'y',
294 ],
295 'line' =>
296 [
297 'class',
298 'clip-path',
299 'clip-rule',
300 'fill',
301 'fill-opacity',
302 'fill-rule',
303 'filter',
304 'id',
305 'marker-end',
306 'marker-mid',
307 'marker-start',
308 'mask',
309 'opacity',
310 'requiredFeatures',
311 'stroke',
312 'stroke-dasharray',
313 'stroke-dashoffset',
314 'stroke-linecap',
315 'stroke-linejoin',
316 'stroke-miterlimit',
317 'stroke-opacity',
318 'stroke-width',
319 'style',
320 'systemLanguage',
321 'transform',
322 'x1',
323 'x2',
324 'y1',
325 'y2',
326 ],
327 'linearGradient' =>
328 [
329 'class',
330 'collect',
331 'href',
332 'id',
333 'gradientTransform',
334 'gradientUnits',
335 'requiredFeatures',
336 'spreadMethod',
337 'systemLanguage',
338 'x1',
339 'x2',
340 'xlink:href',
341 'y1',
342 'y2',
343 ],
344 'marker' =>
345 [
346 'id',
347 'class',
348 'markerHeight',
349 'markerUnits',
350 'markerWidth',
351 'orient',
352 'preserveAspectRatio',
353 'refX',
354 'refY',
355 'systemLanguage',
356 'viewBox',
357 ],
358 'mask' =>
359 [
360 'class',
361 'height',
362 'id',
363 'maskContentUnits',
364 'maskUnits',
365 'width',
366 'x',
367 'y',
368 ],
369 'metadata' =>
370 [
371 'class',
372 'id',
373 ],
374 'path' =>
375 [
376 'class',
377 'clip-path',
378 'clip-rule',
379 'd',
380 'cx',
381 'cy',
382 'rx',
383 'ry',
384 'fill',
385 'type',
386 'fill-opacity',
387 'fill-rule',
388 'filter',
389 'id',
390 'marker-end',
391 'marker-mid',
392 'marker-start',
393 'mask',
394 'opacity',
395 'requiredFeatures',
396 'stroke',
397 'stroke-dasharray',
398 'stroke-dashoffset',
399 'stroke-linecap',
400 'stroke-linejoin',
401 'stroke-miterlimit',
402 'stroke-opacity',
403 'stroke-width',
404 'style',
405 'systemLanguage',
406 'transform',
407 'connector-curvature',
408 ],
409 'pattern' =>
410 [
411 'class',
412 'height',
413 'id',
414 'patternContentUnits',
415 'patternTransform',
416 'patternUnits',
417 'requiredFeatures',
418 'style',
419 'systemLanguage',
420 'viewBox',
421 'width',
422 'x',
423 'xlink:href',
424 'y',
425 ],
426 'polygon' =>
427 [
428 'class',
429 'clip-path',
430 'clip-rule',
431 'id',
432 'fill',
433 'fill-opacity',
434 'fill-rule',
435 'filter',
436 'id',
437 'class',
438 'marker-end',
439 'marker-mid',
440 'marker-start',
441 'mask',
442 'opacity',
443 'points',
444 'requiredFeatures',
445 'stroke',
446 'stroke-dasharray',
447 'stroke-dashoffset',
448 'stroke-linecap',
449 'stroke-linejoin',
450 'stroke-miterlimit',
451 'stroke-opacity',
452 'stroke-width',
453 'style',
454 'systemLanguage',
455 'transform',
456 ],
457 'polyline' =>
458 [
459 'class',
460 'clip-path',
461 'clip-rule',
462 'id',
463 'fill',
464 'fill-opacity',
465 'fill-rule',
466 'filter',
467 'marker-end',
468 'marker-mid',
469 'marker-start',
470 'mask',
471 'opacity',
472 'points',
473 'requiredFeatures',
474 'stroke',
475 'stroke-dasharray',
476 'stroke-dashoffset',
477 'stroke-linecap',
478 'stroke-linejoin',
479 'stroke-miterlimit',
480 'stroke-opacity',
481 'stroke-width',
482 'style',
483 'systemLanguage',
484 'transform',
485 ],
486 'rdf:RDF' =>
487 [],
488 'cc' =>
489 [
490 'about',
491 ],
492 'dc' =>
493 [
494 'resource',
495 'title',
496 ],
497 'radialGradient' =>
498 [
499 'class',
500 'cx',
501 'cy',
502 'fx',
503 'fy',
504 'gradientTransform',
505 'gradientUnits',
506 'id',
507 'r',
508 'requiredFeatures',
509 'spreadMethod',
510 'systemLanguage',
511 'xlink:href',
512 ],
513 'rect' =>
514 [
515 'class',
516 'clip-path',
517 'clip-rule',
518 'fill',
519 'fill-opacity',
520 'fill-rule',
521 'filter',
522 'height',
523 'id',
524 'mask',
525 'opacity',
526 'requiredFeatures',
527 'rx',
528 'ry',
529 'stroke',
530 'stroke-dasharray',
531 'stroke-dashoffset',
532 'stroke-linecap',
533 'stroke-linejoin',
534 'stroke-miterlimit',
535 'stroke-opacity',
536 'stroke-width',
537 'style',
538 'systemLanguage',
539 'transform',
540 'width',
541 'x',
542 'y',
543 ],
544 'stop' =>
545 [
546 'class',
547 'id',
548 'offset',
549 'requiredFeatures',
550 'stop-color',
551 'stop-opacity',
552 'style',
553 'systemLanguage',
554 ],
555 'svg' =>
556 [
557 'class',
558 'clip-path',
559 'clip-rule',
560 'filter',
561 'id',
562 'height',
563 'fill',
564 'mask',
565 'preserveAspectRatio',
566 'requiredFeatures',
567 'style',
568 'systemLanguage',
569 'viewBox',
570 'width',
571 'version',
572 'docname',
573 'space',
574 'enable-background',
575 'x',
576 'baseProfile',
577 'xmlns',
578 'xmlns:se',
579 'xmlns:xlink',
580 'y',
581 ],
582 'switch' =>
583 [
584 'class',
585 'id',
586 'requiredFeatures',
587 'systemLanguage',
588 ],
589 'symbol' =>
590 [
591 'class',
592 'fill',
593 'fill-opacity',
594 'fill-rule',
595 'filter',
596 'font-family',
597 'font-size',
598 'font-style',
599 'font-weight',
600 'id',
601 'opacity',
602 'preserveAspectRatio',
603 'requiredFeatures',
604 'stroke',
605 'stroke-dasharray',
606 'stroke-dashoffset',
607 'stroke-linecap',
608 'stroke-linejoin',
609 'stroke-miterlimit',
610 'stroke-opacity',
611 'stroke-width',
612 'style',
613 'systemLanguage',
614 'transform',
615 'viewBox',
616 ],
617 'text' =>
618 [
619 'class',
620 'clip-path',
621 'clip-rule',
622 'fill',
623 'fill-opacity',
624 'fill-rule',
625 'filter',
626 'font-family',
627 'font-size',
628 'font-style',
629 'font-weight',
630 'id',
631 'mask',
632 'opacity',
633 'requiredFeatures',
634 'stroke',
635 'stroke-dasharray',
636 'stroke-dashoffset',
637 'stroke-linecap',
638 'stroke-linejoin',
639 'stroke-miterlimit',
640 'stroke-opacity',
641 'stroke-width',
642 'style',
643 'systemLanguage',
644 'text-anchor',
645 'transform',
646 'x',
647 'xml:space',
648 'y',
649 ],
650 'textPath' =>
651 [
652 'class',
653 'id',
654 'method',
655 'requiredFeatures',
656 'spacing',
657 'startOffset',
658 'style',
659 'systemLanguage',
660 'transform',
661 'xlink:href',
662 ],
663 'title' =>
664 [],
665 'tspan' =>
666 [
667 'class',
668 'clip-path',
669 'clip-rule',
670 'dx',
671 'dy',
672 'fill',
673 'fill-opacity',
674 'fill-rule',
675 'filter',
676 'font-family',
677 'font-size',
678 'font-style',
679 'font-weight',
680 'id',
681 'mask',
682 'opacity',
683 'requiredFeatures',
684 'rotate',
685 'stroke',
686 'stroke-dasharray',
687 'stroke-dashoffset',
688 'stroke-linecap',
689 'stroke-linejoin',
690 'stroke-miterlimit',
691 'stroke-opacity',
692 'stroke-width',
693 'style',
694 'systemLanguage',
695 'text-anchor',
696 'textLength',
697 'transform',
698 'x',
699 'xml:space',
700 'y',
701 ],
702 'sodipodi' =>
703 [
704 'pagecolor',
705 'bordercolor',
706 'borderopacity',
707 'objecttolerance',
708 ],
709 'use' =>
710 [
711 'class',
712 'clip-path',
713 'clip-rule',
714 'fill',
715 'fill-opacity',
716 'fill-rule',
717 'filter',
718 'height',
719 'id',
720 'href',
721 'overflow',
722 'mask',
723 'stroke',
724 'stroke-dasharray',
725 'stroke-dashoffset',
726 'stroke-linecap',
727 'stroke-linejoin',
728 'stroke-miterlimit',
729 'stroke-opacity',
730 'stroke-width',
731 'style',
732 'transform',
733 'width',
734 'x',
735 'xlink:href',
736 'y',
737 ],
738 'sodipodi:namedview' =>
739 [
740 'gridtolerance',
741 'guidetolerance',
742 'pageopacity',
743 'pageshadow',
744 'window-width',
745 'window-height',
746 'id',
747 'showgrid',
748 'zoom',
749 'cx',
750 'cy',
751 'window-x',
752 'window-y',
753 'window-maximized',
754 'current-layer',
755 'gridtolerance',
756 'guidetolerance',
757 'pageopacity',
758 'pageshadow',
759 'window-width',
760 'window-height',
761 'id',
762 'showgrid',
763 'zoom',
764 'cx',
765 'cy',
766 'window-x',
767 'window-y',
768 'window-maximized',
769 'current-layer',
770 'gridtolerance',
771 'guidetolerance',
772 'pageopacity',
773 'pageshadow',
774 'window-width',
775 'window-height',
776 'id',
777 'showgrid',
778 'zoom',
779 'cx',
780 'cy',
781 'window-x',
782 'window-y',
783 'window-maximized',
784 'current-layer',
785 'pagecolor',
786 'bordercolor',
787 'borderopacity',
788 'objecttolerance',
789 'fit-margin-bottom',
790 'fit-margin-right',
791 'fit-margin-left',
792 'fit-margin-top',
793 ],
794 'cc:Work' =>
795 [
796 "about"
797 ],
798 'dc:format' =>
799 [],
800 'dc:type' =>
801 [
802 "resource"
803 ],
804 'dc:title' =>
805 [
806 ""
807 ],
808 ];
809}
const DENIED
Upload got denied by a processor, the upload will be removed immediately.
process(FileStream $stream, Metadata $metadata)
This method gets invoked by the file upload service to process the file with the help of the processo...
$i
Definition: metadata.php:41
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...