ILIAS  release_7 Revision v7.30-3-g800a261c036
All Data Structures Namespaces Files Functions Variables Modules Pages
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';
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 }
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 The base interface for all filesystem streams.
Definition: FileStream.php:17
$i
Definition: metadata.php:24