How to detect UTF-8 in plain C?

Viewed 37662

I am looking for a code snippet in plain old C that detects that the given string is in UTF-8 encoding. I know the solution with regex, but for various reasons it would be better to avoid using anything but plain C in this particular case.

Solution with regex looks like this (warning: various checks omitted):

#define UTF8_DETECT_REGEXP  "^([\x09\x0A\x0D\x20-\x7E]|[\xC2-\xDF][\x80-\xBF]|\xE0[\xA0-\xBF][\x80-\xBF]|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}|\xED[\x80-\x9F][\x80-\xBF]|\xF0[\x90-\xBF][\x80-\xBF]{2}|[\xF1-\xF3][\x80-\xBF]{3}|\xF4[\x80-\x8F][\x80-\xBF]{2})*$"

const char *error;
int         error_off;
int         rc;
int         vect[100];

utf8_re = pcre_compile(UTF8_DETECT_REGEXP, PCRE_CASELESS, &error, &error_off, NULL);
utf8_pe = pcre_study(utf8_re, 0, &error);

rc = pcre_exec(utf8_re, utf8_pe, str, len, 0, 0, vect, sizeof(vect)/sizeof(vect[0]));

if (rc > 0) {
    printf("string is in UTF8\n");
} else {
    printf("string is not in UTF8\n")
}
11 Answers

Here's a (hopefully bug-free) implementation of this expression in plain C:

_Bool is_utf8(const char * string)
{
    if(!string)
        return 0;

    const unsigned char * bytes = (const unsigned char *)string;
    while(*bytes)
    {
        if( (// ASCII
             // use bytes[0] <= 0x7F to allow ASCII control characters
                bytes[0] == 0x09 ||
                bytes[0] == 0x0A ||
                bytes[0] == 0x0D ||
                (0x20 <= bytes[0] && bytes[0] <= 0x7E)
            )
        ) {
            bytes += 1;
            continue;
        }

        if( (// non-overlong 2-byte
                (0xC2 <= bytes[0] && bytes[0] <= 0xDF) &&
                (0x80 <= bytes[1] && bytes[1] <= 0xBF)
            )
        ) {
            bytes += 2;
            continue;
        }

        if( (// excluding overlongs
                bytes[0] == 0xE0 &&
                (0xA0 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF)
            ) ||
            (// straight 3-byte
                ((0xE1 <= bytes[0] && bytes[0] <= 0xEC) ||
                    bytes[0] == 0xEE ||
                    bytes[0] == 0xEF) &&
                (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF)
            ) ||
            (// excluding surrogates
                bytes[0] == 0xED &&
                (0x80 <= bytes[1] && bytes[1] <= 0x9F) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF)
            )
        ) {
            bytes += 3;
            continue;
        }

        if( (// planes 1-3
                bytes[0] == 0xF0 &&
                (0x90 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
                (0x80 <= bytes[3] && bytes[3] <= 0xBF)
            ) ||
            (// planes 4-15
                (0xF1 <= bytes[0] && bytes[0] <= 0xF3) &&
                (0x80 <= bytes[1] && bytes[1] <= 0xBF) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
                (0x80 <= bytes[3] && bytes[3] <= 0xBF)
            ) ||
            (// plane 16
                bytes[0] == 0xF4 &&
                (0x80 <= bytes[1] && bytes[1] <= 0x8F) &&
                (0x80 <= bytes[2] && bytes[2] <= 0xBF) &&
                (0x80 <= bytes[3] && bytes[3] <= 0xBF)
            )
        ) {
            bytes += 4;
            continue;
        }

        return 0;
    }

    return 1;
}

Please note that this is a faithful translation of the regular expression recommended by W3C for form validation, which does indeed reject some valid UTF-8 sequences (in particular those containing ASCII control characters).

Also, even after fixing this by making the change mentioned in the comment, it still assumes zero-termination, which prevents embedding NUL characters, although it should technically be legal.

When I dabbled in creating my own string library, I went with modified UTF-8 (ie encoding NUL as an overlong two-byte sequence) - feel free to use this header as a template for providing a validation routine which doesn't suffer from the above shortcomings.

You cannot detect if a given string (or byte sequence) is a UTF-8 encoded text as for example each and every series of UTF-8 octets is also a valid (if nonsensical) series of Latin-1 (or some other encoding) octets. However not every series of valid Latin-1 octets are valid UTF-8 series. So you can rule out strings that do not conform to the UTF-8 encoding schema:

U+0000-U+007F    0xxxxxxx
U+0080-U+07FF    110yyyxx    10xxxxxx
U+0800-U+FFFF    1110yyyy    10yyyyxx    10xxxxxx
U+10000-U+10FFFF 11110zzz    10zzyyyy    10yyyyxx    10xxxxxx   

You'd have to parse the string as UTF-8, see http://www.rfc-editor.org/rfc/rfc3629.txt It's very simple. If the parsing fails it's not UTF-8. There's several simple UTF-8 libraries around that can do this.

It could perhaps be simplified if you know the string is either plain old ASCII or it contains characters outside ASCII which are UTF-8 encoded . In which case you often don't need to care for the difference, the design of UTF-8 was that existing programs that could handle ASCII, could in most cases transparently handle UTF-8.

Keep in mind that ASCII is encoded in UTF-8 as itself, so ASCII is valid UTF-8.

A C string can be anything, is the problem you need to solve that you don't know if the content is ASCII,GB 2312,CP437,UTF-16, or any of the other dozen character encodings that makes a programmes life hard.. ?

You can use the UTF-8 detector integrated into Firefox. It is found in the universal charset detector and its pretty much a stand along C++ library. It should be extremely easy to find the class the recognizes UTF-8 and take only that.
What this class basically does is detect character sequences that are unique to UTF-8.

  • get the latest firefox trunk
  • go to \mozilla\extensions\universalchardet\
  • find the UTF-8 detector class (I don't quite remember what is it's exact name)

I know it's an old thread, but I thought I'd post my solution here since I think it's an improvement over @Christoph 's wonderful solution (which I upvoted).

I'm no expert, so I may have read the RFC wrong, but it seems to me a 32 byte map can be used instead of a 256 byte map, saving both memory and time.

This led me to a simple macro that advances a string pointer by one UTF-8 character, storing the UTF8 code-point in a 32bit signed integer and storing the value -1 in case of error.

Here's the code with a some comments.

#include <stdint.h>
/**
 * Maps the last 5 bits in a byte (0b11111xxx) to a UTF-8 codepoint length.
 *
 * Codepoint length 0 == error.
 *
 * The first valid length can be any value between 1 to 4 (5== error).
 *
 * An intermidiate (second, third or forth) valid length must be 5.
 *
 * To map was populated using the following Ruby script:
 *
 *      map = []; 32.times { map << 0 }; (0..0b1111).each {|i| map[i] = 1} ;
 *      (0b10000..0b10111).each {|i| map[i] = 5} ;
 *      (0b11000..0b11011).each {|i| map[i] = 2} ;
 *      (0b11100..0b11101).each {|i| map[i] = 3} ;
 *      map[0b11110] = 4; map;
 */
static uint8_t fio_str_utf8_map[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                                     1, 1, 1, 1, 1, 5, 5, 5, 5, 5, 5,
                                     5, 5, 2, 2, 2, 2, 3, 3, 4, 0};

/**
 * Advances the `ptr` by one utf-8 character, placing the value of the UTF-8
 * character into the i32 variable (which must be a signed integer with 32bits
 * or more). On error, `i32` will be equal to `-1` and `ptr` will not step
 * forwards.
 *
 * The `end` value is only used for overflow protection.
 */
#define FIO_STR_UTF8_CODE_POINT(ptr, end, i32)                                 \
  switch (fio_str_utf8_map[((uint8_t *)(ptr))[0] >> 3]) {                      \
  case 1:                                                                      \
    (i32) = ((uint8_t *)(ptr))[0];                                             \
    ++(ptr);                                                                   \
    break;                                                                     \
  case 2:                                                                      \
    if (((ptr) + 2 > (end)) ||                                                 \
        fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5) {                   \
      (i32) = -1;                                                              \
      break;                                                                   \
    }                                                                          \
    (i32) =                                                                    \
        ((((uint8_t *)(ptr))[0] & 31) << 6) | (((uint8_t *)(ptr))[1] & 63);    \
    (ptr) += 2;                                                                \
    break;                                                                     \
  case 3:                                                                      \
    if (((ptr) + 3 > (end)) ||                                                 \
        fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 ||                   \
        fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5) {                   \
      (i32) = -1;                                                              \
      break;                                                                   \
    }                                                                          \
    (i32) = ((((uint8_t *)(ptr))[0] & 15) << 12) |                             \
            ((((uint8_t *)(ptr))[1] & 63) << 6) |                              \
            (((uint8_t *)(ptr))[2] & 63);                                      \
    (ptr) += 3;                                                                \
    break;                                                                     \
  case 4:                                                                      \
    if (((ptr) + 4 > (end)) ||                                                 \
        fio_str_utf8_map[((uint8_t *)(ptr))[1] >> 3] != 5 ||                   \
        fio_str_utf8_map[((uint8_t *)(ptr))[2] >> 3] != 5 ||                   \
        fio_str_utf8_map[((uint8_t *)(ptr))[3] >> 3] != 5) {                   \
      (i32) = -1;                                                              \
      break;                                                                   \
    }                                                                          \
    (i32) = ((((uint8_t *)(ptr))[0] & 7) << 18) |                              \
            ((((uint8_t *)(ptr))[1] & 63) << 12) |                             \
            ((((uint8_t *)(ptr))[2] & 63) << 6) |                              \
            (((uint8_t *)(ptr))[3] & 63);                                      \
    (ptr) += 4;                                                                \
    break;                                                                     \
  default:                                                                     \
    (i32) = -1;                                                                \
    break;                                                                     \
  }

/** Returns 1 if the String is UTF-8 valid and 0 if not. */
inline static size_t fio_str_utf8_valid2(char const *str, size_t length) {
  if (!str)
    return 0;
  if (!length)
    return 1;
  const char *const end = str + length;
  int32_t c = 0;
  do {
    FIO_STR_UTF8_CODE_POINT(str, end, c);
  } while (c > 0 && str < end);
  return str == end && c >= 0;
}

Additional Suggestion With Expressive Code

I am suggesting the following code. I hope, it is a bit more expressive for better understanding (while maybe not so fast as other suggestions in this post).

Below test cases are demonstrating, how this answers the initial question.

#define __FALSE (0)
#define __TRUE  (!__FALSE)

#define MS1BITCNT_0_IS_0xxxxxxx_NO_SUCCESSOR        (0)
#define MS1BITCNT_1_IS_10xxxxxx_IS_SUCCESSOR        (1)
#define MS1BITCNT_2_IS_110xxxxx_HAS_1_SUCCESSOR     (2)
#define MS1BITCNT_3_IS_1110xxxx_HAS_2_SUCCESSORS    (3)
#define MS1BITCNT_4_IS_11110xxx_HAS_3_SUCCESSORS    (4)

typedef int __BOOL;

int CountMS1BitSequenceAndForward(const char **p) {
    int     Mask;
    int     Result = 0;
    char    c = **p;
    ++(*p);
    for (Mask=0x80;c&(Mask&0xFF);Mask>>=1,++Result);
    return Result;
}


int MS1BitSequenceCount2SuccessorByteCount(int MS1BitSeqCount) {
    switch (MS1BitSeqCount) {
    case MS1BITCNT_2_IS_110xxxxx_HAS_1_SUCCESSOR: return 1;
    case MS1BITCNT_3_IS_1110xxxx_HAS_2_SUCCESSORS: return 2;
    case MS1BITCNT_4_IS_11110xxx_HAS_3_SUCCESSORS: return 3;
    }
    return 0;
}

__BOOL ExpectUTF8SuccessorCharsOrReturnFalse(const char **Str, int NumberOfCharsToExpect) {
    while (NumberOfCharsToExpect--) {
        if (CountMS1BitSequenceAndForward(Str) != MS1BITCNT_1_IS_10xxxxxx_IS_SUCCESSOR) {
            return __FALSE;
        }
    }
    return __TRUE;
}

__BOOL IsMS1BitSequenceCountAValidUTF8Starter(int Number) {
    switch (Number) {
    case MS1BITCNT_0_IS_0xxxxxxx_NO_SUCCESSOR:
    case MS1BITCNT_2_IS_110xxxxx_HAS_1_SUCCESSOR:
    case MS1BITCNT_3_IS_1110xxxx_HAS_2_SUCCESSORS:
    case MS1BITCNT_4_IS_11110xxx_HAS_3_SUCCESSORS:
        return __TRUE;
    }
    return __FALSE;
}

#define NO_FURTHER_CHECKS_REQUIRED_IT_IS_NOT_UTF8       (-1)
#define NOT_ALL_EXPECTED_SUCCESSORS_ARE_10xxxxxx        (-1)

int CountValidUTF8CharactersOrNegativeOnBadUTF8(const char *Str) {
    int NumberOfValidUTF8Sequences = 0;
    if (!Str || !Str[0]) { return 0; }
    while (*Str) {
        int MS1BitSeqCount = CountMS1BitSequenceAndForward(&Str);
        if (!IsMS1BitSequenceCountAValidUTF8Starter(MS1BitSeqCount)) {
            return NO_FURTHER_CHECKS_REQUIRED_IT_IS_NOT_UTF8;
        }
        if (!ExpectUTF8SuccessorCharsOrReturnFalse(&Str, MS1BitSequenceCount2SuccessorByteCount(MS1BitSeqCount))) {
            return NOT_ALL_EXPECTED_SUCCESSORS_ARE_10xxxxxx;
        }
        if (MS1BitSeqCount) { ++NumberOfValidUTF8Sequences; }
    }
    return NumberOfValidUTF8Sequences;
}

I also wrote a few test cases:

static void TestUTF8CheckOrDie(const char *Str, int ExpectedResult) {
    int Result = CountValidUTF8CharactersOrNegativeOnBadUTF8(Str);
    if (Result != ExpectedResult) {
        printf("TEST FAILED: %s:%i: check on '%s' returned %i, but expected was %i\n", __FILE__, __LINE__, Str, Result, ExpectedResult);
        exit(1);
    }
}

void SimpleUTF8TestCases(void) {
    TestUTF8CheckOrDie("abcd89234", 0);  // neither valid nor invalid UTF8 sequences
    TestUTF8CheckOrDie("", 0);           // neither valid nor invalid UTF8 sequences
    TestUTF8CheckOrDie(NULL, 0);
    TestUTF8CheckOrDie("asdföadkg", 1);  // contains one valid UTF8 character sequence
    TestUTF8CheckOrDie("asdföadäkg", 2); // contains two valid UTF8 character sequences
    TestUTF8CheckOrDie("asdf\xF8" "adäkg", -1); // contains at least one invalid UTF8 sequence
}
Related