diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..32b8a7df --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Non-specified newlines with a newline ending every file +[*] +insert_final_newline = true + +# 2 space indentation +[*.{h,cpp,tcc,cmake}] +indent_style = space +indent_size = 2 + +# Trim traling whitespaces +[*.{h,cpp,tcc,cmake}] +trim_trailing_whitespace = true + +# UTF-8 without BOM +[*] +charset = utf-8 diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index dca2dcbe..79ee60bc 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -277,16 +277,6 @@ check_cxx_source_compiles(" } " HAVE_ISO_STRDUP) -# Determine whether your compiler supports codecvt. - -check_cxx_source_compiles(" - #include - int main() { - std::codecvt_utf8_utf16 x; - return 0; - } -" HAVE_STD_CODECVT) - # Determine whether zlib is installed. if(NOT ZLIB_SOURCE) diff --git a/config.h.cmake b/config.h.cmake index 19537e75..dfe97036 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -13,6 +13,15 @@ #cmakedefine _LARGEFILE_SOURCE ${_LARGEFILE_SOURCE} #cmakedefine _FILE_OFFSET_BITS ${_FILE_OFFSET_BITS} +/* Defined if your compiler supports some byte swap functions */ +#cmakedefine HAVE_GCC_BYTESWAP_16 1 +#cmakedefine HAVE_GCC_BYTESWAP_32 1 +#cmakedefine HAVE_GCC_BYTESWAP_64 1 +#cmakedefine HAVE_GLIBC_BYTESWAP 1 +#cmakedefine HAVE_MSC_BYTESWAP 1 +#cmakedefine HAVE_MAC_BYTESWAP 1 +#cmakedefine HAVE_OPENBSD_BYTESWAP 1 + /* Defined if your compiler supports some atomic operations */ #cmakedefine HAVE_STD_ATOMIC 1 #cmakedefine HAVE_BOOST_ATOMIC 1 @@ -25,15 +34,6 @@ #cmakedefine HAVE_STD_SMART_PTR 1 #cmakedefine HAVE_BOOST_SMART_PTR 1 -/* Defined if your compiler supports some byte swap functions */ -#cmakedefine HAVE_GCC_BYTESWAP_16 1 -#cmakedefine HAVE_GCC_BYTESWAP_32 1 -#cmakedefine HAVE_GCC_BYTESWAP_64 1 -#cmakedefine HAVE_GLIBC_BYTESWAP 1 -#cmakedefine HAVE_MSC_BYTESWAP 1 -#cmakedefine HAVE_MAC_BYTESWAP 1 -#cmakedefine HAVE_OPENBSD_BYTESWAP 1 - /* Defined if your compiler supports snprintf or sprintf_s. */ #cmakedefine HAVE_SNPRINTF 1 #cmakedefine HAVE_SPRINTF_S 1 @@ -41,9 +41,6 @@ /* Defined if your compiler supports ISO _strdup. */ #cmakedefine HAVE_ISO_STRDUP 1 -/* Defined if your compiler supports codecvt */ -#cmakedefine HAVE_STD_CODECVT 1 - /* Defined if you have libz */ #cmakedefine HAVE_ZLIB 1 diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index ccda648c..5eb9ecac 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -330,9 +330,14 @@ set(toolkit_SRCS toolkit/tpropertymap.cpp toolkit/trefcounter.cpp toolkit/tdebuglistener.cpp - toolkit/unicode.cpp ) +if(NOT WIN32) + set(unicode_SRCS + toolkit/unicode.cpp + ) +endif() + if(HAVE_ZLIB_SOURCE) set(zlib_SRCS ${ZLIB_SOURCE}/adler32.c @@ -340,7 +345,6 @@ if(HAVE_ZLIB_SOURCE) ${ZLIB_SOURCE}/inffast.c ${ZLIB_SOURCE}/inflate.c ${ZLIB_SOURCE}/inftrees.c - ${ZLIB_SOURCE}/uncompr.c ${ZLIB_SOURCE}/zutil.c ) endif() @@ -351,13 +355,14 @@ set(tag_LIB_SRCS ${wavpack_SRCS} ${speex_SRCS} ${trueaudio_SRCS} ${riff_SRCS} ${aiff_SRCS} ${wav_SRCS} ${asf_SRCS} ${mp4_SRCS} ${mod_SRCS} ${s3m_SRCS} ${it_SRCS} ${xm_SRCS} ${opus_SRCS} ${ebml_SRCS} ${matroska_SRCS} ${dsf_SRCS} + ${unicode_SRCS} ${zlib_SRCS} tag.cpp tagunion.cpp fileref.cpp audioproperties.cpp ) -add_library(tag ${tag_LIB_SRCS} ${zlib_SRCS} ${tag_HDRS}) +add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) if(ZLIB_FOUND) target_link_libraries(tag ${ZLIB_LIBRARIES}) diff --git a/taglib/ape/apeproperties.cpp b/taglib/ape/apeproperties.cpp index 798145d6..dfd72483 100644 --- a/taglib/ape/apeproperties.cpp +++ b/taglib/ape/apeproperties.cpp @@ -61,7 +61,7 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -APE::AudioProperties::AudioProperties(File *file, ReadStyle style) : +APE::AudioProperties::AudioProperties(File *file, ReadStyle style) : d(new PropertiesPrivate()) { read(file); @@ -219,11 +219,19 @@ void APE::AudioProperties::analyzeOld(File *file) else blocksPerFrame = 9216; - d->channels = header.toUInt16LE(4); + d->channels = header.toUInt16LE(4); d->sampleRate = header.toUInt32LE(6); + const uint finalFrameBlocks = header.toUInt32LE(22); - const uint totalBlocks = totalFrames > 0 ? (totalFrames - 1) * blocksPerFrame + finalFrameBlocks : 0; - d->length = totalBlocks / d->sampleRate; - d->bitrate = d->length > 0 ? static_cast(file->length() * 8L / d->length / 1000) : 0; + + uint totalBlocks = 0; + if(totalFrames > 0) + totalBlocks = (totalFrames - 1) * blocksPerFrame + finalFrameBlocks; + + if(d->sampleRate > 0) + d->length = totalBlocks / d->sampleRate; + + if(d->length > 0) + d->bitrate = ((file->length() * 8L) / d->length) / 1000; } diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 6410013a..2112d01e 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -371,10 +371,13 @@ ByteVector APE::Tag::render() const void APE::Tag::parse(const ByteVector &data) { - uint pos = 0; - // 11 bytes is the minimum size for an APE item + if(data.size() < 11) + return; + + uint pos = 0; + for(uint i = 0; i < d->footer.itemCount() && pos <= data.size() - 11; i++) { APE::Item item; item.parse(data.mid(pos)); diff --git a/taglib/fileref.cpp b/taglib/fileref.cpp index 9b41457f..0edcd714 100644 --- a/taglib/fileref.cpp +++ b/taglib/fileref.cpp @@ -63,12 +63,12 @@ namespace ResolverList fileTypeResolvers; SHARED_PTR create( - FileName fileName, + FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle audioPropertiesStyle) { SHARED_PTR file; - for(ResolverConstIterator it = fileTypeResolvers.begin(); it != fileTypeResolvers.end(); ++it) + for(ResolverConstIterator it = fileTypeResolvers.begin(); it != fileTypeResolvers.end(); ++it) { file.reset((*it)->createFile(fileName, readAudioProperties, audioPropertiesStyle)); if(file) @@ -146,23 +146,17 @@ namespace } } -class FileRef::FileRefPrivate +class FileRef::FileRefPrivate { public: - FileRefPrivate() - : file() - { - } + FileRefPrivate() : + file() {} - FileRefPrivate(File *f) - : file(f) - { - } - - FileRefPrivate(const SHARED_PTR &f) - : file(f) - { - } + FileRefPrivate(File *f) : + file(f) {} + + FileRefPrivate(const SHARED_PTR &f) : + file(f) {} SHARED_PTR file; }; @@ -171,25 +165,23 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -FileRef::FileRef() - : d(new FileRefPrivate()) +FileRef::FileRef() : + d(new FileRefPrivate()) { } -FileRef::FileRef(FileName fileName, - bool readAudioProperties, - AudioProperties::ReadStyle audioPropertiesStyle) - : d(new FileRefPrivate(create(fileName, readAudioProperties, audioPropertiesStyle))) +FileRef::FileRef(FileName fileName, bool readAudioProperties, AudioProperties::ReadStyle style) : + d(new FileRefPrivate(create(fileName, readAudioProperties, style))) { } -FileRef::FileRef(File *file) - : d(new FileRefPrivate(file)) +FileRef::FileRef(File *file) : + d(new FileRefPrivate(file)) { } -FileRef::FileRef(const FileRef &ref) - : d(new FileRefPrivate(ref.d->file)) +FileRef::FileRef(const FileRef &ref) : + d(new FileRefPrivate(ref.d->file)) { } diff --git a/taglib/mp4/mp4atom.cpp b/taglib/mp4/mp4atom.cpp index 393fc9cd..e0d74304 100644 --- a/taglib/mp4/mp4atom.cpp +++ b/taglib/mp4/mp4atom.cpp @@ -23,6 +23,10 @@ * http://www.mozilla.org/MPL/ * ***************************************************************************/ +#ifdef HAVE_CONFIG_H +#include +#endif + #include #include #include "mp4atom.h" @@ -50,20 +54,10 @@ MP4::Atom::Atom(File *file) length = header.toUInt32BE(0); - if (length == 1) { - const long long longLength = file->readBlock(8).toInt64BE(0); - if (longLength >= 8 && longLength <= 0xFFFFFFFF) { - // The atom has a 64-bit length, but it's actually a 32-bit value - length = (long)longLength; - } - else { - debug("MP4: 64-bit atoms are not supported"); - length = 0; - file->seek(0, File::End); - return; - } - } - if (length < 8) { + if(length == 1) + length = file->readBlock(8).toInt64BE(0); + + if(length < 8) { debug("MP4: Invalid atom size"); length = 0; file->seek(0, File::End); diff --git a/taglib/mp4/mp4atom.h b/taglib/mp4/mp4atom.h index 9a01f87d..33bba830 100644 --- a/taglib/mp4/mp4atom.h +++ b/taglib/mp4/mp4atom.h @@ -83,7 +83,7 @@ namespace TagLib { bool path(AtomList &path, const char *name1, const char *name2 = 0, const char *name3 = 0); AtomList findall(const char *name, bool recursive = false); offset_t offset; - long length; + offset_t length; TagLib::ByteVector name; AtomList children; private: diff --git a/taglib/mpc/mpcproperties.cpp b/taglib/mpc/mpcproperties.cpp index 23dc5345..2d18b3ca 100644 --- a/taglib/mpc/mpcproperties.cpp +++ b/taglib/mpc/mpcproperties.cpp @@ -147,35 +147,43 @@ int MPC::AudioProperties::albumPeak() const namespace { - unsigned long readSize(File *file, size_t &sizelength) + unsigned long readSize(File *file, size_t &sizeLength, bool &eof) { + sizeLength = 0; + eof = false; + unsigned char tmp; unsigned long size = 0; do { - ByteVector b = file->readBlock(1); + const ByteVector b = file->readBlock(1); + if(b.isEmpty()) { + eof = true; + break; + } + tmp = b[0]; size = (size << 7) | (tmp & 0x7F); - sizelength++; + sizeLength++; } while((tmp & 0x80)); return size; } - unsigned long readSize(const ByteVector &data, size_t &sizelength) + unsigned long readSize(const ByteVector &data, size_t &pos) { unsigned char tmp; unsigned long size = 0; - unsigned long pos = 0; do { tmp = data[pos++]; size = (size << 7) | (tmp & 0x7F); - sizelength++; } while((tmp & 0x80) && (pos < data.size())); return size; } - static const unsigned short sftable [4] = { 44100, 48000, 37800, 32000 }; + // This array looks weird, but the same as original MusePack code found at: + // https://www.musepack.net/index.php?pg=src + static const unsigned short sftable [8] = { 44100, 48000, 37800, 32000, 0, 0, 0, 0 }; } void MPC::AudioProperties::readSV8(File *file, offset_t streamLength) @@ -183,40 +191,73 @@ void MPC::AudioProperties::readSV8(File *file, offset_t streamLength) bool readSH = false, readRG = false; while(!readSH && !readRG) { - ByteVector packetType = file->readBlock(2); - size_t packetSizeLength = 0; - size_t packetSize = readSize(file, packetSizeLength); - size_t dataSize = packetSize - 2 - packetSizeLength; + const ByteVector packetType = file->readBlock(2); + + size_t packetSizeLength; + bool eof; + const size_t packetSize = readSize(file, packetSizeLength, eof); + if(eof) { + debug("MPC::Properties::readSV8() - Reached to EOF."); + break; + } + + const size_t dataSize = packetSize - 2 - packetSizeLength; + + const ByteVector data = file->readBlock(dataSize); + if(data.size() != dataSize) { + debug("MPC::Properties::readSV8() - dataSize doesn't match the actual data size."); + break; + } if(packetType == "SH") { // Stream Header // http://trac.musepack.net/wiki/SV8Specification#StreamHeaderPacket - ByteVector data = file->readBlock(dataSize); + + if(dataSize <= 5) { + debug("MPC::Properties::readSV8() - \"SH\" packet is too short to parse."); + break; + } + readSH = true; size_t pos = 4; d->version = data[pos]; pos += 1; - d->sampleFrames = readSize(data.mid(pos), pos); - uint begSilence = readSize(data.mid(pos), pos); - std::bitset<16> flags(TAGLIB_CONSTRUCT_BITSET(data.toUInt16BE(pos))); + d->sampleFrames = readSize(data, pos); + if(pos > dataSize - 3) { + debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); + break; + } + + ulong begSilence = readSize(data, pos); + if(pos > dataSize - 2) { + debug("MPC::Properties::readSV8() - \"SH\" packet is corrupt."); + break; + } + + const ushort flags = data.toUInt16BE(pos); pos += 2; - d->sampleRate = sftable[flags[15] * 4 + flags[14] * 2 + flags[13]]; - d->channels = flags[7] * 8 + flags[6] * 4 + flags[5] * 2 + flags[4] + 1; + d->sampleRate = sftable[(flags >> 13) & 0x07]; + d->channels = ((flags >> 4) & 0x0F) + 1; - if((d->sampleFrames - begSilence) != 0) - d->bitrate = static_cast(streamLength * 8.0 * d->sampleRate / (d->sampleFrames - begSilence)); - d->bitrate = d->bitrate / 1000; - - d->length = (d->sampleFrames - begSilence) / d->sampleRate; + const uint frameCount = d->sampleFrames - begSilence; + if(frameCount != 0 && d->sampleRate != 0) { + d->bitrate = (int)(streamLength * 8.0 * d->sampleRate / frameCount / 1000); + d->length = frameCount / d->sampleRate; + } } else if (packetType == "RG") { // Replay Gain // http://trac.musepack.net/wiki/SV8Specification#ReplaygainPacket - ByteVector data = file->readBlock(dataSize); + + if(dataSize <= 9) { + debug("MPC::Properties::readSV8() - \"RG\" packet is too short to parse."); + break; + } + readRG = true; int replayGainVersion = data[0]; @@ -306,4 +347,3 @@ void MPC::AudioProperties::readSV7(const ByteVector &data, offset_t streamLength if(!d->bitrate) d->bitrate = d->length > 0 ? static_cast(streamLength * 8L / d->length / 1000) : 0; } - diff --git a/taglib/mpeg/id3v2/frames/chapterframe.cpp b/taglib/mpeg/id3v2/frames/chapterframe.cpp index 475a2b8c..8c754c17 100644 --- a/taglib/mpeg/id3v2/frames/chapterframe.cpp +++ b/taglib/mpeg/id3v2/frames/chapterframe.cpp @@ -44,10 +44,10 @@ public: const ID3v2::Header *tagHeader; ByteVector elementID; - uint startTime; - uint endTime; - uint startOffset; - uint endOffset; + TagLib::uint startTime; + TagLib::uint endTime; + TagLib::uint startOffset; + TagLib::uint endOffset; FrameListMap embeddedFrameListMap; FrameList embeddedFrameList; }; @@ -64,8 +64,8 @@ ChapterFrame::ChapterFrame(const ID3v2::Header *tagHeader, const ByteVector &dat setData(data); } -ChapterFrame::ChapterFrame(const ByteVector &eID, const uint &sT, const uint &eT, - const uint &sO, const uint &eO, const FrameList &eF) : +ChapterFrame::ChapterFrame(const ByteVector &eID, const TagLib::uint &sT, const TagLib::uint &eT, + const TagLib::uint &sO, const TagLib::uint &eO, const FrameList &eF) : ID3v2::Frame("CHAP") { d = new ChapterFramePrivate; @@ -89,22 +89,22 @@ ByteVector ChapterFrame::elementID() const return d->elementID; } -uint ChapterFrame::startTime() const +TagLib::uint ChapterFrame::startTime() const { return d->startTime; } -uint ChapterFrame::endTime() const +TagLib::uint ChapterFrame::endTime() const { return d->endTime; } -uint ChapterFrame::startOffset() const +TagLib::uint ChapterFrame::startOffset() const { return d->startOffset; } -uint ChapterFrame::endOffset() const +TagLib::uint ChapterFrame::endOffset() const { return d->endOffset; } @@ -116,22 +116,22 @@ void ChapterFrame::setElementID(const ByteVector &eID) d->elementID.append(char(0)); } -void ChapterFrame::setStartTime(const uint &sT) +void ChapterFrame::setStartTime(const TagLib::uint &sT) { d->startTime = sT; } -void ChapterFrame::setEndTime(const uint &eT) +void ChapterFrame::setEndTime(const TagLib::uint &eT) { d->endTime = eT; } -void ChapterFrame::setStartOffset(const uint &sO) +void ChapterFrame::setStartOffset(const TagLib::uint &sO) { d->startOffset = sO; } -void ChapterFrame::setEndOffset(const uint &eO) +void ChapterFrame::setEndOffset(const TagLib::uint &eO) { d->endOffset = eO; } @@ -232,6 +232,7 @@ void ChapterFrame::parseFields(const ByteVector &data) pos += 4; size -= pos; while(embPos < size - header()->size()) { + Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader); if(!frame) diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 8703c4de..dd9a220d 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -94,7 +94,7 @@ bool TableOfContentsFrame::isOrdered() const return d->isOrdered; } -uint TableOfContentsFrame::entryCount() const +TagLib::uint TableOfContentsFrame::entryCount() const { return d->childElements.size(); } @@ -229,7 +229,7 @@ TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) void TableOfContentsFrame::parseFields(const ByteVector &data) { - uint size = data.size(); + TagLib::uint size = data.size(); if(size < 6) { debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by " "null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated " @@ -243,8 +243,9 @@ void TableOfContentsFrame::parseFields(const ByteVector &data) d->elementID.append(char(0)); d->isTopLevel = (data.at(pos) & 2) > 0; d->isOrdered = (data.at(pos++) & 1) > 0; - uint entryCount = data.at(pos++); - for(uint i = 0; i < entryCount; i++) { + TagLib::uint entryCount = data.at(pos++); + for(TagLib::uint i = 0; i < entryCount; i++) + { ByteVector childElementID = readStringField(data, String::Latin1, pos).data(String::Latin1); childElementID.append(char(0)); d->childElements.append(childElementID); diff --git a/taglib/mpeg/id3v2/id3v2frame.cpp b/taglib/mpeg/id3v2/id3v2frame.cpp index 2f1d73e6..2f625c46 100644 --- a/taglib/mpeg/id3v2/id3v2frame.cpp +++ b/taglib/mpeg/id3v2/id3v2frame.cpp @@ -254,12 +254,49 @@ ByteVector Frame::fieldData(const ByteVector &frameData) const if(d->header->compression() && !d->header->encryption()) { - ByteVector data(frameDataLength); - uLongf uLongTmp = static_cast(frameDataLength); - ::uncompress((Bytef *) data.data(), - (uLongf *) &uLongTmp, - (Bytef *) frameData.data() + frameDataOffset, - size()); + if(frameData.size() <= frameDataOffset) { + debug("Compressed frame doesn't have enough data to decode"); + return ByteVector(); + } + + z_stream stream = {}; + + if(inflateInit(&stream) != Z_OK) + return ByteVector(); + + stream.avail_in = (uLongf) frameData.size() - frameDataOffset; + stream.next_in = (Bytef *) frameData.data() + frameDataOffset; + + static const size_t chunkSize = 1024; + + ByteVector data; + ByteVector chunk(chunkSize); + + do { + stream.avail_out = (uLongf) chunk.size(); + stream.next_out = (Bytef *) chunk.data(); + + int result = inflate(&stream, Z_NO_FLUSH); + + if(result == Z_STREAM_ERROR || + result == Z_NEED_DICT || + result == Z_DATA_ERROR || + result == Z_MEM_ERROR) + { + if(result != Z_STREAM_ERROR) + inflateEnd(&stream); + debug("Error reading compressed stream"); + return ByteVector(); + } + + data.append(stream.avail_out == 0 ? chunk : chunk.mid(0, chunk.size() - stream.avail_out)); + } while(stream.avail_out == 0); + + inflateEnd(&stream); + + if(frameDataLength != data.size()) + debug("frameDataLength does not match the data length returned by zlib"); + return data; } else diff --git a/taglib/mpeg/id3v2/id3v2framefactory.cpp b/taglib/mpeg/id3v2/id3v2framefactory.cpp index 8a4628aa..35f50cd4 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.cpp +++ b/taglib/mpeg/id3v2/id3v2framefactory.cpp @@ -278,7 +278,7 @@ Frame *FrameFactory::createFrame(const ByteVector &origData, const Header *tagHe return new UnknownFrame(data, header); } -void FrameFactory::rebuildAggregateFrames(Tag *tag) const +void FrameFactory::rebuildAggregateFrames(ID3v2::Tag *tag) const { if(tag->header()->majorVersion() < 4 && tag->frameList("TDRC").size() == 1 && diff --git a/taglib/mpeg/id3v2/id3v2framefactory.h b/taglib/mpeg/id3v2/id3v2framefactory.h index 8f2f6089..023420b4 100644 --- a/taglib/mpeg/id3v2/id3v2framefactory.h +++ b/taglib/mpeg/id3v2/id3v2framefactory.h @@ -78,7 +78,7 @@ namespace TagLib { * have been deprecated and can't be upgraded directly. */ // BIC: Make virtual - void rebuildAggregateFrames(Tag *tag) const; + void rebuildAggregateFrames(ID3v2::Tag *tag) const; /*! * Returns the default text encoding for text frames. If setTextEncoding() diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index a83d9c4f..119ba811 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -52,7 +52,7 @@ using namespace ID3v2; class ID3v2::Tag::TagPrivate { public: - TagPrivate() + TagPrivate() : file(0) , tagOffset(-1) , extendedHeader(0) @@ -91,6 +91,11 @@ namespace const TagLib::StringHandler *ID3v2::Tag::TagPrivate::stringHandler = &defaultStringHandler; +namespace +{ + const uint DefaultPaddingSize = 1024; +} + //////////////////////////////////////////////////////////////////////////////// // Latin1StringHandler implementation //////////////////////////////////////////////////////////////////////////////// @@ -607,15 +612,21 @@ ByteVector ID3v2::Tag::render(int version) const // Compute the amount of padding, and append that to tagData. - size_t paddingSize = 0; - const size_t originalSize = d->header.tagSize(); + size_t paddingSize = DefaultPaddingSize; - if(tagData.size() < originalSize) - paddingSize = originalSize - tagData.size(); - else - paddingSize = 1024; + if(d->file && tagData.size() < d->header.tagSize()) { + paddingSize = d->header.tagSize() - tagData.size(); - tagData.append(ByteVector(paddingSize, char(0))); + // Padding won't increase beyond 1% of the file size. + + if(paddingSize > DefaultPaddingSize) { + const ulonglong threshold = d->file->length() / 100; + if(paddingSize > threshold) + paddingSize = DefaultPaddingSize; + } + } + + tagData.append(ByteVector(paddingSize, '\0')); // Set the version and data size. d->header.setMajorVersion(version); diff --git a/taglib/mpeg/mpegfile.cpp b/taglib/mpeg/mpegfile.cpp index 54eef6a4..4c7d00e0 100644 --- a/taglib/mpeg/mpegfile.cpp +++ b/taglib/mpeg/mpegfile.cpp @@ -374,7 +374,7 @@ offset_t MPEG::File::previousFrameOffset(offset_t position) ByteVector buffer; while (position > 0) { - size_t size = position < static_cast(bufferSize()) + size_t size = position < static_cast(bufferSize()) ? static_cast(position) : bufferSize(); position -= size; @@ -401,9 +401,24 @@ offset_t MPEG::File::firstFrameOffset() { offset_t position = 0; - if(ID3v2Tag()) + if(ID3v2Tag()) { position = d->ID3v2Location + ID3v2Tag()->header()->completeTagSize(); + // Skip duplicate ID3v2 tags. + + // Workaround for some faulty files that have duplicate ID3v2 tags. + // Combination of EAC and LAME creates such files when configured incorrectly. + + long location; + while((location = findID3v2(position)) >= 0) { + seek(location); + const ID3v2::Header header(readBlock(ID3v2::Header::size())); + position = location + header.completeTagSize(); + + debug("MPEG::File::firstFrameOffset() - Duplicate ID3v2 tag found."); + } + } + return nextFrameOffset(position); } @@ -435,7 +450,7 @@ void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle properties { // Look for an ID3v2 tag - d->ID3v2Location = findID3v2(); + d->ID3v2Location = findID3v2(0); if(d->ID3v2Location >= 0) { @@ -478,7 +493,7 @@ void MPEG::File::read(bool readProperties, AudioProperties::ReadStyle properties ID3v1Tag(true); } -offset_t MPEG::File::findID3v2() +offset_t MPEG::File::findID3v2(offset_t offset) { // This method is based on the contents of TagLib::File::find(), but because // of some subtlteies -- specifically the need to look for the bit pattern of @@ -501,9 +516,9 @@ offset_t MPEG::File::findID3v2() const offset_t originalPosition = tell(); - // Start the search at the beginning of the file. + // Start the search at the offset. - seek(0); + seek(offset); // This loop is the crux of the find method. There are three cases that we // want to account for: @@ -514,7 +529,7 @@ offset_t MPEG::File::findID3v2() // (2) The search pattern is wholly contained within the current buffer. // // (3) The current buffer ends with a partial match of the pattern. We will - // note this for use in the next itteration, where we will check for the rest + // note this for use in the next iteration, where we will check for the rest // of the pattern. while(true) @@ -532,7 +547,7 @@ offset_t MPEG::File::findID3v2() const size_t patternOffset = (bufferSize() - previousPartialMatch); if(buffer.containsAt(ID3v2::Header::fileIdentifier(), 0, patternOffset)) { seek(originalPosition); - return bufferOffset - bufferSize() + previousPartialMatch; + return offset + bufferOffset - bufferSize() + previousPartialMatch; } } @@ -541,7 +556,7 @@ offset_t MPEG::File::findID3v2() const size_t location = buffer.find(ID3v2::Header::fileIdentifier()); if(location != ByteVector::npos) { seek(originalPosition); - return bufferOffset + location; + return offset + bufferOffset + location; } size_t firstSynchByte = buffer.find(char(uchar(255))); diff --git a/taglib/mpeg/mpegfile.h b/taglib/mpeg/mpegfile.h index ba2b9f9b..b2be675c 100644 --- a/taglib/mpeg/mpegfile.h +++ b/taglib/mpeg/mpegfile.h @@ -208,8 +208,8 @@ namespace TagLib { * if there is no valid ID3v2 tag. If \a create is true it will create * an ID3v2 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v2 tag. Use hasID3v2Tag() to check if the file * on disk actually has an ID3v2 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -227,8 +227,8 @@ namespace TagLib { * if there is no valid ID3v1 tag. If \a create is true it will create * an ID3v1 tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an ID3v1 tag. Use hasID3v1Tag() to check if the file * on disk actually has an ID3v1 tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -246,8 +246,8 @@ namespace TagLib { * if there is no valid APE tag. If \a create is true it will create * an APE tag if one does not exist and returns a valid pointer. * - * \note This may return a valid pointer regardless of whether or not the - * file on disk has an APE tag. Use hasAPETag() to check if the file + * \note This may return a valid pointer regardless of whether or not the + * file on disk has an APE tag. Use hasAPETag() to check if the file * on disk actually has an APE tag. * * \note The Tag is still owned by the MPEG::File and should not be @@ -336,7 +336,7 @@ namespace TagLib { File &operator=(const File &); void read(bool readProperties, AudioProperties::ReadStyle propertiesStyle); - offset_t findID3v2(); + offset_t findID3v2(offset_t offset); offset_t findID3v1(); void findAPE(); diff --git a/taglib/ogg/flac/oggflacfile.cpp b/taglib/ogg/flac/oggflacfile.cpp index efc248bc..b86f6241 100644 --- a/taglib/ogg/flac/oggflacfile.cpp +++ b/taglib/ogg/flac/oggflacfile.cpp @@ -103,7 +103,7 @@ PropertyMap Ogg::FLAC::File::properties() const PropertyMap Ogg::FLAC::File::setProperties(const PropertyMap &properties) { return d->comment->setProperties(properties); -} +} FLAC::AudioProperties *Ogg::FLAC::File::audioProperties() const { @@ -211,29 +211,30 @@ void Ogg::FLAC::File::scan() offset_t overhead = 0; ByteVector metadataHeader = packet(ipacket); - if(metadataHeader.isNull()) + if(metadataHeader.isEmpty()) return; - ByteVector header; - - if (!metadataHeader.startsWith("fLaC")) { + if(!metadataHeader.startsWith("fLaC")) { // FLAC 1.1.2+ - if (metadataHeader.mid(1,4) != "FLAC") return; + if(metadataHeader.mid(1, 4) != "FLAC") + return; - if (metadataHeader[5] != 1) return; // not version 1 + if(metadataHeader[5] != 1) + return; // not version 1 metadataHeader = metadataHeader.mid(13); } else { // FLAC 1.1.0 & 1.1.1 metadataHeader = packet(++ipacket); - - if(metadataHeader.isNull()) - return; - } - header = metadataHeader.mid(0,4); + ByteVector header = metadataHeader.mid(0, 4); + if(header.size() != 4) { + debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header"); + return; + } + // Header format (from spec): // <1> Last-metadata-block flag // <7> BLOCK_TYPE @@ -262,11 +263,12 @@ void Ogg::FLAC::File::scan() while(!lastBlock) { metadataHeader = packet(++ipacket); - - if(metadataHeader.isNull()) - return; - header = metadataHeader.mid(0, 4); + if(header.size() != 4) { + debug("Ogg::FLAC::File::scan() -- Invalid Ogg/FLAC metadata header"); + return; + } + blockType = header[0] & 0x7f; lastBlock = (header[0] & 0x80) != 0; length = header.toUInt24BE(1); diff --git a/taglib/riff/aiff/aifffile.cpp b/taglib/riff/aiff/aifffile.cpp index dd81abd3..7b83e1cb 100644 --- a/taglib/riff/aiff/aifffile.cpp +++ b/taglib/riff/aiff/aifffile.cpp @@ -39,7 +39,8 @@ public: FilePrivate() : properties(0), tag(0), - tagChunkID("ID3 ") + tagChunkID("ID3 "), + hasID3v2(false) { } @@ -53,6 +54,8 @@ public: AudioProperties *properties; ID3v2::Tag *tag; ByteVector tagChunkID; + + bool hasID3v2; }; //////////////////////////////////////////////////////////////////////////////// @@ -100,7 +103,6 @@ PropertyMap RIFF::AIFF::File::setProperties(const PropertyMap &properties) return d->tag->setProperties(properties); } - RIFF::AIFF::AudioProperties *RIFF::AIFF::File::audioProperties() const { return d->properties; @@ -119,10 +121,15 @@ bool RIFF::AIFF::File::save() } setChunkData(d->tagChunkID, d->tag->render()); + d->hasID3v2 = true; return true; } +bool RIFF::AIFF::File::hasID3v2Tag() const +{ + return d->hasID3v2; +} //////////////////////////////////////////////////////////////////////////////// // private members @@ -134,6 +141,7 @@ void RIFF::AIFF::File::read(bool readProperties, AudioProperties::ReadStyle prop if(chunkName(i) == "ID3 " || chunkName(i) == "id3 ") { d->tagChunkID = chunkName(i); d->tag = new ID3v2::Tag(this, chunkOffset(i)); + d->hasID3v2 = true; } else if(chunkName(i) == "COMM" && readProperties) d->properties = new AudioProperties(chunkData(i), propertiesStyle); diff --git a/taglib/riff/aiff/aiffproperties.cpp b/taglib/riff/aiff/aiffproperties.cpp index 25012ecd..ad18e97b 100644 --- a/taglib/riff/aiff/aiffproperties.cpp +++ b/taglib/riff/aiff/aiffproperties.cpp @@ -131,6 +131,7 @@ void RIFF::AIFF::AudioProperties::read(const ByteVector &data) d->channels = data.toInt16BE(0); d->sampleFrames = data.toUInt32BE(2); d->sampleWidth = data.toInt16BE(6); + const long double sampleRate = data.toFloat80BE(8); d->sampleRate = static_cast(sampleRate); d->bitrate = static_cast((sampleRate * d->sampleWidth * d->channels) / 1000.0); diff --git a/taglib/riff/aiff/aiffproperties.h b/taglib/riff/aiff/aiffproperties.h index 1cc89a3d..09a7ed01 100644 --- a/taglib/riff/aiff/aiffproperties.h +++ b/taglib/riff/aiff/aiffproperties.h @@ -47,7 +47,7 @@ namespace TagLib { { public: /*! - * Creates an instance of AIFF::AudioProperties with the data read from + * Creates an instance of AIFF::AudioProperties with the data read from * the ByteVector \a data. */ AudioProperties(const ByteVector &data, ReadStyle style); diff --git a/taglib/riff/wav/infotag.cpp b/taglib/riff/wav/infotag.cpp index eea4403a..abc5f029 100644 --- a/taglib/riff/wav/infotag.cpp +++ b/taglib/riff/wav/infotag.cpp @@ -244,9 +244,15 @@ void RIFF::Info::Tag::parse(const ByteVector &data) size_t p = 4; while(p < data.size()) { const uint size = data.toUInt32LE(p + 4); - d->fieldListMap[data.mid(p, 4)] = TagPrivate::stringHandler->parse(data.mid(p + 8, size)); + if(size > data.size() - p - 8) + break; + + const ByteVector id = data.mid(p, 4); + if(RIFF::File::isValidChunkName(id)) { + const String text = TagPrivate::stringHandler->parse(data.mid(p + 8, size)); + d->fieldListMap[id] = text; + } p += ((size + 1) & ~1) + 8; } } - diff --git a/taglib/riff/wav/wavfile.cpp b/taglib/riff/wav/wavfile.cpp index 25ad7ec2..feab8d92 100644 --- a/taglib/riff/wav/wavfile.cpp +++ b/taglib/riff/wav/wavfile.cpp @@ -57,7 +57,7 @@ public: } AudioProperties *properties; - + ByteVector tagChunkID; DoubleTagUnion tag; @@ -146,21 +146,30 @@ bool RIFF::WAV::File::save(TagTypes tags, bool stripOthers, int id3v2Version) if(stripOthers) strip(static_cast(AllTags & ~tags)); - ID3v2::Tag *id3v2tag = d->tag.access(ID3v2Index, false); - if((tags & ID3v2) && !id3v2tag->isEmpty()) { - setChunkData(d->tagChunkID, id3v2tag->render(id3v2Version)); - d->hasID3v2 = true; + const ID3v2::Tag *id3v2tag = d->tag.access(ID3v2Index, false); + if(tags & ID3v2) { + if(d->hasID3v2) { + removeChunk(d->tagChunkID); + d->hasID3v2 = false; + } + + if(!id3v2tag->isEmpty()) { + setChunkData(d->tagChunkID, id3v2tag->render(id3v2Version)); + d->hasID3v2 = true; + } } - Info::Tag *infotag = d->tag.access(InfoIndex, false); - if((tags & Info) && !infotag->isEmpty()) { - int chunkId = findInfoTagChunk(); - if(chunkId != -1) - setChunkData(chunkId, infotag->render()); - else - setChunkData("LIST", infotag->render(), true); + const Info::Tag *infotag = d->tag.access(InfoIndex, false); + if(tags & Info) { + if(d->hasInfo) { + removeChunk(findInfoTagChunk()); + d->hasInfo = false; + } - d->hasInfo = true; + if(!infotag->isEmpty()) { + setChunkData("LIST", infotag->render(), true); + d->hasInfo = true; + } } return true; @@ -239,6 +248,6 @@ TagLib::uint RIFF::WAV::File::findInfoTagChunk() return i; } } - + return TagLib::uint(-1); } diff --git a/taglib/riff/wav/wavproperties.cpp b/taglib/riff/wav/wavproperties.cpp index 18a8a4d6..0c4ba8d9 100644 --- a/taglib/riff/wav/wavproperties.cpp +++ b/taglib/riff/wav/wavproperties.cpp @@ -54,7 +54,7 @@ public: // public members //////////////////////////////////////////////////////////////////////////////// -RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &data, uint streamLength, +RIFF::WAV::AudioProperties::AudioProperties(const ByteVector &data, uint streamLength, ReadStyle style) : d(new PropertiesPrivate()) { @@ -108,7 +108,7 @@ TagLib::uint RIFF::WAV::AudioProperties::format() const void RIFF::WAV::AudioProperties::read(const ByteVector &data, uint streamLength) { if(data.size() < 16) { - debug("RIFF::WAV::AudioProperties::read() - \"fmt \" chunk is too short."); + debug("RIFF::WAV::AudioProperties::read() - \"fmt \" chunk is too short for WAV."); return; } diff --git a/taglib/toolkit/tdebug.cpp b/taglib/toolkit/tdebug.cpp index acdb6cc2..e9387656 100644 --- a/taglib/toolkit/tdebug.cpp +++ b/taglib/toolkit/tdebug.cpp @@ -31,11 +31,10 @@ #include "tstring.h" #include "tutils.h" #include "tdebuglistener.h" +#include "tutils.h" #include -using namespace TagLib; - namespace TagLib { // The instance is defined in tdebuglistener.cpp. @@ -44,7 +43,7 @@ namespace TagLib void debug(const String &s) { #if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) - + debugListener->printMessage("TagLib: " + s + "\n"); #endif @@ -54,11 +53,11 @@ namespace TagLib { #if !defined(NDEBUG) || defined(TRACE_IN_RELEASE) - for(size_t i = 0; i < v.size(); ++i) + for(size_t i = 0; i < v.size(); ++i) { std::string bits = std::bitset<8>(v[i]).to_string(); String msg = Utils::formatString( - "*** [%d] - char '%c' - int %d, 0x%02x, 0b%s\n", + "*** [%d] - char '%c' - int %d, 0x%02x, 0b%s\n", i, v[i], v[i], v[i], bits.c_str()); debugListener->printMessage(msg); diff --git a/taglib/toolkit/tiostream.cpp b/taglib/toolkit/tiostream.cpp index 4d7e943b..80570cab 100644 --- a/taglib/toolkit/tiostream.cpp +++ b/taglib/toolkit/tiostream.cpp @@ -63,12 +63,12 @@ namespace debug("unicodeToAnsi() - Should not be used on WinNT systems."); } - const int len = WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL); + const int len = ::WideCharToMultiByte(CP_ACP, 0, wstr, -1, NULL, 0, NULL, NULL); if(len == 0) return std::string(); std::string str(len, '\0'); - WideCharToMultiByte(CP_ACP, 0, wstr, -1, &str[0], len, NULL, NULL); + ::WideCharToMultiByte(CP_ACP, 0, wstr, -1, &str[0], len, NULL, NULL); return str; } @@ -145,12 +145,12 @@ String FileName::toString() const return String(d->data->wname); } else if(!d->data->name.empty()) { - const int len = MultiByteToWideChar(CP_ACP, 0, d->data->name.c_str(), -1, NULL, 0); + const int len = ::MultiByteToWideChar(CP_ACP, 0, d->data->name.c_str(), -1, NULL, 0); if(len == 0) return String::null; std::vector buf(len); - MultiByteToWideChar(CP_ACP, 0, d->data->name.c_str(), -1, &buf[0], len); + ::MultiByteToWideChar(CP_ACP, 0, d->data->name.c_str(), -1, &buf[0], len); return String(&buf[0]); } @@ -159,7 +159,6 @@ String FileName::toString() const } } - #endif // _WIN32 //////////////////////////////////////////////////////////////////////////////// diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index c272a610..07c75c67 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -41,105 +41,80 @@ #include #include -#ifdef HAVE_STD_CODECVT -# include +#ifdef _WIN32 +# include #else # include "unicode.h" #endif namespace { + using namespace TagLib; - void UTF16toUTF8(const wchar_t *src, size_t srcLength, char *dst, size_t dstLength) + inline size_t UTF16toUTF8( + const wchar_t *src, size_t srcLength, char *dst, size_t dstLength) { -#ifdef HAVE_STD_CODECVT + size_t len = 0; - typedef std::codecvt_utf8_utf16 utf8_utf16_t; +#ifdef _WIN32 - using namespace TagLib; - - const wchar_t *srcBegin = src; - const wchar_t *srcEnd = srcBegin + srcLength; - - char *dstBegin = dst; - char *dstEnd = dstBegin + dstLength; - - std::mbstate_t st; - const wchar_t *source; - char *target; - memset(&st, 0, sizeof(st)); - std::codecvt_base::result result = utf8_utf16_t().out( - st, srcBegin, srcEnd, source, dstBegin, dstEnd, target); - - if(result != utf8_utf16_t::ok) { - debug("String::UTF16toUTF8() - Unicode conversion error."); - } + len = ::WideCharToMultiByte(CP_UTF8, 0, src, srcLength, dst, dstLength, NULL, NULL); #else using namespace Unicode; - using namespace TagLib; - const Unicode::UTF16 *srcBegin = src; - const Unicode::UTF16 *srcEnd = srcBegin + srcLength; + const UTF16 *srcBegin = src; + const UTF16 *srcEnd = srcBegin + srcLength; - Unicode::UTF8 *dstBegin = reinterpret_cast(dst); - Unicode::UTF8 *dstEnd = dstBegin + dstLength; + UTF8 *dstBegin = reinterpret_cast(dst); + UTF8 *dstEnd = dstBegin + dstLength; - Unicode::ConversionResult result = Unicode::ConvertUTF16toUTF8( - &srcBegin, srcEnd, &dstBegin, dstEnd, Unicode::lenientConversion); + ConversionResult result = ConvertUTF16toUTF8( + &srcBegin, srcEnd, &dstBegin, dstEnd, lenientConversion); - if(result != Unicode::conversionOK) { - debug("String::UTF16toUTF8() - Unicode conversion error."); - } + if(result == conversionOK) + len = dstBegin - reinterpret_cast(dst); #endif + + if(len == 0) + debug("String::UTF16toUTF8() - Unicode conversion error."); + + return len; } - void UTF8toUTF16(const char *src, size_t srcLength, wchar_t *dst, size_t dstLength) + inline size_t UTF8toUTF16( + const char *src, size_t srcLength, wchar_t *dst, size_t dstLength) { -#ifdef HAVE_STD_CODECVT + size_t len = 0; - typedef std::codecvt_utf8_utf16 utf8_utf16_t; +#ifdef _WIN32 - using namespace TagLib; - - const char *srcBegin = src; - const char *srcEnd = srcBegin + srcLength; - - wchar_t *dstBegin = dst; - wchar_t *dstEnd = dstBegin + dstLength; - - std::mbstate_t st; - const char *source; - wchar_t *target; - memset(&st, 0, sizeof(st)); - std::codecvt_base::result result = utf8_utf16_t().in( - st, srcBegin, srcEnd, source, dstBegin, dstEnd, target); - - if(result != utf8_utf16_t::ok) { - debug("String::UTF8toUTF16() - Unicode conversion error."); - } + len = ::MultiByteToWideChar(CP_UTF8, 0, src, srcLength, dst, dstLength); #else using namespace Unicode; - using namespace TagLib; - const Unicode::UTF8 *srcBegin = reinterpret_cast(src); - const Unicode::UTF8 *srcEnd = srcBegin + srcLength; + const UTF8 *srcBegin = reinterpret_cast(src); + const UTF8 *srcEnd = srcBegin + srcLength; - Unicode::UTF16 *dstBegin = dst; - Unicode::UTF16 *dstEnd = dstBegin + dstLength; + UTF16 *dstBegin = dst; + UTF16 *dstEnd = dstBegin + dstLength; - Unicode::ConversionResult result = Unicode::ConvertUTF8toUTF16( - &srcBegin, srcEnd, &dstBegin, dstEnd, Unicode::lenientConversion); + ConversionResult result = ConvertUTF8toUTF16( + &srcBegin, srcEnd, &dstBegin, dstEnd, lenientConversion); - if(result != Unicode::conversionOK) { - debug("String::UTF8toUTF16() - Unicode conversion error."); - } + if(result == conversionOK) + len = dstBegin - dst; #endif + + if(len == 0) + debug("String::UTF8toUTF16() - Unicode conversion error."); + + return len; } } @@ -412,8 +387,9 @@ ByteVector String::data(Type t) const { ByteVector v(size() * 4 + 1, 0); - UTF16toUTF8(d->data->c_str(), d->data->size(), v.data(), v.size()); - v.resize(::strlen(v.data())); + const size_t len = UTF16toUTF8( + d->data->c_str(), d->data->size(), v.data(), v.size()); + v.resize(len); return v; } @@ -690,8 +666,8 @@ void String::copyFromUTF8(const char *s, size_t length) d->data->resize(length); if(length > 0) { - UTF8toUTF16(s, length, &(*d->data)[0], d->data->size()); - d->data->resize(::wcslen(d->data->c_str())); + const size_t len = UTF8toUTF16(s, length, &(*d->data)[0], d->data->size()); + d->data->resize(len); } } diff --git a/taglib/toolkit/unicode.h b/taglib/toolkit/unicode.h index ebf1915d..d3a869f0 100644 --- a/taglib/toolkit/unicode.h +++ b/taglib/toolkit/unicode.h @@ -106,11 +106,6 @@ bit mask & shift operations. ------------------------------------------------------------------------ */ -// Workaround for when MSVC doesn't have wchar_t as a built-in type. -#if defined(_MSC_VER) && !defined(_WCHAR_T_DEFINED) -# include -#endif - /* Some fundamental constants */ #define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD #define UNI_MAX_BMP (UTF32)0x0000FFFF diff --git a/tests/data/compressed_id3_frame.mp3 b/tests/data/compressed_id3_frame.mp3 index 60bc8956..824d036f 100644 Binary files a/tests/data/compressed_id3_frame.mp3 and b/tests/data/compressed_id3_frame.mp3 differ diff --git a/tests/data/duplicate_id3v2.mp3 b/tests/data/duplicate_id3v2.mp3 new file mode 100644 index 00000000..34f4f158 Binary files /dev/null and b/tests/data/duplicate_id3v2.mp3 differ diff --git a/tests/data/excessive_alloc.aif b/tests/data/excessive_alloc.aif new file mode 100644 index 00000000..9cb3a6e1 Binary files /dev/null and b/tests/data/excessive_alloc.aif differ diff --git a/tests/data/excessive_alloc.mp3 b/tests/data/excessive_alloc.mp3 new file mode 100644 index 00000000..cd8aa2ab Binary files /dev/null and b/tests/data/excessive_alloc.mp3 differ diff --git a/tests/data/infloop.mpc b/tests/data/infloop.mpc new file mode 100644 index 00000000..46861ab3 Binary files /dev/null and b/tests/data/infloop.mpc differ diff --git a/tests/data/infloop.wav b/tests/data/infloop.wav new file mode 100644 index 00000000..c220baa8 Binary files /dev/null and b/tests/data/infloop.wav differ diff --git a/tests/data/longloop.ape b/tests/data/longloop.ape new file mode 100644 index 00000000..3800387a Binary files /dev/null and b/tests/data/longloop.ape differ diff --git a/tests/data/segfault.aif b/tests/data/segfault.aif new file mode 100644 index 00000000..5dce192b Binary files /dev/null and b/tests/data/segfault.aif differ diff --git a/tests/data/segfault.mpc b/tests/data/segfault.mpc new file mode 100644 index 00000000..2c7e29fb Binary files /dev/null and b/tests/data/segfault.mpc differ diff --git a/tests/data/segfault.oga b/tests/data/segfault.oga new file mode 100644 index 00000000..e23c2170 Binary files /dev/null and b/tests/data/segfault.oga differ diff --git a/tests/data/segfault.wav b/tests/data/segfault.wav new file mode 100644 index 00000000..0385e99b Binary files /dev/null and b/tests/data/segfault.wav differ diff --git a/tests/data/segfault2.mpc b/tests/data/segfault2.mpc new file mode 100644 index 00000000..fcfa982f --- /dev/null +++ b/tests/data/segfault2.mpc @@ -0,0 +1 @@ +MPCKSH \ No newline at end of file diff --git a/tests/data/zerodiv.ape b/tests/data/zerodiv.ape new file mode 100644 index 00000000..683bc2dd Binary files /dev/null and b/tests/data/zerodiv.ape differ diff --git a/tests/data/zerodiv.mpc b/tests/data/zerodiv.mpc new file mode 100644 index 00000000..d3ea57c7 Binary files /dev/null and b/tests/data/zerodiv.mpc differ diff --git a/tests/test_aiff.cpp b/tests/test_aiff.cpp index 6ff07ceb..4df8d893 100644 --- a/tests/test_aiff.cpp +++ b/tests/test_aiff.cpp @@ -1,9 +1,9 @@ -#include #include #include #include #include #include +#include #include "utils.h" using namespace std; @@ -13,32 +13,57 @@ class TestAIFF : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestAIFF); CPPUNIT_TEST(testReading); + CPPUNIT_TEST(testSaveID3v2); CPPUNIT_TEST(testAiffCProperties); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST_SUITE_END(); public: void testReading() { - ScopedFileCopy copy("empty", ".aiff"); - string filename = copy.fileName(); + RIFF::AIFF::File f(TEST_FILE_PATH_C("empty.aiff")); + CPPUNIT_ASSERT_EQUAL(705, f.audioProperties()->bitrate()); + } - RIFF::AIFF::File *f = new RIFF::AIFF::File(filename.c_str()); - CPPUNIT_ASSERT_EQUAL(705, f->audioProperties()->bitrate()); - CPPUNIT_ASSERT(!f->audioProperties()->isAiffC()); - delete f; + void testSaveID3v2() + { + ScopedFileCopy copy("empty", ".aiff"); + string newname = copy.fileName(); + + { + RIFF::AIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(!f.hasID3v2Tag()); + f.tag()->setTitle(L"TitleXXX"); + f.save(); + } + + { + RIFF::AIFF::File f(newname.c_str()); + CPPUNIT_ASSERT(f.hasID3v2Tag()); + CPPUNIT_ASSERT_EQUAL(String(L"TitleXXX"), f.tag()->title()); + } } void testAiffCProperties() { - ScopedFileCopy copy("alaw", ".aifc"); - string filename = copy.fileName(); + RIFF::AIFF::File f(TEST_FILE_PATH_C("alaw.aifc")); + CPPUNIT_ASSERT(f.audioProperties()->isAiffC()); + CPPUNIT_ASSERT(f.audioProperties()->compressionType() == "ALAW"); + CPPUNIT_ASSERT(f.audioProperties()->compressionName() == "SGI CCITT G.711 A-law"); + } - RIFF::AIFF::File *f = new RIFF::AIFF::File(filename.c_str()); - CPPUNIT_ASSERT(f->audioProperties()->isAiffC()); - CPPUNIT_ASSERT_EQUAL(ByteVector("ALAW"), f->audioProperties()->compressionType()); - CPPUNIT_ASSERT_EQUAL(String("SGI CCITT G.711 A-law"), f->audioProperties()->compressionName()); - delete f; + void testFuzzedFile1() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("segfault.aif")); + CPPUNIT_ASSERT(!f.isValid()); + } + + void testFuzzedFile2() + { + RIFF::AIFF::File f(TEST_FILE_PATH_C("excessive_alloc.aif")); + CPPUNIT_ASSERT(!f.isValid()); } }; diff --git a/tests/test_ape.cpp b/tests/test_ape.cpp index c95ff0c2..2f842f65 100644 --- a/tests/test_ape.cpp +++ b/tests/test_ape.cpp @@ -1,10 +1,10 @@ -#include #include #include #include #include #include #include +#include #include "utils.h" using namespace std; @@ -16,6 +16,8 @@ class TestAPE : public CppUnit::TestFixture CPPUNIT_TEST(testProperties399); CPPUNIT_TEST(testProperties396); CPPUNIT_TEST(testProperties390); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST_SUITE_END(); public: @@ -47,6 +49,18 @@ public: CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); } + void testFuzzedFile1() + { + APE::File f(TEST_FILE_PATH_C("longloop.ape")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile2() + { + APE::File f(TEST_FILE_PATH_C("zerodiv.ape")); + CPPUNIT_ASSERT(f.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestAPE); diff --git a/tests/test_id3v2.cpp b/tests/test_id3v2.cpp index e368f04f..0037165f 100644 --- a/tests/test_id3v2.cpp +++ b/tests/test_id3v2.cpp @@ -93,6 +93,7 @@ class TestID3v2 : public CppUnit::TestFixture CPPUNIT_TEST(testRenderChapterFrame); CPPUNIT_TEST(testParseTableOfContentsFrame); CPPUNIT_TEST(testRenderTableOfContentsFrame); + CPPUNIT_TEST(testShrinkPadding); CPPUNIT_TEST_SUITE_END(); public: @@ -201,7 +202,7 @@ public: "JPG" "\x01" "d\x00" - "\x00", 18); + "\x00", 14); ID3v2::Header header; header.setMajorVersion(2); ID3v2::AttachedPictureFrame *frame = @@ -224,7 +225,7 @@ public: "JPG" "\x01" "d\x00" - "\x00", 18); + "\x00", 14); ID3v2::Header header; header.setMajorVersion(2); ID3v2::AttachedPictureFrame *frame = @@ -1016,6 +1017,39 @@ public: f.render()); } + void testShrinkPadding() + { + ScopedFileCopy copy("xing", ".mp3"); + string newname = copy.fileName(); + + { + MPEG::File f(newname.c_str()); + ID3v2::Tag *tag = f.ID3v2Tag(true); + + ID3v2::TextIdentificationFrame *frame1 = new ID3v2::TextIdentificationFrame("TIT2"); + frame1->setText("Title"); + tag->addFrame(frame1); + + ID3v2::AttachedPictureFrame *frame2 = new ID3v2::AttachedPictureFrame(); + frame2->setPicture(ByteVector(100 * 1024, '\xff')); + tag->addFrame(frame2); + + f.save(); + CPPUNIT_ASSERT(f.length() > 100 * 1024); + } + + { + MPEG::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(true, f.hasID3v2Tag()); + + ID3v2::Tag *tag = f.ID3v2Tag(); + tag->removeFrames("APIC"); + + f.save(); + CPPUNIT_ASSERT(f.length() < 10 * 1024); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestID3v2); diff --git a/tests/test_mp4.cpp b/tests/test_mp4.cpp index 2fdca4a8..ac2d469d 100644 --- a/tests/test_mp4.cpp +++ b/tests/test_mp4.cpp @@ -155,7 +155,7 @@ public: MP4::Atoms *atoms = new MP4::Atoms(f); MP4::Atom *moov = atoms->atoms[0]; - CPPUNIT_ASSERT_EQUAL(long(77), moov->length); + CPPUNIT_ASSERT_EQUAL(offset_t(77), moov->length); f->tag()->itemListMap()["pgap"] = true; f->save(); @@ -169,7 +169,7 @@ public: atoms = new MP4::Atoms(f); moov = atoms->atoms[0]; // original size + 'pgap' size + padding - CPPUNIT_ASSERT_EQUAL(long(77 + 25 + 974), moov->length); + CPPUNIT_ASSERT_EQUAL(offset_t(77 + 25 + 974), moov->length); delete atoms; delete f; } diff --git a/tests/test_mpc.cpp b/tests/test_mpc.cpp index 12da6ed3..0589740f 100644 --- a/tests/test_mpc.cpp +++ b/tests/test_mpc.cpp @@ -1,10 +1,10 @@ -#include #include #include #include #include #include #include +#include #include "utils.h" using namespace std; @@ -17,6 +17,10 @@ class TestMPC : public CppUnit::TestFixture CPPUNIT_TEST(testPropertiesSV7); CPPUNIT_TEST(testPropertiesSV5); CPPUNIT_TEST(testPropertiesSV4); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); + CPPUNIT_TEST(testFuzzedFile3); + CPPUNIT_TEST(testFuzzedFile4); CPPUNIT_TEST_SUITE_END(); public: @@ -61,6 +65,30 @@ public: CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); } + void testFuzzedFile1() + { + MPC::File f(TEST_FILE_PATH_C("zerodiv.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile2() + { + MPC::File f(TEST_FILE_PATH_C("infloop.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile3() + { + MPC::File f(TEST_FILE_PATH_C("segfault.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + + void testFuzzedFile4() + { + MPC::File f(TEST_FILE_PATH_C("segfault2.mpc")); + CPPUNIT_ASSERT(f.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMPC); diff --git a/tests/test_mpeg.cpp b/tests/test_mpeg.cpp index 44925cac..07b970ee 100644 --- a/tests/test_mpeg.cpp +++ b/tests/test_mpeg.cpp @@ -16,6 +16,8 @@ class TestMPEG : public CppUnit::TestFixture CPPUNIT_TEST(testSaveID3v24); CPPUNIT_TEST(testSaveID3v24WrongParam); CPPUNIT_TEST(testSaveID3v23); + CPPUNIT_TEST(testDuplicateID3v2); + CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); public: @@ -92,6 +94,25 @@ public: } } + void testDuplicateID3v2() + { + ScopedFileCopy copy("duplicate_id3v2", ".mp3"); + string newname = copy.fileName(); + + MPEG::File f(newname.c_str()); + + // duplicate_id3v2.mp3 has duplicate ID3v2 tags. + // Sample rate will be 32000 if can't skip the second tag. + + CPPUNIT_ASSERT_EQUAL(44100, f.audioProperties()->sampleRate()); + } + + void testFuzzedFile() + { + MPEG::File f(TEST_FILE_PATH_C("excessive_alloc.mp3")); + CPPUNIT_ASSERT(f.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestMPEG); diff --git a/tests/test_oggflac.cpp b/tests/test_oggflac.cpp index 1cdb24b0..975af44e 100644 --- a/tests/test_oggflac.cpp +++ b/tests/test_oggflac.cpp @@ -15,6 +15,7 @@ class TestOggFLAC : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestOggFLAC); CPPUNIT_TEST(testFramingBit); + CPPUNIT_TEST(testFuzzedFile); CPPUNIT_TEST_SUITE_END(); public: @@ -39,6 +40,12 @@ public: delete f; } + void testFuzzedFile() + { + Ogg::FLAC::File f(TEST_FILE_PATH_C("segfault.oga")); + CPPUNIT_ASSERT(!f.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestOggFLAC); diff --git a/tests/test_wav.cpp b/tests/test_wav.cpp index 6446a1c2..905a60de 100644 --- a/tests/test_wav.cpp +++ b/tests/test_wav.cpp @@ -1,9 +1,11 @@ -#include #include #include #include +#include +#include #include #include +#include #include "utils.h" using namespace std; @@ -14,8 +16,12 @@ class TestWAV : public CppUnit::TestFixture CPPUNIT_TEST_SUITE(TestWAV); CPPUNIT_TEST(testLength); CPPUNIT_TEST(testZeroSizeDataChunk); + CPPUNIT_TEST(testID3v2Tag); + CPPUNIT_TEST(testInfoTag); CPPUNIT_TEST(testStripTags); CPPUNIT_TEST(testFormat); + CPPUNIT_TEST(testFuzzedFile1); + CPPUNIT_TEST(testFuzzedFile2); CPPUNIT_TEST_SUITE_END(); public: @@ -23,14 +29,80 @@ public: void testLength() { RIFF::WAV::File f(TEST_FILE_PATH_C("empty.wav")); - CPPUNIT_ASSERT_EQUAL(true, f.isValid()); + CPPUNIT_ASSERT(f.isValid()); CPPUNIT_ASSERT_EQUAL(3, f.audioProperties()->length()); } void testZeroSizeDataChunk() { RIFF::WAV::File f(TEST_FILE_PATH_C("zero-size-chunk.wav")); - CPPUNIT_ASSERT_EQUAL(false, f.isValid()); + CPPUNIT_ASSERT(!f.isValid()); + } + + void testID3v2Tag() + { + ScopedFileCopy copy("empty", ".wav"); + string filename = copy.fileName(); + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + + f.ID3v2Tag()->setTitle(L"Title"); + f.ID3v2Tag()->setArtist(L"Artist"); + f.save(); + } + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String(L"Title"), f.ID3v2Tag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L"Artist"), f.ID3v2Tag()->artist()); + + f.ID3v2Tag()->setTitle(L""); + f.ID3v2Tag()->setArtist(L""); + f.save(); + } + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.ID3v2Tag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.ID3v2Tag()->artist()); + } + } + + void testInfoTag() + { + ScopedFileCopy copy("empty", ".wav"); + string filename = copy.fileName(); + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + + f.InfoTag()->setTitle(L"Title"); + f.InfoTag()->setArtist(L"Artist"); + f.save(); + } + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String(L"Title"), f.InfoTag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L"Artist"), f.InfoTag()->artist()); + + f.InfoTag()->setTitle(L""); + f.InfoTag()->setArtist(L""); + f.save(); + } + + { + RIFF::WAV::File f(filename.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.InfoTag()->title()); + CPPUNIT_ASSERT_EQUAL(String(L""), f.InfoTag()->artist()); + } } void testStripTags() @@ -80,6 +152,19 @@ public: CPPUNIT_ASSERT_EQUAL(true, f2.isValid()); CPPUNIT_ASSERT_EQUAL((uint)6, f2.audioProperties()->format()); } + + void testFuzzedFile1() + { + RIFF::WAV::File f1(TEST_FILE_PATH_C("infloop.wav")); + CPPUNIT_ASSERT(!f1.isValid()); + } + + void testFuzzedFile2() + { + RIFF::WAV::File f2(TEST_FILE_PATH_C("segfault.wav")); + CPPUNIT_ASSERT(f2.isValid()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestWAV);