mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
terminal/kitty-gfx: base64 decode data as it comes in
This commit is contained in:
@ -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;
|
||||||
|
@ -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;
|
||||||
|
Reference in New Issue
Block a user