diff options
Diffstat (limited to 'fs/exfat/utf.c')
-rw-r--r-- | fs/exfat/utf.c | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/fs/exfat/utf.c b/fs/exfat/utf.c new file mode 100644 index 00000000000..8a28e7621ca --- /dev/null +++ b/fs/exfat/utf.c @@ -0,0 +1,254 @@ +/* + utf.c (13.09.09) + exFAT file system implementation library. + + Free exFAT implementation. + Copyright (C) 2010-2023 Andrew Nayenko + + This program 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. + + This program 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 this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "exfat.h" +#include <errno.h> + +static char* wchar_to_utf8(char* output, u32 wc, size_t outsize) +{ + if (wc <= 0x7f) + { + if (outsize < 1) + return NULL; + *output++ = (char) wc; + } + else if (wc <= 0x7ff) + { + if (outsize < 2) + return NULL; + *output++ = 0xc0 | (wc >> 6); + *output++ = 0x80 | (wc & 0x3f); + } + else if (wc <= 0xffff) + { + if (outsize < 3) + return NULL; + *output++ = 0xe0 | (wc >> 12); + *output++ = 0x80 | ((wc >> 6) & 0x3f); + *output++ = 0x80 | (wc & 0x3f); + } + else if (wc <= 0x1fffff) + { + if (outsize < 4) + return NULL; + *output++ = 0xf0 | (wc >> 18); + *output++ = 0x80 | ((wc >> 12) & 0x3f); + *output++ = 0x80 | ((wc >> 6) & 0x3f); + *output++ = 0x80 | (wc & 0x3f); + } + else if (wc <= 0x3ffffff) + { + if (outsize < 5) + return NULL; + *output++ = 0xf8 | (wc >> 24); + *output++ = 0x80 | ((wc >> 18) & 0x3f); + *output++ = 0x80 | ((wc >> 12) & 0x3f); + *output++ = 0x80 | ((wc >> 6) & 0x3f); + *output++ = 0x80 | (wc & 0x3f); + } + else if (wc <= 0x7fffffff) + { + if (outsize < 6) + return NULL; + *output++ = 0xfc | (wc >> 30); + *output++ = 0x80 | ((wc >> 24) & 0x3f); + *output++ = 0x80 | ((wc >> 18) & 0x3f); + *output++ = 0x80 | ((wc >> 12) & 0x3f); + *output++ = 0x80 | ((wc >> 6) & 0x3f); + *output++ = 0x80 | (wc & 0x3f); + } + else + return NULL; + + return output; +} + +static const le16_t* utf16_to_wchar(const le16_t* input, u32* wc, + size_t insize) +{ + if ((le16_to_cpu(input[0]) & 0xfc00) == 0xd800) + { + if (insize < 2 || (le16_to_cpu(input[1]) & 0xfc00) != 0xdc00) + return NULL; + *wc = ((u32) (le16_to_cpu(input[0]) & 0x3ff) << 10); + *wc |= (le16_to_cpu(input[1]) & 0x3ff); + *wc += 0x10000; + return input + 2; + } + else + { + *wc = le16_to_cpu(*input); + return input + 1; + } +} + +int exfat_utf16_to_utf8(char* output, const le16_t* input, size_t outsize, + size_t insize) +{ + const le16_t* iptr = input; + const le16_t* iend = input + insize; + char* optr = output; + const char* oend = output + outsize; + u32 wc; + + while (iptr < iend) + { + iptr = utf16_to_wchar(iptr, &wc, iend - iptr); + if (iptr == NULL) + { + exfat_error("illegal UTF-16 sequence"); + return -EILSEQ; + } + optr = wchar_to_utf8(optr, wc, oend - optr); + if (optr == NULL) + { + exfat_error("name is too long"); + return -ENAMETOOLONG; + } + if (wc == 0) + return 0; + } + if (optr >= oend) + { + exfat_error("name is too long"); + return -ENAMETOOLONG; + } + *optr = '\0'; + return 0; +} + +static const char* utf8_to_wchar(const char* input, u32* wc, + size_t insize) +{ + size_t size; + size_t i; + + if (insize == 0) + exfat_bug("no input for utf8_to_wchar"); + + if ((input[0] & 0x80) == 0) + { + *wc = (u32) input[0]; + return input + 1; + } + else if ((input[0] & 0xe0) == 0xc0) + { + *wc = ((u32) input[0] & 0x1f) << 6; + size = 2; + } + else if ((input[0] & 0xf0) == 0xe0) + { + *wc = ((u32) input[0] & 0x0f) << 12; + size = 3; + } + else if ((input[0] & 0xf8) == 0xf0) + { + *wc = ((u32) input[0] & 0x07) << 18; + size = 4; + } + else if ((input[0] & 0xfc) == 0xf8) + { + *wc = ((u32) input[0] & 0x03) << 24; + size = 5; + } + else if ((input[0] & 0xfe) == 0xfc) + { + *wc = ((u32) input[0] & 0x01) << 30; + size = 6; + } + else + return NULL; + + if (insize < size) + return NULL; + + /* the first byte is handled above */ + for (i = 1; i < size; i++) + { + if ((input[i] & 0xc0) != 0x80) + return NULL; + *wc |= (input[i] & 0x3f) << ((size - i - 1) * 6); + } + + return input + size; +} + +static le16_t* wchar_to_utf16(le16_t* output, u32 wc, size_t outsize) +{ + if (wc <= 0xffff) /* if character is from BMP */ + { + if (outsize == 0) + return NULL; + output[0] = cpu_to_le16(wc); + return output + 1; + } + if (outsize < 2) + return NULL; + wc -= 0x10000; + output[0] = cpu_to_le16(0xd800 | ((wc >> 10) & 0x3ff)); + output[1] = cpu_to_le16(0xdc00 | (wc & 0x3ff)); + return output + 2; +} + +int exfat_utf8_to_utf16(le16_t* output, const char* input, size_t outsize, + size_t insize) +{ + const char* iptr = input; + const char* iend = input + insize; + le16_t* optr = output; + const le16_t* oend = output + outsize; + u32 wc; + + while (iptr < iend) + { + iptr = utf8_to_wchar(iptr, &wc, iend - iptr); + if (iptr == NULL) + { + exfat_error("illegal UTF-8 sequence"); + return -EILSEQ; + } + optr = wchar_to_utf16(optr, wc, oend - optr); + if (optr == NULL) + { + exfat_error("name is too long"); + return -ENAMETOOLONG; + } + if (wc == 0) + break; + } + if (optr >= oend) + { + exfat_error("name is too long"); + return -ENAMETOOLONG; + } + *optr = cpu_to_le16(0); + return 0; +} + +size_t exfat_utf16_length(const le16_t* str) +{ + size_t i = 0; + + while (le16_to_cpu(str[i])) + i++; + return i; +} |