From 7f2f2ddcafb2125f6aad70db79b3e0a3de581212 Mon Sep 17 00:00:00 2001 From: Daniel <790119+DanTheMan827@users.noreply.github.com> Date: Sat, 4 Apr 2026 00:43:56 -0500 Subject: [PATCH] Add tests for FileRef content-based detection via ByteVectorStream (#1318) This covers all 18 formats supported by the content-based detection. --- tests/CMakeLists.txt | 1 + tests/test_fileref_detect.cpp | 480 ++++++++++++++++++++++++++++++++++ 2 files changed, 481 insertions(+) create mode 100644 tests/test_fileref_detect.cpp diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 103e97f7..ac3c344e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -87,6 +87,7 @@ SET(test_runner_SRCS test_complexproperties.cpp test_file.cpp test_fileref.cpp + test_fileref_detect.cpp test_id3v1.cpp test_id3v2.cpp test_id3v2framefactory.cpp diff --git a/tests/test_fileref_detect.cpp b/tests/test_fileref_detect.cpp new file mode 100644 index 00000000..837c82fb --- /dev/null +++ b/tests/test_fileref_detect.cpp @@ -0,0 +1,480 @@ +/*************************************************************************** + copyright : (C) 2026 by TagLib developers + ***************************************************************************/ + +/*************************************************************************** + * This library is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Lesser General Public License version * + * 2.1 as published by the Free Software Foundation. * + * * + * This library is distributed in the hope that it will be useful, but * + * WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library; if not, write to the Free Software * + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * + * 02110-1301 USA * + * * + * Alternatively, this file is available under the Mozilla Public * + * License Version 1.1. You may obtain a copy of the License at * + * http://www.mozilla.org/MPL/ * + ***************************************************************************/ + +#include "taglib_config.h" +#include "tfilestream.h" +#include "tbytevectorstream.h" +#include "fileref.h" +#include "mpegfile.h" +#ifdef TAGLIB_WITH_VORBIS +#include "flacfile.h" +#include "oggflacfile.h" +#include "opusfile.h" +#include "speexfile.h" +#include "vorbisfile.h" +#endif +#ifdef TAGLIB_WITH_APE +#include "apefile.h" +#include "mpcfile.h" +#include "wavpackfile.h" +#endif +#ifdef TAGLIB_WITH_ASF +#include "asffile.h" +#endif +#ifdef TAGLIB_WITH_TRUEAUDIO +#include "trueaudiofile.h" +#endif +#ifdef TAGLIB_WITH_MP4 +#include "mp4file.h" +#endif +#ifdef TAGLIB_WITH_RIFF +#include "aifffile.h" +#include "wavfile.h" +#endif +#ifdef TAGLIB_WITH_DSF +#include "dsdifffile.h" +#include "dsffile.h" +#endif +#ifdef TAGLIB_WITH_SHORTEN +#include "shortenfile.h" +#endif +#ifdef TAGLIB_WITH_MATROSKA +#include "matroskafile.h" +#endif +#include +#include "utils.h" + +using namespace TagLib; + +// Files not covered by detection tests and the reason why: +// (All of these return null because no format's isSupported() matches them) +// +// MOD/S3M/IT/XM formats have no isSupported() implementation at all, +// so content-based detection is impossible for these files: +// changed.mod, test.mod, changed.s3m, test.s3m, test.it, +// changed.xm, test.xm, stripped.xm +// +// bare ID3 tag data without any surrounding audio stream: +// 005411.id3, broken-tenc.id3, unsynch.id3 +// +// null bytes / truly unsupported binary format: +// no-extension, unsupported-extension.xx +// +// .mp3-named files where MPEG::File::isSupported() returns false because the +// MPEG frame scanner cannot find any valid frames in the content: +// garbage.mp3 (random binary data with no MPEG sync bytes), +// compressed_id3_frame.mp3 (zlib-compressed ID3 frame inflates to garbage +// that the frame scanner cannot parse past), +// duplicate_id3v2.mp3 (two consecutive ID3v2 headers confuse the size +// calculation, shifting the scan past any real frames), +// excessive_alloc.mp3 (APIC frame carries a crafted huge size field that +// the ID3v2 skip overshoots the actual frames), +// extended-header.mp3 (ID3v2.4 extended header flag causes incorrect size +// skip so the scanner starts inside the header), +// w000.mp3 (malformed file with no discoverable MPEG sync bytes) +// +// MPC SV4/SV5: MPC::File::isSupported() only recognises "MPCK" (SV8) and +// "MP+" (SV7); older SV4/SV5 streams have no standardised magic bytes: +// sv4_header.mpc, sv5_header.mpc +// +// MP4 with 64-bit atom sizes: first box is "moov" rather than the required +// "ftyp", so MP4::File::isSupported() returns false: +// 64bit.mp4 +// +// corrupt AIFF: the FORM header is present but the sub-type bytes at offset 8 +// are garbled (0x80 0x46 instead of 'AIFF'/'AIFC'), so +// RIFF::AIFF::File::isSupported() returns false: +// excessive_alloc.aif + +namespace { + + template void detectByContent(const char *testFile) { + FileStream fs(TEST_FILE_PATH_C(testFile)); + CPPUNIT_ASSERT(fs.isOpen()); + const ByteVector data = fs.readBlock(fs.length()); + ByteVectorStream bvs(data); + const FileRef f(&bvs); + CPPUNIT_ASSERT(!f.isNull()); + CPPUNIT_ASSERT(dynamic_cast(f.file()) != nullptr); + } + + void detectNullByContent(const char *testFile) { + FileStream fs(TEST_FILE_PATH_C(testFile)); + CPPUNIT_ASSERT(fs.isOpen()); + const ByteVector data = fs.readBlock(fs.length()); + ByteVectorStream bvs(data); + const FileRef f(&bvs); + CPPUNIT_ASSERT(f.isNull()); + } + +} // namespace + +class TestFileRefDetectByContent : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(TestFileRefDetectByContent); + + // MPEG (always available) + CPPUNIT_TEST(testApeId3v1Mp3); + CPPUNIT_TEST(testApeId3v2Mp3); + CPPUNIT_TEST(testApeMp3); + CPPUNIT_TEST(testBladeencMp3); + CPPUNIT_TEST(testEmpty1sAac); + CPPUNIT_TEST(testId3v22TdaMp3); + CPPUNIT_TEST(testInvalidFrames1Mp3); + CPPUNIT_TEST(testInvalidFrames2Mp3); + CPPUNIT_TEST(testInvalidFrames3Mp3); + CPPUNIT_TEST(testItunes10Mp3); + CPPUNIT_TEST(testLameCbrMp3); + CPPUNIT_TEST(testLameVbrMp3); + CPPUNIT_TEST(testMpeg2Mp3); + CPPUNIT_TEST(testRareFramesMp3); + CPPUNIT_TEST(testTocManyChildrenMp3); + CPPUNIT_TEST(testXingMp3); + +#ifdef TAGLIB_WITH_VORBIS + // Ogg::Vorbis::File + CPPUNIT_TEST(testEmptyOgg); + CPPUNIT_TEST(testEmptyVorbisOga); + CPPUNIT_TEST(testLowercaseFieldsOgg); + CPPUNIT_TEST(testTestOgg); + // Ogg::FLAC::File + CPPUNIT_TEST(testEmptyFlacOga); + // FLAC::File + CPPUNIT_TEST(testEmptySeektableFlac); + CPPUNIT_TEST(testMultipleVcFlac); + CPPUNIT_TEST(testNoTagsFlac); + CPPUNIT_TEST(testSilence44SFlac); + CPPUNIT_TEST(testSinewaveFlac); + CPPUNIT_TEST(testZeroSizedPaddingFlac); + // Ogg::Speex::File + CPPUNIT_TEST(testEmptySpx); + // Ogg::Opus::File + CPPUNIT_TEST(testCorrectnessGainSilentOutputOpus); + // Corrupt files: isSupported() returns true but isValid() returns false + CPPUNIT_TEST(testNullSegfaultOga); +#endif + +#ifdef TAGLIB_WITH_APE + // MPC::File + CPPUNIT_TEST(testClickMpc); + CPPUNIT_TEST(testInfloopMpc); + CPPUNIT_TEST(testSegfaultMpc); + CPPUNIT_TEST(testSegfault2Mpc); + CPPUNIT_TEST(testSv8HeaderMpc); + CPPUNIT_TEST(testZerodivMpc); + // WavPack::File + CPPUNIT_TEST(testClickWv); + CPPUNIT_TEST(testDsdStereoWv); + CPPUNIT_TEST(testFourChannelsWv); + CPPUNIT_TEST(testInfloopWv); + CPPUNIT_TEST(testNoLengthWv); + CPPUNIT_TEST(testNonStandardRateWv); + CPPUNIT_TEST(testTaggedWv); + // APE::File + CPPUNIT_TEST(testLongloopApe); + CPPUNIT_TEST(testMac390HdrApe); + CPPUNIT_TEST(testMac396Ape); + CPPUNIT_TEST(testMac399Id3v2Ape); + CPPUNIT_TEST(testMac399TaggedApe); + CPPUNIT_TEST(testMac399Ape); + CPPUNIT_TEST(testZerodivApe); +#endif + +#ifdef TAGLIB_WITH_TRUEAUDIO + CPPUNIT_TEST(testEmptyTta); + CPPUNIT_TEST(testTaggedTta); +#endif + +#ifdef TAGLIB_WITH_MP4 + CPPUNIT_TEST(testBlankVideoM4v); + CPPUNIT_TEST(testCovrJunkM4a); + CPPUNIT_TEST(testEmptyAlacM4a); + CPPUNIT_TEST(testGnreM4a); + CPPUNIT_TEST(testHasTagsM4a); + CPPUNIT_TEST(testIlstIsLastM4a); + CPPUNIT_TEST(testInfloopM4a); + CPPUNIT_TEST(testNoTags3g2); + CPPUNIT_TEST(testNoTagsM4a); + CPPUNIT_TEST(testNonFullMetaM4a); + CPPUNIT_TEST(testNonprintableAtomTypeM4a); + CPPUNIT_TEST(testZeroLengthMdatM4a); +#endif + +#ifdef TAGLIB_WITH_ASF + CPPUNIT_TEST(testLosslessWma); + CPPUNIT_TEST(testSilence1Wma); +#endif + +#ifdef TAGLIB_WITH_RIFF + // RIFF::AIFF::File + CPPUNIT_TEST(testAlawAifc); + CPPUNIT_TEST(testDuplicateId3v2Aiff); + CPPUNIT_TEST(testEmptyAiff); + CPPUNIT_TEST(testNoiseAif); + CPPUNIT_TEST(testNoiseOddAif); + CPPUNIT_TEST(testSegfaultAif); + // RIFF::WAV::File + CPPUNIT_TEST(testAlawWav); + CPPUNIT_TEST(testDuplicateTagsWav); + CPPUNIT_TEST(testEmptyWav); + CPPUNIT_TEST(testFloat64Wav); + CPPUNIT_TEST(testInfloopWav); + CPPUNIT_TEST(testInvalidChunkWav); + CPPUNIT_TEST(testPcmWithFactChunkWav); + CPPUNIT_TEST(testSegfaultWav); + CPPUNIT_TEST(testUint8weWav); + CPPUNIT_TEST(testZeroSizeChunkWav); +#endif + +#ifdef TAGLIB_WITH_DSF + CPPUNIT_TEST(testEmpty10msDsf); + CPPUNIT_TEST(testEmpty10msDff); +#endif + +#ifdef TAGLIB_WITH_SHORTEN + CPPUNIT_TEST(test2SecSilenceShn); +#endif + +#ifdef TAGLIB_WITH_MATROSKA + CPPUNIT_TEST(testNoTagsMka); + CPPUNIT_TEST(testNoTagsWebm); + CPPUNIT_TEST(testOptimizedMkv); + CPPUNIT_TEST(testTagsBeforeCuesMkv); +#endif + + CPPUNIT_TEST_SUITE_END(); + +public: + // -- MPEG::File (always available) -- + + void testApeId3v1Mp3() { detectByContent("ape-id3v1.mp3"); } + void testApeId3v2Mp3() { detectByContent("ape-id3v2.mp3"); } + void testApeMp3() { detectByContent("ape.mp3"); } + void testBladeencMp3() { detectByContent("bladeenc.mp3"); } + void testEmpty1sAac() { detectByContent("empty1s.aac"); } + void testId3v22TdaMp3() { detectByContent("id3v22-tda.mp3"); } + void testInvalidFrames1Mp3() { + detectByContent("invalid-frames1.mp3"); + } + void testInvalidFrames2Mp3() { + detectByContent("invalid-frames2.mp3"); + } + void testInvalidFrames3Mp3() { + detectByContent("invalid-frames3.mp3"); + } + void testItunes10Mp3() { detectByContent("itunes10.mp3"); } + void testLameCbrMp3() { detectByContent("lame_cbr.mp3"); } + void testLameVbrMp3() { detectByContent("lame_vbr.mp3"); } + void testMpeg2Mp3() { detectByContent("mpeg2.mp3"); } + void testRareFramesMp3() { + detectByContent("rare_frames.mp3"); + } + void testTocManyChildrenMp3() { + detectByContent("toc_many_children.mp3"); + } + void testXingMp3() { detectByContent("xing.mp3"); } + +#ifdef TAGLIB_WITH_VORBIS + // -- Ogg::Vorbis::File -- + void testEmptyOgg() { detectByContent("empty.ogg"); } + void testEmptyVorbisOga() { + detectByContent("empty_vorbis.oga"); + } + void testLowercaseFieldsOgg() { + detectByContent("lowercase-fields.ogg"); + } + void testTestOgg() { detectByContent("test.ogg"); } + + // -- Ogg::FLAC::File -- + void testEmptyFlacOga() { + detectByContent("empty_flac.oga"); + } + + // -- FLAC::File -- + void testEmptySeektableFlac() { + detectByContent("empty-seektable.flac"); + } + void testMultipleVcFlac() { + detectByContent("multiple-vc.flac"); + } + void testNoTagsFlac() { detectByContent("no-tags.flac"); } + void testSilence44SFlac() { + detectByContent("silence-44-s.flac"); + } + void testSinewaveFlac() { detectByContent("sinewave.flac"); } + void testZeroSizedPaddingFlac() { + detectByContent("zero-sized-padding.flac"); + } + + // -- Ogg::Speex::File -- + void testEmptySpx() { detectByContent("empty.spx"); } + + // -- Ogg::Opus::File -- + void testCorrectnessGainSilentOutputOpus() { + detectByContent("correctness_gain_silent_output.opus"); + } + + // segfault.oga: Ogg::FLAC::File::isSupported() returns true (valid Ogg + // container with a fLaC marker), but the FLAC metadata header inside is + // corrupt so Ogg::FLAC::File::isValid() returns false. + void testNullSegfaultOga() { detectNullByContent("segfault.oga"); } +#endif + +#ifdef TAGLIB_WITH_APE + // -- MPC::File -- + void testClickMpc() { detectByContent("click.mpc"); } + void testInfloopMpc() { detectByContent("infloop.mpc"); } + void testSegfaultMpc() { detectByContent("segfault.mpc"); } + void testSegfault2Mpc() { detectByContent("segfault2.mpc"); } + void testSv8HeaderMpc() { detectByContent("sv8_header.mpc"); } + void testZerodivMpc() { detectByContent("zerodiv.mpc"); } + + // -- WavPack::File -- + void testClickWv() { detectByContent("click.wv"); } + void testDsdStereoWv() { detectByContent("dsd_stereo.wv"); } + void testFourChannelsWv() { + detectByContent("four_channels.wv"); + } + void testInfloopWv() { detectByContent("infloop.wv"); } + void testNoLengthWv() { detectByContent("no_length.wv"); } + void testNonStandardRateWv() { + detectByContent("non_standard_rate.wv"); + } + void testTaggedWv() { detectByContent("tagged.wv"); } + + // -- APE::File -- + void testLongloopApe() { detectByContent("longloop.ape"); } + void testMac390HdrApe() { detectByContent("mac-390-hdr.ape"); } + void testMac396Ape() { detectByContent("mac-396.ape"); } + void testMac399Id3v2Ape() { + detectByContent("mac-399-id3v2.ape"); + } + void testMac399TaggedApe() { + detectByContent("mac-399-tagged.ape"); + } + void testMac399Ape() { detectByContent("mac-399.ape"); } + void testZerodivApe() { detectByContent("zerodiv.ape"); } +#endif + +#ifdef TAGLIB_WITH_TRUEAUDIO + // -- TrueAudio::File -- + void testEmptyTta() { detectByContent("empty.tta"); } + void testTaggedTta() { detectByContent("tagged.tta"); } +#endif + +#ifdef TAGLIB_WITH_MP4 + // -- MP4::File -- + void testBlankVideoM4v() { detectByContent("blank_video.m4v"); } + void testCovrJunkM4a() { detectByContent("covr-junk.m4a"); } + void testEmptyAlacM4a() { detectByContent("empty_alac.m4a"); } + void testGnreM4a() { detectByContent("gnre.m4a"); } + void testHasTagsM4a() { detectByContent("has-tags.m4a"); } + void testIlstIsLastM4a() { + detectByContent("ilst-is-last.m4a"); + } + void testInfloopM4a() { detectByContent("infloop.m4a"); } + void testNoTags3g2() { detectByContent("no-tags.3g2"); } + void testNoTagsM4a() { detectByContent("no-tags.m4a"); } + void testNonFullMetaM4a() { + detectByContent("non-full-meta.m4a"); + } + void testNonprintableAtomTypeM4a() { + detectByContent("nonprintable-atom-type.m4a"); + } + void testZeroLengthMdatM4a() { + detectByContent("zero-length-mdat.m4a"); + } +#endif + +#ifdef TAGLIB_WITH_ASF + // -- ASF::File -- + void testLosslessWma() { detectByContent("lossless.wma"); } + void testSilence1Wma() { detectByContent("silence-1.wma"); } +#endif + +#ifdef TAGLIB_WITH_RIFF + // -- RIFF::AIFF::File -- + void testAlawAifc() { detectByContent("alaw.aifc"); } + void testDuplicateId3v2Aiff() { + detectByContent("duplicate_id3v2.aiff"); + } + void testEmptyAiff() { detectByContent("empty.aiff"); } + void testNoiseAif() { detectByContent("noise.aif"); } + void testNoiseOddAif() { + detectByContent("noise_odd.aif"); + } + void testSegfaultAif() { + detectByContent("segfault.aif"); + } + + // -- RIFF::WAV::File -- + void testAlawWav() { detectByContent("alaw.wav"); } + void testDuplicateTagsWav() { + detectByContent("duplicate_tags.wav"); + } + void testEmptyWav() { detectByContent("empty.wav"); } + void testFloat64Wav() { detectByContent("float64.wav"); } + void testInfloopWav() { detectByContent("infloop.wav"); } + void testInvalidChunkWav() { + detectByContent("invalid-chunk.wav"); + } + void testPcmWithFactChunkWav() { + detectByContent("pcm_with_fact_chunk.wav"); + } + void testSegfaultWav() { detectByContent("segfault.wav"); } + void testUint8weWav() { detectByContent("uint8we.wav"); } + void testZeroSizeChunkWav() { + detectByContent("zero-size-chunk.wav"); + } +#endif + +#ifdef TAGLIB_WITH_DSF + // -- DSF::File -- + void testEmpty10msDsf() { detectByContent("empty10ms.dsf"); } + + // -- DSDIFF::File -- + void testEmpty10msDff() { detectByContent("empty10ms.dff"); } +#endif + +#ifdef TAGLIB_WITH_SHORTEN + // -- Shorten::File -- + void test2SecSilenceShn() { + detectByContent("2sec-silence.shn"); + } +#endif + +#ifdef TAGLIB_WITH_MATROSKA + // -- Matroska::File -- + void testNoTagsMka() { detectByContent("no-tags.mka"); } + void testNoTagsWebm() { detectByContent("no-tags.webm"); } + void testOptimizedMkv() { + detectByContent("optimized.mkv"); + } + void testTagsBeforeCuesMkv() { + detectByContent("tags-before-cues.mkv"); + } +#endif +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(TestFileRefDetectByContent);