diff --git a/CMakeLists.txt b/CMakeLists.txt index f210a565..7fd2df75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,14 @@ if(DEFINED ENABLE_STATIC) endif() option(BUILD_SHARED_LIBS "Build shared libraries" OFF) +if(APPLE) + option(BUILD_FRAMEWORK "Build an OS X framework" OFF) + if(BUILD_FRAMEWORK) + set(BUILD_SHARED_LIBS ON) + #set(CMAKE_MACOSX_RPATH 1) + set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.") + endif() +endif() if(NOT BUILD_SHARED_LIBS) add_definitions(-DTAGLIB_STATIC) endif() @@ -48,11 +56,6 @@ set(BIN_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/bin" CACHE PATH "The subdirectory to set(LIB_INSTALL_DIR "${EXEC_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "The subdirectory relative to the install prefix where libraries will be installed (default is /lib${LIB_SUFFIX})") set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH "The subdirectory to the header prefix") -if(APPLE) - option(BUILD_FRAMEWORK "Build an OS X framework" OFF) - set(FRAMEWORK_INSTALL_DIR "/Library/Frameworks" CACHE STRING "Directory to install frameworks to.") -endif() - if(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") diff --git a/ConfigureChecks.cmake b/ConfigureChecks.cmake index 4654b6a9..e1f97027 100644 --- a/ConfigureChecks.cmake +++ b/ConfigureChecks.cmake @@ -57,64 +57,55 @@ endif() check_cxx_source_compiles(" #include int main() { - std::atomic x; - x.fetch_add(1); - x.fetch_sub(1); + std::atomic_int x; + ++x; + --x; return 0; } " HAVE_STD_ATOMIC) if(NOT HAVE_STD_ATOMIC) - find_package(Boost COMPONENTS atomic) - if(Boost_ATOMIC_FOUND) - set(HAVE_BOOST_ATOMIC 1) - else() - set(HAVE_BOOST_ATOMIC 0) - endif() + check_cxx_source_compiles(" + int main() { + volatile int x; + __sync_add_and_fetch(&x, 1); + int y = __sync_sub_and_fetch(&x, 1); + return 0; + } + " HAVE_GCC_ATOMIC) - if(NOT HAVE_BOOST_ATOMIC) + if(NOT HAVE_GCC_ATOMIC) check_cxx_source_compiles(" + #include int main() { - volatile int x; - __sync_add_and_fetch(&x, 1); - int y = __sync_sub_and_fetch(&x, 1); + volatile int32_t x; + OSAtomicIncrement32Barrier(&x); + int32_t y = OSAtomicDecrement32Barrier(&x); return 0; } - " HAVE_GCC_ATOMIC) + " HAVE_MAC_ATOMIC) - if(NOT HAVE_GCC_ATOMIC) + if(NOT HAVE_MAC_ATOMIC) check_cxx_source_compiles(" - #include + #include int main() { - volatile int32_t x; - OSAtomicIncrement32Barrier(&x); - int32_t y = OSAtomicDecrement32Barrier(&x); + volatile LONG x; + InterlockedIncrement(&x); + LONG y = InterlockedDecrement(&x); return 0; } - " HAVE_MAC_ATOMIC) + " HAVE_WIN_ATOMIC) - if(NOT HAVE_MAC_ATOMIC) + if(NOT HAVE_WIN_ATOMIC) check_cxx_source_compiles(" - #include + #include int main() { - volatile LONG x; - InterlockedIncrement(&x); - LONG y = InterlockedDecrement(&x); + volatile int x; + __sync_add_and_fetch(&x, 1); + int y = __sync_sub_and_fetch(&x, 1); return 0; } - " HAVE_WIN_ATOMIC) - - if(NOT HAVE_WIN_ATOMIC) - check_cxx_source_compiles(" - #include - int main() { - volatile int x; - __sync_add_and_fetch(&x, 1); - int y = __sync_sub_and_fetch(&x, 1); - return 0; - } - " HAVE_IA64_ATOMIC) - endif() + " HAVE_IA64_ATOMIC) endif() endif() endif() @@ -259,15 +250,6 @@ if(NOT ZLIB_SOURCE) else() set(HAVE_ZLIB 0) endif() - - if(NOT HAVE_ZLIB) - find_package(Boost COMPONENTS iostreams zlib) - if(Boost_IOSTREAMS_FOUND AND Boost_ZLIB_FOUND) - set(HAVE_BOOST_ZLIB 1) - else() - set(HAVE_BOOST_ZLIB 0) - endif() - endif() endif() # Determine whether CppUnit is installed. diff --git a/NEWS b/NEWS index 3c74bc45..774e6532 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,9 @@ * Added support for classical music tags of iTunes 12.5. * Dropped support for Windows 9x and NT 4.0 or older. * Fixed reading MP4 atoms with zero length. + * Fixed handling of redundant UTF-8 sequences in Win32. + * Fixed handling of lowercase field names in Vorbis Comments. + * Fixed possible file corruptions when saving Ogg files. * Several smaller bug fixes and performance improvements. TagLib 1.11.1 (Oct 24, 2016) diff --git a/config.h.cmake b/config.h.cmake index ed29dc6b..4c227b3e 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -1,5 +1,8 @@ /* config.h. Generated by cmake from config.h.cmake */ +#ifndef TAGLIB_CONFIG_H +#define TAGLIB_CONFIG_H + /* Defined if required for large files support */ #cmakedefine _LARGE_FILES ${_LARGE_FILES} #cmakedefine _LARGEFILE_SOURCE ${_LARGEFILE_SOURCE} @@ -15,7 +18,6 @@ /* Defined if your compiler supports some atomic operations */ #cmakedefine HAVE_STD_ATOMIC 1 -#cmakedefine HAVE_BOOST_ATOMIC 1 #cmakedefine HAVE_GCC_ATOMIC 1 #cmakedefine HAVE_MAC_ATOMIC 1 #cmakedefine HAVE_WIN_ATOMIC 1 @@ -34,10 +36,10 @@ /* Defined if zlib is installed */ #cmakedefine HAVE_ZLIB 1 -#cmakedefine HAVE_BOOST_ZLIB 1 /* Indicates whether debug messages are shown even in release mode */ #cmakedefine TRACE_IN_RELEASE 1 #cmakedefine TESTS_DIR "@TESTS_DIR@" +#endif diff --git a/taglib/CMakeLists.txt b/taglib/CMakeLists.txt index 5eac169f..92efd64c 100644 --- a/taglib/CMakeLists.txt +++ b/taglib/CMakeLists.txt @@ -35,7 +35,7 @@ elseif(HAVE_ZLIB_SOURCE) include_directories(${ZLIB_SOURCE}) endif() -if(HAVE_BOOST_BYTESWAP OR HAVE_BOOST_ATOMIC OR HAVE_BOOST_ZLIB) +if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIR}) endif() @@ -377,18 +377,10 @@ set(tag_LIB_SRCS add_library(tag ${tag_LIB_SRCS} ${tag_HDRS}) -if(ZLIB_FOUND) +if(HAVE_ZLIB AND NOT HAVE_ZLIB_SOURCE) target_link_libraries(tag ${ZLIB_LIBRARIES}) endif() -if(HAVE_BOOST_ATOMIC) - target_link_libraries(tag ${Boost_ATOMIC_LIBRARY}) -endif() - -if(HAVE_BOOST_ZLIB) - target_link_libraries(tag ${Boost_IOSTREAMS_LIBRARY} ${Boost_ZLIB_LIBRARY}) -endif() - set_target_properties(tag PROPERTIES VERSION ${TAGLIB_SOVERSION_MAJOR}.${TAGLIB_SOVERSION_MINOR}.${TAGLIB_SOVERSION_PATCH} SOVERSION ${TAGLIB_SOVERSION_MAJOR} @@ -398,7 +390,13 @@ set_target_properties(tag PROPERTIES PUBLIC_HEADER "${tag_HDRS}" ) if(BUILD_FRAMEWORK) - set_target_properties(tag PROPERTIES FRAMEWORK TRUE) + unset(INSTALL_NAME_DIR) + set_target_properties(tag PROPERTIES + FRAMEWORK TRUE + MACOSX_RPATH 1 + VERSION "A" + SOVERSION "A" + ) endif() install(TARGETS tag diff --git a/taglib/ape/apetag.cpp b/taglib/ape/apetag.cpp index 171558e8..334f57d7 100644 --- a/taglib/ape/apetag.cpp +++ b/taglib/ape/apetag.cpp @@ -228,7 +228,7 @@ void APE::Tag::setGenre(const String &s) void APE::Tag::setYear(unsigned int i) { - if(i <= 0) + if(i == 0) removeItem("YEAR"); else addValue("YEAR", String::number(i), true); @@ -236,7 +236,7 @@ void APE::Tag::setYear(unsigned int i) void APE::Tag::setTrack(unsigned int i) { - if(i <= 0) + if(i == 0) removeItem("TRACK"); else addValue("TRACK", String::number(i), true); diff --git a/taglib/asf/asfattribute.h b/taglib/asf/asfattribute.h index df7485d5..806a90d0 100644 --- a/taglib/asf/asfattribute.h +++ b/taglib/asf/asfattribute.h @@ -112,7 +112,7 @@ namespace TagLib /*! * Copies the contents of \a other into this item. */ - ASF::Attribute &operator=(const Attribute &other); + Attribute &operator=(const Attribute &other); /*! * Exchanges the content of the Attribute by the content of \a other. diff --git a/taglib/flac/flacfile.cpp b/taglib/flac/flacfile.cpp index 42b55b81..ac4ce6f8 100644 --- a/taglib/flac/flacfile.cpp +++ b/taglib/flac/flacfile.cpp @@ -153,8 +153,8 @@ bool FLAC::File::save() } // Create new vorbis comments - - Tag::duplicate(&d->tag, xiphComment(true), false); + if(!hasXiphComment()) + Tag::duplicate(&d->tag, xiphComment(true), false); d->xiphCommentData = xiphComment()->render(false); diff --git a/taglib/mp4/mp4tag.cpp b/taglib/mp4/mp4tag.cpp index 94582ecb..07ade25c 100644 --- a/taglib/mp4/mp4tag.cpp +++ b/taglib/mp4/mp4tag.cpp @@ -1028,10 +1028,10 @@ PropertyMap MP4::Tag::setProperties(const PropertyMap &props) if(reverseKeyMap.contains(it->first)) { String name = reverseKeyMap[it->first]; if((it->first == "TRACKNUMBER" || it->first == "DISCNUMBER") && !it->second.isEmpty()) { - int first = 0, second = 0; StringList parts = StringList::split(it->second.front(), "/"); if(!parts.isEmpty()) { - first = parts[0].toInt(); + int first = parts[0].toInt(); + int second = 0; if(parts.size() > 1) { second = parts[1].toInt(); } diff --git a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp index 6eaabaad..38c95bbe 100644 --- a/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp +++ b/taglib/mpeg/id3v2/frames/tableofcontentsframe.cpp @@ -36,7 +36,9 @@ class TableOfContentsFrame::TableOfContentsFramePrivate { public: TableOfContentsFramePrivate() : - tagHeader(0) + tagHeader(0), + isTopLevel(false), + isOrdered(false) { embeddedFrameList.setAutoDelete(true); } diff --git a/taglib/mpeg/id3v2/id3v2tag.cpp b/taglib/mpeg/id3v2/id3v2tag.cpp index 3424095c..e59ba0fd 100644 --- a/taglib/mpeg/id3v2/id3v2tag.cpp +++ b/taglib/mpeg/id3v2/id3v2tag.cpp @@ -80,6 +80,7 @@ class ID3v2::Tag::TagPrivate { public: TagPrivate() : + factory(0), file(0), tagOffset(0), extendedHeader(0), @@ -375,7 +376,7 @@ void ID3v2::Tag::setGenre(const String &s) void ID3v2::Tag::setYear(unsigned int i) { - if(i <= 0) { + if(i == 0) { removeFrames("TDRC"); return; } @@ -384,7 +385,7 @@ void ID3v2::Tag::setYear(unsigned int i) void ID3v2::Tag::setTrack(unsigned int i) { - if(i <= 0) { + if(i == 0) { removeFrames("TRCK"); return; } diff --git a/taglib/ogg/oggfile.cpp b/taglib/ogg/oggfile.cpp index 1c6d2182..67d6cc15 100644 --- a/taglib/ogg/oggfile.cpp +++ b/taglib/ogg/oggfile.cpp @@ -253,7 +253,7 @@ void Ogg::File::writePacket(unsigned int i, const ByteVector &packet) ByteVectorList packets = firstPage->packets(); packets[i - firstPage->firstPacketIndex()] = packet; - if(firstPage != lastPage && lastPage->packetCount() > 2) { + if(firstPage != lastPage && lastPage->packetCount() > 1) { ByteVectorList lastPagePackets = lastPage->packets(); lastPagePackets.erase(lastPagePackets.begin()); packets.append(lastPagePackets); diff --git a/taglib/ogg/oggpage.cpp b/taglib/ogg/oggpage.cpp index d95c2cfe..1c6889a4 100644 --- a/taglib/ogg/oggpage.cpp +++ b/taglib/ogg/oggpage.cpp @@ -208,15 +208,15 @@ List Ogg::Page::paginate(const ByteVectorList &packets, static const unsigned int SplitSize = 32 * 255; - // Force repagination if the packets are too large for a page. + // Force repagination if the segment table will exceed the size limit. if(strategy != Repaginate) { - size_t totalSize = packets.size(); + size_t tableSize = 0; for(ByteVectorList::ConstIterator it = packets.begin(); it != packets.end(); ++it) - totalSize += it->size(); + tableSize += it->size() / 255 + 1; - if(totalSize > 255 * 255) + if(tableSize > 255) strategy = Repaginate; } diff --git a/taglib/ogg/xiphcomment.cpp b/taglib/ogg/xiphcomment.cpp index 0bd8e28d..944f16af 100644 --- a/taglib/ogg/xiphcomment.cpp +++ b/taglib/ogg/xiphcomment.cpp @@ -271,10 +271,14 @@ bool Ogg::XiphComment::checkKey(const String &key) { if(key.size() < 1) return false; - for(String::ConstIterator it = key.begin(); it != key.end(); it++) - // forbid non-printable, non-ascii, '=' (#61) and '~' (#126) - if (*it < 32 || *it >= 128 || *it == 61 || *it == 126) + + // A key may consist of ASCII 0x20 through 0x7D, 0x3D ('=') excluded. + + for(String::ConstIterator it = key.begin(); it != key.end(); it++) { + if(*it < 0x20 || *it > 0x7D || *it == 0x3D) return false; + } + return true; } @@ -285,11 +289,18 @@ String Ogg::XiphComment::vendorID() const void Ogg::XiphComment::addField(const String &key, const String &value, bool replace) { + if(!checkKey(key)) { + debug("Ogg::XiphComment::addField() - Invalid key. Field not added."); + return; + } + + const String upperKey = key.upper(); + if(replace) - removeFields(key.upper()); + removeFields(upperKey); if(!key.isEmpty() && !value.isEmpty()) - d->fieldListMap[key.upper()].append(value); + d->fieldListMap[upperKey].append(value); } void Ogg::XiphComment::removeFields(const String &key) @@ -444,86 +455,69 @@ void Ogg::XiphComment::parse(const ByteVector &data) const unsigned int commentLength = data.toUInt32LE(pos); pos += 4; - ByteVector entry = data.mid(pos, commentLength); - + const ByteVector entry = data.mid(pos, commentLength); pos += commentLength; // Don't go past data end + if(pos > data.size()) break; - // Handle Pictures separately - if(entry.startsWith("METADATA_BLOCK_PICTURE=")) { - - // We need base64 encoded data including padding - if((entry.size() - 23) > 3 && ((entry.size() - 23) % 4) == 0) { - - // Decode base64 picture data - ByteVector picturedata = ByteVector::fromBase64(entry.mid(23)); - if(picturedata.size()) { - - // Decode Flac Picture - FLAC::Picture * picture = new FLAC::Picture(); - if(picture->parse(picturedata)) { - - d->pictureList.append(picture); - - // continue to next field - continue; - } - else { - delete picture; - debug("Failed to decode FlacPicture block"); - } - } - else { - debug("Failed to decode base64 encoded data"); - } - } - else { - debug("Invalid base64 encoded data"); - } - } - - - // Handle old picture standard - if(entry.startsWith("COVERART=")) { - - if((entry.size() - 9) > 3 && ((entry.size() - 9) % 4) == 0) { - - // Decode base64 picture data - ByteVector picturedata = ByteVector::fromBase64(entry.mid(9)); - if (picturedata.size()) { - - // Assume it's some type of image file - FLAC::Picture * picture = new FLAC::Picture(); - picture->setData(picturedata); - picture->setMimeType("image/"); - picture->setType(FLAC::Picture::Other); - d->pictureList.append(picture); - - // continue to next field - continue; - } - else { - debug("Failed to decode base64 encoded data"); - } - } - else { - debug("Invalid base64 encoded data"); - } - } - // Check for field separator - size_t sep = entry.find('='); - if(sep == ByteVector::npos()) { - debug("Discarding invalid comment field."); + + const size_t sep = entry.find('='); + if(sep == 0 || sep == ByteVector::npos()) { + debug("Ogg::XiphComment::parse() - Discarding a field. Separator not found."); continue; } - // Parse key and value - String key = String(entry.mid(0, sep), String::UTF8); - String value = String(entry.mid(sep + 1), String::UTF8); - addField(key, value, false); + // Parse the key + + const String key = String(entry.mid(0, sep), String::UTF8).upper(); + if(!checkKey(key)) { + debug("Ogg::XiphComment::parse() - Discarding a field. Invalid key."); + continue; + } + + if(key == "METADATA_BLOCK_PICTURE" || key == "COVERART") { + + // Handle Pictures separately + + const ByteVector picturedata = ByteVector::fromBase64(entry.mid(sep + 1)); + if(picturedata.isEmpty()) { + debug("Ogg::XiphComment::parse() - Discarding a field. Invalid base64 data"); + continue; + } + + if(key[0] == L'M') { + + // Decode FLAC Picture + + FLAC::Picture * picture = new FLAC::Picture(); + if(picture->parse(picturedata)) { + d->pictureList.append(picture); + } + else { + delete picture; + debug("Ogg::XiphComment::parse() - Failed to decode FLAC Picture block"); + } + } + else { + + // Assume it's some type of image file + + FLAC::Picture * picture = new FLAC::Picture(); + picture->setData(picturedata); + picture->setMimeType("image/"); + picture->setType(FLAC::Picture::Other); + d->pictureList.append(picture); + } + } + else { + + // Parse the text + + addField(key, String(entry.mid(sep + 1), String::UTF8), false); + } } } diff --git a/taglib/taglib_config.h.cmake b/taglib/taglib_config.h.cmake new file mode 100644 index 00000000..915f130a --- /dev/null +++ b/taglib/taglib_config.h.cmake @@ -0,0 +1,11 @@ +/* taglib_config.h. Generated by cmake from taglib_config.h.cmake */ + +#ifndef TAGLIB_TAGLIB_CONFIG_H +#define TAGLIB_TAGLIB_CONFIG_H + +/* These values are no longer used. This file is present only for compatibility reasons. */ + +#define TAGLIB_WITH_ASF 1 +#define TAGLIB_WITH_MP4 1 + +#endif diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index 08fced0d..a04950a2 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include @@ -164,7 +163,7 @@ TFloat toFloat(const ByteVector &v, size_t offset) } tmp; ::memcpy(&tmp, v.data() + offset, sizeof(TInt)); - if(ENDIAN != Utils::floatByteOrder()) + if(ENDIAN != Utils::systemByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return tmp.f; @@ -179,7 +178,7 @@ ByteVector fromFloat(TFloat value) } tmp; tmp.f = value; - if(ENDIAN != Utils::floatByteOrder()) + if(ENDIAN != Utils::systemByteOrder()) tmp.i = Utils::byteSwap(tmp.i); return ByteVector(reinterpret_cast(&tmp), sizeof(TInt)); @@ -482,46 +481,60 @@ ByteVector &ByteVector::replace(char oldByte, char newByte) ByteVector &ByteVector::replace(const ByteVector &pattern, const ByteVector &with) { - // TODO: This takes O(n!) time in the worst case. Rewrite it to run in O(n) time. - - if(pattern.size() == 0 || pattern.size() > size()) - return *this; - if(pattern.size() == 1 && with.size() == 1) return replace(pattern[0], with[0]); - const size_t withSize = with.size(); - const size_t patternSize = pattern.size(); - const ptrdiff_t diff = withSize - patternSize; + // Check if there is at least one occurrence of the pattern. - size_t offset = 0; - while (true) { - offset = find(pattern, offset); - if(offset == npos()) - break; + size_t offset = find(pattern, 0); + if(offset == ByteVector::npos()) + return *this; + + if(pattern.size() == with.size()) { + + // We think this case might be common enough to optimize it. detach(); + do + { + ::memcpy(data() + offset, with.data(), with.size()); + offset = find(pattern, offset + pattern.size()); + } while(offset != ByteVector::npos()); + } + else { - if(diff < 0) { - ::memmove( - data() + offset + withSize, - data() + offset + patternSize, - size() - offset - patternSize); - resize(size() + diff); - } - else if(diff > 0) { - resize(size() + diff); - ::memmove( - data() + offset + withSize, - data() + offset + patternSize, - size() - diff - offset - patternSize); + // Loop once to calculate the result size. + + size_t dstSize = size(); + do + { + dstSize += with.size() - pattern.size(); + offset = find(pattern, offset + pattern.size()); + } while(offset != ByteVector::npos()); + + // Loop again to copy modified data to the new vector. + + ByteVector dst(dstSize); + size_t dstOffset = 0; + + offset = 0; + while(true) { + const size_t next = find(pattern, offset); + if(next == ByteVector::npos()) { + ::memcpy(dst.data() + dstOffset, data() + offset, size() - offset); + break; + } + + ::memcpy(dst.data() + dstOffset, data() + offset, next - offset); + dstOffset += next - offset; + + ::memcpy(dst.data() + dstOffset, with.data(), with.size()); + dstOffset += with.size(); + + offset = next + pattern.size(); } - ::memcpy(data() + offset, with.data(), with.size()); - - offset += withSize; - if(offset > size() - patternSize) - break; + swap(dst); } return *this; diff --git a/taglib/toolkit/trefcounter.cpp b/taglib/toolkit/trefcounter.cpp index b13215f3..c8882f1a 100644 --- a/taglib/toolkit/trefcounter.cpp +++ b/taglib/toolkit/trefcounter.cpp @@ -31,14 +31,9 @@ #if defined(HAVE_STD_ATOMIC) # include -# define ATOMIC_INT std::atomic -# define ATOMIC_INC(x) x.fetch_add(1) -# define ATOMIC_DEC(x) (x.fetch_sub(1) - 1) -#elif defined(HAVE_BOOST_ATOMIC) -# include -# define ATOMIC_INT boost::atomic -# define ATOMIC_INC(x) x.fetch_add(1) -# define ATOMIC_DEC(x) (x.fetch_sub(1) - 1) +# define ATOMIC_INT std::atomic_int +# define ATOMIC_INC(x) (++x) +# define ATOMIC_DEC(x) (--x) #elif defined(HAVE_GCC_ATOMIC) # define ATOMIC_INT int # define ATOMIC_INC(x) ::__sync_add_and_fetch(&x, 1) diff --git a/taglib/toolkit/tstring.cpp b/taglib/toolkit/tstring.cpp index 71be1d80..3f849e8f 100644 --- a/taglib/toolkit/tstring.cpp +++ b/taglib/toolkit/tstring.cpp @@ -88,7 +88,7 @@ namespace #ifdef _WIN32 len = ::MultiByteToWideChar( - CP_UTF8, 0, src, static_cast(srcLength), dst, static_cast(dstLength)); + CP_UTF8, MB_ERR_INVALID_CHARS, src, static_cast(srcLength), dst, static_cast(dstLength)); #else @@ -448,7 +448,10 @@ bool String::startsWith(const String &s) const String String::substr(size_t position, size_t length) const { - return String(d->data->substr(position, length)); + if(position == 0 && length >= size()) + return *this; + else + return String(d->data->substr(position, length)); } String &String::append(const String &s) @@ -501,7 +504,7 @@ ByteVector String::data(Type t) const ByteVector v(size(), 0); char *p = v.data(); - for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); it++) + for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); ++it) *p++ = static_cast(*it); return v; @@ -529,7 +532,7 @@ ByteVector String::data(Type t) const *p++ = '\xff'; *p++ = '\xfe'; - for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); it++) { + for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); ++it) { *p++ = static_cast(*it & 0xff); *p++ = static_cast(*it >> 8); } @@ -541,7 +544,7 @@ ByteVector String::data(Type t) const ByteVector v(size() * 2, 0); char *p = v.data(); - for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); it++) { + for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); ++it) { *p++ = static_cast(*it >> 8); *p++ = static_cast(*it & 0xff); } @@ -553,7 +556,7 @@ ByteVector String::data(Type t) const ByteVector v(size() * 2, 0); char *p = v.data(); - for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); it++) { + for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); ++it) { *p++ = static_cast(*it & 0xff); *p++ = static_cast(*it >> 8); } @@ -598,7 +601,7 @@ String String::stripWhiteSpace() const bool String::isLatin1() const { - for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); it++) { + for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); ++it) { if(*it >= 256) return false; } @@ -607,7 +610,7 @@ bool String::isLatin1() const bool String::isAscii() const { - for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); it++) { + for(std::wstring::const_iterator it = d->data->begin(); it != d->data->end(); ++it) { if(*it >= 128) return false; } diff --git a/taglib/toolkit/tutils.h b/taglib/toolkit/tutils.h index b7feec5b..6d1fa5fc 100644 --- a/taglib/toolkit/tutils.h +++ b/taglib/toolkit/tutils.h @@ -222,7 +222,7 @@ namespace TagLib } /*! - * Returns the integer byte order of the system. + * Returns the byte order of the system. */ inline ByteOrder systemByteOrder() { @@ -237,26 +237,6 @@ namespace TagLib else return BigEndian; } - - /*! - * Returns the IEEE754 byte order of the system. - */ - inline ByteOrder floatByteOrder() - { - union { - double d; - char c; - } u; - - // 1.0 is stored in memory like 0x3FF0000000000000 in canonical form. - // So the first byte is zero if little endian. - - u.d = 1.0; - if(u.c == 0) - return LittleEndian; - else - return BigEndian; - } } } } diff --git a/taglib/toolkit/tzlib.cpp b/taglib/toolkit/tzlib.cpp index 118e723e..6d07ba3d 100644 --- a/taglib/toolkit/tzlib.cpp +++ b/taglib/toolkit/tzlib.cpp @@ -27,23 +27,19 @@ # include #endif -#if defined(HAVE_ZLIB) +#ifdef HAVE_ZLIB # include -#elif defined(HAVE_BOOST_ZLIB) -# include -# include +# include +# include #endif -#include -#include - #include "tzlib.h" using namespace TagLib; bool zlib::isAvailable() { -#if defined(HAVE_ZLIB) || defined(HAVE_BOOST_ZLIB) +#ifdef HAVE_ZLIB return true; @@ -56,7 +52,7 @@ bool zlib::isAvailable() ByteVector zlib::decompress(const ByteVector &data) { -#if defined(HAVE_ZLIB) +#ifdef HAVE_ZLIB z_stream stream = {}; @@ -102,38 +98,6 @@ ByteVector zlib::decompress(const ByteVector &data) return outData; -#elif defined(HAVE_BOOST_ZLIB) - - using namespace boost::iostreams; - - struct : public sink - { - ByteVector data; - - typedef char char_type; - typedef sink_tag category; - - std::streamsize write(char const* s, std::streamsize n) - { - const size_t originalSize = data.size(); - - data.resize(static_cast(originalSize + n)); - ::memcpy(data.data() + originalSize, s, static_cast(n)); - - return n; - } - } sink; - - try { - zlib_decompressor().write(sink, data.data(), data.size()); - } - catch(const zlib_error &) { - debug("zlib::decompress() - Error reading compressed stream."); - return ByteVector(); - } - - return sink.data; - #else return ByteVector(); diff --git a/taglib/xm/xmfile.cpp b/taglib/xm/xmfile.cpp index daf6d954..ba947f93 100644 --- a/taglib/xm/xmfile.cpp +++ b/taglib/xm/xmfile.cpp @@ -590,9 +590,9 @@ void XM::File::read(bool) unsigned int count = 4 + instrument.read(*this, instrumentHeaderSize - 4U); READ_ASSERT(count == std::min(instrumentHeaderSize, instrument.size() + 4)); - unsigned int sampleHeaderSize = 0; long offset = 0; if(sampleCount > 0) { + unsigned int sampleHeaderSize = 0; sumSampleCount += sampleCount; // wouldn't know which header size to assume otherwise: READ_ASSERT(instrumentHeaderSize >= count + 4 && readU32L(sampleHeaderSize)); diff --git a/tests/data/lowercase-fields.ogg b/tests/data/lowercase-fields.ogg new file mode 100644 index 00000000..0ddd4935 Binary files /dev/null and b/tests/data/lowercase-fields.ogg differ diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 42b1c0c5..512c7f80 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -46,6 +46,7 @@ class TestByteVector : public CppUnit::TestFixture CPPUNIT_TEST(testIntegerConversion); CPPUNIT_TEST(testFloatingPointConversion); CPPUNIT_TEST(testReplace); + CPPUNIT_TEST(testReplaceAndDetach); CPPUNIT_TEST(testIterator); CPPUNIT_TEST(testResize); CPPUNIT_TEST(testAppend1); @@ -312,6 +313,45 @@ public: } } + void testReplaceAndDetach() + { + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace(ByteVector("a"), ByteVector("x")); + CPPUNIT_ASSERT_EQUAL(ByteVector("xbcdxbf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace('a', 'x'); + CPPUNIT_ASSERT_EQUAL(ByteVector("xbcdxbf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace(ByteVector("ab"), ByteVector("xy")); + CPPUNIT_ASSERT_EQUAL(ByteVector("xycdxyf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abcdabf"); + ByteVector b = a; + a.replace(ByteVector("a"), ByteVector("")); + CPPUNIT_ASSERT_EQUAL(ByteVector("bcdbf"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabf"), b); + } + { + ByteVector a("abdab"); + ByteVector b = a; + a.replace(ByteVector(""), ByteVector("c")); + CPPUNIT_ASSERT_EQUAL(ByteVector("abcdabc"), a); + CPPUNIT_ASSERT_EQUAL(ByteVector("abdab"), b); + } + } + void testIterator() { ByteVector v1("taglib"); diff --git a/tests/test_flac.cpp b/tests/test_flac.cpp index 18a38508..061cbcd0 100644 --- a/tests/test_flac.cpp +++ b/tests/test_flac.cpp @@ -62,6 +62,7 @@ class TestFLAC : public CppUnit::TestFixture CPPUNIT_TEST(testUpdateID3v2); CPPUNIT_TEST(testEmptyID3v2); CPPUNIT_TEST(testStripTags); + CPPUNIT_TEST(testRemoveXiphField); CPPUNIT_TEST_SUITE_END(); public: @@ -493,6 +494,28 @@ public: } } + void testRemoveXiphField() + { + ScopedFileCopy copy("silence-44-s", ".flac"); + + { + FLAC::File f(copy.fileName().c_str()); + f.xiphComment(true)->setTitle("XiphComment Title"); + f.ID3v2Tag(true)->setTitle("ID3v2 Title"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String("XiphComment Title"), f.xiphComment()->title()); + f.xiphComment()->removeFields("TITLE"); + f.save(); + } + { + FLAC::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT_EQUAL(String(), f.xiphComment()->title()); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestFLAC); diff --git a/tests/test_list.cpp b/tests/test_list.cpp index e314836a..e6bd695a 100644 --- a/tests/test_list.cpp +++ b/tests/test_list.cpp @@ -32,12 +32,13 @@ using namespace TagLib; class TestList : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestList); - CPPUNIT_TEST(testList); + CPPUNIT_TEST(testAppend); + CPPUNIT_TEST(testDetach); CPPUNIT_TEST_SUITE_END(); public: - void testList() + void testAppend() { List l1; List l2; @@ -51,14 +52,25 @@ public: l3.append(2); l3.append(3); l3.append(4); + CPPUNIT_ASSERT_EQUAL((size_t)4, l1.size()); CPPUNIT_ASSERT(l1 == l3); - - List l4 = l1; - List::Iterator it = l4.find(3); - *it = 33; - CPPUNIT_ASSERT_EQUAL(l1[2], 3); - CPPUNIT_ASSERT_EQUAL(l4[2], 33); } + + void testDetach() + { + List l1; + l1.append(1); + l1.append(2); + l1.append(3); + l1.append(4); + + List l2 = l1; + List::Iterator it = l2.find(3); + *it = 33; + CPPUNIT_ASSERT_EQUAL(3, l1[2]); + CPPUNIT_ASSERT_EQUAL(33, l2[2]); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestList); diff --git a/tests/test_map.cpp b/tests/test_map.cpp index 3096ff69..d3641e05 100644 --- a/tests/test_map.cpp +++ b/tests/test_map.cpp @@ -34,6 +34,7 @@ class TestMap : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestMap); CPPUNIT_TEST(testInsert); + CPPUNIT_TEST(testDetach); CPPUNIT_TEST_SUITE_END(); public: @@ -42,19 +43,28 @@ public: { Map m1; m1.insert("foo", 3); + m1.insert("bar", 5); + CPPUNIT_ASSERT_EQUAL((size_t)2, m1.size()); CPPUNIT_ASSERT_EQUAL(3, m1["foo"]); + CPPUNIT_ASSERT_EQUAL(5, m1["bar"]); m1.insert("foo", 7); + CPPUNIT_ASSERT_EQUAL((size_t)2, m1.size()); CPPUNIT_ASSERT_EQUAL(7, m1["foo"]); + CPPUNIT_ASSERT_EQUAL(5, m1["bar"]); + } - m1.insert("alice", 5); - m1.insert("bob", 9); + void testDetach() + { + Map m1; + m1.insert("alice", 5); + m1.insert("bob", 9); m1.insert("carol", 11); Map m2 = m1; Map::Iterator it = m2.find("bob"); (*it).second = 99; - CPPUNIT_ASSERT_EQUAL(m1["bob"], 9); - CPPUNIT_ASSERT_EQUAL(m2["bob"], 99); + CPPUNIT_ASSERT_EQUAL(9, m1["bob"]); + CPPUNIT_ASSERT_EQUAL(99, m2["bob"]); } }; diff --git a/tests/test_ogg.cpp b/tests/test_ogg.cpp index d271aac2..f277f56e 100644 --- a/tests/test_ogg.cpp +++ b/tests/test_ogg.cpp @@ -42,7 +42,8 @@ class TestOGG : public CppUnit::TestFixture { CPPUNIT_TEST_SUITE(TestOGG); CPPUNIT_TEST(testSimple); - CPPUNIT_TEST(testSplitPackets); + CPPUNIT_TEST(testSplitPackets1); + CPPUNIT_TEST(testSplitPackets2); CPPUNIT_TEST(testDictInterface1); CPPUNIT_TEST(testDictInterface2); CPPUNIT_TEST(testAudioProperties); @@ -67,7 +68,7 @@ public: } } - void testSplitPackets() + void testSplitPackets1() { ScopedFileCopy copy("empty", ".ogg"); string newname = copy.fileName(); @@ -110,6 +111,33 @@ public: } } + void testSplitPackets2() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + const String text = longText(60890, true); + + { + Ogg::Vorbis::File f(newname.c_str()); + f.tag()->setTitle(text); + f.save(); + } + { + Ogg::Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(text, f.tag()->title()); + + f.tag()->setTitle("ABCDE"); + f.save(); + } + { + Ogg::Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT(f.isValid()); + CPPUNIT_ASSERT_EQUAL(String("ABCDE"), f.tag()->title()); + } + } + void testDictInterface1() { ScopedFileCopy copy("empty", ".ogg"); diff --git a/tests/test_string.cpp b/tests/test_string.cpp index e0e298f6..464c51d5 100644 --- a/tests/test_string.cpp +++ b/tests/test_string.cpp @@ -50,6 +50,7 @@ class TestString : public CppUnit::TestFixture CPPUNIT_TEST(testEncodeNonLatin1); CPPUNIT_TEST(testEncodeEmpty); CPPUNIT_TEST(testIterator); + CPPUNIT_TEST(testRedundantUTF8); CPPUNIT_TEST_SUITE_END(); public: @@ -109,6 +110,9 @@ public: CPPUNIT_ASSERT(String(" foo ").stripWhiteSpace() == String("foo")); CPPUNIT_ASSERT(String("foo ").stripWhiteSpace() == String("foo")); CPPUNIT_ASSERT(String(" foo").stripWhiteSpace() == String("foo")); + CPPUNIT_ASSERT(String("foo").stripWhiteSpace() == String("foo")); + CPPUNIT_ASSERT(String("f o o").stripWhiteSpace() == String("f o o")); + CPPUNIT_ASSERT(String(" f o o ").stripWhiteSpace() == String("f o o")); CPPUNIT_ASSERT(memcmp(String("foo").data(String::Latin1).data(), "foo", 3) == 0); CPPUNIT_ASSERT(memcmp(String("f").data(String::Latin1).data(), "f", 1) == 0); @@ -265,6 +269,8 @@ public: CPPUNIT_ASSERT_EQUAL(String("01"), String("0123456").substr(0, 2)); CPPUNIT_ASSERT_EQUAL(String("12"), String("0123456").substr(1, 2)); CPPUNIT_ASSERT_EQUAL(String("123456"), String("0123456").substr(1, 200)); + CPPUNIT_ASSERT_EQUAL(String("0123456"), String("0123456").substr(0, 7)); + CPPUNIT_ASSERT_EQUAL(String("0123456"), String("0123456").substr(0, 200)); } void testNewline() @@ -332,6 +338,15 @@ public: CPPUNIT_ASSERT_EQUAL(L'i', *it1); CPPUNIT_ASSERT_EQUAL(L'I', *it2); } + + void testRedundantUTF8() + { + CPPUNIT_ASSERT_EQUAL(String("/"), String(ByteVector("\x2F"), String::UTF8)); + CPPUNIT_ASSERT(String(ByteVector("\xC0\xAF"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xE0\x80\xAF"), String::UTF8).isEmpty()); + CPPUNIT_ASSERT(String(ByteVector("\xF0\x80\x80\xAF"), String::UTF8).isEmpty()); + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestString); diff --git a/tests/test_xiphcomment.cpp b/tests/test_xiphcomment.cpp index 3dcfb4d2..254ab965 100644 --- a/tests/test_xiphcomment.cpp +++ b/tests/test_xiphcomment.cpp @@ -42,10 +42,12 @@ class TestXiphComment : public CppUnit::TestFixture CPPUNIT_TEST(testSetYear); CPPUNIT_TEST(testTrack); CPPUNIT_TEST(testSetTrack); - CPPUNIT_TEST(testInvalidKeys); + CPPUNIT_TEST(testInvalidKeys1); + CPPUNIT_TEST(testInvalidKeys2); CPPUNIT_TEST(testClearComment); CPPUNIT_TEST(testRemoveFields); CPPUNIT_TEST(testPicture); + CPPUNIT_TEST(testLowercaseFields); CPPUNIT_TEST_SUITE_END(); public: @@ -90,19 +92,32 @@ public: CPPUNIT_ASSERT_EQUAL(String("3"), cmt.fieldListMap()["TRACKNUMBER"].front()); } - void testInvalidKeys() + void testInvalidKeys1() { PropertyMap map; map[""] = String("invalid key: empty string"); map["A=B"] = String("invalid key: contains '='"); map["A~B"] = String("invalid key: contains '~'"); + map["A\x7F" "B"] = String("invalid key: contains '\x7F'"); + map[L"A\x3456" "B"] = String("invalid key: Unicode"); Ogg::XiphComment cmt; PropertyMap unsuccessful = cmt.setProperties(map); - CPPUNIT_ASSERT_EQUAL((size_t)3, unsuccessful.size()); + CPPUNIT_ASSERT_EQUAL((size_t)5, unsuccessful.size()); CPPUNIT_ASSERT(cmt.properties().isEmpty()); } + void testInvalidKeys2() + { + Ogg::XiphComment cmt; + cmt.addField("", "invalid key: empty string"); + cmt.addField("A=B", "invalid key: contains '='"); + cmt.addField("A~B", "invalid key: contains '~'"); + cmt.addField("A\x7F" "B", "invalid key: contains '\x7F'"); + cmt.addField(L"A\x3456" "B", "invalid key: Unicode"); + CPPUNIT_ASSERT_EQUAL(0U, cmt.fieldCount()); + } + void testClearComment() { ScopedFileCopy copy("empty", ".ogg"); @@ -178,6 +193,23 @@ public: } } + void testLowercaseFields() + { + const ScopedFileCopy copy("lowercase-fields", ".ogg"); + { + Ogg::Vorbis::File f(copy.fileName().c_str()); + List lst = f.tag()->pictureList(); + CPPUNIT_ASSERT_EQUAL(String("TEST TITLE"), f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(String("TEST ARTIST"), f.tag()->artist()); + CPPUNIT_ASSERT_EQUAL((size_t)1, lst.size()); + f.save(); + } + { + Ogg::Vorbis::File f(copy.fileName().c_str()); + CPPUNIT_ASSERT(f.find("METADATA_BLOCK_PICTURE") > 0); + } + } + }; CPPUNIT_TEST_SUITE_REGISTRATION(TestXiphComment);