]> granicus.if.org Git - taglib/commitdiff
Fix writing of new RIFF chunks at even positions
authorLukáš Lalinský <lalinsky@gmail.com>
Sun, 13 Feb 2011 10:27:56 +0000 (10:27 +0000)
committerLukáš Lalinský <lalinsky@gmail.com>
Sun, 13 Feb 2011 10:27:56 +0000 (10:27 +0000)
If the last chunk had an odd size, the new chunk would have been written at
odd position, which is incorrect.

This is based on the patch by Jens Dyffort, but I ended up changing the
implementation to correctly handle subsequential updates to the file.

The whole RIFF code really needs to be rewritten in a different way...

BUG:243954

git-svn-id: svn://anonsvn.kde.org/home/kde/trunk/kdesupport/taglib@1220223 283d02a7-25f6-0310-bc7c-ecb5cbfe19da

taglib/riff/rifffile.cpp
taglib/riff/rifffile.h
tests/data/noise.aif [new file with mode: 0644]
tests/data/noise_odd.aif [new file with mode: 0644]
tests/test_riff.cpp

index e69f4b4ba093ab2d386777489de2392adaf34b9e..5fa8e21253bd6c55aabb84b337b25a3f155e7a90 100644 (file)
@@ -74,6 +74,11 @@ RIFF::File::File(FileName file, Endianness endianness) : TagLib::File(file)
     read();
 }
 
+TagLib::uint RIFF::File::riffSize() const
+{
+  return d->size;
+}
+
 TagLib::uint RIFF::File::chunkCount() const
 {
   return d->chunkNames.size();
@@ -89,6 +94,11 @@ TagLib::uint RIFF::File::chunkOffset(uint i) const
   return d->chunkOffsets[i];
 }
 
+TagLib::uint RIFF::File::chunkPadding(uint i) const
+{
+  return d->chunkPadding[i];
+}
+
 ByteVector RIFF::File::chunkName(uint i) const
 {
   if(i >= chunkCount())
@@ -116,8 +126,7 @@ ByteVector RIFF::File::chunkData(uint i)
 
 void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
 {
-  if(d->chunkNames.size() == 0)
-  {
+  if(d->chunkNames.size() == 0) {
     debug("RIFF::File::setChunkData - No valid chunks found.");
     return;
   }
@@ -125,12 +134,10 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
   for(uint i = 0; i < d->chunkNames.size(); i++) {
     if(d->chunkNames[i] == name) {
 
-      int sizeDifference = data.size() - d->chunkSizes[i];
-
       // First we update the global size
 
-      insert(ByteVector::fromUInt(d->size + sizeDifference,
-                                  d->endianness == BigEndian), 4, 4);
+      d->size += ((data.size() + 1) & ~1) - (d->chunkSizes[i] + d->chunkPadding[i]);
+      insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
 
       // Now update the specific chunk
 
@@ -148,11 +155,30 @@ void RIFF::File::setChunkData(const ByteVector &name, const ByteVector &data)
     }
   }
 
-  // Couldn't find an existing chunk, so let's create a new one.  First update
-  // the global size:
+  // Couldn't find an existing chunk, so let's create a new one.
 
-  insert(ByteVector::fromUInt(d->size + data.size() + 8, d->endianness == BigEndian), 4, 4);
-  writeChunk(name, data, d->chunkOffsets.back() + d->chunkSizes.back());
+  uint i = d->chunkNames.size() - 1;
+  ulong offset = d->chunkOffsets[i] + d->chunkSizes[i];
+
+  // First we update the global size
+
+  d->size += (offset & 1) + data.size() + 8;
+  insert(ByteVector::fromUInt(d->size, d->endianness == BigEndian), 4, 4);
+
+  // Now add the chunk to the file
+
+  writeChunk(name, data, offset, std::max(ulong(0), length() - offset), (offset & 1) ? 1 : 0);
+
+  // And update our internal structure
+
+  if (offset & 1) {
+    d->chunkPadding[i] = 1;
+    offset++;
+  }
+  d->chunkNames.push_back(name);
+  d->chunkSizes.push_back(data.size());
+  d->chunkOffsets.push_back(offset + 8);
+  d->chunkPadding.push_back((data.size() & 0x01) ? 1 : 0);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -203,13 +229,16 @@ void RIFF::File::read()
 }
 
 void RIFF::File::writeChunk(const ByteVector &name, const ByteVector &data,
-                            ulong offset, ulong replace)
+                            ulong offset, ulong replace, uint leadingPadding)
 {
-  ByteVector combined = name;
+  ByteVector combined;
+  if(leadingPadding) {
+    combined.append(ByteVector(leadingPadding, '\x00'));
+  }
+  combined.append(name);
   combined.append(ByteVector::fromUInt(data.size(), d->endianness == BigEndian));
   combined.append(data);
   if((data.size() & 0x01) != 0) {
-    // padding
     combined.append('\x00');
   }
   insert(combined, offset, replace);
index 5c8c7d17e1c7137141872eee8e435b08c20c73f8..99282564275a02f4a547bfe0fda9661f49f81cb1 100644 (file)
@@ -57,6 +57,11 @@ namespace TagLib {
 
       File(FileName file, Endianness endianness);
 
+      /*!
+       * \return The size of the main RIFF chunk.
+       */
+      uint riffSize() const;
+
       /*!
        * \return The number of chunks in the file.
        */
@@ -72,6 +77,11 @@ namespace TagLib {
        */
       uint chunkDataSize(uint i) const;
 
+      /*!
+       * \return The size of the padding after the chunk (can be either 0 or 1).
+       */
+      uint chunkPadding(uint i) const;
+
       /*!
        * \return The name of the specified chunk, for instance, "COMM" or "ID3 "
        */
@@ -99,7 +109,8 @@ namespace TagLib {
 
       void read();
       void writeChunk(const ByteVector &name, const ByteVector &data,
-                      ulong offset, ulong replace = 0);
+                      ulong offset, ulong replace = 0,
+                      uint leadingPadding = 0);
 
       class FilePrivate;
       FilePrivate *d;
diff --git a/tests/data/noise.aif b/tests/data/noise.aif
new file mode 100644 (file)
index 0000000..310b995
Binary files /dev/null and b/tests/data/noise.aif differ
diff --git a/tests/data/noise_odd.aif b/tests/data/noise_odd.aif
new file mode 100644 (file)
index 0000000..bccfd72
Binary files /dev/null and b/tests/data/noise_odd.aif differ
index 20539ae1c97f7f7d4703eb1843c585ba4b77ef66..43b2f8bbe299af5900a9be5458d1a9f4c8832d4d 100644 (file)
@@ -13,8 +13,10 @@ class PublicRIFF : public RIFF::File
 {
 public:
   PublicRIFF(FileName file) : RIFF::File(file, BigEndian) {};
+  TagLib::uint riffSize() { return RIFF::File::riffSize(); };
   TagLib::uint chunkCount() { return RIFF::File::chunkCount(); };
   TagLib::uint chunkOffset(TagLib::uint i) { return RIFF::File::chunkOffset(i); };
+  TagLib::uint chunkPadding(TagLib::uint i) { return RIFF::File::chunkPadding(i); };
   TagLib::uint chunkDataSize(TagLib::uint i) { return RIFF::File::chunkDataSize(i); };
   ByteVector chunkName(TagLib::uint i) { return RIFF::File::chunkName(i); };
   ByteVector chunkData(TagLib::uint i) { return RIFF::File::chunkData(i); };
@@ -30,6 +32,9 @@ class TestRIFF : public CppUnit::TestFixture
 {
   CPPUNIT_TEST_SUITE(TestRIFF);
   CPPUNIT_TEST(testPadding);
+  CPPUNIT_TEST(testLastChunkAtEvenPosition);
+  CPPUNIT_TEST(testLastChunkAtEvenPosition2);
+  CPPUNIT_TEST(testLastChunkAtEvenPosition3);
   CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -77,6 +82,117 @@ public:
     CPPUNIT_ASSERT_EQUAL(ByteVector("foo"), f->chunkData(2));
   }
 
+  void testLastChunkAtEvenPosition()
+  {
+    ScopedFileCopy copy("noise", ".aif");
+    string filename = copy.fileName();
+
+    PublicRIFF *f = new PublicRIFF(filename.c_str());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0xff0 + 8), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(long(4400), f->length());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4399 - 8), f->riffSize());
+    f->setChunkData("TEST", "abcd");
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4412 - 8), f->riffSize());
+    delete f;
+
+    f = new PublicRIFF(filename.c_str());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3));
+    CPPUNIT_ASSERT_EQUAL(long(4412), f->length());
+    delete f;
+  }
+
+  void testLastChunkAtEvenPosition2()
+  {
+    ScopedFileCopy copy("noise_odd", ".aif");
+    string filename = copy.fileName();
+
+    PublicRIFF *f = new PublicRIFF(filename.c_str());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0xff0 + 8), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(long(4399), f->length());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4399 - 8), f->riffSize());
+    f->setChunkData("TEST", "abcd");
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4412 - 8), f->riffSize());
+    delete f;
+
+    f = new PublicRIFF(filename.c_str());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4), f->chunkDataSize(3));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(3));
+    CPPUNIT_ASSERT_EQUAL(long(4412), f->length());
+    delete f;
+  }
+
+  void testLastChunkAtEvenPosition3()
+  {
+    ScopedFileCopy copy("noise_odd", ".aif");
+    string filename = copy.fileName();
+
+    PublicRIFF *f = new PublicRIFF(filename.c_str());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0xff0 + 8), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(0), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(long(4399), f->length());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4399 - 8), f->riffSize());
+    f->setChunkData("TEST", "abc");
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f->chunkDataSize(3));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4411 - 8), f->riffSize());
+    delete f;
+
+    f = new PublicRIFF(filename.c_str());
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4088), f->chunkOffset(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(311), f->chunkDataSize(2));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("SSND"), f->chunkName(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(2));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(4408), f->chunkOffset(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(3), f->chunkDataSize(3));
+    CPPUNIT_ASSERT_EQUAL(ByteVector("TEST"), f->chunkName(3));
+    CPPUNIT_ASSERT_EQUAL(TagLib::uint(1), f->chunkPadding(3));
+    CPPUNIT_ASSERT_EQUAL(long(4412), f->length());
+    delete f;
+  }
+
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(TestRIFF);