ref: 1a9591bd4f932a6ca2fba0eb84cb6b790c8c7250
parent: 78aa2362a55bfb147a995a7df2ec5691ef280aa8
author: Sigrid Solveig Haflínudóttir <sigrid@ftrv.se>
date: Thu Mar 14 17:32:17 EDT 2024
vorbis: support METADATA_BLOCK_PICTURE; don't stop on tags too large, truncate and skip instead
--- /dev/null
+++ b/base64.c
@@ -1,0 +1,67 @@
+#include "tagspriv.h"
+
+static const uint8_t d[] = {
+ 66,66,66,66,66,66,66,66,66,66,64,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,62,66,66,66,63,52,53,
+ 54,55,56,57,58,59,60,61,66,66,66,65,66,66,66, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
+ 10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,66,66,66,66,66,66,26,27,28,
+ 29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,66,
+ 66,66,66,66,66,66
+};
+enum {
+ B64_WHITESPACE = 64,
+ B64_EQUALS,
+ B64_INVALID,
+};
+
+int
+debase64(uint8_t *in, int insz, uint8_t *out, int outsz)
+{
+ uint8_t *end = in + insz;
+ uint8_t iter = 0;
+ uint32_t buf = 0;
+ int len = 0;
+
+ while(in < end){
+ uint8_t c = d[*in++];
+
+ switch(c){
+ case B64_WHITESPACE:
+ continue;
+ case B64_INVALID:
+ return -1;
+ case B64_EQUALS:
+ in = end;
+ continue;
+ default:
+ buf = buf << 6 | c;
+ if(++iter == 4){
+ if((len += 3) > outsz)
+ return -1;
+ *out++ = (buf >> 16) & 0xff;
+ *out++ = (buf >> 8) & 0xff;
+ *out++ = buf & 0xff;
+ buf = 0;
+ iter = 0;
+ }
+ }
+ }
+
+ if(iter == 3){
+ if((len += 2) > outsz)
+ return -1;
+ *out++ = (buf >> 10) & 0xff;
+ *out++ = (buf >> 2) & 0xff;
+ }else if(iter == 2){
+ if(++len > outsz)
+ return -1;
+ *out++ = (buf >> 4) & 0xff;
+ }
+
+ return len;
+}
--- a/meson.build
+++ b/meson.build
@@ -27,6 +27,7 @@
src_lib = [
'437.c',
'8859.c',
+ 'base64.c',
'flac.c',
'id3genres.c',
'id3v1.c',
--- a/tagspriv.h
+++ b/tagspriv.h
@@ -57,6 +57,16 @@
*/
void cbvorbiscomment(Tagctx *ctx, char *k, char *v);
+/*
+ * Used to decode base64-encoded picture block.
+ */
+int debase64(uint8_t *in, int insz, uint8_t *out, int outsz);
+
+/*
+ * METADATA_BLOCK_PICTURE reader function.
+ */
+int mbpdec(void *buf, int *cnt);
+
void tagscallcb(Tagctx *ctx, int type, const char *k, char *s, int offset, int size, Tagread f);
#define txtcb(ctx, type, k, s) tagscallcb(ctx, type, k, (char*)s, 0, 0, nil)
--- a/vorbis.c
+++ b/vorbis.c
@@ -27,7 +27,8 @@
};
void
-cbvorbiscomment(Tagctx *ctx, char *k, char *v){
+cbvorbiscomment(Tagctx *ctx, char *k, char *v)
+{
int i;
if(*v == 0)
@@ -43,11 +44,44 @@
}
int
+mbpdec(void *buf, int *cnt)
+{
+ int sz, n;
+ uint8_t *v;
+
+ v = buf;
+ if((n = debase64(v, *cnt, v, *cnt)) <= 0)
+ return -1;
+
+ beuint(v); /* id3v2 APIC type */
+ v += 4; n -= 4;
+ sz = beuint(v); /* mime size */
+ v += 4; n -= 4;
+ if(sz < 0 || sz >= n-4-4-4-4-4-4)
+ return -1;
+ v += sz; n -= sz; /* skip MIME */
+ sz = beuint(v); /* description size */
+ v += 4; n -= 4;
+ if(sz < 0 || sz >= n-4-4-4-4-4)
+ return -1;
+ v += sz; n -= sz; /* skip description */
+ v += 4+4+4+4; n -= 4+4+4+4; /* skip width, height, depth, palette info */
+ sz = beuint(v); /* picture size */
+ v += 4; n -= 4;
+ if(sz <= 0 || sz > n)
+ return -1;
+ memmove(buf, v, sz);
+ *cnt = sz;
+
+ return 0;
+}
+
+int
tagvorbis(Tagctx *ctx)
{
- char *v;
+ char *v, *mime;
uint8_t *d, h[4];
- int sz, numtags, i, npages, pgend;
+ int sz, picsz, numtags, i, npages, pgend, skip, off, n;
d = (uint8_t*)ctx->buf;
/* need to find vorbis frame with type=3 */
@@ -99,11 +133,12 @@
if(pgend < ctx->seek(ctx, 0, 1)+sz)
break;
+ skip = 0;
if(sz > ctx->bufsz-1){
- if(ctx->seek(ctx, sz, 1) < 0)
- return -1;
- continue;
+ skip = sz - (ctx->bufsz-1);
+ sz -= skip;
}
+
if(ctx->read(ctx, ctx->buf, sz) != sz)
return -1;
ctx->buf[sz] = 0;
@@ -111,7 +146,36 @@
if((v = strchr(ctx->buf, '=')) == nil)
return -1;
*v++ = 0;
- cbvorbiscomment(ctx, ctx->buf, v);
+ if(strcasecmp(ctx->buf, "metadata_block_picture") != 0)
+ cbvorbiscomment(ctx, ctx->buf, v);
+ else{
+ /* off and picsz will point at the base64-encoded picture block */
+ off = ctx->seek(ctx, 0, 1) - sz + (v - ctx->buf);
+ picsz = sz + skip - (v - ctx->buf);
+ n = sz - (v - ctx->buf); /* at most this amount is available */
+ n &= ~3; /* modulo 4 sextets, so debase64 gets complete bytes */
+ n = debase64((uint8_t*)v, n, (uint8_t*)ctx->buf, ctx->bufsz);
+ /* https://xiph.org/flac/format.html#metadata_block_picture */
+ if(n > 4+4+0+4+0+4+4+4+4+4+0){
+ v = ctx->buf;
+ beuint(v); /* id3v2 APIC type */
+ v += 4; n -= 4;
+ sz = beuint(v); /* mime size */
+ v += 4; n -= 4;
+ if(sz < 0 || sz >= n-4-4-4-4-4-4)
+ return -1;
+ mime = v;
+ v += sz; n -= sz; /* skip MIME */
+ sz = beuint(v); /* description size */
+ v += 4; n -= 4;
+ if(sz < 0 || sz >= n-4-4-4-4-4)
+ return -1;
+ *v = 0; /* null-terminate MIME */
+ tagscallcb(ctx, Timage, "", mime, off, picsz, mbpdec);
+ }
+ }
+ if(ctx->seek(ctx, skip, 1) < 0)
+ return -1;
}
}