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