blob: a1883c6a3722a622513962f3c6bb3958107c3aea [file]
/*****************************************************************************\
* xutf.c - Unicode handler
*****************************************************************************
* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <stddef.h>
#include "src/common/read_config.h"
#include "src/common/xassert.h"
#include "src/common/xstring.h"
#include "src/common/xutf.h"
/* The code point and byte checks below rely on the signedness and width here */
_Static_assert((utf_code_t) -1 > 0, "utf_code_t must be unsigned");
_Static_assert(sizeof(utf_code_t) == 4, "utf_code_t must be 32 bits");
_Static_assert((utf8_t) -1 > 0, "utf8_t must be unsigned");
_Static_assert(sizeof(utf8_t) == 1, "utf8_t must be 8 bits");
/*
* Alias the non-prefixed exported functions to slurm_-prefixed names so the
* serializer plugin links against them via slurm_xlator.h. The macro-backed
* helpers are already slurm_-prefixed and need no alias.
*/
strong_alias(utf_encoding_scheme_to_string,
slurm_utf_encoding_scheme_to_string);
strong_alias(utf8_strlen, slurm_utf8_strlen);
strong_alias(utf8_ndup, slurm_utf8_ndup);
strong_alias(utf8_dup, slurm_utf8_dup);
strong_alias(utf_read_encoding_schema, slurm_utf_read_encoding_schema);
strong_alias(utf8_get_loggable, slurm_utf8_get_loggable);
strong_alias(utf16_to_coding, slurm_utf16_to_coding);
strong_alias(utf16_from_coding, slurm_utf16_from_coding);
/*
* Logging here is excessive and called multiple times for every single byte
* which makes even a simple if() expensive. We are disabling the checks here
* outside of developer mode instead of trying to cache or reduce the costs
* here. The logging is being kept as bugs here are extremely hard to track down
* without them though and can easily pop up given the nature of the utf8
* characters involved.
*/
#ifndef NDEBUG
#define DEBUG_LOG_ENABLED true
#else
#define DEBUG_LOG_ENABLED false
#endif
#define LOG(fmt, ...) \
do { \
if (DEBUG_LOG_ENABLED && \
(slurm_conf.debug_flags & DEBUG_FLAG_DATA) && \
(get_log_level() >= LOG_LEVEL_DEBUG5)) \
log_flag(DATA, "%s: " fmt, __func__, ##__VA_ARGS__); \
} while (false)
static const struct {
utf_encoding_schemes_t schema;
char *string;
} utf_schemas[] = {
{ UTF_INVALID, "INVALID" }, { UTF_UNKNOWN_ENCODING, "UNKNOWN" },
{ UTF_8_ENCODING, "UTF-8" }, { UTF_16BE_ENCODING, "UTF-16BE" },
{ UTF_16LE_ENCODING, "UTF-16LE" }, { UTF_32BE_ENCODING, "UTF-32BE" },
{ UTF_32LE_ENCODING, "UTF-32LE" }, { UTF_INVALID_MAX, "INVALID" },
};
static const struct {
utf_code_t utf; /* utf code */
utf_code_t sub; /* utf code for replacement character */
} utf_subs[] = {
/*
* Values from Control Pictures:
* https://www.unicode.org/charts/PDF/U2400.pdf
*/
{ 0x000000, 0x2400 }, /* NUL (null) */
{ 0x000001, 0x2401 }, /* SOH (start of heading) */
{ 0x000002, 0x2402 }, /* STX (start of text) */
{ 0x000003, 0x2403 }, /* ETX (end of text) */
{ 0x000004, 0x2404 }, /* EOT (end of transmission) */
{ 0x000005, 0x2405 }, /* ENQ (enquiry) */
{ 0x000006, 0x2406 }, /* ACK (acknowledge) */
{ 0x000007, 0x2407 }, /* BEL (bell) */
{ 0x000008, 0x2408 }, /* BS (backspace) */
{ 0x000009, 0x2409 }, /* TAB (horizontal tab) */
{ 0x00000A, 0x240A }, /* LF (NL line feed, new line) */
{ 0x00000B, 0x240B }, /* VT (vertical tab) */
{ 0x00000C, 0x240C }, /* FF (NP form feed, new page) */
{ 0x00000D, 0x240D }, /* CR (carriage return) */
{ 0x00000E, 0x240E }, /* SO (shift out) */
{ 0x00000F, 0x240F }, /* SI (shift in) */
{ 0x000010, 0x2410 }, /* DLE (data link escape) */
{ 0x000011, 0x2411 }, /* DC1 (device control 1) */
{ 0x000012, 0x2412 }, /* DC2 (device control 2) */
{ 0x000013, 0x2413 }, /* DC3 (device control 3) */
{ 0x000014, 0x2414 }, /* DC4 (device control 4) */
{ 0x000015, 0x2415 }, /* NAK (negative acknowledge) */
{ 0x000016, 0x2416 }, /* SYN (synchronous idle) */
{ 0x000017, 0x2417 }, /* ETB (end of trans. block) */
{ 0x000018, 0x2418 }, /* CAN (cancel) */
{ 0x000019, 0x2419 }, /* EM (end of medium) */
{ 0x00001A, 0x241A }, /* SUB (substitute) */
{ 0x00001B, 0x241B }, /* ESC (escape) */
{ 0x00001C, 0x241C }, /* FS (file separator) */
{ 0x00001D, 0x241D }, /* GS (group separator) */
{ 0x00001E, 0x241E }, /* RS (record separator) */
{ 0x00001F, 0x241F }, /* US (unit separator) */
{ 0x000020, 0x2420 }, /* SPACE */
{ 0x00007F, 0x2421 }, /* DEL */
{ 0x000085, 0x23CE }, /* next line */
{ 0x00200E, 0x2AAA }, /* LRM (left to right mark) */
{ 0x00200F, 0x2AAB }, /* RLM (right to left mark) */
{ 0x00202A, 0x2AAA }, /* left-to-right embedding */
{ 0x00202B, 0x2AAB }, /* right-to-left embedding */
{ 0x00202C, 0x2AA4 }, /* pop directional formatting */
{ 0x00202D, 0x2AAA }, /* left-to-right override */
{ 0x00202E, 0x2AAB }, /* right-to-left override */
};
extern size_t utf8_strlen(const utf8_t *str)
{
return strlen((const char *) str);
}
extern utf8_t *utf8_ndup(const utf8_t *src, size_t n)
{
return (utf8_t *) xstrndup((const char *) src, n);
}
extern utf8_t *utf8_dup(const utf8_t *src)
{
return (utf8_t *) xstrdup((const char *) src);
}
extern const char *utf_encoding_scheme_to_string(utf_encoding_schemes_t schema)
{
for (int i = 0; i < ARRAY_SIZE(utf_schemas); i++)
if (utf_schemas[i].schema == schema)
return utf_schemas[i].string;
xassert(false);
return "INVALID";
}
extern utf_code_t utf8_get_loggable(const utf_code_t utf)
{
/* swap out explicit replacements */
for (int i = 0; i < ARRAY_SIZE(utf_subs); i++) {
if (utf == utf_subs[i].utf)
return utf_subs[i].sub;
else if (utf_subs[i].utf > utf)
break;
}
if (utf_is_valid(utf))
return UTF_REPLACEMENT_CODE;
if (utf8_is_newline(utf))
return UTF_RETURN_SYMBOL_CODE;
if (utf8_is_space(utf))
return UTF_SPACE_REPLACEMENT_CODE;
if (utf8_is_control(utf))
return UTF_CONTROL_REPLACEMENT_CODE;
return utf;
}
extern bool slurm_is_utf8_control(utf_code_t utf)
{
static const utf_code_t codes[] = {
0x34F, /* combining grapheme joiner */
0x61C, /* arabic letter mark */
0xE0001, /* language tag (deprecated) */
0x115F, /* hangul jamo block */
0x1160, /* hangul jungseong filler */
0x3164, /* hangul filler */
};
#ifndef NDEBUG
/*
* Wrapping macro will have already check ASCII so just verify it didn't
* miss a character. This is more for documentation of the characters
* than anything else.
*/
/*
* Unicode 15.0.0:
* There are 65 code points set aside in the Unicode Standard for
* compatibility with the C0 and C1 control codes defined in the
* ISO/IEC 2022 framework. The ranges of these code points are
* U+0000..U+001F, U+007F, and U+0080..U+009F
*/
xassert(utf > 0x8);
/*
* We are just going to pretend these are not control codes:
* TAB (horizontal tab)
* LF (NL line feed, new line)
* VT (vertical tab)
* FF (NP form feed, new page)
* CR (carriage return)
*/
xassert(!((utf >= 0xE) && (utf <= 0x1F)));
/* DEL */
xassert(utf != 0x7f);
xassert(utf > UTF_ASCII_MAX_CODE);
#endif
/* PAD (Padding Character) -> APPLICATION PROGRAM COMMAND */
if ((utf >= 0x80) && (utf <= 0x9F))
return true;
/* ZERO WIDTH NON-JOINER -> RIGHT-TO-LEFT MARK */
if ((utf >= 0x200C) && (utf <= 0x200F))
return true;
/* left-to-right embedding -> right-to-left override */
if ((utf >= 0x202A) && (utf <= 0x202E))
return true;
/* word joiner -> nominal digit shapes (deprecated) */
if ((utf >= 0x2060) && (utf <= 0x206F))
return true;
/* interlinear annotation anchor -> interlinear annotation terminator */
if ((utf >= 0xFFF9) && (utf <= 0xFFFB))
return true;
for (int i = 0; i < ARRAY_SIZE(codes); i++)
if (utf == codes[i])
return true;
return false;
}
extern bool slurm_is_utf8_newline(utf_code_t utf)
{
static const utf_code_t codes[] = {
0x0085, /* NEL (next line) */
0x2028, /* LS (line separator) */
0x2029, /* PS (paragraph separator) */
};
/*
* Unicode 15.0.0: 5.8 Newline Guidelines gives these as
* newlines:
* CR (carriage return)
* LF (line feed)
* CRLF (carriage return and line feed)
* NEL (next line)
* VT (vertical tab)
* FF (form feed)
* LS (line separator)
* PS (paragraph separator)
*/
#ifndef NDEBUG
/*
* Wrapping macro will have already check ASCII so just verify it didn't
* miss a character. This is more for documentation of the characters
* than anything else.
*/
/* LF (line feed) -> CR (carriage return) */
xassert(!((utf >= 0xA) && (utf <= 0xD)));
/* FS (file separator) -> US (unit separator) */
xassert(!((utf >= 0x1c) && (utf <= 0x1f)));
xassert(utf > UTF_ASCII_MAX_CODE);
#endif
for (int i = 0; i < ARRAY_SIZE(codes); i++)
if (utf == codes[i])
return true;
return false;
}
extern bool slurm_is_utf8_space(utf_code_t utf)
{
static const utf_code_t high_codes[] = {
0x00A0, /* no-break space */
0x1680, /* ogham space mark */
0x180E, /* mongolian vowel separator */
0x202F, /* narrow no-break space */
0x205F, /* medium mathematical space */
0x2800, /* braille pattern blank */
0x3000, /* ideographic space */
0xFFA0, /* halfwidth hangful filler */
};
/*
* Avoid is_space() to avoid locale causing this character list to
* change but instead follow Unicode standard. We also need to be able
* to handle multibyte UTF8 characters so a single char is not enough.
*
* Unicode 15.0.0: Table 6-2. Unicode Space Characters
* U+0020 space
* U+00A0 no-break space
* U+1680 ogham space mark
* U+2000 en quad
* U+2001 em quad
* U+2002 en space
* U+2003 em space
* U+2004 three-per-em space
* U+2005 four-per-em space
* U+2006 six-per-em space
* U+2007 figure space
* U+2008 punctuation space
* U+2009 thin space
* U+200A hair space
* U+202F narrow no-break space
* U+205F medium mathematical space
* U+3000 ideographic space
*/
#ifndef NDEBUG
/*
* Wrapping macro will have already check ASCII so just verify it didn't
* miss a character. This is more for documentation of the characters
* than anything else.
*/
/* horizontal tab */
xassert(utf != 0x09);
/* space */
xassert(utf != 0x20);
xassert(utf > UTF_ASCII_MAX_CODE);
#endif
/* en quad -> zero width space */
if ((utf >= 0x2000) && (utf <= 0x200B))
return true;
for (int i = 0; i < ARRAY_SIZE(high_codes); i++)
if (utf == high_codes[i])
return true;
return false;
}
extern int slurm_is_utf_valid(utf_code_t utf)
{
/*
* Check against invalid UTF codes but try to be as fast as possible as
* this function is called while checking every character.
*
* The Unicode standard doesn't keep a list of the valid codespaces in a
* single page or table but has them scattered about the the entire
* standard. Each check there is based on the utf8 byte ranges and then
* the relevant invalid areas in those. Its split up to try to do the
* checks only once and only when relevant.
*/
/*
* Wrapping macro will eave already check ASCII so just verify it didn't
* miss a character
*/
xassert(!((utf > 0) && (utf <= UTF_ASCII_MAX_CODE)));
if (utf == 0x0) {
/*
* Unicode 15.0.0:
* It is acceptable for a conforming implementation: To
* support only a subset of the Unicode characters.
*
* UTF allows for U+0 but leaves the implementation to reject or
* allow any specific UTF code. Allowing U+0 will leave the door
* open for too many possible avenues of attack against Slurm.
* NULL terminated strings are the standard string type used
* everywhere in Slurm with a few exceptions of mainly buf_t and
* serializers. The 4 byte utf-8 codes will be the slowest since
* that codespace has the most invalid ranges.
*/
return ESLURM_UTF_NULL_CODE;
}
if (utf > 0x10FFFF)
/* outside of UTF codespace */
return ESLURM_UTF_INVALID_CODE;
if (((utf & 0xFFFE) == 0xFFFE) || ((utf >= 0xFDD0) && (utf <= 0xFDEF)))
/*
* Reject noncharacters in any plane.
*
* Unicode 15.0.0:
* D14 Noncharacter: A code point that is permanently
* reserved for internal use. Noncharacters consist of the
* values U+nFFFE and U+nFFFF (where n is from 0 to 1016)
* and the values U+FDD0..U+FDEF.
*
* (utf & 0xFFFE) == 0xFFFE matches both U+nFFFE and U+nFFFF for
* every plane n.
*/
return ESLURM_UTF_NONCHARACTER_CODE;
if (utf <= 0xD7FF) { /* imply (utf > 0) && */
/* 1-3 byte */
return SLURM_SUCCESS;
} else if (utf <= 0xFFFF) { /* imply (utf >= 0xD800) && */
if ((utf >= 0xD800) && (utf <= 0xDFFF))
/*
* Reject surrogate code points only used for UTF-16.
*
* Unicode 15.0.0:
* D71 High-surrogate code point: A Unicode code
* point in the range U+D800 to U+DBFF.
* D73 Low-surrogate code point: A Unicode code
* point in the range U+DC00 to U+DFFF.
*/
return ESLURM_UTF16_SURROGATE_CODE;
if ((utf >= 0xE000) && (utf <= 0xF8FF))
/*
* Reject private use only codes
*
* Unicode 15.0.0:
* D49 Private-use code point: Code points in the
* ranges U+E000..U+F8FF
*/
return ESLURM_UTF_PRIVATE_CODE;
if ((utf >= 0xFFF0) && (utf <= 0xFFF8))
/*
* Unicode 15.0.0:
* The nine unassigned Unicode code points in the
* range U+FFF0..U+FFF8 are reserved for special
* character definitions.
*/
return ESLURM_UTF_RESERVED_CODE;
/* 1-3 bytes */
return SLURM_SUCCESS;
} else if (utf <= 0x0FFFFF) { /* imply (utf >= 0x10000) && */
if ((utf >= 0xF0000) && (utf <= 0xFFFFD))
/*
* Reject private use only codes
*
* Unicode 15.0.0:
* D49 Private-use code point: Code points in the
* ranges U+E000..U+F8FF, U+F0000..U+FFFFD, and
* U+100000..U+10FFFD.
*/
return ESLURM_UTF_PRIVATE_CODE;
/* 4 byte */
return SLURM_SUCCESS;
} else if (utf <= 0x10FFFF) { /* imply (utf >= 0x100000) && */
if ((utf >= 0x100000) && (utf <= 0x10FFFD))
/*
* Reject private use only codes
*
* Unicode 15.0.0:
* D49 Private-use code point: Code points in the
* ranges U+E000..U+F8FF, U+F0000..U+FFFFD, and
* U+100000..U+10FFFD.
*/
return ESLURM_UTF_PRIVATE_CODE;
/* 4 byte */
return SLURM_SUCCESS;
}
/* should never get here as we missed some bits above */
xassert(false);
return ESLURM_UTF_INVALID_CODE;
}
extern int utf16_to_coding(const utf16_t high, const utf16_t low,
utf_code_t *utf_ptr)
{
xassert(utf_ptr);
if (low) {
if (!utf16_is_high_surrogate(high) ||
!utf16_is_low_surrogate(low)) {
*utf_ptr = UTF_REPLACEMENT_CODE;
return ESLURM_UTF16_SURROGATE_CODE;
}
*utf_ptr = 0x00010000;
*utf_ptr += (high - 0xD800) * 0x0400;
*utf_ptr += low - 0xDC00;
} else {
*utf_ptr = high;
}
return utf_is_valid(*utf_ptr);
}
extern int utf16_from_coding(const utf_code_t utf, utf16_t *high_ptr,
utf16_t *low_ptr)
{
int rc = -1;
xassert(high_ptr);
xassert(low_ptr);
if ((rc = utf_is_valid(utf))) {
*high_ptr = '\0';
*low_ptr = '\0';
return rc;
}
if (utf <= 0xFFFF) {
/* BMP scalar: single code unit, no low surrogate */
*high_ptr = utf;
*low_ptr = 0;
} else {
/* Supplementary plane: inverse of utf16_to_coding() */
const utf_code_t v = utf - 0x00010000;
*high_ptr = 0xD800 + (v / 0x0400);
*low_ptr = 0xDC00 + (v % 0x0400);
}
return SLURM_SUCCESS;
}
extern utf_encoding_schemes_t utf_read_encoding_schema(const utf8_t *src,
const utf8_t *end,
int *bytes_ptr)
{
static const utf8_t utf8[] = UTF8_BYTE_ORDER_MARK_SEQ;
static const utf8_t utf16be[] = UTF16BE_BYTE_ORDER_MARK_SEQ;
static const utf8_t utf16le[] = UTF16LE_BYTE_ORDER_MARK_SEQ;
static const utf8_t utf32be[] = UTF32BE_BYTE_ORDER_MARK_SEQ;
static const utf8_t utf32le[] = UTF32LE_BYTE_ORDER_MARK_SEQ;
const ptrdiff_t bytes = (end - src);
xassert(src);
xassert(end);
xassert(bytes >= 0);
xassert(bytes_ptr);
if (bytes <= 0) {
*bytes_ptr = 0;
return UTF_UNKNOWN_ENCODING;
}
if ((sizeof(utf8) <= bytes) && !memcmp(utf8, src, sizeof(utf8))) {
*bytes_ptr = sizeof(utf8);
return UTF_8_ENCODING;
}
/*
* Check the 4-byte UTF-32 BOMs before the 2-byte UTF-16 BOMs: the
* UTF-32LE BOM (FF FE 00 00) begins with the UTF-16LE BOM (FF FE), so
* testing UTF-16 first would shadow UTF-32LE detection.
*/
if ((sizeof(utf32be) <= bytes) &&
!memcmp(utf32be, src, sizeof(utf32be))) {
*bytes_ptr = sizeof(utf32be);
return UTF_32BE_ENCODING;
}
if ((sizeof(utf32le) <= bytes) &&
!memcmp(utf32le, src, sizeof(utf32le))) {
*bytes_ptr = sizeof(utf32le);
return UTF_32LE_ENCODING;
}
if ((sizeof(utf16be) <= bytes) &&
!memcmp(utf16be, src, sizeof(utf16be))) {
*bytes_ptr = sizeof(utf16be);
return UTF_16BE_ENCODING;
}
if ((sizeof(utf16le) <= bytes) &&
!memcmp(utf16le, src, sizeof(utf16le))) {
*bytes_ptr = sizeof(utf16le);
return UTF_16LE_ENCODING;
}
*bytes_ptr = 0;
return UTF_UNKNOWN_ENCODING;
}
extern int slurm_write_utf8_character(const utf_code_t utf, utf8_t *dst,
int *bytes_ptr, bool log)
{
int rc = EINVAL;
xassert(bytes_ptr);
xassert(dst);
if (!dst || !bytes_ptr)
return EINVAL;
if ((rc = utf_is_valid(utf))) {
/*
* Unicode 15.0.0 D92:
* Any UTF-8 byte sequence that does not match the patterns
* listed in Table 3-7 is ill-formed.
*
* Unicode 15.0.0 3.9.2:
* replaces almost every byte of an ill-formed UTF-8 sequence
* with one U+FFFD
*/
if (log)
LOG("replacing invalid U+%06" PRIx32 ": %s", utf,
slurm_strerror(rc));
return slurm_write_utf8_character(UTF_REPLACEMENT_CODE, dst,
bytes_ptr, log);
} else if (utf <= 0x7F) {
/* UTF8 below 128 is same as ASCII */
dst[0] = utf & 0x7F;
*bytes_ptr = 1;
if (log)
LOG("converted U+%06" PRIx32 " to 0x%" PRIx8, utf,
dst[0]);
} else if ((utf >= 0x80) && (utf <= 0x7FF)) {
/*
* UTF8 shifts and masks each byte for every code point
* 110xxxxx 10xxxxxx
*/
dst[0] = 0xc0 | (0x1f & (utf >> 6));
dst[1] = 0x80 | (0x3f & (utf >> 0));
*bytes_ptr = 2;
if (log)
LOG("converted U+%06" PRIx32 " to 0x%" PRIx8
" 0x%" PRIx8,
utf, dst[0], dst[1]);
} else if ((utf >= 0x800) && (utf <= 0xFFFF)) {
/*
* UTF8 shifts and masks each byte for every code point
* 1110xxxx 10xxxxxx 10xxxxxx
*/
dst[0] = 0xe0 | (0x0f & (utf >> 12));
dst[1] = 0x80 | (0x3f & (utf >> 6));
dst[2] = 0x80 | (0x3f & (utf >> 0));
*bytes_ptr = 3;
if (log)
LOG("converted U+%06" PRIx32 " to 0x%" PRIx8
" 0x%" PRIx8 " 0x%" PRIx8,
utf, dst[0], dst[1], dst[2]);
} else if ((utf >= 0x10000) && (utf <= 0x10FFFF)) {
/*
* UTF8 shifts and masks each byte for every code point
* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
*/
dst[0] = 0xf0 | (0x07 & (utf >> 18));
dst[1] = 0x80 | (0x3f & (utf >> 12));
dst[2] = 0x80 | (0x3f & (utf >> 6));
dst[3] = 0x80 | (0x3f & (utf >> 0));
*bytes_ptr = 4;
if (log)
LOG("converted U+%06" PRIx32 " to 0x%" PRIx8
" 0x%" PRIx8 " 0x%" PRIx8 " 0x%" PRIx8,
utf, dst[0], dst[1], dst[2], dst[3]);
} else {
if (log)
fatal_abort("should never happen");
else
abort();
}
return SLURM_SUCCESS;
}
extern int slurm_read_utf8_character(const utf8_t *src, const utf8_t *end,
utf_code_t *utf_ptr, int *bytes_ptr,
bool check_valid)
{
int rc = EINVAL;
const ptrdiff_t remaining = end - src;
int64_t utf = 0;
int bytes = 0;
xassert(src);
xassert(end);
xassert(src <= end);
xassert(remaining >= 0);
xassert(bytes_ptr);
if (!src || !end || (remaining <= 0)) {
*bytes_ptr = 0;
*utf_ptr = '\0';
return EINVAL;
}
if ((*src & 0x80) == 0x00) {
/* ASCII: 0xxxxxxx */
bytes = 1;
utf = *src;
} else if ((*src & 0xe0) == 0xc0) {
/* 110xxxxx 10xxxxxx */
if (remaining < 2) {
rc = ESLURM_UTF8_READ_ILLEGAL_TERMINATION;
bytes = 1;
goto reject;
}
if ((src[1] & 0xc0) != 0x80) {
rc = ESLURM_UTF8_INVALID_BYTE_2;
goto reject;
}
bytes = 2;
utf = ((src[0] & 0x1f) << 6) | (src[1] & 0x3f);
/*
* Reject overlong encodings: a code point encoded in more bytes
* than the minimum required for its value is malformed UTF-8
* (RFC 3629 mandates the shortest form) and a classic way to
* smuggle characters such as '/', '.' or '"' past a byte-level
* filter.
*/
if (utf < 0x80) {
rc = ESLURM_UTF8_INVALID_READ;
goto reject;
}
} else if ((*src & 0xf0) == 0xe0) {
/* 1110xxxx 10xxxxxx 10xxxxxx */
if (remaining < 3) {
rc = ESLURM_UTF8_READ_ILLEGAL_TERMINATION;
bytes = 1;
goto reject;
}
if ((src[1] & 0xc0) != 0x80) {
rc = ESLURM_UTF8_INVALID_BYTE_2;
bytes = 1;
goto reject;
}
if ((src[2] & 0xc0) != 0x80) {
rc = ESLURM_UTF8_INVALID_BYTE_3;
bytes = 2;
goto reject;
}
bytes = 3;
utf = ((src[0] & 0x0f) << 12) | ((src[1] & 0x3f) << 6) |
(src[2] & 0x3f);
if (utf < 0x800) {
rc = ESLURM_UTF8_INVALID_READ;
goto reject;
}
} else if ((*src & 0xf8) == 0xf0) {
/* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
if (remaining < 4) {
rc = ESLURM_UTF8_READ_ILLEGAL_TERMINATION;
bytes = 1;
goto reject;
}
if ((src[1] & 0xc0) != 0x80) {
rc = ESLURM_UTF8_INVALID_BYTE_2;
bytes = 1;
goto reject;
}
if ((src[2] & 0xc0) != 0x80) {
rc = ESLURM_UTF8_INVALID_BYTE_3;
bytes = 2;
goto reject;
}
if ((src[3] & 0xc0) != 0x80) {
rc = ESLURM_UTF8_INVALID_BYTE_4;
bytes = 3;
goto reject;
}
bytes = 4;
utf = ((src[0] & 0x07) << 18) | ((src[1] & 0x3f) << 12) |
((src[2] & 0x3f) << 6) | (src[3] & 0x3f);
if (utf < 0x10000) {
rc = ESLURM_UTF8_INVALID_READ;
goto reject;
}
} else {
rc = ESLURM_UTF8_INVALID_READ;
goto reject;
}
/* utf8 is restricted to 4 bytes in Unicode 15 */
xassert((bytes >= 0) && (bytes <= 4));
/* check against invalid UTF codes */
if (check_valid && (rc = utf_is_valid(utf)))
goto reject;
rc = SLURM_SUCCESS;
reject:
if (rc) {
/*
* Unicode 15.0 3.9: U+FFFD Substitution of Maximal Subparts:
* Maximal subpart of an ill-formed subsequence: The longest
* code unit subsequence starting at an unconvertible offset
* that is either:
* a. the initial subsequence of a well-formed
* code unit sequence, or
* b. a subsequence of length one.
*/
utf = UTF_REPLACEMENT_CODE;
if (!bytes)
bytes = 1;
}
*bytes_ptr = bytes;
*utf_ptr = utf;
return rc;
}