[Asterisk-code-review] utf8.c: Add UTF-8 validation and utility functions (asterisk[13])

Sean Bright asteriskteam at digium.com
Wed Jul 15 10:00:41 CDT 2020


Sean Bright has uploaded this change for review. ( https://gerrit.asterisk.org/c/asterisk/+/14640 )


Change subject: utf8.c: Add UTF-8 validation and utility functions
......................................................................

utf8.c: Add UTF-8 validation and utility functions

Change-Id: I3555d787a79e7c780a7800cd26e0b5056368abf9
---
A include/asterisk/utf8.h
M main/asterisk.c
A main/utf8.c
3 files changed, 555 insertions(+), 0 deletions(-)



  git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/40/14640/1

diff --git a/include/asterisk/utf8.h b/include/asterisk/utf8.h
new file mode 100644
index 0000000..2a4e910
--- /dev/null
+++ b/include/asterisk/utf8.h
@@ -0,0 +1,176 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sean Bright
+ *
+ * Sean Bright <sean.bright at gmail.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief UTF-8 information and validation functions
+ */
+
+#ifndef ASTERISK_UTF8_H
+#define ASTERISK_UTF8_H
+
+/*!
+ * \brief Check if a zero-terminated string is valid UTF-8
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * \param str The zero-terminated string to check
+ *
+ * \retval 0 if the string is not valid UTF-8
+ * \retval Non-zero if the string is valid UTF-8
+ */
+int ast_utf8_is_valid(const char *str);
+
+/*!
+ * \brief Check if the first \a size bytes of a string are valid UTF-8
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * Similar to \a ast_utf8_is_valid() but checks the first \a size bytes or until
+ * a zero byte is reached, whichever comes first.
+ *
+ * \param str The string to check
+ * \param size The number of bytes to evaluate
+ *
+ * \retval 0 if the string is not valid UTF-8
+ * \retval Non-zero if the string is valid UTF-8
+ */
+int ast_utf8_is_validn(const char *str, size_t size);
+
+/*!
+ * \brief Copy a string safely ensuring valid UTF-8
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * This is similar to \a ast_copy_string, but it will only copy valid UTF-8
+ * sequences from the source string into the destination buffer. If an invalid
+ * UTF-8 sequence is encountered, or the available space in the destination
+ * buffer is exhausted in the middle of an otherwise valid UTF-8 sequence, the
+ * destination buffer will be truncated to ensure that it only contains valid
+ * UTF-8.
+ *
+ * \param dst The destination buffer.
+ * \param src The source string
+ * \param size The size of the destination buffer
+ * \return Nothing.
+ */
+void ast_utf8_copy_string(char *dst, const char *src, size_t size);
+
+enum ast_utf8_validation_result {
+	/*! \brief The consumed sequence is valid UTF-8
+	 *
+	 * The bytes consumed thus far by the validator represent a valid sequence of
+	 * UTF-8 bytes. If additional bytes are fed into the validator, it can
+	 * transition into either \a AST_UTF8_INVALID or \a AST_UTF8_UNKNOWN
+	 */
+	AST_UTF8_VALID,
+
+	/*! \brief The consumed sequence is invalid UTF-8
+	 *
+	 * The bytes consumed thus far by the validator represent an invalid sequence
+	 * of UTF-8 bytes. Feeding additional bytes into the validator will not
+	 * change its state.
+	 */
+	AST_UTF8_INVALID,
+
+	/*! \brief The validator is in an intermediate state
+	 *
+	 * The validator is in the process of validating a multibyte UTF-8 sequence
+	 * and requires additional data to be fed into it to determine validity. If
+	 * additional bytes are fed into the validator, it can transition into either
+	 * \a AST_UTF8_VALID or \a AST_UTF8_INVALID. If you have no additional data
+	 * to feed into the validator the UTF-8 sequence is invalid.
+	 */
+	AST_UTF8_UNKNOWN,
+};
+
+/*!
+ * \brief Opaque type for UTF-8 validator state.
+ * \since 13.26.0, 16.13.0, 17.7.0
+ */
+struct ast_utf8_validator;
+
+/*!
+ * \brief Create a new UTF-8 validator
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * \param[out] validator The validator instance
+ *
+ * \retval 0 on success
+ * \retval -1 on failure
+ */
+int ast_utf8_validator_new(struct ast_utf8_validator **validator);
+
+/*!
+ * \brief Feed a zero-terminated string into the UTF-8 validator
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * \param validator The validator instance
+ * \param data The zero-terminated string to feed into the validator
+ *
+ * \return The \ref ast_utf8_validation_result indicating the current state of
+ *         the validator.
+ */
+enum ast_utf8_validation_result ast_utf8_validator_feed(
+	struct ast_utf8_validator *validator, const char *data);
+
+/*!
+ * \brief Feed a string into the UTF-8 validator
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * Similar to \a ast_utf8_validator_feed but will stop feeding in data if a zero
+ * byte is encountered or \a size bytes have been read.
+ *
+ * \param validator The validator instance
+ * \param data The string to feed into the validator
+ * \param size The number of bytes to feed into the validator
+ *
+ * \return The \ref ast_utf8_validation_result indicating the current state of
+ *         the validator.
+ */
+enum ast_utf8_validation_result ast_utf8_validator_feedn(
+	struct ast_utf8_validator *validator, const char *data, size_t size);
+
+/*!
+ * \brief Get the current UTF-8 validator state
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * \param validator The validator instance
+ *
+ * \return The \ref ast_utf8_validation_result indicating the current state of
+ *         the validator.
+ */
+enum ast_utf8_validation_result ast_utf8_validator_state(
+	struct ast_utf8_validator *validator);
+
+/*!
+ * \brief Destroy a UTF-8 validator
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * \param validator The validator instance to destroy
+ */
+void ast_utf8_validator_destroy(struct ast_utf8_validator *validator);
+
+/*!
+ * \brief Register UTF-8 tests
+ * \since 13.26.0, 16.13.0, 17.7.0
+ *
+ * Does nothing unless TEST_FRAMEWORK is defined.
+ *
+ * \return Always returns 0
+ */
+int ast_utf8_init(void);
+
+#endif /* ASTERISK_UTF8_H */
diff --git a/main/asterisk.c b/main/asterisk.c
index 5572c9a..47e8d4f 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -251,6 +251,7 @@
 #include "asterisk/format_cache.h"
 #include "asterisk/astdb.h"
 #include "asterisk/options.h"
+#include "asterisk/utf8.h"
 
 #include "../defaults.h"
 
@@ -4188,6 +4189,7 @@
 	ast_json_init();
 	ast_ulaw_init();
 	ast_alaw_init();
+	ast_utf8_init();
 	tdd_init();
 	callerid_init();
 	ast_builtins_init();
diff --git a/main/utf8.c b/main/utf8.c
new file mode 100644
index 0000000..efac5fe
--- /dev/null
+++ b/main/utf8.c
@@ -0,0 +1,377 @@
+/*
+ * Asterisk -- An open source telephony toolkit.
+ *
+ * Copyright (C) 2020, Sean Bright
+ *
+ * Sean Bright <sean.bright at gmail.com>
+ *
+ * See http://www.asterisk.org for more information about
+ * the Asterisk project. Please do not directly contact
+ * any of the maintainers of this project for assistance;
+ * the project provides a web site, mailing lists and IRC
+ * channels for your use.
+ *
+ * This program is free software, distributed under the terms of
+ * the GNU General Public License Version 2. See the LICENSE file
+ * at the top of the source tree.
+ */
+
+/*! \file
+ *
+ * \brief UTF-8 information and validation functions
+ */
+
+/*** MODULEINFO
+	 <support_level>core</support_level>
+***/
+
+#include "asterisk.h"
+
+ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
+
+#include "asterisk/utils.h"
+#include "asterisk/utf8.h"
+#include "asterisk/test.h"
+
+/*
+ * BEGIN THIRD PARTY CODE
+ *
+ * Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern at hoehrmann.de>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * See http://bjoern.hoehrmann.de/utf-8/decoder/dfa/ for details.
+ */
+
+#define UTF8_ACCEPT 0
+#define UTF8_REJECT 12
+
+	static const uint8_t utf8d[] = {
+	/* The first part of the table maps bytes to character classes that
+	 * to reduce the size of the transition table and create bitmasks. */
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+	1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
+	7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+	8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
+	10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,
+
+	/* The second part is a transition table that maps a combination
+	 * of a state of the automaton and a character class to a state. */
+	0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
+	12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
+	12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
+	12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
+	12,36,12,12,12,12,12,12,12,12,12,12,
+};
+
+#if 0
+/* We can bring this back if we need the codepoint? */
+static uint32_t inline decode(uint32_t *state, uint32_t *codep, uint32_t byte) {
+	uint32_t type = utf8d[byte];
+
+	*codep = (*state != UTF8_ACCEPT) ?
+		(byte & 0x3fu) | (*codep << 6) :
+		(0xff >> type) & (byte);
+
+	*state = utf8d[256 + *state + type];
+	return *state;
+}
+#endif
+
+static uint32_t inline decode(uint32_t *state, uint32_t byte) {
+	uint32_t type = utf8d[byte];
+	*state = utf8d[256 + *state + type];
+	return *state;
+}
+
+/*
+ * END THIRD PARTY CODE
+ *
+ * See copyright notice above.
+ */
+
+int ast_utf8_is_valid(const char *src)
+{
+	uint32_t state = UTF8_ACCEPT;
+
+	while (*src) {
+		decode(&state, (uint8_t) *src++);
+	}
+
+	return state == UTF8_ACCEPT;
+}
+
+int ast_utf8_is_validn(const char *src, size_t size)
+{
+	uint32_t state = UTF8_ACCEPT;
+
+	while (size && *src) {
+		decode(&state, (uint8_t) *src++);
+		size--;
+	}
+
+	return state == UTF8_ACCEPT;
+}
+
+void ast_utf8_copy_string(char *dst, const char *src, size_t size)
+{
+	uint32_t state = UTF8_ACCEPT;
+	char *last_good = dst;
+
+	ast_assert(size > 0);
+
+	while (size && *src) {
+		if (decode(&state, (uint8_t) *src) == UTF8_REJECT) {
+			/* We _could_ replace with U+FFFD and try to recover, but for now
+			 * we treat this the same as if we had run out of space */
+			break;
+		}
+
+		*dst++ = *src++;
+		size--;
+
+		if (size && state == UTF8_ACCEPT) {
+			/* last_good is where we will ultimately write the 0 byte */
+			last_good = dst;
+		}
+	}
+
+	*last_good = '\0';
+}
+
+struct ast_utf8_validator {
+	uint32_t state;
+};
+
+int ast_utf8_validator_new(struct ast_utf8_validator **validator)
+{
+	struct ast_utf8_validator *tmp = ast_malloc(sizeof(*tmp));
+
+	if (!tmp) {
+		return 1;
+	}
+
+	tmp->state = UTF8_ACCEPT;
+	*validator = tmp;
+	return 0;
+}
+
+enum ast_utf8_validation_result ast_utf8_validator_state(
+	struct ast_utf8_validator *validator)
+{
+	switch (validator->state) {
+	case UTF8_ACCEPT:
+		return AST_UTF8_VALID;
+	case UTF8_REJECT:
+		return AST_UTF8_INVALID;
+	default:
+		return AST_UTF8_UNKNOWN;
+	}
+}
+
+enum ast_utf8_validation_result ast_utf8_validator_feed(
+	struct ast_utf8_validator *validator, const char *data)
+{
+	while (*data) {
+		decode(&validator->state, (uint8_t) *data++);
+	}
+
+	return ast_utf8_validator_state(validator);
+}
+
+enum ast_utf8_validation_result ast_utf8_validator_feedn(
+	struct ast_utf8_validator *validator, const char *data, size_t size)
+{
+	while (size && *data) {
+		decode(&validator->state, (uint8_t) *data++);
+		size--;
+	}
+
+	return ast_utf8_validator_state(validator);
+}
+
+void ast_utf8_validator_destroy(struct ast_utf8_validator *validator)
+{
+	ast_free(validator);
+}
+
+#ifdef TEST_FRAMEWORK
+
+AST_TEST_DEFINE(test_utf8_is_valid)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "is_valid";
+		info->category = "/main/utf8/";
+		info->summary = "Test ast_utf8_is_valid and ast_utf8_is_validn";
+		info->description =
+			"Tests UTF-8 string validation code.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	/* Valid UTF-8 */
+	ast_test_validate(test, ast_utf8_is_valid("Asterisk"));
+	ast_test_validate(test, ast_utf8_is_valid("\xce\xbb"));
+	ast_test_validate(test, ast_utf8_is_valid("\xe2\x8a\x9b"));
+	ast_test_validate(test, ast_utf8_is_valid("\xf0\x9f\x93\x9e"));
+
+	/* Valid with leading */
+	ast_test_validate(test, ast_utf8_is_valid("aaa Asterisk"));
+	ast_test_validate(test, ast_utf8_is_valid("aaa \xce\xbb"));
+	ast_test_validate(test, ast_utf8_is_valid("aaa \xe2\x8a\x9b"));
+	ast_test_validate(test, ast_utf8_is_valid("aaa \xf0\x9f\x93\x9e"));
+
+	/* Valid with trailing */
+	ast_test_validate(test, ast_utf8_is_valid("Asterisk aaa"));
+	ast_test_validate(test, ast_utf8_is_valid("\xce\xbb aaa"));
+	ast_test_validate(test, ast_utf8_is_valid("\xe2\x8a\x9b aaa"));
+	ast_test_validate(test, ast_utf8_is_valid("\xf0\x9f\x93\x9e aaa"));
+
+	/* Valid with leading and trailing */
+	ast_test_validate(test, ast_utf8_is_valid("aaa Asterisk aaa"));
+	ast_test_validate(test, ast_utf8_is_valid("aaa \xce\xbb aaa"));
+	ast_test_validate(test, ast_utf8_is_valid("aaa \xe2\x8a\x9b aaa"));
+	ast_test_validate(test, ast_utf8_is_valid("aaa \xf0\x9f\x93\x9e aaa"));
+
+	/* Valid if limited by number of bytes */
+	ast_test_validate(test, ast_utf8_is_validn("Asterisk" "\xff", strlen("Asterisk")));
+	ast_test_validate(test, ast_utf8_is_validn("\xce\xbb" "\xff", strlen("\xce\xbb")));
+	ast_test_validate(test, ast_utf8_is_validn("\xe2\x8a\x9b" "\xff", strlen("\xe2\x8a\x9b")));
+	ast_test_validate(test, ast_utf8_is_validn("\xf0\x9f\x93\x9e" "\xff", strlen("\xf0\x9f\x93\x9e")));
+
+	/* Invalid */
+	ast_test_validate(test, !ast_utf8_is_valid("\xc0\x00")); /* Overlong */
+	ast_test_validate(test, !ast_utf8_is_valid("98.6\xa7")); /* 'High ASCII' */
+	ast_test_validate(test, !ast_utf8_is_valid("\xc3\x28"));
+	ast_test_validate(test, !ast_utf8_is_valid("\xa0\xa1"));
+	ast_test_validate(test, !ast_utf8_is_valid("\xe2\x28\xa1"));
+	ast_test_validate(test, !ast_utf8_is_valid("\xe2\x82\x28"));
+	ast_test_validate(test, !ast_utf8_is_valid("\xf0\x28\x8c\xbc"));
+	ast_test_validate(test, !ast_utf8_is_valid("\xf0\x90\x28\xbc"));
+	ast_test_validate(test, !ast_utf8_is_valid("\xf0\x28\x8c\x28"));
+
+	return AST_TEST_PASS;
+}
+
+static int test_copy_and_compare(const char *src, size_t dst_len, const char *cmp)
+{
+	char dst[dst_len];
+	ast_utf8_copy_string(dst, src, dst_len);
+	return strcmp(dst, cmp) == 0;
+}
+
+AST_TEST_DEFINE(test_utf8_copy_string)
+{
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "copy_string";
+		info->category = "/main/utf8/";
+		info->summary = "Test ast_utf8_copy_string";
+		info->description =
+			"Tests UTF-8 string copying code.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	ast_test_validate(test, test_copy_and_compare("Asterisk",           6, "Aster"));
+	ast_test_validate(test, test_copy_and_compare("Asterisk \xc2\xae", 11, "Asterisk "));
+	ast_test_validate(test, test_copy_and_compare("Asterisk \xc2\xae", 12, "Asterisk \xc2\xae"));
+	ast_test_validate(test, test_copy_and_compare("Asterisk \xc0\x00", 12, "Asterisk "));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 1, ""));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 2, ""));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 3, "\xce\xbb"));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 4, "\xce\xbb "));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 5, "\xce\xbb x"));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 6, "\xce\xbb xy"));
+	ast_test_validate(test, test_copy_and_compare("\xce\xbb xyz", 7, "\xce\xbb xyz"));
+
+	return AST_TEST_PASS;
+}
+
+AST_TEST_DEFINE(test_utf8_validator)
+{
+	struct ast_utf8_validator *validator;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "utf8_validator";
+		info->category = "/main/utf8/";
+		info->summary = "Test ast_utf8_validator";
+		info->description =
+			"Tests UTF-8 progressive validator code.";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	if (ast_utf8_validator_new(&validator)) {
+		return AST_TEST_FAIL;
+	}
+
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "Asterisk") == AST_UTF8_VALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "\xc2")     == AST_UTF8_UNKNOWN);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "\xae")     == AST_UTF8_VALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "Private")  == AST_UTF8_VALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "Branch")   == AST_UTF8_VALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "Exchange") == AST_UTF8_VALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "\xe2")     == AST_UTF8_UNKNOWN);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "\x84")     == AST_UTF8_UNKNOWN);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "\xbb")     == AST_UTF8_VALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "\xc0\x00") == AST_UTF8_INVALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "valid")    == AST_UTF8_INVALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "valid")    == AST_UTF8_INVALID);
+	ast_test_validate(test, ast_utf8_validator_feed(validator, "valid")    == AST_UTF8_INVALID);
+
+	ast_utf8_validator_destroy(validator);
+
+	return AST_TEST_PASS;
+}
+
+static void test_utf8_shutdown(void)
+{
+	AST_TEST_UNREGISTER(test_utf8_is_valid);
+	AST_TEST_UNREGISTER(test_utf8_copy_string);
+	AST_TEST_UNREGISTER(test_utf8_validator);
+}
+
+int ast_utf8_init(void)
+{
+	AST_TEST_REGISTER(test_utf8_is_valid);
+	AST_TEST_REGISTER(test_utf8_copy_string);
+	AST_TEST_REGISTER(test_utf8_validator);
+
+	ast_register_cleanup(test_utf8_shutdown);
+
+	return 0;
+}
+
+#else /* !TEST_FRAMEWORK */
+
+int ast_utf8_init(void)
+{
+	return 0;
+}
+
+#endif

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/14640
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 13
Gerrit-Change-Id: I3555d787a79e7c780a7800cd26e0b5056368abf9
Gerrit-Change-Number: 14640
Gerrit-PatchSet: 1
Gerrit-Owner: Sean Bright <sean.bright at gmail.com>
Gerrit-MessageType: newchange
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20200715/56d4a532/attachment-0001.html>


More information about the asterisk-code-review mailing list