ILIAS  release_8 Revision v8.23
SVGWhitelistPreProcessor.php
Go to the documentation of this file.
1 <?php
2 
20 
24 
32 final class SVGWhitelistPreProcessor implements PreProcessor
33 {
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 }
process(FileStream $stream, Metadata $metadata)
This method gets invoked by the file upload service to process the file with the help of the processo...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
const DENIED
Upload got denied by a processor, the upload will be removed immediately.
Interface FileStream.
Definition: FileStream.php:33
$i
Definition: metadata.php:41