fortrangoingonforty/fgof-screen / 883069f

Browse files

Harden screen rendering

Authored by espadonne
SHA
883069f8b736cffb013b625da8fed7b9b6876247
Parents
7946628
Tree
9ebdb03

4 changed files

StatusFile+-
M README.md 4 1
M src/fgof_screen.f90 16 3
M test/test_screen_render_ansi.f90 3 2
M test/test_screen_render_edges.f90 16 0
README.mdmodified
@@ -38,6 +38,8 @@ Tracked today:
3838
 - explicit cursor-state ANSI output
3939
 - tracked example programs for full-frame and diff rendering
4040
 - render-edge coverage for empty screens and cursor-only diffs
41
+- full-frame ANSI output with explicit row addressing
42
+- render-time sanitization for non-printable control glyphs
4143
 - CI on macOS and Ubuntu, including direct example execution
4244
 - row-first, column-second grid semantics pinned down in tests
4345
 - focused scaffold coverage in `fpm test`
@@ -92,10 +94,11 @@ Current semantics:
9294
 - `diff_screen(previous, current)` compares two virtual frames and reports cell damage separately from size or cursor changes
9395
 - `screen_diff%damage` uses one bounding rectangle plus a `changed_cells` count for the changed frame area
9496
 - newly added or removed visible cells from resizes count as changed damage even when their glyphs are blank
95
-- `render_screen_ansi()` emits a whole-frame ANSI repaint with clear/home semantics and final cursor restoration
97
+- `render_screen_ansi()` emits a whole-frame ANSI repaint with clear/home semantics, explicit row addressing, and final cursor restoration
9698
 - `render_screen_diff_ansi()` emits a damage-scoped ANSI repaint and uses blank cells to erase removed content after shrinks
9799
 - `render_cursor_ansi()` emits the final cursor move plus visibility state for a screen buffer
98100
 - ANSI row rendering tracks style transitions explicitly and resets back to default style when needed
101
+- ANSI renderers replace ASCII control glyphs with `?` so screen cells cannot inject raw control bytes into the output stream
99102
 
100103
 ## Build And Test
101104
 
src/fgof_screen.f90modified
@@ -247,8 +247,8 @@ contains
247247
 
248248
     if (allocated(buffer%cells)) then
249249
       do row = 1, size(buffer%cells, 1)
250
+        output = output // move_cursor_ansi(row, 1)
250251
         output = output // render_current_row_ansi(buffer, row, 1, size(buffer%cells, 2))
251
-        if (row < size(buffer%cells, 1)) output = output // new_line("a")
252252
       end do
253253
     end if
254254
 
@@ -415,7 +415,7 @@ contains
415415
         end if
416416
         current_key = cell_key
417417
       end if
418
-      output = output // cell%glyph
418
+      output = output // renderable_glyph(cell%glyph)
419419
     end do
420420
 
421421
     if (len(current_key) > 0) then
@@ -449,7 +449,7 @@ contains
449449
         end if
450450
         current_key = cell_key
451451
       end if
452
-      output = output // cell%glyph
452
+      output = output // renderable_glyph(cell%glyph)
453453
     end do
454454
 
455455
     if (len(current_key) > 0) then
@@ -567,4 +567,17 @@ contains
567567
     text = trim(scratch)
568568
   end function integer_text
569569
 
570
+  function renderable_glyph(glyph) result(output)
571
+    character(len=1), intent(in) :: glyph
572
+    character(len=1) :: output
573
+    integer :: code
574
+
575
+    code = iachar(glyph)
576
+    if (code < 32 .or. code == 127) then
577
+      output = "?"
578
+    else
579
+      output = glyph
580
+    end if
581
+  end function renderable_glyph
582
+
570583
 end module fgof_screen
test/test_screen_render_ansi.f90modified
@@ -28,9 +28,10 @@ program test_screen_render_ansi
2828
   call set_cursor(previous, 2, 1)
2929
 
3030
   expected = esc // "?25l" // esc // "2J" // esc // "H" // &
31
-             "AB" // new_line("a") // "CD" // esc // "0m" // esc // "2;1H" // esc // "?25h"
31
+             esc // "1;1H" // "AB" // esc // "2;1H" // "CD" // &
32
+             esc // "0m" // esc // "2;1H" // esc // "?25h"
3233
   rendered = render_screen_ansi(previous)
33
-  if (rendered /= expected) error stop "render_screen_ansi should render a full blank-style frame deterministically"
34
+  if (rendered /= expected) error stop "render_screen_ansi should use explicit row addressing for full-frame repaints"
3435
 
3536
   style = clear_screen_style()
3637
   style%fg = 33
test/test_screen_render_edges.f90modified
@@ -1,6 +1,7 @@
11
 program test_screen_render_edges
22
   use fgof_screen, only : &
33
     allocate_screen, &
4
+    put_glyph, &
45
     render_screen_ansi, &
56
     render_screen_diff_ansi, &
67
     set_cursor
@@ -35,4 +36,19 @@ program test_screen_render_edges
3536
   rendered = render_screen_diff_ansi(previous, current)
3637
   expected = esc // "?25l"
3738
   if (rendered /= expected) error stop "cursor-visibility-only diffs should render only the visibility change"
39
+
40
+  current = allocate_screen(2, 1)
41
+  call put_glyph(current, 1, 1, achar(27))
42
+  call put_glyph(current, 1, 2, new_line("a"))
43
+  rendered = render_screen_ansi(current)
44
+  expected = esc // "?25l" // esc // "2J" // esc // "H" // &
45
+             esc // "1;1H" // "??" // esc // "0m" // esc // "1;1H" // esc // "?25h"
46
+  if (rendered /= expected) error stop "full renders should sanitize control glyphs before emitting ANSI output"
47
+
48
+  previous = allocate_screen(2, 1)
49
+  current = previous
50
+  call put_glyph(current, 1, 2, achar(9))
51
+  rendered = render_screen_diff_ansi(previous, current)
52
+  expected = esc // "?25l" // esc // "1;2H" // "?" // esc // "0m" // esc // "1;1H" // esc // "?25h"
53
+  if (rendered /= expected) error stop "diff renders should sanitize control glyphs before emitting ANSI output"
3854
 end program test_screen_render_edges