blob: 89057ef7ce22a21f33de9df1bfde46971cf551f8 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* charset conversion utils
*
* Copyright (c) 2017 Rob Clark
*/
#include <charset.h>
#include <capitalization.h>
#include <cp437.h>
#include <efi_loader.h>
#include <errno.h>
#include <malloc.h>
/**
* codepage_437 - Unicode to codepage 437 translation table
*/
const u16 codepage_437[128] = CP437;
static struct capitalization_table capitalization_table[] =
#ifdef CONFIG_EFI_UNICODE_CAPITALIZATION
UNICODE_CAPITALIZATION_TABLE;
#elif CONFIG_FAT_DEFAULT_CODEPAGE == 1250
CP1250_CAPITALIZATION_TABLE;
#else
CP437_CAPITALIZATION_TABLE;
#endif
/**
* get_code() - read Unicode code point from UTF-8 stream
*
* @read_u8: - stream reader
* @src: - string buffer passed to stream reader, optional
* Return: - Unicode code point, or -1
*/
static int get_code(u8 (*read_u8)(void *data), void *data)
{
s32 ch = 0;
ch = read_u8(data);
if (!ch)
return 0;
if (ch >= 0xc2 && ch <= 0xf4) {
int code = 0;
if (ch >= 0xe0) {
if (ch >= 0xf0) {
/* 0xf0 - 0xf4 */
ch &= 0x07;
code = ch << 18;
ch = read_u8(data);
if (ch < 0x80 || ch > 0xbf)
goto error;
ch &= 0x3f;
} else {
/* 0xe0 - 0xef */
ch &= 0x0f;
}
code += ch << 12;
if ((code >= 0xD800 && code <= 0xDFFF) ||
code >= 0x110000)
goto error;
ch = read_u8(data);
if (ch < 0x80 || ch > 0xbf)
goto error;
}
/* 0xc0 - 0xdf or continuation byte (0x80 - 0xbf) */
ch &= 0x3f;
code += ch << 6;
ch = read_u8(data);
if (ch < 0x80 || ch > 0xbf)
goto error;
ch &= 0x3f;
ch += code;
} else if (ch >= 0x80) {
goto error;
}
return ch;
error:
return -1;
}
/**
* read_string() - read byte from character string
*
* @data: - pointer to string
* Return: - byte read
*
* The string pointer is incremented if it does not point to '\0'.
*/
static u8 read_string(void *data)
{
const char **src = (const char **)data;
u8 c;
if (!src || !*src || !**src)
return 0;
c = **src;
++*src;
return c;
}
/**
* read_console() - read byte from console
*
* @data - not used, needed to match interface
* Return: - byte read or 0 on error
*/
static u8 read_console(void *data)
{
int ch;
ch = getchar();
if (ch < 0)
ch = 0;
return ch;
}
int console_read_unicode(s32 *code)
{
for (;;) {
s32 c;
if (!tstc()) {
/* No input available */
return 1;
}
/* Read Unicode code */
c = get_code(read_console, NULL);
if (c > 0) {
*code = c;
return 0;
}
}
}
s32 utf8_get(const char **src)
{
return get_code(read_string, src);
}
int utf8_put(s32 code, char **dst)
{
if (!dst || !*dst)
return -1;
if ((code >= 0xD800 && code <= 0xDFFF) || code >= 0x110000)
return -1;
if (code <= 0x007F) {
**dst = code;
} else {
if (code <= 0x07FF) {
**dst = code >> 6 | 0xC0;
} else {
if (code < 0x10000) {
**dst = code >> 12 | 0xE0;
} else {
**dst = code >> 18 | 0xF0;
++*dst;
**dst = (code >> 12 & 0x3F) | 0x80;
}
++*dst;
**dst = (code >> 6 & 0x3F) | 0x80;
}
++*dst;
**dst = (code & 0x3F) | 0x80;
}
++*dst;
return 0;
}
size_t utf8_utf16_strnlen(const char *src, size_t count)
{
size_t len = 0;
for (; *src && count; --count) {
s32 code = utf8_get(&src);
if (!code)
break;
if (code < 0) {
/* Reserve space for a replacement character */
len += 1;
} else if (code < 0x10000) {
len += 1;
} else {
len += 2;
}
}
return len;
}
int utf8_utf16_strncpy(u16 **dst, const char *src, size_t count)
{
if (!src || !dst || !*dst)
return -1;
for (; count && *src; --count) {
s32 code = utf8_get(&src);
if (code < 0)
code = '?';
utf16_put(code, dst);
}
**dst = 0;
return 0;
}
s32 utf16_get(const u16 **src)
{
s32 code, code2;
if (!src || !*src)
return -1;
if (!**src)
return 0;
code = **src;
++*src;
if (code >= 0xDC00 && code <= 0xDFFF)
return -1;
if (code >= 0xD800 && code <= 0xDBFF) {
if (!**src)
return -1;
code &= 0x3ff;
code <<= 10;
code += 0x10000;
code2 = **src;
++*src;
if (code2 <= 0xDC00 || code2 >= 0xDFFF)
return -1;
code2 &= 0x3ff;
code += code2;
}
return code;
}
int utf16_put(s32 code, u16 **dst)
{
if (!dst || !*dst)
return -1;
if ((code >= 0xD800 && code <= 0xDFFF) || code >= 0x110000)
return -1;
if (code < 0x10000) {
**dst = code;
} else {
code -= 0x10000;
**dst = code >> 10 | 0xD800;
++*dst;
**dst = (code & 0x3ff) | 0xDC00;
}
++*dst;
return 0;
}
size_t utf16_strnlen(const u16 *src, size_t count)
{
size_t len = 0;
for (; *src && count; --count) {
s32 code = utf16_get(&src);
if (!code)
break;
/*
* In case of an illegal sequence still reserve space for a
* replacement character.
*/
++len;
}
return len;
}
size_t utf16_utf8_strnlen(const u16 *src, size_t count)
{
size_t len = 0;
for (; *src && count; --count) {
s32 code = utf16_get(&src);
if (!code)
break;
if (code < 0)
/* Reserve space for a replacement character */
len += 1;
else if (code < 0x80)
len += 1;
else if (code < 0x800)
len += 2;
else if (code < 0x10000)
len += 3;
else
len += 4;
}
return len;
}
int utf16_utf8_strncpy(char **dst, const u16 *src, size_t count)
{
if (!src || !dst || !*dst)
return -1;
for (; count && *src; --count) {
s32 code = utf16_get(&src);
if (code < 0)
code = '?';
utf8_put(code, dst);
}
**dst = 0;
return 0;
}
s32 utf_to_lower(const s32 code)
{
struct capitalization_table *pos = capitalization_table;
s32 ret = code;
if (code <= 0x7f) {
if (code >= 'A' && code <= 'Z')
ret += 0x20;
return ret;
}
for (; pos->upper; ++pos) {
if (pos->upper == code) {
ret = pos->lower;
break;
}
}
return ret;
}
s32 utf_to_upper(const s32 code)
{
struct capitalization_table *pos = capitalization_table;
s32 ret = code;
if (code <= 0x7f) {
if (code >= 'a' && code <= 'z')
ret -= 0x20;
return ret;
}
for (; pos->lower; ++pos) {
if (pos->lower == code) {
ret = pos->upper;
break;
}
}
return ret;
}
/*
* u16_strcasecmp() - compare two u16 strings case insensitively
*
* @s1: first string to compare
* @s2: second string to compare
* @n: maximum number of u16 to compare
* Return: 0 if the first n u16 are the same in s1 and s2
* < 0 if the first different u16 in s1 is less than the
* corresponding u16 in s2
* > 0 if the first different u16 in s1 is greater than the
*/
int u16_strcasecmp(const u16 *s1, const u16 *s2)
{
int ret = 0;
s32 c1, c2;
for (;;) {
c1 = utf_to_upper(utf16_get(&s1));
c2 = utf_to_upper(utf16_get(&s2));
ret = c1 - c2;
if (ret || !c1 || c1 == -1 || c2 == -1)
break;
}
return ret;
}
/*
* u16_strncmp() - compare two u16 string
*
* @s1: first string to compare
* @s2: second string to compare
* @n: maximum number of u16 to compare
* Return: 0 if the first n u16 are the same in s1 and s2
* < 0 if the first different u16 in s1 is less than the
* corresponding u16 in s2
* > 0 if the first different u16 in s1 is greater than the
* corresponding u16 in s2
*/
int u16_strncmp(const u16 *s1, const u16 *s2, size_t n)
{
int ret = 0;
for (; n; --n, ++s1, ++s2) {
ret = *s1 - *s2;
if (ret || !*s1)
break;
}
return ret;
}
size_t __efi_runtime u16_strnlen(const u16 *in, size_t count)
{
size_t i;
for (i = 0; count-- && in[i]; i++);
return i;
}
size_t u16_strsize(const void *in)
{
return (u16_strlen(in) + 1) * sizeof(u16);
}
u16 *u16_strcpy(u16 *dest, const u16 *src)
{
u16 *tmp = dest;
for (;; dest++, src++) {
*dest = *src;
if (!*src)
break;
}
return tmp;
}
u16 *u16_strdup(const void *src)
{
u16 *new;
size_t len;
if (!src)
return NULL;
len = u16_strsize(src);
new = malloc(len);
if (!new)
return NULL;
memcpy(new, src, len);
return new;
}
size_t u16_strlcat(u16 *dest, const u16 *src, size_t count)
{
size_t destlen = u16_strnlen(dest, count);
size_t srclen = u16_strlen(src);
size_t ret = destlen + srclen;
if (destlen >= count)
return ret;
if (ret >= count)
srclen -= (ret - count + 1);
memcpy(&dest[destlen], src, 2 * srclen);
dest[destlen + srclen] = 0x0000;
return ret;
}
/* Convert UTF-16 to UTF-8. */
uint8_t *utf16_to_utf8(uint8_t *dest, const uint16_t *src, size_t size)
{
uint32_t code_high = 0;
while (size--) {
uint32_t code = *src++;
if (code_high) {
if (code >= 0xDC00 && code <= 0xDFFF) {
/* Surrogate pair. */
code = ((code_high - 0xD800) << 10) + (code - 0xDC00) + 0x10000;
*dest++ = (code >> 18) | 0xF0;
*dest++ = ((code >> 12) & 0x3F) | 0x80;
*dest++ = ((code >> 6) & 0x3F) | 0x80;
*dest++ = (code & 0x3F) | 0x80;
} else {
/* Error... */
*dest++ = '?';
/* *src may be valid. Don't eat it. */
src--;
}
code_high = 0;
} else {
if (code <= 0x007F) {
*dest++ = code;
} else if (code <= 0x07FF) {
*dest++ = (code >> 6) | 0xC0;
*dest++ = (code & 0x3F) | 0x80;
} else if (code >= 0xD800 && code <= 0xDBFF) {
code_high = code;
continue;
} else if (code >= 0xDC00 && code <= 0xDFFF) {
/* Error... */
*dest++ = '?';
} else if (code < 0x10000) {
*dest++ = (code >> 12) | 0xE0;
*dest++ = ((code >> 6) & 0x3F) | 0x80;
*dest++ = (code & 0x3F) | 0x80;
} else {
*dest++ = (code >> 18) | 0xF0;
*dest++ = ((code >> 12) & 0x3F) | 0x80;
*dest++ = ((code >> 6) & 0x3F) | 0x80;
*dest++ = (code & 0x3F) | 0x80;
}
}
}
return dest;
}
int utf_to_cp(s32 *c, const u16 *codepage)
{
if (*c >= 0x80) {
int j;
/* Look up codepage translation */
for (j = 0; j < 0x80; ++j) {
if (*c == codepage[j]) {
*c = j + 0x80;
return 0;
}
}
*c = '?';
return -ENOENT;
}
return 0;
}
int utf8_to_cp437_stream(u8 c, char *buffer)
{
char *end;
const char *pos;
s32 s;
int ret;
for (;;) {
pos = buffer;
end = buffer + strlen(buffer);
*end++ = c;
*end = 0;
s = utf8_get(&pos);
if (s > 0) {
*buffer = 0;
ret = utf_to_cp(&s, codepage_437);
return s;
}
if (pos == end)
return 0;
*buffer = 0;
}
}
int utf8_to_utf32_stream(u8 c, char *buffer)
{
char *end;
const char *pos;
s32 s;
for (;;) {
pos = buffer;
end = buffer + strlen(buffer);
*end++ = c;
*end = 0;
s = utf8_get(&pos);
if (s > 0) {
*buffer = 0;
return s;
}
if (pos == end)
return 0;
*buffer = 0;
}
}