diff --git a/taglib/toolkit/tbytevector.cpp b/taglib/toolkit/tbytevector.cpp index ed4835a8..6dd30dd7 100644 --- a/taglib/toolkit/tbytevector.cpp +++ b/taglib/toolkit/tbytevector.cpp @@ -894,6 +894,19 @@ ByteVector ByteVector::fromBase64(const ByteVector & input) 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }; + // Some encoders wrap the output into lines (e.g. MIME style base64 with a + // line break every 76 characters), skip such whitespace. + const auto isWhitespace = [](char c) { + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; + }; + if(std::any_of(input.begin(), input.end(), isWhitespace)) { + ByteVector stripped(input.size()); + const auto end = std::remove_copy_if(input.begin(), input.end(), + stripped.begin(), isWhitespace); + stripped.resize(static_cast(end - stripped.begin())); + return fromBase64(stripped); + } + unsigned int len = input.size(); ByteVector output(len); diff --git a/taglib/toolkit/tbytevector.h b/taglib/toolkit/tbytevector.h index abf88210..acacf66c 100644 --- a/taglib/toolkit/tbytevector.h +++ b/taglib/toolkit/tbytevector.h @@ -639,7 +639,9 @@ namespace TagLib { ByteVector toBase64() const; /*! - * Decodes the base64 encoded byte vector. + * Decodes the base64 encoded byte vector. Whitespace (space, tab, CR and + * LF), e.g. from line wrapped input, is ignored. Returns an empty byte + * vector if the input contains other invalid characters. * * \see toBase64() */ diff --git a/tests/test_bytevector.cpp b/tests/test_bytevector.cpp index 2ffd35a1..8fac2025 100644 --- a/tests/test_bytevector.cpp +++ b/tests/test_bytevector.cpp @@ -628,6 +628,18 @@ public: CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(invalid)); } + // Skip whitespace, as in line wrapped input + { + CPPUNIT_ASSERT_EQUAL(s1,ByteVector::fromBase64("YW55IGNhcm5h\nbCBwbGVhc3VyZS4=")); + CPPUNIT_ASSERT_EQUAL(s1,ByteVector::fromBase64("YW55IGNhcm5hbCBw\r\nbGVhc3VyZS4=\r\n")); + CPPUNIT_ASSERT_EQUAL(s3,ByteVector::fromBase64(" YW55 IGNh\tcm5hbCBwbGVhc3Vy\n")); + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64(" \r\n\t")); + + // Whitespace does not make otherwise invalid input valid + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64("YW55\nIGNh-cm5hbCBwbGVhc3VyZS4=")); + CPPUNIT_ASSERT_EQUAL(sempty,ByteVector::fromBase64("YW55IGNhcm5hbCBwbGVhc3VyZQ=\n")); + } + } void testEmpty() diff --git a/tests/test_xiphcomment.cpp b/tests/test_xiphcomment.cpp index 8cb1ff83..50580611 100644 --- a/tests/test_xiphcomment.cpp +++ b/tests/test_xiphcomment.cpp @@ -47,6 +47,7 @@ class TestXiphComment : public CppUnit::TestFixture CPPUNIT_TEST(testClearComment); CPPUNIT_TEST(testRemoveFields); CPPUNIT_TEST(testPicture); + CPPUNIT_TEST(testWrappedPicture); CPPUNIT_TEST(testLowercaseFields); CPPUNIT_TEST_SUITE_END(); @@ -193,6 +194,43 @@ public: } } + void testWrappedPicture() + { + ScopedFileCopy copy("empty", ".ogg"); + string newname = copy.fileName(); + + FLAC::Picture picture; + picture.setType(FLAC::Picture::FrontCover); + picture.setMimeType("image/jpeg"); + picture.setData(ByteVector(200, 'x')); + + // Base64 with a line break every 76 characters, as MIME style encoders write it. + const ByteVector base64 = picture.render().toBase64(); + ByteVector wrapped; + for(unsigned int i = 0; i < base64.size(); i += 76) { + wrapped.append(base64.mid(i, 76)); + wrapped.append('\n'); + } + + { + Vorbis::File f(newname.c_str()); + f.tag()->addField("METADATA_BLOCK_PICTURE", String(wrapped, String::Latin1)); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(1U, f.tag()->pictureList().size()); + CPPUNIT_ASSERT_EQUAL(ByteVector(200, 'x'), f.tag()->pictureList().front()->data()); + f.tag()->setTitle("Title"); + f.save(); + } + { + Vorbis::File f(newname.c_str()); + CPPUNIT_ASSERT_EQUAL(String("Title"), f.tag()->title()); + CPPUNIT_ASSERT_EQUAL(1U, f.tag()->pictureList().size()); + } + } + void testLowercaseFields() { const ScopedFileCopy copy("lowercase-fields", ".ogg");