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