From 57b5b68f7e5693236ed838f35a8ce5209949633a Mon Sep 17 00:00:00 2001
From: Oliver Hader <oliver@typo3.org>
Date: Tue, 16 Mar 2021 10:04:20 +0100
Subject: [PATCH] [SECURITY] Mitigate directly accessible file upload in form
 framework

File handling implementation in `UploadedFileReferenceConverter` of
`ext:form` creates files in `/fileadmin/user_uploads/` whenever some
Extbase controller is (implicitly) dealing with `FileReference` models,
unless particular implementations assign specific type converters or
register type converters having a higher processing priority.

As a side-effect this could lead to by-passing mime-type validators,
allowing to plant cross-site scripting and other malicious binaries
to public accessible `/fileadmin/` storage. PHP files and similar are
blocked since `fileDenyPattern` rule is active in any case.

This change makes the usage of `UploadedFileReferenceConverter` more
specific in the scope of processing contact forms with `ext:form`

* use random folder names for files, `.../form_abcde12345/image.png`
* removes `UploadedFileReferenceConverter` from being used implicitly
  by other Extbase implementations dealing with `FileReference` models

`PseudoFileReference` has been introduced to limit properties being
serialized to `uid` (in case it's a real file reference) or `uidLocal`
(in case it's a transient reference, pointing to a file).

Direct URLs to uploaded files are substituted by `fileDump` eID script
now, enforcing corresponding FAL mime-type and denying the web server
from guessing/interpreting a different mime-type based on file suffix.

A unique form `__session` value has been introduce, serving as seed
to derive for instance mentioned folder names for uploaded files. In
addition to that, form `__state` is only parsed when having been
submitted via expected `FormFrontendController::performAction`.

Resolves: #92136
Releases: master, 11.1, 10.4, 9.5
Change-Id: I7c33803443a68d6b3c895ec74da802a70bd390c1
Security-Bulletin: TYPO3-CORE-SA-2021-002
Security-References: CVE-2021-21355
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/68435
Tested-by: Oliver Hader <oliver.hader@typo3.org>
Reviewed-by: Oliver Hader <oliver.hader@typo3.org>
---
 Build/Scripts/generateMimeTypes.php           |  82 ++
 Build/package.json                            |   1 +
 Build/yarn.lock                               |   5 +
 .../core/Classes/Resource/FileReference.php   |   6 +-
 .../Classes/Resource/MimeTypeCollection.php   | 993 ++++++++++++++++++
 .../Classes/Resource/MimeTypeDetector.php     |  60 ++
 .../Finishers/DeleteUploadsFinisher.php       |  39 +
 .../Classes/Domain/Runtime/FormRuntime.php    | 117 ++-
 .../Runtime/FormRuntime/FormSession.php       |  94 ++
 .../AfterFormStateInitializedInterface.php    |  34 +
 .../Property/PropertyMappingConfiguration.php |  64 +-
 .../Mvc/Property/TypeConverter/PseudoFile.php | 106 ++
 .../TypeConverter/PseudoFileReference.php     |  86 ++
 .../UploadedFileReferenceConverter.php        | 112 +-
 .../Mvc/Validation/FileSizeValidator.php      |  12 +-
 .../Mvc/Validation/MimeTypeValidator.php      |  38 +-
 .../Classes/Slot/ResourcePublicationSlot.php  |  83 ++
 .../Classes/ViewHelpers/FormViewHelper.php    |  42 +-
 typo3/sysext/form/Configuration/Services.yaml |   7 +
 .../Resources/Private/Language/locallang.xlf  |   3 +
 .../Mvc/Validation/MimeTypeValidatorTest.php  | 143 +++
 .../PropertyMappingConfigurationTest.php      |  21 +
 .../Mvc/Validation/MimeTypeValidatorTest.php  |  39 +
 typo3/sysext/form/ext_localconf.php           |   5 +-
 24 files changed, 2139 insertions(+), 53 deletions(-)
 create mode 100755 Build/Scripts/generateMimeTypes.php
 create mode 100644 typo3/sysext/core/Classes/Resource/MimeTypeCollection.php
 create mode 100644 typo3/sysext/core/Classes/Resource/MimeTypeDetector.php
 create mode 100644 typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/FormSession.php
 create mode 100644 typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/Lifecycle/AfterFormStateInitializedInterface.php
 create mode 100644 typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFile.php
 create mode 100644 typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFileReference.php
 create mode 100644 typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php
 create mode 100644 typo3/sysext/form/Tests/Functional/Mvc/Validation/MimeTypeValidatorTest.php

diff --git a/Build/Scripts/generateMimeTypes.php b/Build/Scripts/generateMimeTypes.php
new file mode 100755
index 000000000000..fa0530785b79
--- /dev/null
+++ b/Build/Scripts/generateMimeTypes.php
@@ -0,0 +1,82 @@
+#!/usr/bin/env php
+<?php
+declare(strict_types=1);
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+$dbJson = file_get_contents(dirname(__DIR__) . '/node_modules/mime-db/db.json');
+$dbJson = json_decode($dbJson, true);
+
+$mimeTypeMapping = [];
+$mimeTypeString = '';
+foreach ($dbJson as $mimeType => $mimeTypeInfo) {
+    if (isset($mimeTypeInfo['extensions'])) {
+        $mimeTypeMapping[$mimeType] = $mimeTypeInfo['extensions'];
+    }
+}
+
+// @todo: add our own file extensions here
+
+foreach ($mimeTypeMapping as $mimeType => $extensionInfo) {
+    $mimeTypeString .= "        '" . $mimeType . "' => ['" . implode("', '", $extensionInfo) . "'],
+";
+}
+
+$classTemplate = '<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Resource;
+
+/**
+ * This class contains a list of all available / known mimetypes and file extensions,
+ * and is automatically generated by TYPO3 via Core/Build/Scripts/generateMimeTypes.php
+ */
+final class MimeTypeCollection
+{
+    private $map = [
+' . rtrim($mimeTypeString, ',') .
+'   ];
+
+    /**
+     * @return array<string, List<string>>
+     */
+    public function getMap(): array
+    {
+        return $this->map;
+    }
+
+    /**
+     * @return List<string>
+     */
+    public function getMimeTypes(): array
+    {
+        return array_keys($this->map);
+    }
+}
+';
+
+file_put_contents(dirname(dirname(__DIR__)) . '/typo3/sysext/core/Classes/Resource/MimeTypeCollection.php', $classTemplate);
diff --git a/Build/package.json b/Build/package.json
index fa1dd286b40f..12cfdb12419a 100644
--- a/Build/package.json
+++ b/Build/package.json
@@ -56,6 +56,7 @@
     "karma-opera-launcher": "^1.0.0",
     "karma-requirejs": "^1.1.0",
     "karma-safari-launcher": "^1.0.0",
+    "mime-db": "^1.46.0",
     "node-sass": "^4.14.1",
     "patch-package": "^6.2.2",
     "postcss-banner": "^3.0.2",
diff --git a/Build/yarn.lock b/Build/yarn.lock
index 57f550aadb7c..a15a0fc45842 100644
--- a/Build/yarn.lock
+++ b/Build/yarn.lock
@@ -5528,6 +5528,11 @@ mime-db@^1.28.0:
   resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
   integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
 
+mime-db@^1.46.0:
+  version "1.46.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee"
+  integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==
+
 mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
   version "2.1.27"
   resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
diff --git a/typo3/sysext/core/Classes/Resource/FileReference.php b/typo3/sysext/core/Classes/Resource/FileReference.php
index 68c8dfc8da72..c25d9d260b30 100644
--- a/typo3/sysext/core/Classes/Resource/FileReference.php
+++ b/typo3/sysext/core/Classes/Resource/FileReference.php
@@ -516,14 +516,16 @@ class FileReference implements FileInterface
     public function __sleep(): array
     {
         $keys = get_object_vars($this);
-        unset($keys['originalFile']);
+        unset($keys['originalFile'], $keys['mergedProperties']);
         return array_keys($keys);
     }
 
     public function __wakeup(): void
     {
+        $factory = GeneralUtility::makeInstance(ResourceFactory::class);
         $this->originalFile = $this->getFileObject(
-            (int)$this->propertiesOfFileReference['uid_local']
+            (int)$this->propertiesOfFileReference['uid_local'],
+            $factory
         );
     }
 }
diff --git a/typo3/sysext/core/Classes/Resource/MimeTypeCollection.php b/typo3/sysext/core/Classes/Resource/MimeTypeCollection.php
new file mode 100644
index 000000000000..36293f392cfd
--- /dev/null
+++ b/typo3/sysext/core/Classes/Resource/MimeTypeCollection.php
@@ -0,0 +1,993 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Resource;
+
+/**
+ * This class contains a list of all available / known mimetypes and file extensions,
+ * and is automatically generated by TYPO3 via Core/Build/Scripts/generateMimeTypes.php
+ */
+final class MimeTypeCollection
+{
+    private $map = [
+        'application/andrew-inset' => ['ez'],
+        'application/applixware' => ['aw'],
+        'application/atom+xml' => ['atom'],
+        'application/atomcat+xml' => ['atomcat'],
+        'application/atomdeleted+xml' => ['atomdeleted'],
+        'application/atomsvc+xml' => ['atomsvc'],
+        'application/atsc-dwd+xml' => ['dwd'],
+        'application/atsc-held+xml' => ['held'],
+        'application/atsc-rsat+xml' => ['rsat'],
+        'application/bdoc' => ['bdoc'],
+        'application/calendar+xml' => ['xcs'],
+        'application/ccxml+xml' => ['ccxml'],
+        'application/cdfx+xml' => ['cdfx'],
+        'application/cdmi-capability' => ['cdmia'],
+        'application/cdmi-container' => ['cdmic'],
+        'application/cdmi-domain' => ['cdmid'],
+        'application/cdmi-object' => ['cdmio'],
+        'application/cdmi-queue' => ['cdmiq'],
+        'application/cu-seeme' => ['cu'],
+        'application/dash+xml' => ['mpd'],
+        'application/davmount+xml' => ['davmount'],
+        'application/docbook+xml' => ['dbk'],
+        'application/dssc+der' => ['dssc'],
+        'application/dssc+xml' => ['xdssc'],
+        'application/ecmascript' => ['ecma', 'es'],
+        'application/emma+xml' => ['emma'],
+        'application/emotionml+xml' => ['emotionml'],
+        'application/epub+zip' => ['epub'],
+        'application/exi' => ['exi'],
+        'application/fdt+xml' => ['fdt'],
+        'application/font-tdpfr' => ['pfr'],
+        'application/geo+json' => ['geojson'],
+        'application/gml+xml' => ['gml'],
+        'application/gpx+xml' => ['gpx'],
+        'application/gxf' => ['gxf'],
+        'application/gzip' => ['gz'],
+        'application/hjson' => ['hjson'],
+        'application/hyperstudio' => ['stk'],
+        'application/inkml+xml' => ['ink', 'inkml'],
+        'application/ipfix' => ['ipfix'],
+        'application/its+xml' => ['its'],
+        'application/java-archive' => ['jar', 'war', 'ear'],
+        'application/java-serialized-object' => ['ser'],
+        'application/java-vm' => ['class'],
+        'application/javascript' => ['js', 'mjs'],
+        'application/json' => ['json', 'map'],
+        'application/json5' => ['json5'],
+        'application/jsonml+json' => ['jsonml'],
+        'application/ld+json' => ['jsonld'],
+        'application/lgr+xml' => ['lgr'],
+        'application/lost+xml' => ['lostxml'],
+        'application/mac-binhex40' => ['hqx'],
+        'application/mac-compactpro' => ['cpt'],
+        'application/mads+xml' => ['mads'],
+        'application/manifest+json' => ['webmanifest'],
+        'application/marc' => ['mrc'],
+        'application/marcxml+xml' => ['mrcx'],
+        'application/mathematica' => ['ma', 'nb', 'mb'],
+        'application/mathml+xml' => ['mathml'],
+        'application/mbox' => ['mbox'],
+        'application/mediaservercontrol+xml' => ['mscml'],
+        'application/metalink+xml' => ['metalink'],
+        'application/metalink4+xml' => ['meta4'],
+        'application/mets+xml' => ['mets'],
+        'application/mmt-aei+xml' => ['maei'],
+        'application/mmt-usd+xml' => ['musd'],
+        'application/mods+xml' => ['mods'],
+        'application/mp21' => ['m21', 'mp21'],
+        'application/mp4' => ['mp4s', 'm4p'],
+        'application/mrb-consumer+xml' => ['xdf'],
+        'application/mrb-publish+xml' => ['xdf'],
+        'application/msword' => ['doc', 'dot'],
+        'application/mxf' => ['mxf'],
+        'application/n-quads' => ['nq'],
+        'application/n-triples' => ['nt'],
+        'application/node' => ['cjs'],
+        'application/octet-stream' => ['bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy', 'exe', 'dll', 'deb', 'dmg', 'iso', 'img', 'msi', 'msp', 'msm', 'buffer'],
+        'application/oda' => ['oda'],
+        'application/oebps-package+xml' => ['opf'],
+        'application/ogg' => ['ogx'],
+        'application/omdoc+xml' => ['omdoc'],
+        'application/onenote' => ['onetoc', 'onetoc2', 'onetmp', 'onepkg'],
+        'application/oxps' => ['oxps'],
+        'application/p2p-overlay+xml' => ['relo'],
+        'application/patch-ops-error+xml' => ['xer'],
+        'application/pdf' => ['pdf'],
+        'application/pgp-encrypted' => ['pgp'],
+        'application/pgp-signature' => ['asc', 'sig'],
+        'application/pics-rules' => ['prf'],
+        'application/pkcs10' => ['p10'],
+        'application/pkcs7-mime' => ['p7m', 'p7c'],
+        'application/pkcs7-signature' => ['p7s'],
+        'application/pkcs8' => ['p8'],
+        'application/pkix-attr-cert' => ['ac'],
+        'application/pkix-cert' => ['cer'],
+        'application/pkix-crl' => ['crl'],
+        'application/pkix-pkipath' => ['pkipath'],
+        'application/pkixcmp' => ['pki'],
+        'application/pls+xml' => ['pls'],
+        'application/postscript' => ['ai', 'eps', 'ps'],
+        'application/provenance+xml' => ['provx'],
+        'application/prs.cww' => ['cww'],
+        'application/pskc+xml' => ['pskcxml'],
+        'application/raml+yaml' => ['raml'],
+        'application/rdf+xml' => ['rdf', 'owl'],
+        'application/reginfo+xml' => ['rif'],
+        'application/relax-ng-compact-syntax' => ['rnc'],
+        'application/resource-lists+xml' => ['rl'],
+        'application/resource-lists-diff+xml' => ['rld'],
+        'application/rls-services+xml' => ['rs'],
+        'application/route-apd+xml' => ['rapd'],
+        'application/route-s-tsid+xml' => ['sls'],
+        'application/route-usd+xml' => ['rusd'],
+        'application/rpki-ghostbusters' => ['gbr'],
+        'application/rpki-manifest' => ['mft'],
+        'application/rpki-roa' => ['roa'],
+        'application/rsd+xml' => ['rsd'],
+        'application/rss+xml' => ['rss'],
+        'application/rtf' => ['rtf'],
+        'application/sbml+xml' => ['sbml'],
+        'application/scvp-cv-request' => ['scq'],
+        'application/scvp-cv-response' => ['scs'],
+        'application/scvp-vp-request' => ['spq'],
+        'application/scvp-vp-response' => ['spp'],
+        'application/sdp' => ['sdp'],
+        'application/senml+xml' => ['senmlx'],
+        'application/sensml+xml' => ['sensmlx'],
+        'application/set-payment-initiation' => ['setpay'],
+        'application/set-registration-initiation' => ['setreg'],
+        'application/shf+xml' => ['shf'],
+        'application/sieve' => ['siv', 'sieve'],
+        'application/smil+xml' => ['smi', 'smil'],
+        'application/sparql-query' => ['rq'],
+        'application/sparql-results+xml' => ['srx'],
+        'application/srgs' => ['gram'],
+        'application/srgs+xml' => ['grxml'],
+        'application/sru+xml' => ['sru'],
+        'application/ssdl+xml' => ['ssdl'],
+        'application/ssml+xml' => ['ssml'],
+        'application/swid+xml' => ['swidtag'],
+        'application/tei+xml' => ['tei', 'teicorpus'],
+        'application/thraud+xml' => ['tfi'],
+        'application/timestamped-data' => ['tsd'],
+        'application/toml' => ['toml'],
+        'application/ttml+xml' => ['ttml'],
+        'application/ubjson' => ['ubj'],
+        'application/urc-ressheet+xml' => ['rsheet'],
+        'application/urc-targetdesc+xml' => ['td'],
+        'application/vnd.1000minds.decision-model+xml' => ['1km'],
+        'application/vnd.3gpp.pic-bw-large' => ['plb'],
+        'application/vnd.3gpp.pic-bw-small' => ['psb'],
+        'application/vnd.3gpp.pic-bw-var' => ['pvb'],
+        'application/vnd.3gpp2.tcap' => ['tcap'],
+        'application/vnd.3m.post-it-notes' => ['pwn'],
+        'application/vnd.accpac.simply.aso' => ['aso'],
+        'application/vnd.accpac.simply.imp' => ['imp'],
+        'application/vnd.acucobol' => ['acu'],
+        'application/vnd.acucorp' => ['atc', 'acutc'],
+        'application/vnd.adobe.air-application-installer-package+zip' => ['air'],
+        'application/vnd.adobe.formscentral.fcdt' => ['fcdt'],
+        'application/vnd.adobe.fxp' => ['fxp', 'fxpl'],
+        'application/vnd.adobe.xdp+xml' => ['xdp'],
+        'application/vnd.adobe.xfdf' => ['xfdf'],
+        'application/vnd.ahead.space' => ['ahead'],
+        'application/vnd.airzip.filesecure.azf' => ['azf'],
+        'application/vnd.airzip.filesecure.azs' => ['azs'],
+        'application/vnd.amazon.ebook' => ['azw'],
+        'application/vnd.americandynamics.acc' => ['acc'],
+        'application/vnd.amiga.ami' => ['ami'],
+        'application/vnd.android.package-archive' => ['apk'],
+        'application/vnd.anser-web-certificate-issue-initiation' => ['cii'],
+        'application/vnd.anser-web-funds-transfer-initiation' => ['fti'],
+        'application/vnd.antix.game-component' => ['atx'],
+        'application/vnd.apple.installer+xml' => ['mpkg'],
+        'application/vnd.apple.keynote' => ['key'],
+        'application/vnd.apple.mpegurl' => ['m3u8'],
+        'application/vnd.apple.numbers' => ['numbers'],
+        'application/vnd.apple.pages' => ['pages'],
+        'application/vnd.apple.pkpass' => ['pkpass'],
+        'application/vnd.aristanetworks.swi' => ['swi'],
+        'application/vnd.astraea-software.iota' => ['iota'],
+        'application/vnd.audiograph' => ['aep'],
+        'application/vnd.balsamiq.bmml+xml' => ['bmml'],
+        'application/vnd.blueice.multipass' => ['mpm'],
+        'application/vnd.bmi' => ['bmi'],
+        'application/vnd.businessobjects' => ['rep'],
+        'application/vnd.chemdraw+xml' => ['cdxml'],
+        'application/vnd.chipnuts.karaoke-mmd' => ['mmd'],
+        'application/vnd.cinderella' => ['cdy'],
+        'application/vnd.citationstyles.style+xml' => ['csl'],
+        'application/vnd.claymore' => ['cla'],
+        'application/vnd.cloanto.rp9' => ['rp9'],
+        'application/vnd.clonk.c4group' => ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'],
+        'application/vnd.cluetrust.cartomobile-config' => ['c11amc'],
+        'application/vnd.cluetrust.cartomobile-config-pkg' => ['c11amz'],
+        'application/vnd.commonspace' => ['csp'],
+        'application/vnd.contact.cmsg' => ['cdbcmsg'],
+        'application/vnd.cosmocaller' => ['cmc'],
+        'application/vnd.crick.clicker' => ['clkx'],
+        'application/vnd.crick.clicker.keyboard' => ['clkk'],
+        'application/vnd.crick.clicker.palette' => ['clkp'],
+        'application/vnd.crick.clicker.template' => ['clkt'],
+        'application/vnd.crick.clicker.wordbank' => ['clkw'],
+        'application/vnd.criticaltools.wbs+xml' => ['wbs'],
+        'application/vnd.ctc-posml' => ['pml'],
+        'application/vnd.cups-ppd' => ['ppd'],
+        'application/vnd.curl.car' => ['car'],
+        'application/vnd.curl.pcurl' => ['pcurl'],
+        'application/vnd.dart' => ['dart'],
+        'application/vnd.data-vision.rdz' => ['rdz'],
+        'application/vnd.dbf' => ['dbf'],
+        'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'],
+        'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'],
+        'application/vnd.dece.unspecified' => ['uvx', 'uvvx'],
+        'application/vnd.dece.zip' => ['uvz', 'uvvz'],
+        'application/vnd.denovo.fcselayout-link' => ['fe_launch'],
+        'application/vnd.dna' => ['dna'],
+        'application/vnd.dolby.mlp' => ['mlp'],
+        'application/vnd.dpgraph' => ['dpg'],
+        'application/vnd.dreamfactory' => ['dfac'],
+        'application/vnd.ds-keypoint' => ['kpxx'],
+        'application/vnd.dvb.ait' => ['ait'],
+        'application/vnd.dvb.service' => ['svc'],
+        'application/vnd.dynageo' => ['geo'],
+        'application/vnd.ecowin.chart' => ['mag'],
+        'application/vnd.enliven' => ['nml'],
+        'application/vnd.epson.esf' => ['esf'],
+        'application/vnd.epson.msf' => ['msf'],
+        'application/vnd.epson.quickanime' => ['qam'],
+        'application/vnd.epson.salt' => ['slt'],
+        'application/vnd.epson.ssf' => ['ssf'],
+        'application/vnd.eszigno3+xml' => ['es3', 'et3'],
+        'application/vnd.ezpix-album' => ['ez2'],
+        'application/vnd.ezpix-package' => ['ez3'],
+        'application/vnd.fdf' => ['fdf'],
+        'application/vnd.fdsn.mseed' => ['mseed'],
+        'application/vnd.fdsn.seed' => ['seed', 'dataless'],
+        'application/vnd.flographit' => ['gph'],
+        'application/vnd.fluxtime.clip' => ['ftc'],
+        'application/vnd.framemaker' => ['fm', 'frame', 'maker', 'book'],
+        'application/vnd.frogans.fnc' => ['fnc'],
+        'application/vnd.frogans.ltf' => ['ltf'],
+        'application/vnd.fsc.weblaunch' => ['fsc'],
+        'application/vnd.fujitsu.oasys' => ['oas'],
+        'application/vnd.fujitsu.oasys2' => ['oa2'],
+        'application/vnd.fujitsu.oasys3' => ['oa3'],
+        'application/vnd.fujitsu.oasysgp' => ['fg5'],
+        'application/vnd.fujitsu.oasysprs' => ['bh2'],
+        'application/vnd.fujixerox.ddd' => ['ddd'],
+        'application/vnd.fujixerox.docuworks' => ['xdw'],
+        'application/vnd.fujixerox.docuworks.binder' => ['xbd'],
+        'application/vnd.fuzzysheet' => ['fzs'],
+        'application/vnd.genomatix.tuxedo' => ['txd'],
+        'application/vnd.geogebra.file' => ['ggb'],
+        'application/vnd.geogebra.tool' => ['ggt'],
+        'application/vnd.geometry-explorer' => ['gex', 'gre'],
+        'application/vnd.geonext' => ['gxt'],
+        'application/vnd.geoplan' => ['g2w'],
+        'application/vnd.geospace' => ['g3w'],
+        'application/vnd.gmx' => ['gmx'],
+        'application/vnd.google-apps.document' => ['gdoc'],
+        'application/vnd.google-apps.presentation' => ['gslides'],
+        'application/vnd.google-apps.spreadsheet' => ['gsheet'],
+        'application/vnd.google-earth.kml+xml' => ['kml'],
+        'application/vnd.google-earth.kmz' => ['kmz'],
+        'application/vnd.grafeq' => ['gqf', 'gqs'],
+        'application/vnd.groove-account' => ['gac'],
+        'application/vnd.groove-help' => ['ghf'],
+        'application/vnd.groove-identity-message' => ['gim'],
+        'application/vnd.groove-injector' => ['grv'],
+        'application/vnd.groove-tool-message' => ['gtm'],
+        'application/vnd.groove-tool-template' => ['tpl'],
+        'application/vnd.groove-vcard' => ['vcg'],
+        'application/vnd.hal+xml' => ['hal'],
+        'application/vnd.handheld-entertainment+xml' => ['zmm'],
+        'application/vnd.hbci' => ['hbci'],
+        'application/vnd.hhe.lesson-player' => ['les'],
+        'application/vnd.hp-hpgl' => ['hpgl'],
+        'application/vnd.hp-hpid' => ['hpid'],
+        'application/vnd.hp-hps' => ['hps'],
+        'application/vnd.hp-jlyt' => ['jlt'],
+        'application/vnd.hp-pcl' => ['pcl'],
+        'application/vnd.hp-pclxl' => ['pclxl'],
+        'application/vnd.hydrostatix.sof-data' => ['sfd-hdstx'],
+        'application/vnd.ibm.minipay' => ['mpy'],
+        'application/vnd.ibm.modcap' => ['afp', 'listafp', 'list3820'],
+        'application/vnd.ibm.rights-management' => ['irm'],
+        'application/vnd.ibm.secure-container' => ['sc'],
+        'application/vnd.iccprofile' => ['icc', 'icm'],
+        'application/vnd.igloader' => ['igl'],
+        'application/vnd.immervision-ivp' => ['ivp'],
+        'application/vnd.immervision-ivu' => ['ivu'],
+        'application/vnd.insors.igm' => ['igm'],
+        'application/vnd.intercon.formnet' => ['xpw', 'xpx'],
+        'application/vnd.intergeo' => ['i2g'],
+        'application/vnd.intu.qbo' => ['qbo'],
+        'application/vnd.intu.qfx' => ['qfx'],
+        'application/vnd.ipunplugged.rcprofile' => ['rcprofile'],
+        'application/vnd.irepository.package+xml' => ['irp'],
+        'application/vnd.is-xpr' => ['xpr'],
+        'application/vnd.isac.fcs' => ['fcs'],
+        'application/vnd.jam' => ['jam'],
+        'application/vnd.jcp.javame.midlet-rms' => ['rms'],
+        'application/vnd.jisp' => ['jisp'],
+        'application/vnd.joost.joda-archive' => ['joda'],
+        'application/vnd.kahootz' => ['ktz', 'ktr'],
+        'application/vnd.kde.karbon' => ['karbon'],
+        'application/vnd.kde.kchart' => ['chrt'],
+        'application/vnd.kde.kformula' => ['kfo'],
+        'application/vnd.kde.kivio' => ['flw'],
+        'application/vnd.kde.kontour' => ['kon'],
+        'application/vnd.kde.kpresenter' => ['kpr', 'kpt'],
+        'application/vnd.kde.kspread' => ['ksp'],
+        'application/vnd.kde.kword' => ['kwd', 'kwt'],
+        'application/vnd.kenameaapp' => ['htke'],
+        'application/vnd.kidspiration' => ['kia'],
+        'application/vnd.kinar' => ['kne', 'knp'],
+        'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'],
+        'application/vnd.kodak-descriptor' => ['sse'],
+        'application/vnd.las.las+xml' => ['lasxml'],
+        'application/vnd.llamagraphics.life-balance.desktop' => ['lbd'],
+        'application/vnd.llamagraphics.life-balance.exchange+xml' => ['lbe'],
+        'application/vnd.lotus-1-2-3' => ['123'],
+        'application/vnd.lotus-approach' => ['apr'],
+        'application/vnd.lotus-freelance' => ['pre'],
+        'application/vnd.lotus-notes' => ['nsf'],
+        'application/vnd.lotus-organizer' => ['org'],
+        'application/vnd.lotus-screencam' => ['scm'],
+        'application/vnd.lotus-wordpro' => ['lwp'],
+        'application/vnd.macports.portpkg' => ['portpkg'],
+        'application/vnd.mcd' => ['mcd'],
+        'application/vnd.medcalcdata' => ['mc1'],
+        'application/vnd.mediastation.cdkey' => ['cdkey'],
+        'application/vnd.mfer' => ['mwf'],
+        'application/vnd.mfmp' => ['mfm'],
+        'application/vnd.micrografx.flo' => ['flo'],
+        'application/vnd.micrografx.igx' => ['igx'],
+        'application/vnd.mif' => ['mif'],
+        'application/vnd.mobius.daf' => ['daf'],
+        'application/vnd.mobius.dis' => ['dis'],
+        'application/vnd.mobius.mbk' => ['mbk'],
+        'application/vnd.mobius.mqy' => ['mqy'],
+        'application/vnd.mobius.msl' => ['msl'],
+        'application/vnd.mobius.plc' => ['plc'],
+        'application/vnd.mobius.txf' => ['txf'],
+        'application/vnd.mophun.application' => ['mpn'],
+        'application/vnd.mophun.certificate' => ['mpc'],
+        'application/vnd.mozilla.xul+xml' => ['xul'],
+        'application/vnd.ms-artgalry' => ['cil'],
+        'application/vnd.ms-cab-compressed' => ['cab'],
+        'application/vnd.ms-excel' => ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw'],
+        'application/vnd.ms-excel.addin.macroenabled.12' => ['xlam'],
+        'application/vnd.ms-excel.sheet.binary.macroenabled.12' => ['xlsb'],
+        'application/vnd.ms-excel.sheet.macroenabled.12' => ['xlsm'],
+        'application/vnd.ms-excel.template.macroenabled.12' => ['xltm'],
+        'application/vnd.ms-fontobject' => ['eot'],
+        'application/vnd.ms-htmlhelp' => ['chm'],
+        'application/vnd.ms-ims' => ['ims'],
+        'application/vnd.ms-lrm' => ['lrm'],
+        'application/vnd.ms-officetheme' => ['thmx'],
+        'application/vnd.ms-outlook' => ['msg'],
+        'application/vnd.ms-pki.seccat' => ['cat'],
+        'application/vnd.ms-pki.stl' => ['stl'],
+        'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot'],
+        'application/vnd.ms-powerpoint.addin.macroenabled.12' => ['ppam'],
+        'application/vnd.ms-powerpoint.presentation.macroenabled.12' => ['pptm'],
+        'application/vnd.ms-powerpoint.slide.macroenabled.12' => ['sldm'],
+        'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => ['ppsm'],
+        'application/vnd.ms-powerpoint.template.macroenabled.12' => ['potm'],
+        'application/vnd.ms-project' => ['mpp', 'mpt'],
+        'application/vnd.ms-word.document.macroenabled.12' => ['docm'],
+        'application/vnd.ms-word.template.macroenabled.12' => ['dotm'],
+        'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb'],
+        'application/vnd.ms-wpl' => ['wpl'],
+        'application/vnd.ms-xpsdocument' => ['xps'],
+        'application/vnd.mseq' => ['mseq'],
+        'application/vnd.musician' => ['mus'],
+        'application/vnd.muvee.style' => ['msty'],
+        'application/vnd.mynfc' => ['taglet'],
+        'application/vnd.neurolanguage.nlu' => ['nlu'],
+        'application/vnd.nitf' => ['ntf', 'nitf'],
+        'application/vnd.noblenet-directory' => ['nnd'],
+        'application/vnd.noblenet-sealer' => ['nns'],
+        'application/vnd.noblenet-web' => ['nnw'],
+        'application/vnd.nokia.n-gage.ac+xml' => ['ac'],
+        'application/vnd.nokia.n-gage.data' => ['ngdat'],
+        'application/vnd.nokia.n-gage.symbian.install' => ['n-gage'],
+        'application/vnd.nokia.radio-preset' => ['rpst'],
+        'application/vnd.nokia.radio-presets' => ['rpss'],
+        'application/vnd.novadigm.edm' => ['edm'],
+        'application/vnd.novadigm.edx' => ['edx'],
+        'application/vnd.novadigm.ext' => ['ext'],
+        'application/vnd.oasis.opendocument.chart' => ['odc'],
+        'application/vnd.oasis.opendocument.chart-template' => ['otc'],
+        'application/vnd.oasis.opendocument.database' => ['odb'],
+        'application/vnd.oasis.opendocument.formula' => ['odf'],
+        'application/vnd.oasis.opendocument.formula-template' => ['odft'],
+        'application/vnd.oasis.opendocument.graphics' => ['odg'],
+        'application/vnd.oasis.opendocument.graphics-template' => ['otg'],
+        'application/vnd.oasis.opendocument.image' => ['odi'],
+        'application/vnd.oasis.opendocument.image-template' => ['oti'],
+        'application/vnd.oasis.opendocument.presentation' => ['odp'],
+        'application/vnd.oasis.opendocument.presentation-template' => ['otp'],
+        'application/vnd.oasis.opendocument.spreadsheet' => ['ods'],
+        'application/vnd.oasis.opendocument.spreadsheet-template' => ['ots'],
+        'application/vnd.oasis.opendocument.text' => ['odt'],
+        'application/vnd.oasis.opendocument.text-master' => ['odm'],
+        'application/vnd.oasis.opendocument.text-template' => ['ott'],
+        'application/vnd.oasis.opendocument.text-web' => ['oth'],
+        'application/vnd.olpc-sugar' => ['xo'],
+        'application/vnd.oma.dd2+xml' => ['dd2'],
+        'application/vnd.openblox.game+xml' => ['obgx'],
+        'application/vnd.openofficeorg.extension' => ['oxt'],
+        'application/vnd.openstreetmap.data+xml' => ['osm'],
+        'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx'],
+        'application/vnd.openxmlformats-officedocument.presentationml.slide' => ['sldx'],
+        'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => ['ppsx'],
+        'application/vnd.openxmlformats-officedocument.presentationml.template' => ['potx'],
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'],
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => ['xltx'],
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'],
+        'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => ['dotx'],
+        'application/vnd.osgeo.mapguide.package' => ['mgp'],
+        'application/vnd.osgi.dp' => ['dp'],
+        'application/vnd.osgi.subsystem' => ['esa'],
+        'application/vnd.palm' => ['pdb', 'pqa', 'oprc'],
+        'application/vnd.pawaafile' => ['paw'],
+        'application/vnd.pg.format' => ['str'],
+        'application/vnd.pg.osasli' => ['ei6'],
+        'application/vnd.picsel' => ['efif'],
+        'application/vnd.pmi.widget' => ['wg'],
+        'application/vnd.pocketlearn' => ['plf'],
+        'application/vnd.powerbuilder6' => ['pbd'],
+        'application/vnd.previewsystems.box' => ['box'],
+        'application/vnd.proteus.magazine' => ['mgz'],
+        'application/vnd.publishare-delta-tree' => ['qps'],
+        'application/vnd.pvi.ptid1' => ['ptid'],
+        'application/vnd.quark.quarkxpress' => ['qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb'],
+        'application/vnd.rar' => ['rar'],
+        'application/vnd.realvnc.bed' => ['bed'],
+        'application/vnd.recordare.musicxml' => ['mxl'],
+        'application/vnd.recordare.musicxml+xml' => ['musicxml'],
+        'application/vnd.rig.cryptonote' => ['cryptonote'],
+        'application/vnd.rim.cod' => ['cod'],
+        'application/vnd.rn-realmedia' => ['rm'],
+        'application/vnd.rn-realmedia-vbr' => ['rmvb'],
+        'application/vnd.route66.link66+xml' => ['link66'],
+        'application/vnd.sailingtracker.track' => ['st'],
+        'application/vnd.seemail' => ['see'],
+        'application/vnd.sema' => ['sema'],
+        'application/vnd.semd' => ['semd'],
+        'application/vnd.semf' => ['semf'],
+        'application/vnd.shana.informed.formdata' => ['ifm'],
+        'application/vnd.shana.informed.formtemplate' => ['itp'],
+        'application/vnd.shana.informed.interchange' => ['iif'],
+        'application/vnd.shana.informed.package' => ['ipk'],
+        'application/vnd.simtech-mindmapper' => ['twd', 'twds'],
+        'application/vnd.smaf' => ['mmf'],
+        'application/vnd.smart.teacher' => ['teacher'],
+        'application/vnd.software602.filler.form+xml' => ['fo'],
+        'application/vnd.solent.sdkm+xml' => ['sdkm', 'sdkd'],
+        'application/vnd.spotfire.dxp' => ['dxp'],
+        'application/vnd.spotfire.sfs' => ['sfs'],
+        'application/vnd.stardivision.calc' => ['sdc'],
+        'application/vnd.stardivision.draw' => ['sda'],
+        'application/vnd.stardivision.impress' => ['sdd'],
+        'application/vnd.stardivision.math' => ['smf'],
+        'application/vnd.stardivision.writer' => ['sdw', 'vor'],
+        'application/vnd.stardivision.writer-global' => ['sgl'],
+        'application/vnd.stepmania.package' => ['smzip'],
+        'application/vnd.stepmania.stepchart' => ['sm'],
+        'application/vnd.sun.wadl+xml' => ['wadl'],
+        'application/vnd.sun.xml.calc' => ['sxc'],
+        'application/vnd.sun.xml.calc.template' => ['stc'],
+        'application/vnd.sun.xml.draw' => ['sxd'],
+        'application/vnd.sun.xml.draw.template' => ['std'],
+        'application/vnd.sun.xml.impress' => ['sxi'],
+        'application/vnd.sun.xml.impress.template' => ['sti'],
+        'application/vnd.sun.xml.math' => ['sxm'],
+        'application/vnd.sun.xml.writer' => ['sxw'],
+        'application/vnd.sun.xml.writer.global' => ['sxg'],
+        'application/vnd.sun.xml.writer.template' => ['stw'],
+        'application/vnd.sus-calendar' => ['sus', 'susp'],
+        'application/vnd.svd' => ['svd'],
+        'application/vnd.symbian.install' => ['sis', 'sisx'],
+        'application/vnd.syncml+xml' => ['xsm'],
+        'application/vnd.syncml.dm+wbxml' => ['bdm'],
+        'application/vnd.syncml.dm+xml' => ['xdm'],
+        'application/vnd.syncml.dmddf+xml' => ['ddf'],
+        'application/vnd.tao.intent-module-archive' => ['tao'],
+        'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'],
+        'application/vnd.tmobile-livetv' => ['tmo'],
+        'application/vnd.trid.tpt' => ['tpt'],
+        'application/vnd.triscape.mxs' => ['mxs'],
+        'application/vnd.trueapp' => ['tra'],
+        'application/vnd.ufdl' => ['ufd', 'ufdl'],
+        'application/vnd.uiq.theme' => ['utz'],
+        'application/vnd.umajin' => ['umj'],
+        'application/vnd.unity' => ['unityweb'],
+        'application/vnd.uoml+xml' => ['uoml'],
+        'application/vnd.vcx' => ['vcx'],
+        'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'],
+        'application/vnd.visionary' => ['vis'],
+        'application/vnd.vsf' => ['vsf'],
+        'application/vnd.wap.wbxml' => ['wbxml'],
+        'application/vnd.wap.wmlc' => ['wmlc'],
+        'application/vnd.wap.wmlscriptc' => ['wmlsc'],
+        'application/vnd.webturbo' => ['wtb'],
+        'application/vnd.wolfram.player' => ['nbp'],
+        'application/vnd.wordperfect' => ['wpd'],
+        'application/vnd.wqd' => ['wqd'],
+        'application/vnd.wt.stf' => ['stf'],
+        'application/vnd.xara' => ['xar'],
+        'application/vnd.xfdl' => ['xfdl'],
+        'application/vnd.yamaha.hv-dic' => ['hvd'],
+        'application/vnd.yamaha.hv-script' => ['hvs'],
+        'application/vnd.yamaha.hv-voice' => ['hvp'],
+        'application/vnd.yamaha.openscoreformat' => ['osf'],
+        'application/vnd.yamaha.openscoreformat.osfpvg+xml' => ['osfpvg'],
+        'application/vnd.yamaha.smaf-audio' => ['saf'],
+        'application/vnd.yamaha.smaf-phrase' => ['spf'],
+        'application/vnd.yellowriver-custom-menu' => ['cmp'],
+        'application/vnd.zul' => ['zir', 'zirz'],
+        'application/vnd.zzazz.deck+xml' => ['zaz'],
+        'application/voicexml+xml' => ['vxml'],
+        'application/wasm' => ['wasm'],
+        'application/widget' => ['wgt'],
+        'application/winhlp' => ['hlp'],
+        'application/wsdl+xml' => ['wsdl'],
+        'application/wspolicy+xml' => ['wspolicy'],
+        'application/x-7z-compressed' => ['7z'],
+        'application/x-abiword' => ['abw'],
+        'application/x-ace-compressed' => ['ace'],
+        'application/x-apple-diskimage' => ['dmg'],
+        'application/x-arj' => ['arj'],
+        'application/x-authorware-bin' => ['aab', 'x32', 'u32', 'vox'],
+        'application/x-authorware-map' => ['aam'],
+        'application/x-authorware-seg' => ['aas'],
+        'application/x-bcpio' => ['bcpio'],
+        'application/x-bdoc' => ['bdoc'],
+        'application/x-bittorrent' => ['torrent'],
+        'application/x-blorb' => ['blb', 'blorb'],
+        'application/x-bzip' => ['bz'],
+        'application/x-bzip2' => ['bz2', 'boz'],
+        'application/x-cbr' => ['cbr', 'cba', 'cbt', 'cbz', 'cb7'],
+        'application/x-cdlink' => ['vcd'],
+        'application/x-cfs-compressed' => ['cfs'],
+        'application/x-chat' => ['chat'],
+        'application/x-chess-pgn' => ['pgn'],
+        'application/x-chrome-extension' => ['crx'],
+        'application/x-cocoa' => ['cco'],
+        'application/x-conference' => ['nsc'],
+        'application/x-cpio' => ['cpio'],
+        'application/x-csh' => ['csh'],
+        'application/x-debian-package' => ['deb', 'udeb'],
+        'application/x-dgc-compressed' => ['dgc'],
+        'application/x-director' => ['dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa'],
+        'application/x-doom' => ['wad'],
+        'application/x-dtbncx+xml' => ['ncx'],
+        'application/x-dtbook+xml' => ['dtb'],
+        'application/x-dtbresource+xml' => ['res'],
+        'application/x-dvi' => ['dvi'],
+        'application/x-envoy' => ['evy'],
+        'application/x-eva' => ['eva'],
+        'application/x-font-bdf' => ['bdf'],
+        'application/x-font-ghostscript' => ['gsf'],
+        'application/x-font-linux-psf' => ['psf'],
+        'application/x-font-pcf' => ['pcf'],
+        'application/x-font-snf' => ['snf'],
+        'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm'],
+        'application/x-freearc' => ['arc'],
+        'application/x-futuresplash' => ['spl'],
+        'application/x-gca-compressed' => ['gca'],
+        'application/x-glulx' => ['ulx'],
+        'application/x-gnumeric' => ['gnumeric'],
+        'application/x-gramps-xml' => ['gramps'],
+        'application/x-gtar' => ['gtar'],
+        'application/x-hdf' => ['hdf'],
+        'application/x-httpd-php' => ['php'],
+        'application/x-install-instructions' => ['install'],
+        'application/x-iso9660-image' => ['iso'],
+        'application/x-java-archive-diff' => ['jardiff'],
+        'application/x-java-jnlp-file' => ['jnlp'],
+        'application/x-keepass2' => ['kdbx'],
+        'application/x-latex' => ['latex'],
+        'application/x-lua-bytecode' => ['luac'],
+        'application/x-lzh-compressed' => ['lzh', 'lha'],
+        'application/x-makeself' => ['run'],
+        'application/x-mie' => ['mie'],
+        'application/x-mobipocket-ebook' => ['prc', 'mobi'],
+        'application/x-ms-application' => ['application'],
+        'application/x-ms-shortcut' => ['lnk'],
+        'application/x-ms-wmd' => ['wmd'],
+        'application/x-ms-wmz' => ['wmz'],
+        'application/x-ms-xbap' => ['xbap'],
+        'application/x-msaccess' => ['mdb'],
+        'application/x-msbinder' => ['obd'],
+        'application/x-mscardfile' => ['crd'],
+        'application/x-msclip' => ['clp'],
+        'application/x-msdos-program' => ['exe'],
+        'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'],
+        'application/x-msmediaview' => ['mvb', 'm13', 'm14'],
+        'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'],
+        'application/x-msmoney' => ['mny'],
+        'application/x-mspublisher' => ['pub'],
+        'application/x-msschedule' => ['scd'],
+        'application/x-msterminal' => ['trm'],
+        'application/x-mswrite' => ['wri'],
+        'application/x-netcdf' => ['nc', 'cdf'],
+        'application/x-ns-proxy-autoconfig' => ['pac'],
+        'application/x-nzb' => ['nzb'],
+        'application/x-perl' => ['pl', 'pm'],
+        'application/x-pilot' => ['prc', 'pdb'],
+        'application/x-pkcs12' => ['p12', 'pfx'],
+        'application/x-pkcs7-certificates' => ['p7b', 'spc'],
+        'application/x-pkcs7-certreqresp' => ['p7r'],
+        'application/x-rar-compressed' => ['rar'],
+        'application/x-redhat-package-manager' => ['rpm'],
+        'application/x-research-info-systems' => ['ris'],
+        'application/x-sea' => ['sea'],
+        'application/x-sh' => ['sh'],
+        'application/x-shar' => ['shar'],
+        'application/x-shockwave-flash' => ['swf'],
+        'application/x-silverlight-app' => ['xap'],
+        'application/x-sql' => ['sql'],
+        'application/x-stuffit' => ['sit'],
+        'application/x-stuffitx' => ['sitx'],
+        'application/x-subrip' => ['srt'],
+        'application/x-sv4cpio' => ['sv4cpio'],
+        'application/x-sv4crc' => ['sv4crc'],
+        'application/x-t3vm-image' => ['t3'],
+        'application/x-tads' => ['gam'],
+        'application/x-tar' => ['tar'],
+        'application/x-tcl' => ['tcl', 'tk'],
+        'application/x-tex' => ['tex'],
+        'application/x-tex-tfm' => ['tfm'],
+        'application/x-texinfo' => ['texinfo', 'texi'],
+        'application/x-tgif' => ['obj'],
+        'application/x-ustar' => ['ustar'],
+        'application/x-virtualbox-hdd' => ['hdd'],
+        'application/x-virtualbox-ova' => ['ova'],
+        'application/x-virtualbox-ovf' => ['ovf'],
+        'application/x-virtualbox-vbox' => ['vbox'],
+        'application/x-virtualbox-vbox-extpack' => ['vbox-extpack'],
+        'application/x-virtualbox-vdi' => ['vdi'],
+        'application/x-virtualbox-vhd' => ['vhd'],
+        'application/x-virtualbox-vmdk' => ['vmdk'],
+        'application/x-wais-source' => ['src'],
+        'application/x-web-app-manifest+json' => ['webapp'],
+        'application/x-x509-ca-cert' => ['der', 'crt', 'pem'],
+        'application/x-xfig' => ['fig'],
+        'application/x-xliff+xml' => ['xlf'],
+        'application/x-xpinstall' => ['xpi'],
+        'application/x-xz' => ['xz'],
+        'application/x-zmachine' => ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'],
+        'application/xaml+xml' => ['xaml'],
+        'application/xcap-att+xml' => ['xav'],
+        'application/xcap-caps+xml' => ['xca'],
+        'application/xcap-diff+xml' => ['xdf'],
+        'application/xcap-el+xml' => ['xel'],
+        'application/xcap-error+xml' => ['xer'],
+        'application/xcap-ns+xml' => ['xns'],
+        'application/xenc+xml' => ['xenc'],
+        'application/xhtml+xml' => ['xhtml', 'xht'],
+        'application/xliff+xml' => ['xlf'],
+        'application/xml' => ['xml', 'xsl', 'xsd', 'rng'],
+        'application/xml-dtd' => ['dtd'],
+        'application/xop+xml' => ['xop'],
+        'application/xproc+xml' => ['xpl'],
+        'application/xslt+xml' => ['xsl', 'xslt'],
+        'application/xspf+xml' => ['xspf'],
+        'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'],
+        'application/yang' => ['yang'],
+        'application/yin+xml' => ['yin'],
+        'application/zip' => ['zip'],
+        'audio/3gpp' => ['3gpp'],
+        'audio/adpcm' => ['adp'],
+        'audio/amr' => ['amr'],
+        'audio/basic' => ['au', 'snd'],
+        'audio/midi' => ['mid', 'midi', 'kar', 'rmi'],
+        'audio/mobile-xmf' => ['mxmf'],
+        'audio/mp3' => ['mp3'],
+        'audio/mp4' => ['m4a', 'mp4a'],
+        'audio/mpeg' => ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'],
+        'audio/ogg' => ['oga', 'ogg', 'spx', 'opus'],
+        'audio/s3m' => ['s3m'],
+        'audio/silk' => ['sil'],
+        'audio/vnd.dece.audio' => ['uva', 'uvva'],
+        'audio/vnd.digital-winds' => ['eol'],
+        'audio/vnd.dra' => ['dra'],
+        'audio/vnd.dts' => ['dts'],
+        'audio/vnd.dts.hd' => ['dtshd'],
+        'audio/vnd.lucent.voice' => ['lvp'],
+        'audio/vnd.ms-playready.media.pya' => ['pya'],
+        'audio/vnd.nuera.ecelp4800' => ['ecelp4800'],
+        'audio/vnd.nuera.ecelp7470' => ['ecelp7470'],
+        'audio/vnd.nuera.ecelp9600' => ['ecelp9600'],
+        'audio/vnd.rip' => ['rip'],
+        'audio/wav' => ['wav'],
+        'audio/wave' => ['wav'],
+        'audio/webm' => ['weba'],
+        'audio/x-aac' => ['aac'],
+        'audio/x-aiff' => ['aif', 'aiff', 'aifc'],
+        'audio/x-caf' => ['caf'],
+        'audio/x-flac' => ['flac'],
+        'audio/x-m4a' => ['m4a'],
+        'audio/x-matroska' => ['mka'],
+        'audio/x-mpegurl' => ['m3u'],
+        'audio/x-ms-wax' => ['wax'],
+        'audio/x-ms-wma' => ['wma'],
+        'audio/x-pn-realaudio' => ['ram', 'ra'],
+        'audio/x-pn-realaudio-plugin' => ['rmp'],
+        'audio/x-realaudio' => ['ra'],
+        'audio/x-wav' => ['wav'],
+        'audio/xm' => ['xm'],
+        'chemical/x-cdx' => ['cdx'],
+        'chemical/x-cif' => ['cif'],
+        'chemical/x-cmdf' => ['cmdf'],
+        'chemical/x-cml' => ['cml'],
+        'chemical/x-csml' => ['csml'],
+        'chemical/x-xyz' => ['xyz'],
+        'font/collection' => ['ttc'],
+        'font/otf' => ['otf'],
+        'font/ttf' => ['ttf'],
+        'font/woff' => ['woff'],
+        'font/woff2' => ['woff2'],
+        'image/aces' => ['exr'],
+        'image/apng' => ['apng'],
+        'image/avif' => ['avif'],
+        'image/bmp' => ['bmp'],
+        'image/cgm' => ['cgm'],
+        'image/dicom-rle' => ['drle'],
+        'image/emf' => ['emf'],
+        'image/fits' => ['fits'],
+        'image/g3fax' => ['g3'],
+        'image/gif' => ['gif'],
+        'image/heic' => ['heic'],
+        'image/heic-sequence' => ['heics'],
+        'image/heif' => ['heif'],
+        'image/heif-sequence' => ['heifs'],
+        'image/hej2k' => ['hej2'],
+        'image/hsj2' => ['hsj2'],
+        'image/ief' => ['ief'],
+        'image/jls' => ['jls'],
+        'image/jp2' => ['jp2', 'jpg2'],
+        'image/jpeg' => ['jpeg', 'jpg', 'jpe'],
+        'image/jph' => ['jph'],
+        'image/jphc' => ['jhc'],
+        'image/jpm' => ['jpm'],
+        'image/jpx' => ['jpx', 'jpf'],
+        'image/jxr' => ['jxr'],
+        'image/jxra' => ['jxra'],
+        'image/jxrs' => ['jxrs'],
+        'image/jxs' => ['jxs'],
+        'image/jxsc' => ['jxsc'],
+        'image/jxsi' => ['jxsi'],
+        'image/jxss' => ['jxss'],
+        'image/ktx' => ['ktx'],
+        'image/ktx2' => ['ktx2'],
+        'image/png' => ['png'],
+        'image/prs.btif' => ['btif'],
+        'image/prs.pti' => ['pti'],
+        'image/sgi' => ['sgi'],
+        'image/svg+xml' => ['svg', 'svgz'],
+        'image/t38' => ['t38'],
+        'image/tiff' => ['tif', 'tiff'],
+        'image/tiff-fx' => ['tfx'],
+        'image/vnd.adobe.photoshop' => ['psd'],
+        'image/vnd.airzip.accelerator.azv' => ['azv'],
+        'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'],
+        'image/vnd.djvu' => ['djvu', 'djv'],
+        'image/vnd.dvb.subtitle' => ['sub'],
+        'image/vnd.dwg' => ['dwg'],
+        'image/vnd.dxf' => ['dxf'],
+        'image/vnd.fastbidsheet' => ['fbs'],
+        'image/vnd.fpx' => ['fpx'],
+        'image/vnd.fst' => ['fst'],
+        'image/vnd.fujixerox.edmics-mmr' => ['mmr'],
+        'image/vnd.fujixerox.edmics-rlc' => ['rlc'],
+        'image/vnd.microsoft.icon' => ['ico'],
+        'image/vnd.ms-dds' => ['dds'],
+        'image/vnd.ms-modi' => ['mdi'],
+        'image/vnd.ms-photo' => ['wdp'],
+        'image/vnd.net-fpx' => ['npx'],
+        'image/vnd.pco.b16' => ['b16'],
+        'image/vnd.tencent.tap' => ['tap'],
+        'image/vnd.valve.source.texture' => ['vtf'],
+        'image/vnd.wap.wbmp' => ['wbmp'],
+        'image/vnd.xiff' => ['xif'],
+        'image/vnd.zbrush.pcx' => ['pcx'],
+        'image/webp' => ['webp'],
+        'image/wmf' => ['wmf'],
+        'image/x-3ds' => ['3ds'],
+        'image/x-cmu-raster' => ['ras'],
+        'image/x-cmx' => ['cmx'],
+        'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'],
+        'image/x-icon' => ['ico'],
+        'image/x-jng' => ['jng'],
+        'image/x-mrsid-image' => ['sid'],
+        'image/x-ms-bmp' => ['bmp'],
+        'image/x-pcx' => ['pcx'],
+        'image/x-pict' => ['pic', 'pct'],
+        'image/x-portable-anymap' => ['pnm'],
+        'image/x-portable-bitmap' => ['pbm'],
+        'image/x-portable-graymap' => ['pgm'],
+        'image/x-portable-pixmap' => ['ppm'],
+        'image/x-rgb' => ['rgb'],
+        'image/x-tga' => ['tga'],
+        'image/x-xbitmap' => ['xbm'],
+        'image/x-xpixmap' => ['xpm'],
+        'image/x-xwindowdump' => ['xwd'],
+        'message/disposition-notification' => ['disposition-notification'],
+        'message/global' => ['u8msg'],
+        'message/global-delivery-status' => ['u8dsn'],
+        'message/global-disposition-notification' => ['u8mdn'],
+        'message/global-headers' => ['u8hdr'],
+        'message/rfc822' => ['eml', 'mime'],
+        'message/vnd.wfa.wsc' => ['wsc'],
+        'model/3mf' => ['3mf'],
+        'model/gltf+json' => ['gltf'],
+        'model/gltf-binary' => ['glb'],
+        'model/iges' => ['igs', 'iges'],
+        'model/mesh' => ['msh', 'mesh', 'silo'],
+        'model/mtl' => ['mtl'],
+        'model/obj' => ['obj'],
+        'model/stl' => ['stl'],
+        'model/vnd.collada+xml' => ['dae'],
+        'model/vnd.dwf' => ['dwf'],
+        'model/vnd.gdl' => ['gdl'],
+        'model/vnd.gtw' => ['gtw'],
+        'model/vnd.mts' => ['mts'],
+        'model/vnd.opengex' => ['ogex'],
+        'model/vnd.parasolid.transmit.binary' => ['x_b'],
+        'model/vnd.parasolid.transmit.text' => ['x_t'],
+        'model/vnd.usdz+zip' => ['usdz'],
+        'model/vnd.valve.source.compiled-map' => ['bsp'],
+        'model/vnd.vtu' => ['vtu'],
+        'model/vrml' => ['wrl', 'vrml'],
+        'model/x3d+binary' => ['x3db', 'x3dbz'],
+        'model/x3d+fastinfoset' => ['x3db'],
+        'model/x3d+vrml' => ['x3dv', 'x3dvz'],
+        'model/x3d+xml' => ['x3d', 'x3dz'],
+        'model/x3d-vrml' => ['x3dv'],
+        'text/cache-manifest' => ['appcache', 'manifest'],
+        'text/calendar' => ['ics', 'ifb'],
+        'text/coffeescript' => ['coffee', 'litcoffee'],
+        'text/css' => ['css'],
+        'text/csv' => ['csv'],
+        'text/html' => ['html', 'htm', 'shtml'],
+        'text/jade' => ['jade'],
+        'text/jsx' => ['jsx'],
+        'text/less' => ['less'],
+        'text/markdown' => ['markdown', 'md'],
+        'text/mathml' => ['mml'],
+        'text/mdx' => ['mdx'],
+        'text/n3' => ['n3'],
+        'text/plain' => ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini'],
+        'text/prs.lines.tag' => ['dsc'],
+        'text/richtext' => ['rtx'],
+        'text/rtf' => ['rtf'],
+        'text/sgml' => ['sgml', 'sgm'],
+        'text/shex' => ['shex'],
+        'text/slim' => ['slim', 'slm'],
+        'text/spdx' => ['spdx'],
+        'text/stylus' => ['stylus', 'styl'],
+        'text/tab-separated-values' => ['tsv'],
+        'text/troff' => ['t', 'tr', 'roff', 'man', 'me', 'ms'],
+        'text/turtle' => ['ttl'],
+        'text/uri-list' => ['uri', 'uris', 'urls'],
+        'text/vcard' => ['vcard'],
+        'text/vnd.curl' => ['curl'],
+        'text/vnd.curl.dcurl' => ['dcurl'],
+        'text/vnd.curl.mcurl' => ['mcurl'],
+        'text/vnd.curl.scurl' => ['scurl'],
+        'text/vnd.dvb.subtitle' => ['sub'],
+        'text/vnd.fly' => ['fly'],
+        'text/vnd.fmi.flexstor' => ['flx'],
+        'text/vnd.graphviz' => ['gv'],
+        'text/vnd.in3d.3dml' => ['3dml'],
+        'text/vnd.in3d.spot' => ['spot'],
+        'text/vnd.sun.j2me.app-descriptor' => ['jad'],
+        'text/vnd.wap.wml' => ['wml'],
+        'text/vnd.wap.wmlscript' => ['wmls'],
+        'text/vtt' => ['vtt'],
+        'text/x-asm' => ['s', 'asm'],
+        'text/x-c' => ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'],
+        'text/x-component' => ['htc'],
+        'text/x-fortran' => ['f', 'for', 'f77', 'f90'],
+        'text/x-handlebars-template' => ['hbs'],
+        'text/x-java-source' => ['java'],
+        'text/x-lua' => ['lua'],
+        'text/x-markdown' => ['mkd'],
+        'text/x-nfo' => ['nfo'],
+        'text/x-opml' => ['opml'],
+        'text/x-org' => ['org'],
+        'text/x-pascal' => ['p', 'pas'],
+        'text/x-processing' => ['pde'],
+        'text/x-sass' => ['sass'],
+        'text/x-scss' => ['scss'],
+        'text/x-setext' => ['etx'],
+        'text/x-sfv' => ['sfv'],
+        'text/x-suse-ymp' => ['ymp'],
+        'text/x-uuencode' => ['uu'],
+        'text/x-vcalendar' => ['vcs'],
+        'text/x-vcard' => ['vcf'],
+        'text/xml' => ['xml'],
+        'text/yaml' => ['yaml', 'yml'],
+        'video/3gpp' => ['3gp', '3gpp'],
+        'video/3gpp2' => ['3g2'],
+        'video/h261' => ['h261'],
+        'video/h263' => ['h263'],
+        'video/h264' => ['h264'],
+        'video/iso.segment' => ['m4s'],
+        'video/jpeg' => ['jpgv'],
+        'video/jpm' => ['jpm', 'jpgm'],
+        'video/mj2' => ['mj2', 'mjp2'],
+        'video/mp2t' => ['ts'],
+        'video/mp4' => ['mp4', 'mp4v', 'mpg4'],
+        'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'],
+        'video/ogg' => ['ogv'],
+        'video/quicktime' => ['qt', 'mov'],
+        'video/vnd.dece.hd' => ['uvh', 'uvvh'],
+        'video/vnd.dece.mobile' => ['uvm', 'uvvm'],
+        'video/vnd.dece.pd' => ['uvp', 'uvvp'],
+        'video/vnd.dece.sd' => ['uvs', 'uvvs'],
+        'video/vnd.dece.video' => ['uvv', 'uvvv'],
+        'video/vnd.dvb.file' => ['dvb'],
+        'video/vnd.fvt' => ['fvt'],
+        'video/vnd.mpegurl' => ['mxu', 'm4u'],
+        'video/vnd.ms-playready.media.pyv' => ['pyv'],
+        'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'],
+        'video/vnd.vivo' => ['viv'],
+        'video/webm' => ['webm'],
+        'video/x-f4v' => ['f4v'],
+        'video/x-fli' => ['fli'],
+        'video/x-flv' => ['flv'],
+        'video/x-m4v' => ['m4v'],
+        'video/x-matroska' => ['mkv', 'mk3d', 'mks'],
+        'video/x-mng' => ['mng'],
+        'video/x-ms-asf' => ['asf', 'asx'],
+        'video/x-ms-vob' => ['vob'],
+        'video/x-ms-wm' => ['wm'],
+        'video/x-ms-wmv' => ['wmv'],
+        'video/x-ms-wmx' => ['wmx'],
+        'video/x-ms-wvx' => ['wvx'],
+        'video/x-msvideo' => ['avi'],
+        'video/x-sgi-movie' => ['movie'],
+        'video/x-smv' => ['smv'],
+        'x-conference/x-cooltalk' => ['ice'],
+   ];
+
+    /**
+     * @return array<string, List<string>>
+     */
+    public function getMap(): array
+    {
+        return $this->map;
+    }
+
+    /**
+     * @return List<string>
+     */
+    public function getMimeTypes(): array
+    {
+        return array_keys($this->map);
+    }
+}
diff --git a/typo3/sysext/core/Classes/Resource/MimeTypeDetector.php b/typo3/sysext/core/Classes/Resource/MimeTypeDetector.php
new file mode 100644
index 000000000000..0e124883a878
--- /dev/null
+++ b/typo3/sysext/core/Classes/Resource/MimeTypeDetector.php
@@ -0,0 +1,60 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Core\Resource;
+
+/**
+ * This class contains a list of all available / known mimetypes and file extensions,
+ * and is automatically generated by TYPO3 via Core/Build/Scripts/generateMimeTypes.php
+ */
+final class MimeTypeDetector
+{
+    /**
+     * @var MimeTypeCollection
+     */
+    private $collection;
+
+    public function __construct()
+    {
+        $this->collection = new MimeTypeCollection();
+    }
+
+    /**
+     * @param string $fileExtension
+     * @return array<int, string>
+     */
+    public function getMimeTypesForFileExtension(string $fileExtension): array
+    {
+        $mimeTypes = [];
+        $fileExtension = strtolower($fileExtension);
+        foreach ($this->collection->getMap() as $mimeType => $availableExtensions) {
+            if (in_array($fileExtension, $availableExtensions, true)) {
+                $mimeTypes[] = $mimeType;
+            }
+        }
+        return $mimeTypes;
+    }
+
+    /**
+     * @param string $mimeType
+     * @return array<int, string>
+     */
+    public function getFileExtensionsForMimeType(string $mimeType): array
+    {
+        return $this->collection->getMap()[strtolower($mimeType)] ?? [];
+    }
+}
diff --git a/typo3/sysext/form/Classes/Domain/Finishers/DeleteUploadsFinisher.php b/typo3/sysext/form/Classes/Domain/Finishers/DeleteUploadsFinisher.php
index 5be9be344732..62865d2c0ee4 100644
--- a/typo3/sysext/form/Classes/Domain/Finishers/DeleteUploadsFinisher.php
+++ b/typo3/sysext/form/Classes/Domain/Finishers/DeleteUploadsFinisher.php
@@ -17,6 +17,7 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Domain\Finishers;
 
+use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Extbase\Domain\Model\FileReference;
 use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
 
@@ -38,6 +39,7 @@ class DeleteUploadsFinisher extends AbstractFinisher
     {
         $formRuntime = $this->finisherContext->getFormRuntime();
 
+        $uploadFolders = [];
         $elements = $formRuntime->getFormDefinition()->getRenderablesRecursively();
         foreach ($elements as $element) {
             if (!$element instanceof FileUpload) {
@@ -51,7 +53,44 @@ class DeleteUploadsFinisher extends AbstractFinisher
             if ($file instanceof FileReference) {
                 $file = $file->getOriginalResource();
             }
+
+            $folder = $file->getParentFolder();
+            $uploadFolders[$folder->getCombinedIdentifier()] = $folder;
+
             $file->getStorage()->deleteFile($file->getOriginalFile());
         }
+
+        $this->deleteEmptyUploadFolders($uploadFolders);
+    }
+
+    /**
+     * note:
+     * TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter::importUploadedResource()
+     * creates a sub-folder for file uploads (e.g. .../form_<40-chars-hash>/actual.file)
+     * @param Folder[] $folders
+     */
+    protected function deleteEmptyUploadFolders(array $folders): void
+    {
+        foreach ($folders as $folder) {
+            $parentFolder = $folder->getParentFolder();
+
+            if ($this->isEmptyFolder($folder)) {
+                $folder->delete();
+            }
+
+            if ($this->isEmptyFolder($parentFolder)) {
+                $parentFolder->delete();
+            }
+        }
+    }
+
+    /**
+     * @param Folder $folder
+     * @return bool
+     */
+    protected function isEmptyFolder(Folder $folder): bool
+    {
+        return $folder->getFileCount() === 0
+            && $folder->getStorage()->countFoldersInFolder($folder) === 0;
     }
 }
diff --git a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php
index 42258c7fc2f3..ca76dc6caa29 100644
--- a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php
+++ b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime.php
@@ -33,6 +33,7 @@ use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
 use TYPO3\CMS\Core\Utility\ArrayUtility;
 use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
 use TYPO3\CMS\Extbase\Error\Result;
 use TYPO3\CMS\Extbase\Mvc\Controller\Arguments;
 use TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext;
@@ -53,9 +54,12 @@ use TYPO3\CMS\Form\Domain\Model\Renderable\RootRenderableInterface;
 use TYPO3\CMS\Form\Domain\Model\Renderable\VariableRenderableInterface;
 use TYPO3\CMS\Form\Domain\Renderer\RendererInterface;
 use TYPO3\CMS\Form\Domain\Runtime\Exception\PropertyMappingException;
+use TYPO3\CMS\Form\Domain\Runtime\FormRuntime\FormSession;
+use TYPO3\CMS\Form\Domain\Runtime\FormRuntime\Lifecycle\AfterFormStateInitializedInterface;
 use TYPO3\CMS\Form\Exception as FormException;
 use TYPO3\CMS\Form\Mvc\Validation\EmptyValidator;
 use TYPO3\CMS\Frontend\Authentication\FrontendUserAuthentication;
+use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
 use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
 
 /**
@@ -125,6 +129,14 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      */
     protected $formState;
 
+    /**
+     * Individual unique random form session identifier valid
+     * for current user session. This value is not persisted server-side.
+     *
+     * @var FormSession|null
+     */
+    protected $formSession;
+
     /**
      * The current page is the page which will be displayed to the user
      * during rendering.
@@ -164,6 +176,11 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
      */
     protected $currentFinisher;
 
+    /**
+     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
+     */
+    protected $configurationManager;
+
     /**
      * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
      * @internal
@@ -182,6 +199,14 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
         $this->objectManager = $objectManager;
     }
 
+    /**
+     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
+     */
+    public function injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
+    {
+        $this->configurationManager = $configurationManager;
+    }
+
     /**
      * @param FormDefinition $formDefinition
      * @param Request $request
@@ -206,43 +231,79 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
     public function initializeObject()
     {
         $this->initializeCurrentSiteLanguage();
+        $this->initializeFormSessionFromRequest();
         $this->initializeFormStateFromRequest();
+        $this->triggerAfterFormStateInitialized();
         $this->processVariants();
         $this->initializeCurrentPageFromRequest();
         $this->initializeHoneypotFromRequest();
 
-        if (!$this->isFirstRequest() && $this->getRequest()->getMethod() === 'POST') {
+        // Only validate and set form values within the form state
+        // if the current request is not the very first request
+        // and the current request can be processed (POST request and uncached).
+        if (!$this->isFirstRequest() && $this->canProcessFormSubmission()) {
             $this->processSubmittedFormValues();
         }
 
         $this->renderHoneypot();
     }
 
+    /**
+     * @todo `FormRuntime::$formSession` is still vulnerable to session fixation unless a real cookie-based process is used
+     */
+    protected function initializeFormSessionFromRequest(): void
+    {
+        // Initialize the form session only if the current request can be processed
+        // (POST request and uncached) to ensure unique sessions for each form submitter.
+        if (!$this->canProcessFormSubmission()) {
+            return;
+        }
+
+        $sessionIdentifierFromRequest = $this->request->getInternalArgument('__session');
+        $this->formSession = GeneralUtility::makeInstance(FormSession::class, $sessionIdentifierFromRequest);
+    }
+
     /**
      * Initializes the current state of the form, based on the request
      * @throws BadRequestException
      */
     protected function initializeFormStateFromRequest()
     {
+        // Only try to reconstitute the form state if the current request
+        // is not the very first request and if the current request can
+        // be processed (POST request and uncached).
         $serializedFormStateWithHmac = $this->request->getInternalArgument('__state');
-        if ($serializedFormStateWithHmac === null) {
+        if ($serializedFormStateWithHmac === null || !$this->canProcessFormSubmission()) {
             $this->formState = GeneralUtility::makeInstance(FormState::class);
         } else {
             try {
                 $serializedFormState = $this->hashService->validateAndStripHmac($serializedFormStateWithHmac);
             } catch (InvalidHashException | InvalidArgumentForHashGenerationException $e) {
-                throw new BadRequestException('The HMAC of the form could not be validated.', 1581862823);
+                throw new BadRequestException('The HMAC of the form state could not be validated.', 1581862823);
             }
             $this->formState = unserialize(base64_decode($serializedFormState));
         }
     }
 
+    protected function triggerAfterFormStateInitialized(): void
+    {
+        foreach ($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterFormStateInitialized'] ?? [] as $className) {
+            $hookObj = GeneralUtility::makeInstance($className);
+            if ($hookObj instanceof AfterFormStateInitializedInterface) {
+                $hookObj->afterFormStateInitialized($this);
+            }
+        }
+    }
+
     /**
      * Initializes the current page data based on the current request, also modifiable by a hook
      */
     protected function initializeCurrentPageFromRequest()
     {
-        if (!$this->formState->isFormSubmitted()) {
+        // If there was no previous form submissions or if the current request
+        // can't be processed (no POST request and/or cached) then display the first
+        // form step
+        if (!$this->formState->isFormSubmitted() || !$this->canProcessFormSubmission()) {
             $this->currentPage = $this->formDefinition->getPageByIndex(0);
 
             if (!$this->currentPage->isEnabled()) {
@@ -478,6 +539,31 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
         return $this->lastDisplayedPage === null;
     }
 
+    /**
+     * @return bool
+     */
+    protected function isPostRequest(): bool
+    {
+        return $this->getRequest()->getMethod() === 'POST';
+    }
+
+    /**
+     * Determine whether the surrounding content object is cached.
+     * If no surrounding content object can be found (which would be strange)
+     * we assume a cached request for safety which means that an empty form
+     * will be rendered.
+     *
+     * @return bool
+     */
+    protected function isRenderedCached(): bool
+    {
+        $contentObject = $this->configurationManager->getContentObject();
+        return $contentObject === null
+            ? true
+            // @todo this does not work when rendering a cached `FLUIDTEMPLATE` (not nested in `COA_INT`)
+            : $contentObject->getUserObjectType() === ContentObjectRenderer::OBJECTTYPE_USER;
+    }
+
     /**
      * Runs through all validations
      */
@@ -709,6 +795,29 @@ class FormRuntime implements RootRenderableInterface, \ArrayAccess
         return $this->response;
     }
 
+    /**
+     * Only process values if there is a post request and if the
+     * surrounding content object is uncached.
+     * Is this not the case, all possible submitted values will be discarded
+     * and the first form step will be shown with an empty form state.
+     *
+     * @return bool
+     * @internal
+     */
+    public function canProcessFormSubmission(): bool
+    {
+        return $this->isPostRequest() && !$this->isRenderedCached();
+    }
+
+    /**
+     * @return FormSession|null
+     * @internal
+     */
+    public function getFormSession(): ?FormSession
+    {
+        return $this->formSession;
+    }
+
     /**
      * Returns the currently selected page
      *
diff --git a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/FormSession.php b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/FormSession.php
new file mode 100644
index 000000000000..73fe89bfbdc0
--- /dev/null
+++ b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/FormSession.php
@@ -0,0 +1,94 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
+
+use TYPO3\CMS\Core\Crypto\Random;
+use TYPO3\CMS\Core\Error\Http\BadRequestException;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
+use TYPO3\CMS\Extbase\Security\Exception\InvalidArgumentForHashGenerationException;
+use TYPO3\CMS\Extbase\Security\Exception\InvalidHashException;
+
+/**
+ * @internal
+ */
+class FormSession
+{
+    protected $identifier;
+
+    /**
+     * Factory to create the form session from the current state
+     *
+     * @param string|null $authenticatedIdentifier
+     * @throws BadRequestException
+     */
+    public function __construct(string $authenticatedIdentifier = null)
+    {
+        if ($authenticatedIdentifier === null) {
+            $this->identifier = $this->generateIdentifier();
+        } else {
+            $this->identifier = $this->validateIdentifier($authenticatedIdentifier);
+        }
+    }
+
+    /**
+     * @return string
+     * @internal
+     */
+    public function getIdentifier(): string
+    {
+        return $this->identifier;
+    }
+
+    /**
+     * Consumed by TYPO3\CMS\Form\ViewHelpers\FormViewHelper
+     *
+     * @return string
+     * @internal
+     */
+    public function getAuthenticatedIdentifier(): string
+    {
+        return GeneralUtility::makeInstance(HashService::class)
+            // restrict string expansion by adding some char ('|')
+            ->appendHmac($this->identifier . '|');
+    }
+
+    /**
+     * @return string
+     */
+    protected function generateIdentifier(): string
+    {
+        return GeneralUtility::makeInstance(Random::class)->generateRandomHexString(40);
+    }
+
+    /**
+     * @param string $authenticatedIdentifier
+     * @return string
+     * @throws BadRequestException
+     */
+    protected function validateIdentifier(string $authenticatedIdentifier): string
+    {
+        try {
+            $identifier = GeneralUtility::makeInstance(HashService::class)
+                ->validateAndStripHmac($authenticatedIdentifier);
+            return rtrim($identifier, '|');
+        } catch (InvalidHashException | InvalidArgumentForHashGenerationException $e) {
+            throw new BadRequestException('The HMAC of the form session could not be validated.', 1613300274);
+        }
+    }
+}
diff --git a/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/Lifecycle/AfterFormStateInitializedInterface.php b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/Lifecycle/AfterFormStateInitializedInterface.php
new file mode 100644
index 000000000000..78972e584221
--- /dev/null
+++ b/typo3/sysext/form/Classes/Domain/Runtime/FormRuntime/Lifecycle/AfterFormStateInitializedInterface.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Form\Domain\Runtime\FormRuntime\Lifecycle;
+
+use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
+
+/**
+ * Event is triggered with current form state and form session, which is
+ * not the case with e.g. `afterBuildingFinished`. Can be used to further
+ * enrich components with runtime state.
+ * @internal
+ */
+interface AfterFormStateInitializedInterface
+{
+    /**
+     * @param FormRuntime $formRuntime holding current form state and static form definition
+     */
+    public function afterFormStateInitialized(FormRuntime $formRuntime): void;
+}
diff --git a/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php b/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php
index 9b5cc59cc756..571a8ae20c80 100644
--- a/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php
+++ b/typo3/sysext/form/Classes/Mvc/Property/PropertyMappingConfiguration.php
@@ -25,6 +25,8 @@ use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
 use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
 use TYPO3\CMS\Form\Domain\Model\FormElements\FileUpload;
 use TYPO3\CMS\Form\Domain\Model\Renderable\RenderableInterface;
+use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
+use TYPO3\CMS\Form\Domain\Runtime\FormRuntime\Lifecycle\AfterFormStateInitializedInterface;
 use TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter;
 use TYPO3\CMS\Form\Mvc\Validation\MimeTypeValidator;
 
@@ -32,13 +34,16 @@ use TYPO3\CMS\Form\Mvc\Validation\MimeTypeValidator;
  * Scope: frontend
  * @internal
  */
-class PropertyMappingConfiguration
+class PropertyMappingConfiguration implements AfterFormStateInitializedInterface
 {
 
     /**
      * This hook is called for each form element after the class
      * TYPO3\CMS\Form\Domain\Factory\ArrayFormFactory has built the entire form.
      *
+     * It is invoked after the static form definition is ready, but without knowing
+     * about the individual state organized in `FormRuntime` and `FormState`.
+     *
      * @param RenderableInterface $renderable
      * @internal
      */
@@ -53,12 +58,17 @@ class PropertyMappingConfiguration
             // * Setup the storage:
             //   If the property "saveToFileMount" exist for this element it will be used.
             //   If this file mount or the property "saveToFileMount" does not exist
-            //   the folder in which the form definition lies (persistence identifier) will be used.
-            //   If the form is generated programmatically and therefore no
-            //   persistence identifier exist the default storage "1:/user_upload/" will be used.
+            //   the default storage "1:/user_uploads/" will be used. Uploads are placed
+            //   in a dedicated sub-folder (e.g. ".../form_<40-chars-hash>/actual.file").
 
+            /** @var UploadedFileReferenceConverter $typeConverter */
+            $typeConverter = GeneralUtility::makeInstance(ObjectManager::class)
+                ->get(UploadedFileReferenceConverter::class);
             /** @var \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration $propertyMappingConfiguration */
-            $propertyMappingConfiguration = $renderable->getRootForm()->getProcessingRule($renderable->getIdentifier())->getPropertyMappingConfiguration();
+            $propertyMappingConfiguration = $renderable->getRootForm()
+                ->getProcessingRule($renderable->getIdentifier())
+                ->getPropertyMappingConfiguration()
+                ->setTypeConverter($typeConverter);
 
             $allowedMimeTypes = [];
             $validators = [];
@@ -88,6 +98,7 @@ class PropertyMappingConfiguration
             if ($this->checkSaveFileMountAccess($saveToFileMountIdentifier)) {
                 $uploadConfiguration[UploadedFileReferenceConverter::CONFIGURATION_UPLOAD_FOLDER] = $saveToFileMountIdentifier;
             } else {
+                // @todo Why should uploaded files be stored to the same directory as the *.form.yaml definitions?
                 $persistenceIdentifier = $renderable->getRootForm()->getPersistenceIdentifier();
                 if (!empty($persistenceIdentifier)) {
                     $pathinfo = PathUtility::pathinfo($persistenceIdentifier);
@@ -97,7 +108,6 @@ class PropertyMappingConfiguration
                     }
                 }
             }
-
             $propertyMappingConfiguration->setTypeConverterOptions(UploadedFileReferenceConverter::class, $uploadConfiguration);
             return;
         }
@@ -138,4 +148,46 @@ class PropertyMappingConfiguration
             return false;
         }
     }
+
+    /**
+     * @param FormRuntime $formRuntime holding current form state and static form definition
+     */
+    public function afterFormStateInitialized(FormRuntime $formRuntime): void
+    {
+        foreach ($formRuntime->getFormDefinition()->getRenderablesRecursively() as $renderable) {
+            $this->adjustPropertyMappingForFileUploadsAtRuntime($formRuntime, $renderable);
+        }
+    }
+
+    /**
+     * If the form runtime is able to process form submissions
+     * (determined by $formRuntime->canProcessFormSubmission()) then a
+     * 'form session' is available.
+     * This form session identifier will be used to deriving storage sub-folders
+     * for the file uploads.
+     * This is done by setting `UploadedFileReferenceConverter::CONFIGURATION_UPLOAD_SEED`
+     * type converter option.
+     *
+     * @param FormRuntime $formRuntime
+     * @param RenderableInterface $renderable
+     */
+    protected function adjustPropertyMappingForFileUploadsAtRuntime(
+        FormRuntime $formRuntime,
+        RenderableInterface $renderable
+    ): void {
+        if (!$renderable instanceof FileUpload
+            || $formRuntime->getFormSession() === null
+            || !$formRuntime->canProcessFormSubmission()
+        ) {
+            return;
+        }
+        $renderable->getRootForm()
+            ->getProcessingRule($renderable->getIdentifier())
+            ->getPropertyMappingConfiguration()
+            ->setTypeConverterOption(
+                UploadedFileReferenceConverter::class,
+                UploadedFileReferenceConverter::CONFIGURATION_UPLOAD_SEED,
+                $formRuntime->getFormSession()->getIdentifier()
+            );
+    }
 }
diff --git a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFile.php b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFile.php
new file mode 100644
index 000000000000..89284cd6417e
--- /dev/null
+++ b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFile.php
@@ -0,0 +1,106 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Form\Mvc\Property\TypeConverter;
+
+use TYPO3\CMS\Core\Type\File\FileInfo;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Form\Mvc\Property\Exception\TypeConverterException;
+
+/**
+ * Use in `UploadedFileReferenceConverter` handling file uploads.
+ * `PseudoFile` and `PseudoFileReference` are independent and not associated.
+ * @internal
+ */
+class PseudoFile
+{
+    /**
+     * @var \SplFileInfo
+     */
+    protected $nameFileInfo;
+
+    /**
+     * @var FileInfo
+     */
+    protected $payloadFileInfo;
+
+    /**
+     * @var string
+     */
+    protected $payloadFilePath;
+
+    /**
+     * see https://www.php.net/manual/en/features.file-upload.post-method.php
+     *
+     * @param array $uploadInfo as in $_FILES
+     * @throws TypeConverterException
+     */
+    public function __construct(array $uploadInfo)
+    {
+        if (!isset($uploadInfo['tmp_name']) || !isset($uploadInfo['name'])) {
+            throw new TypeConverterException(
+                'Could not determine uploaded file',
+                1602103603
+            );
+        }
+        $this->nameFileInfo = new \SplFileInfo($uploadInfo['name']);
+        $this->payloadFilePath = $uploadInfo['tmp_name'];
+        $this->payloadFileInfo = GeneralUtility::makeInstance(FileInfo::class, $uploadInfo['tmp_name']);
+    }
+
+    public function getName(): string
+    {
+        return $this->nameFileInfo->getBasename();
+    }
+
+    public function getNameWithoutExtension(): string
+    {
+        // `image...png`
+        return rtrim(
+            $this->nameFileInfo->getBasename($this->getExtension()),
+            '.'
+        );
+    }
+
+    public function getExtension(): string
+    {
+        return $this->nameFileInfo->getExtension();
+    }
+
+    public function getSize(): ?int
+    {
+        // returns `null` in case size is empty (includes `0`)
+        // @see \TYPO3\CMS\Core\Resource\AbstractFile::getSize()
+        return $this->payloadFileInfo->getSize() ?: null;
+    }
+
+    public function getMimeType(): ?string
+    {
+        $mimeType = $this->payloadFileInfo->getMimeType();
+        return is_string($mimeType) ? $mimeType : null;
+    }
+
+    public function getContents(): string
+    {
+        return file_get_contents($this->payloadFilePath);
+    }
+
+    public function getSha1(): string
+    {
+        return sha1_file($this->payloadFilePath);
+    }
+}
diff --git a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFileReference.php b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFileReference.php
new file mode 100644
index 000000000000..bdcdb61bb181
--- /dev/null
+++ b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/PseudoFileReference.php
@@ -0,0 +1,86 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Form\Mvc\Property\TypeConverter;
+
+use TYPO3\CMS\Core\Resource\ResourceFactory;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Domain\Model\FileReference;
+
+/**
+ * Use in `UploadedFileReferenceConverter` handling file uploads.
+ * `PseudoFile` and `PseudoFileReference` are independent and not associated.
+ *
+ * This facade hides (potential) internal properties from being exposed to the
+ * public during serialization. `sys_file_reference.uid` or `sys_file.uid` are
+ * the only aspects used externally. In case of missing integrity checks during
+ * deserialization, both properties would allow direct object reference (IDOR).
+ *
+ * @internal
+ */
+class PseudoFileReference extends FileReference
+{
+    /**
+     * @var int|null
+     */
+    private $_uid;
+
+    /**
+     * @var int|null
+     */
+    private $_uidLocal;
+
+    public function __sleep(): array
+    {
+        // in case this is a persisted file reference, use it directly as reference
+        // as a consequence, in-memory changes are lost and have to be persisted first
+        // (it seems that in ext:form a `FileReference` was never persisted)
+        if ($this->uid > 0) {
+            $this->_uid = (int)$this->uid;
+            return ['_uid'];
+        }
+        if ($this->getOriginalResource()->getUid() > 0) {
+            $this->_uid = (int)$this->getOriginalResource()->getUid();
+            return ['_uid'];
+        }
+        // in case this is a transient file reference, just expose the associated `sys_file.uid`
+        // (based on previous comments, this is the most probably case in ext:form)
+        $this->_uidLocal = (int)$this->getOriginalResource()->getOriginalFile()->getUid();
+        return ['_uidLocal'];
+    }
+
+    public function __wakeup(): void
+    {
+        $factory = GeneralUtility::makeInstance(ResourceFactory::class);
+        if ($this->_uid > 0) {
+            $this->originalResource = $factory->getFileReferenceObject($this->_uid);
+        } elseif ($this->_uidLocal > 0) {
+            $this->originalResource = $factory->createFileReferenceObject([
+                'uid_local' => $this->_uidLocal,
+                'uid_foreign' => 0,
+                'uid' => 0,
+                'crop' => null,
+            ]);
+        } else {
+            throw new \LogicException(
+                sprintf('Cannot unserialize %s', static::class),
+                1613216548
+            );
+        }
+        unset($this->_uid, $this->_uidLocal);
+    }
+}
diff --git a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php
index 16f64c58d9fe..2539b7109949 100644
--- a/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php
+++ b/typo3/sysext/form/Classes/Mvc/Property/TypeConverter/UploadedFileReferenceConverter.php
@@ -17,15 +17,17 @@ declare(strict_types=1);
 
 namespace TYPO3\CMS\Form\Mvc\Property\TypeConverter;
 
+use TYPO3\CMS\Core\Crypto\Random;
 use TYPO3\CMS\Core\Log\LogManager;
+use TYPO3\CMS\Core\Resource\Exception\FolderDoesNotExistException;
 use TYPO3\CMS\Core\Resource\File as File;
 use TYPO3\CMS\Core\Resource\FileReference as CoreFileReference;
+use TYPO3\CMS\Core\Resource\Folder;
 use TYPO3\CMS\Core\Resource\ResourceFactory;
 use TYPO3\CMS\Core\Resource\Security\FileNameValidator;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Core\Utility\StringUtility;
 use TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder;
-use TYPO3\CMS\Extbase\Domain\Model\FileReference as ExtbaseFileReference;
 use TYPO3\CMS\Extbase\Error\Error;
 use TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface;
 use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;
@@ -34,6 +36,7 @@ use TYPO3\CMS\Extbase\Security\Cryptography\HashService;
 use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
 use TYPO3\CMS\Form\Mvc\Property\Exception\TypeConverterException;
 use TYPO3\CMS\Form\Service\TranslationService;
+use TYPO3\CMS\Form\Slot\ResourcePublicationSlot;
 
 /**
  * Class UploadedFileReferenceConverter
@@ -54,6 +57,11 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
      */
     const CONFIGURATION_UPLOAD_CONFLICT_MODE = 2;
 
+    /**
+     * Random seed to be used for deriving storage sub-folders.
+     */
+    const CONFIGURATION_UPLOAD_SEED = 3;
+
     /**
      * Validator for file types
      */
@@ -79,7 +87,7 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
     /**
      * @var string
      */
-    protected $targetType = ExtbaseFileReference::class;
+    protected $targetType = PseudoFileReference::class;
 
     /**
      * Take precedence over the available FileReferenceConverter
@@ -89,7 +97,7 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
     protected $priority = 12;
 
     /**
-     * @var \TYPO3\CMS\Core\Resource\FileInterface[]
+     * @var PseudoFileReference[]
      */
     protected $convertedResources = [];
 
@@ -148,6 +156,8 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
      */
     public function convertFrom($source, $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
     {
+        // slot/listener using `FileDumpController` instead of direct public URL in (later) rendering process
+        $resourcePublicationSlot = GeneralUtility::makeInstance(ResourcePublicationSlot::class);
         if (!isset($source['error']) || $source['error'] === \UPLOAD_ERR_NO_FILE) {
             if (isset($source['submittedFile']['resourcePointer'])) {
                 try {
@@ -156,12 +166,15 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
                     $resourcePointer = $this->hashService->validateAndStripHmac($source['submittedFile']['resourcePointer']);
                     if (strpos($resourcePointer, 'file:') === 0) {
                         $fileUid = (int)substr($resourcePointer, 5);
-                        return $this->createFileReferenceFromFalFileObject($this->resourceFactory->getFileObject($fileUid));
+                        $resource = $this->createFileReferenceFromFalFileObject($this->resourceFactory->getFileObject($fileUid));
+                    } else {
+                        $resource = $this->createFileReferenceFromFalFileReferenceObject(
+                            $this->resourceFactory->getFileReferenceObject($resourcePointer),
+                            (int)$resourcePointer
+                        );
                     }
-                    return $this->createFileReferenceFromFalFileReferenceObject(
-                        $this->resourceFactory->getFileReferenceObject($resourcePointer),
-                        (int)$resourcePointer
-                    );
+                    $resourcePublicationSlot->add($resource->getOriginalResource()->getOriginalFile());
+                    return $resource;
                 } catch (\InvalidArgumentException $e) {
                     // Nothing to do. No file is uploaded and resource pointer is invalid. Discard!
                 }
@@ -183,6 +196,7 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
 
         try {
             $resource = $this->importUploadedResource($source, $configuration);
+            $resourcePublicationSlot->add($resource->getOriginalResource()->getOriginalFile());
         } catch (TypeConverterException $e) {
             return $e->getError();
         } catch (\Exception $e) {
@@ -198,35 +212,42 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
      *
      * @param array $uploadInfo
      * @param PropertyMappingConfigurationInterface $configuration
-     * @return ExtbaseFileReference
+     * @return PseudoFileReference
      */
     protected function importUploadedResource(
         array $uploadInfo,
         PropertyMappingConfigurationInterface $configuration
-    ): ExtbaseFileReference {
+    ): PseudoFileReference {
         if (!GeneralUtility::makeInstance(FileNameValidator::class)->isValid($uploadInfo['name'])) {
             throw new TypeConverterException('Uploading files with PHP file extensions is not allowed!', 1471710357);
         }
-
+        // `CONFIGURATION_UPLOAD_SEED` is expected to be defined
+        // if it's not given any random seed is generated, instead of throwing an exception
+        $seed = $configuration->getConfigurationValue(self::class, self::CONFIGURATION_UPLOAD_SEED)
+            ?: GeneralUtility::makeInstance(Random::class)->generateRandomHexString(40);
         $uploadFolderId = $configuration->getConfigurationValue(self::class, self::CONFIGURATION_UPLOAD_FOLDER) ?: $this->defaultUploadFolder;
         $conflictMode = $configuration->getConfigurationValue(self::class, self::CONFIGURATION_UPLOAD_CONFLICT_MODE) ?: $this->defaultConflictMode;
-
-        $uploadFolder = $this->resourceFactory->retrieveFileOrFolderObject($uploadFolderId);
-        $uploadedFile = $uploadFolder->addUploadedFile($uploadInfo, $conflictMode);
+        $pseudoFile = GeneralUtility::makeInstance(PseudoFile::class, $uploadInfo);
 
         $validators = $configuration->getConfigurationValue(self::class, self::CONFIGURATION_FILE_VALIDATORS);
         if (is_array($validators)) {
             foreach ($validators as $validator) {
                 if ($validator instanceof AbstractValidator) {
-                    $validationResult = $validator->validate($uploadedFile);
+                    $validationResult = $validator->validate($pseudoFile);
                     if ($validationResult->hasErrors()) {
-                        $uploadedFile->getStorage()->deleteFile($uploadedFile);
                         throw TypeConverterException::fromError($validationResult->getErrors()[0]);
                     }
                 }
             }
         }
 
+        $uploadFolder = $this->provideUploadFolder($uploadFolderId);
+        // current folder name, derived from public random seed (`formSession`)
+        $currentName = 'form_' . GeneralUtility::hmac($seed, self::class);
+        $uploadFolder = $this->provideTargetFolder($uploadFolder, $currentName);
+        // sub-folder in $uploadFolder with 160 bit of derived entropy (.../form_<40-chars-hash>/actual.file)
+        $uploadedFile = $uploadFolder->addUploadedFile($uploadInfo, $conflictMode);
+
         $resourcePointer = isset($uploadInfo['submittedFile']['resourcePointer']) && strpos($uploadInfo['submittedFile']['resourcePointer'], 'file:') === false
             ? (int)$this->hashService->validateAndStripHmac($uploadInfo['submittedFile']['resourcePointer'])
             : null;
@@ -239,12 +260,12 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
     /**
      * @param File $file
      * @param int $resourcePointer
-     * @return ExtbaseFileReference
+     * @return PseudoFileReference
      */
     protected function createFileReferenceFromFalFileObject(
         File $file,
         int $resourcePointer = null
-    ): ExtbaseFileReference {
+    ): PseudoFileReference {
         $fileReference = $this->resourceFactory->createFileReferenceObject(
             [
                 'uid_local' => $file->getUid(),
@@ -263,16 +284,16 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
      *
      * @param CoreFileReference $falFileReference
      * @param int $resourcePointer
-     * @return ExtbaseFileReference
+     * @return PseudoFileReference
      */
     protected function createFileReferenceFromFalFileReferenceObject(
         CoreFileReference $falFileReference,
         int $resourcePointer = null
-    ): ExtbaseFileReference {
+    ): PseudoFileReference {
         if ($resourcePointer === null) {
-            $fileReference = $this->objectManager->get(ExtbaseFileReference::class);
+            $fileReference = $this->objectManager->get(PseudoFileReference::class);
         } else {
-            $fileReference = $this->persistenceManager->getObjectByIdentifier($resourcePointer, ExtbaseFileReference::class, false);
+            $fileReference = $this->persistenceManager->getObjectByIdentifier($resourcePointer, PseudoFileReference::class, false);
         }
 
         $fileReference->setOriginalResource($falFileReference);
@@ -316,4 +337,51 @@ class UploadedFileReferenceConverter extends AbstractTypeConverter
                 return TranslationService::getInstance()->translate('upload.error.150530348', null, 'EXT:form/Resources/Private/Language/locallang.xlf');
         }
     }
+
+    /**
+     * Ensures that upload folder exists, creates it if it does not.
+     *
+     * @param string $uploadFolderIdentifier
+     * @return Folder
+     */
+    protected function provideUploadFolder(string $uploadFolderIdentifier): Folder
+    {
+        try {
+            return $this->resourceFactory->getFolderObjectFromCombinedIdentifier($uploadFolderIdentifier);
+        } catch (FolderDoesNotExistException $exception) {
+            [$storageId, $storagePath] = explode(':', $uploadFolderIdentifier, 2);
+            $storage = $this->resourceFactory->getStorageObject($storageId);
+            $folderNames = GeneralUtility::trimExplode('/', $storagePath, true);
+            $uploadFolder = $this->provideTargetFolder($storage->getRootLevelFolder(), ...$folderNames);
+            $this->provideFolderInitialization($uploadFolder);
+            return $uploadFolder;
+        }
+    }
+
+    /**
+     * Ensures that particular target folder exists, creates it if it does not.
+     *
+     * @param Folder $parentFolder
+     * @param string $folderName
+     * @return Folder
+     */
+    protected function provideTargetFolder(Folder $parentFolder, string $folderName): Folder
+    {
+        return $parentFolder->hasFolder($folderName)
+            ? $parentFolder->getSubfolder($folderName)
+            : $parentFolder->createFolder($folderName);
+    }
+
+    /**
+     * Creates empty index.html file to avoid directory indexing,
+     * in case it does not exist yet.
+     *
+     * @param Folder $parentFolder
+     */
+    protected function provideFolderInitialization(Folder $parentFolder): void
+    {
+        if (!$parentFolder->hasFile('index.html')) {
+            $parentFolder->createFile('index.html');
+        }
+    }
 }
diff --git a/typo3/sysext/form/Classes/Mvc/Validation/FileSizeValidator.php b/typo3/sysext/form/Classes/Mvc/Validation/FileSizeValidator.php
index b05db8897e4d..1b248a1f72d0 100644
--- a/typo3/sysext/form/Classes/Mvc/Validation/FileSizeValidator.php
+++ b/typo3/sysext/form/Classes/Mvc/Validation/FileSizeValidator.php
@@ -21,6 +21,7 @@ use TYPO3\CMS\Core\Resource\File;
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Extbase\Domain\Model\FileReference;
 use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
+use TYPO3\CMS\Form\Mvc\Property\TypeConverter\PseudoFile;
 use TYPO3\CMS\Form\Mvc\Validation\Exception\InvalidValidationOptionsException;
 
 /**
@@ -42,14 +43,18 @@ class FileSizeValidator extends AbstractValidator
     /**
      * The given value is valid
      *
-     * @param FileReference|File $resource
+     * @param FileReference|File|PseudoFile $resource
      */
     public function isValid($resource)
     {
         $this->validateOptions();
         if ($resource instanceof FileReference) {
-            $resource = $resource->getOriginalResource();
-        } elseif (!$resource instanceof File) {
+            $fileSize = $resource->getOriginalResource()->getSize();
+        } elseif ($resource instanceof File) {
+            $fileSize = $resource->getSize();
+        } elseif ($resource instanceof PseudoFile) {
+            $fileSize = $resource->getSize();
+        } else {
             $this->addError(
                 $this->translateErrorMessage(
                     'validation.error.1505303626',
@@ -60,7 +65,6 @@ class FileSizeValidator extends AbstractValidator
             return;
         }
 
-        $fileSize = $resource->getSize();
         $minFileSize = GeneralUtility::getBytesFromSizeMeasurement($this->options['minimum']);
         $maxFileSize = GeneralUtility::getBytesFromSizeMeasurement($this->options['maximum']);
 
diff --git a/typo3/sysext/form/Classes/Mvc/Validation/MimeTypeValidator.php b/typo3/sysext/form/Classes/Mvc/Validation/MimeTypeValidator.php
index e2a5496dbcfa..4bd34b257de1 100644
--- a/typo3/sysext/form/Classes/Mvc/Validation/MimeTypeValidator.php
+++ b/typo3/sysext/form/Classes/Mvc/Validation/MimeTypeValidator.php
@@ -18,8 +18,10 @@ declare(strict_types=1);
 namespace TYPO3\CMS\Form\Mvc\Validation;
 
 use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\MimeTypeDetector;
 use TYPO3\CMS\Extbase\Domain\Model\FileReference;
 use TYPO3\CMS\Extbase\Validation\Validator\AbstractValidator;
+use TYPO3\CMS\Form\Mvc\Property\TypeConverter\PseudoFile;
 use TYPO3\CMS\Form\Mvc\Validation\Exception\InvalidValidationOptionsException;
 
 /**
@@ -42,15 +44,22 @@ class MimeTypeValidator extends AbstractValidator
      *
      * Note: a value of NULL or empty string ('') is considered valid
      *
-     * @param FileReference|File $resource The resource that should be validated
+     * @param FileReference|File|PseudoFile $resource The resource that should be validated
      */
     public function isValid($resource)
     {
         $this->validateOptions();
 
         if ($resource instanceof FileReference) {
-            $resource = $resource->getOriginalResource();
-        } elseif (!$resource instanceof File) {
+            $mimeType = $resource->getOriginalResource()->getMimeType();
+            $fileExtension = $resource->getOriginalResource()->getExtension();
+        } elseif ($resource instanceof File) {
+            $mimeType = $resource->getMimeType();
+            $fileExtension = $resource->getExtension();
+        } elseif ($resource instanceof PseudoFile) {
+            $mimeType = $resource->getMimeType();
+            $fileExtension = $resource->getExtension();
+        } else {
             $this->addError(
                 $this->translateErrorMessage(
                     'validation.error.1471708997',
@@ -62,16 +71,33 @@ class MimeTypeValidator extends AbstractValidator
         }
 
         $allowedMimeTypes = $this->options['allowedMimeTypes'];
-        if (!in_array($resource->getMimeType(), $allowedMimeTypes, true)) {
+        if (!in_array($mimeType, $allowedMimeTypes, true)) {
             $this->addError(
                 $this->translateErrorMessage(
                     'validation.error.1471708998',
                     'form',
-                    [$resource->getMimeType()]
+                    [$mimeType]
                 ) ?? '',
                 1471708998,
-                [$resource->getMimeType()]
+                [$mimeType]
             );
+        } else {
+            // The mime-type which was detected by FAL matches, but the file name does not match.
+            // Example: myfile.txt is actually a PDF file (defined by mime-type), but .txt is not associated
+            // for application/pdf, so this is not valid. The file extension of the uploaded file must match
+            // the mime-type for this file.
+            $assumedMimesTypeOfFileExtension = (new MimeTypeDetector())->getMimeTypesForFileExtension($fileExtension);
+            if (empty(array_intersect($allowedMimeTypes, $assumedMimesTypeOfFileExtension))) {
+                $this->addError(
+                    $this->translateErrorMessage(
+                        'validation.error.1613126216',
+                        'form',
+                        [$fileExtension]
+                    ) ?? '',
+                    1613126216,
+                    [$fileExtension]
+                );
+            }
         }
     }
 
diff --git a/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php b/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php
new file mode 100644
index 000000000000..748133913083
--- /dev/null
+++ b/typo3/sysext/form/Classes/Slot/ResourcePublicationSlot.php
@@ -0,0 +1,83 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Form\Slot;
+
+use TYPO3\CMS\Core\Core\Environment;
+use TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent;
+use TYPO3\CMS\Core\Resource\File;
+use TYPO3\CMS\Core\Resource\FileInterface;
+use TYPO3\CMS\Core\Resource\ProcessedFile;
+use TYPO3\CMS\Core\Resource\ResourceInterface;
+use TYPO3\CMS\Core\SingletonInterface;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Core\Utility\PathUtility;
+
+/**
+ * A PSR-14 event listener for using FAL resources in public (frontend)
+ *
+ * @internal will be renamed at some point.
+ */
+class ResourcePublicationSlot implements SingletonInterface
+{
+    /**
+     * @var list<string>
+     */
+    protected $fileIdentifiers = [];
+
+    public function getPublicUrl(GeneratePublicUrlForResourceEvent $event): void
+    {
+        $resource = $event->getResource();
+        if (!$resource instanceof FileInterface
+            || !$this->has($resource)
+            || $event->getStorage()->getDriverType() !== 'Local'
+        ) {
+            return;
+        }
+        $event->setPublicUrl($this->getStreamUrl($event->getResource()));
+    }
+
+    public function add(FileInterface $resource): void
+    {
+        if ($this->has($resource)) {
+            return;
+        }
+        $this->fileIdentifiers[] = $resource->getIdentifier();
+    }
+
+    public function has(FileInterface $resource): bool
+    {
+        return in_array($resource->getIdentifier(), $this->fileIdentifiers, true);
+    }
+
+    protected function getStreamUrl(ResourceInterface $resource): string
+    {
+        $queryParameterArray = ['eID' => 'dumpFile', 't' => ''];
+        if ($resource instanceof File) {
+            $queryParameterArray['f'] = $resource->getUid();
+            $queryParameterArray['t'] = 'f';
+        } elseif ($resource instanceof ProcessedFile) {
+            $queryParameterArray['p'] = $resource->getUid();
+            $queryParameterArray['t'] = 'p';
+        }
+
+        $queryParameterArray['token'] = GeneralUtility::hmac(implode('|', $queryParameterArray), 'resourceStorageDumpFile');
+        $publicUrl = GeneralUtility::locationHeaderUrl(PathUtility::getAbsoluteWebPath(Environment::getPublicPath() . '/index.php'));
+        $publicUrl .= '?' . http_build_query($queryParameterArray, '', '&', PHP_QUERY_RFC3986);
+        return $publicUrl;
+    }
+}
diff --git a/typo3/sysext/form/Classes/ViewHelpers/FormViewHelper.php b/typo3/sysext/form/Classes/ViewHelpers/FormViewHelper.php
index 5edd62a41cf4..b92488c419d6 100644
--- a/typo3/sysext/form/Classes/ViewHelpers/FormViewHelper.php
+++ b/typo3/sysext/form/Classes/ViewHelpers/FormViewHelper.php
@@ -23,6 +23,7 @@ namespace TYPO3\CMS\Form\ViewHelpers;
 
 use TYPO3\CMS\Core\Utility\GeneralUtility;
 use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper as FluidFormViewHelper;
+use TYPO3\CMS\Form\Domain\Runtime\FormRuntime;
 use TYPO3Fluid\Fluid\Core\ViewHelper\TagBuilder;
 
 /**
@@ -40,14 +41,38 @@ class FormViewHelper extends FluidFormViewHelper
      * @return string Hidden fields with referrer information
      */
     protected function renderHiddenReferrerFields()
+    {
+        $formRuntime = $this->getFormRuntime();
+        $prefix = $this->prefixFieldName($this->getFormObjectName());
+
+        $markup = $this->createHiddenInputElement(
+            $prefix . '[__state]',
+            $this->hashService->appendHmac(
+                base64_encode(serialize($formRuntime->getFormState()))
+            )
+        );
+
+        // ONLY assign `__session` if form is performing (uncached)
+        if ($formRuntime->canProcessFormSubmission() && $formRuntime->getFormSession() !== null) {
+            $markup .= $this->createHiddenInputElement(
+                $prefix . '[__session]',
+                $formRuntime->getFormSession()->getAuthenticatedIdentifier()
+            );
+        }
+        return $markup;
+    }
+
+    /**
+     * @param string $name
+     * @param string $value
+     * @return string
+     */
+    protected function createHiddenInputElement(string $name, string $value): string
     {
         $tagBuilder = GeneralUtility::makeInstance(TagBuilder::class, 'input');
         $tagBuilder->addAttribute('type', 'hidden');
-        $stateName = $this->prefixFieldName($this->arguments['object']->getFormDefinition()->getIdentifier()) . '[__state]';
-        $tagBuilder->addAttribute('name', $stateName);
-
-        $serializedFormState = base64_encode(serialize($this->arguments['object']->getFormState()));
-        $tagBuilder->addAttribute('value', $this->hashService->appendHmac($serializedFormState));
+        $tagBuilder->addAttribute('name', $name);
+        $tagBuilder->addAttribute('value', $value);
         return $tagBuilder->render();
     }
 
@@ -59,6 +84,11 @@ class FormViewHelper extends FluidFormViewHelper
      */
     protected function getFormObjectName()
     {
-        return $this->arguments['object']->getFormDefinition()->getIdentifier();
+        return $this->getFormRuntime()->getFormDefinition()->getIdentifier();
+    }
+
+    protected function getFormRuntime(): FormRuntime
+    {
+        return $this->arguments['object'];
     }
 }
diff --git a/typo3/sysext/form/Configuration/Services.yaml b/typo3/sysext/form/Configuration/Services.yaml
index f90443d7f76e..1cdce1dfd1cb 100644
--- a/typo3/sysext/form/Configuration/Services.yaml
+++ b/typo3/sysext/form/Configuration/Services.yaml
@@ -8,6 +8,13 @@ services:
     resource: '../Classes/*'
     exclude: '../Classes/{Domain/Model}'
 
+  TYPO3\CMS\Form\Slot\ResourcePublicationSlot:
+    tags:
+      - name: event.listener
+        identifier: 'form-framework/resource-getPublicUrl'
+        method: 'getPublicUrl'
+        event: TYPO3\CMS\Core\Resource\Event\GeneratePublicUrlForResourceEvent
+
   TYPO3\CMS\Form\Slot\FilePersistenceSlot:
     tags:
       - name: event.listener
diff --git a/typo3/sysext/form/Resources/Private/Language/locallang.xlf b/typo3/sysext/form/Resources/Private/Language/locallang.xlf
index 8b6a25fdb31b..562e13238d60 100644
--- a/typo3/sysext/form/Resources/Private/Language/locallang.xlf
+++ b/typo3/sysext/form/Resources/Private/Language/locallang.xlf
@@ -79,6 +79,9 @@
             <trans-unit id="validation.error.1471708998" resname="validation.error.1471708998" xml:space="preserve">
                 <source>You entered an incorrect media type, "%s" is not allowed for this file. Please refer to the description of this field.</source>
             </trans-unit>
+            <trans-unit id="validation.error.1613126216" resname="validation.error.1613126216" xml:space="preserve">
+                <source>The file extension provided "%s" does not match to expected media types. Please refer to the description of this field.</source>
+            </trans-unit>
             <trans-unit id="validation.error.1476396435" resname="validation.error.1476396435" xml:space="preserve">
                 <source>You must NOT fill this field.</source>
             </trans-unit>
diff --git a/typo3/sysext/form/Tests/Functional/Mvc/Validation/MimeTypeValidatorTest.php b/typo3/sysext/form/Tests/Functional/Mvc/Validation/MimeTypeValidatorTest.php
new file mode 100644
index 000000000000..aeac8ea83cbc
--- /dev/null
+++ b/typo3/sysext/form/Tests/Functional/Mvc/Validation/MimeTypeValidatorTest.php
@@ -0,0 +1,143 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+namespace TYPO3\CMS\Form\Tests\Functional\Mvc\Validation;
+
+use org\bovigo\vfs\vfsStream;
+use org\bovigo\vfs\vfsStreamDirectory;
+use TYPO3\CMS\Core\Localization\LanguageService;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Error\Error;
+use TYPO3\CMS\Form\Mvc\Property\TypeConverter\PseudoFile;
+use TYPO3\CMS\Form\Mvc\Validation\MimeTypeValidator;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class MimeTypeValidatorTest extends FunctionalTestCase
+{
+    /**
+     * @var string[]
+     */
+    protected $coreExtensionsToLoad = [
+        'form',
+    ];
+
+    /**
+     * @var array<string, string>
+     */
+    private $files = [
+        'file.exe' => "MZ\x90\x00\x03\x00",
+        'file.zip' => "PK\x03\x04",
+        'file.jpg' => "\xFF\xD8\xFF\xDB",
+        'file.gif' => 'GIF87a',
+        'file.pdf' => '%PDF-',
+    ];
+
+    /**
+     * @var vfsStreamDirectory
+     */
+    private $tmp;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $GLOBALS['LANG'] = GeneralUtility::makeInstance(LanguageService::class);
+        $this->tmp = vfsStream::setup('tmp', null, $this->files);
+    }
+
+    protected function tearDown(): void
+    {
+        unset($GLOBALS['LANG'], $this->tmp);
+        parent::tearDown();
+    }
+
+    public function dataProvider(): array
+    {
+        // error-codes
+        // + 1471708998: mime-type not allowed
+        // + 1613126216: mime-type to file-extension mismatch
+        return [
+            'submitted gif as upload.gif' => [
+                [
+                    'tmp_name' => 'vfs://tmp/file.gif',
+                    'name' => 'upload.gif',
+                    'type' => 'does/not-matter',
+                ],
+                ['image/gif'],
+            ],
+            'submitted jpg as upload.jpg' => [
+                [
+                    'tmp_name' => 'vfs://tmp/file.jpg',
+                    'name' => 'upload.jpg',
+                    'type' => 'does/not-matter',
+                ],
+                ['image/jpeg'],
+            ],
+            'submitted pdf as upload.pdf' => [
+                [
+                    'tmp_name' => 'vfs://tmp/file.pdf',
+                    'name' => 'upload.pdf',
+                    'type' => 'does/not-matter',
+                ],
+                ['application/pdf'],
+            ],
+            'submitted exe as upload.exe' => [
+                [
+                    'tmp_name' => 'vfs://tmp/file.exe',
+                    'name' => 'upload.exe',
+                    'type' => 'does/not-matter',
+                ], // upload data (as in $_FILES)
+                ['image/gif'], // allowed mime-types
+                [1471708998], // expected error-codes
+            ],
+            'submitted gif as upload.exe' => [
+                [
+                    'tmp_name' => 'vfs://tmp/file.gif',
+                    'name' => 'upload.exe',
+                    'type' => 'does/not-matter',
+                ], // upload data (as in $_FILES)
+                ['image/gif'], // allowed mime-types
+                [1613126216], // expected error-codes
+            ],
+        ];
+    }
+
+    /**
+     * @param array<string, int|string> $uploadData
+     * @param List<string> $allowedMimeTypes
+     * @param List<int> $expectedErrorCodes
+     *
+     * @test
+     * @dataProvider dataProvider
+     */
+    public function someTest(array $uploadData, array $allowedMimeTypes, array $expectedErrorCodes = []): void
+    {
+        $uploadData['error'] = \UPLOAD_ERR_OK;
+        $uploadData['size'] = filesize($uploadData['tmp_name']);
+
+        $validator = new MimeTypeValidator(['allowedMimeTypes' => $allowedMimeTypes]);
+
+        $resource = new PseudoFile($uploadData);
+        $result = $validator->validate($resource);
+        $errorCodes = array_map([$this, 'resolveErrorCode'], $result->getErrors());
+        self::assertSame($expectedErrorCodes, $errorCodes);
+    }
+
+    private function resolveErrorCode(Error $error)
+    {
+        return $error->getCode();
+    }
+}
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php b/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php
index 94c3dcbd8f14..be20ccd9cae6 100644
--- a/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php
+++ b/typo3/sysext/form/Tests/Unit/Mvc/Property/PropertyMappingConfigurationTest.php
@@ -120,9 +120,12 @@ class PropertyMappingConfigurationTest extends UnitTestCase
         // Resource Factory
         /** @var \PHPUnit\Framework\MockObject\MockObject|ResourceFactory $resourceFactory */
         $resourceFactory = $this->createMock(ResourceFactory::class);
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager (in order to return mocked Resource Factory)
         $objectManager = $this->prophesize(ObjectManager::class);
+        $objectManager->get(UploadedFileReferenceConverter::class)->willReturn($typeConverter);
         $objectManager->get(MimeTypeValidator::class)->willReturn($mimeTypeValidator);
         $objectManager->get(ResourceFactory::class)->willReturn($resourceFactory);
         GeneralUtility::setSingletonInstance(ObjectManager::class, $objectManager->reveal());
@@ -161,9 +164,12 @@ class PropertyMappingConfigurationTest extends UnitTestCase
             ->setMethods(['__construct'])
             ->disableOriginalConstructor()
             ->getMock();
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager to return the MimeTypeValidator
         $objectManager = $this->prophesize(ObjectManager::class);
+        $objectManager->get(UploadedFileReferenceConverter::class)->willReturn($typeConverter);
         $objectManager->get(MimeTypeValidator::class, $mimeTypes)->willReturn($mimeTypeValidator);
         GeneralUtility::setSingletonInstance(ObjectManager::class, $objectManager->reveal());
 
@@ -205,6 +211,8 @@ class PropertyMappingConfigurationTest extends UnitTestCase
         // Resource Factory
         /** @var \PHPUnit\Framework\MockObject\MockObject|ResourceFactory $resourceFactory */
         $resourceFactory = $this->createMock(ResourceFactory::class);
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager (in order to return mocked Resource Factory)
         /** @var \PHPUnit\Framework\MockObject\MockObject|ObjectManager $objectManager */
@@ -217,6 +225,7 @@ class PropertyMappingConfigurationTest extends UnitTestCase
             ->expects(self::any())
             ->method('get')
             ->willReturnMap([
+                [UploadedFileReferenceConverter::class, $typeConverter],
                 [MimeTypeValidator::class, $mimeTypeValidator],
                 [ResourceFactory::class, $resourceFactory]
             ]);
@@ -261,6 +270,8 @@ class PropertyMappingConfigurationTest extends UnitTestCase
         // Resource Factory
         /** @var \PHPUnit\Framework\MockObject\MockObject|ResourceFactory $resourceFactory */
         $resourceFactory = $this->createMock(ResourceFactory::class);
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager (in order to return mocked Resource Factory)
         /** @var \PHPUnit\Framework\MockObject\MockObject|ObjectManager $objectManager */
@@ -273,6 +284,7 @@ class PropertyMappingConfigurationTest extends UnitTestCase
             ->expects(self::any())
             ->method('get')
             ->willReturnMap([
+                [UploadedFileReferenceConverter::class, $typeConverter],
                 [MimeTypeValidator::class, $mimeTypeValidator],
                 [ResourceFactory::class, $resourceFactory]
             ]);
@@ -322,6 +334,8 @@ class PropertyMappingConfigurationTest extends UnitTestCase
         // Resource Factory
         /** @var \PHPUnit\Framework\MockObject\MockObject|ResourceFactory $resourceFactory */
         $resourceFactory = $this->createMock(ResourceFactory::class);
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager (in order to return mocked Resource Factory)
         /** @var \PHPUnit\Framework\MockObject\MockObject|ObjectManager $objectManager */
@@ -334,6 +348,7 @@ class PropertyMappingConfigurationTest extends UnitTestCase
             ->expects(self::any())
             ->method('get')
             ->willReturnMap([
+                [UploadedFileReferenceConverter::class, $typeConverter],
                 [MimeTypeValidator::class, $mimeTypeValidator],
                 [ResourceFactory::class, $resourceFactory]
             ]);
@@ -383,6 +398,8 @@ class PropertyMappingConfigurationTest extends UnitTestCase
         // Resource Factory
         /** @var \PHPUnit\Framework\MockObject\MockObject|ResourceFactory $resourceFactory */
         $resourceFactory = $this->createMock(ResourceFactory::class);
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager (in order to return mocked Resource Factory)
         /** @var \PHPUnit\Framework\MockObject\MockObject|ObjectManager $objectManager */
@@ -395,6 +412,7 @@ class PropertyMappingConfigurationTest extends UnitTestCase
             ->expects(self::any())
             ->method('get')
             ->willReturnMap([
+                [UploadedFileReferenceConverter::class, $typeConverter],
                 [MimeTypeValidator::class, $mimeTypeValidator],
                 [ResourceFactory::class, $resourceFactory]
             ]);
@@ -439,6 +457,8 @@ class PropertyMappingConfigurationTest extends UnitTestCase
         // Resource Factory
         /** @var \PHPUnit\Framework\MockObject\MockObject|ResourceFactory $resourceFactory */
         $resourceFactory = $this->createMock(ResourceFactory::class);
+        /** @var \PHPUnit\Framework\MockObject\MockObject|UploadedFileReferenceConverter $typeConverter */
+        $typeConverter = $this->createMock(UploadedFileReferenceConverter::class);
 
         // Object Manager (in order to return mocked Resource Factory)
         /** @var \PHPUnit\Framework\MockObject\MockObject|ObjectManager $objectManager */
@@ -451,6 +471,7 @@ class PropertyMappingConfigurationTest extends UnitTestCase
             ->expects(self::any())
             ->method('get')
             ->willReturnMap([
+                [UploadedFileReferenceConverter::class, $typeConverter],
                 [MimeTypeValidator::class, $mimeTypeValidator],
                 [ResourceFactory::class, $resourceFactory]
             ]);
diff --git a/typo3/sysext/form/Tests/Unit/Mvc/Validation/MimeTypeValidatorTest.php b/typo3/sysext/form/Tests/Unit/Mvc/Validation/MimeTypeValidatorTest.php
index 5899a0d3fe9b..47f532044ed7 100644
--- a/typo3/sysext/form/Tests/Unit/Mvc/Validation/MimeTypeValidatorTest.php
+++ b/typo3/sysext/form/Tests/Unit/Mvc/Validation/MimeTypeValidatorTest.php
@@ -108,4 +108,43 @@ class MimeTypeValidatorTest extends UnitTestCase
 
         self::assertTrue($validator->validate('string')->hasErrors());
     }
+
+    public function fileExtensionMatchesMimeTypesDataProvider(): array
+    {
+        $allowedMimeTypes = ['application/pdf', 'application/vnd.oasis.opendocument.text'];
+        return [
+            // filename,      file mime-type,    allowed types,     is valid (is allowed)
+            ['something.pdf', 'application/pdf', $allowedMimeTypes, true],
+            ['something.txt', 'application/pdf', $allowedMimeTypes, false],
+            ['something.pdf', 'application/pdf', [false], false],
+            ['something.pdf', 'false', $allowedMimeTypes, false],
+        ];
+    }
+
+    /**
+     * @param string $fileName
+     * @param string $fileMimeType
+     * @param array $allowedMimeTypes
+     * @param bool $isValid
+     * @test
+     * @dataProvider fileExtensionMatchesMimeTypesDataProvider
+     */
+    public function fileExtensionMatchesMimeTypes(string $fileName, string $fileMimeType, array $allowedMimeTypes, bool $isValid): void
+    {
+        $options = ['allowedMimeTypes' => $allowedMimeTypes];
+        $validator = $this->getMockBuilder(MimeTypeValidator::class)
+            ->setMethods(['translateErrorMessage'])
+            ->setConstructorArgs(['options' => $options])
+            ->getMock();
+        $mockedStorage = $this->getMockBuilder(ResourceStorage::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+        $file = new File([
+            'name' => $fileName,
+            'identifier' => '/folder/' . $fileName,
+            'mime_type' => $fileMimeType
+        ], $mockedStorage);
+        $result = $validator->validate($file);
+        self::assertSame($isValid, !$result->hasErrors());
+    }
 }
diff --git a/typo3/sysext/form/ext_localconf.php b/typo3/sysext/form/ext_localconf.php
index 74dad0e5a1bd..ee27f80aded4 100644
--- a/typo3/sysext/form/ext_localconf.php
+++ b/typo3/sysext/form/ext_localconf.php
@@ -61,13 +61,12 @@ call_user_func(function () {
     // FE file upload processing
     $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterBuildingFinished'][1489772699]
         = \TYPO3\CMS\Form\Mvc\Property\PropertyMappingConfiguration::class;
+    $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['ext/form']['afterFormStateInitialized'][1613296803]
+        = \TYPO3\CMS\Form\Mvc\Property\PropertyMappingConfiguration::class;
 
     \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter(
         \TYPO3\CMS\Form\Mvc\Property\TypeConverter\FormDefinitionArrayConverter::class
     );
-    \TYPO3\CMS\Extbase\Utility\ExtensionUtility::registerTypeConverter(
-        \TYPO3\CMS\Form\Mvc\Property\TypeConverter\UploadedFileReferenceConverter::class
-    );
 
     // Register "formvh:" namespace
     $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['formvh'][] = 'TYPO3\\CMS\\Form\\ViewHelpers';
-- 
GitLab