Commit f4d6215eec for wordpress.org

commit f4d6215eece693d363490de9429572b4fe9668d5
Author: SergeyBiryukov <sergeybiryukov@git.wordpress.org>
Date:   Mon Sep 7 17:16:50 2026 +0000

    External Libraries: Update getID3 to version 1.9.26.

    The release hardens parsing of malformed or truncated files (ID3v2 frame size clamped to filesize, no negative seeks for APE/Lyrics3 on small files, bounds check in the QuickTime ES descriptor length parser), replaces the error-suppressed `@list()` destructuring with `array_pad()`, switches `is_integer()` to `is_int()`, and escapes command paths for the Windows `vorbiscomment.exe` helper.

    Notably, 1.9.26 also incorporates the change from r61889 (removing `LIBXML_NOENT` from `GETID3_LIBXML_OPTIONS`) upstream, so `getid3.lib.php` no longer needs a local patch. All 16 bundled files are now identical to upstream v1.9.26 again.

    Only the bundled audio/video subset was updated. The new upstream modules (`module.audio-video.isobmff.php`, `module.graphic.jpegxl.php`, …) are not included, consistent with prior updates, none of the bundled modules gained a new hard dependency.

    References:
    * [https://github.com/JamesHeinrich/getID3/releases/tag/v1.9.26 getID3 1.9.26 release notes]
    * [https://github.com/JamesHeinrich/getID3/compare/v1.9.25...v1.9.26 Full list of changes in getID3 1.9.26]

    Developed in https://github.com/WordPress/wordpress-develop/pull/13420.

    Follow-up to r47601, r48278, r52254, r54376, r56975, r61253, r61889, r62087.

    Props Soean, mukesh27.
    Fixes #66059.
    Built from https://develop.svn.wordpress.org/trunk@63520


    git-svn-id: http://core.svn.wordpress.org/trunk@62696 1a063a9b-81f0-0310-95a4-ce76da25c4cd

diff --git a/wp-includes/ID3/getid3.php b/wp-includes/ID3/getid3.php
index 121ff2faa2..4e7ccf3356 100644
--- a/wp-includes/ID3/getid3.php
+++ b/wp-includes/ID3/getid3.php
@@ -387,7 +387,7 @@ class getID3
 	 */
 	protected $startup_warning = '';

-	const VERSION           = '1.9.25-202603080933';
+	const VERSION           = '1.9.26-202609042051';
 	const FREAD_BUFFER_SIZE = 32768;

 	const ATTACHMENTS_NONE   = false;
@@ -1328,6 +1328,26 @@ class getID3
 							'fail_ape'  => 'ERROR',
 						),

+				// JXL  - still image - JPEG XL (bare codestream)
+				'jpegxl'  => array(
+							'pattern'   => '^\\xFF\\x0A',
+							'group'     => 'graphic',
+							'module'    => 'jpegxl',
+							'mime_type' => 'image/jxl',
+							'fail_id3'  => 'ERROR',
+							'fail_ape'  => 'ERROR',
+						),
+
+				// JXL  - still image - JPEG XL (ISO BMFF container)
+				'jpegxlbmff'  => array(
+							'pattern'   => '^\\x00\\x00\\x00\\x0CJXL\\x20\\x0D\\x0A\\x87\\x0A',
+							'group'     => 'graphic',
+							'module'    => 'jpegxl',
+							'mime_type' => 'image/jxl',
+							'fail_id3'  => 'ERROR',
+							'fail_ape'  => 'ERROR',
+						),
+
 				// PCD  - still image - Kodak Photo CD
 				'pcd'  => array(
 							'pattern'   => '^.{2048}PCD_IPI\\x00',
@@ -1820,7 +1840,7 @@ class getID3

 					if (file_exists(GETID3_HELPERAPPSDIR.'vorbiscomment.exe')) {

-						$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c "'.$empty.'" "'.$file.'" "'.$temp.'"';
+						$commandline = '"'.GETID3_HELPERAPPSDIR.'vorbiscomment.exe" -w -c '.escapeshellarg($empty).' '.escapeshellarg($file).' '.escapeshellarg($temp);
 						$VorbisCommentError = shell_exec($commandline);

 					} else {
diff --git a/wp-includes/ID3/module.audio-video.quicktime.php b/wp-includes/ID3/module.audio-video.quicktime.php
index 7598e0a616..161702ba18 100644
--- a/wp-includes/ID3/module.audio-video.quicktime.php
+++ b/wp-includes/ID3/module.audio-video.quicktime.php
@@ -153,7 +153,7 @@ class getid3_quicktime extends getid3_handler
 			foreach ($info['quicktime']['comments']['location.ISO6709'] as $ISO6709string) {
 				$ISO6709parsed = array('latitude'=>false, 'longitude'=>false, 'altitude'=>false);
 				if (preg_match('#^([\\+\\-])([0-9]{2}|[0-9]{4}|[0-9]{6})(\\.[0-9]+)?([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?(([\\+\\-])([0-9]{3}|[0-9]{5}|[0-9]{7})(\\.[0-9]+)?)?/$#', $ISO6709string, $matches)) {
-					@list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = $matches;
+					list($dummy, $lat_sign, $lat_deg, $lat_deg_dec, $lon_sign, $lon_deg, $lon_deg_dec, $dummy, $alt_sign, $alt_deg, $alt_deg_dec) = array_pad($matches, 11, '');

 					if (strlen($lat_deg) == 2) {        // [+-]DD.D
 						$ISO6709parsed['latitude'] = (($lat_sign == '-') ? -1 : 1) * (float) (ltrim($lat_deg, '0').$lat_deg_dec);
@@ -318,7 +318,7 @@ $this->error('HEIF files not currently supported');
 						// some "ilst" atoms contain data atoms that have a numeric name, and the data is far more accessible if the returned array is compacted
 						$allnumericnames = true;
 						foreach ($atom_structure['subatoms'] as $subatomarray) {
-							if (!is_integer($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) {
+							if (!is_int($subatomarray['name']) || (count($subatomarray['subatoms']) != 1)) {
 								$allnumericnames = false;
 								break;
 							}
@@ -1681,10 +1681,10 @@ $this->warning('incomplete/incorrect handling of "stsd" with Parrot metadata in
 				case "\xA9".'xyz':  // GPS latitude+longitude+altitude
 					$atom_structure['data'] = $atom_data;
 					if (preg_match('#([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)([\\+\\-][0-9\\.]+)?/$#i', $atom_data, $matches)) {
-						@list($all, $latitude, $longitude, $altitude) = $matches;
+						list($all, $latitude, $longitude, $altitude) = array_pad($matches, 4, '');
 						$info['quicktime']['comments']['gps_latitude'][]  = floatval($latitude);
 						$info['quicktime']['comments']['gps_longitude'][] = floatval($longitude);
-						if (!empty($altitude)) { // @phpstan-ignore-line
+						if (!empty($altitude)) {
 							$info['quicktime']['comments']['gps_altitude'][] = floatval($altitude);
 						}
 					} else {
@@ -2371,6 +2371,9 @@ $this->error('fragmented mp4 files not currently supported');
 		$num_bytes = 0;
 		$length    = 0;
 		do {
+			if ($offset >= strlen($data)) { // https://github.com/JamesHeinrich/getID3/issues/477#issuecomment-4351311805
+				break;
+			}
 			$b = ord(substr($data, $offset++, 1));
 			$length = ($length << 7) | ($b & 0x7F);
 		} while (($b & 0x80) && ($num_bytes++ < 4));
diff --git a/wp-includes/ID3/module.audio-video.riff.php b/wp-includes/ID3/module.audio-video.riff.php
index 64e4ce52f3..2bf1ec515e 100644
--- a/wp-includes/ID3/module.audio-video.riff.php
+++ b/wp-includes/ID3/module.audio-video.riff.php
@@ -446,11 +446,11 @@ class getid3_riff extends getid3_handler
 					if ($parsedXML = getid3_lib::XML2array($thisfile_riff_WAVE['iXML'][0]['data'])) {
 						$thisfile_riff_WAVE['iXML'][0]['parsed'] = $parsedXML;
 						if (isset($parsedXML['SPEED']['MASTER_SPEED'])) {
-							@list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['MASTER_SPEED']);
+							list($numerator, $denominator) = array_pad(explode('/', $parsedXML['SPEED']['MASTER_SPEED']), 2, '');
 							$thisfile_riff_WAVE['iXML'][0]['master_speed'] = (int) $numerator / ($denominator ? $denominator : 1000);
 						}
 						if (isset($parsedXML['SPEED']['TIMECODE_RATE'])) {
-							@list($numerator, $denominator) = explode('/', $parsedXML['SPEED']['TIMECODE_RATE']);
+							list($numerator, $denominator) = array_pad(explode('/', $parsedXML['SPEED']['TIMECODE_RATE']), 2, '');
 							$thisfile_riff_WAVE['iXML'][0]['timecode_rate'] = (int) $numerator / ($denominator ? $denominator : 1000);
 						}
 						if (isset($parsedXML['SPEED']['TIMESTAMP_SAMPLES_SINCE_MIDNIGHT_LO']) && !empty($parsedXML['SPEED']['TIMESTAMP_SAMPLE_RATE']) && !empty($thisfile_riff_WAVE['iXML'][0]['timecode_rate'])) {
@@ -476,7 +476,7 @@ class getid3_riff extends getid3_handler
 						$thisfile_riff['guano'] = array();
 						foreach (explode("\n", $thisfile_riff_WAVE_guan_0['data']) as $line) {
 							if ($line) {
-								@list($key, $value) = explode(':', $line, 2);
+								list($key, $value) = array_pad(explode(':', $line, 2), 2, '');
 								if (substr($value, 0, 3) == '[{"') {
 									if ($decoded = @json_decode($value, true)) {
 										if (count($decoded) === 1) {
@@ -2048,6 +2048,7 @@ class getid3_riff extends getid3_handler
 			'ISTD'=>'productionstudio',
 			'ISTR'=>'starring',
 			'ITCH'=>'encoded_by',
+			'ITRK'=>'track_number',
 			'IWEB'=>'url',
 			'IWRI'=>'writer',
 			'____'=>'comment',
diff --git a/wp-includes/ID3/module.audio.mp3.php b/wp-includes/ID3/module.audio.mp3.php
index bc414c3911..f035581c5a 100644
--- a/wp-includes/ID3/module.audio.mp3.php
+++ b/wp-includes/ID3/module.audio.mp3.php
@@ -748,6 +748,7 @@ class getid3_mp3 extends getid3_handler
 						$thisfile_mpeg_audio_lame['numeric_version'] = $matches[1];
 					}
 					if (strlen($thisfile_mpeg_audio_lame['numeric_version']) > 0) {
+						$thisfile_mpeg_audio_lame['integer_version'] = array(0, 0); // initialize to prevent undefined array keys later in case of malformed numeric_version -- https://github.com/JamesHeinrich/getID3/issues/477
 						foreach (explode('.', $thisfile_mpeg_audio_lame['numeric_version']) as $key => $number) {
 							$thisfile_mpeg_audio_lame['integer_version'][$key] = intval($number);
 						}
diff --git a/wp-includes/ID3/module.tag.apetag.php b/wp-includes/ID3/module.tag.apetag.php
index f47e354eb0..1c4c6231b1 100644
--- a/wp-includes/ID3/module.tag.apetag.php
+++ b/wp-includes/ID3/module.tag.apetag.php
@@ -53,21 +53,21 @@ class getid3_apetag extends getid3_handler

 		if ($this->overrideendoffset == 0) {

-			$this->fseek(0 - $id3v1tagsize - $apetagheadersize - $lyrics3tagsize, SEEK_END);
-			$APEfooterID3v1 = $this->fread($id3v1tagsize + $apetagheadersize + $lyrics3tagsize);
-
-			//if (preg_match('/APETAGEX.{24}TAG.{125}$/i', $APEfooterID3v1)) {
-			if (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX') {
-
-				// APE tag found before ID3v1
+			// don't seek before the start of the file on tiny files
+			$scanlength = min($id3v1tagsize + $apetagheadersize + $lyrics3tagsize, $info['filesize']);
+			$this->fseek(0 - $scanlength, SEEK_END);
+			$APEfooterID3v1 = $this->fread($scanlength);
+			$buffersize     = strlen($APEfooterID3v1);
+
+			// APE footer immediately before a trailing ID3v1 tag: /APETAGEX.{24}TAG.{125}$/i
+			$APEbeforeID3v1 = ($buffersize >= ($id3v1tagsize + $apetagheadersize)) && (substr($APEfooterID3v1, $buffersize - $id3v1tagsize - $apetagheadersize, 8) == 'APETAGEX');
+			// APE footer at the very end of the file: /APETAGEX.{24}$/i
+			$APEatEndOfFile = ($buffersize >= $apetagheadersize) && (substr($APEfooterID3v1, $buffersize - $apetagheadersize, 8) == 'APETAGEX');
+
+			if ($APEbeforeID3v1) {
 				$info['ape']['tag_offset_end'] = $info['filesize'] - $id3v1tagsize;
-
-			//} elseif (preg_match('/APETAGEX.{24}$/i', $APEfooterID3v1)) {
-			} elseif (substr($APEfooterID3v1, strlen($APEfooterID3v1) - $apetagheadersize, 8) == 'APETAGEX') {
-
-				// APE tag found, no ID3v1
+			} elseif ($APEatEndOfFile) {
 				$info['ape']['tag_offset_end'] = $info['filesize'];
-
 			}

 		} else {
@@ -276,7 +276,7 @@ class getid3_apetag extends getid3_handler
 						$this->warning('APEtag "'.$item_key.'" should be flagged as Binary data, but was incorrectly flagged as UTF-8');
 						$thisfile_ape_items_current['data'] = implode("\x00", $thisfile_ape_items_current['data']);
 					}
-					list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = explode("\x00", $thisfile_ape_items_current['data'], 2);
+					list($thisfile_ape_items_current['filename'], $thisfile_ape_items_current['data']) = array_pad(explode("\x00", $thisfile_ape_items_current['data'], 2), 2, '');
 					$thisfile_ape_items_current['data_offset'] = $thisfile_ape_items_current['offset'] + strlen($thisfile_ape_items_current['filename']."\x00");
 					$thisfile_ape_items_current['data_length'] = strlen($thisfile_ape_items_current['data']);

diff --git a/wp-includes/ID3/module.tag.id3v2.php b/wp-includes/ID3/module.tag.id3v2.php
index 981d4d442d..51a6b54104 100644
--- a/wp-includes/ID3/module.tag.id3v2.php
+++ b/wp-includes/ID3/module.tag.id3v2.php
@@ -136,6 +136,7 @@ class getid3_id3v2 extends getid3_handler
 		if (!empty($thisfile_id3v2_flags['isfooter'])) {
 			$sizeofframes -= 10; // footer takes last 10 bytes of ID3v2 header, after frame data, before audio
 		}
+		$sizeofframes = min($sizeofframes, $this->getid3->info['filesize'] - $this->ftell());
 		if ($sizeofframes > 0) {

 			$framedata = $this->fread($sizeofframes); // read all frames from file into $framedata variable
@@ -1998,7 +1999,7 @@ class getid3_id3v2 extends getid3_handler
 			// <Optional embedded sub-frames>

 			$frame_offset = 0;
-			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
+			list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
 			$parsedFrame['time_begin'] = getid3_lib::BigEndian2Int(substr($parsedFrame['data'], $frame_offset, 4));
 			$frame_offset += 4;
@@ -2066,7 +2067,7 @@ class getid3_id3v2 extends getid3_handler
 							$parsedFrame['subframes'][] = $subframe;
 							break;
 						case 'WXXX':
-							@list($subframe['chapter_url_description'], $subframe['chapter_url']) = explode("\x00", $encoding_converted_text, 2);
+							list($subframe['chapter_url_description'], $subframe['chapter_url']) = array_pad(explode("\x00", $encoding_converted_text, 2), 2, '');
 							$parsedFrame['chapter_url'][$subframe['chapter_url_description']] = $subframe['chapter_url'];
 							$parsedFrame['subframes'][] = $subframe;
 							break;
@@ -2122,7 +2123,7 @@ class getid3_id3v2 extends getid3_handler
 			// <Optional embedded sub-frames>

 			$frame_offset = 0;
-			@list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
+			list($parsedFrame['element_id']) = explode("\x00", $parsedFrame['data'], 2);
 			$frame_offset += strlen($parsedFrame['element_id']."\x00");
 			$ctoc_flags_raw = ord(substr($parsedFrame['data'], $frame_offset, 1));
 			$frame_offset += 1;
diff --git a/wp-includes/ID3/module.tag.lyrics3.php b/wp-includes/ID3/module.tag.lyrics3.php
index 01f942d005..dcd50e3cf8 100644
--- a/wp-includes/ID3/module.tag.lyrics3.php
+++ b/wp-includes/ID3/module.tag.lyrics3.php
@@ -32,11 +32,12 @@ class getid3_lyrics3 extends getid3_handler
 			return false;
 		}

-		$this->fseek((0 - 128 - 9 - 6), SEEK_END);          // end - ID3v1 - "LYRICSEND" - [Lyrics3size]
+		$scanlength = min(128 + 9 + 6, $info['filesize']); // end - ID3v1 - "LYRICSEND" - [Lyrics3size];
+		$this->fseek(0 - $scanlength, SEEK_END);
 		$lyrics3offset = null;
 		$lyrics3version = null;
 		$lyrics3size   = null;
-		$lyrics3_id3v1 = $this->fread(128 + 9 + 6);
+		$lyrics3_id3v1 = $this->fread($scanlength);
 		$lyrics3lsz    = (int) substr($lyrics3_id3v1, 0, 6); // Lyrics3size
 		$lyrics3end    = substr($lyrics3_id3v1,  6,   9); // LYRICSEND or LYRICS200
 		$id3v1tag      = substr($lyrics3_id3v1, 15, 128); // ID3v1
diff --git a/wp-includes/ID3/readme.txt b/wp-includes/ID3/readme.txt
index c1b3d47bff..426889a4bb 100644
--- a/wp-includes/ID3/readme.txt
+++ b/wp-includes/ID3/readme.txt
@@ -120,6 +120,7 @@ Reads & parses (to varying degrees):
   * BMP
   * GIF
   * JPEG
+  * JPEG XL
   * PNG
   * TIFF
   * SWF (Flash)
diff --git a/wp-includes/version.php b/wp-includes/version.php
index 4d8289925f..2785f4bc23 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
  *
  * @global string $wp_version
  */
-$wp_version = '7.2-alpha-63519';
+$wp_version = '7.2-alpha-63520';

 /**
  * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.