This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * Extraction of metadata from different bitmap image types. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @ingroup Media |
||
22 | */ |
||
23 | |||
24 | use MediaWiki\Logger\LoggerFactory; |
||
25 | |||
26 | /** |
||
27 | * Class to deal with reconciling and extracting metadata from bitmap images. |
||
28 | * This is meant to comply with http://www.metadataworkinggroup.org/pdf/mwg_guidance.pdf |
||
29 | * |
||
30 | * This sort of acts as an intermediary between MediaHandler::getMetadata |
||
31 | * and the various metadata extractors. |
||
32 | * |
||
33 | * @todo Other image formats. |
||
34 | * @ingroup Media |
||
35 | */ |
||
36 | class BitmapMetadataHandler { |
||
37 | /** @var array */ |
||
38 | private $metadata = []; |
||
39 | |||
40 | /** @var array Metadata priority */ |
||
41 | private $metaPriority = [ |
||
42 | 20 => [ 'other' ], |
||
43 | 40 => [ 'native' ], |
||
44 | 60 => [ 'iptc-good-hash', 'iptc-no-hash' ], |
||
45 | 70 => [ 'xmp-deprecated' ], |
||
46 | 80 => [ 'xmp-general' ], |
||
47 | 90 => [ 'xmp-exif' ], |
||
48 | 100 => [ 'iptc-bad-hash' ], |
||
49 | 120 => [ 'exif' ], |
||
50 | ]; |
||
51 | |||
52 | /** @var string */ |
||
53 | private $iptcType = 'iptc-no-hash'; |
||
54 | |||
55 | /** |
||
56 | * This does the photoshop image resource app13 block |
||
57 | * of interest, IPTC-IIM metadata is stored here. |
||
58 | * |
||
59 | * Mostly just calls doPSIR and doIPTC |
||
60 | * |
||
61 | * @param string $app13 String containing app13 block from jpeg file |
||
62 | */ |
||
63 | private function doApp13( $app13 ) { |
||
64 | try { |
||
65 | $this->iptcType = JpegMetadataExtractor::doPSIR( $app13 ); |
||
66 | } catch ( Exception $e ) { |
||
67 | // Error reading the iptc hash information. |
||
68 | // This probably means the App13 segment is something other than what we expect. |
||
69 | // However, still try to read it, and treat it as if the hash didn't exist. |
||
70 | wfDebug( "Error parsing iptc data of file: " . $e->getMessage() . "\n" ); |
||
71 | $this->iptcType = 'iptc-no-hash'; |
||
72 | } |
||
73 | |||
74 | $iptc = IPTC::parse( $app13 ); |
||
75 | $this->addMetadata( $iptc, $this->iptcType ); |
||
76 | } |
||
77 | |||
78 | /** |
||
79 | * Get exif info using exif class. |
||
80 | * Basically what used to be in BitmapHandler::getMetadata(). |
||
81 | * Just calls stuff in the Exif class. |
||
82 | * |
||
83 | * Parameters are passed to the Exif class. |
||
84 | * |
||
85 | * @param string $filename |
||
86 | * @param string $byteOrder |
||
87 | */ |
||
88 | function getExif( $filename, $byteOrder ) { |
||
89 | global $wgShowEXIF; |
||
90 | if ( file_exists( $filename ) && $wgShowEXIF ) { |
||
91 | $exif = new Exif( $filename, $byteOrder ); |
||
92 | $data = $exif->getFilteredData(); |
||
93 | if ( $data ) { |
||
94 | $this->addMetadata( $data, 'exif' ); |
||
95 | } |
||
96 | } |
||
97 | } |
||
98 | |||
99 | /** Add misc metadata. Warning: atm if the metadata category |
||
100 | * doesn't have a priority, it will be silently discarded. |
||
101 | * |
||
102 | * @param array $metaArray Array of metadata values |
||
103 | * @param string $type Type. defaults to other. if two things have the same type they're merged |
||
104 | */ |
||
105 | function addMetadata( $metaArray, $type = 'other' ) { |
||
106 | if ( isset( $this->metadata[$type] ) ) { |
||
107 | /* merge with old data */ |
||
108 | $metaArray = $metaArray + $this->metadata[$type]; |
||
109 | } |
||
110 | |||
111 | $this->metadata[$type] = $metaArray; |
||
112 | } |
||
113 | |||
114 | /** |
||
115 | * Merge together the various types of metadata |
||
116 | * the different types have different priorites, |
||
117 | * and are merged in order. |
||
118 | * |
||
119 | * This function is generally called by the media handlers' getMetadata() |
||
120 | * |
||
121 | * @return array Metadata array |
||
122 | */ |
||
123 | function getMetadataArray() { |
||
124 | // this seems a bit ugly... This is all so its merged in right order |
||
125 | // based on the MWG recomendation. |
||
126 | $temp = []; |
||
127 | krsort( $this->metaPriority ); |
||
128 | foreach ( $this->metaPriority as $pri ) { |
||
129 | foreach ( $pri as $type ) { |
||
130 | if ( isset( $this->metadata[$type] ) ) { |
||
131 | // Do some special casing for multilingual values. |
||
132 | // Don't discard translations if also as a simple value. |
||
133 | foreach ( $this->metadata[$type] as $itemName => $item ) { |
||
134 | if ( is_array( $item ) && isset( $item['_type'] ) && $item['_type'] === 'lang' ) { |
||
135 | if ( isset( $temp[$itemName] ) && !is_array( $temp[$itemName] ) ) { |
||
136 | $default = $temp[$itemName]; |
||
137 | $temp[$itemName] = $item; |
||
138 | $temp[$itemName]['x-default'] = $default; |
||
139 | unset( $this->metadata[$type][$itemName] ); |
||
140 | } |
||
141 | } |
||
142 | } |
||
143 | |||
144 | $temp = $temp + $this->metadata[$type]; |
||
145 | } |
||
146 | } |
||
147 | } |
||
148 | |||
149 | return $temp; |
||
150 | } |
||
151 | |||
152 | /** Main entry point for jpeg's. |
||
153 | * |
||
154 | * @param string $filename Filename (with full path) |
||
155 | * @return array Metadata result array. |
||
156 | * @throws MWException On invalid file. |
||
157 | */ |
||
158 | static function Jpeg( $filename ) { |
||
159 | $showXMP = XMPReader::isSupported(); |
||
160 | $meta = new self(); |
||
161 | |||
162 | $seg = JpegMetadataExtractor::segmentSplitter( $filename ); |
||
163 | if ( isset( $seg['COM'] ) && isset( $seg['COM'][0] ) ) { |
||
164 | $meta->addMetadata( [ 'JPEGFileComment' => $seg['COM'] ], 'native' ); |
||
165 | } |
||
166 | if ( isset( $seg['PSIR'] ) && count( $seg['PSIR'] ) > 0 ) { |
||
167 | foreach ( $seg['PSIR'] as $curPSIRValue ) { |
||
0 ignored issues
–
show
|
|||
168 | $meta->doApp13( $curPSIRValue ); |
||
169 | } |
||
170 | } |
||
171 | if ( isset( $seg['XMP'] ) && $showXMP ) { |
||
172 | $xmp = new XMPReader( LoggerFactory::getInstance( 'XMP' ) ); |
||
173 | $xmp->parse( $seg['XMP'] ); |
||
0 ignored issues
–
show
It seems like
$seg['XMP'] can also be of type array ; however, XMPReader::parse() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
174 | foreach ( $seg['XMP_ext'] as $xmpExt ) { |
||
175 | /* Support for extended xmp in jpeg files |
||
176 | * is not well tested and a bit fragile. |
||
177 | */ |
||
178 | $xmp->parseExtended( $xmpExt ); |
||
179 | } |
||
180 | $res = $xmp->getResults(); |
||
181 | foreach ( $res as $type => $array ) { |
||
182 | $meta->addMetadata( $array, $type ); |
||
183 | } |
||
184 | } |
||
185 | if ( isset( $seg['byteOrder'] ) ) { |
||
186 | $meta->getExif( $filename, $seg['byteOrder'] ); |
||
0 ignored issues
–
show
It seems like
$seg['byteOrder'] can also be of type array ; however, BitmapMetadataHandler::getExif() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
187 | } |
||
188 | |||
189 | return $meta->getMetadataArray(); |
||
190 | } |
||
191 | |||
192 | /** Entry point for png |
||
193 | * At some point in the future this might |
||
194 | * merge the png various tEXt chunks to that |
||
195 | * are interesting, but for now it only does XMP |
||
196 | * |
||
197 | * @param string $filename Full path to file |
||
198 | * @return array Array for storage in img_metadata. |
||
199 | */ |
||
200 | public static function PNG( $filename ) { |
||
201 | $showXMP = XMPReader::isSupported(); |
||
202 | |||
203 | $meta = new self(); |
||
204 | $array = PNGMetadataExtractor::getMetadata( $filename ); |
||
205 | if ( isset( $array['text']['xmp']['x-default'] ) |
||
206 | && $array['text']['xmp']['x-default'] !== '' && $showXMP |
||
207 | ) { |
||
208 | $xmp = new XMPReader( LoggerFactory::getInstance( 'XMP' ) ); |
||
209 | $xmp->parse( $array['text']['xmp']['x-default'] ); |
||
210 | $xmpRes = $xmp->getResults(); |
||
211 | foreach ( $xmpRes as $type => $xmpSection ) { |
||
212 | $meta->addMetadata( $xmpSection, $type ); |
||
213 | } |
||
214 | } |
||
215 | unset( $array['text']['xmp'] ); |
||
216 | $meta->addMetadata( $array['text'], 'native' ); |
||
217 | unset( $array['text'] ); |
||
218 | $array['metadata'] = $meta->getMetadataArray(); |
||
219 | $array['metadata']['_MW_PNG_VERSION'] = PNGMetadataExtractor::VERSION; |
||
220 | |||
221 | return $array; |
||
222 | } |
||
223 | |||
224 | /** function for gif images. |
||
225 | * |
||
226 | * They don't really have native metadata, so just merges together |
||
227 | * XMP and image comment. |
||
228 | * |
||
229 | * @param string $filename Full path to file |
||
230 | * @return array Metadata array |
||
231 | */ |
||
232 | public static function GIF( $filename ) { |
||
233 | |||
234 | $meta = new self(); |
||
235 | $baseArray = GIFMetadataExtractor::getMetadata( $filename ); |
||
236 | |||
237 | if ( count( $baseArray['comment'] ) > 0 ) { |
||
238 | $meta->addMetadata( [ 'GIFFileComment' => $baseArray['comment'] ], 'native' ); |
||
239 | } |
||
240 | |||
241 | if ( $baseArray['xmp'] !== '' && XMPReader::isSupported() ) { |
||
242 | $xmp = new XMPReader( LoggerFactory::getInstance( 'XMP' ) ); |
||
243 | $xmp->parse( $baseArray['xmp'] ); |
||
244 | $xmpRes = $xmp->getResults(); |
||
245 | foreach ( $xmpRes as $type => $xmpSection ) { |
||
246 | $meta->addMetadata( $xmpSection, $type ); |
||
247 | } |
||
248 | } |
||
249 | |||
250 | unset( $baseArray['comment'] ); |
||
251 | unset( $baseArray['xmp'] ); |
||
252 | |||
253 | $baseArray['metadata'] = $meta->getMetadataArray(); |
||
254 | $baseArray['metadata']['_MW_GIF_VERSION'] = GIFMetadataExtractor::VERSION; |
||
255 | |||
256 | return $baseArray; |
||
257 | } |
||
258 | |||
259 | /** |
||
260 | * This doesn't do much yet, but eventually I plan to add |
||
261 | * XMP support for Tiff. (PHP's exif support already extracts |
||
262 | * but needs some further processing because PHP's exif support |
||
263 | * is stupid...) |
||
264 | * |
||
265 | * @todo Add XMP support, so this function actually makes sense to put here. |
||
266 | * |
||
267 | * The various exceptions this throws are caught later. |
||
268 | * @param string $filename |
||
269 | * @throws MWException |
||
270 | * @return array The metadata. |
||
271 | */ |
||
272 | public static function Tiff( $filename ) { |
||
273 | if ( file_exists( $filename ) ) { |
||
274 | $byteOrder = self::getTiffByteOrder( $filename ); |
||
275 | if ( !$byteOrder ) { |
||
0 ignored issues
–
show
The expression
$byteOrder of type false|string is loosely compared to false ; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.
In PHP, under loose comparison (like For '' == false // true
'' == null // true
'ab' == false // false
'ab' == null // false
// It is often better to use strict comparison
'' === false // false
'' === null // false
![]() |
|||
276 | throw new MWException( "Error determining byte order of $filename" ); |
||
277 | } |
||
278 | $exif = new Exif( $filename, $byteOrder ); |
||
279 | $data = $exif->getFilteredData(); |
||
280 | if ( $data ) { |
||
281 | $data['MEDIAWIKI_EXIF_VERSION'] = Exif::version(); |
||
282 | |||
283 | return $data; |
||
284 | } else { |
||
285 | throw new MWException( "Could not extract data from tiff file $filename" ); |
||
286 | } |
||
287 | } else { |
||
288 | throw new MWException( "File doesn't exist - $filename" ); |
||
289 | } |
||
290 | } |
||
291 | |||
292 | /** |
||
293 | * Read the first 2 bytes of a tiff file to figure out |
||
294 | * Little Endian or Big Endian. Needed for exif stuff. |
||
295 | * |
||
296 | * @param string $filename The filename |
||
297 | * @return string 'BE' or 'LE' or false |
||
298 | */ |
||
299 | static function getTiffByteOrder( $filename ) { |
||
300 | $fh = fopen( $filename, 'rb' ); |
||
301 | if ( !$fh ) { |
||
302 | return false; |
||
303 | } |
||
304 | $head = fread( $fh, 2 ); |
||
305 | fclose( $fh ); |
||
306 | |||
307 | switch ( $head ) { |
||
308 | case 'II': |
||
309 | return 'LE'; // II for intel. |
||
310 | case 'MM': |
||
311 | return 'BE'; // MM for motorla. |
||
312 | default: |
||
313 | return false; // Something went wrong. |
||
314 | |||
315 | } |
||
316 | } |
||
317 | } |
||
318 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.