diff options
author | Jelmer Vernooij <jelmer@samba.org> | 2008-10-24 00:06:35 +0200 |
---|---|---|
committer | Jelmer Vernooij <jelmer@samba.org> | 2008-10-24 00:06:35 +0200 |
commit | 09a63accb8863a883ef8aba3a44776ac92ae5764 (patch) | |
tree | 1181f77950c4674f80b61421354d199d30ede6bd /lib/util/charset | |
parent | 8baf1695492300fd8c2ce7e4f106a14f510357e5 (diff) | |
download | samba-09a63accb8863a883ef8aba3a44776ac92ae5764.tar.gz samba-09a63accb8863a883ef8aba3a44776ac92ae5764.tar.bz2 samba-09a63accb8863a883ef8aba3a44776ac92ae5764.zip |
Move charset library to top level.
Diffstat (limited to 'lib/util/charset')
-rw-r--r-- | lib/util/charset/charcnv.c | 768 | ||||
-rw-r--r-- | lib/util/charset/charset.h | 154 | ||||
-rw-r--r-- | lib/util/charset/config.m4 | 86 | ||||
-rw-r--r-- | lib/util/charset/config.mk | 13 | ||||
-rw-r--r-- | lib/util/charset/iconv.c | 711 | ||||
-rw-r--r-- | lib/util/charset/tests/charset.c | 272 | ||||
-rw-r--r-- | lib/util/charset/tests/iconv.c | 424 | ||||
-rw-r--r-- | lib/util/charset/util_unistr.c | 684 |
8 files changed, 3112 insertions, 0 deletions
diff --git a/lib/util/charset/charcnv.c b/lib/util/charset/charcnv.c new file mode 100644 index 0000000000..3e384304cf --- /dev/null +++ b/lib/util/charset/charcnv.c @@ -0,0 +1,768 @@ +/* + Unix SMB/CIFS implementation. + Character set conversion Extensions + Copyright (C) Igor Vergeichik <iverg@mail.ru> 2001 + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Simo Sorce 2001 + Copyright (C) Jelmer Vernooij 2007 + + 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 3 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, see <http://www.gnu.org/licenses/>. + +*/ +#include "includes.h" +#include "system/iconv.h" +#include "param/param.h" + +/** + * @file + * + * @brief Character-set conversion routines built on our iconv. + * + * @note Samba's internal character set (at least in the 3.0 series) + * is always the same as the one for the Unix filesystem. It is + * <b>not</b> necessarily UTF-8 and may be different on machines that + * need i18n filenames to be compatible with Unix software. It does + * have to be a superset of ASCII. All multibyte sequences must start + * with a byte with the high bit set. + * + * @sa lib/iconv.c + */ + +struct smb_iconv_convenience { + const char *unix_charset; + const char *dos_charset; + bool native_iconv; + smb_iconv_t conv_handles[NUM_CHARSETS][NUM_CHARSETS]; +}; + + +/** + * Return the name of a charset to give to iconv(). + **/ +static const char *charset_name(struct smb_iconv_convenience *ic, charset_t ch) +{ + switch (ch) { + case CH_UTF16: return "UTF-16LE"; + case CH_UNIX: return ic->unix_charset; + case CH_DOS: return ic->dos_charset; + case CH_UTF8: return "UTF8"; + case CH_UTF16BE: return "UTF-16BE"; + default: + return "ASCII"; + } +} + +/** + re-initialize iconv conversion descriptors +**/ +static int close_iconv(struct smb_iconv_convenience *data) +{ + unsigned c1, c2; + for (c1=0;c1<NUM_CHARSETS;c1++) { + for (c2=0;c2<NUM_CHARSETS;c2++) { + if (data->conv_handles[c1][c2] != NULL) { + if (data->conv_handles[c1][c2] != (smb_iconv_t)-1) { + smb_iconv_close(data->conv_handles[c1][c2]); + } + data->conv_handles[c1][c2] = NULL; + } + } + } + + return 0; +} + +_PUBLIC_ struct smb_iconv_convenience *smb_iconv_convenience_init(TALLOC_CTX *mem_ctx, + const char *dos_charset, + const char *unix_charset, + bool native_iconv) +{ + struct smb_iconv_convenience *ret = talloc_zero(mem_ctx, + struct smb_iconv_convenience); + + if (ret == NULL) { + return NULL; + } + + talloc_set_destructor(ret, close_iconv); + + ret->dos_charset = talloc_strdup(ret, dos_charset); + ret->unix_charset = talloc_strdup(ret, unix_charset); + ret->native_iconv = native_iconv; + + return ret; +} + +/* + on-demand initialisation of conversion handles +*/ +static smb_iconv_t get_conv_handle(struct smb_iconv_convenience *ic, + charset_t from, charset_t to) +{ + const char *n1, *n2; + static bool initialised; + + if (initialised == false) { + initialised = true; + +#ifdef LC_ALL + /* we set back the locale to C to get ASCII-compatible + toupper/lower functions. For now we do not need + any other POSIX localisations anyway. When we + should really need localized string functions one + day we need to write our own ascii_tolower etc. + */ + setlocale(LC_ALL, "C"); +#endif + } + + if (ic->conv_handles[from][to]) { + return ic->conv_handles[from][to]; + } + + n1 = charset_name(ic, from); + n2 = charset_name(ic, to); + + ic->conv_handles[from][to] = smb_iconv_open_ex(ic, n2, n1, + ic->native_iconv); + + if (ic->conv_handles[from][to] == (smb_iconv_t)-1) { + if ((from == CH_DOS || to == CH_DOS) && + strcasecmp(charset_name(ic, CH_DOS), "ASCII") != 0) { + DEBUG(0,("dos charset '%s' unavailable - using ASCII\n", + charset_name(ic, CH_DOS))); + ic->dos_charset = "ASCII"; + + n1 = charset_name(ic, from); + n2 = charset_name(ic, to); + + ic->conv_handles[from][to] = + smb_iconv_open_ex(ic, n2, n1, ic->native_iconv); + } + } + + return ic->conv_handles[from][to]; +} + + +/** + * Convert string from one encoding to another, making error checking etc + * + * @param src pointer to source string (multibyte or singlebyte) + * @param srclen length of the source string in bytes + * @param dest pointer to destination string (multibyte or singlebyte) + * @param destlen maximal length allowed for string + * @returns the number of bytes occupied in the destination + **/ +_PUBLIC_ ssize_t convert_string(struct smb_iconv_convenience *ic, + charset_t from, charset_t to, + void const *src, size_t srclen, + void *dest, size_t destlen) +{ + size_t i_len, o_len; + size_t retval; + const char* inbuf = (const char*)src; + char* outbuf = (char*)dest; + smb_iconv_t descriptor; + + if (srclen == (size_t)-1) + srclen = strlen(inbuf)+1; + + descriptor = get_conv_handle(ic, from, to); + + if (descriptor == (smb_iconv_t)-1 || descriptor == (smb_iconv_t)0) { + /* conversion not supported, use as is */ + size_t len = MIN(srclen,destlen); + memcpy(dest,src,len); + return len; + } + + i_len=srclen; + o_len=destlen; + retval = smb_iconv(descriptor, &inbuf, &i_len, &outbuf, &o_len); + if(retval==(size_t)-1) { + const char *reason; + switch(errno) { + case EINVAL: + reason="Incomplete multibyte sequence"; + return -1; + case E2BIG: + reason="No more room"; + if (from == CH_UNIX) { + DEBUG(0,("E2BIG: convert_string(%s,%s): srclen=%d destlen=%d - '%s'\n", + charset_name(ic, from), charset_name(ic, to), + (int)srclen, (int)destlen, + (const char *)src)); + } else { + DEBUG(0,("E2BIG: convert_string(%s,%s): srclen=%d destlen=%d\n", + charset_name(ic, from), charset_name(ic, to), + (int)srclen, (int)destlen)); + } + return -1; + case EILSEQ: + reason="Illegal multibyte sequence"; + return -1; + } + /* smb_panic(reason); */ + } + return destlen-o_len; +} + +_PUBLIC_ ssize_t convert_string_talloc_descriptor(TALLOC_CTX *ctx, smb_iconv_t descriptor, void const *src, size_t srclen, void **dest) +{ + size_t i_len, o_len, destlen; + size_t retval; + const char *inbuf = (const char *)src; + char *outbuf, *ob; + + *dest = NULL; + + /* it is _very_ rare that a conversion increases the size by + more than 3x */ + destlen = srclen; + outbuf = NULL; +convert: + destlen = 2 + (destlen*3); + ob = talloc_realloc(ctx, outbuf, char, destlen); + if (!ob) { + DEBUG(0, ("convert_string_talloc: realloc failed!\n")); + talloc_free(outbuf); + return (size_t)-1; + } else { + outbuf = ob; + } + + /* we give iconv 2 less bytes to allow us to terminate at the + end */ + i_len = srclen; + o_len = destlen-2; + retval = smb_iconv(descriptor, + &inbuf, &i_len, + &outbuf, &o_len); + if(retval == (size_t)-1) { + const char *reason="unknown error"; + switch(errno) { + case EINVAL: + reason="Incomplete multibyte sequence"; + break; + case E2BIG: + goto convert; + case EILSEQ: + reason="Illegal multibyte sequence"; + break; + } + DEBUG(0,("Conversion error: %s(%s)\n",reason,inbuf)); + talloc_free(ob); + return (size_t)-1; + } + + destlen = (destlen-2) - o_len; + + /* guarantee null termination in all charsets */ + SSVAL(ob, destlen, 0); + + *dest = ob; + + return destlen; +} + +/** + * Convert between character sets, allocating a new buffer using talloc for the result. + * + * @param srclen length of source buffer. + * @param dest always set at least to NULL + * @note -1 is not accepted for srclen. + * + * @returns Size in bytes of the converted string; or -1 in case of error. + **/ + +_PUBLIC_ ssize_t convert_string_talloc(TALLOC_CTX *ctx, + struct smb_iconv_convenience *ic, + charset_t from, charset_t to, + void const *src, size_t srclen, + void **dest) +{ + smb_iconv_t descriptor; + + *dest = NULL; + + if (src == NULL || srclen == (size_t)-1 || srclen == 0) + return (size_t)-1; + + descriptor = get_conv_handle(ic, from, to); + + if (descriptor == (smb_iconv_t)-1 || descriptor == (smb_iconv_t)0) { + /* conversion not supported, return -1*/ + DEBUG(3, ("convert_string_talloc: conversion from %s to %s not supported!\n", + charset_name(ic, from), + charset_name(ic, to))); + return -1; + } + + return convert_string_talloc_descriptor(ctx, descriptor, src, srclen, dest); +} + +/** + * Copy a string from a char* unix src to a dos codepage string destination. + * + * @return the number of bytes occupied by the string in the destination. + * + * @param flags can include + * <dl> + * <dt>STR_TERMINATE</dt> <dd>means include the null termination</dd> + * <dt>STR_UPPER</dt> <dd>means uppercase in the destination</dd> + * </dl> + * + * @param dest_len the maximum length in bytes allowed in the + * destination. If @p dest_len is -1 then no maximum is used. + **/ +static ssize_t push_ascii(struct smb_iconv_convenience *ic, + void *dest, const char *src, size_t dest_len, int flags) +{ + size_t src_len; + ssize_t ret; + + if (flags & STR_UPPER) { + char *tmpbuf = strupper_talloc(NULL, src); + if (tmpbuf == NULL) { + return -1; + } + ret = push_ascii(ic, dest, tmpbuf, dest_len, flags & ~STR_UPPER); + talloc_free(tmpbuf); + return ret; + } + + src_len = strlen(src); + + if (flags & (STR_TERMINATE | STR_TERMINATE_ASCII)) + src_len++; + + return convert_string(ic, CH_UNIX, CH_DOS, src, src_len, dest, dest_len); +} + +/** + * Copy a string from a unix char* src to an ASCII destination, + * allocating a buffer using talloc(). + * + * @param dest always set at least to NULL + * + * @returns The number of bytes occupied by the string in the destination + * or -1 in case of error. + **/ +_PUBLIC_ ssize_t push_ascii_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src) +{ + size_t src_len = strlen(src)+1; + *dest = NULL; + return convert_string_talloc(ctx, ic, CH_UNIX, CH_DOS, src, src_len, (void **)dest); +} + + +/** + * Copy a string from a dos codepage source to a unix char* destination. + * + * The resulting string in "dest" is always null terminated. + * + * @param flags can have: + * <dl> + * <dt>STR_TERMINATE</dt> + * <dd>STR_TERMINATE means the string in @p src + * is null terminated, and src_len is ignored.</dd> + * </dl> + * + * @param src_len is the length of the source area in bytes. + * @returns the number of bytes occupied by the string in @p src. + **/ +static ssize_t pull_ascii(struct smb_iconv_convenience *ic, char *dest, const void *src, size_t dest_len, size_t src_len, int flags) +{ + size_t ret; + + if (flags & (STR_TERMINATE | STR_TERMINATE_ASCII)) { + if (src_len == (size_t)-1) { + src_len = strlen((const char *)src) + 1; + } else { + size_t len = strnlen((const char *)src, src_len); + if (len < src_len) + len++; + src_len = len; + } + } + + ret = convert_string(ic, CH_DOS, CH_UNIX, src, src_len, dest, dest_len); + + if (dest_len) + dest[MIN(ret, dest_len-1)] = 0; + + return src_len; +} + +/** + * Copy a string from a char* src to a unicode destination. + * + * @returns the number of bytes occupied by the string in the destination. + * + * @param flags can have: + * + * <dl> + * <dt>STR_TERMINATE <dd>means include the null termination. + * <dt>STR_UPPER <dd>means uppercase in the destination. + * <dt>STR_NOALIGN <dd>means don't do alignment. + * </dl> + * + * @param dest_len is the maximum length allowed in the + * destination. If dest_len is -1 then no maxiumum is used. + **/ +static ssize_t push_ucs2(struct smb_iconv_convenience *ic, + void *dest, const char *src, size_t dest_len, int flags) +{ + size_t len=0; + size_t src_len = strlen(src); + size_t ret; + + if (flags & STR_UPPER) { + char *tmpbuf = strupper_talloc(NULL, src); + if (tmpbuf == NULL) { + return -1; + } + ret = push_ucs2(ic, dest, tmpbuf, dest_len, flags & ~STR_UPPER); + talloc_free(tmpbuf); + return ret; + } + + if (flags & STR_TERMINATE) + src_len++; + + if (ucs2_align(NULL, dest, flags)) { + *(char *)dest = 0; + dest = (void *)((char *)dest + 1); + if (dest_len) dest_len--; + len++; + } + + /* ucs2 is always a multiple of 2 bytes */ + dest_len &= ~1; + + ret = convert_string(ic, CH_UNIX, CH_UTF16, src, src_len, dest, dest_len); + if (ret == (size_t)-1) { + return 0; + } + + len += ret; + + return len; +} + + +/** + * Copy a string from a unix char* src to a UCS2 destination, + * allocating a buffer using talloc(). + * + * @param dest always set at least to NULL + * + * @returns The number of bytes occupied by the string in the destination + * or -1 in case of error. + **/ +_PUBLIC_ ssize_t push_ucs2_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, void **dest, const char *src) +{ + size_t src_len = strlen(src)+1; + *dest = NULL; + return convert_string_talloc(ctx, ic, CH_UNIX, CH_UTF16, src, src_len, dest); +} + + +/** + * Copy a string from a unix char* src to a UTF-8 destination, allocating a buffer using talloc + * + * @param dest always set at least to NULL + * + * @returns The number of bytes occupied by the string in the destination + **/ + +_PUBLIC_ ssize_t push_utf8_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src) +{ + size_t src_len = strlen(src)+1; + *dest = NULL; + return convert_string_talloc(ctx, ic, CH_UNIX, CH_UTF8, src, src_len, (void **)dest); +} + +/** + Copy a string from a ucs2 source to a unix char* destination. + Flags can have: + STR_TERMINATE means the string in src is null terminated. + STR_NOALIGN means don't try to align. + if STR_TERMINATE is set then src_len is ignored if it is -1. + src_len is the length of the source area in bytes + Return the number of bytes occupied by the string in src. + The resulting string in "dest" is always null terminated. +**/ + +static size_t pull_ucs2(struct smb_iconv_convenience *ic, char *dest, const void *src, size_t dest_len, size_t src_len, int flags) +{ + size_t ret; + + if (ucs2_align(NULL, src, flags)) { + src = (const void *)((const char *)src + 1); + if (src_len > 0) + src_len--; + } + + if (flags & STR_TERMINATE) { + if (src_len == (size_t)-1) { + src_len = utf16_len(src); + } else { + src_len = utf16_len_n(src, src_len); + } + } + + /* ucs2 is always a multiple of 2 bytes */ + if (src_len != (size_t)-1) + src_len &= ~1; + + ret = convert_string(ic, CH_UTF16, CH_UNIX, src, src_len, dest, dest_len); + if (dest_len) + dest[MIN(ret, dest_len-1)] = 0; + + return src_len; +} + +/** + * Copy a string from a ASCII src to a unix char * destination, allocating a buffer using talloc + * + * @param dest always set at least to NULL + * + * @returns The number of bytes occupied by the string in the destination + **/ + +_PUBLIC_ ssize_t pull_ascii_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src) +{ + size_t src_len = strlen(src)+1; + *dest = NULL; + return convert_string_talloc(ctx, ic, CH_DOS, CH_UNIX, src, src_len, (void **)dest); +} + +/** + * Copy a string from a UCS2 src to a unix char * destination, allocating a buffer using talloc + * + * @param dest always set at least to NULL + * + * @returns The number of bytes occupied by the string in the destination + **/ + +_PUBLIC_ ssize_t pull_ucs2_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const void *src) +{ + size_t src_len = utf16_len(src); + *dest = NULL; + return convert_string_talloc(ctx, ic, CH_UTF16, CH_UNIX, src, src_len, (void **)dest); +} + +/** + * Copy a string from a UTF-8 src to a unix char * destination, allocating a buffer using talloc + * + * @param dest always set at least to NULL + * + * @returns The number of bytes occupied by the string in the destination + **/ + +_PUBLIC_ ssize_t pull_utf8_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src) +{ + size_t src_len = strlen(src)+1; + *dest = NULL; + return convert_string_talloc(ctx, ic, CH_UTF8, CH_UNIX, src, src_len, (void **)dest); +} + +/** + Copy a string from a char* src to a unicode or ascii + dos codepage destination choosing unicode or ascii based on the + flags in the SMB buffer starting at base_ptr. + Return the number of bytes occupied by the string in the destination. + flags can have: + STR_TERMINATE means include the null termination. + STR_UPPER means uppercase in the destination. + STR_ASCII use ascii even with unicode packet. + STR_NOALIGN means don't do alignment. + dest_len is the maximum length allowed in the destination. If dest_len + is -1 then no maxiumum is used. +**/ + +_PUBLIC_ ssize_t push_string(struct smb_iconv_convenience *ic, + void *dest, const char *src, size_t dest_len, int flags) +{ + if (flags & STR_ASCII) { + return push_ascii(ic, dest, src, dest_len, flags); + } else if (flags & STR_UNICODE) { + return push_ucs2(ic, dest, src, dest_len, flags); + } else { + smb_panic("push_string requires either STR_ASCII or STR_UNICODE flag to be set"); + return -1; + } +} + + +/** + Copy a string from a unicode or ascii source (depending on + the packet flags) to a char* destination. + Flags can have: + STR_TERMINATE means the string in src is null terminated. + STR_UNICODE means to force as unicode. + STR_ASCII use ascii even with unicode packet. + STR_NOALIGN means don't do alignment. + if STR_TERMINATE is set then src_len is ignored is it is -1 + src_len is the length of the source area in bytes. + Return the number of bytes occupied by the string in src. + The resulting string in "dest" is always null terminated. +**/ + +_PUBLIC_ ssize_t pull_string(struct smb_iconv_convenience *ic, + char *dest, const void *src, size_t dest_len, size_t src_len, int flags) +{ + if (flags & STR_ASCII) { + return pull_ascii(ic, dest, src, dest_len, src_len, flags); + } else if (flags & STR_UNICODE) { + return pull_ucs2(ic, dest, src, dest_len, src_len, flags); + } else { + smb_panic("pull_string requires either STR_ASCII or STR_UNICODE flag to be set"); + return -1; + } +} + + +/* + return the unicode codepoint for the next multi-byte CH_UNIX character + in the string + + also return the number of bytes consumed (which tells the caller + how many bytes to skip to get to the next CH_UNIX character) + + return INVALID_CODEPOINT if the next character cannot be converted +*/ +_PUBLIC_ codepoint_t next_codepoint(struct smb_iconv_convenience *ic, + const char *str, size_t *size) +{ + /* it cannot occupy more than 4 bytes in UTF16 format */ + uint8_t buf[4]; + smb_iconv_t descriptor; + size_t ilen_orig; + size_t ilen; + size_t olen; + char *outbuf; + + if ((str[0] & 0x80) == 0) { + *size = 1; + return (codepoint_t)str[0]; + } + + /* we assume that no multi-byte character can take + more than 5 bytes. This is OK as we only + support codepoints up to 1M */ + ilen_orig = strnlen(str, 5); + ilen = ilen_orig; + + descriptor = get_conv_handle(ic, CH_UNIX, CH_UTF16); + if (descriptor == (smb_iconv_t)-1) { + *size = 1; + return INVALID_CODEPOINT; + } + + /* this looks a little strange, but it is needed to cope + with codepoints above 64k */ + olen = 2; + outbuf = (char *)buf; + smb_iconv(descriptor, &str, &ilen, &outbuf, &olen); + if (olen == 2) { + olen = 4; + outbuf = (char *)buf; + smb_iconv(descriptor, &str, &ilen, &outbuf, &olen); + if (olen == 4) { + /* we didn't convert any bytes */ + *size = 1; + return INVALID_CODEPOINT; + } + olen = 4 - olen; + } else { + olen = 2 - olen; + } + + *size = ilen_orig - ilen; + + if (olen == 2) { + return (codepoint_t)SVAL(buf, 0); + } + if (olen == 4) { + /* decode a 4 byte UTF16 character manually */ + return (codepoint_t)0x10000 + + (buf[2] | ((buf[3] & 0x3)<<8) | + (buf[0]<<10) | ((buf[1] & 0x3)<<18)); + } + + /* no other length is valid */ + return INVALID_CODEPOINT; +} + +/* + push a single codepoint into a CH_UNIX string the target string must + be able to hold the full character, which is guaranteed if it is at + least 5 bytes in size. The caller may pass less than 5 bytes if they + are sure the character will fit (for example, you can assume that + uppercase/lowercase of a character will not add more than 1 byte) + + return the number of bytes occupied by the CH_UNIX character, or + -1 on failure +*/ +_PUBLIC_ ssize_t push_codepoint(struct smb_iconv_convenience *ic, + char *str, codepoint_t c) +{ + smb_iconv_t descriptor; + uint8_t buf[4]; + size_t ilen, olen; + const char *inbuf; + + if (c < 128) { + *str = c; + return 1; + } + + descriptor = get_conv_handle(ic, + CH_UTF16, CH_UNIX); + if (descriptor == (smb_iconv_t)-1) { + return -1; + } + + if (c < 0x10000) { + ilen = 2; + olen = 5; + inbuf = (char *)buf; + SSVAL(buf, 0, c); + smb_iconv(descriptor, &inbuf, &ilen, &str, &olen); + if (ilen != 0) { + return -1; + } + return 5 - olen; + } + + c -= 0x10000; + + buf[0] = (c>>10) & 0xFF; + buf[1] = (c>>18) | 0xd8; + buf[2] = c & 0xFF; + buf[3] = ((c>>8) & 0x3) | 0xdc; + + ilen = 4; + olen = 5; + inbuf = (char *)buf; + + smb_iconv(descriptor, &inbuf, &ilen, &str, &olen); + if (ilen != 0) { + return -1; + } + return 5 - olen; +} diff --git a/lib/util/charset/charset.h b/lib/util/charset/charset.h new file mode 100644 index 0000000000..041eaeace7 --- /dev/null +++ b/lib/util/charset/charset.h @@ -0,0 +1,154 @@ +/* + Unix SMB/CIFS implementation. + charset defines + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jelmer Vernooij 2002 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +/* This is a public header file that is installed as part of Samba. + * If you remove any functions or change their signature, update + * the so version number. */ + +#ifndef __CHARSET_H__ +#define __CHARSET_H__ + +#include <talloc.h> + +/* this defines the charset types used in samba */ +typedef enum {CH_UTF16=0, CH_UNIX, CH_DOS, CH_UTF8, CH_UTF16BE} charset_t; + +#define NUM_CHARSETS 5 + +/* + * for each charset we have a function that pulls from that charset to + * a ucs2 buffer, and a function that pushes to a ucs2 buffer + * */ + +struct charset_functions { + const char *name; + size_t (*pull)(void *, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); + size_t (*push)(void *, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); + struct charset_functions *prev, *next; +}; + +/* this type is used for manipulating unicode codepoints */ +typedef uint32_t codepoint_t; + +#define INVALID_CODEPOINT ((codepoint_t)-1) + + +/* generic iconv conversion structure */ +typedef struct smb_iconv_s { + size_t (*direct)(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); + size_t (*pull)(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); + size_t (*push)(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); + void *cd_direct, *cd_pull, *cd_push; +} *smb_iconv_t; + +/* string manipulation flags */ +#define STR_TERMINATE 1 +#define STR_UPPER 2 +#define STR_ASCII 4 +#define STR_UNICODE 8 +#define STR_NOALIGN 16 +#define STR_NO_RANGE_CHECK 32 +#define STR_LEN8BIT 64 +#define STR_TERMINATE_ASCII 128 /* only terminate if ascii */ +#define STR_LEN_NOTERM 256 /* the length field is the unterminated length */ + +struct loadparm_context; +struct smb_iconv_convenience; +extern struct smb_iconv_convenience *global_smb_iconv_convenience; + +/* replace some string functions with multi-byte + versions */ +#define strlower(s) strlower_m(s) +#define strupper(s) strupper_m(s) + +char *strchr_m(const char *s, char c); +size_t strlen_m_term(const char *s); +size_t strlen_m(const char *s); +char *alpha_strcpy(char *dest, const char *src, const char *other_safe_chars, size_t maxlength); +void string_replace_w(char *s, char oldc, char newc); +bool strcsequal_w(const char *s1,const char *s2); +bool strequal_w(const char *s1, const char *s2); +int strncasecmp_m(const char *s1, const char *s2, size_t n); +bool next_token(const char **ptr,char *buff, const char *sep, size_t bufsize); +int strcasecmp_m(const char *s1, const char *s2); +size_t count_chars_w(const char *s, char c); +void strupper_m(char *s); +void strlower_m(char *s); +char *strupper_talloc(TALLOC_CTX *ctx, const char *src); +char *talloc_strdup_upper(TALLOC_CTX *ctx, const char *src); +char *strupper_talloc_n(TALLOC_CTX *ctx, const char *src, size_t n); +char *strlower_talloc(TALLOC_CTX *ctx, const char *src); +bool strhasupper(const char *string); +bool strhaslower(const char *string); +char *strrchr_m(const char *s, char c); +char *strchr_m(const char *s, char c); + +/* codepoints */ +codepoint_t next_codepoint(struct smb_iconv_convenience *ic, + const char *str, size_t *size); +ssize_t push_codepoint(struct smb_iconv_convenience *ic, + char *str, codepoint_t c); +codepoint_t toupper_w(codepoint_t val); +codepoint_t tolower_w(codepoint_t val); +int codepoint_cmpi(codepoint_t c1, codepoint_t c2); +ssize_t push_string(struct smb_iconv_convenience *ic, void *dest, const char *src, size_t dest_len, int flags); +ssize_t pull_string(struct smb_iconv_convenience *ic, + char *dest, const void *src, size_t dest_len, size_t src_len, int flags); +ssize_t convert_string(struct smb_iconv_convenience *ic, + charset_t from, charset_t to, + void const *src, size_t srclen, + void *dest, size_t destlen); +ssize_t convert_string_talloc_descriptor(TALLOC_CTX *ctx, smb_iconv_t descriptor, void const *src, size_t srclen, void **dest); +ssize_t convert_string_talloc(TALLOC_CTX *ctx, + struct smb_iconv_convenience *ic, + charset_t from, charset_t to, + void const *src, size_t srclen, + void **dest); +ssize_t push_ascii_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src); +ssize_t push_ucs2_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, void **dest, const char *src); +ssize_t push_utf8_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src); +ssize_t pull_ascii_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src); +ssize_t pull_ucs2_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const void *src); +ssize_t pull_utf8_talloc(TALLOC_CTX *ctx, struct smb_iconv_convenience *ic, char **dest, const char *src); + +/* iconv */ +smb_iconv_t smb_iconv_open(const char *tocode, const char *fromcode); +int smb_iconv_close(smb_iconv_t cd); +size_t smb_iconv(smb_iconv_t cd, + const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft); +smb_iconv_t smb_iconv_open_ex(TALLOC_CTX *mem_ctx, const char *tocode, + const char *fromcode, bool native_iconv); + +/* iconv convenience */ +struct smb_iconv_convenience *smb_iconv_convenience_init(TALLOC_CTX *mem_ctx, + const char *dos_charset, + const char *unix_charset, + bool native_iconv); + +void load_case_tables(void); +bool charset_register_backend(const void *_funcs); + +#endif /* __CHARSET_H__ */ diff --git a/lib/util/charset/config.m4 b/lib/util/charset/config.m4 new file mode 100644 index 0000000000..453de9fe26 --- /dev/null +++ b/lib/util/charset/config.m4 @@ -0,0 +1,86 @@ +dnl SMB_CHECK_ICONV(hdr, msg, action-if-found,action-if-not-found) +AC_DEFUN(SMB_CHECK_ICONV,[ + AC_MSG_CHECKING($2) + AC_TRY_RUN([#include <stdlib.h> +#include <$1> + +int main() +{ + iconv_t cd = iconv_open("ASCII","UCS-2LE"); + if (cd == 0 || cd == (iconv_t)-1) return -1; + return 0; +} + ], + [AC_MSG_RESULT(yes); $3], + [AC_MSG_RESULT(no); $4], + [AC_MSG_RESULT(cross); $4]) +]) + +dnl SMB_CHECK_ICONV_DIR(dir,action-if-found,action-if-not-found) +AC_DEFUN(SMB_CHECK_ICONV_DIR, +[ + save_CPPFLAGS="$CPPFLAGS" + save_LDFLAGS="$LDFLAGS" + save_LIBS="$LIBS" + CPPFLAGS="-I$1/include" + LDFLAGS="-L$1/lib" + LIBS=-liconv + + SMB_CHECK_ICONV(iconv.h,Whether iconv.h is present,[ AC_DEFINE(HAVE_ICONV_H,1,[Whether iconv.h is present]) $2 ], [ + LIBS=-lgiconv + SMB_CHECK_ICONV(giconv.h,Whether giconv.h is present, [AC_DEFINE(HAVE_GICONV_H,1,[Whether giconv.h is present]) $2],[$3]) + ]) + + CPPFLAGS="$save_CPPFLAGS" + LDFLAGS="$save_LDFLAGS" + LIBS="$save_LIBS" +]) + +ICONV_FOUND=no +LOOK_DIRS="/usr /usr/local /sw" +AC_ARG_WITH(libiconv, +[ --with-libiconv=BASEDIR Use libiconv in BASEDIR/lib and BASEDIR/include (default=auto) ], +[ + if test "$withval" = "no" ; then + AC_MSG_ERROR(I won't take no for an answer) + else + if test "$withval" != "yes" ; then + SMB_CHECK_ICONV_DIR($withval, [ + ICONV_FOUND=yes; + ICONV_CPPFLAGS="$CPPFLAGS" + ICONV_LIBS="$LIBS" + ICONV_LDFLAGS="$LDFLAGS" + ], [AC_MSG_ERROR([No iconv library found in $withval])]) + fi + fi +]) + +if test x$ICONV_FOUND = xno; then + SMB_CHECK_ICONV(iconv.h, + [Whether iconv.h is present], + [AC_DEFINE(HAVE_ICONV_H,1,[Whether iconv.h is present]) ICONV_FOUND=yes]) +fi + +for i in $LOOK_DIRS ; do + if test x$ICONV_FOUND = xyes; then + break + fi + + SMB_CHECK_ICONV_DIR($i, [ + ICONV_FOUND=yes + ICONV_CPPFLAGS="$CPPFLAGS" + ICONV_LIBS="$LIBS" + ICONV_LDFLAGS="$LDFLAGS" + ], []) +done + +if test x"$ICONV_FOUND" = x"no"; then + AC_MSG_WARN([Sufficient support for iconv function was not found. + Install libiconv from http://www.gnu.org/software/libiconv/ for better charset compatibility!]) + SMB_ENABLE(ICONV,NO) +else + AC_DEFINE(HAVE_NATIVE_ICONV,1,[Whether external iconv is available]) + SMB_ENABLE(ICONV,YES) +fi + +SMB_EXT_LIB(ICONV,[${ICONV_LIBS}],[${ICONV_CFLAGS}],[${ICONV_CPPFLAGS}],[${ICONV_LDFLAGS}]) diff --git a/lib/util/charset/config.mk b/lib/util/charset/config.mk new file mode 100644 index 0000000000..12c2f5f321 --- /dev/null +++ b/lib/util/charset/config.mk @@ -0,0 +1,13 @@ +################################################ +# Start SUBSYSTEM CHARSET +[SUBSYSTEM::CHARSET] +PUBLIC_DEPENDENCIES = ICONV +PRIVATE_DEPENDENCIES = DYNCONFIG +# End SUBSYSTEM CHARSET +################################################ + +CHARSET_OBJ_FILES = $(addprefix $(libcharsetsrcdir)/, iconv.o charcnv.o util_unistr.o) + +PUBLIC_HEADERS += $(libcharsetsrcdir)/charset.h + +$(eval $(call proto_header_template,$(libcharsetsrcdir)/charset_proto.h,$(CHARSET_OBJ_FILES:.o=.c))) diff --git a/lib/util/charset/iconv.c b/lib/util/charset/iconv.c new file mode 100644 index 0000000000..150383e7f9 --- /dev/null +++ b/lib/util/charset/iconv.c @@ -0,0 +1,711 @@ +/* + Unix SMB/CIFS implementation. + minimal iconv implementation + Copyright (C) Andrew Tridgell 2001 + Copyright (C) Jelmer Vernooij 2002 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "../lib/util/dlinklist.h" +#include "system/iconv.h" +#include "system/filesys.h" +#include "param/param.h" + + +/** + * @file + * + * @brief Samba wrapper/stub for iconv character set conversion. + * + * iconv is the XPG2 interface for converting between character + * encodings. This file provides a Samba wrapper around it, and also + * a simple reimplementation that is used if the system does not + * implement iconv. + * + * Samba only works with encodings that are supersets of ASCII: ascii + * characters like whitespace can be tested for directly, multibyte + * sequences start with a byte with the high bit set, and strings are + * terminated by a nul byte. + * + * Note that the only function provided by iconv is conversion between + * characters. It doesn't directly support operations like + * uppercasing or comparison. We have to convert to UTF-16LE and + * compare there. + * + * @sa Samba Developers Guide + **/ + +static size_t ascii_pull (void *,const char **, size_t *, char **, size_t *); +static size_t ascii_push (void *,const char **, size_t *, char **, size_t *); +static size_t utf8_pull (void *,const char **, size_t *, char **, size_t *); +static size_t utf8_push (void *,const char **, size_t *, char **, size_t *); +static size_t ucs2hex_pull(void *,const char **, size_t *, char **, size_t *); +static size_t ucs2hex_push(void *,const char **, size_t *, char **, size_t *); +static size_t iconv_copy (void *,const char **, size_t *, char **, size_t *); +static size_t iconv_swab (void *,const char **, size_t *, char **, size_t *); + +static const struct charset_functions builtin_functions[] = { + /* windows is closest to UTF-16 */ + {"UCS-2LE", iconv_copy, iconv_copy}, + {"UTF-16LE", iconv_copy, iconv_copy}, + {"UCS-2BE", iconv_swab, iconv_swab}, + {"UTF-16BE", iconv_swab, iconv_swab}, + + /* we include the UTF-8 alias to cope with differing locale settings */ + {"UTF8", utf8_pull, utf8_push}, + {"UTF-8", utf8_pull, utf8_push}, + {"ASCII", ascii_pull, ascii_push}, + {"UCS2-HEX", ucs2hex_pull, ucs2hex_push} +}; + +static struct charset_functions *charsets = NULL; + +bool charset_register_backend(const void *_funcs) +{ + struct charset_functions *funcs = (struct charset_functions *)memdup(_funcs,sizeof(struct charset_functions)); + struct charset_functions *c; + + /* Check whether we already have this charset... */ + for (c = charsets; c != NULL; c = c->next) { + if(!strcasecmp(c->name, funcs->name)) { + DEBUG(2, ("Duplicate charset %s, not registering\n", funcs->name)); + return false; + } + } + + funcs->next = funcs->prev = NULL; + DLIST_ADD(charsets, funcs); + return true; +} + +#ifdef HAVE_NATIVE_ICONV +/* if there was an error then reset the internal state, + this ensures that we don't have a shift state remaining for + character sets like SJIS */ +static size_t sys_iconv(void *cd, + const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + size_t ret = iconv((iconv_t)cd, + discard_const_p(char *, inbuf), inbytesleft, + outbuf, outbytesleft); + if (ret == (size_t)-1) iconv(cd, NULL, NULL, NULL, NULL); + return ret; +} +#endif + +/** + * This is a simple portable iconv() implementaion. + * + * It only knows about a very small number of character sets - just + * enough that Samba works on systems that don't have iconv. + **/ +_PUBLIC_ size_t smb_iconv(smb_iconv_t cd, + const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + char cvtbuf[2048]; + size_t bufsize; + + /* in many cases we can go direct */ + if (cd->direct) { + return cd->direct(cd->cd_direct, + inbuf, inbytesleft, outbuf, outbytesleft); + } + + + /* otherwise we have to do it chunks at a time */ + while (*inbytesleft > 0) { + char *bufp1 = cvtbuf; + const char *bufp2 = cvtbuf; + + bufsize = sizeof(cvtbuf); + + if (cd->pull(cd->cd_pull, + inbuf, inbytesleft, &bufp1, &bufsize) == -1 + && errno != E2BIG) return -1; + + bufsize = sizeof(cvtbuf) - bufsize; + + if (cd->push(cd->cd_push, + &bufp2, &bufsize, + outbuf, outbytesleft) == -1) return -1; + } + + return 0; +} + +static bool is_utf16(const char *name) +{ + return strcasecmp(name, "UCS-2LE") == 0 || + strcasecmp(name, "UTF-16LE") == 0; +} + + + +_PUBLIC_ smb_iconv_t smb_iconv_open_ex(TALLOC_CTX *mem_ctx, const char *tocode, + const char *fromcode, bool native_iconv) +{ + smb_iconv_t ret; + const struct charset_functions *from=NULL, *to=NULL; + int i; + + ret = (smb_iconv_t)talloc_named(mem_ctx, + sizeof(*ret), + "iconv(%s,%s)", tocode, fromcode); + if (!ret) { + errno = ENOMEM; + return (smb_iconv_t)-1; + } + memset(ret, 0, sizeof(*ret)); + + /* check for the simplest null conversion */ + if (strcmp(fromcode, tocode) == 0) { + ret->direct = iconv_copy; + return ret; + } + + for (i=0;i<ARRAY_SIZE(builtin_functions);i++) { + if (strcasecmp(fromcode, builtin_functions[i].name) == 0) { + from = &builtin_functions[i]; + } + if (strcasecmp(tocode, builtin_functions[i].name) == 0) { + to = &builtin_functions[i]; + } + } + + if (from == NULL) { + for (from=charsets; from; from=from->next) { + if (strcasecmp(from->name, fromcode) == 0) break; + } + } + + if (to == NULL) { + for (to=charsets; to; to=to->next) { + if (strcasecmp(to->name, tocode) == 0) break; + } + } + +#ifdef HAVE_NATIVE_ICONV + if ((!from || !to) && !native_iconv) { + goto failed; + } + if (!from) { + ret->pull = sys_iconv; + ret->cd_pull = iconv_open("UTF-16LE", fromcode); + if (ret->cd_pull == (iconv_t)-1) + ret->cd_pull = iconv_open("UCS-2LE", fromcode); + if (ret->cd_pull == (iconv_t)-1) goto failed; + } + + if (!to) { + ret->push = sys_iconv; + ret->cd_push = iconv_open(tocode, "UTF-16LE"); + if (ret->cd_push == (iconv_t)-1) + ret->cd_push = iconv_open(tocode, "UCS-2LE"); + if (ret->cd_push == (iconv_t)-1) goto failed; + } +#else + if (!from || !to) { + goto failed; + } +#endif + + /* check for conversion to/from ucs2 */ + if (is_utf16(fromcode) && to) { + ret->direct = to->push; + return ret; + } + if (is_utf16(tocode) && from) { + ret->direct = from->pull; + return ret; + } + +#ifdef HAVE_NATIVE_ICONV + if (is_utf16(fromcode)) { + ret->direct = sys_iconv; + ret->cd_direct = ret->cd_push; + ret->cd_push = NULL; + return ret; + } + if (is_utf16(tocode)) { + ret->direct = sys_iconv; + ret->cd_direct = ret->cd_pull; + ret->cd_pull = NULL; + return ret; + } +#endif + + /* the general case has to go via a buffer */ + if (!ret->pull) ret->pull = from->pull; + if (!ret->push) ret->push = to->push; + return ret; + +failed: + talloc_free(ret); + errno = EINVAL; + return (smb_iconv_t)-1; +} + +/* + simple iconv_open() wrapper + */ +_PUBLIC_ smb_iconv_t smb_iconv_open(const char *tocode, const char *fromcode) +{ + return smb_iconv_open_ex(NULL, tocode, fromcode, true); +} + +/* + simple iconv_close() wrapper +*/ +_PUBLIC_ int smb_iconv_close(smb_iconv_t cd) +{ +#ifdef HAVE_NATIVE_ICONV + if (cd->cd_direct) iconv_close((iconv_t)cd->cd_direct); + if (cd->cd_pull) iconv_close((iconv_t)cd->cd_pull); + if (cd->cd_push) iconv_close((iconv_t)cd->cd_push); +#endif + + talloc_free(cd); + return 0; +} + + +/********************************************************************** + the following functions implement the builtin character sets in Samba + and also the "test" character sets that are designed to test + multi-byte character set support for english users +***********************************************************************/ +static size_t ascii_pull(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + while (*inbytesleft >= 1 && *outbytesleft >= 2) { + (*outbuf)[0] = (*inbuf)[0]; + (*outbuf)[1] = 0; + (*inbytesleft) -= 1; + (*outbytesleft) -= 2; + (*inbuf) += 1; + (*outbuf) += 2; + } + + if (*inbytesleft > 0) { + errno = E2BIG; + return -1; + } + + return 0; +} + +static size_t ascii_push(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + int ir_count=0; + + while (*inbytesleft >= 2 && *outbytesleft >= 1) { + (*outbuf)[0] = (*inbuf)[0] & 0x7F; + if ((*inbuf)[1]) ir_count++; + (*inbytesleft) -= 2; + (*outbytesleft) -= 1; + (*inbuf) += 2; + (*outbuf) += 1; + } + + if (*inbytesleft == 1) { + errno = EINVAL; + return -1; + } + + if (*inbytesleft > 1) { + errno = E2BIG; + return -1; + } + + return ir_count; +} + + +static size_t ucs2hex_pull(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + while (*inbytesleft >= 1 && *outbytesleft >= 2) { + uint_t v; + + if ((*inbuf)[0] != '@') { + /* seven bit ascii case */ + (*outbuf)[0] = (*inbuf)[0]; + (*outbuf)[1] = 0; + (*inbytesleft) -= 1; + (*outbytesleft) -= 2; + (*inbuf) += 1; + (*outbuf) += 2; + continue; + } + /* it's a hex character */ + if (*inbytesleft < 5) { + errno = EINVAL; + return -1; + } + + if (sscanf(&(*inbuf)[1], "%04x", &v) != 1) { + errno = EILSEQ; + return -1; + } + + (*outbuf)[0] = v&0xff; + (*outbuf)[1] = v>>8; + (*inbytesleft) -= 5; + (*outbytesleft) -= 2; + (*inbuf) += 5; + (*outbuf) += 2; + } + + if (*inbytesleft > 0) { + errno = E2BIG; + return -1; + } + + return 0; +} + +static size_t ucs2hex_push(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + while (*inbytesleft >= 2 && *outbytesleft >= 1) { + char buf[6]; + + if ((*inbuf)[1] == 0 && + ((*inbuf)[0] & 0x80) == 0 && + (*inbuf)[0] != '@') { + (*outbuf)[0] = (*inbuf)[0]; + (*inbytesleft) -= 2; + (*outbytesleft) -= 1; + (*inbuf) += 2; + (*outbuf) += 1; + continue; + } + if (*outbytesleft < 5) { + errno = E2BIG; + return -1; + } + snprintf(buf, 6, "@%04x", SVAL(*inbuf, 0)); + memcpy(*outbuf, buf, 5); + (*inbytesleft) -= 2; + (*outbytesleft) -= 5; + (*inbuf) += 2; + (*outbuf) += 5; + } + + if (*inbytesleft == 1) { + errno = EINVAL; + return -1; + } + + if (*inbytesleft > 1) { + errno = E2BIG; + return -1; + } + + return 0; +} + +static size_t iconv_swab(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + int n; + + n = MIN(*inbytesleft, *outbytesleft); + + swab(*inbuf, *outbuf, (n&~1)); + if (n&1) { + (*outbuf)[n-1] = 0; + } + + (*inbytesleft) -= n; + (*outbytesleft) -= n; + (*inbuf) += n; + (*outbuf) += n; + + if (*inbytesleft > 0) { + errno = E2BIG; + return -1; + } + + return 0; +} + + +static size_t iconv_copy(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + int n; + + n = MIN(*inbytesleft, *outbytesleft); + + memmove(*outbuf, *inbuf, n); + + (*inbytesleft) -= n; + (*outbytesleft) -= n; + (*inbuf) += n; + (*outbuf) += n; + + if (*inbytesleft > 0) { + errno = E2BIG; + return -1; + } + + return 0; +} + +/* + this takes a UTF8 sequence and produces a UTF16 sequence + */ +static size_t utf8_pull(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + size_t in_left=*inbytesleft, out_left=*outbytesleft; + const uint8_t *c = (const uint8_t *)*inbuf; + uint8_t *uc = (uint8_t *)*outbuf; + + while (in_left >= 1 && out_left >= 2) { + if ((c[0] & 0x80) == 0) { + uc[0] = c[0]; + uc[1] = 0; + c += 1; + in_left -= 1; + out_left -= 2; + uc += 2; + continue; + } + + if ((c[0] & 0xe0) == 0xc0) { + if (in_left < 2 || + (c[1] & 0xc0) != 0x80) { + errno = EILSEQ; + goto error; + } + uc[1] = (c[0]>>2) & 0x7; + uc[0] = (c[0]<<6) | (c[1]&0x3f); + c += 2; + in_left -= 2; + out_left -= 2; + uc += 2; + continue; + } + + if ((c[0] & 0xf0) == 0xe0) { + if (in_left < 3 || + (c[1] & 0xc0) != 0x80 || + (c[2] & 0xc0) != 0x80) { + errno = EILSEQ; + goto error; + } + uc[1] = ((c[0]&0xF)<<4) | ((c[1]>>2)&0xF); + uc[0] = (c[1]<<6) | (c[2]&0x3f); + c += 3; + in_left -= 3; + out_left -= 2; + uc += 2; + continue; + } + + if ((c[0] & 0xf8) == 0xf0) { + unsigned int codepoint; + if (in_left < 4 || + (c[1] & 0xc0) != 0x80 || + (c[2] & 0xc0) != 0x80 || + (c[3] & 0xc0) != 0x80) { + errno = EILSEQ; + goto error; + } + codepoint = + (c[3]&0x3f) | + ((c[2]&0x3f)<<6) | + ((c[1]&0x3f)<<12) | + ((c[0]&0x7)<<18); + if (codepoint < 0x10000) { + /* accept UTF-8 characters that are not + minimally packed, but pack the result */ + uc[0] = (codepoint & 0xFF); + uc[1] = (codepoint >> 8); + c += 4; + in_left -= 4; + out_left -= 2; + uc += 2; + continue; + } + + codepoint -= 0x10000; + + if (out_left < 4) { + errno = E2BIG; + goto error; + } + + uc[0] = (codepoint>>10) & 0xFF; + uc[1] = (codepoint>>18) | 0xd8; + uc[2] = codepoint & 0xFF; + uc[3] = ((codepoint>>8) & 0x3) | 0xdc; + c += 4; + in_left -= 4; + out_left -= 4; + uc += 4; + continue; + } + + /* we don't handle 5 byte sequences */ + errno = EINVAL; + goto error; + } + + if (in_left > 0) { + errno = E2BIG; + goto error; + } + + *inbytesleft = in_left; + *outbytesleft = out_left; + *inbuf = (const char *)c; + *outbuf = (char *)uc; + return 0; + +error: + *inbytesleft = in_left; + *outbytesleft = out_left; + *inbuf = (const char *)c; + *outbuf = (char *)uc; + return -1; +} + + +/* + this takes a UTF16 sequence and produces a UTF8 sequence + */ +static size_t utf8_push(void *cd, const char **inbuf, size_t *inbytesleft, + char **outbuf, size_t *outbytesleft) +{ + size_t in_left=*inbytesleft, out_left=*outbytesleft; + uint8_t *c = (uint8_t *)*outbuf; + const uint8_t *uc = (const uint8_t *)*inbuf; + + while (in_left >= 2 && out_left >= 1) { + unsigned int codepoint; + + if (uc[1] == 0 && !(uc[0] & 0x80)) { + /* simplest case */ + c[0] = uc[0]; + in_left -= 2; + out_left -= 1; + uc += 2; + c += 1; + continue; + } + + if ((uc[1]&0xf8) == 0) { + /* next simplest case */ + if (out_left < 2) { + errno = E2BIG; + goto error; + } + c[0] = 0xc0 | (uc[0]>>6) | (uc[1]<<2); + c[1] = 0x80 | (uc[0] & 0x3f); + in_left -= 2; + out_left -= 2; + uc += 2; + c += 2; + continue; + } + + if ((uc[1] & 0xfc) == 0xdc) { + /* its the second part of a 4 byte sequence. Illegal */ + if (in_left < 4) { + errno = EINVAL; + } else { + errno = EILSEQ; + } + goto error; + } + + if ((uc[1] & 0xfc) != 0xd8) { + codepoint = uc[0] | (uc[1]<<8); + if (out_left < 3) { + errno = E2BIG; + goto error; + } + c[0] = 0xe0 | (codepoint >> 12); + c[1] = 0x80 | ((codepoint >> 6) & 0x3f); + c[2] = 0x80 | (codepoint & 0x3f); + + in_left -= 2; + out_left -= 3; + uc += 2; + c += 3; + continue; + } + + /* its the first part of a 4 byte sequence */ + if (in_left < 4) { + errno = EINVAL; + goto error; + } + if ((uc[3] & 0xfc) != 0xdc) { + errno = EILSEQ; + goto error; + } + codepoint = 0x10000 + (uc[2] | ((uc[3] & 0x3)<<8) | + (uc[0]<<10) | ((uc[1] & 0x3)<<18)); + + if (out_left < 4) { + errno = E2BIG; + goto error; + } + c[0] = 0xf0 | (codepoint >> 18); + c[1] = 0x80 | ((codepoint >> 12) & 0x3f); + c[2] = 0x80 | ((codepoint >> 6) & 0x3f); + c[3] = 0x80 | (codepoint & 0x3f); + + in_left -= 4; + out_left -= 4; + uc += 4; + c += 4; + } + + if (in_left == 1) { + errno = EINVAL; + goto error; + } + + if (in_left > 1) { + errno = E2BIG; + goto error; + } + + *inbytesleft = in_left; + *outbytesleft = out_left; + *inbuf = (const char *)uc; + *outbuf = (char *)c; + + return 0; + +error: + *inbytesleft = in_left; + *outbytesleft = out_left; + *inbuf = (const char *)uc; + *outbuf = (char *)c; + return -1; +} + + + diff --git a/lib/util/charset/tests/charset.c b/lib/util/charset/tests/charset.c new file mode 100644 index 0000000000..5e42ca2932 --- /dev/null +++ b/lib/util/charset/tests/charset.c @@ -0,0 +1,272 @@ +/* + Unix SMB/CIFS implementation. + test suite for the charcnv functions + + Copyright (C) Jelmer Vernooij 2007 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" + +static bool test_toupper_w(struct torture_context *tctx) +{ + torture_assert_int_equal(tctx, toupper_w('c'), 'C', "c"); + torture_assert_int_equal(tctx, toupper_w('Z'), 'Z', "z"); + torture_assert_int_equal(tctx, toupper_w(0xFFFF4565), 0xFFFF4565, "0xFFFF4565"); + return true; +} + +static bool test_tolower_w(struct torture_context *tctx) +{ + torture_assert_int_equal(tctx, tolower_w('C'), 'c', "c"); + torture_assert_int_equal(tctx, tolower_w('z'), 'z', "z"); + torture_assert_int_equal(tctx, tolower_w(0xFFFF4565), 0xFFFF4565, "0xFFFF4565"); + return true; +} + +static bool test_codepoint_cmpi(struct torture_context *tctx) +{ + torture_assert_int_equal(tctx, codepoint_cmpi('a', 'a'), 0, "same char"); + torture_assert_int_equal(tctx, codepoint_cmpi('A', 'a'), 0, "upcase version"); + torture_assert_int_equal(tctx, codepoint_cmpi('b', 'a'), 1, "right diff"); + torture_assert_int_equal(tctx, codepoint_cmpi('a', 'b'), -1, "right diff"); + return true; +} + +static bool test_strcasecmp_m(struct torture_context *tctx) +{ + torture_assert(tctx, strcasecmp_m("foo", "bar") != 0, "different strings"); + torture_assert(tctx, strcasecmp_m("foo", "foo") == 0, "same case strings"); + torture_assert(tctx, strcasecmp_m("foo", "Foo") == 0, "different case strings"); + torture_assert(tctx, strcasecmp_m(NULL, "Foo") != 0, "one NULL"); + torture_assert(tctx, strcasecmp_m("foo", NULL) != 0, "other NULL"); + torture_assert(tctx, strcasecmp_m(NULL, NULL) == 0, "both NULL"); + return true; +} + + +static bool test_strequal_w(struct torture_context *tctx) +{ + torture_assert(tctx, !strequal_w("foo", "bar"), "different strings"); + torture_assert(tctx, strequal_w("foo", "foo"), "same case strings"); + torture_assert(tctx, strequal_w("foo", "Foo"), "different case strings"); + torture_assert(tctx, !strequal_w(NULL, "Foo"), "one NULL"); + torture_assert(tctx, !strequal_w("foo", NULL), "other NULL"); + torture_assert(tctx, strequal_w(NULL, NULL), "both NULL"); + return true; +} + +static bool test_strcsequal_w(struct torture_context *tctx) +{ + torture_assert(tctx, !strcsequal_w("foo", "bar"), "different strings"); + torture_assert(tctx, strcsequal_w("foo", "foo"), "same case strings"); + torture_assert(tctx, !strcsequal_w("foo", "Foo"), "different case strings"); + torture_assert(tctx, !strcsequal_w(NULL, "Foo"), "one NULL"); + torture_assert(tctx, !strcsequal_w("foo", NULL), "other NULL"); + torture_assert(tctx, strcsequal_w(NULL, NULL), "both NULL"); + return true; +} + +static bool test_string_replace_w(struct torture_context *tctx) +{ + char data[6] = "bla"; + string_replace_w(data, 'b', 'c'); + torture_assert_str_equal(tctx, data, "cla", "first char replaced"); + memcpy(data, "bab", 4); + string_replace_w(data, 'b', 'c'); + torture_assert_str_equal(tctx, data, "cac", "other chars replaced"); + memcpy(data, "bba", 4); + string_replace_w(data, 'b', 'c'); + torture_assert_str_equal(tctx, data, "cca", "other chars replaced"); + memcpy(data, "blala", 6); + string_replace_w(data, 'o', 'c'); + torture_assert_str_equal(tctx, data, "blala", "no chars replaced"); + string_replace_w(NULL, 'b', 'c'); + return true; +} + +static bool test_strncasecmp_m(struct torture_context *tctx) +{ + torture_assert(tctx, strncasecmp_m("foo", "bar", 3) != 0, "different strings"); + torture_assert(tctx, strncasecmp_m("foo", "foo", 3) == 0, "same case strings"); + torture_assert(tctx, strncasecmp_m("foo", "Foo", 3) == 0, "different case strings"); + torture_assert(tctx, strncasecmp_m("fool", "Foo", 3) == 0, "different case strings"); + torture_assert(tctx, strncasecmp_m("fool", "Fool", 40) == 0, "over size"); + torture_assert(tctx, strncasecmp_m("BLA", "Fool", 0) == 0, "empty"); + torture_assert(tctx, strncasecmp_m(NULL, "Foo", 3) != 0, "one NULL"); + torture_assert(tctx, strncasecmp_m("foo", NULL, 3) != 0, "other NULL"); + torture_assert(tctx, strncasecmp_m(NULL, NULL, 3) == 0, "both NULL"); + return true; +} + +static bool test_next_token_null(struct torture_context *tctx) +{ + char buf[20]; + torture_assert(tctx, !next_token(NULL, buf, " ", 20), "null ptr works"); + return true; +} + +static bool test_next_token(struct torture_context *tctx) +{ + const char *teststr = "foo bar bla"; + char buf[20]; + torture_assert(tctx, next_token(&teststr, buf, " ", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "foo", "token matches"); + torture_assert_str_equal(tctx, teststr, "bar bla", "ptr modified correctly"); + + torture_assert(tctx, next_token(&teststr, buf, " ", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "bar", "token matches"); + torture_assert_str_equal(tctx, teststr, "bla", "ptr modified correctly"); + + torture_assert(tctx, next_token(&teststr, buf, " ", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "bla", "token matches"); + torture_assert_str_equal(tctx, teststr, "", "ptr modified correctly"); + + torture_assert(tctx, !next_token(&teststr, buf, " ", 20), "finding token doesn't work"); + return true; +} + +static bool test_next_token_implicit_sep(struct torture_context *tctx) +{ + const char *teststr = "foo\tbar\n bla"; + char buf[20]; + torture_assert(tctx, next_token(&teststr, buf, NULL, 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "foo", "token matches"); + torture_assert_str_equal(tctx, teststr, "bar\n bla", "ptr modified correctly"); + + torture_assert(tctx, next_token(&teststr, buf, NULL, 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "bar", "token matches"); + torture_assert_str_equal(tctx, teststr, " bla", "ptr modified correctly"); + + torture_assert(tctx, next_token(&teststr, buf, NULL, 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "bla", "token matches"); + torture_assert_str_equal(tctx, teststr, "", "ptr modified correctly"); + + torture_assert(tctx, !next_token(&teststr, buf, NULL, 20), "finding token doesn't work"); + return true; +} + +static bool test_next_token_seps(struct torture_context *tctx) +{ + const char *teststr = ",foo bla"; + char buf[20]; + torture_assert(tctx, next_token(&teststr, buf, ",", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "foo bla", "token matches"); + torture_assert_str_equal(tctx, teststr, "", "ptr modified correctly"); + + torture_assert(tctx, !next_token(&teststr, buf, ",", 20), "finding token doesn't work"); + return true; +} + +static bool test_next_token_quotes(struct torture_context *tctx) +{ + const char *teststr = "\"foo bar\" bla"; + char buf[20]; + torture_assert(tctx, next_token(&teststr, buf, " ", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "foo bar", "token matches"); + torture_assert_str_equal(tctx, teststr, "bla", "ptr modified correctly"); + + torture_assert(tctx, next_token(&teststr, buf, " ", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "bla", "token matches"); + torture_assert_str_equal(tctx, teststr, "", "ptr modified correctly"); + + torture_assert(tctx, !next_token(&teststr, buf, " ", 20), "finding token doesn't work"); + return true; +} + +static bool test_next_token_quote_wrong(struct torture_context *tctx) +{ + const char *teststr = "\"foo bar bla"; + char buf[20]; + torture_assert(tctx, next_token(&teststr, buf, " ", 20), "finding token works"); + torture_assert_str_equal(tctx, buf, "foo bar bla", "token matches"); + torture_assert_str_equal(tctx, teststr, "", "ptr modified correctly"); + + torture_assert(tctx, !next_token(&teststr, buf, " ", 20), "finding token doesn't work"); + return true; +} + +static bool test_strlen_m(struct torture_context *tctx) +{ + torture_assert_int_equal(tctx, strlen_m("foo"), 3, "simple len"); + torture_assert_int_equal(tctx, strlen_m("foo\x83l"), 6, "extended len"); + torture_assert_int_equal(tctx, strlen_m(NULL), 0, "NULL"); + return true; +} + +static bool test_strlen_m_term(struct torture_context *tctx) +{ + torture_assert_int_equal(tctx, strlen_m_term("foo"), 4, "simple len"); + torture_assert_int_equal(tctx, strlen_m_term("foo\x83l"), 7, "extended len"); + torture_assert_int_equal(tctx, strlen_m(NULL), 0, "NULL"); + return true; +} + +static bool test_strhaslower(struct torture_context *tctx) +{ + torture_assert(tctx, strhaslower("a"), "one low char"); + torture_assert(tctx, strhaslower("aB"), "one low, one up char"); + torture_assert(tctx, !strhaslower("B"), "one up char"); + torture_assert(tctx, !strhaslower(""), "empty string"); + torture_assert(tctx, !strhaslower("3"), "one digit"); + return true; +} + +static bool test_strhasupper(struct torture_context *tctx) +{ + torture_assert(tctx, strhasupper("B"), "one up char"); + torture_assert(tctx, strhasupper("aB"), "one low, one up char"); + torture_assert(tctx, !strhasupper("a"), "one low char"); + torture_assert(tctx, !strhasupper(""), "empty string"); + torture_assert(tctx, !strhasupper("3"), "one digit"); + return true; +} + +static bool test_count_chars_w(struct torture_context *tctx) +{ + torture_assert_int_equal(tctx, count_chars_w("foo", 'o'), 2, "simple"); + torture_assert_int_equal(tctx, count_chars_w("", 'o'), 0, "empty"); + torture_assert_int_equal(tctx, count_chars_w("bla", 'o'), 0, "none"); + torture_assert_int_equal(tctx, count_chars_w("bla", '\0'), 0, "null"); + return true; +} + +struct torture_suite *torture_local_charset(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "CHARSET"); + + torture_suite_add_simple_test(suite, "toupper_w", test_toupper_w); + torture_suite_add_simple_test(suite, "tolower_w", test_tolower_w); + torture_suite_add_simple_test(suite, "codepoint_cmpi", test_codepoint_cmpi); + torture_suite_add_simple_test(suite, "strcasecmp_m", test_strcasecmp_m); + torture_suite_add_simple_test(suite, "strequal_w", test_strequal_w); + torture_suite_add_simple_test(suite, "strcsequal_w", test_strcsequal_w); + torture_suite_add_simple_test(suite, "string_replace_w", test_string_replace_w); + torture_suite_add_simple_test(suite, "strncasecmp_m", test_strncasecmp_m); + torture_suite_add_simple_test(suite, "next_token", test_next_token); + torture_suite_add_simple_test(suite, "next_token_null", test_next_token_null); + torture_suite_add_simple_test(suite, "next_token_implicit_sep", test_next_token_implicit_sep); + torture_suite_add_simple_test(suite, "next_token_quotes", test_next_token_quotes); + torture_suite_add_simple_test(suite, "next_token_seps", test_next_token_seps); + torture_suite_add_simple_test(suite, "next_token_quote_wrong", test_next_token_quote_wrong); + torture_suite_add_simple_test(suite, "strlen_m", test_strlen_m); + torture_suite_add_simple_test(suite, "strlen_m_term", test_strlen_m_term); + torture_suite_add_simple_test(suite, "strhaslower", test_strhaslower); + torture_suite_add_simple_test(suite, "strhasupper", test_strhasupper); + torture_suite_add_simple_test(suite, "count_chars_w", test_count_chars_w); + + return suite; +} diff --git a/lib/util/charset/tests/iconv.c b/lib/util/charset/tests/iconv.c new file mode 100644 index 0000000000..aeb42c2fa1 --- /dev/null +++ b/lib/util/charset/tests/iconv.c @@ -0,0 +1,424 @@ +/* + Unix SMB/CIFS implementation. + + local testing of iconv routines. This tests the system iconv code against + the built-in iconv code + + Copyright (C) Andrew Tridgell 2004 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "torture/torture.h" +#include "system/iconv.h" +#include "system/time.h" +#include "libcli/raw/libcliraw.h" +#include "param/param.h" +#include "torture/util.h" + +#if HAVE_NATIVE_ICONV + +static bool iconv_untestable(struct torture_context *tctx) +{ + iconv_t cd; + + if (!lp_parm_bool(tctx->lp_ctx, NULL, "iconv", "native", true)) + torture_skip(tctx, "system iconv disabled - skipping test"); + + cd = iconv_open("UTF-16LE", "UCS-4LE"); + if (cd == (iconv_t)-1) + torture_skip(tctx, "unable to test - system iconv library does not support UTF-16LE -> UCS-4LE"); + iconv_close(cd); + + cd = iconv_open("UTF-16LE", "CP850"); + if (cd == (iconv_t)-1) + torture_skip(tctx, "unable to test - system iconv library does not support UTF-16LE -> CP850\n"); + iconv_close(cd); + + return false; +} + +/* + generate a UTF-16LE buffer for a given unicode codepoint +*/ +static int gen_codepoint_utf16(unsigned int codepoint, + char *buf, size_t *size) +{ + static iconv_t cd; + uint8_t in[4]; + char *ptr_in; + size_t size_in, size_out, ret; + if (!cd) { + cd = iconv_open("UTF-16LE", "UCS-4LE"); + if (cd == (iconv_t)-1) { + cd = NULL; + return -1; + } + } + + in[0] = codepoint & 0xFF; + in[1] = (codepoint>>8) & 0xFF; + in[2] = (codepoint>>16) & 0xFF; + in[3] = (codepoint>>24) & 0xFF; + + ptr_in = (char *)in; + size_in = 4; + size_out = 8; + + ret = iconv(cd, &ptr_in, &size_in, &buf, &size_out); + + *size = 8 - size_out; + + return ret; +} + + +/* + work out the unicode codepoint of the first UTF-8 character in the buffer +*/ +static unsigned int get_codepoint(char *buf, size_t size, const char *charset) +{ + iconv_t cd; + uint8_t out[4]; + char *ptr_out; + size_t size_out, size_in, ret; + + cd = iconv_open("UCS-4LE", charset); + + size_in = size; + ptr_out = (char *)out; + size_out = sizeof(out); + memset(out, 0, sizeof(out)); + + ret = iconv(cd, &buf, &size_in, &ptr_out, &size_out); + + iconv_close(cd); + + return out[0] | (out[1]<<8) | (out[2]<<16) | (out[3]<<24); +} + +/* + display a buffer with name prefix +*/ +static void show_buf(const char *name, uint8_t *buf, size_t size) +{ + int i; + printf("%s ", name); + for (i=0;i<size;i++) { + printf("%02x ", buf[i]); + } + printf("\n"); +} + +/* + given a UTF-16LE buffer, test the system and built-in iconv code to + make sure they do exactly the same thing in converting the buffer to + "charset", then convert it back again and ensure we get the same + buffer back +*/ +static bool test_buffer(struct torture_context *test, + uint8_t *inbuf, size_t size, const char *charset) +{ + uint8_t buf1[1000], buf2[1000], buf3[1000]; + size_t outsize1, outsize2, outsize3; + const char *ptr_in; + char *ptr_out; + size_t size_in1, size_in2, size_in3; + size_t ret1, ret2, ret3, len1, len2; + int errno1, errno2; + static iconv_t cd; + static smb_iconv_t cd2, cd3; + static const char *last_charset; + + if (cd && last_charset) { + iconv_close(cd); + smb_iconv_close(cd2); + smb_iconv_close(cd3); + cd = NULL; + } + + if (!cd) { + cd = iconv_open(charset, "UTF-16LE"); + if (cd == (iconv_t)-1) { + torture_fail(test, + talloc_asprintf(test, + "failed to open %s to UTF-16LE", + charset)); + } + cd2 = smb_iconv_open_ex(test, charset, "UTF-16LE", lp_parm_bool(test->lp_ctx, NULL, "iconv", "native", true)); + cd3 = smb_iconv_open_ex(test, "UTF-16LE", charset, lp_parm_bool(test->lp_ctx, NULL, "iconv", "native", true)); + last_charset = charset; + } + + /* internal convert to charset - placing result in buf1 */ + ptr_in = (const char *)inbuf; + ptr_out = (char *)buf1; + size_in1 = size; + outsize1 = sizeof(buf1); + + memset(ptr_out, 0, outsize1); + errno = 0; + ret1 = smb_iconv(cd2, &ptr_in, &size_in1, &ptr_out, &outsize1); + errno1 = errno; + + /* system convert to charset - placing result in buf2 */ + ptr_in = (const char *)inbuf; + ptr_out = (char *)buf2; + size_in2 = size; + outsize2 = sizeof(buf2); + + memset(ptr_out, 0, outsize2); + errno = 0; + ret2 = iconv(cd, discard_const_p(char *, &ptr_in), &size_in2, &ptr_out, &outsize2); + errno2 = errno; + + len1 = sizeof(buf1) - outsize1; + len2 = sizeof(buf2) - outsize2; + + /* codepoints above 1M are not interesting for now */ + if (len2 > len1 && + memcmp(buf1, buf2, len1) == 0 && + get_codepoint((char *)(buf2+len1), len2-len1, charset) >= (1<<20)) { + return true; + } + if (len1 > len2 && + memcmp(buf1, buf2, len2) == 0 && + get_codepoint((char *)(buf1+len2), len1-len2, charset) >= (1<<20)) { + return true; + } + + torture_assert_int_equal(test, ret1, ret2, "ret mismatch"); + + if (errno1 != errno2) { + show_buf(" rem1:", inbuf+(size-size_in1), size_in1); + show_buf(" rem2:", inbuf+(size-size_in2), size_in2); + torture_fail(test, talloc_asprintf(test, + "e1=%d/%s e2=%d/%s", + errno1, strerror(errno1), + errno2, strerror(errno2))); + } + + torture_assert_int_equal(test, outsize1, outsize2, "outsize mismatch"); + + torture_assert_int_equal(test, size_in1, size_in2, "size_in mismatch"); + + if (len1 != len2 || + memcmp(buf1, buf2, len1) != 0) { + torture_comment(test, "size=%d ret1=%d ret2=%d", (int)size, (int)ret1, (int)ret2); + show_buf(" IN1:", inbuf, size-size_in1); + show_buf(" IN2:", inbuf, size-size_in2); + show_buf("OUT1:", buf1, len1); + show_buf("OUT2:", buf2, len2); + if (len2 > len1 && memcmp(buf1, buf2, len1) == 0) { + torture_comment(test, "next codepoint is %u", + get_codepoint((char *)(buf2+len1), len2-len1, charset)); + } + if (len1 > len2 && memcmp(buf1, buf2, len2) == 0) { + torture_comment(test, "next codepoint is %u", + get_codepoint((char *)(buf1+len2),len1-len2, charset)); + } + + torture_fail(test, "failed"); + } + + /* convert back to UTF-16, putting result in buf3 */ + size = size - size_in1; + ptr_in = (const char *)buf1; + ptr_out = (char *)buf3; + size_in3 = len1; + outsize3 = sizeof(buf3); + + memset(ptr_out, 0, outsize3); + ret3 = smb_iconv(cd3, &ptr_in, &size_in3, &ptr_out, &outsize3); + + /* we only internally support the first 1M codepoints */ + if (outsize3 != sizeof(buf3) - size && + get_codepoint((char *)(inbuf+sizeof(buf3) - outsize3), + size - (sizeof(buf3) - outsize3), + "UTF-16LE") >= (1<<20)) { + return true; + } + + torture_assert_int_equal(test, ret3, 0, talloc_asprintf(test, + "pull failed - %s", strerror(errno))); + + if (strncmp(charset, "UTF", 3) != 0) { + /* don't expect perfect mappings for non UTF charsets */ + return true; + } + + + torture_assert_int_equal(test, outsize3, sizeof(buf3) - size, + "wrong outsize3"); + + if (memcmp(buf3, inbuf, size) != 0) { + torture_comment(test, "pull bytes mismatch:"); + show_buf("inbuf", inbuf, size); + show_buf(" buf3", buf3, sizeof(buf3) - outsize3); + torture_comment(test, "next codepoint is %u\n", + get_codepoint((char *)(inbuf+sizeof(buf3) - outsize3), + size - (sizeof(buf3) - outsize3), + "UTF-16LE")); + torture_fail(test, ""); + } + + return true; +} + + +/* + test the push_codepoint() and next_codepoint() functions for a given + codepoint +*/ +static bool test_codepoint(struct torture_context *tctx, unsigned int codepoint) +{ + uint8_t buf[10]; + size_t size, size2; + codepoint_t c; + + size = push_codepoint(lp_iconv_convenience(tctx->lp_ctx), (char *)buf, codepoint); + torture_assert(tctx, size != -1 || (codepoint >= 0xd800 && codepoint <= 0x10000), + "Invalid Codepoint range"); + + if (size == -1) return true; + + buf[size] = random(); + buf[size+1] = random(); + buf[size+2] = random(); + buf[size+3] = random(); + + c = next_codepoint(lp_iconv_convenience(tctx->lp_ctx), (char *)buf, &size2); + + torture_assert(tctx, c == codepoint, + talloc_asprintf(tctx, + "next_codepoint(%u) failed - gave %u", codepoint, c)); + + torture_assert(tctx, size2 == size, + talloc_asprintf(tctx, "next_codepoint(%u) gave wrong size %d (should be %d)\n", + codepoint, (int)size2, (int)size)); + + return true; +} + +static bool test_next_codepoint(struct torture_context *tctx) +{ + unsigned int codepoint; + if (iconv_untestable(tctx)) + return true; + + for (codepoint=0;codepoint<(1<<20);codepoint++) { + if (!test_codepoint(tctx, codepoint)) + return false; + } + return true; +} + +static bool test_first_1m(struct torture_context *tctx) +{ + unsigned int codepoint; + size_t size; + unsigned char inbuf[1000]; + + if (iconv_untestable(tctx)) + return true; + + for (codepoint=0;codepoint<(1<<20);codepoint++) { + if (gen_codepoint_utf16(codepoint, (char *)inbuf, &size) != 0) { + continue; + } + + if (codepoint % 1000 == 0) { + if (torture_setting_bool(tctx, "progress", true)) { + torture_comment(tctx, "codepoint=%u \r", codepoint); + fflush(stdout); + } + } + + if (!test_buffer(tctx, inbuf, size, "UTF-8")) + return false; + } + return true; +} + +static bool test_random_5m(struct torture_context *tctx) +{ + unsigned char inbuf[1000]; + unsigned int i; + + if (iconv_untestable(tctx)) + return true; + + for (i=0;i<500000;i++) { + size_t size; + unsigned int c; + + if (i % 1000 == 0) { + if (torture_setting_bool(tctx, "progress", true)) { + torture_comment(tctx, "i=%u \r", i); + fflush(stdout); + } + } + + size = random() % 100; + for (c=0;c<size;c++) { + if (random() % 100 < 80) { + inbuf[c] = random() % 128; + } else { + inbuf[c] = random(); + } + if (random() % 10 == 0) { + inbuf[c] |= 0xd8; + } + if (random() % 10 == 0) { + inbuf[c] |= 0xdc; + } + } + if (!test_buffer(tctx, inbuf, size, "UTF-8")) { + printf("i=%d failed UTF-8\n", i); + return false; + } + + if (!test_buffer(tctx, inbuf, size, "CP850")) { + printf("i=%d failed CP850\n", i); + return false; + } + } + return true; +} + +struct torture_suite *torture_local_iconv(TALLOC_CTX *mem_ctx) +{ + struct torture_suite *suite = torture_suite_create(mem_ctx, "ICONV"); + + torture_suite_add_simple_test(suite, "next_codepoint()", + test_next_codepoint); + + torture_suite_add_simple_test(suite, "first 1M codepoints", + test_first_1m); + + torture_suite_add_simple_test(suite, "5M random UTF-16LE sequences", + test_random_5m); + return suite; +} + +#else + +struct torture_suite *torture_local_iconv(TALLOC_CTX *mem_ctx) +{ + printf("No native iconv library - can't run iconv test\n"); + return NULL; +} + +#endif diff --git a/lib/util/charset/util_unistr.c b/lib/util/charset/util_unistr.c new file mode 100644 index 0000000000..e4f4bb551a --- /dev/null +++ b/lib/util/charset/util_unistr.c @@ -0,0 +1,684 @@ +/* + Unix SMB/CIFS implementation. + Samba utility functions + Copyright (C) Andrew Tridgell 1992-2001 + Copyright (C) Simo Sorce 2001 + + 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 3 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, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "system/locale.h" +#include "dynconfig/dynconfig.h" +#include "param/param.h" + +/** + * @file + * @brief Unicode string manipulation + */ + +/* these 2 tables define the unicode case handling. They are loaded + at startup either via mmap() or read() from the lib directory */ +static void *upcase_table; +static void *lowcase_table; + + +/******************************************************************* +load the case handling tables +********************************************************************/ +void load_case_tables(void) +{ + TALLOC_CTX *mem_ctx; + + mem_ctx = talloc_init("load_case_tables"); + if (!mem_ctx) { + smb_panic("No memory for case_tables"); + } + upcase_table = map_file(talloc_asprintf(mem_ctx, "%s/upcase.dat", dyn_DATADIR), 0x20000); + lowcase_table = map_file(talloc_asprintf(mem_ctx, "%s/lowcase.dat", dyn_DATADIR), 0x20000); + talloc_free(mem_ctx); + if (upcase_table == NULL) { + /* try also under codepages for testing purposes */ + upcase_table = map_file("codepages/upcase.dat", 0x20000); + if (upcase_table == NULL) { + upcase_table = (void *)-1; + } + } + if (lowcase_table == NULL) { + /* try also under codepages for testing purposes */ + lowcase_table = map_file("codepages/lowcase.dat", 0x20000); + if (lowcase_table == NULL) { + lowcase_table = (void *)-1; + } + } +} + +/** + Convert a codepoint_t to upper case. +**/ +_PUBLIC_ codepoint_t toupper_w(codepoint_t val) +{ + if (val < 128) { + return toupper(val); + } + if (upcase_table == NULL) { + load_case_tables(); + } + if (upcase_table == (void *)-1) { + return val; + } + if (val & 0xFFFF0000) { + return val; + } + return SVAL(upcase_table, val*2); +} + +/** + Convert a codepoint_t to lower case. +**/ +_PUBLIC_ codepoint_t tolower_w(codepoint_t val) +{ + if (val < 128) { + return tolower(val); + } + if (lowcase_table == NULL) { + load_case_tables(); + } + if (lowcase_table == (void *)-1) { + return val; + } + if (val & 0xFFFF0000) { + return val; + } + return SVAL(lowcase_table, val*2); +} + +/** + compare two codepoints case insensitively +*/ +_PUBLIC_ int codepoint_cmpi(codepoint_t c1, codepoint_t c2) +{ + if (c1 == c2 || + toupper_w(c1) == toupper_w(c2)) { + return 0; + } + return c1 - c2; +} + +/** + Case insensitive string compararison +**/ +_PUBLIC_ int strcasecmp_m(const char *s1, const char *s2) +{ + codepoint_t c1=0, c2=0; + size_t size1, size2; + struct smb_iconv_convenience *iconv_convenience = lp_iconv_convenience(global_loadparm); + + /* handle null ptr comparisons to simplify the use in qsort */ + if (s1 == s2) return 0; + if (s1 == NULL) return -1; + if (s2 == NULL) return 1; + + while (*s1 && *s2) { + c1 = next_codepoint(iconv_convenience, s1, &size1); + c2 = next_codepoint(iconv_convenience, s2, &size2); + + s1 += size1; + s2 += size2; + + if (c1 == c2) { + continue; + } + + if (c1 == INVALID_CODEPOINT || + c2 == INVALID_CODEPOINT) { + /* what else can we do?? */ + return strcasecmp(s1, s2); + } + + if (toupper_w(c1) != toupper_w(c2)) { + return c1 - c2; + } + } + + return *s1 - *s2; +} + +/** + * Get the next token from a string, return False if none found. + * Handles double-quotes. + * + * Based on a routine by GJC@VILLAGE.COM. + * Extensively modified by Andrew.Tridgell@anu.edu.au + **/ +_PUBLIC_ bool next_token(const char **ptr,char *buff, const char *sep, size_t bufsize) +{ + const char *s; + bool quoted; + size_t len=1; + + if (!ptr) + return false; + + s = *ptr; + + /* default to simple separators */ + if (!sep) + sep = " \t\n\r"; + + /* find the first non sep char */ + while (*s && strchr_m(sep,*s)) + s++; + + /* nothing left? */ + if (!*s) + return false; + + /* copy over the token */ + for (quoted = false; len < bufsize && *s && (quoted || !strchr_m(sep,*s)); s++) { + if (*s == '\"') { + quoted = !quoted; + } else { + len++; + *buff++ = *s; + } + } + + *ptr = (*s) ? s+1 : s; + *buff = 0; + + return true; +} + +/** + Case insensitive string compararison, length limited +**/ +_PUBLIC_ int strncasecmp_m(const char *s1, const char *s2, size_t n) +{ + codepoint_t c1=0, c2=0; + size_t size1, size2; + struct smb_iconv_convenience *iconv_convenience = lp_iconv_convenience(global_loadparm); + + /* handle null ptr comparisons to simplify the use in qsort */ + if (s1 == s2) return 0; + if (s1 == NULL) return -1; + if (s2 == NULL) return 1; + + while (*s1 && *s2 && n) { + n--; + + c1 = next_codepoint(iconv_convenience, s1, &size1); + c2 = next_codepoint(iconv_convenience, s2, &size2); + + s1 += size1; + s2 += size2; + + if (c1 == c2) { + continue; + } + + if (c1 == INVALID_CODEPOINT || + c2 == INVALID_CODEPOINT) { + /* what else can we do?? */ + return strcasecmp(s1, s2); + } + + if (toupper_w(c1) != toupper_w(c2)) { + return c1 - c2; + } + } + + if (n == 0) { + return 0; + } + + return *s1 - *s2; +} + +/** + * Compare 2 strings. + * + * @note The comparison is case-insensitive. + **/ +_PUBLIC_ bool strequal_w(const char *s1, const char *s2) +{ + return strcasecmp_m(s1,s2) == 0; +} + +/** + Compare 2 strings (case sensitive). +**/ +_PUBLIC_ bool strcsequal_w(const char *s1,const char *s2) +{ + if (s1 == s2) + return true; + if (!s1 || !s2) + return false; + + return strcmp(s1,s2) == 0; +} + + +/** + String replace. + NOTE: oldc and newc must be 7 bit characters +**/ +_PUBLIC_ void string_replace_w(char *s, char oldc, char newc) +{ + while (s && *s) { + size_t size; + codepoint_t c = next_codepoint(lp_iconv_convenience(global_loadparm), s, &size); + if (c == oldc) { + *s = newc; + } + s += size; + } +} + +/** + Paranoid strcpy into a buffer of given length (includes terminating + zero. Strips out all but 'a-Z0-9' and the character in other_safe_chars + and replaces with '_'. Deliberately does *NOT* check for multibyte + characters. Don't change it ! +**/ + +_PUBLIC_ char *alpha_strcpy(char *dest, const char *src, const char *other_safe_chars, size_t maxlength) +{ + size_t len, i; + + if (maxlength == 0) { + /* can't fit any bytes at all! */ + return NULL; + } + + if (!dest) { + DEBUG(0,("ERROR: NULL dest in alpha_strcpy\n")); + return NULL; + } + + if (!src) { + *dest = 0; + return dest; + } + + len = strlen(src); + if (len >= maxlength) + len = maxlength - 1; + + if (!other_safe_chars) + other_safe_chars = ""; + + for(i = 0; i < len; i++) { + int val = (src[i] & 0xff); + if (isupper(val) || islower(val) || isdigit(val) || strchr_m(other_safe_chars, val)) + dest[i] = src[i]; + else + dest[i] = '_'; + } + + dest[i] = '\0'; + + return dest; +} + +/** + Count the number of UCS2 characters in a string. Normally this will + be the same as the number of bytes in a string for single byte strings, + but will be different for multibyte. +**/ +_PUBLIC_ size_t strlen_m(const char *s) +{ + size_t count = 0; + + if (!s) { + return 0; + } + + while (*s && !(((uint8_t)*s) & 0x80)) { + s++; + count++; + } + + if (!*s) { + return count; + } + + while (*s) { + size_t c_size; + codepoint_t c = next_codepoint(lp_iconv_convenience(global_loadparm), s, &c_size); + if (c < 0x10000) { + count += 1; + } else { + count += 2; + } + s += c_size; + } + + return count; +} + +/** + Work out the number of multibyte chars in a string, including the NULL + terminator. +**/ +_PUBLIC_ size_t strlen_m_term(const char *s) +{ + if (!s) { + return 0; + } + + return strlen_m(s) + 1; +} + +/** + Strchr and strrchr_m are a bit complex on general multi-byte strings. +**/ +_PUBLIC_ char *strchr_m(const char *s, char c) +{ + if (s == NULL) { + return NULL; + } + /* characters below 0x3F are guaranteed to not appear in + non-initial position in multi-byte charsets */ + if ((c & 0xC0) == 0) { + return strchr(s, c); + } + + while (*s) { + size_t size; + codepoint_t c2 = next_codepoint(lp_iconv_convenience(global_loadparm), s, &size); + if (c2 == c) { + return discard_const_p(char, s); + } + s += size; + } + + return NULL; +} + +/** + * Multibyte-character version of strrchr + */ +_PUBLIC_ char *strrchr_m(const char *s, char c) +{ + char *ret = NULL; + + if (s == NULL) { + return NULL; + } + + /* characters below 0x3F are guaranteed to not appear in + non-initial position in multi-byte charsets */ + if ((c & 0xC0) == 0) { + return strrchr(s, c); + } + + while (*s) { + size_t size; + codepoint_t c2 = next_codepoint(lp_iconv_convenience(global_loadparm), s, &size); + if (c2 == c) { + ret = discard_const_p(char, s); + } + s += size; + } + + return ret; +} + +/** + return True if any (multi-byte) character is lower case +*/ +_PUBLIC_ bool strhaslower(const char *string) +{ + while (*string) { + size_t c_size; + codepoint_t s; + codepoint_t t; + + s = next_codepoint(lp_iconv_convenience(global_loadparm), string, &c_size); + string += c_size; + + t = toupper_w(s); + + if (s != t) { + return true; /* that means it has lower case chars */ + } + } + + return false; +} + +/** + return True if any (multi-byte) character is upper case +*/ +_PUBLIC_ bool strhasupper(const char *string) +{ + while (*string) { + size_t c_size; + codepoint_t s; + codepoint_t t; + + s = next_codepoint(lp_iconv_convenience(global_loadparm), string, &c_size); + string += c_size; + + t = tolower_w(s); + + if (s != t) { + return true; /* that means it has upper case chars */ + } + } + + return false; +} + +/** + Convert a string to lower case, allocated with talloc +**/ +_PUBLIC_ char *strlower_talloc(TALLOC_CTX *ctx, const char *src) +{ + size_t size=0; + char *dest; + struct smb_iconv_convenience *iconv_convenience = lp_iconv_convenience(global_loadparm); + + /* this takes advantage of the fact that upper/lower can't + change the length of a character by more than 1 byte */ + dest = talloc_array(ctx, char, 2*(strlen(src))+1); + if (dest == NULL) { + return NULL; + } + + while (*src) { + size_t c_size; + codepoint_t c = next_codepoint(iconv_convenience, src, &c_size); + src += c_size; + + c = tolower_w(c); + + c_size = push_codepoint(iconv_convenience, dest+size, c); + if (c_size == -1) { + talloc_free(dest); + return NULL; + } + size += c_size; + } + + dest[size] = 0; + + /* trim it so talloc_append_string() works */ + dest = talloc_realloc(ctx, dest, char, size+1); + + talloc_set_name_const(dest, dest); + + return dest; +} + +/** + Convert a string to UPPER case, allocated with talloc + source length limited to n bytes +**/ +_PUBLIC_ char *strupper_talloc_n(TALLOC_CTX *ctx, const char *src, size_t n) +{ + size_t size=0; + char *dest; + struct smb_iconv_convenience *iconv_convenience = lp_iconv_convenience(global_loadparm); + + if (!src) { + return NULL; + } + + /* this takes advantage of the fact that upper/lower can't + change the length of a character by more than 1 byte */ + dest = talloc_array(ctx, char, 2*(n+1)); + if (dest == NULL) { + return NULL; + } + + while (*src && n--) { + size_t c_size; + codepoint_t c = next_codepoint(iconv_convenience, src, &c_size); + src += c_size; + + c = toupper_w(c); + + c_size = push_codepoint(iconv_convenience, dest+size, c); + if (c_size == -1) { + talloc_free(dest); + return NULL; + } + size += c_size; + } + + dest[size] = 0; + + /* trim it so talloc_append_string() works */ + dest = talloc_realloc(ctx, dest, char, size+1); + + talloc_set_name_const(dest, dest); + + return dest; +} + +/** + Convert a string to UPPER case, allocated with talloc +**/ +_PUBLIC_ char *strupper_talloc(TALLOC_CTX *ctx, const char *src) +{ + return strupper_talloc_n(ctx, src, src?strlen(src):0); +} + +/** + talloc_strdup() a unix string to upper case. +**/ +_PUBLIC_ char *talloc_strdup_upper(TALLOC_CTX *ctx, const char *src) +{ + return strupper_talloc(ctx, src); +} + +/** + Convert a string to lower case. +**/ +_PUBLIC_ void strlower_m(char *s) +{ + char *d; + struct smb_iconv_convenience *iconv_convenience; + + /* this is quite a common operation, so we want it to be + fast. We optimise for the ascii case, knowing that all our + supported multi-byte character sets are ascii-compatible + (ie. they match for the first 128 chars) */ + while (*s && !(((uint8_t)*s) & 0x80)) { + *s = tolower((uint8_t)*s); + s++; + } + + if (!*s) + return; + + iconv_convenience = lp_iconv_convenience(global_loadparm); + + d = s; + + while (*s) { + size_t c_size, c_size2; + codepoint_t c = next_codepoint(iconv_convenience, s, &c_size); + c_size2 = push_codepoint(iconv_convenience, d, tolower_w(c)); + if (c_size2 > c_size) { + DEBUG(0,("FATAL: codepoint 0x%x (0x%x) expanded from %d to %d bytes in strlower_m\n", + c, tolower_w(c), (int)c_size, (int)c_size2)); + smb_panic("codepoint expansion in strlower_m\n"); + } + s += c_size; + d += c_size2; + } + *d = 0; +} + +/** + Convert a string to UPPER case. +**/ +_PUBLIC_ void strupper_m(char *s) +{ + char *d; + struct smb_iconv_convenience *iconv_convenience; + + /* this is quite a common operation, so we want it to be + fast. We optimise for the ascii case, knowing that all our + supported multi-byte character sets are ascii-compatible + (ie. they match for the first 128 chars) */ + while (*s && !(((uint8_t)*s) & 0x80)) { + *s = toupper((uint8_t)*s); + s++; + } + + if (!*s) + return; + + iconv_convenience = lp_iconv_convenience(global_loadparm); + + d = s; + + while (*s) { + size_t c_size, c_size2; + codepoint_t c = next_codepoint(iconv_convenience, s, &c_size); + c_size2 = push_codepoint(iconv_convenience, d, toupper_w(c)); + if (c_size2 > c_size) { + DEBUG(0,("FATAL: codepoint 0x%x (0x%x) expanded from %d to %d bytes in strupper_m\n", + c, toupper_w(c), (int)c_size, (int)c_size2)); + smb_panic("codepoint expansion in strupper_m\n"); + } + s += c_size; + d += c_size2; + } + *d = 0; +} + + +/** + Find the number of 'c' chars in a string +**/ +_PUBLIC_ size_t count_chars_w(const char *s, char c) +{ + size_t count = 0; + + while (*s) { + size_t size; + codepoint_t c2 = next_codepoint(lp_iconv_convenience(global_loadparm), s, &size); + if (c2 == c) count++; + s += size; + } + + return count; +} + + |