[PATCH BlueZ v2 05/10] shared/util: Add str2utf8

Luiz Augusto von Dentz <[email protected]>
Newsgroups org.kernel.vger.linux-bluetooth
Message-ID <[email protected]>
From: Luiz Augusto von Dentz <[email protected]>

There are five near copies of the same "turn a remote name into a UTF-8
string" helper, in monitor/att.c, profiles/audio/mcp.c, profiles/gap/gas.c,
src/eir.c and src/shared/ad.c, and they do not agree with each other.

Most truncate at the first ill-formed sequence, which throws away the
rest of the name, while the monitor replaces every non-ASCII byte with a
space, which mangles perfectly valid UTF-8 names as soon as one bad byte
appears. Most also copy into a fixed size stack buffer first, which is
what made the missing clamp in src/eir.c a buffer overflow.

Add a single helper they can share. It allocates the result, so there is
no truncation to a buffer size, and replaces each ill-formed sequence
with U+FFFD REPLACEMENT CHARACTER rather than dropping the rest of the
string, matching what g_utf8_make_valid() and the WHATWG Encoding
Standard do.

The result has been checked byte for byte against Python's
bytes.decode('utf-8', errors='replace') over all one and two byte
sequences, a sample of the three byte ones and 200000 random inputs.

Assisted-by: Claude:claude-opus-5
---
 src/shared/util.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 src/shared/util.h |  7 +++++++
 2 files changed, 53 insertions(+)

diff --git a/src/shared/util.c b/src/shared/util.c
index e946214edbb9..8ec9b52e6401 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -2315,3 +2315,49 @@ char *strtoutf8(char *str, size_t len)
 	memset(str + i, 0, len - i);
 	return str;
 }
+
+char *str2utf8(const uint8_t *str, size_t len)
+{
+	char *utf8, *out, *stripped;
+	size_t i = 0;
+
+	if (!str)
+		return NULL;
+
+	/*
+	 * Invalid bytes are replaced with U+FFFD REPLACEMENT CHARACTER, which
+	 * is 3 bytes long, so that is the worst case size of the result.
+	 */
+	utf8 = malloc(len * 3 + 1);
+	if (!utf8)
+		return NULL;
+
+	out = utf8;
+
+	while (i < len) {
+		size_t sublen;
+		size_t size = utf8_seqlen(str + i, len - i, &sublen);
+
+		if (size) {
+			memcpy(out, str + i, size);
+			out += size;
+			i += size;
+			continue;
+		}
+
+		/* Replace the maximal subpart with U+FFFD */
+		*out++ = 0xef;
+		*out++ = 0xbf;
+		*out++ = 0xbd;
+		i += sublen;
+	}
+
+	*out = '\0';
+
+	/* Remove leading and trailing whitespace characters */
+	stripped = strstrip(utf8);
+	if (stripped != utf8)
+		memmove(utf8, stripped, strlen(stripped) + 1);
+
+	return utf8;
+}
diff --git a/src/shared/util.h b/src/shared/util.h
index 562a5af31751..1984fb75f09e 100644
--- a/src/shared/util.h
+++ b/src/shared/util.h
@@ -143,6 +143,13 @@ bool strisutf8(const char *str, size_t length);
 bool argsisutf8(int argc, char *argv[]);
 char *strtoutf8(char *str, size_t len);
 
+/*
+ * Return a newly allocated, NUL terminated and whitespace stripped UTF-8
+ * copy of the first len bytes of str, with each ill-formed sequence replaced
+ * by U+FFFD REPLACEMENT CHARACTER. The result must be freed with free().
+ */
+char *str2utf8(const uint8_t *str, size_t len);
+
 void *util_malloc(size_t size);
 void *util_memdup(const void *src, size_t size);
 
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.