From 197d2a684b14dc29d41f770e95a359c5d156db29 Mon Sep 17 00:00:00 2001 From: Urs Fleisch Date: Thu, 3 Mar 2022 21:03:48 +0100 Subject: [PATCH] Correctly parse MP4 non-full meta atoms (#1041) There are m4a files with regular (non-full) meta atoms. When such a meta atom is not correctly parsed, the subsequent atoms are not recognized and offsets will not be adjusted when atoms are added, which will corrupt the MP4 file. This change will look behind the meta atom to check if the next atom follows directly, i.e. without the four bytes with version and flags as they exist in full atoms. In such a case, these four bytes will not be skipped. Witnesses of this strange format specification are https://leo-van-stee.github.io/ https://github.com/axiomatic-systems/Bento4/blob/v1.6.0-639/Source/C%2B%2B/Core/Ap4ContainerAtom.cpp#L60 --- taglib/mp4/mp4atom.cpp | 20 +++++++++++++++++++- tests/data/non-full-meta.m4a | Bin 0 -> 5108 bytes tests/test_mp4.cpp | 22 ++++++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/data/non-full-meta.m4a diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp index e030579f..140ed12b 100644 --- a/taglib/mp4/mp4atom.cpp +++ b/taglib/mp4/mp4atom.cpp @@ -85,7 +85,25 @@ MP4::Atom::Atom(File *file) for(int i = 0; i < numContainers; i++) { if(name == containers[i]) { if(name == "meta") { - file->seek(4, File::Current); + long posAfterMeta = file->tell(); + ByteVector nextSize = file->readBlock(8).mid(4, 4); + static const char *const metaChildrenNames[] = { + "hdlr", "ilst", "mhdr", "ctry", "lang" + }; + bool metaIsFullAtom = true; + for(size_t j = 0; + j < sizeof(metaChildrenNames) / sizeof(metaChildrenNames[0]); + ++j) { + if(nextSize == metaChildrenNames[j]) { + // meta is not a full atom (i.e. not followed by version, flags). It + // is followed by the size and type of the first child atom. + metaIsFullAtom = false; + break; + } + } + // Only skip next four bytes, which contain version and flags, if meta + // is a full atom. + file->seek(posAfterMeta + (metaIsFullAtom ? 4 : 0)); } else if(name == "stsd") { file->seek(8, File::Current); diff --git a/tests/data/non-full-meta.m4a b/tests/data/non-full-meta.m4a new file mode 100644 index 0000000000000000000000000000000000000000..724cc67583ec8a0ab4ccee2e0d5c547619e0eef6 GIT binary patch literal 5108 zcmeHJ&1(};5TD(o`D*i}sr?Fd)3mjSO*OViG|;9WsS$0Z1oR-(ZFXbRX0su?DJmkt zQ$>V=;z5Lpx1xB~gAk!0cvi3$#G`+JS6OG0jZK0GLO~DvOLk}G_ukITo1OO(0O(As zH}X<`GzNqE4S!!R3w#0J|1!IGgEMKC(S z*agNe_#buwfGa4;@*1AyS!r!qphLa=4!ewP4A>kE{%pqk*bdLfqtRN`-Or?;{-~vtysu% zRo!$?QJ1o)UsF`2QuHbhm+%*rk|XRbiv%Jzs8ow*!s7KIVMghjXb0%VAm0F5 zms*fv4|psn7^*j?sA{F&sFS^k522tCY#FbIn8r;abUa0g&cJS5Mh*T^4-8>XrZ6^v zdX~1sF|8%Ch?OCn!6WKyyeqM%|ZlPu;$+PJl(+eo+HL*6j|M>8j(ms@!jwP=@N!{9fUq)=xtqkC` zGnb#Q?^fPgVrFjo<>b{n7-mFw@5CoT`=osZ&$)Oa4h+M9fFICyVG8UPtIcY$+iW(6 z!`|xZaJ!sNm*3a!?daL9FydgA>CrDqvK=sDM!cqXPRlIV literal 0 HcmV?d00001 diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 31f99287..14b23e1f 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -66,6 +66,7 @@ class TestMP4 : public CppUnit::TestFixture CPPUNIT_TEST(testWithZeroLengthAtom); CPPUNIT_TEST(testEmptyValuesRemoveItems); CPPUNIT_TEST(testRemoveMetadata); + CPPUNIT_TEST(testNonFullMetaAtom); CPPUNIT_TEST_SUITE_END(); public: @@ -686,6 +687,27 @@ public: TEST_FILE_PATH_C("no-tags.m4a"))); } } + + void testNonFullMetaAtom() + { + { + MP4::File f(TEST_FILE_PATH_C("non-full-meta.m4a")); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT(f.hasMP4Tag()); + + CPPUNIT_ASSERT(f.tag()->contains("covr")); + MP4::CoverArtList l = f.tag()->item("covr").toCoverArtList(); + CPPUNIT_ASSERT_EQUAL((unsigned int)2, l.size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::PNG, l[0].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)79, l[0].data().size()); + CPPUNIT_ASSERT_EQUAL(MP4::CoverArt::JPEG, l[1].format()); + CPPUNIT_ASSERT_EQUAL((unsigned int)287, l[1].data().size()); + + PropertyMap properties = f.properties(); + CPPUNIT_ASSERT_EQUAL(StringList("Test Artist!!!!"), properties["ARTIST"]); + CPPUNIT_ASSERT_EQUAL(StringList("FAAC 1.24"), properties["ENCODEDBY"]); + } + } }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMP4);