Issues (4122)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/media/BitmapMetadataHandler.php (4 issues)

Upgrade to new PHP Analysis Engine

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
The expression $seg['PSIR'] of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. 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:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
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.

Loading history...
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.

Loading history...
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
Bug Best Practice introduced by
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 ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
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