Tolerate trailing garbage in M4A files (#1186)

If an M4A file contains trailing garbage (e.g. an APE tag at the
end of the file), it was considered invalid since [e21640b].
It is important to make sure that atoms which affect modifications
written by TagLib are valid, otherwise the file might be damaged.
However, in cases where invalid atoms do not affect modifications
(which are only done inside the root level "moov" and "moof"
containers), they can be safely ignored.

This commit allows trailing garbage in M4A files as it is also
accepted in MP3, WAV and DSF files.
This commit is contained in:
Urs Fleisch 2023-12-08 08:30:28 +01:00
parent 8b3f1a459e
commit 491322d1ba
2 changed files with 31 additions and 5 deletions

View File

@ -40,6 +40,35 @@ namespace
return std::none_of(list.begin(), list.end(),
[](const auto &a) { return a->length == 0 || !checkValid(a->children); });
}
bool checkRootLevelAtoms(MP4::AtomList &list)
{
bool moovValid = false;
for(auto it = list.begin(); it != list.end(); ++it) {
bool invalid = (*it)->length == 0 || !checkValid((*it)->children);
if(!moovValid && !invalid && (*it)->name == "moov") {
moovValid = true;
}
if(invalid) {
if(moovValid && (*it)->name != "moof") {
// Only the root level atoms "moov" and (if present) "moof" are
// modified. If they are valid, ignore following invalid root level
// atoms as trailing garbage.
while(it != list.end()) {
delete *it;
it = list.erase(it);
}
return true;
}
else {
return false;
}
}
}
return true;
}
} // namespace
class MP4::File::FilePrivate
@ -127,7 +156,7 @@ MP4::File::read(bool readProperties)
return;
d->atoms = std::make_unique<Atoms>(this);
if(!checkValid(d->atoms->atoms)) {
if(!checkRootLevelAtoms(d->atoms->atoms)) {
setValid(false);
return;
}

View File

@ -602,10 +602,7 @@ public:
void testFuzzedFile()
{
MP4::File f(TEST_FILE_PATH_C("infloop.m4a"));
// The file has an invalid atom length of 2775 in the last atom
// ("free", offset 0xc521, 00000ad7 66726565), whereas the remaining file
// length is 2727 bytes, therefore the file is now considered invalid.
CPPUNIT_ASSERT(!f.isValid());
CPPUNIT_ASSERT(f.isValid());
}
void testRepeatedSave()