Accept independently padded base64 chunks in Kitty image transfers.

This commit is contained in:
Michael Grant
2026-10-08 22:48:42 +02:00
parent bd2852e45d
commit 00e3103a48
2 changed files with 65 additions and 14 deletions

View File

@@ -93,8 +93,8 @@ struct kitty_state {
u_int data_size;
int more;
char *encoded;
size_t encodedlen;
u_char *raw;
size_t rawlen;
};
struct kitty_placement {
@@ -664,7 +664,7 @@ kitty_state_free(struct kitty_state *ks)
{
if (ks == NULL)
return;
free(ks->encoded);
free(ks->raw);
free(ks);
}
@@ -892,17 +892,50 @@ kitty_source_get(struct kitty_context *kc, u_int id)
return (im);
}
/* Append encoded payload data to a Kitty graphics command. */
/* Decode and append one base64-encoded Kitty payload chunk. */
static int
kitty_append(struct kitty_state *ks, const u_char *buf, size_t len)
{
if (len > IMAGE_SIZE_LIMIT ||
ks->encodedlen > IMAGE_SIZE_LIMIT - len)
static const char base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
u_char *decoded;
char *copy = NULL;
const char *digit;
size_t decodedlen, offset;
u_int mask;
if (len > IMAGE_SIZE_LIMIT)
return (-1);
ks->encoded = xrealloc(ks->encoded, ks->encodedlen + len + 1);
memcpy(ks->encoded + ks->encodedlen, buf, len);
ks->encodedlen += len;
ks->encoded[ks->encodedlen] = '\0';
/* Older Chafa chunks have padding with nonzero unused bits. */
if (len >= 4 && len % 4 == 0 && buf[len - 1] == '=') {
offset = len - 2;
mask = 0x3c;
if (buf[offset] == '=') {
offset--;
mask = 0x30;
}
digit = strchr(base64, buf[offset]);
if (digit == NULL)
return (-1);
copy = xmalloc(len);
memcpy(copy, buf, len);
copy[offset] = base64[(digit - base64) & mask];
buf = (const u_char *)copy;
}
decoded = image_base64_decode((const char *)buf, len,
IMAGE_SIZE_LIMIT, &decodedlen);
free(copy);
if (decoded == NULL)
return (-1);
if (decodedlen > IMAGE_SIZE_LIMIT - ks->rawlen) {
free(decoded);
return (-1);
}
ks->raw = xrealloc(ks->raw, ks->rawlen + decodedlen);
memcpy(ks->raw + ks->rawlen, decoded, decodedlen);
ks->rawlen += decodedlen;
free(decoded);
return (0);
}
@@ -1167,10 +1200,9 @@ kitty_parse_image(void **state, const u_char *buf, size_t len, u_int xpixel,
if (ks->action != 'T' && ks->action != 't' && ks->action != 'q')
goto fail;
decoded = image_base64_decode(ks->encoded, ks->encodedlen,
IMAGE_SIZE_LIMIT, &decodedlen);
if (decoded == NULL)
goto fail;
decoded = ks->raw;
decodedlen = ks->rawlen;
ks->raw = NULL;
if (ks->format == 100) {
if (ks->compression == 'z') {
if (ks->data_size == 0 ||

View File

@@ -28,6 +28,25 @@ sleep 1
[ "$($TMUX display-message -p '#{image_support}')" = 0 ] && exit 0
[ "$($TMUX display-message -p '#{sixel_support}')" = 1 ] || exit 1
# Older Chafa chunks are independently padded, with nonzero unused bits.
$TMUX new-window -d -n kitty-chunks "
printf '\033_Ga=T,q=2,f=32,s=1,v=1,c=1,r=1,m=1;/wD=\033\\'
printf '\033_Gm=1;AP/=\033\\'
printf '\033_Gm=0\033\\'
sleep 10"
sleep 1
[ "$($TMUX display-message -pt:kitty-chunks '#{cursor_y}')" = 1 ] || exit 1
$TMUX kill-window -t:kitty-chunks || exit 1
# Accept two padding characters too, with pixel data in the final chunk.
$TMUX new-window -d -n kitty-chunks "
printf '\033_Ga=T,q=2,f=32,s=1,v=1,c=1,r=1,m=1;/x==\033\\'
printf '\033_Gm=0;AAD/\033\\'
sleep 10"
sleep 1
[ "$($TMUX display-message -pt:kitty-chunks '#{cursor_y}')" = 1 ] || exit 1
$TMUX kill-window -t:kitty-chunks || exit 1
# Images scroll as grid cells, while capture output contains ordinary spaces.
[ "$($TMUX display-message -p '#{history_size}')" -gt 0 ] || exit 1
$TMUX capture-pane -pS- >$TMP || exit 1