ID3v2: Render at most 255 child elements in a CTOC frame (#1479)

The entry count is a single byte, so a table of more than 255 children wrapped the count while still writing every child element ID, and readers took the surplus IDs as embedded frames. Render the first 255 with a matching count, as renderChplData does for Nero chapters.
This commit is contained in:
Ryan Francesconi
2026-10-31 13:42:00 +01:00
committed by GitHub
parent 36c73e883b
commit 747c6ca126
2 changed files with 30 additions and 1 deletions
@@ -25,6 +25,7 @@
#include "tableofcontentsframe.h"
#include <algorithm>
#include <utility>
#include "tpropertymap.h"
@@ -293,8 +294,13 @@ ByteVector TableOfContentsFrame::renderFields() const
if(d->isOrdered)
flags += 1;
data.append(flags);
data.append(static_cast<char>(entryCount()));
// The entry count is a single byte, so only the first 255 child elements are rendered.
const unsigned int count = std::min(entryCount(), 255U);
data.append(static_cast<char>(count));
unsigned int i = 0;
for(const auto &element : std::as_const(d->childElements)) {
if(i++ >= count)
break;
data.append(element);
data.append('\0');
}
+23
View File
@@ -137,6 +137,7 @@ class TestID3v2 : public CppUnit::TestFixture
CPPUNIT_TEST(testRenderChapterFrame);
CPPUNIT_TEST(testParseTableOfContentsFrame);
CPPUNIT_TEST(testRenderTableOfContentsFrame);
CPPUNIT_TEST(testRenderTableOfContentsFrameWithManyChildren);
CPPUNIT_TEST(testShrinkPadding);
CPPUNIT_TEST(testEmptyFrame);
CPPUNIT_TEST(testDuplicateTags);
@@ -1707,6 +1708,28 @@ public:
f.render());
}
void testRenderTableOfContentsFrameWithManyChildren()
{
const unsigned int counts[] = { 255, 256, 300 };
for(unsigned int count : counts) {
ID3v2::Header header;
ID3v2::TableOfContentsFrame f(&header, "CTOC");
f.setElementID("T");
for(unsigned int i = 0; i < count; ++i)
f.addChildElement(ByteVector("C") + ByteVector(String::number(i).toCString()));
auto eF = new ID3v2::TextIdentificationFrame("TIT2");
eF->setText("TC1");
f.addEmbeddedFrame(eF);
ID3v2::TableOfContentsFrame parsed(&header, f.render());
CPPUNIT_ASSERT_EQUAL(255U, parsed.entryCount());
CPPUNIT_ASSERT_EQUAL(ByteVector("C254"), parsed.childElements().back());
CPPUNIT_ASSERT_EQUAL(1U, parsed.embeddedFrameList().size());
CPPUNIT_ASSERT_EQUAL(ByteVector("TIT2"),
parsed.embeddedFrameList().front()->frameID());
}
}
void testShrinkPadding()
{
ScopedFileCopy copy("xing", ".mp3");