First commit. Version 0.1.0

This commit is contained in:
2026-05-10 09:58:23 +02:00
parent af105b7f7d
commit bbab5e238d
635 changed files with 53627 additions and 175 deletions

112
tests/security_upload.rs Normal file
View File

@@ -0,0 +1,112 @@
// Defensive coverage for the image-upload boundary. The GLES path requires a
// live GL context (out of scope for these CPU-only tests) so the assertions
// below exercise the software backend, which shares the same length-vs-
// dimensions check before delegating to tiny-skia. Both paths:
//
// * refuse the upload when `data.len() != width * height * 4`,
// * refuse zero-area uploads (`width == 0` or `height == 0`),
// * never panic — they log to stderr and return without drawing.
use std::sync::Arc;
use ltk::core::{ RenderOptions, UiSurface };
use ltk::{ column, img_widget, Color, Element };
fn render_image( rgba: Arc<Vec<u8>>, w: u32, h: u32 ) -> UiSurface<()>
{
let mut surface = UiSurface::<()>::new( 64, 64 );
let view: Element<()> = column()
.padding( 0.0 )
.push( img_widget( rgba, w, h ) )
.into();
let _ = surface.render(
&view,
RenderOptions::full_canvas( 64, 64 ).background( Color::BLACK ),
);
surface
}
#[ test ]
fn declared_size_matching_buffer_renders_without_panic()
{
// Sanity baseline: a 4×4 image with the expected 64-byte buffer must
// render through the path that the validation tests below exercise.
let bytes = Arc::new( vec![ 0xFFu8; 4 * 4 * 4 ] );
let _ = render_image( bytes, 4, 4 );
}
#[ test ]
fn shorter_buffer_is_rejected_without_panic()
{
// 10×10 declared but only 16 bytes provided. Without the boundary
// check tiny-skia would walk past the slice end inside its premul
// loop. With the check the renderer logs once and skips the draw.
let bytes = Arc::new( vec![ 0u8; 16 ] );
let _ = render_image( bytes, 10, 10 );
}
#[ test ]
fn longer_buffer_is_rejected_without_panic()
{
// 2×2 declared, 256 bytes provided. The buffer is larger than the
// driver would read, but the size mismatch still fails the strict
// equality and the draw is refused.
let bytes = Arc::new( vec![ 0u8; 256 ] );
let _ = render_image( bytes, 2, 2 );
}
#[ test ]
fn zero_width_image_does_not_blit()
{
// IntSize::from_wh rejects zero dimensions on tiny-skia, but the
// boundary check fires earlier for a uniform error path on both
// backends.
let bytes = Arc::new( vec![] );
let _ = render_image( bytes, 0, 10 );
}
#[ test ]
fn zero_height_image_does_not_blit()
{
let bytes = Arc::new( vec![] );
let _ = render_image( bytes, 10, 0 );
}
#[ test ]
fn non_multiple_of_four_buffer_is_rejected()
{
// 1×1 should be 4 bytes; 3 bytes is shorter and not a multiple of 4 —
// every byte counts.
let bytes = Arc::new( vec![ 0xFF, 0xFF, 0xFF ] );
let _ = render_image( bytes, 1, 1 );
}
#[ test ]
fn dimensions_overflow_usize_are_rejected_without_panic()
{
// `u32::MAX × u32::MAX × 4` overflows. The boundary check uses
// `saturating_mul` (or i64-promotion in the GLES path) so the
// expected length never wraps to a small number that happens to
// match a real buffer length. The provided buffer is small, so the
// equality fails and the draw is refused.
let bytes = Arc::new( vec![ 0u8; 16 ] );
let _ = render_image( bytes, u32::MAX, u32::MAX );
}
#[ test ]
fn renderer_recovers_after_a_rejected_upload()
{
// A frame with a malformed image must not poison the surface. The
// next render with a valid view succeeds.
let bad = Arc::new( vec![ 0u8; 5 ] );
let mut s = render_image( bad, 4, 4 );
let view: Element<()> = column().padding( 0.0 ).into();
let out = s.render(
&view,
RenderOptions::full_canvas( 64, 64 ).background( Color::rgb( 0.0, 1.0, 0.0 ) ),
);
// Tree shape change → full redraw, but the important check is that
// render() returned at all.
let _ = out;
}