Completed
Push — vendor/getid3 ( dfd0b4...d7a1ee )
by Pauli
02:47
created

getid3_handler   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 372
rs 3.6
c 0
b 0
f 0
wmc 60
lcom 2
cbo 3

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 2
Analyze() 0 1 ?
A AnalyzeString() 0 24 2
A setStringMode() 0 5 1
A ftell() 0 6 2
B fread() 0 31 7
B fseek() 0 29 8
C fgets() 0 49 12
A feof() 0 6 2
A isDependencyFor() 0 3 1
A error() 0 5 1
A warning() 0 3 1
A notice() 0 3 1
F saveAttachment() 0 70 20

How to fix   Complexity   

Complex Class

Complex classes like getid3_handler often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use getid3_handler, and based on these observations, apply Extract Interface, too.

1
<?php
2
/////////////////////////////////////////////////////////////////
3
/// getID3() by James Heinrich <[email protected]>               //
4
//  available at https://github.com/JamesHeinrich/getID3       //
5
//            or https://www.getid3.org                        //
6
//            or http://getid3.sourceforge.net                 //
7
//                                                             //
8
// Please see readme.txt for more information                  //
9
//                                                            ///
10
/////////////////////////////////////////////////////////////////
11
12
// define a constant rather than looking up every time it is needed
13
if (!defined('GETID3_OS_ISWINDOWS')) {
14
	define('GETID3_OS_ISWINDOWS', (stripos(PHP_OS, 'WIN') === 0));
15
}
16
// Get base path of getID3() - ONCE
17
if (!defined('GETID3_INCLUDEPATH')) {
18
	define('GETID3_INCLUDEPATH', dirname(__FILE__).DIRECTORY_SEPARATOR);
19
}
20
// Workaround Bug #39923 (https://bugs.php.net/bug.php?id=39923)
21
if (!defined('IMG_JPG') && defined('IMAGETYPE_JPEG')) {
22
	define('IMG_JPG', IMAGETYPE_JPEG);
23
}
24
if (!defined('ENT_SUBSTITUTE')) { // PHP5.3 adds ENT_IGNORE, PHP5.4 adds ENT_SUBSTITUTE
25
	define('ENT_SUBSTITUTE', (defined('ENT_IGNORE') ? ENT_IGNORE : 8));
26
}
27
28
/*
29
https://www.getid3.org/phpBB3/viewtopic.php?t=2114
30
If you are running into a the problem where filenames with special characters are being handled
31
incorrectly by external helper programs (e.g. metaflac), notably with the special characters removed,
32
and you are passing in the filename in UTF8 (typically via a HTML form), try uncommenting this line:
33
*/
34
//setlocale(LC_CTYPE, 'en_US.UTF-8');
35
36
// attempt to define temp dir as something flexible but reliable
37
$temp_dir = ini_get('upload_tmp_dir');
38
if ($temp_dir && (!is_dir($temp_dir) || !is_readable($temp_dir))) {
39
	$temp_dir = '';
40
}
41
if (!$temp_dir && function_exists('sys_get_temp_dir')) { // sys_get_temp_dir added in PHP v5.2.1
42
	// sys_get_temp_dir() may give inaccessible temp dir, e.g. with open_basedir on virtual hosts
43
	$temp_dir = sys_get_temp_dir();
44
}
45
$temp_dir = @realpath($temp_dir); // see https://github.com/JamesHeinrich/getID3/pull/10
46
$open_basedir = ini_get('open_basedir');
47
if ($open_basedir) {
48
	// e.g. "/var/www/vhosts/getid3.org/httpdocs/:/tmp/"
49
	$temp_dir     = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $temp_dir);
50
	$open_basedir = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $open_basedir);
51
	if (substr($temp_dir, -1, 1) != DIRECTORY_SEPARATOR) {
52
		$temp_dir .= DIRECTORY_SEPARATOR;
53
	}
54
	$found_valid_tempdir = false;
55
	$open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
56
	foreach ($open_basedirs as $basedir) {
57
		if (substr($basedir, -1, 1) != DIRECTORY_SEPARATOR) {
58
			$basedir .= DIRECTORY_SEPARATOR;
59
		}
60
		if (preg_match('#^'.preg_quote($basedir).'#', $temp_dir)) {
61
			$found_valid_tempdir = true;
62
			break;
63
		}
64
	}
65
	if (!$found_valid_tempdir) {
66
		$temp_dir = '';
67
	}
68
	unset($open_basedirs, $found_valid_tempdir, $basedir);
69
}
70
if (!$temp_dir) {
71
	$temp_dir = '*'; // invalid directory name should force tempnam() to use system default temp dir
72
}
73
// $temp_dir = '/something/else/';  // feel free to override temp dir here if it works better for your system
74
if (!defined('GETID3_TEMP_DIR')) {
75
	define('GETID3_TEMP_DIR', $temp_dir);
76
}
77
unset($open_basedir, $temp_dir);
78
79
// End: Defines
80
81
82
class getID3
83
{
84
	/*
85
	 * Settings
86
	 */
87
88
	/**
89
	 * CASE SENSITIVE! - i.e. (must be supported by iconv()). Examples:  ISO-8859-1  UTF-8  UTF-16  UTF-16BE
90
	 *
91
	 * @var string
92
	 */
93
	public $encoding        = 'UTF-8';
94
95
	/**
96
	 * Should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'EUC-CN' or 'CP1252'
97
	 *
98
	 * @var string
99
	 */
100
	public $encoding_id3v1  = 'ISO-8859-1';
101
102
	/**
103
	 * ID3v1 should always be 'ISO-8859-1', but some tags may be written in other encodings such as 'Windows-1251' or 'KOI8-R'. If true attempt to detect these encodings, but may return incorrect values for some tags actually in ISO-8859-1 encoding
104
	 *
105
	 * @var bool
106
	 */
107
	public $encoding_id3v1_autodetect  = false;
108
109
	/*
110
	 * Optional tag checks - disable for speed.
111
	 */
112
113
	/**
114
	 * Read and process ID3v1 tags
115
	 *
116
	 * @var bool
117
	 */
118
	public $option_tag_id3v1         = true;
119
120
	/**
121
	 * Read and process ID3v2 tags
122
	 *
123
	 * @var bool
124
	 */
125
	public $option_tag_id3v2         = true;
126
127
	/**
128
	 * Read and process Lyrics3 tags
129
	 *
130
	 * @var bool
131
	 */
132
	public $option_tag_lyrics3       = true;
133
134
	/**
135
	 * Read and process APE tags
136
	 *
137
	 * @var bool
138
	 */
139
	public $option_tag_apetag        = true;
140
141
	/**
142
	 * Copy tags to root key 'tags' and encode to $this->encoding
143
	 *
144
	 * @var bool
145
	 */
146
	public $option_tags_process      = true;
147
148
	/**
149
	 * Copy tags to root key 'tags_html' properly translated from various encodings to HTML entities
150
	 *
151
	 * @var bool
152
	 */
153
	public $option_tags_html         = true;
154
155
	/*
156
	 * Optional tag/comment calculations
157
	 */
158
159
	/**
160
	 * Calculate additional info such as bitrate, channelmode etc
161
	 *
162
	 * @var bool
163
	 */
164
	public $option_extra_info        = true;
165
166
	/*
167
	 * Optional handling of embedded attachments (e.g. images)
168
	 */
169
170
	/**
171
	 * Defaults to true (ATTACHMENTS_INLINE) for backward compatibility
172
	 *
173
	 * @var bool|string
174
	 */
175
	public $option_save_attachments  = true;
176
177
	/*
178
	 * Optional calculations
179
	 */
180
181
	/**
182
	 * Get MD5 sum of data part - slow
183
	 *
184
	 * @var bool
185
	 */
186
	public $option_md5_data          = false;
187
188
	/**
189
	 * Use MD5 of source file if availble - only FLAC and OptimFROG
190
	 *
191
	 * @var bool
192
	 */
193
	public $option_md5_data_source   = false;
194
195
	/**
196
	 * Get SHA1 sum of data part - slow
197
	 *
198
	 * @var bool
199
	 */
200
	public $option_sha1_data         = false;
201
202
	/**
203
	 * Check whether file is larger than 2GB and thus not supported by 32-bit PHP (null: auto-detect based on
204
	 * PHP_INT_MAX)
205
	 *
206
	 * @var bool|null
207
	 */
208
	public $option_max_2gb_check;
209
210
	/**
211
	 * Read buffer size in bytes
212
	 *
213
	 * @var int
214
	 */
215
	public $option_fread_buffer_size = 32768;
216
217
	// Public variables
218
219
	/**
220
	 * Filename of file being analysed.
221
	 *
222
	 * @var string
223
	 */
224
	public $filename;
225
226
	/**
227
	 * Filepointer to file being analysed.
228
	 *
229
	 * @var resource
230
	 */
231
	public $fp;
232
233
	/**
234
	 * Result array.
235
	 *
236
	 * @var array
237
	 */
238
	public $info;
239
240
	/**
241
	 * @var string
242
	 */
243
	public $tempdir = GETID3_TEMP_DIR;
244
245
	/**
246
	 * @var int
247
	 */
248
	public $memory_limit = 0;
249
250
	/**
251
	 * @var string
252
	 */
253
	protected $startup_error   = '';
254
255
	/**
256
	 * @var string
257
	 */
258
	protected $startup_warning = '';
259
260
	const VERSION           = '1.9.20-202006061653';
261
	const FREAD_BUFFER_SIZE = 32768;
262
263
	const ATTACHMENTS_NONE   = false;
264
	const ATTACHMENTS_INLINE = true;
265
266
	public function __construct() {
267
268
		// Check for PHP version
269
		$required_php_version = '5.3.0';
270
		if (version_compare(PHP_VERSION, $required_php_version, '<')) {
271
			$this->startup_error .= 'getID3() requires PHP v'.$required_php_version.' or higher - you are running v'.PHP_VERSION."\n";
272
			return;
273
		}
274
275
		// Check memory
276
		$memoryLimit = ini_get('memory_limit');
277
		if (preg_match('#([0-9]+) ?M#i', $memoryLimit, $matches)) {
278
			// could be stored as "16M" rather than 16777216 for example
279
			$memoryLimit = $matches[1] * 1048576;
280
		} elseif (preg_match('#([0-9]+) ?G#i', $memoryLimit, $matches)) { // The 'G' modifier is available since PHP 5.1.0
281
			// could be stored as "2G" rather than 2147483648 for example
282
			$memoryLimit = $matches[1] * 1073741824;
283
		}
284
		$this->memory_limit = $memoryLimit;
0 ignored issues
show
Documentation Bug introduced by
It seems like $memoryLimit can also be of type double or string. However, the property $memory_limit is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
285
286
		if ($this->memory_limit <= 0) {
287
			// memory limits probably disabled
288
		} elseif ($this->memory_limit <= 4194304) {
289
			$this->startup_error .= 'PHP has less than 4MB available memory and will very likely run out. Increase memory_limit in php.ini'."\n";
290
		} elseif ($this->memory_limit <= 12582912) {
291
			$this->startup_warning .= 'PHP has less than 12MB available memory and might run out if all modules are loaded. Increase memory_limit in php.ini'."\n";
292
		}
293
294
		// Check safe_mode off
295
		if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
296
			$this->warning('WARNING: Safe mode is on, shorten support disabled, md5data/sha1data for ogg vorbis disabled, ogg vorbos/flac tag writing disabled.');
297
		}
298
299
		if (($mbstring_func_overload = (int) ini_get('mbstring.func_overload')) && ($mbstring_func_overload & 0x02)) {
300
			// http://php.net/manual/en/mbstring.overload.php
301
			// "mbstring.func_overload in php.ini is a positive value that represents a combination of bitmasks specifying the categories of functions to be overloaded. It should be set to 1 to overload the mail() function. 2 for string functions, 4 for regular expression functions"
302
			// getID3 cannot run when string functions are overloaded. It doesn't matter if mail() or ereg* functions are overloaded since getID3 does not use those.
303
			$this->startup_error .= 'WARNING: php.ini contains "mbstring.func_overload = '.ini_get('mbstring.func_overload').'", getID3 cannot run with this setting (bitmask 2 (string functions) cannot be set). Recommended to disable entirely.'."\n";
304
		}
305
306
		// check for magic quotes in PHP < 7.4.0 (when these functions became deprecated)
307
		if (version_compare(PHP_VERSION, '7.4.0', '<')) {
308
			// Check for magic_quotes_runtime
309
			if (function_exists('get_magic_quotes_runtime')) {
310
				if (get_magic_quotes_runtime()) {
311
					$this->startup_error .= 'magic_quotes_runtime must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_runtime(0) and set_magic_quotes_runtime(1).'."\n";
312
				}
313
			}
314
			// Check for magic_quotes_gpc
315
			if (function_exists('get_magic_quotes_gpc')) {
316
				if (get_magic_quotes_gpc()) {
317
					$this->startup_error .= 'magic_quotes_gpc must be disabled before running getID3(). Surround getid3 block by set_magic_quotes_gpc(0) and set_magic_quotes_gpc(1).'."\n";
318
				}
319
			}
320
		}
321
322
		// Load support library
323
		if (!include_once(GETID3_INCLUDEPATH.'getid3.lib.php')) {
324
			$this->startup_error .= 'getid3.lib.php is missing or corrupt'."\n";
325
		}
326
327
		if ($this->option_max_2gb_check === null) {
328
			$this->option_max_2gb_check = (PHP_INT_MAX <= 2147483647);
329
		}
330
331
332
		// Needed for Windows only:
333
		// Define locations of helper applications for Shorten, VorbisComment, MetaFLAC
334
		//   as well as other helper functions such as head, etc
335
		// This path cannot contain spaces, but the below code will attempt to get the
336
		//   8.3-equivalent path automatically
337
		// IMPORTANT: This path must include the trailing slash
338
		if (GETID3_OS_ISWINDOWS && !defined('GETID3_HELPERAPPSDIR')) {
339
340
			$helperappsdir = GETID3_INCLUDEPATH.'..'.DIRECTORY_SEPARATOR.'helperapps'; // must not have any space in this path
341
342
			if (!is_dir($helperappsdir)) {
343
				$this->startup_warning .= '"'.$helperappsdir.'" cannot be defined as GETID3_HELPERAPPSDIR because it does not exist'."\n";
344
			} elseif (strpos(realpath($helperappsdir), ' ') !== false) {
345
				$DirPieces = explode(DIRECTORY_SEPARATOR, realpath($helperappsdir));
346
				$path_so_far = array();
347
				foreach ($DirPieces as $key => $value) {
348
					if (strpos($value, ' ') !== false) {
349
						if (!empty($path_so_far)) {
350
							$commandline = 'dir /x '.escapeshellarg(implode(DIRECTORY_SEPARATOR, $path_so_far));
351
							$dir_listing = `$commandline`;
352
							$lines = explode("\n", $dir_listing);
353
							foreach ($lines as $line) {
354
								$line = trim($line);
355
								if (preg_match('#^([0-9/]{10}) +([0-9:]{4,5}( [AP]M)?) +(<DIR>|[0-9,]+) +([^ ]{0,11}) +(.+)$#', $line, $matches)) {
356
									list($dummy, $date, $time, $ampm, $filesize, $shortname, $filename) = $matches;
0 ignored issues
show
Unused Code introduced by
The assignment to $dummy is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $date is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $time is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
Unused Code introduced by
The assignment to $ampm is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
357
									if ((strtoupper($filesize) == '<DIR>') && (strtolower($filename) == strtolower($value))) {
358
										$value = $shortname;
359
									}
360
								}
361
							}
362
						} else {
363
							$this->startup_warning .= 'GETID3_HELPERAPPSDIR must not have any spaces in it - use 8dot3 naming convention if neccesary. You can run "dir /x" from the commandline to see the correct 8.3-style names.'."\n";
364
						}
365
					}
366
					$path_so_far[] = $value;
367
				}
368
				$helperappsdir = implode(DIRECTORY_SEPARATOR, $path_so_far);
369
			}
370
			define('GETID3_HELPERAPPSDIR', $helperappsdir.DIRECTORY_SEPARATOR);
371
		}
372
373
		if (!empty($this->startup_error)) {
374
			echo $this->startup_error;
375
			throw new getid3_exception($this->startup_error);
376
		}
377
	}
378
379
	/**
380
	 * @return string
381
	 */
382
	public function version() {
383
		return self::VERSION;
384
	}
385
386
	/**
387
	 * @return int
388
	 */
389
	public function fread_buffer_size() {
390
		return $this->option_fread_buffer_size;
391
	}
392
393
	/**
394
	 * @param array $optArray
395
	 *
396
	 * @return bool
397
	 */
398
	public function setOption($optArray) {
399
		if (!is_array($optArray) || empty($optArray)) {
400
			return false;
401
		}
402
		foreach ($optArray as $opt => $val) {
403
			if (isset($this->$opt) === false) {
404
				continue;
405
			}
406
			$this->$opt = $val;
407
		}
408
		return true;
409
	}
410
411
	/**
412
	 * @param string   $filename
413
	 * @param int      $filesize
414
	 * @param resource $fp
415
	 *
416
	 * @return bool
417
	 *
418
	 * @throws getid3_exception
419
	 */
420
	public function openfile($filename, $filesize=null, $fp=null) {
421
		try {
422
			if (!empty($this->startup_error)) {
423
				throw new getid3_exception($this->startup_error);
424
			}
425
			if (!empty($this->startup_warning)) {
426
				foreach (explode("\n", $this->startup_warning) as $startup_warning) {
427
					$this->warning($startup_warning);
428
				}
429
			}
430
431
			// init result array and set parameters
432
			$this->filename = $filename;
433
			$this->info = array();
434
			$this->info['GETID3_VERSION']   = $this->version();
435
			$this->info['php_memory_limit'] = (($this->memory_limit > 0) ? $this->memory_limit : false);
436
437
			// remote files not supported
438
			if (preg_match('#^(ht|f)tp://#', $filename)) {
439
				throw new getid3_exception('Remote files are not supported - please copy the file locally first');
440
			}
441
442
			$filename = str_replace('/', DIRECTORY_SEPARATOR, $filename);
443
			//$filename = preg_replace('#(?<!gs:)('.preg_quote(DIRECTORY_SEPARATOR).'{2,})#', DIRECTORY_SEPARATOR, $filename);
444
445
			// open local file
446
			//if (is_readable($filename) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) { // see https://www.getid3.org/phpBB3/viewtopic.php?t=1720
447
			if (($fp != null) && ((get_resource_type($fp) == 'file') || (get_resource_type($fp) == 'stream'))) {
448
				$this->fp = $fp;
449
			} elseif ((is_readable($filename) || file_exists($filename)) && is_file($filename) && ($this->fp = fopen($filename, 'rb'))) {
450
				// great
451
			} else {
452
				$errormessagelist = array();
453
				if (!is_readable($filename)) {
454
					$errormessagelist[] = '!is_readable';
455
				}
456
				if (!is_file($filename)) {
457
					$errormessagelist[] = '!is_file';
458
				}
459
				if (!file_exists($filename)) {
460
					$errormessagelist[] = '!file_exists';
461
				}
462
				if (empty($errormessagelist)) {
463
					$errormessagelist[] = 'fopen failed';
464
				}
465
				throw new getid3_exception('Could not open "'.$filename.'" ('.implode('; ', $errormessagelist).')');
466
			}
467
468
			$this->info['filesize'] = (!is_null($filesize) ? $filesize : filesize($filename));
469
			// set redundant parameters - might be needed in some include file
470
			// filenames / filepaths in getID3 are always expressed with forward slashes (unix-style) for both Windows and other to try and minimize confusion
471
			$filename = str_replace('\\', '/', $filename);
472
			$this->info['filepath']     = str_replace('\\', '/', realpath(dirname($filename)));
473
			$this->info['filename']     = getid3_lib::mb_basename($filename);
474
			$this->info['filenamepath'] = $this->info['filepath'].'/'.$this->info['filename'];
475
476
			// set more parameters
477
			$this->info['avdataoffset']        = 0;
478
			$this->info['avdataend']           = $this->info['filesize'];
479
			$this->info['fileformat']          = '';                // filled in later
480
			$this->info['audio']['dataformat'] = '';                // filled in later, unset if not used
481
			$this->info['video']['dataformat'] = '';                // filled in later, unset if not used
482
			$this->info['tags']                = array();           // filled in later, unset if not used
483
			$this->info['error']               = array();           // filled in later, unset if not used
484
			$this->info['warning']             = array();           // filled in later, unset if not used
485
			$this->info['comments']            = array();           // filled in later, unset if not used
486
			$this->info['encoding']            = $this->encoding;   // required by id3v2 and iso modules - can be unset at the end if desired
487
488
			// option_max_2gb_check
489
			if ($this->option_max_2gb_check) {
490
				// PHP (32-bit all, and 64-bit Windows) doesn't support integers larger than 2^31 (~2GB)
491
				// filesize() simply returns (filesize % (pow(2, 32)), no matter the actual filesize
492
				// ftell() returns 0 if seeking to the end is beyond the range of unsigned integer
493
				$fseek = fseek($this->fp, 0, SEEK_END);
494
				if (($fseek < 0) || (($this->info['filesize'] != 0) && (ftell($this->fp) == 0)) ||
495
					($this->info['filesize'] < 0) ||
496
					(ftell($this->fp) < 0)) {
497
						$real_filesize = getid3_lib::getFileSizeSyscall($this->info['filenamepath']);
498
499
						if ($real_filesize === false) {
500
							unset($this->info['filesize']);
501
							fclose($this->fp);
502
							throw new getid3_exception('Unable to determine actual filesize. File is most likely larger than '.round(PHP_INT_MAX / 1073741824).'GB and is not supported by PHP.');
503
						} elseif (getid3_lib::intValueSupported($real_filesize)) {
0 ignored issues
show
Documentation introduced by
$real_filesize is of type double|boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
504
							unset($this->info['filesize']);
505
							fclose($this->fp);
506
							throw new getid3_exception('PHP seems to think the file is larger than '.round(PHP_INT_MAX / 1073741824).'GB, but filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB, please report to [email protected]');
507
						}
508
						$this->info['filesize'] = $real_filesize;
509
						$this->warning('File is larger than '.round(PHP_INT_MAX / 1073741824).'GB (filesystem reports it as '.number_format($real_filesize / 1073741824, 3).'GB) and is not properly supported by PHP.');
510
				}
511
			}
512
513
			return true;
514
515
		} catch (Exception $e) {
516
			$this->error($e->getMessage());
517
		}
518
		return false;
519
	}
520
521
	/**
522
	 * analyze file
523
	 *
524
	 * @param string   $filename
525
	 * @param int      $filesize
526
	 * @param string   $original_filename
527
	 * @param resource $fp
528
	 *
529
	 * @return array
530
	 */
531
	public function analyze($filename, $filesize=null, $original_filename='', $fp=null) {
532
		try {
533
			if (!$this->openfile($filename, $filesize, $fp)) {
534
				return $this->info;
535
			}
536
537
			// Handle tags
538
			foreach (array('id3v2'=>'id3v2', 'id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
539
				$option_tag = 'option_tag_'.$tag_name;
540
				if ($this->$option_tag) {
541
					$this->include_module('tag.'.$tag_name);
542
					try {
543
						$tag_class = 'getid3_'.$tag_name;
544
						$tag = new $tag_class($this);
545
						$tag->Analyze();
546
					}
547
					catch (getid3_exception $e) {
548
						throw $e;
549
					}
550
				}
551
			}
552
			if (isset($this->info['id3v2']['tag_offset_start'])) {
553
				$this->info['avdataoffset'] = max($this->info['avdataoffset'], $this->info['id3v2']['tag_offset_end']);
554
			}
555
			foreach (array('id3v1'=>'id3v1', 'apetag'=>'ape', 'lyrics3'=>'lyrics3') as $tag_name => $tag_key) {
556
				if (isset($this->info[$tag_key]['tag_offset_start'])) {
557
					$this->info['avdataend'] = min($this->info['avdataend'], $this->info[$tag_key]['tag_offset_start']);
558
				}
559
			}
560
561
			// ID3v2 detection (NOT parsing), even if ($this->option_tag_id3v2 == false) done to make fileformat easier
562
			if (!$this->option_tag_id3v2) {
563
				fseek($this->fp, 0);
564
				$header = fread($this->fp, 10);
565
				if ((substr($header, 0, 3) == 'ID3') && (strlen($header) == 10)) {
566
					$this->info['id3v2']['header']        = true;
567
					$this->info['id3v2']['majorversion']  = ord($header[3]);
568
					$this->info['id3v2']['minorversion']  = ord($header[4]);
569
					$this->info['avdataoffset']          += getid3_lib::BigEndian2Int(substr($header, 6, 4), 1) + 10; // length of ID3v2 tag in 10-byte header doesn't include 10-byte header length
0 ignored issues
show
Documentation introduced by
1 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
570
				}
571
			}
572
573
			// read 32 kb file data
574
			fseek($this->fp, $this->info['avdataoffset']);
575
			$formattest = fread($this->fp, 32774);
576
577
			// determine format
578
			$determined_format = $this->GetFileFormat($formattest, ($original_filename ? $original_filename : $filename));
579
580
			// unable to determine file format
581
			if (!$determined_format) {
582
				fclose($this->fp);
583
				return $this->error('unable to determine file format');
584
			}
585
586
			// check for illegal ID3 tags
587
			if (isset($determined_format['fail_id3']) && (in_array('id3v1', $this->info['tags']) || in_array('id3v2', $this->info['tags']))) {
588 View Code Duplication
				if ($determined_format['fail_id3'] === 'ERROR') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
589
					fclose($this->fp);
590
					return $this->error('ID3 tags not allowed on this file type.');
591
				} elseif ($determined_format['fail_id3'] === 'WARNING') {
592
					$this->warning('ID3 tags not allowed on this file type.');
593
				}
594
			}
595
596
			// check for illegal APE tags
597
			if (isset($determined_format['fail_ape']) && in_array('ape', $this->info['tags'])) {
598 View Code Duplication
				if ($determined_format['fail_ape'] === 'ERROR') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
599
					fclose($this->fp);
600
					return $this->error('APE tags not allowed on this file type.');
601
				} elseif ($determined_format['fail_ape'] === 'WARNING') {
602
					$this->warning('APE tags not allowed on this file type.');
603
				}
604
			}
605
606
			// set mime type
607
			$this->info['mime_type'] = $determined_format['mime_type'];
608
609
			// supported format signature pattern detected, but module deleted
610
			if (!file_exists(GETID3_INCLUDEPATH.$determined_format['include'])) {
611
				fclose($this->fp);
612
				return $this->error('Format not supported, module "'.$determined_format['include'].'" was removed.');
613
			}
614
615
			// module requires mb_convert_encoding/iconv support
616
			// Check encoding/iconv support
617
			if (!empty($determined_format['iconv_req']) && !function_exists('mb_convert_encoding') && !function_exists('iconv') && !in_array($this->encoding, array('ISO-8859-1', 'UTF-8', 'UTF-16LE', 'UTF-16BE', 'UTF-16'))) {
618
				$errormessage = 'mb_convert_encoding() or iconv() support is required for this module ('.$determined_format['include'].') for encodings other than ISO-8859-1, UTF-8, UTF-16LE, UTF16-BE, UTF-16. ';
619
				if (GETID3_OS_ISWINDOWS) {
620
					$errormessage .= 'PHP does not have mb_convert_encoding() or iconv() support. Please enable php_mbstring.dll / php_iconv.dll in php.ini, and copy php_mbstring.dll / iconv.dll from c:/php/dlls to c:/windows/system32';
621
				} else {
622
					$errormessage .= 'PHP is not compiled with mb_convert_encoding() or iconv() support. Please recompile with the --enable-mbstring / --with-iconv switch';
623
				}
624
				return $this->error($errormessage);
625
			}
626
627
			// include module
628
			include_once(GETID3_INCLUDEPATH.$determined_format['include']);
629
630
			// instantiate module class
631
			$class_name = 'getid3_'.$determined_format['module'];
632
			if (!class_exists($class_name)) {
633
				return $this->error('Format not supported, module "'.$determined_format['include'].'" is corrupt.');
634
			}
635
			$class = new $class_name($this);
636
			$class->Analyze();
637
			unset($class);
638
639
			// close file
640
			fclose($this->fp);
641
642
			// process all tags - copy to 'tags' and convert charsets
643
			if ($this->option_tags_process) {
644
				$this->HandleAllTags();
645
			}
646
647
			// perform more calculations
648
			if ($this->option_extra_info) {
649
				$this->ChannelsBitratePlaytimeCalculations();
650
				$this->CalculateCompressionRatioVideo();
651
				$this->CalculateCompressionRatioAudio();
652
				$this->CalculateReplayGain();
653
				$this->ProcessAudioStreams();
654
			}
655
656
			// get the MD5 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
657
			if ($this->option_md5_data) {
658
				// do not calc md5_data if md5_data_source is present - set by flac only - future MPC/SV8 too
659
				if (!$this->option_md5_data_source || empty($this->info['md5_data_source'])) {
660
					$this->getHashdata('md5');
661
				}
662
			}
663
664
			// get the SHA1 sum of the audio/video portion of the file - without ID3/APE/Lyrics3/etc header/footer tags
665
			if ($this->option_sha1_data) {
666
				$this->getHashdata('sha1');
667
			}
668
669
			// remove undesired keys
670
			$this->CleanUp();
671
672
		} catch (Exception $e) {
673
			$this->error('Caught exception: '.$e->getMessage());
674
		}
675
676
		// return info array
677
		return $this->info;
678
	}
679
680
681
	/**
682
	 * Error handling.
683
	 *
684
	 * @param string $message
685
	 *
686
	 * @return array
687
	 */
688
	public function error($message) {
689
		$this->CleanUp();
690
		if (!isset($this->info['error'])) {
691
			$this->info['error'] = array();
692
		}
693
		$this->info['error'][] = $message;
694
		return $this->info;
695
	}
696
697
698
	/**
699
	 * Warning handling.
700
	 *
701
	 * @param string $message
702
	 *
703
	 * @return bool
704
	 */
705
	public function warning($message) {
706
		$this->info['warning'][] = $message;
707
		return true;
708
	}
709
710
711
	/**
712
	 * @return bool
713
	 */
714
	private function CleanUp() {
715
716
		// remove possible empty keys
717
		$AVpossibleEmptyKeys = array('dataformat', 'bits_per_sample', 'encoder_options', 'streams', 'bitrate');
718
		foreach ($AVpossibleEmptyKeys as $dummy => $key) {
719 View Code Duplication
			if (empty($this->info['audio'][$key]) && isset($this->info['audio'][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
720
				unset($this->info['audio'][$key]);
721
			}
722 View Code Duplication
			if (empty($this->info['video'][$key]) && isset($this->info['video'][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
723
				unset($this->info['video'][$key]);
724
			}
725
		}
726
727
		// remove empty root keys
728
		if (!empty($this->info)) {
729
			foreach ($this->info as $key => $value) {
730
				if (empty($this->info[$key]) && ($this->info[$key] !== 0) && ($this->info[$key] !== '0')) {
731
					unset($this->info[$key]);
732
				}
733
			}
734
		}
735
736
		// remove meaningless entries from unknown-format files
737
		if (empty($this->info['fileformat'])) {
738
			if (isset($this->info['avdataoffset'])) {
739
				unset($this->info['avdataoffset']);
740
			}
741
			if (isset($this->info['avdataend'])) {
742
				unset($this->info['avdataend']);
743
			}
744
		}
745
746
		// remove possible duplicated identical entries
747 View Code Duplication
		if (!empty($this->info['error'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
748
			$this->info['error'] = array_values(array_unique($this->info['error']));
749
		}
750 View Code Duplication
		if (!empty($this->info['warning'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
751
			$this->info['warning'] = array_values(array_unique($this->info['warning']));
752
		}
753
754
		// remove "global variable" type keys
755
		unset($this->info['php_memory_limit']);
756
757
		return true;
758
	}
759
760
	/**
761
	 * Return array containing information about all supported formats.
762
	 *
763
	 * @return array
764
	 */
765
	public function GetFileFormatArray() {
766
		static $format_info = array();
767
		if (empty($format_info)) {
768
			$format_info = array(
769
770
				// Audio formats
771
772
				// AC-3   - audio      - Dolby AC-3 / Dolby Digital
773
				'ac3'  => array(
774
							'pattern'   => '^\\x0B\\x77',
775
							'group'     => 'audio',
776
							'module'    => 'ac3',
777
							'mime_type' => 'audio/ac3',
778
						),
779
780
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADIF format
781
				'adif' => array(
782
							'pattern'   => '^ADIF',
783
							'group'     => 'audio',
784
							'module'    => 'aac',
785
							'mime_type' => 'audio/aac',
786
							'fail_ape'  => 'WARNING',
787
						),
788
789
/*
790
				// AA   - audio       - Audible Audiobook
791
				'aa'   => array(
792
							'pattern'   => '^.{4}\\x57\\x90\\x75\\x36',
793
							'group'     => 'audio',
794
							'module'    => 'aa',
795
							'mime_type' => 'audio/audible',
796
						),
797
*/
798
				// AAC  - audio       - Advanced Audio Coding (AAC) - ADTS format (very similar to MP3)
799
				'adts' => array(
800
							'pattern'   => '^\\xFF[\\xF0-\\xF1\\xF8-\\xF9]',
801
							'group'     => 'audio',
802
							'module'    => 'aac',
803
							'mime_type' => 'audio/aac',
804
							'fail_ape'  => 'WARNING',
805
						),
806
807
808
				// AU   - audio       - NeXT/Sun AUdio (AU)
809
				'au'   => array(
810
							'pattern'   => '^\\.snd',
811
							'group'     => 'audio',
812
							'module'    => 'au',
813
							'mime_type' => 'audio/basic',
814
						),
815
816
				// AMR  - audio       - Adaptive Multi Rate
817
				'amr'  => array(
818
							'pattern'   => '^\\x23\\x21AMR\\x0A', // #!AMR[0A]
819
							'group'     => 'audio',
820
							'module'    => 'amr',
821
							'mime_type' => 'audio/amr',
822
						),
823
824
				// AVR  - audio       - Audio Visual Research
825
				'avr'  => array(
826
							'pattern'   => '^2BIT',
827
							'group'     => 'audio',
828
							'module'    => 'avr',
829
							'mime_type' => 'application/octet-stream',
830
						),
831
832
				// BONK - audio       - Bonk v0.9+
833
				'bonk' => array(
834
							'pattern'   => '^\\x00(BONK|INFO|META| ID3)',
835
							'group'     => 'audio',
836
							'module'    => 'bonk',
837
							'mime_type' => 'audio/xmms-bonk',
838
						),
839
840
				// DSF  - audio       - Direct Stream Digital (DSD) Storage Facility files (DSF) - https://en.wikipedia.org/wiki/Direct_Stream_Digital
841
				'dsf'  => array(
842
							'pattern'   => '^DSD ',  // including trailing space: 44 53 44 20
843
							'group'     => 'audio',
844
							'module'    => 'dsf',
845
							'mime_type' => 'audio/dsd',
846
						),
847
848
				// DSS  - audio       - Digital Speech Standard
849
				'dss'  => array(
850
							'pattern'   => '^[\\x02-\\x08]ds[s2]',
851
							'group'     => 'audio',
852
							'module'    => 'dss',
853
							'mime_type' => 'application/octet-stream',
854
						),
855
856
				// DSDIFF - audio     - Direct Stream Digital Interchange File Format
857
				'dsdiff' => array(
858
							'pattern'   => '^FRM8',
859
							'group'     => 'audio',
860
							'module'    => 'dsdiff',
861
							'mime_type' => 'audio/dsd',
862
						),
863
864
				// DTS  - audio       - Dolby Theatre System
865
				'dts'  => array(
866
							'pattern'   => '^\\x7F\\xFE\\x80\\x01',
867
							'group'     => 'audio',
868
							'module'    => 'dts',
869
							'mime_type' => 'audio/dts',
870
						),
871
872
				// FLAC - audio       - Free Lossless Audio Codec
873
				'flac' => array(
874
							'pattern'   => '^fLaC',
875
							'group'     => 'audio',
876
							'module'    => 'flac',
877
							'mime_type' => 'audio/flac',
878
						),
879
880
				// LA   - audio       - Lossless Audio (LA)
881
				'la'   => array(
882
							'pattern'   => '^LA0[2-4]',
883
							'group'     => 'audio',
884
							'module'    => 'la',
885
							'mime_type' => 'application/octet-stream',
886
						),
887
888
				// LPAC - audio       - Lossless Predictive Audio Compression (LPAC)
889
				'lpac' => array(
890
							'pattern'   => '^LPAC',
891
							'group'     => 'audio',
892
							'module'    => 'lpac',
893
							'mime_type' => 'application/octet-stream',
894
						),
895
896
				// MIDI - audio       - MIDI (Musical Instrument Digital Interface)
897
				'midi' => array(
898
							'pattern'   => '^MThd',
899
							'group'     => 'audio',
900
							'module'    => 'midi',
901
							'mime_type' => 'audio/midi',
902
						),
903
904
				// MAC  - audio       - Monkey's Audio Compressor
905
				'mac'  => array(
906
							'pattern'   => '^MAC ',
907
							'group'     => 'audio',
908
							'module'    => 'monkey',
909
							'mime_type' => 'audio/x-monkeys-audio',
910
						),
911
912
// has been known to produce false matches in random files (e.g. JPEGs), leave out until more precise matching available
913
//				// MOD  - audio       - MODule (assorted sub-formats)
914
//				'mod'  => array(
915
//							'pattern'   => '^.{1080}(M\\.K\\.|M!K!|FLT4|FLT8|[5-9]CHN|[1-3][0-9]CH)',
916
//							'group'     => 'audio',
917
//							'module'    => 'mod',
918
//							'option'    => 'mod',
919
//							'mime_type' => 'audio/mod',
920
//						),
921
922
				// MOD  - audio       - MODule (Impulse Tracker)
923
				'it'   => array(
924
							'pattern'   => '^IMPM',
925
							'group'     => 'audio',
926
							'module'    => 'mod',
927
							//'option'    => 'it',
928
							'mime_type' => 'audio/it',
929
						),
930
931
				// MOD  - audio       - MODule (eXtended Module, various sub-formats)
932
				'xm'   => array(
933
							'pattern'   => '^Extended Module',
934
							'group'     => 'audio',
935
							'module'    => 'mod',
936
							//'option'    => 'xm',
937
							'mime_type' => 'audio/xm',
938
						),
939
940
				// MOD  - audio       - MODule (ScreamTracker)
941
				's3m'  => array(
942
							'pattern'   => '^.{44}SCRM',
943
							'group'     => 'audio',
944
							'module'    => 'mod',
945
							//'option'    => 's3m',
946
							'mime_type' => 'audio/s3m',
947
						),
948
949
				// MPC  - audio       - Musepack / MPEGplus
950
				'mpc'  => array(
951
							'pattern'   => '^(MPCK|MP\\+|[\\x00\\x01\\x10\\x11\\x40\\x41\\x50\\x51\\x80\\x81\\x90\\x91\\xC0\\xC1\\xD0\\xD1][\\x20-\\x37][\\x00\\x20\\x40\\x60\\x80\\xA0\\xC0\\xE0])',
952
							'group'     => 'audio',
953
							'module'    => 'mpc',
954
							'mime_type' => 'audio/x-musepack',
955
						),
956
957
				// MP3  - audio       - MPEG-audio Layer 3 (very similar to AAC-ADTS)
958
				'mp3'  => array(
959
							'pattern'   => '^\\xFF[\\xE2-\\xE7\\xF2-\\xF7\\xFA-\\xFF][\\x00-\\x0B\\x10-\\x1B\\x20-\\x2B\\x30-\\x3B\\x40-\\x4B\\x50-\\x5B\\x60-\\x6B\\x70-\\x7B\\x80-\\x8B\\x90-\\x9B\\xA0-\\xAB\\xB0-\\xBB\\xC0-\\xCB\\xD0-\\xDB\\xE0-\\xEB\\xF0-\\xFB]',
960
							'group'     => 'audio',
961
							'module'    => 'mp3',
962
							'mime_type' => 'audio/mpeg',
963
						),
964
965
				// OFR  - audio       - OptimFROG
966
				'ofr'  => array(
967
							'pattern'   => '^(\\*RIFF|OFR)',
968
							'group'     => 'audio',
969
							'module'    => 'optimfrog',
970
							'mime_type' => 'application/octet-stream',
971
						),
972
973
				// RKAU - audio       - RKive AUdio compressor
974
				'rkau' => array(
975
							'pattern'   => '^RKA',
976
							'group'     => 'audio',
977
							'module'    => 'rkau',
978
							'mime_type' => 'application/octet-stream',
979
						),
980
981
				// SHN  - audio       - Shorten
982
				'shn'  => array(
983
							'pattern'   => '^ajkg',
984
							'group'     => 'audio',
985
							'module'    => 'shorten',
986
							'mime_type' => 'audio/xmms-shn',
987
							'fail_id3'  => 'ERROR',
988
							'fail_ape'  => 'ERROR',
989
						),
990
991
				// TAK  - audio       - Tom's lossless Audio Kompressor
992
				'tak'  => array(
993
							'pattern'   => '^tBaK',
994
							'group'     => 'audio',
995
							'module'    => 'tak',
996
							'mime_type' => 'application/octet-stream',
997
						),
998
999
				// TTA  - audio       - TTA Lossless Audio Compressor (http://tta.corecodec.org)
1000
				'tta'  => array(
1001
							'pattern'   => '^TTA',  // could also be '^TTA(\\x01|\\x02|\\x03|2|1)'
1002
							'group'     => 'audio',
1003
							'module'    => 'tta',
1004
							'mime_type' => 'application/octet-stream',
1005
						),
1006
1007
				// VOC  - audio       - Creative Voice (VOC)
1008
				'voc'  => array(
1009
							'pattern'   => '^Creative Voice File',
1010
							'group'     => 'audio',
1011
							'module'    => 'voc',
1012
							'mime_type' => 'audio/voc',
1013
						),
1014
1015
				// VQF  - audio       - transform-domain weighted interleave Vector Quantization Format (VQF)
1016
				'vqf'  => array(
1017
							'pattern'   => '^TWIN',
1018
							'group'     => 'audio',
1019
							'module'    => 'vqf',
1020
							'mime_type' => 'application/octet-stream',
1021
						),
1022
1023
				// WV  - audio        - WavPack (v4.0+)
1024
				'wv'   => array(
1025
							'pattern'   => '^wvpk',
1026
							'group'     => 'audio',
1027
							'module'    => 'wavpack',
1028
							'mime_type' => 'application/octet-stream',
1029
						),
1030
1031
1032
				// Audio-Video formats
1033
1034
				// ASF  - audio/video - Advanced Streaming Format, Windows Media Video, Windows Media Audio
1035
				'asf'  => array(
1036
							'pattern'   => '^\\x30\\x26\\xB2\\x75\\x8E\\x66\\xCF\\x11\\xA6\\xD9\\x00\\xAA\\x00\\x62\\xCE\\x6C',
1037
							'group'     => 'audio-video',
1038
							'module'    => 'asf',
1039
							'mime_type' => 'video/x-ms-asf',
1040
							'iconv_req' => false,
1041
						),
1042
1043
				// BINK - audio/video - Bink / Smacker
1044
				'bink' => array(
1045
							'pattern'   => '^(BIK|SMK)',
1046
							'group'     => 'audio-video',
1047
							'module'    => 'bink',
1048
							'mime_type' => 'application/octet-stream',
1049
						),
1050
1051
				// FLV  - audio/video - FLash Video
1052
				'flv' => array(
1053
							'pattern'   => '^FLV[\\x01]',
1054
							'group'     => 'audio-video',
1055
							'module'    => 'flv',
1056
							'mime_type' => 'video/x-flv',
1057
						),
1058
1059
				// IVF - audio/video - IVF
1060
				'ivf' => array(
1061
							'pattern'   => '^DKIF',
1062
							'group'     => 'audio-video',
1063
							'module'    => 'ivf',
1064
							'mime_type' => 'video/x-ivf',
1065
						),
1066
1067
				// MKAV - audio/video - Mastroka
1068
				'matroska' => array(
1069
							'pattern'   => '^\\x1A\\x45\\xDF\\xA3',
1070
							'group'     => 'audio-video',
1071
							'module'    => 'matroska',
1072
							'mime_type' => 'video/x-matroska', // may also be audio/x-matroska
1073
						),
1074
1075
				// MPEG - audio/video - MPEG (Moving Pictures Experts Group)
1076
				'mpeg' => array(
1077
							'pattern'   => '^\\x00\\x00\\x01[\\xB3\\xBA]',
1078
							'group'     => 'audio-video',
1079
							'module'    => 'mpeg',
1080
							'mime_type' => 'video/mpeg',
1081
						),
1082
1083
				// NSV  - audio/video - Nullsoft Streaming Video (NSV)
1084
				'nsv'  => array(
1085
							'pattern'   => '^NSV[sf]',
1086
							'group'     => 'audio-video',
1087
							'module'    => 'nsv',
1088
							'mime_type' => 'application/octet-stream',
1089
						),
1090
1091
				// Ogg  - audio/video - Ogg (Ogg-Vorbis, Ogg-FLAC, Speex, Ogg-Theora(*), Ogg-Tarkin(*))
1092
				'ogg'  => array(
1093
							'pattern'   => '^OggS',
1094
							'group'     => 'audio',
1095
							'module'    => 'ogg',
1096
							'mime_type' => 'application/ogg',
1097
							'fail_id3'  => 'WARNING',
1098
							'fail_ape'  => 'WARNING',
1099
						),
1100
1101
				// QT   - audio/video - Quicktime
1102
				'quicktime' => array(
1103
							'pattern'   => '^.{4}(cmov|free|ftyp|mdat|moov|pnot|skip|wide)',
1104
							'group'     => 'audio-video',
1105
							'module'    => 'quicktime',
1106
							'mime_type' => 'video/quicktime',
1107
						),
1108
1109
				// RIFF - audio/video - Resource Interchange File Format (RIFF) / WAV / AVI / CD-audio / SDSS = renamed variant used by SmartSound QuickTracks (www.smartsound.com) / FORM = Audio Interchange File Format (AIFF)
1110
				'riff' => array(
1111
							'pattern'   => '^(RIFF|SDSS|FORM)',
1112
							'group'     => 'audio-video',
1113
							'module'    => 'riff',
1114
							'mime_type' => 'audio/wav',
1115
							'fail_ape'  => 'WARNING',
1116
						),
1117
1118
				// Real - audio/video - RealAudio, RealVideo
1119
				'real' => array(
1120
							'pattern'   => '^\\.(RMF|ra)',
1121
							'group'     => 'audio-video',
1122
							'module'    => 'real',
1123
							'mime_type' => 'audio/x-realaudio',
1124
						),
1125
1126
				// SWF - audio/video - ShockWave Flash
1127
				'swf' => array(
1128
							'pattern'   => '^(F|C)WS',
1129
							'group'     => 'audio-video',
1130
							'module'    => 'swf',
1131
							'mime_type' => 'application/x-shockwave-flash',
1132
						),
1133
1134
				// TS - audio/video - MPEG-2 Transport Stream
1135
				'ts' => array(
1136
							'pattern'   => '^(\\x47.{187}){10,}', // packets are 188 bytes long and start with 0x47 "G".  Check for at least 10 packets matching this pattern
1137
							'group'     => 'audio-video',
1138
							'module'    => 'ts',
1139
							'mime_type' => 'video/MP2T',
1140
						),
1141
1142
				// WTV - audio/video - Windows Recorded TV Show
1143
				'wtv' => array(
1144
							'pattern'   => '^\\xB7\\xD8\\x00\\x20\\x37\\x49\\xDA\\x11\\xA6\\x4E\\x00\\x07\\xE9\\x5E\\xAD\\x8D',
1145
							'group'     => 'audio-video',
1146
							'module'    => 'wtv',
1147
							'mime_type' => 'video/x-ms-wtv',
1148
						),
1149
1150
1151
				// Still-Image formats
1152
1153
				// BMP  - still image - Bitmap (Windows, OS/2; uncompressed, RLE8, RLE4)
1154
				'bmp'  => array(
1155
							'pattern'   => '^BM',
1156
							'group'     => 'graphic',
1157
							'module'    => 'bmp',
1158
							'mime_type' => 'image/bmp',
1159
							'fail_id3'  => 'ERROR',
1160
							'fail_ape'  => 'ERROR',
1161
						),
1162
1163
				// GIF  - still image - Graphics Interchange Format
1164
				'gif'  => array(
1165
							'pattern'   => '^GIF',
1166
							'group'     => 'graphic',
1167
							'module'    => 'gif',
1168
							'mime_type' => 'image/gif',
1169
							'fail_id3'  => 'ERROR',
1170
							'fail_ape'  => 'ERROR',
1171
						),
1172
1173
				// JPEG - still image - Joint Photographic Experts Group (JPEG)
1174
				'jpg'  => array(
1175
							'pattern'   => '^\\xFF\\xD8\\xFF',
1176
							'group'     => 'graphic',
1177
							'module'    => 'jpg',
1178
							'mime_type' => 'image/jpeg',
1179
							'fail_id3'  => 'ERROR',
1180
							'fail_ape'  => 'ERROR',
1181
						),
1182
1183
				// PCD  - still image - Kodak Photo CD
1184
				'pcd'  => array(
1185
							'pattern'   => '^.{2048}PCD_IPI\\x00',
1186
							'group'     => 'graphic',
1187
							'module'    => 'pcd',
1188
							'mime_type' => 'image/x-photo-cd',
1189
							'fail_id3'  => 'ERROR',
1190
							'fail_ape'  => 'ERROR',
1191
						),
1192
1193
1194
				// PNG  - still image - Portable Network Graphics (PNG)
1195
				'png'  => array(
1196
							'pattern'   => '^\\x89\\x50\\x4E\\x47\\x0D\\x0A\\x1A\\x0A',
1197
							'group'     => 'graphic',
1198
							'module'    => 'png',
1199
							'mime_type' => 'image/png',
1200
							'fail_id3'  => 'ERROR',
1201
							'fail_ape'  => 'ERROR',
1202
						),
1203
1204
1205
				// SVG  - still image - Scalable Vector Graphics (SVG)
1206
				'svg'  => array(
1207
							'pattern'   => '(<!DOCTYPE svg PUBLIC |xmlns="http://www\\.w3\\.org/2000/svg")',
1208
							'group'     => 'graphic',
1209
							'module'    => 'svg',
1210
							'mime_type' => 'image/svg+xml',
1211
							'fail_id3'  => 'ERROR',
1212
							'fail_ape'  => 'ERROR',
1213
						),
1214
1215
1216
				// TIFF - still image - Tagged Information File Format (TIFF)
1217
				'tiff' => array(
1218
							'pattern'   => '^(II\\x2A\\x00|MM\\x00\\x2A)',
1219
							'group'     => 'graphic',
1220
							'module'    => 'tiff',
1221
							'mime_type' => 'image/tiff',
1222
							'fail_id3'  => 'ERROR',
1223
							'fail_ape'  => 'ERROR',
1224
						),
1225
1226
1227
				// EFAX - still image - eFax (TIFF derivative)
1228
				'efax'  => array(
1229
							'pattern'   => '^\\xDC\\xFE',
1230
							'group'     => 'graphic',
1231
							'module'    => 'efax',
1232
							'mime_type' => 'image/efax',
1233
							'fail_id3'  => 'ERROR',
1234
							'fail_ape'  => 'ERROR',
1235
						),
1236
1237
1238
				// Data formats
1239
1240
				// ISO  - data        - International Standards Organization (ISO) CD-ROM Image
1241
				'iso'  => array(
1242
							'pattern'   => '^.{32769}CD001',
1243
							'group'     => 'misc',
1244
							'module'    => 'iso',
1245
							'mime_type' => 'application/octet-stream',
1246
							'fail_id3'  => 'ERROR',
1247
							'fail_ape'  => 'ERROR',
1248
							'iconv_req' => false,
1249
						),
1250
1251
				// HPK  - data        - HPK compressed data
1252
				'hpk'  => array(
1253
							'pattern'   => '^BPUL',
1254
							'group'     => 'archive',
1255
							'module'    => 'hpk',
1256
							'mime_type' => 'application/octet-stream',
1257
							'fail_id3'  => 'ERROR',
1258
							'fail_ape'  => 'ERROR',
1259
						),
1260
1261
				// RAR  - data        - RAR compressed data
1262
				'rar'  => array(
1263
							'pattern'   => '^Rar\\!',
1264
							'group'     => 'archive',
1265
							'module'    => 'rar',
1266
							'mime_type' => 'application/vnd.rar',
1267
							'fail_id3'  => 'ERROR',
1268
							'fail_ape'  => 'ERROR',
1269
						),
1270
1271
				// SZIP - audio/data  - SZIP compressed data
1272
				'szip' => array(
1273
							'pattern'   => '^SZ\\x0A\\x04',
1274
							'group'     => 'archive',
1275
							'module'    => 'szip',
1276
							'mime_type' => 'application/octet-stream',
1277
							'fail_id3'  => 'ERROR',
1278
							'fail_ape'  => 'ERROR',
1279
						),
1280
1281
				// TAR  - data        - TAR compressed data
1282
				'tar'  => array(
1283
							'pattern'   => '^.{100}[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20]{7}\\x00[0-9\\x20\\x00]{12}[0-9\\x20\\x00]{12}',
1284
							'group'     => 'archive',
1285
							'module'    => 'tar',
1286
							'mime_type' => 'application/x-tar',
1287
							'fail_id3'  => 'ERROR',
1288
							'fail_ape'  => 'ERROR',
1289
						),
1290
1291
				// GZIP  - data        - GZIP compressed data
1292
				'gz'  => array(
1293
							'pattern'   => '^\\x1F\\x8B\\x08',
1294
							'group'     => 'archive',
1295
							'module'    => 'gzip',
1296
							'mime_type' => 'application/gzip',
1297
							'fail_id3'  => 'ERROR',
1298
							'fail_ape'  => 'ERROR',
1299
						),
1300
1301
				// ZIP  - data         - ZIP compressed data
1302
				'zip'  => array(
1303
							'pattern'   => '^PK\\x03\\x04',
1304
							'group'     => 'archive',
1305
							'module'    => 'zip',
1306
							'mime_type' => 'application/zip',
1307
							'fail_id3'  => 'ERROR',
1308
							'fail_ape'  => 'ERROR',
1309
						),
1310
1311
				// XZ   - data         - XZ compressed data
1312
				'xz'  => array(
1313
							'pattern'   => '^\\xFD7zXZ\\x00',
1314
							'group'     => 'archive',
1315
							'module'    => 'xz',
1316
							'mime_type' => 'application/x-xz',
1317
							'fail_id3'  => 'ERROR',
1318
							'fail_ape'  => 'ERROR',
1319
						),
1320
1321
1322
				// Misc other formats
1323
1324
				// PAR2 - data        - Parity Volume Set Specification 2.0
1325
				'par2' => array (
1326
							'pattern'   => '^PAR2\\x00PKT',
1327
							'group'     => 'misc',
1328
							'module'    => 'par2',
1329
							'mime_type' => 'application/octet-stream',
1330
							'fail_id3'  => 'ERROR',
1331
							'fail_ape'  => 'ERROR',
1332
						),
1333
1334
				// PDF  - data        - Portable Document Format
1335
				'pdf'  => array(
1336
							'pattern'   => '^\\x25PDF',
1337
							'group'     => 'misc',
1338
							'module'    => 'pdf',
1339
							'mime_type' => 'application/pdf',
1340
							'fail_id3'  => 'ERROR',
1341
							'fail_ape'  => 'ERROR',
1342
						),
1343
1344
				// MSOFFICE  - data   - ZIP compressed data
1345
				'msoffice' => array(
1346
							'pattern'   => '^\\xD0\\xCF\\x11\\xE0\\xA1\\xB1\\x1A\\xE1', // D0CF11E == DOCFILE == Microsoft Office Document
1347
							'group'     => 'misc',
1348
							'module'    => 'msoffice',
1349
							'mime_type' => 'application/octet-stream',
1350
							'fail_id3'  => 'ERROR',
1351
							'fail_ape'  => 'ERROR',
1352
						),
1353
1354
				 // CUE  - data       - CUEsheet (index to single-file disc images)
1355
				 'cue' => array(
1356
							'pattern'   => '', // empty pattern means cannot be automatically detected, will fall through all other formats and match based on filename and very basic file contents
1357
							'group'     => 'misc',
1358
							'module'    => 'cue',
1359
							'mime_type' => 'application/octet-stream',
1360
						   ),
1361
1362
			);
1363
		}
1364
1365
		return $format_info;
1366
	}
1367
1368
	/**
1369
	 * @param string $filedata
1370
	 * @param string $filename
1371
	 *
1372
	 * @return mixed|false
1373
	 */
1374
	public function GetFileFormat(&$filedata, $filename='') {
1375
		// this function will determine the format of a file based on usually
1376
		// the first 2-4 bytes of the file (8 bytes for PNG, 16 bytes for JPG,
1377
		// and in the case of ISO CD image, 6 bytes offset 32kb from the start
1378
		// of the file).
1379
1380
		// Identify file format - loop through $format_info and detect with reg expr
1381
		foreach ($this->GetFileFormatArray() as $format_name => $info) {
1382
			// The /s switch on preg_match() forces preg_match() NOT to treat
1383
			// newline (0x0A) characters as special chars but do a binary match
1384
			if (!empty($info['pattern']) && preg_match('#'.$info['pattern'].'#s', $filedata)) {
1385
				$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1386
				return $info;
1387
			}
1388
		}
1389
1390
1391
		if (preg_match('#\\.mp[123a]$#i', $filename)) {
1392
			// Too many mp3 encoders on the market put garbage in front of mpeg files
1393
			// use assume format on these if format detection failed
1394
			$GetFileFormatArray = $this->GetFileFormatArray();
1395
			$info = $GetFileFormatArray['mp3'];
1396
			$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
1397
			return $info;
1398
		} elseif (preg_match('#\\.cue$#i', $filename) && preg_match('#FILE "[^"]+" (BINARY|MOTOROLA|AIFF|WAVE|MP3)#', $filedata)) {
1399
			// there's not really a useful consistent "magic" at the beginning of .cue files to identify them
1400
			// so until I think of something better, just go by filename if all other format checks fail
1401
			// and verify there's at least one instance of "TRACK xx AUDIO" in the file
1402
			$GetFileFormatArray = $this->GetFileFormatArray();
1403
			$info = $GetFileFormatArray['cue'];
1404
			$info['include']   = 'module.'.$info['group'].'.'.$info['module'].'.php';
1405
			return $info;
1406
		}
1407
1408
		return false;
1409
	}
1410
1411
	/**
1412
	 * Converts array to $encoding charset from $this->encoding.
1413
	 *
1414
	 * @param array  $array
1415
	 * @param string $encoding
1416
	 */
1417
	public function CharConvert(&$array, $encoding) {
1418
1419
		// identical encoding - end here
1420
		if ($encoding == $this->encoding) {
1421
			return;
1422
		}
1423
1424
		// loop thru array
1425
		foreach ($array as $key => $value) {
1426
1427
			// go recursive
1428
			if (is_array($value)) {
1429
				$this->CharConvert($array[$key], $encoding);
1430
			}
1431
1432
			// convert string
1433
			elseif (is_string($value)) {
1434
				$array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));
1435
			}
1436
		}
1437
	}
1438
1439
	/**
1440
	 * @return bool
1441
	 */
1442
	public function HandleAllTags() {
1443
1444
		// key name => array (tag name, character encoding)
1445
		static $tags;
1446
		if (empty($tags)) {
1447
			$tags = array(
1448
				'asf'       => array('asf'           , 'UTF-16LE'),
1449
				'midi'      => array('midi'          , 'ISO-8859-1'),
1450
				'nsv'       => array('nsv'           , 'ISO-8859-1'),
1451
				'ogg'       => array('vorbiscomment' , 'UTF-8'),
1452
				'png'       => array('png'           , 'UTF-8'),
1453
				'tiff'      => array('tiff'          , 'ISO-8859-1'),
1454
				'quicktime' => array('quicktime'     , 'UTF-8'),
1455
				'real'      => array('real'          , 'ISO-8859-1'),
1456
				'vqf'       => array('vqf'           , 'ISO-8859-1'),
1457
				'zip'       => array('zip'           , 'ISO-8859-1'),
1458
				'riff'      => array('riff'          , 'ISO-8859-1'),
1459
				'lyrics3'   => array('lyrics3'       , 'ISO-8859-1'),
1460
				'id3v1'     => array('id3v1'         , $this->encoding_id3v1),
1461
				'id3v2'     => array('id3v2'         , 'UTF-8'), // not according to the specs (every frame can have a different encoding), but getID3() force-converts all encodings to UTF-8
1462
				'ape'       => array('ape'           , 'UTF-8'),
1463
				'cue'       => array('cue'           , 'ISO-8859-1'),
1464
				'matroska'  => array('matroska'      , 'UTF-8'),
1465
				'flac'      => array('vorbiscomment' , 'UTF-8'),
1466
				'divxtag'   => array('divx'          , 'ISO-8859-1'),
1467
				'iptc'      => array('iptc'          , 'ISO-8859-1'),
1468
				'dsdiff'    => array('dsdiff'        , 'ISO-8859-1'),
1469
			);
1470
		}
1471
1472
		// loop through comments array
1473
		foreach ($tags as $comment_name => $tagname_encoding_array) {
1474
			list($tag_name, $encoding) = $tagname_encoding_array;
1475
1476
			// fill in default encoding type if not already present
1477
			if (isset($this->info[$comment_name]) && !isset($this->info[$comment_name]['encoding'])) {
1478
				$this->info[$comment_name]['encoding'] = $encoding;
1479
			}
1480
1481
			// copy comments if key name set
1482
			if (!empty($this->info[$comment_name]['comments'])) {
1483
				foreach ($this->info[$comment_name]['comments'] as $tag_key => $valuearray) {
1484
					foreach ($valuearray as $key => $value) {
1485
						if (is_string($value)) {
1486
							$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
1487
						}
1488
						if ($value) {
1489
							if (!is_numeric($key)) {
1490
								$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
1491
							} else {
1492
								$this->info['tags'][trim($tag_name)][trim($tag_key)][]     = $value;
1493
							}
1494
						}
1495
					}
1496
					if ($tag_key == 'picture') {
1497
						// pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
1498
						unset($this->info[$comment_name]['comments'][$tag_key]);
1499
					}
1500
				}
1501
1502
				if (!isset($this->info['tags'][$tag_name])) {
1503
					// comments are set but contain nothing but empty strings, so skip
1504
					continue;
1505
				}
1506
1507
				$this->CharConvert($this->info['tags'][$tag_name], $this->info[$comment_name]['encoding']);           // only copy gets converted!
1508
1509
				if ($this->option_tags_html) {
1510
					foreach ($this->info['tags'][$tag_name] as $tag_key => $valuearray) {
1511
						if ($tag_key == 'picture') {
1512
							// Do not to try to convert binary picture data to HTML
1513
							// https://github.com/JamesHeinrich/getID3/issues/178
1514
							continue;
1515
						}
1516
						$this->info['tags_html'][$tag_name][$tag_key] = getid3_lib::recursiveMultiByteCharString2HTML($valuearray, $this->info[$comment_name]['encoding']);
1517
					}
1518
				}
1519
1520
			}
1521
1522
		}
1523
1524
		// pictures can take up a lot of space, and we don't need multiple copies of them; let there be a single copy in [comments][picture], and not elsewhere
1525
		if (!empty($this->info['tags'])) {
1526
			$unset_keys = array('tags', 'tags_html');
1527
			foreach ($this->info['tags'] as $tagtype => $tagarray) {
1528
				foreach ($tagarray as $tagname => $tagdata) {
1529
					if ($tagname == 'picture') {
1530
						foreach ($tagdata as $key => $tagarray) {
1531
							$this->info['comments']['picture'][] = $tagarray;
1532
							if (isset($tagarray['data']) && isset($tagarray['image_mime'])) {
1533 View Code Duplication
								if (isset($this->info['tags'][$tagtype][$tagname][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1534
									unset($this->info['tags'][$tagtype][$tagname][$key]);
1535
								}
1536 View Code Duplication
								if (isset($this->info['tags_html'][$tagtype][$tagname][$key])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1537
									unset($this->info['tags_html'][$tagtype][$tagname][$key]);
1538
								}
1539
							}
1540
						}
1541
					}
1542
				}
1543
				foreach ($unset_keys as $unset_key) {
1544
					// remove possible empty keys from (e.g. [tags][id3v2][picture])
1545
					if (empty($this->info[$unset_key][$tagtype]['picture'])) {
1546
						unset($this->info[$unset_key][$tagtype]['picture']);
1547
					}
1548
					if (empty($this->info[$unset_key][$tagtype])) {
1549
						unset($this->info[$unset_key][$tagtype]);
1550
					}
1551
					if (empty($this->info[$unset_key])) {
1552
						unset($this->info[$unset_key]);
1553
					}
1554
				}
1555
				// remove duplicate copy of picture data from (e.g. [id3v2][comments][picture])
1556
				if (isset($this->info[$tagtype]['comments']['picture'])) {
1557
					unset($this->info[$tagtype]['comments']['picture']);
1558
				}
1559
				if (empty($this->info[$tagtype]['comments'])) {
1560
					unset($this->info[$tagtype]['comments']);
1561
				}
1562
				if (empty($this->info[$tagtype])) {
1563
					unset($this->info[$tagtype]);
1564
				}
1565
			}
1566
		}
1567
		return true;
1568
	}
1569
1570
	/**
1571
	 * Calls getid3_lib::CopyTagsToComments() but passes in the option_tags_html setting from this instance of getID3
1572
	 *
1573
	 * @param array $ThisFileInfo
1574
	 *
1575
	 * @return bool
1576
	 */
1577
	public function CopyTagsToComments(&$ThisFileInfo) {
1578
	    return getid3_lib::CopyTagsToComments($ThisFileInfo, $this->option_tags_html);
1579
	}
1580
1581
	/**
1582
	 * @param string $algorithm
1583
	 *
1584
	 * @return array|bool
1585
	 */
1586
	public function getHashdata($algorithm) {
1587
		switch ($algorithm) {
1588
			case 'md5':
1589
			case 'sha1':
1590
				break;
1591
1592
			default:
1593
				return $this->error('bad algorithm "'.$algorithm.'" in getHashdata()');
1594
		}
1595
1596
		if (!empty($this->info['fileformat']) && !empty($this->info['dataformat']) && ($this->info['fileformat'] == 'ogg') && ($this->info['audio']['dataformat'] == 'vorbis')) {
1597
1598
			// We cannot get an identical md5_data value for Ogg files where the comments
1599
			// span more than 1 Ogg page (compared to the same audio data with smaller
1600
			// comments) using the normal getID3() method of MD5'ing the data between the
1601
			// end of the comments and the end of the file (minus any trailing tags),
1602
			// because the page sequence numbers of the pages that the audio data is on
1603
			// do not match. Under normal circumstances, where comments are smaller than
1604
			// the nominal 4-8kB page size, then this is not a problem, but if there are
1605
			// very large comments, the only way around it is to strip off the comment
1606
			// tags with vorbiscomment and MD5 that file.
1607
			// This procedure must be applied to ALL Ogg files, not just the ones with
1608
			// comments larger than 1 page, because the below method simply MD5's the
1609
			// whole file with the comments stripped, not just the portion after the
1610
			// comments block (which is the standard getID3() method.
1611
1612
			// The above-mentioned problem of comments spanning multiple pages and changing
1613
			// page sequence numbers likely happens for OggSpeex and OggFLAC as well, but
1614
			// currently vorbiscomment only works on OggVorbis files.
1615
1616
			if (preg_match('#(1|ON)#i', ini_get('safe_mode'))) {
1617
1618
				$this->warning('Failed making system call to vorbiscomment.exe - '.$algorithm.'_data is incorrect - error returned: PHP running in Safe Mode (backtick operator not available)');
1619
				$this->info[$algorithm.'_data'] = false;
1620
1621
			} else {
1622
1623
				// Prevent user from aborting script
1624
				$old_abort = ignore_user_abort(true);
1625
1626
				// Create empty file
1627
				$empty = tempnam(GETID3_TEMP_DIR, 'getID3');
1628
				touch($empty);
1629
1630
				// Use vorbiscomment to make temp file without comments
1631
				$temp = tempnam(GETID3_TEMP_DIR, 'getID3');
1632
				$file = $this->info['filenamepath'];
1633
1634
				if (GETID3_OS_ISWINDOWS) {
1635
1636
					if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {
1637
1638
						$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
1639
						$VorbisCommentError = `$commandline`;
1640
1641
					} else {
1642
1643
						$VorbisCommentError = 'vorbiscomment.exe not found in '.GETID3_HELPERAPPSDIR;
1644
1645
					}
1646
1647
				} else {
1648
1649
					$commandline = 'vorbiscomment -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp).' 2>&1';
1650
					$VorbisCommentError = `$commandline`;
1651
1652
				}
1653
1654
				if (!empty($VorbisCommentError)) {
1655
1656
					$this->warning('Failed making system call to vorbiscomment(.exe) - '.$algorithm.'_data will be incorrect. If vorbiscomment is unavailable, please download from http://www.vorbis.com/download.psp and put in the getID3() directory. Error returned: '.$VorbisCommentError);
1657
					$this->info[$algorithm.'_data'] = false;
1658
1659
				} else {
1660
1661
					// Get hash of newly created file
1662
					switch ($algorithm) {
1663
						case 'md5':
1664
							$this->info[$algorithm.'_data'] = md5_file($temp);
1665
							break;
1666
1667
						case 'sha1':
1668
							$this->info[$algorithm.'_data'] = sha1_file($temp);
1669
							break;
1670
					}
1671
				}
1672
1673
				// Clean up
1674
				unlink($empty);
1675
				unlink($temp);
1676
1677
				// Reset abort setting
1678
				ignore_user_abort($old_abort);
1679
1680
			}
1681
1682
		} else {
1683
1684
			if (!empty($this->info['avdataoffset']) || (isset($this->info['avdataend']) && ($this->info['avdataend'] < $this->info['filesize']))) {
1685
1686
				// get hash from part of file
1687
				$this->info[$algorithm.'_data'] = getid3_lib::hash_data($this->info['filenamepath'], $this->info['avdataoffset'], $this->info['avdataend'], $algorithm);
1688
1689
			} else {
1690
1691
				// get hash from whole file
1692
				switch ($algorithm) {
1693
					case 'md5':
1694
						$this->info[$algorithm.'_data'] = md5_file($this->info['filenamepath']);
1695
						break;
1696
1697
					case 'sha1':
1698
						$this->info[$algorithm.'_data'] = sha1_file($this->info['filenamepath']);
1699
						break;
1700
				}
1701
			}
1702
1703
		}
1704
		return true;
1705
	}
1706
1707
	public function ChannelsBitratePlaytimeCalculations() {
1708
1709
		// set channelmode on audio
1710
		if (!empty($this->info['audio']['channelmode']) || !isset($this->info['audio']['channels'])) {
1711
			// ignore
1712
		} elseif ($this->info['audio']['channels'] == 1) {
1713
			$this->info['audio']['channelmode'] = 'mono';
1714
		} elseif ($this->info['audio']['channels'] == 2) {
1715
			$this->info['audio']['channelmode'] = 'stereo';
1716
		}
1717
1718
		// Calculate combined bitrate - audio + video
1719
		$CombinedBitrate  = 0;
1720
		$CombinedBitrate += (isset($this->info['audio']['bitrate']) ? $this->info['audio']['bitrate'] : 0);
1721
		$CombinedBitrate += (isset($this->info['video']['bitrate']) ? $this->info['video']['bitrate'] : 0);
1722
		if (($CombinedBitrate > 0) && empty($this->info['bitrate'])) {
1723
			$this->info['bitrate'] = $CombinedBitrate;
1724
		}
1725
		//if ((isset($this->info['video']) && !isset($this->info['video']['bitrate'])) || (isset($this->info['audio']) && !isset($this->info['audio']['bitrate']))) {
1726
		//	// for example, VBR MPEG video files cannot determine video bitrate:
1727
		//	// should not set overall bitrate and playtime from audio bitrate only
1728
		//	unset($this->info['bitrate']);
1729
		//}
1730
1731
		// video bitrate undetermined, but calculable
1732
		if (isset($this->info['video']['dataformat']) && $this->info['video']['dataformat'] && (!isset($this->info['video']['bitrate']) || ($this->info['video']['bitrate'] == 0))) {
1733
			// if video bitrate not set
1734
			if (isset($this->info['audio']['bitrate']) && ($this->info['audio']['bitrate'] > 0) && ($this->info['audio']['bitrate'] == $this->info['bitrate'])) {
1735
				// AND if audio bitrate is set to same as overall bitrate
1736
				if (isset($this->info['playtime_seconds']) && ($this->info['playtime_seconds'] > 0)) {
1737
					// AND if playtime is set
1738
					if (isset($this->info['avdataend']) && isset($this->info['avdataoffset'])) {
1739
						// AND if AV data offset start/end is known
1740
						// THEN we can calculate the video bitrate
1741
						$this->info['bitrate'] = round((($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds']);
1742
						$this->info['video']['bitrate'] = $this->info['bitrate'] - $this->info['audio']['bitrate'];
1743
					}
1744
				}
1745
			}
1746
		}
1747
1748 View Code Duplication
		if ((!isset($this->info['playtime_seconds']) || ($this->info['playtime_seconds'] <= 0)) && !empty($this->info['bitrate'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1749
			$this->info['playtime_seconds'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['bitrate'];
1750
		}
1751
1752 View Code Duplication
		if (!isset($this->info['bitrate']) && !empty($this->info['playtime_seconds'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1753
			$this->info['bitrate'] = (($this->info['avdataend'] - $this->info['avdataoffset']) * 8) / $this->info['playtime_seconds'];
1754
		}
1755
		if (isset($this->info['bitrate']) && empty($this->info['audio']['bitrate']) && empty($this->info['video']['bitrate'])) {
1756
			if (isset($this->info['audio']['dataformat']) && empty($this->info['video']['resolution_x'])) {
1757
				// audio only
1758
				$this->info['audio']['bitrate'] = $this->info['bitrate'];
1759
			} elseif (isset($this->info['video']['resolution_x']) && empty($this->info['audio']['dataformat'])) {
1760
				// video only
1761
				$this->info['video']['bitrate'] = $this->info['bitrate'];
1762
			}
1763
		}
1764
1765
		// Set playtime string
1766
		if (!empty($this->info['playtime_seconds']) && empty($this->info['playtime_string'])) {
1767
			$this->info['playtime_string'] = getid3_lib::PlaytimeString($this->info['playtime_seconds']);
1768
		}
1769
	}
1770
1771
	/**
1772
	 * @return bool
1773
	 */
1774
	public function CalculateCompressionRatioVideo() {
1775
		if (empty($this->info['video'])) {
1776
			return false;
1777
		}
1778
		if (empty($this->info['video']['resolution_x']) || empty($this->info['video']['resolution_y'])) {
1779
			return false;
1780
		}
1781
		if (empty($this->info['video']['bits_per_sample'])) {
1782
			return false;
1783
		}
1784
1785
		switch ($this->info['video']['dataformat']) {
1786
			case 'bmp':
1787
			case 'gif':
1788
			case 'jpeg':
1789
			case 'jpg':
1790
			case 'png':
1791 View Code Duplication
			case 'tiff':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1792
				$FrameRate = 1;
1793
				$PlaytimeSeconds = 1;
0 ignored issues
show
Unused Code introduced by
$PlaytimeSeconds is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1794
				$BitrateCompressed = $this->info['filesize'] * 8;
1795
				break;
1796
1797
			default:
1798
				if (!empty($this->info['video']['frame_rate'])) {
1799
					$FrameRate = $this->info['video']['frame_rate'];
1800
				} else {
1801
					return false;
1802
				}
1803
				if (!empty($this->info['playtime_seconds'])) {
1804
					$PlaytimeSeconds = $this->info['playtime_seconds'];
0 ignored issues
show
Unused Code introduced by
$PlaytimeSeconds is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1805
				} else {
1806
					return false;
1807
				}
1808
				if (!empty($this->info['video']['bitrate'])) {
1809
					$BitrateCompressed = $this->info['video']['bitrate'];
1810
				} else {
1811
					return false;
1812
				}
1813
				break;
1814
		}
1815
		$BitrateUncompressed = $this->info['video']['resolution_x'] * $this->info['video']['resolution_y'] * $this->info['video']['bits_per_sample'] * $FrameRate;
1816
1817
		$this->info['video']['compression_ratio'] = $BitrateCompressed / $BitrateUncompressed;
1818
		return true;
1819
	}
1820
1821
	/**
1822
	 * @return bool
1823
	 */
1824
	public function CalculateCompressionRatioAudio() {
1825
		if (empty($this->info['audio']['bitrate']) || empty($this->info['audio']['channels']) || empty($this->info['audio']['sample_rate']) || !is_numeric($this->info['audio']['sample_rate'])) {
1826
			return false;
1827
		}
1828
		$this->info['audio']['compression_ratio'] = $this->info['audio']['bitrate'] / ($this->info['audio']['channels'] * $this->info['audio']['sample_rate'] * (!empty($this->info['audio']['bits_per_sample']) ? $this->info['audio']['bits_per_sample'] : 16));
1829
1830
		if (!empty($this->info['audio']['streams'])) {
1831
			foreach ($this->info['audio']['streams'] as $streamnumber => $streamdata) {
1832
				if (!empty($streamdata['bitrate']) && !empty($streamdata['channels']) && !empty($streamdata['sample_rate'])) {
1833
					$this->info['audio']['streams'][$streamnumber]['compression_ratio'] = $streamdata['bitrate'] / ($streamdata['channels'] * $streamdata['sample_rate'] * (!empty($streamdata['bits_per_sample']) ? $streamdata['bits_per_sample'] : 16));
1834
				}
1835
			}
1836
		}
1837
		return true;
1838
	}
1839
1840
	/**
1841
	 * @return bool
1842
	 */
1843
	public function CalculateReplayGain() {
1844
		if (isset($this->info['replay_gain'])) {
1845
			if (!isset($this->info['replay_gain']['reference_volume'])) {
1846
				$this->info['replay_gain']['reference_volume'] = 89.0;
1847
			}
1848
			if (isset($this->info['replay_gain']['track']['adjustment'])) {
1849
				$this->info['replay_gain']['track']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['track']['adjustment'];
1850
			}
1851 View Code Duplication
			if (isset($this->info['replay_gain']['album']['adjustment'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1852
				$this->info['replay_gain']['album']['volume'] = $this->info['replay_gain']['reference_volume'] - $this->info['replay_gain']['album']['adjustment'];
1853
			}
1854
1855 View Code Duplication
			if (isset($this->info['replay_gain']['track']['peak'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1856
				$this->info['replay_gain']['track']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['track']['peak']);
1857
			}
1858 View Code Duplication
			if (isset($this->info['replay_gain']['album']['peak'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1859
				$this->info['replay_gain']['album']['max_noclip_gain'] = 0 - getid3_lib::RGADamplitude2dB($this->info['replay_gain']['album']['peak']);
1860
			}
1861
		}
1862
		return true;
1863
	}
1864
1865
	/**
1866
	 * @return bool
1867
	 */
1868
	public function ProcessAudioStreams() {
1869
		if (!empty($this->info['audio']['bitrate']) || !empty($this->info['audio']['channels']) || !empty($this->info['audio']['sample_rate'])) {
1870
			if (!isset($this->info['audio']['streams'])) {
1871
				foreach ($this->info['audio'] as $key => $value) {
1872
					if ($key != 'streams') {
1873
						$this->info['audio']['streams'][0][$key] = $value;
1874
					}
1875
				}
1876
			}
1877
		}
1878
		return true;
1879
	}
1880
1881
	/**
1882
	 * @return string|bool
1883
	 */
1884
	public function getid3_tempnam() {
1885
		return tempnam($this->tempdir, 'gI3');
1886
	}
1887
1888
	/**
1889
	 * @param string $name
1890
	 *
1891
	 * @return bool
1892
	 *
1893
	 * @throws getid3_exception
1894
	 */
1895
	public function include_module($name) {
1896
		//if (!file_exists($this->include_path.'module.'.$name.'.php')) {
1897
		if (!file_exists(GETID3_INCLUDEPATH.'module.'.$name.'.php')) {
1898
			throw new getid3_exception('Required module.'.$name.'.php is missing.');
1899
		}
1900
		include_once(GETID3_INCLUDEPATH.'module.'.$name.'.php');
1901
		return true;
1902
	}
1903
1904
	/**
1905
	 * @param string $filename
1906
	 *
1907
	 * @return bool
1908
	 */
1909
	public static function is_writable ($filename) {
1910
		$ret = is_writable($filename);
1911
		if (!$ret) {
1912
			$perms = fileperms($filename);
1913
			$ret = ($perms & 0x0080) || ($perms & 0x0010) || ($perms & 0x0002);
1914
		}
1915
		return $ret;
1916
	}
1917
1918
}
1919
1920
1921
abstract class getid3_handler
1922
{
1923
1924
	/**
1925
	* @var getID3
1926
	*/
1927
	protected $getid3;                       // pointer
1928
1929
	/**
1930
	 * Analyzing filepointer or string.
1931
	 *
1932
	 * @var bool
1933
	 */
1934
	protected $data_string_flag     = false;
1935
1936
	/**
1937
	 * String to analyze.
1938
	 *
1939
	 * @var string
1940
	 */
1941
	protected $data_string          = '';
1942
1943
	/**
1944
	 * Seek position in string.
1945
	 *
1946
	 * @var int
1947
	 */
1948
	protected $data_string_position = 0;
1949
1950
	/**
1951
	 * String length.
1952
	 *
1953
	 * @var int
1954
	 */
1955
	protected $data_string_length   = 0;
1956
1957
	/**
1958
	 * @var string
1959
	 */
1960
	private $dependency_to;
1961
1962
	/**
1963
	 * getid3_handler constructor.
1964
	 *
1965
	 * @param getID3 $getid3
1966
	 * @param string $call_module
1967
	 */
1968
	public function __construct(getID3 $getid3, $call_module=null) {
1969
		$this->getid3 = $getid3;
1970
1971
		if ($call_module) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $call_module of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null 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...
1972
			$this->dependency_to = str_replace('getid3_', '', $call_module);
1973
		}
1974
	}
1975
1976
	/**
1977
	 * Analyze from file pointer.
1978
	 *
1979
	 * @return bool
1980
	 */
1981
	abstract public function Analyze();
1982
1983
	/**
1984
	 * Analyze from string instead.
1985
	 *
1986
	 * @param string $string
1987
	 */
1988
	public function AnalyzeString($string) {
1989
		// Enter string mode
1990
		$this->setStringMode($string);
1991
1992
		// Save info
1993
		$saved_avdataoffset = $this->getid3->info['avdataoffset'];
1994
		$saved_avdataend    = $this->getid3->info['avdataend'];
1995
		$saved_filesize     = (isset($this->getid3->info['filesize']) ? $this->getid3->info['filesize'] : null); // may be not set if called as dependency without openfile() call
1996
1997
		// Reset some info
1998
		$this->getid3->info['avdataoffset'] = 0;
1999
		$this->getid3->info['avdataend']    = $this->getid3->info['filesize'] = $this->data_string_length;
2000
2001
		// Analyze
2002
		$this->Analyze();
2003
2004
		// Restore some info
2005
		$this->getid3->info['avdataoffset'] = $saved_avdataoffset;
2006
		$this->getid3->info['avdataend']    = $saved_avdataend;
2007
		$this->getid3->info['filesize']     = $saved_filesize;
2008
2009
		// Exit string mode
2010
		$this->data_string_flag = false;
2011
	}
2012
2013
	/**
2014
	 * @param string $string
2015
	 */
2016
	public function setStringMode($string) {
2017
		$this->data_string_flag   = true;
2018
		$this->data_string        = $string;
2019
		$this->data_string_length = strlen($string);
2020
	}
2021
2022
	/**
2023
	 * @return int|bool
2024
	 */
2025
	protected function ftell() {
2026
		if ($this->data_string_flag) {
2027
			return $this->data_string_position;
2028
		}
2029
		return ftell($this->getid3->fp);
2030
	}
2031
2032
	/**
2033
	 * @param int $bytes
2034
	 *
2035
	 * @return string|false
2036
	 *
2037
	 * @throws getid3_exception
2038
	 */
2039
	protected function fread($bytes) {
2040
		if ($this->data_string_flag) {
2041
			$this->data_string_position += $bytes;
2042
			return substr($this->data_string, $this->data_string_position - $bytes, $bytes);
2043
		}
2044
		$pos = $this->ftell() + $bytes;
2045
		if (!getid3_lib::intValueSupported($pos)) {
2046
			throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') because beyond PHP filesystem limit', 10);
2047
		}
2048
2049
		//return fread($this->getid3->fp, $bytes);
2050
		/*
2051
		* https://www.getid3.org/phpBB3/viewtopic.php?t=1930
2052
		* "I found out that the root cause for the problem was how getID3 uses the PHP system function fread().
2053
		* It seems to assume that fread() would always return as many bytes as were requested.
2054
		* However, according the PHP manual (http://php.net/manual/en/function.fread.php), this is the case only with regular local files, but not e.g. with Linux pipes.
2055
		* The call may return only part of the requested data and a new call is needed to get more."
2056
		*/
2057
		$contents = '';
2058
		do {
2059
			//if (($this->getid3->memory_limit > 0) && ($bytes > $this->getid3->memory_limit)) {
2060
			if (($this->getid3->memory_limit > 0) && (($bytes / $this->getid3->memory_limit) > 0.99)) { // enable a more-fuzzy match to prevent close misses generating errors like "PHP Fatal error: Allowed memory size of 33554432 bytes exhausted (tried to allocate 33554464 bytes)"
2061
				throw new getid3_exception('cannot fread('.$bytes.' from '.$this->ftell().') that is more than available PHP memory ('.$this->getid3->memory_limit.')', 10);
2062
			}
2063
			$part = fread($this->getid3->fp, $bytes);
2064
			$partLength  = strlen($part);
2065
			$bytes      -= $partLength;
2066
			$contents   .= $part;
2067
		} while (($bytes > 0) && ($partLength > 0));
2068
		return $contents;
2069
	}
2070
2071
	/**
2072
	 * @param int $bytes
2073
	 * @param int $whence
2074
	 *
2075
	 * @return int
2076
	 *
2077
	 * @throws getid3_exception
2078
	 */
2079
	protected function fseek($bytes, $whence=SEEK_SET) {
2080
		if ($this->data_string_flag) {
2081
			switch ($whence) {
2082
				case SEEK_SET:
2083
					$this->data_string_position = $bytes;
2084
					break;
2085
2086
				case SEEK_CUR:
2087
					$this->data_string_position += $bytes;
2088
					break;
2089
2090
				case SEEK_END:
2091
					$this->data_string_position = $this->data_string_length + $bytes;
2092
					break;
2093
			}
2094
			return 0;
2095
		} else {
2096
			$pos = $bytes;
2097
			if ($whence == SEEK_CUR) {
2098
				$pos = $this->ftell() + $bytes;
2099
			} elseif ($whence == SEEK_END) {
2100
				$pos = $this->getid3->info['filesize'] + $bytes;
2101
			}
2102
			if (!getid3_lib::intValueSupported($pos)) {
2103
				throw new getid3_exception('cannot fseek('.$pos.') because beyond PHP filesystem limit', 10);
2104
			}
2105
		}
2106
		return fseek($this->getid3->fp, $bytes, $whence);
2107
	}
2108
2109
	/**
2110
	 * @return string|false
2111
	 *
2112
	 * @throws getid3_exception
2113
	 */
2114
	protected function fgets() {
2115
		// must be able to handle CR/LF/CRLF but not read more than one lineend
2116
		$buffer   = ''; // final string we will return
2117
		$prevchar = ''; // save previously-read character for end-of-line checking
2118
		if ($this->data_string_flag) {
2119
			while (true) {
2120
				$thischar = substr($this->data_string, $this->data_string_position++, 1);
2121
				if (($prevchar == "\r") && ($thischar != "\n")) {
2122
					// read one byte too many, back up
2123
					$this->data_string_position--;
2124
					break;
2125
				}
2126
				$buffer .= $thischar;
2127
				if ($thischar == "\n") {
2128
					break;
2129
				}
2130
				if ($this->data_string_position >= $this->data_string_length) {
2131
					// EOF
2132
					break;
2133
				}
2134
				$prevchar = $thischar;
2135
			}
2136
2137
		} else {
2138
2139
			// Ideally we would just use PHP's fgets() function, however...
2140
			// it does not behave consistently with regards to mixed line endings, may be system-dependent
2141
			// and breaks entirely when given a file with mixed \r vs \n vs \r\n line endings (e.g. some PDFs)
2142
			//return fgets($this->getid3->fp);
2143
			while (true) {
2144
				$thischar = fgetc($this->getid3->fp);
2145
				if (($prevchar == "\r") && ($thischar != "\n")) {
2146
					// read one byte too many, back up
2147
					fseek($this->getid3->fp, -1, SEEK_CUR);
2148
					break;
2149
				}
2150
				$buffer .= $thischar;
2151
				if ($thischar == "\n") {
2152
					break;
2153
				}
2154
				if (feof($this->getid3->fp)) {
2155
					break;
2156
				}
2157
				$prevchar = $thischar;
2158
			}
2159
2160
		}
2161
		return $buffer;
2162
	}
2163
2164
	/**
2165
	 * @return bool
2166
	 */
2167
	protected function feof() {
2168
		if ($this->data_string_flag) {
2169
			return $this->data_string_position >= $this->data_string_length;
2170
		}
2171
		return feof($this->getid3->fp);
2172
	}
2173
2174
	/**
2175
	 * @param string $module
2176
	 *
2177
	 * @return bool
2178
	 */
2179
	final protected function isDependencyFor($module) {
2180
		return $this->dependency_to == $module;
2181
	}
2182
2183
	/**
2184
	 * @param string $text
2185
	 *
2186
	 * @return bool
2187
	 */
2188
	protected function error($text) {
2189
		$this->getid3->info['error'][] = $text;
2190
2191
		return false;
2192
	}
2193
2194
	/**
2195
	 * @param string $text
2196
	 *
2197
	 * @return bool
2198
	 */
2199
	protected function warning($text) {
2200
		return $this->getid3->warning($text);
2201
	}
2202
2203
	/**
2204
	 * @param string $text
2205
	 */
2206
	protected function notice($text) {
0 ignored issues
show
Unused Code introduced by
The parameter $text is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2207
		// does nothing for now
2208
	}
2209
2210
	/**
2211
	 * @param string $name
2212
	 * @param int    $offset
2213
	 * @param int    $length
2214
	 * @param string $image_mime
2215
	 *
2216
	 * @return string|null
2217
	 *
2218
	 * @throws Exception
2219
	 * @throws getid3_exception
2220
	 */
2221
	public function saveAttachment($name, $offset, $length, $image_mime=null) {
2222
		try {
2223
2224
			// do not extract at all
2225
			if ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_NONE) {
2226
2227
				$attachment = null; // do not set any
2228
2229
			// extract to return array
2230
			} elseif ($this->getid3->option_save_attachments === getID3::ATTACHMENTS_INLINE) {
2231
2232
				$this->fseek($offset);
2233
				$attachment = $this->fread($length); // get whole data in one pass, till it is anyway stored in memory
2234
				if ($attachment === false || strlen($attachment) != $length) {
2235
					throw new Exception('failed to read attachment data');
2236
				}
2237
2238
			// assume directory path is given
2239
			} else {
2240
2241
				// set up destination path
2242
				$dir = rtrim(str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $this->getid3->option_save_attachments), DIRECTORY_SEPARATOR);
2243
				if (!is_dir($dir) || !getID3::is_writable($dir)) { // check supplied directory
2244
					throw new Exception('supplied path ('.$dir.') does not exist, or is not writable');
2245
				}
2246
				$dest = $dir.DIRECTORY_SEPARATOR.$name.($image_mime ? '.'.getid3_lib::ImageExtFromMime($image_mime) : '');
2247
2248
				// create dest file
2249
				if (($fp_dest = fopen($dest, 'wb')) == false) {
2250
					throw new Exception('failed to create file '.$dest);
2251
				}
2252
2253
				// copy data
2254
				$this->fseek($offset);
2255
				$buffersize = ($this->data_string_flag ? $length : $this->getid3->fread_buffer_size());
2256
				$bytesleft = $length;
2257
				while ($bytesleft > 0) {
2258
					if (($buffer = $this->fread(min($buffersize, $bytesleft))) === false || ($byteswritten = fwrite($fp_dest, $buffer)) === false || ($byteswritten === 0)) {
2259
						throw new Exception($buffer === false ? 'not enough data to read' : 'failed to write to destination file, may be not enough disk space');
2260
					}
2261
					$bytesleft -= $byteswritten;
2262
				}
2263
2264
				fclose($fp_dest);
2265
				$attachment = $dest;
2266
2267
			}
2268
2269
		} catch (Exception $e) {
2270
2271
			// close and remove dest file if created
2272
			if (isset($fp_dest) && is_resource($fp_dest)) {
2273
				fclose($fp_dest);
2274
			}
2275
2276
			if (isset($dest) && file_exists($dest)) {
2277
				unlink($dest);
2278
			}
2279
2280
			// do not set any is case of error
2281
			$attachment = null;
2282
			$this->warning('Failed to extract attachment '.$name.': '.$e->getMessage());
2283
2284
		}
2285
2286
		// seek to the end of attachment
2287
		$this->fseek($offset + $length);
2288
2289
		return $attachment;
2290
	}
2291
2292
}
2293
2294
2295
class getid3_exception extends Exception
2296
{
2297
	public $message;
2298
}
2299