mirror of
https://github.com/taglib/taglib.git
synced 2025-05-27 21:20:26 -04:00
More robust checks for invalid MPEG frame headers.
This commit is contained in:
parent
847eec23cf
commit
be9b5cc93a
@ -23,8 +23,6 @@
|
||||
* http://www.mozilla.org/MPL/ *
|
||||
***************************************************************************/
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <tbytevector.h>
|
||||
#include <tstring.h>
|
||||
#include <tdebug.h>
|
||||
@ -68,20 +66,21 @@ public:
|
||||
// public members
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MPEG::Header::Header(const ByteVector &data)
|
||||
MPEG::Header::Header(const ByteVector &data) :
|
||||
d(new HeaderPrivate())
|
||||
{
|
||||
d = new HeaderPrivate;
|
||||
parse(data);
|
||||
}
|
||||
|
||||
MPEG::Header::Header(const Header &h) : d(h.d)
|
||||
MPEG::Header::Header(const Header &h) :
|
||||
d(h.d)
|
||||
{
|
||||
d->ref();
|
||||
}
|
||||
|
||||
MPEG::Header::~Header()
|
||||
{
|
||||
if (d->deref())
|
||||
if(d->deref())
|
||||
delete d;
|
||||
}
|
||||
|
||||
@ -164,39 +163,54 @@ MPEG::Header &MPEG::Header::operator=(const Header &h)
|
||||
|
||||
void MPEG::Header::parse(const ByteVector &data)
|
||||
{
|
||||
if(data.size() < 4 || static_cast<unsigned char>(data[0]) != 0xff) {
|
||||
if(data.size() < 4) {
|
||||
debug("MPEG::Header::parse() -- data is too short for an MPEG frame header.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for the MPEG synch bytes.
|
||||
|
||||
if(static_cast<unsigned char>(data[0]) != 0xFF) {
|
||||
debug("MPEG::Header::parse() -- First byte did not match MPEG synch.");
|
||||
return;
|
||||
}
|
||||
|
||||
std::bitset<32> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt()));
|
||||
|
||||
// Check for the second byte's part of the MPEG synch
|
||||
|
||||
if(!flags[23] || !flags[22] || !flags[21]) {
|
||||
if((static_cast<unsigned char>(data[1]) & 0xE0) != 0xE0) {
|
||||
debug("MPEG::Header::parse() -- Second byte did not match MPEG synch.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the MPEG version
|
||||
|
||||
if(!flags[20] && !flags[19])
|
||||
const int versionBits = (static_cast<unsigned char>(data[1]) >> 3) & 0x03;
|
||||
|
||||
if(versionBits == 0)
|
||||
d->version = Version2_5;
|
||||
else if(flags[20] && !flags[19])
|
||||
else if(versionBits == 2)
|
||||
d->version = Version2;
|
||||
else if(flags[20] && flags[19])
|
||||
else if(versionBits == 3)
|
||||
d->version = Version1;
|
||||
else {
|
||||
debug("MPEG::Header::parse() -- Invalid MPEG version bits.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the MPEG layer
|
||||
|
||||
if(!flags[18] && flags[17])
|
||||
d->layer = 3;
|
||||
else if(flags[18] && !flags[17])
|
||||
d->layer = 2;
|
||||
else if(flags[18] && flags[17])
|
||||
d->layer = 1;
|
||||
const int layerBits = (static_cast<unsigned char>(data[1]) >> 1) & 0x03;
|
||||
|
||||
d->protectionEnabled = !flags[16];
|
||||
if(layerBits == 1)
|
||||
d->layer = 3;
|
||||
else if(layerBits == 2)
|
||||
d->layer = 2;
|
||||
else if(layerBits == 3)
|
||||
d->layer = 1;
|
||||
else {
|
||||
debug("MPEG::Header::parse() -- Invalid MPEG layer bits.");
|
||||
return;
|
||||
}
|
||||
|
||||
d->protectionEnabled = (static_cast<unsigned char>(data[1] & 0x01) == 0);
|
||||
|
||||
// Set the bitrate
|
||||
|
||||
@ -219,9 +233,14 @@ void MPEG::Header::parse(const ByteVector &data)
|
||||
// The bitrate index is encoded as the first 4 bits of the 3rd byte,
|
||||
// i.e. 1111xxxx
|
||||
|
||||
int i = static_cast<unsigned char>(data[2]) >> 4;
|
||||
const int bitrateIndex = (static_cast<unsigned char>(data[2]) >> 4) & 0x0F;
|
||||
|
||||
d->bitrate = bitrates[versionIndex][layerIndex][i];
|
||||
d->bitrate = bitrates[versionIndex][layerIndex][bitrateIndex];
|
||||
|
||||
if(d->bitrate == 0) {
|
||||
debug("MPEG::Header::parse() -- Invalid bit rate.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the sample rate
|
||||
|
||||
@ -233,9 +252,9 @@ void MPEG::Header::parse(const ByteVector &data)
|
||||
|
||||
// The sample rate index is encoded as two bits in the 3nd byte, i.e. xxxx11xx
|
||||
|
||||
i = static_cast<unsigned char>(data[2]) >> 2 & 0x03;
|
||||
const int samplerateIndex = (static_cast<unsigned char>(data[2]) >> 2) & 0x03;
|
||||
|
||||
d->sampleRate = sampleRates[d->version][i];
|
||||
d->sampleRate = sampleRates[d->version][samplerateIndex];
|
||||
|
||||
if(d->sampleRate == 0) {
|
||||
debug("MPEG::Header::parse() -- Invalid sample rate.");
|
||||
@ -245,13 +264,13 @@ void MPEG::Header::parse(const ByteVector &data)
|
||||
// The channel mode is encoded as a 2 bit value at the end of the 3nd byte,
|
||||
// i.e. xxxxxx11
|
||||
|
||||
d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) & 0xC0) >> 6);
|
||||
d->channelMode = static_cast<ChannelMode>((static_cast<unsigned char>(data[3]) >> 6) & 0x03);
|
||||
|
||||
// TODO: Add mode extension for completeness
|
||||
|
||||
d->isOriginal = flags[2];
|
||||
d->isCopyrighted = flags[3];
|
||||
d->isPadded = flags[9];
|
||||
d->isOriginal = ((static_cast<unsigned char>(data[3]) & 0x04) != 0);
|
||||
d->isCopyrighted = ((static_cast<unsigned char>(data[3]) & 0x08) != 0);
|
||||
d->isPadded = ((static_cast<unsigned char>(data[2]) & 0x02) != 0);
|
||||
|
||||
// Samples per frame
|
||||
|
||||
|
@ -155,27 +155,33 @@ bool MPEG::Properties::isOriginal() const
|
||||
|
||||
void MPEG::Properties::read(File *file)
|
||||
{
|
||||
// Only the first frame is required if we have a VBR header.
|
||||
// Only the first valid frame is required if we have a VBR header.
|
||||
|
||||
const long first = file->firstFrameOffset();
|
||||
if(first < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
|
||||
long firstFrameOffset = file->firstFrameOffset();
|
||||
if(firstFrameOffset < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find an MPEG frame in the stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
file->seek(first);
|
||||
const Header firstHeader(file->readBlock(4));
|
||||
file->seek(firstFrameOffset);
|
||||
Header firstHeader(file->readBlock(4));
|
||||
|
||||
if(!firstHeader.isValid()) {
|
||||
debug("MPEG::Properties::read() -- The first page header is invalid.");
|
||||
return;
|
||||
while(!firstHeader.isValid()) {
|
||||
firstFrameOffset = file->nextFrameOffset(firstFrameOffset + 1);
|
||||
if(firstFrameOffset < 0) {
|
||||
debug("MPEG::Properties::read() -- Could not find a valid first MPEG frame in the stream.");
|
||||
return;
|
||||
}
|
||||
|
||||
file->seek(firstFrameOffset);
|
||||
firstHeader = Header(file->readBlock(4));
|
||||
}
|
||||
|
||||
// Check for a VBR header that will help us in gathering information about a
|
||||
// VBR stream.
|
||||
|
||||
file->seek(first + 4);
|
||||
d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength() - 4));
|
||||
file->seek(firstFrameOffset);
|
||||
d->xingHeader = new XingHeader(file->readBlock(firstHeader.frameLength()));
|
||||
if(!d->xingHeader->isValid()) {
|
||||
delete d->xingHeader;
|
||||
d->xingHeader = 0;
|
||||
@ -201,7 +207,7 @@ void MPEG::Properties::read(File *file)
|
||||
|
||||
d->bitrate = firstHeader.bitrate();
|
||||
|
||||
long streamLength = file->length() - first;
|
||||
long streamLength = file->length() - firstFrameOffset;
|
||||
|
||||
if(file->hasID3v1Tag())
|
||||
streamLength -= 128;
|
||||
|
BIN
tests/data/invalid-frames.mp3
Normal file
BIN
tests/data/invalid-frames.mp3
Normal file
Binary file not shown.
@ -22,6 +22,7 @@ class TestMPEG : public CppUnit::TestFixture
|
||||
CPPUNIT_TEST(testAudioPropertiesXingHeaderVBR);
|
||||
CPPUNIT_TEST(testAudioPropertiesVBRIHeader);
|
||||
CPPUNIT_TEST(testAudioPropertiesNoVBRHeaders);
|
||||
CPPUNIT_TEST(testSkipInvalidFrames);
|
||||
CPPUNIT_TEST(testVersion2DurationWithXingHeader);
|
||||
CPPUNIT_TEST(testSaveID3v24);
|
||||
CPPUNIT_TEST(testSaveID3v24WrongParam);
|
||||
@ -102,6 +103,19 @@ public:
|
||||
CPPUNIT_ASSERT_EQUAL(209, lastHeader.frameLength());
|
||||
}
|
||||
|
||||
void testSkipInvalidFrames()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("invalid-frames.mp3"));
|
||||
CPPUNIT_ASSERT(f.audioProperties());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->length());
|
||||
CPPUNIT_ASSERT_EQUAL(0, f.audioProperties()->lengthInSeconds());
|
||||
CPPUNIT_ASSERT_EQUAL(393, f.audioProperties()->lengthInMilliseconds());
|
||||
CPPUNIT_ASSERT_EQUAL(160, f.audioProperties()->bitrate());
|
||||
CPPUNIT_ASSERT_EQUAL(2, f.audioProperties()->channels());
|
||||
CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate());
|
||||
CPPUNIT_ASSERT(!f.audioProperties()->xingHeader());
|
||||
}
|
||||
|
||||
void testVersion2DurationWithXingHeader()
|
||||
{
|
||||
MPEG::File f(TEST_FILE_PATH_C("mpeg2.mp3"));
|
||||
|
Loading…
Reference in New Issue
Block a user