unicode_iconv.c 4.23 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/*
 * 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"

typedef enum {
	unicode_iconv_utf8_to_16,
	unicode_iconv_utf16_to_8
} unicode_iconv_encoding_direction;

24
bool ntlm_unicode_init(ntlm_client *ntlm)
25
{
26 27
	ntlm->unicode_ctx.utf8_to_16 = iconv_open("UTF-16LE", "UTF-8");
	ntlm->unicode_ctx.utf16_to_8 = iconv_open("UTF-8", "UTF-16LE");
28

29 30
	if (ntlm->unicode_ctx.utf8_to_16 == (iconv_t)-1 ||
	    ntlm->unicode_ctx.utf16_to_8 == (iconv_t)-1) {
31
		if (errno == EINVAL)
32
			ntlm_client_set_errmsg(ntlm,
33 34
				"iconv does not support UTF8 <-> UTF16 conversion");
		else
35
			ntlm_client_set_errmsg(ntlm, strerror(errno));
36 37 38 39 40 41 42 43 44 45

		return false;
	}

	return true;
}

static inline bool unicode_iconv_encoding_convert(
	char **converted,
	size_t *converted_len,
46
	ntlm_client *ntlm,
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
	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;

	/*
	 * 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) {
66
		converter = ntlm->unicode_ctx.utf8_to_16;
67 68 69
		out_size = (string_len * 2) + 2;
		nul_size = 2;
	} else {
70
		converter = ntlm->unicode_ctx.utf16_to_8;
71 72 73 74 75 76 77 78
		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) {
79
		ntlm_client_set_errmsg(ntlm, "out of memory");
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96
		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) {
97
			ntlm_client_set_errmsg(ntlm, strerror(errno));
98 99 100 101 102 103 104
			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) {
105
			ntlm_client_set_errmsg(ntlm, "unicode conversion too large");
106 107 108 109
			goto on_error;
		}

		if ((new_out = realloc(out, out_size)) == NULL) {
110
			ntlm_client_set_errmsg(ntlm, "out of memory");
111 112 113 114 115 116 117
			goto on_error;
		}

		out = new_out;
	}

	if (in_start_len != 0) {
118
		ntlm_client_set_errmsg(ntlm,
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
			"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,
144
	ntlm_client *ntlm,
145 146 147 148
	const char *string,
	size_t string_len)
{
	return unicode_iconv_encoding_convert(
149
		converted, converted_len, ntlm, string, string_len,
150 151 152 153 154 155
		unicode_iconv_utf8_to_16);
}

bool ntlm_unicode_utf16_to_8(
	char **converted,
	size_t *converted_len,
156
	ntlm_client *ntlm,
157 158 159 160
	const char *string,
	size_t string_len)
{
	return unicode_iconv_encoding_convert(
161
		converted, converted_len, ntlm, string, string_len,
162 163 164
		unicode_iconv_utf16_to_8);
}

165
void ntlm_unicode_shutdown(ntlm_client *ntlm)
166
{
167 168 169
	if (ntlm->unicode_ctx.utf16_to_8 != (iconv_t)0 &&
	    ntlm->unicode_ctx.utf16_to_8 != (iconv_t)-1)
		iconv_close(ntlm->unicode_ctx.utf16_to_8);
170

171 172 173
	if (ntlm->unicode_ctx.utf8_to_16 != (iconv_t)0 &&
	    ntlm->unicode_ctx.utf8_to_16 != (iconv_t)-1)
		iconv_close(ntlm->unicode_ctx.utf8_to_16);
174

175 176
	ntlm->unicode_ctx.utf8_to_16 = (iconv_t)-1;
	ntlm->unicode_ctx.utf16_to_8 = (iconv_t)-1;
177
}