terminal/kitty-gfx: base64 decode data as it comes in

This commit is contained in:
Mitchell Hashimoto
2023-08-21 11:19:22 -07:00
parent 9c9a62bf3c
commit e56bc01c7e
2 changed files with 70 additions and 29 deletions

View File

@ -204,7 +204,7 @@ fn loadAndAddImage(
var img = if (storage.chunk) |chunk| img: { var img = if (storage.chunk) |chunk| img: {
// Note: we do NOT want to call "cmd.toOwnedData" here because // Note: we do NOT want to call "cmd.toOwnedData" here because
// we're _copying_ the data. We want the command data to be freed. // we're _copying_ the data. We want the command data to be freed.
try chunk.data.appendSlice(alloc, cmd.data); try chunk.addData(alloc, cmd.data);
// If we have more then we're done // If we have more then we're done
if (t.more_chunks) return chunk.image; if (t.more_chunks) return chunk.image;

View File

@ -47,6 +47,27 @@ pub const ChunkedImage = struct {
alloc.destroy(self); alloc.destroy(self);
} }
/// Adds a chunk of base64-encoded data to the image.
pub fn addData(self: *ChunkedImage, alloc: Allocator, data: []const u8) !void {
const Base64Decoder = std.base64.standard.Decoder;
// Grow our array list by size capacity if it needs it
const size = Base64Decoder.calcSizeForSlice(data) catch |err| {
log.warn("failed to calculate size for base64 data: {}", .{err});
return error.InvalidData;
};
try self.data.ensureUnusedCapacity(alloc, size);
// We decode directly into the arraylist
const start_i = self.data.items.len;
self.data.items.len = start_i + size;
const buf = self.data.items[start_i..];
Base64Decoder.decode(buf, data) catch |err| {
log.warn("failed to decode base64 data: {}", .{err});
return error.InvalidData;
};
}
/// Complete the chunked image, returning a completed image. /// Complete the chunked image, returning a completed image.
pub fn complete(self: *ChunkedImage, alloc: Allocator) !Image { pub fn complete(self: *ChunkedImage, alloc: Allocator) !Image {
var result = self.image; var result = self.image;
@ -146,29 +167,6 @@ pub const Image = struct {
if (self.width == 0 or self.height == 0) return error.DimensionsRequired; if (self.width == 0 or self.height == 0) return error.DimensionsRequired;
if (self.width > max_dimension or self.height > max_dimension) return error.DimensionsTooLarge; if (self.width > max_dimension or self.height > max_dimension) return error.DimensionsTooLarge;
// The data is base64 encoded, we must decode it.
var decoded = decoded: {
const Base64Decoder = std.base64.standard.Decoder;
const size = Base64Decoder.calcSizeForSlice(self.data) catch |err| {
log.warn("failed to calculate base64 decoded size: {}", .{err});
return error.InvalidData;
};
var buf = try alloc.alloc(u8, size);
errdefer alloc.free(buf);
Base64Decoder.decode(buf, self.data) catch |err| {
log.warn("failed to decode base64 data: {}", .{err});
return error.InvalidData;
};
break :decoded buf;
};
// After decoding, we swap the data immediately and free the old.
// This will ensure that we never leak memory.
alloc.free(self.data);
self.data = decoded;
// Decompress the data if it is compressed. // Decompress the data if it is compressed.
try self.decompress(alloc); try self.decompress(alloc);
@ -192,28 +190,53 @@ pub const Image = struct {
pub fn load(alloc: Allocator, cmd: *command.Command) !Image { pub fn load(alloc: Allocator, cmd: *command.Command) !Image {
const t = cmd.transmission().?; const t = cmd.transmission().?;
// We must have data to load an image
if (cmd.data.len == 0) return error.InvalidData;
// Load the data // Load the data
const data = switch (t.medium) { const raw_data = switch (t.medium) {
.direct => cmd.data, .direct => direct: {
const data = cmd.data;
_ = cmd.toOwnedData();
break :direct data;
},
else => { else => {
std.log.warn("unimplemented medium={}", .{t.medium}); std.log.warn("unimplemented medium={}", .{t.medium});
return error.UnsupportedMedium; return error.UnsupportedMedium;
}, },
}; };
// We always free the raw data because it is base64 decoded below
defer alloc.free(raw_data);
// We base64 the data immediately
const decoded_data = base64Decode(alloc, raw_data) catch |err| {
log.warn("failed to calculate base64 decoded size: {}", .{err});
return error.InvalidData;
};
// If we loaded an image successfully then we take ownership // If we loaded an image successfully then we take ownership
// of the command data and we need to make sure to clean up on error. // of the command data and we need to make sure to clean up on error.
_ = cmd.toOwnedData(); errdefer if (decoded_data.len > 0) alloc.free(decoded_data);
errdefer if (data.len > 0) alloc.free(data);
const img = switch (t.format) { const img = switch (t.format) {
.rgb, .rgba => try loadPacked(t, data), .rgb, .rgba => try loadPacked(t, decoded_data),
else => return error.UnsupportedFormat, else => return error.UnsupportedFormat,
}; };
return img; return img;
} }
/// Read the temporary file data from a command. This will also DELETE
/// the temporary file if it is successful and the temporary file is
/// in a safe, well-known location.
fn readTemporaryFile(alloc: Allocator, path: []const u8) ![]const u8 {
_ = alloc;
_ = path;
return "";
}
/// Load a package image format, i.e. RGB or RGBA. /// Load a package image format, i.e. RGB or RGBA.
fn loadPacked( fn loadPacked(
t: command.Transmission, t: command.Transmission,
@ -246,6 +269,24 @@ pub const Image = struct {
} }
}; };
/// Helper to base64 decode some data. No data is freed.
fn base64Decode(alloc: Allocator, data: []const u8) ![]const u8 {
const Base64Decoder = std.base64.standard.Decoder;
const size = Base64Decoder.calcSizeForSlice(data) catch |err| {
log.warn("failed to calculate base64 decoded size: {}", .{err});
return error.InvalidData;
};
var buf = try alloc.alloc(u8, size);
errdefer alloc.free(buf);
Base64Decoder.decode(buf, data) catch |err| {
log.warn("failed to decode base64 data: {}", .{err});
return error.InvalidData;
};
return buf;
}
/// Loads test data from a file path and base64 encodes it. /// Loads test data from a file path and base64 encodes it.
fn testB64(alloc: Allocator, data: []const u8) ![]const u8 { fn testB64(alloc: Allocator, data: []const u8) ![]const u8 {
const B64Encoder = std.base64.standard.Encoder; const B64Encoder = std.base64.standard.Encoder;