#ifndef lint
static char *rcsid = "$Id: res.c,v 1.68 2003/02/05 09:04:22 ishisone Exp $";
#endif

/*
 * Copyright (c) 2000,2002 Japan Network Information Center.
 * All rights reserved.
 *  
 * By using this file, you agree to the terms and conditions set forth bellow.
 * 
 * 			LICENSE TERMS AND CONDITIONS 
 * 
 * The following License Terms and Conditions apply, unless a different
 * license is obtained from Japan Network Information Center ("JPNIC"),
 * a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
 * Chiyoda-ku, Tokyo 101-0047, Japan.
 * 
 * 1. Use, Modification and Redistribution (including distribution of any
 *    modified or derived work) in source and/or binary forms is permitted
 *    under this License Terms and Conditions.
 * 
 * 2. Redistribution of source code must retain the copyright notices as they
 *    appear in each source code file, this License Terms and Conditions.
 * 
 * 3. Redistribution in binary form must reproduce the Copyright Notice,
 *    this License Terms and Conditions, in the documentation and/or other
 *    materials provided with the distribution.  For the purposes of binary
 *    distribution the "Copyright Notice" refers to the following language:
 *    "Copyright (c) 2000-2002 Japan Network Information Center.  All rights reserved."
 * 
 * 4. The name of JPNIC may not be used to endorse or promote products
 *    derived from this Software without specific prior written approval of
 *    JPNIC.
 * 
 * 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
 *    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL JPNIC BE LIABLE
 *    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 *    BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 *    WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 *    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 *    ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 */

#include <config.h>

#include <stddef.h>
#include <stdlib.h>
#include <string.h>

#include <idn/result.h>
#include <idn/assert.h>
#include <idn/logmacro.h>
#include <idn/converter.h>
#include <idn/normalizer.h>
#include <idn/checker.h>
#include <idn/mapper.h>
#include <idn/mapselector.h>
#include <idn/delimitermap.h>
#include <idn/resconf.h>
#include <idn/res.h>
#include <idn/util.h>
#include <idn/debug.h>
#include <idn/ucs4.h>

#ifndef IDN_UTF8_ENCODING_NAME
#define IDN_UTF8_ENCODING_NAME "UTF-8"		/* by IANA */
#endif

#ifndef WITHOUT_ICONV
#define ENCODE_MASK \
	(IDN_LOCALCONV | IDN_DELIMMAP | IDN_LOCALMAP | IDN_MAP | \
	 IDN_NORMALIZE | IDN_PROHCHECK | IDN_UNASCHECK | IDN_BIDICHECK | \
	 IDN_ASCCHECK | IDN_IDNCONV | IDN_LENCHECK | IDN_ENCODE_QUERY | \
	 IDN_UNDOIFERR)
#define DECODE_MASK \
	(IDN_DELIMMAP | IDN_MAP | IDN_NORMALIZE | IDN_PROHCHECK | \
	 IDN_UNASCHECK | IDN_BIDICHECK | IDN_IDNCONV | IDN_ASCCHECK | \
	 IDN_RTCHECK | IDN_LOCALCONV | IDN_DECODE_QUERY)
#else
#define ENCODE_MASK \
	(IDN_DELIMMAP | IDN_LOCALMAP | IDN_MAP | IDN_NORMALIZE | \
	 IDN_PROHCHECK | IDN_UNASCHECK | IDN_BIDICHECK | IDN_ASCCHECK | \
	 IDN_IDNCONV | IDN_LENCHECK | IDN_ENCODE_QUERY | IDN_UNDOIFERR)
#define DECODE_MASK \
	(IDN_DELIMMAP | IDN_MAP | IDN_NORMALIZE | IDN_PROHCHECK | \
	 IDN_UNASCHECK | IDN_BIDICHECK | IDN_IDNCONV | IDN_ASCCHECK | \
	 IDN_RTCHECK | IDN_DECODE_QUERY)
#endif

#define MAX_LABEL_LENGTH	63

/*
 * label to convert.
 */
typedef struct labellist * labellist_t;
struct labellist {
	unsigned long *name;
	size_t name_length;
	unsigned long *undo_name;
	labellist_t next;
	labellist_t previous;
	int dot_followed;
};

typedef idn_result_t (*res_insnproc_t)(idn_resconf_t ctx,
				       labellist_t label);

static void		idn_res_initialize(void);
static idn_result_t	copy_verbatim(const char *from, char *to,
				      size_t tolen);
static idn_result_t	labellist_create(const unsigned long *name,
					 labellist_t *labelp);
static void		labellist_destroy(labellist_t label);
static idn_result_t	labellist_setname(labellist_t label,
					  const unsigned long *name);
static const unsigned long *
			labellist_getname(labellist_t label);
static const unsigned long *
			labellist_gettldname(labellist_t label);
static idn_result_t	labellist_getnamelist(labellist_t label,
					      unsigned long *name,
					      size_t label_length);
static void		labellist_undo(labellist_t label);
static labellist_t	labellist_tail(labellist_t label);
static labellist_t	labellist_previous(labellist_t label);

#ifndef WITHOUT_ICONV
static idn_result_t	label_localdecodecheck(idn_resconf_t ctx,
					       labellist_t label);
#endif
static idn_result_t	label_idnencode_ace(idn_resconf_t ctx,
					    labellist_t label);
static idn_result_t	label_idndecode(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_localmap(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_map(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_normalize(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_prohcheck(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_unascheck(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_bidicheck(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_asccheck(idn_resconf_t ctx, labellist_t label);
static idn_result_t	label_lencheck_ace(idn_resconf_t ctx,
					   labellist_t label);
static idn_result_t	label_lencheck_nonace(idn_resconf_t ctx,
					      labellist_t label);
static idn_result_t	label_rtcheck(idn_resconf_t ctx, idn_action_t actions,
				      labellist_t label,
				      const unsigned long *original_name);

static int initialized;
static int enabled;

void
idn_res_enable(int on_off) {
	if (!initialized) {
		idn_res_initialize();
	}

	if (on_off == 0) {
		enabled = 0;
	} else {
		enabled = 1;
	}
}

static void
idn_res_initialize(void) {
	if (!initialized) {
		char *value = getenv("IDN_DISABLE");

		if (value == NULL) {
			enabled = 1;
		} else {
			enabled = 0;
		}
		initialized = 1;
	}
}

idn_result_t
idn_res_encodename(idn_resconf_t ctx, idn_action_t actions, const char *from,
		    char *to, size_t tolen) {
	idn_converter_t local_converter = NULL;
	idn_converter_t idn_converter = NULL;
	idn_delimitermap_t delimiter_mapper;
	idn_result_t r;
	labellist_t labels = NULL, l;
	unsigned long *buffer = NULL;
	size_t buffer_length;
	int from_is_root;
	int idn_is_ace;

	assert(ctx != NULL && from != NULL && to != NULL);

	TRACE(("idn_res_encodename(actions=%s, from=\"%s\", tolen=%d)\n",
		idn__res_actionstostring(actions),
		idn__debug_xstring(from, 50), (int)tolen));

	if (actions & ~ENCODE_MASK) {
		WARNING(("idn_res_encodename: invalid actions 0x%x\n",
			 actions));
		r = idn_invalid_action;
		goto ret;
	}

	if (!initialized)
		idn_res_initialize();
	if (!enabled || actions == 0) {
		r = copy_verbatim(from, to, tolen);
		goto ret;
	} else if (tolen <= 0) {
		r = idn_buffer_overflow;
		goto ret;
	}

	if (actions & IDN_ENCODE_QUERY) {
#ifndef WITHOUT_ICONV
		actions |= (IDN_LOCALCONV | IDN_DELIMMAP | IDN_LOCALMAP | \
			    IDN_MAP | IDN_NORMALIZE | IDN_PROHCHECK | \
			    IDN_BIDICHECK | IDN_IDNCONV | IDN_LENCHECK);
#else
		actions |= (IDN_DELIMMAP | IDN_LOCALMAP | IDN_MAP | \
			    IDN_NORMALIZE | IDN_PROHCHECK | IDN_BIDICHECK | \
			    IDN_IDNCONV | IDN_LENCHECK);
#endif
	}

	/*
	 * Convert `from' to UCS4.
	 */
	local_converter = idn_resconf_getlocalconverter(ctx);
#ifndef WITHOUT_ICONV
	if (local_converter == NULL) {
		r = idn_invalid_name;
		goto ret;
	}
#endif

	idn_converter = idn_resconf_getidnconverter(ctx);
	if (idn_converter != NULL &&
	    idn_converter_isasciicompatible(idn_converter))
		idn_is_ace = 1;
	else
		idn_is_ace = 0;

	buffer_length = tolen * 2;

	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer, sizeof(*buffer) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer = (unsigned long *)new_buffer;

		if (actions & IDN_LOCALCONV) {
			r = idn_converter_convtoucs4(local_converter, from,
						     buffer, buffer_length);
		} else {
			r = idn_ucs4_utf8toucs4(from, buffer, buffer_length);
		}
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}

	if (*buffer == '\0') {
		if (tolen <= 0) {
			r = idn_buffer_overflow;
			goto ret;
		}
		*to = '\0';
		r = idn_success;
		goto ret;
	}

	/*
	 * Delimiter map.
	 */
	if (actions & IDN_DELIMMAP) {
		TRACE(("res delimitermap(name=\"%s\")\n",
		       idn__debug_ucs4xstring(buffer, 50)));

		delimiter_mapper = idn_resconf_getdelimitermap(ctx);
		if (delimiter_mapper != NULL) {
			r = idn_delimitermap_map(delimiter_mapper, buffer,
						 buffer, buffer_length);
			idn_delimitermap_destroy(delimiter_mapper);
			if (r != idn_success)
				goto ret;
		}
		TRACE(("res delimitermap(): success (name=\"%s\")\n",
		       idn__debug_ucs4xstring(buffer, 50)));
	}

	from_is_root = (buffer[0] == '.' && buffer[1] == '\0');

	/*
	 * Split the name into a list of labels.
	 */
	r = labellist_create(buffer, &labels);
	if (r != idn_success)
		goto ret;

	/*
	 * Perform conversions and tests.
	 */
	for (l = labellist_tail(labels); l != NULL;
	     l = labellist_previous(l)) {

		if (!idn__util_ucs4isasciirange(labellist_getname(l))) {
			if (actions & IDN_LOCALMAP) {
				r = label_localmap(ctx, l);
				if (r != idn_success)
					goto ret;
			}
		}

		if (!idn__util_ucs4isasciirange(labellist_getname(l))) {
			if (actions & IDN_MAP) {
				r = label_map(ctx, l);
				if (r != idn_success)
					goto ret;
			}
			if (actions & IDN_NORMALIZE) {
				r = label_normalize(ctx, l);
				if (r != idn_success)
					goto ret;
			}
			if (actions & IDN_PROHCHECK) {
				r = label_prohcheck(ctx, l);
				if (r == idn_prohibited && 
				    (actions & IDN_UNDOIFERR)) {
					labellist_undo(l);
					continue;
				} else if (r != idn_success) {
					goto ret;
				}
			}
			if (actions & IDN_UNASCHECK) {
				r = label_unascheck(ctx, l);
				if (r == idn_prohibited && 
				    (actions & IDN_UNDOIFERR)) {
					labellist_undo(l);
					continue;
				} else if (r != idn_success) {
					goto ret;
				}
			}
			if (actions & IDN_BIDICHECK) {
				r = label_bidicheck(ctx, l);
				if (r == idn_prohibited && 
				    (actions & IDN_UNDOIFERR)) {
					labellist_undo(l);
					continue;
				} else if (r != idn_success) {
					goto ret;
				}
			}
		}

		if (actions & IDN_ASCCHECK) {
			r = label_asccheck(ctx, l);
			if (r == idn_prohibited && (actions & IDN_UNDOIFERR)) {
				labellist_undo(l);
				continue;
			} else if (r != idn_success) {
				goto ret;
			}
		}

		if (!idn__util_ucs4isasciirange(labellist_getname(l))) {
			if ((actions & IDN_IDNCONV) && idn_is_ace) {
				r = label_idnencode_ace(ctx, l);
				if (r != idn_success)
					goto ret;
			}
		}

		if (!from_is_root && (actions & IDN_LENCHECK)) {
			if (idn_is_ace)
				r = label_lencheck_ace(ctx, l);
			else
				r = label_lencheck_nonace(ctx, l);
			if (r == idn_invalid_length &&
			    (actions & IDN_UNDOIFERR)) {
				labellist_undo(l);
				continue;
			} else if (r != idn_success) {
				goto ret;
			}
		}
	}

	/*
	 * Concat a list of labels to a name.
	 */
	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer, sizeof(*buffer) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer = (unsigned long *)new_buffer;

		r = labellist_getnamelist(labels, buffer, buffer_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}

	if ((actions & IDN_IDNCONV) && idn_converter != NULL && !idn_is_ace) {
		r = idn_converter_convfromucs4(idn_converter, buffer, to,
					       tolen);
	} else {
		r = idn_ucs4_ucs4toutf8(buffer, to, tolen);
	}

ret:
	if (r == idn_success) {
		TRACE(("idn_res_encodename(): success (to=\"%s\")\n",
		       idn__debug_xstring(to, 50)));
	} else {
		TRACE(("idn_res_encodename(): %s\n", idn_result_tostring(r)));
	}
	free(buffer);
	if (local_converter != NULL)
		idn_converter_destroy(local_converter);
	if (idn_converter != NULL)
		idn_converter_destroy(idn_converter);
	if (labels != NULL)
		labellist_destroy(labels);
	return (r);
}

idn_result_t
idn_res_decodename(idn_resconf_t ctx, idn_action_t actions, const char *from,
		    char *to, size_t tolen) {
	idn_converter_t local_converter = NULL;
	idn_converter_t idn_converter = NULL;
	idn_delimitermap_t delimiter_mapper;
	idn_result_t r;
	labellist_t labels = NULL, l;
	unsigned long *buffer = NULL;
	unsigned long *saved_name = NULL;
	size_t buffer_length;
	int idn_is_ace;

	assert(ctx != NULL && from != NULL && to != NULL);

	TRACE(("idn_res_decodename(actions=%s, from=\"%s\", tolen=%d)\n",
		idn__res_actionstostring(actions),
		idn__debug_xstring(from, 50), (int)tolen));

	if (actions & ~DECODE_MASK) {
		WARNING(("idn_res_decodename: invalid actions 0x%x\n",
			 actions));
		r = idn_invalid_action;
		goto ret;
	}

	if (!initialized)
		idn_res_initialize();
	if (!enabled || actions == 0) {
		r = copy_verbatim(from, to, tolen);
		goto ret;
	} else if (tolen <= 0) {
		r = idn_buffer_overflow;
		goto ret;
	}

	if (actions & IDN_DECODE_QUERY) {
#ifndef WITHOUT_ICONV
		actions |= (IDN_DELIMMAP | IDN_MAP | IDN_NORMALIZE | \
			    IDN_PROHCHECK | IDN_BIDICHECK | IDN_IDNCONV | \
			    IDN_RTCHECK | IDN_LOCALCONV);
#else
		actions |= (IDN_DELIMMAP | IDN_MAP | IDN_NORMALIZE | \
			    IDN_PROHCHECK | IDN_BIDICHECK | IDN_IDNCONV | \
			    IDN_RTCHECK);
#endif
	}

	/*
	 * Convert `from' to UCS4.
	 */
	local_converter = idn_resconf_getlocalconverter(ctx);
#ifndef WITHOUT_ICONV
	if (local_converter == NULL) {
		r = idn_invalid_name;
		goto ret;
	}
#endif

	idn_converter = idn_resconf_getidnconverter(ctx);
	if (idn_converter != NULL &&
	    idn_converter_isasciicompatible(idn_converter))
		idn_is_ace = 1;
	else
		idn_is_ace = 0;

	buffer_length = tolen * 2;

	TRACE(("res idndecode(name=\"%s\")\n", idn__debug_xstring(from, 50)));

	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer, sizeof(*buffer) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer = (unsigned long *)new_buffer;

		if ((actions & IDN_IDNCONV) &&
		     idn_converter != NULL && !idn_is_ace) {
			r = idn_converter_convtoucs4(idn_converter, from,
						     buffer, buffer_length);
		} else {
			r = idn_ucs4_utf8toucs4(from, buffer, buffer_length);
		}
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}

	if (*buffer == '\0') {
		if (tolen <= 0) {
			r = idn_buffer_overflow;
			goto ret;
		}
		*to = '\0';
		r = idn_success;
		goto ret;
	}

	/*
	 * Delimiter map.
	 */
	if (actions & IDN_DELIMMAP) {
		TRACE(("res delimitermap(name=\"%s\")\n",
		       idn__debug_ucs4xstring(buffer, 50)));

		delimiter_mapper = idn_resconf_getdelimitermap(ctx);
		if (delimiter_mapper != NULL) {
			r = idn_delimitermap_map(delimiter_mapper, buffer,
						 buffer, buffer_length);
			idn_delimitermap_destroy(delimiter_mapper);
			if (r != idn_success)
				goto ret;
		}
		TRACE(("res delimitermap(): success (name=\"%s\")\n",
		       idn__debug_ucs4xstring(buffer, 50)));
	}

	/*
	 * Split the name into a list of labels.
	 */
	r = labellist_create(buffer, &labels);
	if (r != idn_success)
		goto ret;

	/*
	 * Perform conversions and tests.
	 */
	for (l = labellist_tail(labels); l != NULL;
	     l = labellist_previous(l)) {

		free(saved_name);
		saved_name = NULL;

		if (!idn__util_ucs4isasciirange(labellist_getname(l))) {
			if (actions & IDN_MAP) {
				r = label_map(ctx, l);
				if (r != idn_success)
					goto ret;
			}
			if (actions & IDN_NORMALIZE) {
				r = label_normalize(ctx, l);
				if (r != idn_success)
					goto ret;
			}
			if (actions & IDN_PROHCHECK) {
				r = label_prohcheck(ctx, l);
				if (r == idn_prohibited) {
					labellist_undo(l);
					continue;
				} else if (r != idn_success) {
					goto ret;
				}
			}
			if (actions & IDN_UNASCHECK) {
				r = label_unascheck(ctx, l);
				if (r == idn_prohibited) {
					labellist_undo(l);
					continue;
				} else if (r != idn_success) {
					goto ret;
				}
			}
			if (actions & IDN_BIDICHECK) {
				r = label_bidicheck(ctx, l);
				if (r == idn_prohibited) {
					labellist_undo(l);
					continue;
				} else if (r != idn_success) {
					goto ret;
				}
			}
		}

		if ((actions & IDN_IDNCONV) && idn_is_ace) {
			saved_name = idn_ucs4_strdup(labellist_getname(l));
			if (saved_name == NULL) {
				r = idn_nomemory;
				goto ret;
			}
			r = label_idndecode(ctx, l);
			if (r == idn_invalid_encoding) {
				labellist_undo(l);
				continue;
			} else if (r != idn_success) {
				goto ret;
			}
		}
		if ((actions & IDN_RTCHECK) && saved_name != NULL) {
			r = label_rtcheck(ctx, actions, l, saved_name);
			if (r == idn_invalid_encoding) {
				labellist_undo(l);
				continue;
			} else if (r != idn_success) {
				goto ret;
			}
		}

#ifndef WITHOUT_ICONV
		if (actions & IDN_LOCALCONV) {
			r = label_localdecodecheck(ctx, l);
			if (r != idn_success)
				goto ret;
		}
#endif
	}

	/*
	 * Concat a list of labels to a name.
	 */
	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer, sizeof(*buffer) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer = (unsigned long *)new_buffer;

		r = labellist_getnamelist(labels, buffer, buffer_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}

	if (actions & IDN_LOCALCONV) {
		r = idn_converter_convfromucs4(local_converter, buffer, to,
					       tolen);
	} else {
		r = idn_ucs4_ucs4toutf8(buffer, to, tolen);
	}

ret:
	if (r == idn_success) {
		TRACE(("idn_res_decodename(): success (to=\"%s\")\n",
		       idn__debug_xstring(to, 50)));
	} else {
		TRACE(("idn_res_decodename(): %s\n", idn_result_tostring(r)));
	}
	free(saved_name);
	free(buffer);
	if (local_converter != NULL)
		idn_converter_destroy(local_converter);
	if (idn_converter != NULL)
		idn_converter_destroy(idn_converter);
	if (labels != NULL)
		labellist_destroy(labels);
	return (r);
}

idn_result_t
idn_res_decodename2(idn_resconf_t ctx, idn_action_t actions, const char *from,
		    char *to, size_t tolen, const char *auxencoding) {
#ifdef WITHOUT_ICONV
	return idn_failure;

#else /* WITHOUT_ICONV */
	idn_result_t r;
	idn_converter_t aux_converter = NULL;
	unsigned long *buffer_ucs4 = NULL;
	char *buffer_utf8 = NULL;
	size_t buffer_length;

	assert(ctx != NULL && from != NULL && to != NULL);

	TRACE(("idn_res_decodename2(actions=%s, from=\"%s\", tolen=%d, "
		"auxencoding=\"%s\")\n",
		idn__res_actionstostring(actions),
		idn__debug_xstring(from, 50), (int)tolen,
		(auxencoding != NULL) ? auxencoding : "<null>"));

	if (!initialized)
		idn_res_initialize();
	if (!enabled || actions == 0) {
		r = copy_verbatim(from, to, tolen);
		goto ret;
	} else if (tolen <= 0) {
		r = idn_buffer_overflow;
		goto ret;
	}

	if (auxencoding == NULL ||
	    strcmp(auxencoding, IDN_UTF8_ENCODING_NAME) == 0 ||
	    strcmp(auxencoding, "UTF-8") == 0) {
		return idn_res_decodename(ctx, actions, from, to, tolen);
	}

	/*
	 * Convert `from' to UCS4.
	 */
	r = idn_resconf_setauxidnconvertername(ctx, auxencoding,
					       IDN_CONVERTER_DELAYEDOPEN);
	if (r != idn_success) {
		goto ret;
	}

	aux_converter = idn_resconf_getauxidnconverter(ctx);
	if (aux_converter == NULL) {
		r = idn_failure;
		goto ret;
	}

	buffer_length = tolen * 2;
	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer_ucs4,
				     sizeof(*buffer_ucs4) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer_ucs4 = (unsigned long *)new_buffer;

		r = idn_converter_convtoucs4(aux_converter, from,
					     buffer_ucs4,
					     buffer_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}

	if (*buffer_ucs4 == '\0') {
		if (tolen <= 0) {
			r = idn_buffer_overflow;
			goto ret;
		}
		*to = '\0';
		r = idn_success;
		goto ret;
	}

	/*
	 * Convert `buffer_ucs4' to UTF-8.
	 */
	buffer_length = tolen * 2;
	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer_utf8,
				     sizeof(*buffer_utf8) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer_utf8 = (char *)new_buffer;
		r = idn_ucs4_ucs4toutf8(buffer_ucs4, buffer_utf8,
					buffer_length);

		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}
	
	if (*buffer_utf8 == '\0') {
		if (tolen <= 0) {
			r = idn_buffer_overflow;
			goto ret;
		}
		*to = '\0';
		r = idn_success;
		goto ret;
	}

	r = idn_res_decodename(ctx, actions, buffer_utf8, to, tolen);

ret:
	if (r == idn_success) {
		TRACE(("idn_res_decodename2(): success (to=\"%s\")\n",
		       idn__debug_xstring(to, 50)));
	} else {
		TRACE(("idn_res_decodename2(): %s\n", idn_result_tostring(r)));
	}
	free(buffer_ucs4);
	free(buffer_utf8);
	if (aux_converter != NULL)
		idn_converter_destroy(aux_converter);

	return (r);

#endif /* WITHOUT_ICONV */
}

static idn_result_t
copy_verbatim(const char *from, char *to, size_t tolen) {
	size_t fromlen = strlen(from);

	if (fromlen + 1 > tolen)
		return (idn_buffer_overflow);
	(void)memcpy(to, from, fromlen + 1);
	return (idn_success);
}

static idn_result_t
labellist_create(const unsigned long *name, labellist_t *labelp) {
	size_t length, malloc_length;
	labellist_t head_label = NULL;
	labellist_t tail_label = NULL;
	labellist_t new_label = NULL;
	const unsigned long *endp = NULL;
	idn_result_t r;

	while (*name != '\0') {
		for (endp = name; *endp != '.' && *endp != '\0'; endp++)
			;  /* nothing to be done */
		length = (endp - name) + 1;
		malloc_length = length + 15;  /* add 15 for margin */

		new_label = (labellist_t)
			    malloc(sizeof(struct labellist));
		if (new_label == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		if (head_label == NULL)
			head_label = new_label;

		new_label->name = NULL;
		new_label->undo_name = NULL;
		new_label->name_length = malloc_length;
		new_label->next = NULL;
		new_label->previous = NULL;
		new_label->dot_followed = (*endp == '.');

		new_label->name = (unsigned long *)
				  malloc(sizeof(long) * malloc_length);
		if (new_label->name == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		memcpy(new_label->name, name, sizeof(long) * length);
		*(new_label->name + length - 1) = '\0';

		new_label->undo_name = (unsigned long *)
				       malloc(sizeof(long) * malloc_length);
		if (new_label->undo_name == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		memcpy(new_label->undo_name, name, sizeof(long) * length);
		*(new_label->undo_name + length - 1) = '\0';

		if (tail_label != NULL) {
			tail_label->next = new_label;
			new_label->previous = tail_label;
		}
		tail_label = new_label;

		if (*endp == '.')
			name = endp + 1;
		else
			name = endp;
	}

	*labelp = head_label;
	r = idn_success;

ret:
	if (r != idn_success) {
		if (new_label != NULL) {
			free(new_label->name);
			free(new_label->undo_name);
			free(new_label);
		}
		if (head_label != NULL)
			labellist_destroy(head_label);
	}
	return (r);
}


static void
labellist_destroy(labellist_t label) {
	labellist_t l, l_next;

	for (l = label; l != NULL; l = l_next) {
		l_next = l->next;
		free(l->name);
		free(l->undo_name);
		free(l);
	}
}

static idn_result_t
labellist_setname(labellist_t label, const unsigned long *name) {
	unsigned long *new_name;
	size_t length, new_length;

	length = idn_ucs4_strlen(name) + 1;
	new_length = length + 15;  /* add 15 for margin */

	if (label->name_length < new_length) {
		new_name = (unsigned long *)
			   realloc(label->name, sizeof(long) * new_length);
		if (new_name == NULL)
			return (idn_nomemory);
		label->name = new_name;
		label->name_length = new_length;
	}
	memcpy(label->name, name, sizeof(long) * length);

	return (idn_success);
}

static const unsigned long *
labellist_getname(labellist_t label) {
	return (label->name);
}

static const unsigned long *
labellist_gettldname(labellist_t label) {
	labellist_t l;

	if (label->previous == NULL && label->next == NULL &&
	    !label->dot_followed)
		return (idn_mapselector_getnotld());

	for (l = label; l->next != NULL; l = l->next)
		;  /* nothing to be done */

	return (l->name);
}

static idn_result_t
labellist_getnamelist(labellist_t label, unsigned long *name,
			  size_t name_length) {
	static const unsigned long dot_string[] = {0x002e, 0x0000};  /* "." */
	size_t length;
	labellist_t l;

	for (l = label, length = 0; l != NULL; l = l->next)
		length += idn_ucs4_strlen(l->name) + 1;  /* name + `.' */
	length++;  /* for NUL */

	if (name_length < length)
		return (idn_buffer_overflow);

	*name = '\0';
	for (l = label; l != NULL; l = l->next) {
		idn_ucs4_strcat(name, l->name);
		name += idn_ucs4_strlen(name);
		if (l->dot_followed)
			idn_ucs4_strcat(name, dot_string);
	}
	return (idn_success);
}

static void
labellist_undo(labellist_t label) {
	size_t length;

	length = idn_ucs4_strlen(label->undo_name) + 1;
	memcpy(label->name, label->undo_name, sizeof(long) * length);
}

static labellist_t
labellist_tail(labellist_t label) {
	labellist_t l;

	if (label == NULL)
		return (NULL);
	for (l = label; l->next != NULL; l = l->next)
		;  /* nothing to be done */
	return (l);
}

static labellist_t
labellist_previous(labellist_t label) {
	return (label->previous);
}

#ifndef WITHOUT_ICONV

static idn_result_t
label_localdecodecheck(idn_resconf_t ctx, labellist_t label) {
	idn_converter_t local_converter = NULL;
	const unsigned long *from;
	char *to = NULL;
	size_t to_length;
	idn_result_t r;

	from = labellist_getname(label);
	to_length = idn_ucs4_strlen(from) + 1 + 15;  /* 15 for margin */
	TRACE(("res ucs4tolocal_check(label=\"%s\")\n",
	       idn__debug_ucs4xstring(from, 50)));

	local_converter = idn_resconf_getlocalconverter(ctx);
	if (local_converter == NULL) {
		r = idn_success;
		goto ret;
	}

	for (;;) {
		char *new_buffer;

		new_buffer = (char *)realloc(to, to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		to = new_buffer;
		r = idn_converter_convfromucs4(local_converter, from, to,
					       to_length);
		if (r == idn_success)
			break;
		else if (r == idn_nomapping) {
			r = label_idnencode_ace(ctx, label);
			if (r != idn_success)
				goto ret;
			break;
		} else if (r != idn_buffer_overflow) {
			goto ret;
		}
		to_length *= 2;
	}

	r = idn_success;
ret:
	TRACE(("res ucs4tolocal_check(): %s\n", idn_result_tostring(r)));
	if (local_converter != NULL)
		idn_converter_destroy(local_converter);
	free(to);
	return (r);
}

#endif /* !WITHOUT_ICONV */

static idn_result_t
label_idndecode(idn_resconf_t ctx, labellist_t label) {
	idn_converter_t idn_converter = NULL;
	const unsigned long *from;
	char *ascii_from = NULL;
	unsigned long *to = NULL;
	size_t from_length, to_length;
	idn_result_t r;

	from = labellist_getname(label);
	from_length = idn_ucs4_strlen(from) + 1;
	TRACE(("res idntoucs4(label=\"%s\")\n",
	       idn__debug_ucs4xstring(from, 50)));

	idn_converter = idn_resconf_getidnconverter(ctx);
	if (idn_converter == NULL) {
		r = idn_success;
		goto ret;
	}

	for (;;) {
		char *new_buffer;

		new_buffer = (char *) realloc(ascii_from, from_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		ascii_from = new_buffer;
		r = idn_ucs4_ucs4toutf8(from, ascii_from, from_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		from_length *= 2;
	}

	to = NULL;
	to_length = from_length;

	for (;;) {
		unsigned long *new_buffer;

		new_buffer = (unsigned long *)
			     realloc(to, sizeof(long) * to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		to = new_buffer;
		r = idn_converter_convtoucs4(idn_converter, ascii_from, to,
					     to_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		to_length *= 2;
	}

	r = labellist_setname(label, to);
ret:
	if (r == idn_success) {
		TRACE(("res idntoucs4(): success (label=\"%s\")\n",
		       idn__debug_ucs4xstring(labellist_getname(label),
					      50)));
	} else {
		TRACE(("res idntoucs4(): %s\n", idn_result_tostring(r)));
	}
	if (idn_converter != NULL)
		idn_converter_destroy(idn_converter);
	free(to);
	free(ascii_from);
	return (r);
}

static idn_result_t
label_idnencode_ace(idn_resconf_t ctx, labellist_t label) {
	idn_converter_t idn_converter = NULL;
	const unsigned long *from;
	char *ascii_to = NULL;
	unsigned long *to = NULL;
	size_t to_length;
	idn_result_t r;

	from = labellist_getname(label);
	TRACE(("res ucs4toidn(label=\"%s\")\n",
	       idn__debug_ucs4xstring(from, 50)));

	idn_converter = idn_resconf_getidnconverter(ctx);
	if (idn_converter == NULL) {
		r = idn_success;
		goto ret;
	}

	ascii_to = NULL;
	to_length = idn_ucs4_strlen(from) * 4 + 16;  /* add mergin */

	for (;;) {
		char *new_buffer;

		new_buffer = (char *) realloc(ascii_to, to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		ascii_to = new_buffer;
		r = idn_converter_convfromucs4(idn_converter, from, ascii_to,
					       to_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		to_length *= 2;
	}

	for (;;) {
		unsigned long *new_buffer;

		new_buffer = (unsigned long *)
			     realloc(to, sizeof(long) * to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		to = new_buffer;
		r = idn_ucs4_utf8toucs4(ascii_to, to, to_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		to_length *= 2;
	}

	if (r != idn_success)
		goto ret;

	r = labellist_setname(label, to);
ret:
	if (r == idn_success) {
		TRACE(("res ucs4toidn(): success (label=\"%s\")\n",
		       idn__debug_ucs4xstring(labellist_getname(label),
					      50)));
	} else {
		TRACE(("res ucs4toidn(): %s\n", idn_result_tostring(r)));
	}
	if (idn_converter != NULL)
		idn_converter_destroy(idn_converter);
	free(to);
	free(ascii_to);
	return (r);
}

static idn_result_t
label_localmap(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *from;
	const unsigned long *tld;
	unsigned long *to = NULL;
	size_t to_length;
	idn_mapselector_t local_mapper;
	idn_result_t r;

	from = labellist_getname(label);
	tld = labellist_gettldname(label);
	TRACE(("res localmap(label=\"%s\", tld=\"%s\")\n",
	       idn__debug_ucs4xstring(from, 50),
	       idn__debug_ucs4xstring(tld, 50)));

	local_mapper = idn_resconf_getlocalmapselector(ctx);
	if (local_mapper == NULL) {
		r = idn_success;
		goto ret;
	}

	if (tld == from)
		tld = idn_mapselector_getdefaulttld();
	to_length = idn_ucs4_strlen(from) + 1 + 15;  /* 15 for margin */

	for (;;) {
		unsigned long *new_buffer;

		new_buffer = (unsigned long *)
			     realloc(to, sizeof(long) * to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		to = new_buffer;
		r = idn_mapselector_map2(local_mapper, from, tld, to,
					 to_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		to_length *= 2;
	}

	r = labellist_setname(label, to);
ret:
	if (r == idn_success) {
		TRACE(("res localmap(): success (label=\"%s\")\n",
		       idn__debug_ucs4xstring(labellist_getname(label),
					      50)));
	} else {
		TRACE(("res localmap(): %s\n", idn_result_tostring(r)));
	}
	if (local_mapper != NULL)
		idn_mapselector_destroy(local_mapper);
	free(to);
	return (r);
}

static idn_result_t
label_map(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *from;
	unsigned long *to = NULL;
	size_t to_length;
	idn_mapper_t mapper;
	idn_result_t r;

	from = labellist_getname(label);
	TRACE(("res map(label=\"%s\")\n", idn__debug_ucs4xstring(from, 50)));

	mapper = idn_resconf_getmapper(ctx);
	if (mapper == NULL) {
		r = idn_success;
		goto ret;
	}
	to_length = idn_ucs4_strlen(from) + 1 + 15;  /* 15 for margin */

	for (;;) {
		unsigned long *new_buffer;

		new_buffer = (unsigned long *)
			     realloc(to, sizeof(long) * to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		to = new_buffer;
		r = idn_mapper_map(mapper, from, to, to_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		to_length *= 2;
	}

	r = labellist_setname(label, to);
ret:
	if (r == idn_success) {
		TRACE(("res map(): success (label=\"%s\")\n",
		       idn__debug_ucs4xstring(labellist_getname(label),
					      50)));
	} else {
		TRACE(("res map(): %s\n", idn_result_tostring(r)));
	}
	if (mapper != NULL)
		idn_mapper_destroy(mapper);
	free(to);
	return (r);
}

static idn_result_t
label_normalize(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *from;
	unsigned long *to = NULL;
	size_t to_length;
	idn_normalizer_t normalizer;
	idn_result_t r;

	from = labellist_getname(label);
	TRACE(("res normalzie(label=\"%s\")\n",
	       idn__debug_ucs4xstring(from, 50)));

	normalizer = idn_resconf_getnormalizer(ctx);
	if (normalizer == NULL) {
		r = idn_success;
		goto ret;
	}
	to_length = idn_ucs4_strlen(from) + 1 + 15;  /* 15 for margin */

	for (;;) {
		unsigned long *new_buffer;

		new_buffer = (unsigned long *)
			     realloc(to, sizeof(long) * to_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		to = new_buffer;
		r = idn_normalizer_normalize(normalizer, from, to, to_length);
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;
		to_length *= 2;
	}

	r = labellist_setname(label, to);
ret:
	if (r == idn_success) {
		TRACE(("res normalize(): success (label=\"%s\")\n",
		       idn__debug_ucs4xstring(labellist_getname(label),
					      50)));
	} else {
		TRACE(("res normalize(): %s\n", idn_result_tostring(r)));
	}
	if (normalizer != NULL)
		idn_normalizer_destroy(normalizer);
	free(to);
	return (r);
}

static idn_result_t
label_prohcheck(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *name, *found;
	idn_checker_t prohibit_checker;
	idn_result_t r;

	name = labellist_getname(label);
	TRACE(("res prohcheck(label=\"%s\")\n",
	       idn__debug_ucs4xstring(name, 50)));

	prohibit_checker = idn_resconf_getprohibitchecker(ctx);
	if (prohibit_checker == NULL) {
		r = idn_success;
		goto ret;
	}

	r = idn_checker_lookup(prohibit_checker, name, &found);
	idn_checker_destroy(prohibit_checker);
	if (r == idn_success && found != NULL)
		r = idn_prohibited;

ret:
	TRACE(("res prohcheck(): %s\n", idn_result_tostring(r)));
	return (r);
}

static idn_result_t
label_unascheck(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *name, *found;
	idn_checker_t unassigned_checker;
	idn_result_t r;

	name = labellist_getname(label);
	TRACE(("res unascheck(label=\"%s\")\n",
	       idn__debug_ucs4xstring(name, 50)));

	unassigned_checker = idn_resconf_getunassignedchecker(ctx);
	if (unassigned_checker == NULL) {
		r = idn_success;
		goto ret;
	}

	r = idn_checker_lookup(unassigned_checker, name, &found);
	idn_checker_destroy(unassigned_checker);
	if (r == idn_success && found != NULL)
		r = idn_prohibited;

ret:
	TRACE(("res unascheck(): %s\n", idn_result_tostring(r)));
	return (r);
}

static idn_result_t
label_bidicheck(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *name, *found;
	idn_checker_t bidi_checker;
	idn_result_t r;

	name = labellist_getname(label);
	TRACE(("res bidicheck(label=\"%s\")\n",
	       idn__debug_ucs4xstring(name, 50)));

	bidi_checker = idn_resconf_getbidichecker(ctx);
	if (bidi_checker == NULL) {
		r = idn_success;
		goto ret;
	}

	r = idn_checker_lookup(bidi_checker, name, &found);
	idn_checker_destroy(bidi_checker);
	if (r == idn_success && found != NULL)
		r = idn_prohibited;

ret:
	TRACE(("res bidicheck(): %s\n", idn_result_tostring(r)));
	return (r);
}

static idn_result_t
label_asccheck(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *name, *n;
	idn_result_t r;

	name = labellist_getname(label);
	TRACE(("res asccheck(label=\"%s\")\n",
	       idn__debug_ucs4xstring(name, 50)));

	if (*name == '-') {
		r = idn_prohibited;
		goto ret;
	}

	for (n = name; *n != '\0'; n++) {
		if (*n <= '\177') {
			if ((*n < '0' || *n > '9') &&
			    (*n < 'A' || *n > 'Z') &&
			    (*n < 'a' || *n > 'z') &&
			    *n != '-') {
				r  = idn_prohibited;
				goto ret;
			}
		}
	}

	if (n > name && *(n - 1) == '-') {
		r  = idn_prohibited;
		goto ret;
	}

	r = idn_success;
ret:	
	TRACE(("res asccheck(): %s\n", idn_result_tostring(r)));
	return (r);
}

static idn_result_t
label_lencheck_ace(idn_resconf_t ctx, labellist_t label) {
	const unsigned long *name;
	size_t name_length;
	idn_result_t r;

	name = labellist_getname(label);
	name_length = idn_ucs4_strlen(name);
	TRACE(("res lencheck(label=\"%s\")\n",
	       idn__debug_ucs4xstring(name, 50)));

	if (name_length == 0 || name_length > MAX_LABEL_LENGTH) {
		r = idn_invalid_length;
		goto ret;
	}

	r = idn_success;
ret:	
	TRACE(("res lencheck(): %s\n", idn_result_tostring(r)));
	return (r);
}

static idn_result_t
label_lencheck_nonace(idn_resconf_t ctx, labellist_t label) {
	idn_converter_t idn_converter;
	const unsigned long *from;
	size_t to_length;
	idn_result_t r;
	char *buffer = NULL;
	size_t buffer_length;

	from = labellist_getname(label);
	TRACE(("res lencheck(label=\"%s\")\n",
	       idn__debug_ucs4xstring(from, 50)));

	buffer_length = idn_ucs4_strlen(from) * 4 + 16; /* 16 for margin */
	idn_converter = idn_resconf_getidnconverter(ctx);

	for (;;) {
		void *new_buffer;

		new_buffer = realloc(buffer, sizeof(*buffer) * buffer_length);
		if (new_buffer == NULL) {
			r = idn_nomemory;
			goto ret;
		}
		buffer = (char *)new_buffer;

		if (idn_converter != NULL) {
			r = idn_converter_convfromucs4(idn_converter, from,
						       buffer, buffer_length);
		} else {
			r = idn_ucs4_ucs4toutf8(from, buffer, buffer_length);
		}
		if (r == idn_success)
			break;
		else if (r != idn_buffer_overflow)
			goto ret;

		buffer_length *= 2;
	}

	to_length = strlen(buffer);
	if (to_length == 0 || to_length > MAX_LABEL_LENGTH) {
		r = idn_invalid_length;
		goto ret;
	}

	r = idn_success;
ret:
	TRACE(("res lencheck(): %s\n", idn_result_tostring(r)));
	if (idn_converter != NULL)
		idn_converter_destroy(idn_converter);
	free(buffer);
	return (r);
}

static idn_result_t
label_rtcheck(idn_resconf_t ctx, idn_action_t actions, labellist_t label,
	    const unsigned long *original_name) {
	labellist_t rt_label = NULL;
	const unsigned long *rt_name;
	const unsigned long *cur_name;
	idn_result_t r;

	cur_name = labellist_getname(label);
	TRACE(("res rtcheck(label=\"%s\", org_label=\"%s\")\n",
		idn__debug_ucs4xstring(cur_name, 50),
		idn__debug_ucs4xstring(original_name, 50)));

	r = labellist_create(cur_name, &rt_label);
	if (r != idn_success)
		goto ret;
	if (rt_label == NULL) {
		if (*original_name == '\0')
			r = idn_success;
		else
			r = idn_invalid_encoding;
		goto ret;
	}

	if (!idn__util_ucs4isasciirange(labellist_getname(rt_label))) {
		r = label_map(ctx, rt_label);
		if (r != idn_success)
			goto ret;
		r = label_normalize(ctx, rt_label);
		if (r != idn_success)
			goto ret;
		r = label_prohcheck(ctx, rt_label);
		if (r != idn_success)
			goto ret;
		if (actions & IDN_UNASCHECK) {
			r = label_unascheck(ctx, rt_label);
			if (r != idn_success)
				goto ret;
		}
		r = label_bidicheck(ctx, rt_label);
		if (r != idn_success)
			goto ret;
	}

	if (actions & IDN_ASCCHECK) {
		r = label_asccheck(ctx, rt_label);
		if (r != idn_success)
			goto ret;
	}
	if (!idn__util_ucs4isasciirange(labellist_getname(rt_label))) {
		r = label_idnencode_ace(ctx, rt_label);
		if (r != idn_success)
			goto ret;
	}
	r = label_lencheck_ace(ctx, rt_label);
	if (r != idn_success)
		goto ret;
	rt_name = labellist_getname(rt_label);

	if (idn_ucs4_strcasecmp(rt_name, original_name) != 0) {
		TRACE(("res rtcheck(): round trip failed, org =\"%s\", rt=\"%s\"\n",
		       idn__debug_ucs4xstring(original_name, 50),
		       idn__debug_ucs4xstring(rt_name, 50)));
		r = idn_invalid_encoding;
		goto ret;
	}

	r  = idn_success;
ret:
	if (r != idn_nomemory && r != idn_success)
		r = idn_invalid_encoding;
	TRACE(("res rtcheck(): %s\n", idn_result_tostring(r)));
	if (rt_label != NULL)
		labellist_destroy(rt_label);
	return (r);
}

const char *
idn__res_actionstostring(idn_action_t actions) {
	static char buf[100];

	buf[0] = '\0';

	if (actions == IDN_ENCODE_QUERY)
		strcpy(buf, "encode-query");
	else if (actions == IDN_DECODE_QUERY)
		strcpy(buf, "decode-query");
	else if (actions == IDN_ENCODE_APP)
		strcpy(buf, "encode-app");
	else if (actions == IDN_DECODE_APP)
		strcpy(buf, "decode-app");
	else if (actions == IDN_ENCODE_STORED)
		strcpy(buf, "encode-stored");
	else if (actions == IDN_DECODE_STORED)
		strcpy(buf, "decode-stored");
	else {
		if (actions & IDN_LOCALCONV)
			strcat(buf, "|localconv");
		if (actions & IDN_DELIMMAP)
			strcat(buf, "|delimmap");
		if (actions & IDN_LOCALMAP)
			strcat(buf, "|localmap");

		if (actions & IDN_MAP)
			strcat(buf, "|map");
		if (actions & IDN_NORMALIZE)
			strcat(buf, "|normalize");
		if (actions & IDN_PROHCHECK)
			strcat(buf, "|prohcheck");
		if (actions & IDN_UNASCHECK)
			strcat(buf, "|unascheck");
		if (actions & IDN_BIDICHECK)
			strcat(buf, "|bidicheck");

		if (actions & IDN_IDNCONV)
			strcat(buf, "|idnconv");
		if (actions & IDN_ASCCHECK)
			strcat(buf, "|asccheck");
		if (actions & IDN_LENCHECK)
			strcat(buf, "|lencheck");
		if (actions & IDN_RTCHECK)
			strcat(buf, "|rtcheck");
	}

	if (buf[0] == '|')
		return (buf + 1);
	else
		return (buf);
}
