text: /*
   * Copyright (c) Edward Thomson.  All rights reserved.
   *
   * This file is part of ntlmclient, distributed under the MIT license.
   * For full terms and copyright information, and for third-party
   * copyright information, see the included LICENSE.txt file.
   */
  
  #include <locale.h>
  #include <iconv.h>
  #include <string.h>
  #include <errno.h>
  
  #include "ntlmclient.h"
  #include "unicode.h"
  #include "ntlm.h"
  #include "compat.h"
  
  struct ntlm_unicode_ctx {
  	ntlm_client *ntlm;
  	iconv_t utf8_to_16;
  	iconv_t utf16_to_8;
  };
  
  ntlm_unicode_ctx *ntlm_unicode_ctx_init(ntlm_client *ntlm)
  {
  	ntlm_unicode_ctx *ctx;
  
  	if ((ctx = calloc(1, sizeof(ntlm_unicode_ctx))) == NULL)
  		return NULL;
  
  	ctx->ntlm = ntlm;
  	ctx->utf8_to_16 = (iconv_t)-1;
  	ctx->utf16_to_8 = (iconv_t)-1;
  
  	return ctx;
  }
  
  typedef enum {
  	unicode_iconv_utf8_to_16,
  	unicode_iconv_utf16_to_8
  } unicode_iconv_encoding_direction;
  
  static inline bool unicode_iconv_init(ntlm_unicode_ctx *ctx)
  {
  	if (ctx->utf8_to_16 != (iconv_t)-1 || ctx->utf16_to_8 != (iconv_t)-1)
  		return true;
  
  	if ((ctx->utf8_to_16 = iconv_open("UTF-16LE", "UTF-8")) == (iconv_t)-1 ||
  		(ctx->utf16_to_8 = iconv_open("UTF-8", "UTF-16LE")) == (iconv_t)-1) {
  		if (errno == EINVAL)
  			ntlm_client_set_errmsg(ctx->ntlm,
  				"iconv does not support UTF8 <-> UTF16 conversion");
  		else
  			ntlm_client_set_errmsg(ctx->ntlm, strerror(errno));
  
  		return false;
  	}
  
  	return true;
  }
  
  static inline bool unicode_iconv_encoding_convert(
  	char **converted,
  	size_t *converted_len,
  	ntlm_unicode_ctx *ctx,
  	const char *string,
  	size_t string_len,
  	unicode_iconv_encoding_direction direction)
  {
  	char *in_start, *out_start, *out, *new_out;
  	size_t in_start_len, out_start_len, out_size, nul_size, ret, written = 0;
  	iconv_t converter;
  
  	*converted = NULL;
  	*converted_len = 0;
  
  	if (!unicode_iconv_init(ctx))
  		return false;
  
  	/*
  	 * When translating UTF8 to UTF16, these strings are only used
  	 * internally, and we obey the given length, so we can simply
  	 * use a buffer that is 2x the size.  When translating from UTF16
  	 * to UTF8, we may need to return to callers, so we need to NUL
  	 * terminate and expect an extra byte for UTF8, two for UTF16.
  	 */
  	if (direction == unicode_iconv_utf8_to_16) {
  		converter = ctx->utf8_to_16;
  		out_size = (string_len * 2) + 2;
  		nul_size = 2;
  	} else {
  		converter = ctx->utf16_to_8;
  		out_size = (string_len / 2) + 1;
  		nul_size = 1;
  	}
  
  	/* Round to the nearest multiple of 8 */
  	out_size = (out_size + 7) & ~7;
  
  	if ((out = malloc(out_size)) == NULL) {
  		ntlm_client_set_errmsg(ctx->ntlm, "out of memory");
  		return false;
  	}
  
  	in_start = (char *)string;
  	in_start_len = string_len;
  
  	while (true) {
  		out_start = out + written;
  		out_start_len = (out_size - nul_size) - written;
  
  		ret = iconv(converter, &in_start, &in_start_len, &out_start, &out_start_len);
  		written = (out_size - nul_size) - out_start_len;
  
  		if (ret == 0)
  			break;
  
  		if (ret == (size_t)-1 && errno != E2BIG) {
  			ntlm_client_set_errmsg(ctx->ntlm, strerror(errno));
  			goto on_error;
  		}
  
  		/* Grow buffer size by 1.5 (rounded up to a multiple of 8) */
  		out_size = ((((out_size << 1) - (out_size >> 1)) + 7) & ~7);
  
  		if (out_size > NTLM_UNICODE_MAX_LEN) {
  			ntlm_client_set_errmsg(ctx->ntlm,
  				"unicode conversion too large");
  			goto on_error;
  		}
  
  		if ((new_out = realloc(out, out_size)) == NULL) {
  			ntlm_client_set_errmsg(ctx->ntlm, "out of memory");
  			goto on_error;
  		}
  
  		out = new_out;
  	}
  
  	if (in_start_len != 0) {
  		ntlm_client_set_errmsg(ctx->ntlm,
  			"invalid unicode string; trailing data remains");
  		goto on_error;
  	}
  
  	/* NUL terminate */
  	out[written] = '\0';
  
  	if (direction == unicode_iconv_utf8_to_16)
  		out[written + 1] = '\0';
  
  	*converted = out;
  
  	if (converted_len)
  		*converted_len = written;
  
  	return true;
  
  on_error:
  	free(out);
  	return false;
  }
  
  bool ntlm_unicode_utf8_to_16(
  	char **converted,
  	size_t *converted_len,
  	ntlm_unicode_ctx *ctx,
  	const char *string,
  	size_t string_len)
  {
  	return unicode_iconv_encoding_convert(
  		converted, converted_len, ctx, string, string_len,
  		unicode_iconv_utf8_to_16);
  }
  
  bool ntlm_unicode_utf16_to_8(
  	char **converted,
  	size_t *converted_len,
  	ntlm_unicode_ctx *ctx,
  	const char *string,
  	size_t string_len)
  {
  	return unicode_iconv_encoding_convert(
  		converted, converted_len, ctx, string, string_len,
  		unicode_iconv_utf16_to_8);
  }
  
  void ntlm_unicode_ctx_free(ntlm_unicode_ctx *ctx)
  {
  	if (!ctx)
  		return;
  
  	if (ctx->utf16_to_8 != (iconv_t)-1)
  		iconv_close(ctx->utf16_to_8);
  
  	if (ctx->utf8_to_16 != (iconv_t)-1)
  		iconv_close(ctx->utf8_to_16);
  
  	free(ctx);
  }
  