Reduce MAX_CONTROL_DATA_SIZE + EINVAL for i and I

This commit is contained in:
Anthony Kim
2026-02-02 20:17:21 -08:00
parent ae58176bcb
commit 5f724c5924
3 changed files with 49 additions and 1 deletions
@@ -30,7 +30,7 @@ const BASE64_SHARD_SIZE = 1048576;
const DECODED_SHARD_SIZE = 786432;
// Maximum control data size
const MAX_CONTROL_DATA_SIZE = 4096;
const MAX_CONTROL_DATA_SIZE = 512;
// Semicolon codepoint
const SEMICOLON = 0x3B;
@@ -211,6 +211,13 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler {
// Parse command to check m=1 and get pending key
const cmd = parseKittyCommand(this._parseControlDataString());
// Per spec: specifying both i and I is an error
if (cmd.id !== undefined && cmd.imageNumber !== undefined) {
this._sendResponse(cmd.id, 'EINVAL:cannot specify both i and I keys', cmd.quiet ?? 0);
return true;
}
const pendingKey = cmd.id ?? 0;
const isMoreComing = cmd.more === 1;
const pending = this._pendingTransmissions.get(pendingKey);
@@ -381,6 +388,13 @@ export class KittyGraphicsHandler implements IApcHandler, IResetHandler {
private _handleNoPayloadCommand(): boolean | Promise<boolean> {
const cmd = parseKittyCommand(this._parseControlDataString());
// Per spec: specifying both i and I is an error
if (cmd.id !== undefined && cmd.imageNumber !== undefined) {
this._sendResponse(cmd.id, 'EINVAL:cannot specify both i and I keys', cmd.quiet ?? 0);
return true;
}
const action = cmd.action ?? 't';
switch (action) {
@@ -47,6 +47,8 @@ export const enum KittyKey {
FORMAT = 'f',
// Image ID for referencing stored images
ID = 'i',
// Image number (alternative to ID, terminal assigns ID)
IMAGE_NUMBER = 'I',
// Source image width in pixels
WIDTH = 's',
// Source image height in pixels
@@ -79,6 +81,7 @@ export interface IKittyCommand {
action?: string;
format?: number;
id?: number;
imageNumber?: number;
width?: number;
height?: number;
x?: number;
@@ -148,6 +151,7 @@ export function parseKittyCommand(data: string): IKittyCommand {
switch (key) {
case KittyKey.FORMAT: cmd.format = numValue; break;
case KittyKey.ID: cmd.id = numValue; break;
case KittyKey.IMAGE_NUMBER: cmd.imageNumber = numValue; break;
case KittyKey.WIDTH: cmd.width = numValue; break;
case KittyKey.HEIGHT: cmd.height = numValue; break;
case KittyKey.X_OFFSET: cmd.x = numValue; break;
@@ -504,6 +504,36 @@ test.describe('ImageAddon', () => {
strictEqual(await ctx.page.evaluate('window.kittyGotResponse'), false);
});
test('responds with EINVAL when both i and I keys are specified', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
});
// Per spec: "Specifying both i and I keys in any command is an error"
await ctx.proxy.write(`\x1b_Gi=100,I=200,a=q,f=100;${KITTY_BLACK_1X1_BASE64}\x1b\\`);
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=100;EINVAL:cannot specify both i and I keys\x1b\\');
});
test('responds with EINVAL for i+I conflict even without payload', async () => {
let response = '';
await ctx.page.evaluate(() => {
(window as any).kittyResponse = '';
(window as any).term.onData((data: string) => { (window as any).kittyResponse = data; });
});
// Delete command with both i and I (no payload case)
await ctx.proxy.write('\x1b_Gi=101,I=201,a=d\x1b\\');
await timeout(100);
response = await ctx.page.evaluate('window.kittyResponse');
strictEqual(response, '\x1b_Gi=101;EINVAL:cannot specify both i and I keys\x1b\\');
});
});
test.describe('Kitty pixel verification', () => {