avformat/id3v2: support buggy id3v2.3 tag length in id3v2.4
authorBenoit Fouet <benoit.fouet@free.fr>
Fri, 17 Oct 2014 08:56:59 +0000 (10:56 +0200)
committerMichael Niedermayer <michaelni@gmx.at>
Fri, 17 Oct 2014 14:05:53 +0000 (16:05 +0200)
Some encoders do not use syncsafe sizes in v2.4 id3 tags. Check the next
tag to try to choose between the two.

Fixes ticket #4003

Signed-off-by: Michael Niedermayer <michaelni@gmx.at>
libavformat/id3v2.c

index 44df4ff..3e54620 100644 (file)
@@ -170,6 +170,48 @@ static unsigned int get_size(AVIOContext *s, int len)
     return v;
 }
 
+static unsigned int size_to_syncsafe(unsigned int size)
+{
+    return (((size) & (0x7f <<  0)) >> 0) +
+           (((size) & (0x7f <<  8)) >> 1) +
+           (((size) & (0x7f << 16)) >> 2) +
+           (((size) & (0x7f << 24)) >> 3);
+}
+
+/* No real verification, only check that the tag consists of
+ * a combination of capital alpha-numerical characters */
+static int is_tag(const char *buf, unsigned int len)
+{
+    if (!len)
+        return 0;
+
+    while (len--)
+        if ((buf[len] < 'A' ||
+             buf[len] > 'Z') &&
+            (buf[len] < '0' ||
+             buf[len] > '9'))
+            return 0;
+
+    return 1;
+}
+
+/**
+ * Return 1 if the tag of length len at the given offset is valid, 0 if not, -1 on error
+ */
+static int check_tag(AVIOContext *s, int offset, unsigned int len)
+{
+    char tag[4];
+
+    if (len > 4 ||
+        avio_seek(s, offset, SEEK_SET) < 0 ||
+        avio_read(s, tag, len) < len)
+        return -1;
+    else if (!AV_RB32(tag) || is_tag(tag, len))
+        return 1;
+
+    return 0;
+}
+
 /**
  * Free GEOB type extra metadata.
  */
@@ -734,8 +776,26 @@ static void id3v2_parse(AVIOContext *pb, AVDictionary **metadata,
             tag[4] = 0;
             if (version == 3) {
                 tlen = avio_rb32(pb);
-            } else
-                tlen = get_size(pb, 4);
+            } else {
+                /* some encoders incorrectly uses v3 sizes instead of syncsafe ones
+                 * so check the next tag to see which one to use */
+                tlen = avio_rb32(pb);
+                if (tlen > 0x7f) {
+                    if (tlen < len) {
+                        int64_t cur = avio_tell(pb);
+
+                        if (ffio_ensure_seekback(pb, 2 /* tflags */ + tlen + 4 /* next tag */))
+                            break;
+
+                        if (check_tag(pb, cur + 2 + size_to_syncsafe(tlen), 4) == 1)
+                            tlen = size_to_syncsafe(tlen);
+                        else if (check_tag(pb, cur + 2 + tlen, 4) != 1)
+                            break;
+                        avio_seek(pb, cur, SEEK_SET);
+                    } else
+                        tlen = size_to_syncsafe(tlen);
+                }
+            }
             tflags  = avio_rb16(pb);
             tunsync = tflags & ID3v2_FLAG_UNSYNCH;
         } else {