]> git.sesse.net Git - ffmpeg/blobdiff - libavformat/id3v2.c
Merge remote-tracking branch 'qatar/master'
[ffmpeg] / libavformat / id3v2.c
index 9d7ceef5a4090d486d36b0996b1c15c6eaf061f1..7797cca09168593d781b96d3dd34ca916d870624 100644 (file)
 #include "libavutil/dict.h"
 #include "avio_internal.h"
 
+const AVMetadataConv ff_id3v2_34_metadata_conv[] = {
+    { "TALB", "album"},
+    { "TCOM", "composer"},
+    { "TCON", "genre"},
+    { "TCOP", "copyright"},
+    { "TENC", "encoded_by"},
+    { "TIT2", "title"},
+    { "TLAN", "language"},
+    { "TPE1", "artist"},
+    { "TPE2", "album_artist"},
+    { "TPE3", "performer"},
+    { "TPOS", "disc"},
+    { "TPUB", "publisher"},
+    { "TRCK", "track"},
+    { "TSSE", "encoder"},
+    { 0 }
+};
+
+const AVMetadataConv ff_id3v2_4_metadata_conv[] = {
+    { "TDRL", "date"},
+    { "TDRC", "date"},
+    { "TDEN", "creation_time"},
+    { "TSOA", "album-sort"},
+    { "TSOP", "artist-sort"},
+    { "TSOT", "title-sort"},
+    { 0 }
+};
+
+static const AVMetadataConv id3v2_2_metadata_conv[] = {
+    { "TAL",  "album"},
+    { "TCO",  "genre"},
+    { "TT2",  "title"},
+    { "TEN",  "encoded_by"},
+    { "TP1",  "artist"},
+    { "TP2",  "album_artist"},
+    { "TP3",  "performer"},
+    { "TRK",  "track"},
+    { 0 }
+};
+
+
+const char ff_id3v2_tags[][4] = {
+   "TALB", "TBPM", "TCOM", "TCON", "TCOP", "TDLY", "TENC", "TEXT",
+   "TFLT", "TIT1", "TIT2", "TIT3", "TKEY", "TLAN", "TLEN", "TMED",
+   "TOAL", "TOFN", "TOLY", "TOPE", "TOWN", "TPE1", "TPE2", "TPE3",
+   "TPE4", "TPOS", "TPUB", "TRCK", "TRSN", "TRSO", "TSRC", "TSSE",
+   { 0 },
+};
+
+const char ff_id3v2_4_tags[][4] = {
+   "TDEN", "TDOR", "TDRC", "TDRL", "TDTG", "TIPL", "TMCL", "TMOO",
+   "TPRO", "TSOA", "TSOP", "TSOT", "TSST",
+   { 0 },
+};
+
+const char ff_id3v2_3_tags[][4] = {
+   "TDAT", "TIME", "TORY", "TRDA", "TSIZ", "TYER",
+   { 0 },
+};
+
 int ff_id3v2_match(const uint8_t *buf, const char * magic)
 {
     return  buf[0]         == magic[0] &&
@@ -81,23 +141,20 @@ static void free_geobtag(void *obj)
 
 /**
  * Decode characters to UTF-8 according to encoding type. The decoded buffer is
- * always null terminated.
+ * always null terminated. Stop reading when either *maxread bytes are read from
+ * pb or U+0000 character is found.
  *
  * @param dst Pointer where the address of the buffer with the decoded bytes is
  * stored. Buffer must be freed by caller.
- * @param dstlen Pointer to an int where the length of the decoded string
- * is stored (in bytes, incl. null termination)
  * @param maxread Pointer to maximum number of characters to read from the
  * AVIOContext. After execution the value is decremented by the number of bytes
  * actually read.
- * @seeknull If true, decoding stops after the first U+0000 character found, if
- * there is any before maxread is reached
  * @returns 0 if no error occured, dst is uninitialized on error
  */
 static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
-                      uint8_t **dst, int *dstlen, int *maxread, const int seeknull)
+                      uint8_t **dst, int *maxread)
 {
-    int len, ret;
+    int ret;
     uint8_t tmp;
     uint32_t ch = 1;
     int left = *maxread;
@@ -112,7 +169,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
     switch (encoding) {
 
     case ID3v2_ENCODING_ISO8859:
-        while (left && (!seeknull || ch)) {
+        while (left && ch) {
             ch = avio_r8(pb);
             PUT_UTF8(ch, tmp, avio_w8(dynbuf, tmp);)
             left--;
@@ -122,7 +179,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
     case ID3v2_ENCODING_UTF16BOM:
         if ((left -= 2) < 0) {
             av_log(s, AV_LOG_ERROR, "Cannot read BOM value, input too short\n");
-            avio_close_dyn_buf(dynbuf, (uint8_t **)dst);
+            avio_close_dyn_buf(dynbuf, dst);
             av_freep(dst);
             return AVERROR_INVALIDDATA;
         }
@@ -133,7 +190,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
             break;
         default:
             av_log(s, AV_LOG_ERROR, "Incorrect BOM value\n");
-            avio_close_dyn_buf(dynbuf, (uint8_t **)dst);
+            avio_close_dyn_buf(dynbuf, dst);
             av_freep(dst);
             *maxread = left;
             return AVERROR_INVALIDDATA;
@@ -141,7 +198,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
         // fall-through
 
     case ID3v2_ENCODING_UTF16BE:
-        while ((left > 1) && (!seeknull || ch)) {
+        while ((left > 1) && ch) {
             GET_UTF16(ch, ((left -= 2) >= 0 ? get(pb) : 0), break;)
             PUT_UTF8(ch, tmp, avio_w8(dynbuf, tmp);)
         }
@@ -150,7 +207,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
         break;
 
     case ID3v2_ENCODING_UTF8:
-        while (left && (!seeknull || ch)) {
+        while (left && ch) {
             ch = avio_r8(pb);
             avio_w8(dynbuf, ch);
             left--;
@@ -163,10 +220,7 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
     if (ch)
         avio_w8(dynbuf, 0);
 
-    len = avio_close_dyn_buf(dynbuf, (uint8_t **)dst);
-    if (dstlen)
-        *dstlen = len;
-
+    avio_close_dyn_buf(dynbuf, dst);
     *maxread = left;
 
     return 0;
@@ -178,38 +232,40 @@ static int decode_str(AVFormatContext *s, AVIOContext *pb, int encoding,
 static void read_ttag(AVFormatContext *s, AVIOContext *pb, int taglen, const char *key)
 {
     uint8_t *dst;
-    const char *val = NULL;
-    int len, dstlen;
+    int encoding, dict_flags = AV_DICT_DONT_OVERWRITE;
     unsigned genre;
 
     if (taglen < 1)
         return;
 
+    encoding = avio_r8(pb);
     taglen--; /* account for encoding type byte */
 
-    if (decode_str(s, pb, avio_r8(pb), &dst, &dstlen, &taglen, 0) < 0) {
+    if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
         av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
         return;
     }
 
     if (!(strcmp(key, "TCON") && strcmp(key, "TCO"))
         && (sscanf(dst, "(%d)", &genre) == 1 || sscanf(dst, "%d", &genre) == 1)
-        && genre <= ID3v1_GENRE_MAX)
-        val = ff_id3v1_genre_str[genre];
-    else if (!(strcmp(key, "TXXX") && strcmp(key, "TXX"))) {
-        /* dst now contains two 0-terminated strings */
-        dst[dstlen] = 0;
-        len = strlen(dst);
+        && genre <= ID3v1_GENRE_MAX) {
+        av_freep(&dst);
+        dst = ff_id3v1_genre_str[genre];
+    } else if (!(strcmp(key, "TXXX") && strcmp(key, "TXX"))) {
+        /* dst now contains the key, need to get value */
         key = dst;
-        val = dst + FFMIN(len + 1, dstlen);
+        if (decode_str(s, pb, encoding, &dst, &taglen) < 0) {
+            av_log(s, AV_LOG_ERROR, "Error reading frame %s, skipped\n", key);
+            av_freep(&key);
+            return;
+        }
+        dict_flags |= AV_DICT_DONT_STRDUP_VAL | AV_DICT_DONT_STRDUP_KEY;
     }
     else if (*dst)
-        val = dst;
+        dict_flags |= AV_DICT_DONT_STRDUP_VAL;
 
-    if (val)
-        av_dict_set(&s->metadata, key, val, AV_DICT_DONT_OVERWRITE);
-
-    av_free(dst);
+    if (dst)
+        av_dict_set(&s->metadata, key, dst, dict_flags);
 }
 
 /**
@@ -242,17 +298,17 @@ static void read_geobtag(AVFormatContext *s, AVIOContext *pb, int taglen, char *
     taglen--;
 
     /* read MIME type (always ISO-8859) */
-    if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &geob_data->mime_type, NULL, &taglen, 1) < 0
+    if (decode_str(s, pb, ID3v2_ENCODING_ISO8859, &geob_data->mime_type, &taglen) < 0
         || taglen <= 0)
         goto fail;
 
     /* read file name */
-    if (decode_str(s, pb, encoding, &geob_data->file_name, NULL, &taglen, 1) < 0
+    if (decode_str(s, pb, encoding, &geob_data->file_name, &taglen) < 0
         || taglen <= 0)
         goto fail;
 
     /* read content description */
-    if (decode_str(s, pb, encoding, &geob_data->description, NULL, &taglen, 1) < 0
+    if (decode_str(s, pb, encoding, &geob_data->description, &taglen) < 0
         || taglen < 0)
         goto fail;
 
@@ -332,6 +388,18 @@ finish:
         av_dict_set(m, "date", date, 0);
 }
 
+typedef struct ID3v2EMFunc {
+    const char *tag3;
+    const char *tag4;
+    void (*read)(AVFormatContext*, AVIOContext*, int, char*, ID3v2ExtraMeta **);
+    void (*free)(void *obj);
+} ID3v2EMFunc;
+
+static const ID3v2EMFunc id3v2_extra_meta_funcs[] = {
+    { "GEO", "GEOB", read_geobtag, free_geobtag },
+    { NULL }
+};
+
 /**
  * Get the corresponding ID3v2EMFunc struct for a tag.
  * @param isv34 Determines if v2.2 or v2.3/4 strings are used
@@ -340,16 +408,15 @@ finish:
 static const ID3v2EMFunc *get_extra_meta_func(const char *tag, int isv34)
 {
     int i = 0;
-    while (ff_id3v2_extra_meta_funcs[i].tag3) {
+    while (id3v2_extra_meta_funcs[i].tag3) {
         if (!memcmp(tag,
-                    (isv34 ?
-                        ff_id3v2_extra_meta_funcs[i].tag4 :
-                        ff_id3v2_extra_meta_funcs[i].tag3),
+                    (isv34 ? id3v2_extra_meta_funcs[i].tag4 :
+                             id3v2_extra_meta_funcs[i].tag3),
                     (isv34 ? 4 : 3)))
-            return &ff_id3v2_extra_meta_funcs[i];
+            return &id3v2_extra_meta_funcs[i];
         i++;
     }
-    return &ff_id3v2_extra_meta_funcs[i];
+    return &id3v2_extra_meta_funcs[i];
 }
 
 static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t flags, ID3v2ExtraMeta **extra_meta)
@@ -364,7 +431,7 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
     AVIOContext *pbx;
     unsigned char *buffer = NULL;
     int buffer_size = 0;
-    void (*extra_func)(AVFormatContext*, AVIOContext*, int, char*, ID3v2ExtraMeta**) = NULL;
+    const ID3v2EMFunc *extra_func;
 
     switch (version) {
     case 2:
@@ -389,8 +456,22 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
 
     unsync = flags & 0x80;
 
-    if (isv34 && flags & 0x40) /* Extended header present, just skip over it */
-        avio_skip(s->pb, get_size(s->pb, 4));
+    /* Extended header present, just skip over it */
+    if (isv34 && flags & 0x40) {
+        int size = get_size(s->pb, 4);
+        if (size < 6) {
+            reason = "extended header too short.";
+            goto error;
+        }
+        len -= size;
+        if (len < 0) {
+            reason = "extended header too long.";
+            goto error;
+        }
+        /* already seeked past size, skip the reset */
+        size -= 4;
+        avio_skip(s->pb, size);
+    }
 
     while (len >= taghdrlen) {
         unsigned int tflags = 0;
@@ -410,7 +491,7 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
             tag[3] = 0;
             tlen = avio_rb24(s->pb);
         }
-        if (tlen > (1<<28) || !tlen)
+        if (tlen > (1<<28))
             break;
         len -= taghdrlen + tlen;
 
@@ -419,6 +500,12 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
 
         next = avio_tell(s->pb) + tlen;
 
+        if (!tlen) {
+            if (tag[0])
+                av_log(s, AV_LOG_DEBUG, "Invalid empty frame %s, skipping.\n", tag);
+            continue;
+        }
+
         if (tflags & ID3v2_FLAG_DATALEN) {
             if (tlen < 4)
                 break;
@@ -430,7 +517,7 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
             av_log(s, AV_LOG_WARNING, "Skipping encrypted/compressed ID3v2 frame %s.\n", tag);
             avio_skip(s->pb, tlen);
         /* check for text tag or supported special meta tag */
-        } else if (tag[0] == 'T' || (extra_meta && (extra_func = get_extra_meta_func(tag, isv34)->read))) {
+        } else if (tag[0] == 'T' || (extra_meta && (extra_func = get_extra_meta_func(tag, isv34)))) {
             if (unsync || tunsync) {
                 int i, j;
                 av_fast_malloc(&buffer, &buffer_size, tlen);
@@ -456,7 +543,7 @@ static void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t
                 read_ttag(s, pbx, tlen, tag);
             else
                 /* parse special meta tag */
-                extra_func(s, pbx, tlen, tag, extra_meta);
+                extra_func->read(s, pbx, tlen, tag, extra_meta);
         }
         else if (!tag[0]) {
             if (tag[1])
@@ -506,7 +593,7 @@ void ff_id3v2_read_all(AVFormatContext *s, const char *magic, ID3v2ExtraMeta **e
         }
     } while (found_header);
     ff_metadata_conv(&s->metadata, NULL, ff_id3v2_34_metadata_conv);
-    ff_metadata_conv(&s->metadata, NULL, ff_id3v2_2_metadata_conv);
+    ff_metadata_conv(&s->metadata, NULL, id3v2_2_metadata_conv);
     ff_metadata_conv(&s->metadata, NULL, ff_id3v2_4_metadata_conv);
     merge_date(&s->metadata);
 }
@@ -519,78 +606,13 @@ void ff_id3v2_read(AVFormatContext *s, const char *magic)
 void ff_id3v2_free_extra_meta(ID3v2ExtraMeta **extra_meta)
 {
     ID3v2ExtraMeta *current = *extra_meta, *next;
-    void (*free_func)(void *);
+    const ID3v2EMFunc *extra_func;
 
     while (current) {
-        if ((free_func = get_extra_meta_func(current->tag, 1)->free))
-            free_func(current->data);
+        if ((extra_func = get_extra_meta_func(current->tag, 1)))
+            extra_func->free(current->data);
         next = current->next;
         av_freep(&current);
         current = next;
     }
 }
-
-const ID3v2EMFunc ff_id3v2_extra_meta_funcs[] = {
-    { "GEO", "GEOB", read_geobtag, free_geobtag },
-    { NULL,  NULL,   NULL,         NULL }
-};
-
-const AVMetadataConv ff_id3v2_34_metadata_conv[] = {
-    { "TALB", "album"},
-    { "TCOM", "composer"},
-    { "TCON", "genre"},
-    { "TCOP", "copyright"},
-    { "TENC", "encoded_by"},
-    { "TIT2", "title"},
-    { "TLAN", "language"},
-    { "TPE1", "artist"},
-    { "TPE2", "album_artist"},
-    { "TPE3", "performer"},
-    { "TPOS", "disc"},
-    { "TPUB", "publisher"},
-    { "TRCK", "track"},
-    { "TSSE", "encoder"},
-    { 0 }
-};
-
-const AVMetadataConv ff_id3v2_4_metadata_conv[] = {
-    { "TDRL", "date"},
-    { "TDRC", "date"},
-    { "TDEN", "creation_time"},
-    { "TSOA", "album-sort"},
-    { "TSOP", "artist-sort"},
-    { "TSOT", "title-sort"},
-    { 0 }
-};
-
-const AVMetadataConv ff_id3v2_2_metadata_conv[] = {
-    { "TAL",  "album"},
-    { "TCO",  "genre"},
-    { "TT2",  "title"},
-    { "TEN",  "encoded_by"},
-    { "TP1",  "artist"},
-    { "TP2",  "album_artist"},
-    { "TP3",  "performer"},
-    { "TRK",  "track"},
-    { 0 }
-};
-
-
-const char ff_id3v2_tags[][4] = {
-   "TALB", "TBPM", "TCOM", "TCON", "TCOP", "TDLY", "TENC", "TEXT",
-   "TFLT", "TIT1", "TIT2", "TIT3", "TKEY", "TLAN", "TLEN", "TMED",
-   "TOAL", "TOFN", "TOLY", "TOPE", "TOWN", "TPE1", "TPE2", "TPE3",
-   "TPE4", "TPOS", "TPUB", "TRCK", "TRSN", "TRSO", "TSRC", "TSSE",
-   { 0 },
-};
-
-const char ff_id3v2_4_tags[][4] = {
-   "TDEN", "TDOR", "TDRC", "TDRL", "TDTG", "TIPL", "TMCL", "TMOO",
-   "TPRO", "TSOA", "TSOP", "TSOT", "TSST",
-   { 0 },
-};
-
-const char ff_id3v2_3_tags[][4] = {
-   "TDAT", "TIME", "TORY", "TRDA", "TSIZ", "TYER",
-   { 0 },
-};