22
33namespace wcf \system \worker ;
44
5+ use wcf \data \file \File ;
56use wcf \data \file \FileEditor ;
67use wcf \data \file \FileList ;
78use wcf \system \file \processor \exception \DamagedImage ;
89use wcf \system \file \processor \FileProcessor ;
10+ use wcf \util \FileUtil ;
911
1012use function wcf \functions \exception \logThrowable ;
1113
@@ -45,6 +47,8 @@ public function execute()
4547 {
4648 parent ::execute ();
4749
50+ $ this ->fixMimeType ();
51+
4852 $ damagedFileIDs = [];
4953 foreach ($ this ->objectList as $ file ) {
5054 try {
@@ -61,4 +65,92 @@ public function execute()
6165 FileEditor::deleteAll ($ damagedFileIDs );
6266 }
6367 }
68+
69+ private function fixMimeType (): void
70+ {
71+ $ reloadFiles = false ;
72+ foreach ($ this ->objectList as $ file ) {
73+ // Workaround for images that have been detected but failed to
74+ // determine their dimensions.
75+ $ isImageWithoutDimensions = $ file ->isImage () && $ file ->width === null ;
76+
77+ if ($ file ->mimeType !== 'application/octet-stream ' && !$ isImageWithoutDimensions ) {
78+ continue ;
79+ }
80+
81+ $ mimeType = FileUtil::getMimeType ($ file ->getPathname ());
82+ if ($ file ->mimeType === $ mimeType && !$ isImageWithoutDimensions ) {
83+ continue ;
84+ }
85+
86+ // When the mime type was incorrectly detected before, for example,
87+ // because fileinfo was not present or malfunctioning, the physical
88+ // location of the file may be incorrect.
89+ //
90+ // The location is determined by the safe file extension, anything
91+ // that ends in `.bin` is piped through PHP instead of being served
92+ // through the web server directly.
93+ $ previousFileExtension = File::getSafeFileExtension ($ file ->mimeType , $ file ->filename );
94+ $ detectedFileExtension = File::getSafeFileExtension ($ mimeType , $ file ->filename );
95+
96+ $ width = $ height = null ;
97+ if (\str_starts_with ($ mimeType , 'image/ ' )) {
98+ $ data = @\getimagesize ($ file ->getPathname ());
99+ if ($ data === null ) {
100+ // Treat broken images as binary files.
101+ $ mimeType === 'application/octet-stream ' ;
102+ $ detectedFileExtension = 'bin ' ;
103+
104+ if ($ file ->mimeType === $ mimeType ) {
105+ continue ;
106+ }
107+ } else {
108+ $ width = $ data [0 ];
109+ $ height = $ data [1 ];
110+ }
111+ }
112+
113+ if ($ previousFileExtension !== $ detectedFileExtension ) {
114+ $ path = $ this ->getPath ($ file ->fileHash , $ detectedFileExtension );
115+ FileUtil::makePath ($ path );
116+
117+ \rename (
118+ $ file ->getPathname (),
119+ $ path . \sprintf (
120+ '%d-%s.%s ' ,
121+ $ file ->fileID ,
122+ $ file ->fileHash ,
123+ $ detectedFileExtension ,
124+ ),
125+ );
126+ }
127+
128+ (new FileEditor ($ file ))->update ([
129+ 'fileExtension ' => $ detectedFileExtension ,
130+ 'mimeType ' => $ mimeType ,
131+ 'width ' => $ width ,
132+ 'height ' => $ height ,
133+ ]);
134+
135+ $ reloadFiles = true ;
136+ }
137+
138+ if ($ reloadFiles ) {
139+ $ this ->objectList ->readObjects ();
140+ }
141+ }
142+
143+ private function getPath (string $ fileHash , string $ fileExtension ): string
144+ {
145+ $ folderA = \substr ($ fileHash , 0 , 2 );
146+ $ folderB = \substr ($ fileHash , 2 , 2 );
147+ $ isStaticFile = $ fileExtension !== 'bin ' ;
148+
149+ return \sprintf (
150+ '_data/%s/files/%s/%s/ ' ,
151+ $ isStaticFile ? 'public ' : 'private ' ,
152+ $ folderA ,
153+ $ folderB ,
154+ );
155+ }
64156}
0 commit comments