diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-19 07:55:53 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-19 07:55:53 +0200 |
| commit | 7228d0634a960c38da96b80378ec617768dfedf7 (patch) | |
| tree | 8b2f67c185eb87d28d63ca231082596014667bb2 /lib | |
| parent | eff4b89cff4e1b8fcceb23c54cb62ec636ce62fe (diff) | |
| download | colitur-7228d0634a960c38da96b80378ec617768dfedf7.tar.gz colitur-7228d0634a960c38da96b80378ec617768dfedf7.zip | |
fix(render): make fold_ics total on arbitrary octet strings
fold_ics's UTF-8 backoff loop could back `cut` all the way down to
`pos` on 74+ consecutive continuation bytes (0x80-0xBF), producing a
zero-length chunk and recursing on the identical position forever --
not producible by valid UTF-8, whose longest continuation run is 3,
but the kernel's own totality requirement covers arbitrary octet
strings, not only valid ones. When backoff finds no boundary inside
the window, cut hard at the limit instead, so forward progress is
unconditional.
test_fold_never_splits_utf8 previously asserted only that unfolding
reproduced the original bytes, a property folding preserves at any
cut position and therefore blind to a boundary violation. It now also
asserts the named property directly: no continuation chunk may start
with a UTF-8 continuation byte. A new regression test feeds fold_ics
100 consecutive continuation bytes and asserts it terminates with
every line at or under 75 octets.
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/render/escape.ml | 8 |
1 files changed, 8 insertions, 0 deletions
diff --git a/lib/render/escape.ml b/lib/render/escape.ml index 53fdd46..f506c44 100644 --- a/lib/render/escape.ml +++ b/lib/render/escape.ml @@ -77,6 +77,14 @@ let fold_ics line = else begin let cut = ref (pos + limit) in while !cut > pos && is_cont line.[!cut] do decr cut done; + (* Valid UTF-8's longest continuation run is 3, so backoff finds a + boundary within a few bytes. But this function must stay TOTAL on + ARBITRARY octet strings, not only valid UTF-8: 74+ consecutive + continuation bytes back `cut` all the way down to `pos`, which would + yield a zero-length chunk and recurse on the identical position + forever. When backoff finds no boundary inside the window, cut hard + at the limit instead -- forward progress is then unconditional. *) + if !cut = pos then cut := pos + limit; if not first then Buffer.add_char b ' '; Buffer.add_string b (String.sub line pos (!cut - pos)); Buffer.add_string b "\r\n"; |
