shithub: libtags

Download patch

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;
 		}
 	}