1#ifndef lint
2static char *rcsid = "$Id: idnconv.c,v 1.1 2003/06/04 00:27:07 marka Exp $";
3#endif
4
5/*
6 * Copyright (c) 2000,2001,2002 Japan Network Information Center.
7 * All rights reserved.
8 *
9 * By using this file, you agree to the terms and conditions set forth bellow.
10 *
11 * 			LICENSE TERMS AND CONDITIONS
12 *
13 * The following License Terms and Conditions apply, unless a different
14 * license is obtained from Japan Network Information Center ("JPNIC"),
15 * a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
16 * Chiyoda-ku, Tokyo 101-0047, Japan.
17 *
18 * 1. Use, Modification and Redistribution (including distribution of any
19 *    modified or derived work) in source and/or binary forms is permitted
20 *    under this License Terms and Conditions.
21 *
22 * 2. Redistribution of source code must retain the copyright notices as they
23 *    appear in each source code file, this License Terms and Conditions.
24 *
25 * 3. Redistribution in binary form must reproduce the Copyright Notice,
26 *    this License Terms and Conditions, in the documentation and/or other
27 *    materials provided with the distribution.  For the purposes of binary
28 *    distribution the "Copyright Notice" refers to the following language:
29 *    "Copyright (c) 2000-2002 Japan Network Information Center.  All rights reserved."
30 *
31 * 4. The name of JPNIC may not be used to endorse or promote products
32 *    derived from this Software without specific prior written approval of
33 *    JPNIC.
34 *
35 * 5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
36 *    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37 *    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
38 *    PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL JPNIC BE LIABLE
39 *    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
40 *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
41 *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
42 *    BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
43 *    WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
44 *    OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
45 *    ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
46 */
47
48/*
49 * idnconv -- Codeset converter for named.conf and zone files
50 */
51
52#include <config.h>
53
54#include <stdio.h>
55#include <stddef.h>
56#include <stdlib.h>
57#include <string.h>
58#include <errno.h>
59#ifdef HAVE_LOCALE_H
60#include <locale.h>
61#endif
62
63#include <idn/result.h>
64#include <idn/converter.h>
65#include <idn/normalizer.h>
66#include <idn/utf8.h>
67#include <idn/resconf.h>
68#include <idn/res.h>
69#include <idn/util.h>
70#include <idn/version.h>
71
72#include "util.h"
73
74#define MAX_DELIMITER		10
75#define MAX_LOCALMAPPER		10
76#define MAX_MAPPER		10
77#define MAX_NORMALIZER		10
78#define MAX_CHEKER		10
79
80#define FLAG_REVERSE		0x0001
81#define FLAG_DELIMMAP		0x0002
82#define FLAG_LOCALMAP		0x0004
83#define FLAG_MAP		0x0008
84#define FLAG_NORMALIZE		0x0010
85#define FLAG_PROHIBITCHECK	0x0020
86#define FLAG_UNASSIGNCHECK	0x0040
87#define FLAG_BIDICHECK		0x0080
88#define FLAG_ASCIICHECK		0x0100
89#define FLAG_LENGTHCHECK	0x0200
90#define FLAG_ROUNDTRIPCHECK	0x0400
91#define FLAG_SELECTIVE		0x0800
92
93#define FLAG_NAMEPREP \
94	(FLAG_MAP|FLAG_NORMALIZE|FLAG_PROHIBITCHECK|FLAG_UNASSIGNCHECK|\
95	 FLAG_BIDICHECK)
96
97#define DEFAULT_FLAGS \
98	(FLAG_LOCALMAP|FLAG_NAMEPREP|FLAG_ASCIICHECK|FLAG_LENGTHCHECK|\
99	FLAG_ROUNDTRIPCHECK|FLAG_SELECTIVE|FLAG_DELIMMAP)
100
101int		line_number;		/* current input file line number */
102static int	flush_every_line = 0;	/* pretty obvious */
103
104static int		encode_file(idn_resconf_t conf1, idn_resconf_t conf2,
105				    FILE *fp, int flags);
106static int		decode_file(idn_resconf_t conf1, idn_resconf_t conf2,
107				    FILE *fp, int flags);
108static int		trim_newline(idnconv_strbuf_t *buf);
109static idn_result_t	convert_line(idnconv_strbuf_t *from,
110				     idnconv_strbuf_t *to,
111				     idn_resconf_t conf,
112				     idn_action_t actions, int flags);
113static void		print_usage(char *cmd);
114static void		print_version(void);
115static unsigned long	get_ucs(const char *p);
116
117int
118main(int ac, char **av) {
119	char *cmd = *av;
120	char *cname;
121	unsigned long delimiters[MAX_DELIMITER];
122	char *localmappers[MAX_LOCALMAPPER];
123	char *nameprep_version = NULL;
124	int ndelimiters = 0;
125	int nlocalmappers = 0;
126	char *in_code = NULL;
127	char *out_code = NULL;
128	char *resconf_file = NULL;
129	int no_resconf = 0;
130	char *encoding_alias = NULL;
131	int flags = DEFAULT_FLAGS;
132	FILE *fp;
133	idn_result_t r;
134	idn_resconf_t resconf1, resconf2;
135	idn_converter_t conv;
136	int exit_value;
137
138#ifdef HAVE_SETLOCALE
139	(void)setlocale(LC_ALL, "");
140#endif
141
142	/*
143	 * If the command name begins with 'r', reverse mode is assumed.
144	 */
145	if ((cname = strrchr(cmd, '/')) != NULL)
146		cname++;
147	else
148		cname = cmd;
149	if (cname[0] == 'r')
150		flags |= FLAG_REVERSE;
151
152	ac--;
153	av++;
154	while (ac > 0 && **av == '-') {
155
156#define OPT_MATCH(opt) (strcmp(*av, opt) == 0)
157#define MUST_HAVE_ARG if (ac < 2) print_usage(cmd)
158#define APPEND_LIST(array, size, item, what) \
159	if (size >= (sizeof(array) / sizeof(array[0]))) { \
160		errormsg("too many " what "\n"); \
161		exit(1); \
162	} \
163	array[size++] = item; \
164	ac--; av++
165
166		if (OPT_MATCH("-in") || OPT_MATCH("-i")) {
167			MUST_HAVE_ARG;
168			in_code = av[1];
169			ac--;
170			av++;
171		} else if (OPT_MATCH("-out") || OPT_MATCH("-o")) {
172			MUST_HAVE_ARG;
173			out_code = av[1];
174			ac--;
175			av++;
176		} else if (OPT_MATCH("-conf") || OPT_MATCH("-c")) {
177			MUST_HAVE_ARG;
178			resconf_file = av[1];
179			ac--;
180			av++;
181		} else if (OPT_MATCH("-nameprep") || OPT_MATCH("-n")) {
182			MUST_HAVE_ARG;
183			nameprep_version = av[1];
184			ac--;
185			av++;
186		} else if (OPT_MATCH("-noconf") || OPT_MATCH("-C")) {
187			no_resconf = 1;
188		} else if (OPT_MATCH("-reverse") || OPT_MATCH("-r")) {
189			flags |= FLAG_REVERSE;
190		} else if (OPT_MATCH("-nolocalmap") || OPT_MATCH("-L")) {
191			flags &= ~FLAG_LOCALMAP;
192		} else if (OPT_MATCH("-nonameprep") || OPT_MATCH("-N")) {
193			flags &= ~FLAG_NAMEPREP;
194		} else if (OPT_MATCH("-unassigncheck") || OPT_MATCH("-u")) {
195			flags |= FLAG_UNASSIGNCHECK;
196		} else if (OPT_MATCH("-nounassigncheck") || OPT_MATCH("-U")) {
197			flags &= ~FLAG_UNASSIGNCHECK;
198		} else if (OPT_MATCH("-nobidicheck") || OPT_MATCH("-B")) {
199			flags &= ~FLAG_BIDICHECK;
200		} else if (OPT_MATCH("-noasciicheck") || OPT_MATCH("-A")) {
201			flags &= ~FLAG_ASCIICHECK;
202		} else if (OPT_MATCH("-nolengthcheck")) {
203			flags &= ~FLAG_LENGTHCHECK;
204		} else if (OPT_MATCH("-noroundtripcheck")) {
205			flags &= ~FLAG_ROUNDTRIPCHECK;
206		} else if (OPT_MATCH("-whole") || OPT_MATCH("-w")) {
207			flags &= ~FLAG_SELECTIVE;
208		} else if (OPT_MATCH("-localmap")) {
209			MUST_HAVE_ARG;
210			APPEND_LIST(localmappers, nlocalmappers, av[1],
211				    "local maps");
212		} else if (OPT_MATCH("-delimiter")) {
213			unsigned long v;
214			MUST_HAVE_ARG;
215			v = get_ucs(av[1]);
216			APPEND_LIST(delimiters, ndelimiters, v,
217				    "delimiter maps");
218		} else if (OPT_MATCH("-alias") || OPT_MATCH("-a")) {
219			MUST_HAVE_ARG;
220			encoding_alias = av[1];
221			ac--;
222			av++;
223		} else if (OPT_MATCH("-flush")) {
224			flush_every_line = 1;
225		} else if (OPT_MATCH("-version") || OPT_MATCH("-v")) {
226			print_version();
227		} else {
228			print_usage(cmd);
229		}
230#undef OPT_MATCH
231#undef MUST_HAVE_ARG
232#undef APPEND_LIST
233
234		ac--;
235		av++;
236	}
237
238	if (ac > 1)
239		print_usage(cmd);
240
241	/* Initialize. */
242	if ((r = idn_resconf_initialize()) != idn_success) {
243		errormsg("error initializing library\n");
244		return (1);
245	}
246
247	/*
248	 * Create resource contexts.
249	 * `resconf1' and `resconf2' are almost the same but local and
250	 * IDN encodings are reversed.
251	 */
252	resconf1 = NULL;
253	resconf2 = NULL;
254	if (idn_resconf_create(&resconf1) != idn_success ||
255	    idn_resconf_create(&resconf2) != idn_success) {
256		errormsg("error initializing configuration contexts\n");
257		return (1);
258	}
259
260	/* Load configuration file. */
261	if (no_resconf) {
262		set_defaults(resconf1);
263		set_defaults(resconf2);
264	} else {
265		load_conf_file(resconf1, resconf_file);
266		load_conf_file(resconf2, resconf_file);
267	}
268
269	/* Set encoding alias file. */
270	if (encoding_alias != NULL)
271		set_encoding_alias(encoding_alias);
272
273	/* Set input codeset. */
274	if (flags & FLAG_REVERSE) {
275		if (in_code == NULL) {
276			conv = idn_resconf_getidnconverter(resconf1);
277			if (conv == NULL) {
278				errormsg("cannot get the IDN encoding.\n"
279					 "please specify an appropriate one "
280			 		 "with `-in' option.\n");
281				exit(1);
282			}
283			idn_resconf_setlocalconverter(resconf2, conv);
284			idn_converter_destroy(conv);
285		} else {
286			set_idncode(resconf1, in_code);
287			set_localcode(resconf2, in_code);
288		}
289	} else {
290		if (in_code == NULL) {
291			conv = idn_resconf_getlocalconverter(resconf1);
292			if (conv == NULL) {
293				errormsg("cannot get the local encoding.\n"
294					 "please specify an appropriate one "
295			 		 "with `-in' option.\n");
296				exit(1);
297			}
298			idn_resconf_setidnconverter(resconf2, conv);
299			idn_converter_destroy(conv);
300		} else {
301			set_localcode(resconf1, in_code);
302			set_idncode(resconf2, in_code);
303		}
304	}
305
306	/* Set output codeset. */
307	if (flags & FLAG_REVERSE) {
308		if (out_code == NULL) {
309			conv = idn_resconf_getlocalconverter(resconf1);
310			if (conv == NULL) {
311				errormsg("cannot get the local encoding.\n"
312					 "please specify an appropriate one "
313			 		 "with `-out' option.\n");
314				exit(1);
315			}
316			idn_resconf_setidnconverter(resconf2, conv);
317			idn_converter_destroy(conv);
318		} else {
319			set_localcode(resconf1, out_code);
320			set_idncode(resconf2, out_code);
321		}
322	} else {
323		if (out_code == NULL) {
324			conv = idn_resconf_getidnconverter(resconf1);
325			if (conv == NULL) {
326				errormsg("cannot get the IDN encoding.\n"
327					 "please specify an appropriate one "
328			 		 "with `-out' option.\n");
329				exit(1);
330			}
331			idn_resconf_setlocalconverter(resconf2, conv);
332			idn_converter_destroy(conv);
333		} else {
334			set_idncode(resconf1, out_code);
335			set_localcode(resconf2, out_code);
336		}
337	}
338
339	/* Set delimiter map(s). */
340	if (ndelimiters > 0) {
341		set_delimitermapper(resconf1, delimiters, ndelimiters);
342		set_delimitermapper(resconf2, delimiters, ndelimiters);
343	}
344
345	/* Set local map(s). */
346	if (nlocalmappers > 0) {
347		set_localmapper(resconf1, localmappers, nlocalmappers);
348		set_localmapper(resconf2, localmappers, nlocalmappers);
349	}
350
351	/* Set NAMEPREP version. */
352	if (nameprep_version != NULL) {
353		set_nameprep(resconf1, nameprep_version);
354		set_nameprep(resconf2, nameprep_version);
355	}
356
357	idn_res_enable(1);
358
359	/* Open input file. */
360	if (ac > 0) {
361		if ((fp = fopen(av[0], "r")) == NULL) {
362			errormsg("cannot open file %s: %s\n",
363				 av[0], strerror(errno));
364			return (1);
365		}
366	} else {
367		fp = stdin;
368	}
369
370	/* Do the conversion. */
371	if (flags & FLAG_REVERSE)
372		exit_value = decode_file(resconf1, resconf2, fp, flags);
373	else
374		exit_value = encode_file(resconf1, resconf2, fp, flags);
375
376	idn_resconf_destroy(resconf1);
377	idn_resconf_destroy(resconf2);
378
379	return exit_value;
380}
381
382static int
383encode_file(idn_resconf_t conf1, idn_resconf_t conf2, FILE *fp, int flags) {
384	idn_result_t r;
385	idnconv_strbuf_t buf1, buf2;
386	idn_action_t actions1, actions2;
387	int nl_trimmed;
388	int local_ace_hack;
389	idn_converter_t conv;
390
391	/*
392	 * See if the input codeset is an ACE.
393	 */
394	conv = idn_resconf_getlocalconverter(conf1);
395	if (conv != NULL && idn_converter_isasciicompatible(conv) &&
396	    (flags & FLAG_SELECTIVE))
397		local_ace_hack = 1;
398	else
399		local_ace_hack = 0;
400	if (conv != NULL)
401		idn_converter_destroy(conv);
402
403	if (local_ace_hack) {
404		actions1 = IDN_IDNCONV;
405		if (flags & FLAG_ROUNDTRIPCHECK)
406			actions1 |= IDN_RTCHECK;
407	} else {
408		actions1 = IDN_LOCALCONV;
409	}
410
411	actions2 = IDN_IDNCONV;
412	if (flags & FLAG_DELIMMAP)
413		actions2 |= IDN_DELIMMAP;
414	if (flags & FLAG_LOCALMAP)
415		actions2 |= IDN_LOCALMAP;
416	if (flags & FLAG_MAP)
417		actions2 |= IDN_MAP;
418	if (flags & FLAG_NORMALIZE)
419		actions2 |= IDN_NORMALIZE;
420	if (flags & FLAG_PROHIBITCHECK)
421		actions2 |= IDN_PROHCHECK;
422	if (flags & FLAG_UNASSIGNCHECK)
423		actions2 |= IDN_UNASCHECK;
424	if (flags & FLAG_BIDICHECK)
425		actions2 |= IDN_BIDICHECK;
426	if (flags & FLAG_ASCIICHECK)
427		actions2 |= IDN_ASCCHECK;
428	if (flags & FLAG_LENGTHCHECK)
429		actions2 |= IDN_LENCHECK;
430
431	strbuf_init(&buf1);
432	strbuf_init(&buf2);
433	line_number = 1;
434	while (strbuf_getline(&buf1, fp) != NULL) {
435		/*
436		 * Trim newline at the end.  This is needed for
437		 * those ascii-comatible encodings such as UTF-5 or RACE
438		 * not to try converting newlines, which will result
439		 * in `invalid encoding' error.
440		 */
441		nl_trimmed = trim_newline(&buf1);
442
443		/*
444		 * Convert input line to UTF-8.
445		 */
446		if (local_ace_hack)
447			r = convert_line(&buf1, &buf2, conf2, actions1,
448					 FLAG_REVERSE|FLAG_SELECTIVE);
449		else
450			r = convert_line(&buf1, &buf2, conf1, actions1,
451					 0);
452
453		if (r != idn_success) {
454			errormsg("conversion failed at line %d: %s\n",
455				 line_number,
456				 idn_result_tostring(r));
457			goto error;
458		}
459		if (!idn_utf8_isvalidstring(strbuf_get(&buf2))) {
460			errormsg("conversion to utf-8 failed at line %d\n",
461				 line_number);
462			goto error;
463		}
464
465		/*
466		 * Perform local mapping and NAMEPREP, and convert to
467		 * the output codeset.
468		 */
469		r = convert_line(&buf2, &buf1, conf1, actions2,
470				 flags & FLAG_SELECTIVE);
471
472		if (r != idn_success) {
473			errormsg("error in nameprep or output conversion "
474				 "at line %d: %s\n",
475				 line_number, idn_result_tostring(r));
476			goto error;
477		}
478
479		fputs(strbuf_get(&buf1), stdout);
480		if (nl_trimmed)
481			putc('\n', stdout);
482
483		if (flush_every_line)
484			fflush(stdout);
485
486		line_number++;
487	}
488
489	strbuf_reset(&buf1);
490	strbuf_reset(&buf2);
491	return (0);
492
493 error:
494	strbuf_reset(&buf1);
495	strbuf_reset(&buf2);
496	return (1);
497}
498
499static int
500decode_file(idn_resconf_t conf1, idn_resconf_t conf2, FILE *fp, int flags) {
501	idn_result_t r;
502	idnconv_strbuf_t buf1, buf2;
503	idn_action_t actions1, actions2;
504	int nl_trimmed;
505	int local_ace_hack, idn_ace_hack;
506	idn_converter_t conv;
507
508	/*
509	 * See if the input codeset is an ACE.
510	 */
511	conv = idn_resconf_getidnconverter(conf1);
512	if (conv != NULL && idn_converter_isasciicompatible(conv) &&
513	    (flags & FLAG_SELECTIVE))
514		idn_ace_hack = 1;
515	else
516		idn_ace_hack = 0;
517	if (conv != NULL)
518		idn_converter_destroy(conv);
519
520	conv = idn_resconf_getlocalconverter(conf1);
521	if (conv != NULL && idn_converter_isasciicompatible(conv) &&
522	    (flags & FLAG_SELECTIVE))
523		local_ace_hack = 1;
524	else
525		local_ace_hack = 0;
526	if (conv != NULL)
527		idn_converter_destroy(conv);
528
529	actions1 = IDN_IDNCONV;
530
531	if (local_ace_hack) {
532		actions2 = IDN_IDNCONV;
533		if (flags & FLAG_MAP)
534			actions2 |= IDN_MAP;
535		if (flags & FLAG_NORMALIZE)
536			actions2 |= IDN_NORMALIZE;
537		if (flags & FLAG_PROHIBITCHECK)
538			actions2 |= IDN_PROHCHECK;
539		if (flags & FLAG_UNASSIGNCHECK)
540			actions2 |= IDN_UNASCHECK;
541		if (flags & FLAG_BIDICHECK)
542			actions2 |= IDN_BIDICHECK;
543		if (flags & FLAG_ASCIICHECK)
544			actions2 |= IDN_ASCCHECK;
545		if (flags & FLAG_LENGTHCHECK)
546			actions2 |= IDN_LENCHECK;
547	} else {
548		actions2 = IDN_LOCALCONV;
549	}
550
551	if (flags & FLAG_DELIMMAP)
552		actions1 |= IDN_DELIMMAP;
553	if (flags & FLAG_MAP)
554		actions1 |= IDN_MAP;
555	if (flags & FLAG_NORMALIZE)
556		actions1 |= IDN_NORMALIZE;
557	if (flags & FLAG_NORMALIZE)
558		actions1 |= IDN_NORMALIZE;
559	if (flags & FLAG_PROHIBITCHECK)
560		actions1 |= IDN_PROHCHECK;
561	if (flags & FLAG_UNASSIGNCHECK)
562		actions1 |= IDN_UNASCHECK;
563	if (flags & FLAG_BIDICHECK)
564		actions1 |= IDN_BIDICHECK;
565	if (flags & FLAG_ASCIICHECK)
566		actions1 |= IDN_ASCCHECK;
567	if (flags & FLAG_ROUNDTRIPCHECK)
568		actions1 |= IDN_RTCHECK;
569
570	strbuf_init(&buf1);
571	strbuf_init(&buf2);
572	line_number = 1;
573	while (strbuf_getline(&buf1, fp) != NULL) {
574		/*
575		 * Trim newline at the end.  This is needed for
576		 * those ascii-comatible encodings such as UTF-5 or RACE
577		 * not to try converting newlines, which will result
578		 * in `invalid encoding' error.
579		 */
580		nl_trimmed = trim_newline(&buf1);
581
582		/*
583		 * Treat input line as the string encoded in local
584		 * encoding and convert it to UTF-8 encoded string.
585		 */
586		if (local_ace_hack) {
587			if (strbuf_copy(&buf2, strbuf_get(&buf1)) == NULL)
588				r = idn_nomemory;
589			else
590				r = idn_success;
591		} else {
592			r = convert_line(&buf1, &buf2, conf1, IDN_LOCALCONV,
593					 0);
594		}
595		if (r != idn_success) {
596			errormsg("conversion failed at line %d: %s\n",
597				 line_number, idn_result_tostring(r));
598			goto error;
599		}
600
601		/*
602		 * Convert internationalized domain names in the line.
603		 */
604		if (idn_ace_hack) {
605			r = convert_line(&buf2, &buf1, conf1, actions1,
606					 FLAG_REVERSE|FLAG_SELECTIVE);
607		} else {
608			r = convert_line(&buf2, &buf1, conf1, actions1,
609					 FLAG_REVERSE);
610		}
611		if (r != idn_success) {
612			errormsg("conversion failed at line %d: %s\n",
613				 line_number,
614				 idn_result_tostring(r));
615			goto error;
616		}
617		if (!idn_utf8_isvalidstring(strbuf_get(&buf1))) {
618			errormsg("conversion to utf-8 failed at line %d\n",
619				 line_number);
620			goto error;
621		}
622
623		/*
624		 * Perform round trip check and convert to the output
625		 * codeset.
626		 */
627		if (local_ace_hack) {
628			r = convert_line(&buf1, &buf2, conf2, actions2,
629					 FLAG_SELECTIVE);
630		} else {
631			r = convert_line(&buf1, &buf2, conf1, actions2,
632					 FLAG_REVERSE);
633		}
634
635		if (r != idn_success) {
636			errormsg("error in nameprep or output conversion "
637				 "at line %d: %s\n",
638				 line_number, idn_result_tostring(r));
639			goto error;
640		}
641
642		fputs(strbuf_get(&buf2), stdout);
643		if (nl_trimmed)
644			putc('\n', stdout);
645
646		if (flush_every_line)
647			fflush(stdout);
648
649		line_number++;
650	}
651	strbuf_reset(&buf1);
652	strbuf_reset(&buf2);
653	return (0);
654
655 error:
656	strbuf_reset(&buf1);
657	strbuf_reset(&buf2);
658	return (1);
659}
660
661static int
662trim_newline(idnconv_strbuf_t *buf) {
663	/*
664	 * If the string in BUF ends with a newline, trim it and
665	 * return 1.  Otherwise, just return 0 without modifying BUF.
666	 */
667	char *s = strbuf_get(buf);
668	size_t len = strlen(s);
669
670	if (s[len - 1] == '\n') {
671		s[len - 1] = '\0';
672		return (1);
673	}
674
675	return (0);
676}
677
678static idn_result_t
679convert_line(idnconv_strbuf_t *from, idnconv_strbuf_t *to,
680	     idn_resconf_t conf, idn_action_t actions, int flags)
681{
682	idn_result_t r = idn_success;
683	char *from_str = strbuf_get(from);
684
685	for (;;) {
686		char *to_str = strbuf_get(to);
687		size_t to_size = strbuf_size(to);
688
689		switch (flags & (FLAG_REVERSE|FLAG_SELECTIVE)) {
690		case 0:
691			r = idn_res_encodename(conf, actions, from_str,
692					       to_str, to_size);
693			break;
694		case FLAG_REVERSE:
695			r = idn_res_decodename(conf, actions, from_str,
696					       to_str, to_size);
697			break;
698		case FLAG_SELECTIVE:
699			r = selective_encode(conf, actions, from_str,
700					     to_str, to_size);
701			break;
702		case FLAG_REVERSE|FLAG_SELECTIVE:
703			r = selective_decode(conf, actions, from_str,
704					     to_str, to_size);
705			break;
706		}
707		if (r == idn_buffer_overflow) {
708			/*
709			 * Conversion is not successful because
710			 * the size of the target buffer is not enough.
711			 * Double the size and retry.
712			 */
713			if (strbuf_double(to) == NULL) {
714				/* oops. allocation failed. */
715				return (idn_nomemory);
716			}
717		} else {
718			break;
719		}
720	}
721	return (r);
722}
723
724static char *options[] = {
725	"-in INPUT-CODESET   : specifies input codeset name.",
726	"-i INPUT-CODESET    : synonym for -in",
727	"-out OUTPUT-CODESET : specifies output codeset name.",
728	"-o OUTPUT-CODESET   : synonym for -out",
729	"-conf CONF-FILE     : specifies idnkit configuration file.",
730	"-c CONF-FILE        : synonym for -conf",
731	"-noconf             : do not load idnkit configuration file.",
732	"-C                  : synonym for -noconf",
733	"-reverse            : specifies reverse conversion.",
734	"                      (i.e. IDN encoding to local encoding)",
735	"-r                  : synonym for -reverse",
736	"-nameprep VERSION   : specifies version name of NAMEPREP.",
737	"-n VERSION          : synonym for -nameprep",
738	"-nonameprep         : do not perform NAMEPREP.",
739	"-N                  : synonym for -nonameprep",
740	"-localmap MAPPING   : specifies local mapping.",
741	"-nolocalmap         : do not perform local mapping.",
742	"-L                  : synonym for -nolocalmap",
743	"-nounassigncheck    : do not perform unassigned codepoint check.",
744	"-U                  : synonym for -nounassigncheck",
745	"-nobidicheck        : do not perform bidirectional text check.",
746	"-B                  : synonym for -nobidicheck",
747	"-nolengthcheck      : do not check label length.",
748	"-noasciicheck       : do not check ASCII range characters.",
749	"-A                  : synonym for -noasciicheck",
750	"-noroundtripcheck   : do not perform round trip check.",
751	"-delimiter U+XXXX   : specifies local delimiter code point.",
752	"-alias alias-file   : specifies codeset alias file.",
753	"-a                  : synonym for -alias",
754	"-flush              : line-buffering mode.",
755	"-whole              : convert the whole region instead of",
756	"                      regions containing non-ascii characters.",
757	"-w                  : synonym for -whole",
758	"-version            : print version number, then exit.",
759	"-v                  : synonym for -version",
760	"",
761	" The following options can be specified multiple times",
762	"   -localmap, -delimiter",
763	NULL,
764};
765
766static void
767print_version() {
768	fprintf(stderr, "idnconv (idnkit) version: %s\n"
769		"library version: %s\n",
770		IDNKIT_VERSION,
771		idn_version_getstring());
772	exit(0);
773}
774
775static void
776print_usage(char *cmd) {
777	int i;
778
779	fprintf(stderr, "Usage: %s [options..] [file]\n", cmd);
780
781	for (i = 0; options[i] != NULL; i++)
782		fprintf(stderr, "\t%s\n", options[i]);
783
784	exit(1);
785}
786
787static unsigned long
788get_ucs(const char *p) {
789	unsigned long v;
790	char *end;
791
792	/* Skip optional 'U+' */
793	if (strncmp(p, "U+", 2) == 0)
794		p += 2;
795
796	v = strtoul(p, &end, 16);
797	if (*end != '\0') {
798		fprintf(stderr, "invalid UCS code point \"%s\"\n", p);
799		exit(1);
800	}
801
802	return v;
803}
804