This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch master
in repository terminology.

View the commit online.

commit 9e37a4f5a44d7e61abbf594f258e74027d23cbf0
Author: Cedric BAIL <[email protected]>
AuthorDate: Mon Aug 3 13:26:07 2026 -0600

    utf8: detect truncated sequences instead of guessing at them
    
    EFL's decoder reports an invalid byte and a byte cut off by the end of the
    buffer identically, by mapping the offender into the surrogate-escape range.
    The read loop guessed between the two on position alone: anything bad within
    four bytes of the end was assumed truncated and carried over.
    
    That is wrong in both directions. A genuinely invalid sequence near the end
    of a read was carried instead of reported, and since the carry was only
    replayed while bytes had the high bit set, a non-continuation byte stashed
    next to it was dropped. The same input then decoded differently depending on
    where the read boundaries fell -- feed a terminal one byte at a time and it
    renders something else.
    
    Decide from the bytes: a lead byte followed only by continuation bytes, with
    at least one still missing, is truncated. Anything else is invalid and is
    reported now rather than deferred.
    
    Co-Authored-By: Claude Opus 5 <[email protected]>
---
 src/bin/utf8.c | 57 ++++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 44 insertions(+), 13 deletions(-)

diff --git a/src/bin/utf8.c b/src/bin/utf8.c
index 4f674132..0a1a1610 100644
--- a/src/bin/utf8.c
+++ b/src/bin/utf8.c
@@ -1,6 +1,38 @@
 #include "private.h"
 #include "utf8.h"
 
+/* How many bytes the sequence introduced by this lead byte occupies, or 0 if it
+ * cannot start one (a continuation byte, or a length this decoder will not
+ * carry across a boundary). */
+static inline int
+_utf8_seq_len(unsigned char d)
+{
+   if (d < 0x80) return 1;
+   if ((d & 0xe0) == 0xc0) return 2;
+   if ((d & 0xf0) == 0xe0) return 3;
+   if ((d & 0xf8) == 0xf0) return 4;
+   return 0;
+}
+
+/* True when the 'avail' bytes at 'p' are the beginning of a longer sequence
+ * that has simply not arrived yet: a lead byte followed only by continuation
+ * bytes, with at least one still missing. */
+static Eina_Bool
+_utf8_truncated(const char *p, int avail)
+{
+   int need = _utf8_seq_len((unsigned char)p[0]);
+   int i;
+
+   if ((need < 2) || (need > UTF8_CARRY_MAX)) return EINA_FALSE;
+   if (avail >= need) return EINA_FALSE;
+
+   for (i = 1; i < avail; i++)
+     {
+        if (((unsigned char)p[i] & 0xc0) != 0x80) return EINA_FALSE;
+     }
+   return EINA_TRUE;
+}
+
 /* Decode UTF-8 bytes into codepoints.
  *
  * 'buf' must hold 'len' bytes and be NUL-terminated at buf[len], as
@@ -10,8 +42,12 @@
  *
  * Returns the number of codepoints written. '*consumed' gets the number of
  * input bytes decoded; anything left over is a multibyte sequence truncated by
- * the end of the buffer, which the caller carries over and re-submits in front
- * of the next chunk.
+ * the end of the buffer, which the caller is expected to carry over and
+ * re-submit in front of the next chunk. At most UTF8_CARRY_MAX - 1 bytes are
+ * ever left behind.
+ *
+ * Truncation is decided from the bytes themselves, so that where the read
+ * boundaries fall cannot change how the same input decodes.
  */
 int
 utf8_to_codepoints(const char *buf, int len, Eina_Unicode *codepoints,
@@ -25,18 +61,13 @@ utf8_to_codepoints(const char *buf, int len, Eina_Unicode *codepoints,
 
         if (buf[i])
           {
-             int prev_i = i;
-
+             /* Only the last few bytes can possibly hold a cut-off sequence, so
+              * lead with that test: it is false for all but the tail of the
+              * buffer and keeps this to one comparison per character. */
+             if (EINA_UNLIKELY((len - i) < UTF8_CARRY_MAX) &&
+                 _utf8_truncated(buf + i, len - i))
+               break;
              g = eina_unicode_utf8_next_get(buf, &i);
-             /* EFL maps invalid and truncated sequences alike into the
-              * surrogate-escape range; near the end of the buffer, assume
-              * truncation and hand the tail back to the caller. */
-             if ((0xdc80 <= g) && (g <= 0xdcff) &&
-                 ((len - prev_i) <= UTF8_CARRY_MAX))
-               {
-                  i = prev_i;
-                  break;
-               }
           }
         else
           {

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to