Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/wp-includes/ID3/getid3.lib.php
Original file line number Diff line number Diff line change
Expand Up @@ -746,10 +746,14 @@ public static function XML2array($XMLstring) {
// https://core.trac.wordpress.org/changeset/29378
// This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading is
// disabled by default, but is still needed when LIBXML_NOENT is used.
$loader = @libxml_disable_entity_loader(true);
$loader = libxml_disable_entity_loader(true);
if ($loader === false && !libxml_disable_entity_loader(true)) {
// Failed to disable external entity loading — refuse to parse to prevent XXE
return false;
}
$XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', GETID3_LIBXML_OPTIONS);
$return = self::SimpleXMLelement2array($XMLobject);
@libxml_disable_entity_loader($loader);
libxml_disable_entity_loader($loader);
return $return;
}
} else {
Expand Down
25 changes: 16 additions & 9 deletions src/wp-includes/ID3/getid3.php
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ class getID3
*/
protected $startup_warning = '';

const VERSION = '1.9.26-202609042051';
const VERSION = '1.9.28-202610301738';
const FREAD_BUFFER_SIZE = 32768;

const ATTACHMENTS_NONE = false;
Expand Down Expand Up @@ -1008,7 +1008,7 @@ public function GetFileFormatArray() {
'mime_type' => 'audio/dsd',
),

// DTS - audio - Dolby Theatre System
// DTS - audio - Digital Theater Systems
'dts' => array(
'pattern' => '^\\x7F\\xFE\\x80\\x01',
'group' => 'audio',
Expand Down Expand Up @@ -1606,7 +1606,7 @@ public function GetFileFormat(&$filedata, $filename='') {
// and verify there's at least one instance of "TRACK xx AUDIO" in the file
$GetFileFormatArray = $this->GetFileFormatArray();
$info = $GetFileFormatArray['cue'];
$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
$info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php';
return $info;
}

Expand All @@ -1629,14 +1629,21 @@ public function CharConvert(&$array, $encoding) {
// loop thru array
foreach ($array as $key => $value) {

// go recursive
if (is_array($value)) {
if ($key == 'picture') { // https://github.com/JamesHeinrich/getID3/issues/508

// pictures are binary data and should not be altered
continue;

} elseif (is_array($value)) {

// convert arrays recursively
$this->CharConvert($array[$key], $encoding);
}

// convert string
elseif (is_string($value)) {
} elseif (is_string($value)) {

// actually convert only strings
$array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value));

}
}
}
Expand Down Expand Up @@ -1690,7 +1697,7 @@ public function HandleAllTags() {
if (is_string($value)) {
$value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed!
}
if (isset($value) && $value !== "") {
if (isset($value) && ($value !== '')) {
if (!is_numeric($key)) {
$this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value;
} else {
Expand Down
24 changes: 24 additions & 0 deletions src/wp-includes/ID3/module.audio-video.quicktime.php
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
}
}

// prevent memory exhaustion from crafted files: each "stsd" entry is at least 16 bytes (4 size + 4 data_format + 6 reserved + 2 reference_index)
$max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 16);
if ($atom_structure['number_entries'] > $max_entries) {
$this->warning('QuickTime atom "stsd" claims '.$atom_structure['number_entries'].' entries but only has data for '.$max_entries.', truncating');
$atom_structure['number_entries'] = $max_entries;
}
$stsdEntriesDataOffset = 8;
for ($i = 0; $i < (int) $atom_structure['number_entries']; $i++) {
$atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4));
Expand Down Expand Up @@ -1180,6 +1186,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
$atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
// prevent memory exhaustion from crafted files: each "dref" entry is at least 12 bytes (4 size + 4 type + 1 version + 3 flags)
$max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 12);
if ($atom_structure['number_entries'] > $max_entries) {
$this->warning('QuickTime atom "dref" claims '.$atom_structure['number_entries'].' entries but only has data for '.$max_entries.', truncating');
$atom_structure['number_entries'] = $max_entries;
}
$drefDataOffset = 8;
for ($i = 0; $i < $atom_structure['number_entries']; $i++) {
$atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4));
Expand Down Expand Up @@ -1318,6 +1330,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
$atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000
$atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
// prevent memory exhaustion from crafted files: each "elst" entry is at least 12 bytes (4 track_duration + 4 media_time + 4 media_rate)
$max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 12);
if ($atom_structure['number_entries'] > $max_entries) {
$this->warning('QuickTime atom "elst" claims '.$atom_structure['number_entries'].' entries but only has data for '.$max_entries.', truncating');
$atom_structure['number_entries'] = $max_entries;
}
for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) {
$atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4));
$atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4));
Expand Down Expand Up @@ -1800,6 +1818,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset
$atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1));
$atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3));
$atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4));
// prevent memory exhaustion from crafted files: each "keys" entry is at least 8 bytes (4 key_size + 4 key_namespace)
$max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 8);
if ($atom_structure['entry_count'] > $max_entries) {
$this->warning('QuickTime atom "keys" claims '.$atom_structure['entry_count'].' entries but only has data for '.$max_entries.', truncating');
$atom_structure['entry_count'] = $max_entries;
}
$keys_atom_offset = 8;

$keys_index_base = (!empty($info['quicktime']['temp_meta_key_names']) ? count($info['quicktime']['temp_meta_key_names']) : 0); // file may contain multiple "keys" entries, starting index should be culmulative not reset to 1 on each set; https://github.com/JamesHeinrich/getID3/issues/452
Expand Down
172 changes: 90 additions & 82 deletions src/wp-includes/ID3/module.tag.id3v2.php
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,11 @@ public function Analyze() {
$info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data'])));
}
break;
case 'replaygain_album_peak':
if (empty($info['replay_gain']['album']['peak']) && !empty($txxx_array['data'])) {
$info['replay_gain']['album']['peak'] = floatval($txxx_array['data']);
}
break;
}
}
}
Expand Down Expand Up @@ -622,8 +627,10 @@ public function ParseID3v2Frame(&$parsedFrame) {
if (!function_exists('gzuncompress')) {
$this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"');
} else {
if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) {
//if ($decompresseddata = @gzuncompress($parsedFrame['data'])) {
// https://github.com/JamesHeinrich/getID3/security/advisories/GHSA-c2xw-vp6w-gpph
// Set a limit of 1/4 of PHP memory limit (if known), or default hard cap of 32MB if unknown.
$decompression_limit = ($this->getid3->memory_limit > 0) ? (int) round($this->getid3->memory_limit / 4) : 32*1024*1024;
if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4), $decompression_limit)) {
$parsedFrame['data'] = $decompresseddata;
unset($decompresseddata);
} else {
Expand Down Expand Up @@ -702,6 +709,87 @@ public function ParseID3v2Frame(&$parsedFrame) {
//unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain


} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL')) || // 4.4 IPL Involved people list (ID3v2.2 only)
(($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'TIPL')) || // 4.2.2 TIPL Involved people list (ID3v2.4 only)
(($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'TMCL'))) { // 4.2.2 TMCL Musician credits list (ID3v2.4 only)
// http://id3.org/id3v2.3.0#sec4.4
// There may only be one 'IPL' frame in each tag
// <Header for 'Involved people list', ID: 'IPL'>
// Text encoding $xx
// People list strings <textstrings>

$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
$parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);

// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
// "this tag typically contains null terminated strings, which are associated in pairs"
// "there are users that use the tag incorrectly"
$IPLS_parts_unsorted = array();
if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
// UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
$thisILPS = '';
for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
$twobytes = substr($parsedFrame['data_raw'], $i, 2);
if ($twobytes === "\x00\x00") {
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
$thisILPS = '';
} else {
$thisILPS .= $twobytes;
}
}
if (strlen($thisILPS) > 0) { // extract the last part if any, even if it's only a BOM with no text following
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
}
} else {
// ISO-8859-1 or UTF-8 or other single-byte-null character set
$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
if (end($IPLS_parts_unsorted) === '') {
// there was a terminating null after the last part and explode appended an empty string; drop it
array_pop($IPLS_parts_unsorted);
}
}

$IPLS_parts = array();
if (count($IPLS_parts_unsorted) == 1) {
// Just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson".
// Use the normal output format but with empty roles.
$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $IPLS_parts_unsorted[0]);
foreach ($IPLS_parts_sorted as $person) {
$IPLS_parts[] = array(
'position' => '',
'person' => $person
);
}
}
else {
while (\count($IPLS_parts_unsorted) >= 2) {
$IPLS_parts[] = array(
'position' => array_shift($IPLS_parts_unsorted),
'person' => array_shift($IPLS_parts_unsorted)
);
}
if (\count($IPLS_parts_unsorted) > 0) {
$this->warning("Odd number of {$parsedFrame['frame_name']} parts - expected even number forming role/name pairs");
$IPLS_parts[] = array(
'position' => array_shift($IPLS_parts_unsorted),
'person' => ''
);
}
}
$parsedFrame['data'] = $IPLS_parts;

if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']] = $parsedFrame['data'];
}


} elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame
// There may only be one text information frame of its kind in an tag.
// <Header for 'Text information frame', ID: 'T000' - 'TZZZ',
Expand Down Expand Up @@ -802,86 +890,6 @@ public function ParseID3v2Frame(&$parsedFrame) {
unset($parsedFrame['data']);


} elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only)
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL'))) { // 4.4 IPL Involved people list (ID3v2.2 only)
// http://id3.org/id3v2.3.0#sec4.4
// There may only be one 'IPL' frame in each tag
// <Header for 'User defined URL link frame', ID: 'IPL'>
// Text encoding $xx
// People list strings <textstrings>

$frame_offset = 0;
$frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1));
if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) {
$this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding');
}
$parsedFrame['encodingid'] = $frame_textencoding;
$parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']);
$parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset);

// https://www.getid3.org/phpBB3/viewtopic.php?t=1369
// "this tag typically contains null terminated strings, which are associated in pairs"
// "there are users that use the tag incorrectly"
$IPLS_parts = array();
if (strpos($parsedFrame['data_raw'], "\x00") !== false) {
$IPLS_parts_unsorted = array();
if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) {
// UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding
$thisILPS = '';
for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) {
$twobytes = substr($parsedFrame['data_raw'], $i, 2);
if ($twobytes === "\x00\x00") {
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
$thisILPS = '';
} else {
$thisILPS .= $twobytes;
}
}
if (strlen($thisILPS) > 2) { // 2-byte BOM
$IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS);
}
} else {
// ISO-8859-1 or UTF-8 or other single-byte-null character set
$IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']);
}
if (count($IPLS_parts_unsorted) == 1) {
// just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson"
foreach ($IPLS_parts_unsorted as $key => $value) {
$IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value);
$position = '';
foreach ($IPLS_parts_sorted as $person) {
$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
}
}
} elseif ((count($IPLS_parts_unsorted) % 2) == 0) {
$position = '';
$person = '';
foreach ($IPLS_parts_unsorted as $key => $value) {
if (($key % 2) == 0) {
$position = $value;
} else {
$person = $value;
$IPLS_parts[] = array('position'=>$position, 'person'=>$person);
$position = '';
$person = '';
}
}
} else {
foreach ($IPLS_parts_unsorted as $key => $value) {
$IPLS_parts[] = array($value);
}
}

} else {
$IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']);
}
$parsedFrame['data'] = $IPLS_parts;

if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) {
$info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data'];
}


} elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier
(($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier
// There may only be one 'MCDI' frame in each tag
Expand Down
Loading