]> arthur.barton.de Git - netatalk.git/blob - libatalk/unicode/charcnv.c
785e47720f1d7ab2e74ce2be611c93164386fdb9
[netatalk.git] / libatalk / unicode / charcnv.c
1 /*
2   Unix SMB/CIFS implementation.
3   Character set conversion Extensions
4   Copyright (C) Igor Vergeichik <iverg@mail.ru> 2001
5   Copyright (C) Andrew Tridgell 2001
6   Copyright (C) Simo Sorce 2001
7   Copyright (C) Martin Pool 2003
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19   You should have received a copy of the GNU General Public License
20   along with this program; if not, write to the Free Software
21   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22
23 */
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif /* HAVE_CONFIG_H */
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <unistd.h>
31 #include <string.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <sys/stat.h>
35 #include <sys/param.h>
36 #ifdef HAVE_USABLE_ICONV
37 #include <iconv.h>
38 #endif
39 #include <arpa/inet.h>
40
41 #include <atalk/logger.h>
42 #include <atalk/unicode.h>
43 #include <atalk/util.h>
44 #include <atalk/compat.h>
45
46 #include "byteorder.h"
47
48
49 /**
50  * @file
51  *
52  * @brief Character-set conversion routines built on our iconv.
53  *
54  * @note Samba's internal character set (at least in the 3.0 series)
55  * is always the same as the one for the Unix filesystem.  It is
56  * <b>not</b> necessarily UTF-8 and may be different on machines that
57  * need i18n filenames to be compatible with Unix software.  It does
58  * have to be a superset of ASCII.  All multibyte sequences must start
59  * with a byte with the high bit set.
60  *
61  * @sa lib/iconv.c
62  */
63
64
65 #define MAX_CHARSETS 20
66
67 #define CHECK_FLAGS(a,b) (((a)!=NULL) ? (*(a) & (b)) : 0 )
68
69 static atalk_iconv_t conv_handles[MAX_CHARSETS][MAX_CHARSETS];
70 static char* charset_names[MAX_CHARSETS];
71 static struct charset_functions* charsets[MAX_CHARSETS];
72 static char hexdig[] = "0123456789abcdef";
73 #define hextoint( c )   ( isdigit( c ) ? c - '0' : c + 10 - 'a' )
74
75
76 /**
77  * Return the name of a charset to give to iconv().
78  **/
79 static const char *charset_name(charset_t ch)
80 {
81     const char *ret = NULL;
82
83     if (ch == CH_UCS2) ret = "UCS-2";
84     else if (ch == CH_UTF8) ret = "UTF8";
85     else if (ch == CH_UTF8_MAC) ret = "UTF8-MAC";
86     else ret = charset_names[ch];
87     return ret;
88 }
89
90 int set_charset_name(charset_t ch, const char *name)
91 {
92     if (ch >= NUM_CHARSETS)
93         return -1;
94     charset_names[ch] = strdup(name);
95     return 0;
96 }
97
98 static struct charset_functions* get_charset_functions (charset_t ch)
99 {
100     if (charsets[ch] != NULL)
101         return charsets[ch];
102
103     charsets[ch] = find_charset_functions(charset_name(ch));
104
105     return charsets[ch];
106 }
107
108
109 static void lazy_initialize_conv(void)
110 {
111     static int initialized = 0;
112
113     if (!initialized) {
114         initialized = 1;
115         init_iconv();
116     }
117 }
118
119 charset_t add_charset(const char* name)
120 {
121     static charset_t max_charset_t = NUM_CHARSETS-1;
122     charset_t cur_charset_t = max_charset_t+1;
123     unsigned int c1;
124
125     lazy_initialize_conv();
126
127     for (c1=0; c1<=max_charset_t;c1++) {
128         if ( strcasecmp(name, charset_name(c1)) == 0)
129             return (c1);
130     }
131
132     if ( cur_charset_t >= MAX_CHARSETS )  {
133         LOG (log_debug, logtype_default, "Adding charset %s failed, too many charsets (max. %u allowed)",
134              name, MAX_CHARSETS);
135         return (charset_t) -1;
136     }
137
138     /* First try to setup the required conversions */
139
140     conv_handles[cur_charset_t][CH_UCS2] = atalk_iconv_open( charset_name(CH_UCS2), name);
141     if (conv_handles[cur_charset_t][CH_UCS2] == (atalk_iconv_t)-1) {
142         LOG(log_error, logtype_default, "Required conversion from %s to %s not supported",
143             name,  charset_name(CH_UCS2));
144         conv_handles[cur_charset_t][CH_UCS2] = NULL;
145         return (charset_t) -1;
146     }
147
148     conv_handles[CH_UCS2][cur_charset_t] = atalk_iconv_open( name, charset_name(CH_UCS2));
149     if (conv_handles[CH_UCS2][cur_charset_t] == (atalk_iconv_t)-1) {
150         LOG(log_error, logtype_default, "Required conversion from %s to %s not supported",
151             charset_name(CH_UCS2), name);
152         conv_handles[CH_UCS2][cur_charset_t] = NULL;
153         return (charset_t) -1;
154     }
155
156     /* register the new charset_t name */
157     charset_names[cur_charset_t] = strdup(name);
158
159     charsets[cur_charset_t] = get_charset_functions (cur_charset_t);
160     max_charset_t++;
161
162 #ifdef DEBUG
163     LOG(log_debug9, logtype_default, "Added charset %s with handle %u", name, cur_charset_t);
164 #endif
165     return (cur_charset_t);
166 }
167
168 /**
169  * Initialize iconv conversion descriptors.
170  *
171  * This is called the first time it is needed, and also called again
172  * every time the configuration is reloaded, because the charset or
173  * codepage might have changed.
174  **/
175 void init_iconv(void)
176 {
177     int c1;
178
179     for (c1=0;c1<NUM_CHARSETS;c1++) {
180         const char *name = charset_name((charset_t)c1);
181
182         conv_handles[c1][CH_UCS2] = atalk_iconv_open( charset_name(CH_UCS2), name);
183         if (conv_handles[c1][CH_UCS2] == (atalk_iconv_t)-1) {
184             LOG(log_error, logtype_default, "Required conversion from %s to %s not supported",
185                 name,  charset_name(CH_UCS2));
186             conv_handles[c1][CH_UCS2] = NULL;
187         }
188
189         if (c1 != CH_UCS2) { /* avoid lost memory, make valgrind happy */
190             conv_handles[CH_UCS2][c1] = atalk_iconv_open( name, charset_name(CH_UCS2));
191             if (conv_handles[CH_UCS2][c1] == (atalk_iconv_t)-1) {
192                 LOG(log_error, logtype_default, "Required conversion from %s to %s not supported",
193                     charset_name(CH_UCS2), name);
194                 conv_handles[CH_UCS2][c1] = NULL;
195             }
196         }
197
198         charsets[c1] = get_charset_functions (c1);
199     }
200 }
201
202 /**
203  *
204  **/
205 static size_t add_null(charset_t to, char *buf, size_t bytesleft, size_t len)
206 {
207     /* Terminate the string */
208     if (to == CH_UCS2 && bytesleft >= 2) {
209         buf[len]   = 0;
210         buf[len+1] = 0;
211
212     }
213     else if ( to != CH_UCS2 && bytesleft > 0 )
214         buf[len]   = 0;
215     else {
216         errno = E2BIG;
217         return (size_t)(-1);
218     }
219
220     return len;
221 }
222
223
224 /**
225  * Convert string from one encoding to another, making error checking etc
226  *
227  * @param src pointer to source string (multibyte or singlebyte)
228  * @param srclen length of the source string in bytes
229  * @param dest pointer to destination string (multibyte or singlebyte)
230  * @param destlen maximal length allowed for string
231  * @returns the number of bytes occupied in the destination
232  **/
233 static size_t convert_string_internal(charset_t from, charset_t to,
234                                       void const *src, size_t srclen,
235                                       void *dest, size_t destlen)
236 {
237     size_t i_len, o_len;
238     size_t retval;
239     const char* inbuf = (const char*)src;
240     char* outbuf = (char*)dest;
241     char* o_save = outbuf;
242     atalk_iconv_t descriptor;
243
244     /* Fixed based on Samba 3.0.6 */
245     if (srclen == (size_t)-1) {
246         if (from == CH_UCS2) {
247             srclen = (strlen_w((const ucs2_t *)src)) * 2;
248         } else {
249             srclen = strlen((const char *)src);
250         }
251     }
252
253
254     lazy_initialize_conv();
255
256     descriptor = conv_handles[from][to];
257
258     if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
259         return (size_t) -1;
260     }
261
262     i_len=srclen;
263     o_len=destlen;
264     retval = atalk_iconv(descriptor,  &inbuf, &i_len, &outbuf, &o_len);
265     if(retval==(size_t)-1) {
266         const char *reason="unknown error";
267         switch(errno) {
268         case EINVAL:
269             reason="Incomplete multibyte sequence";
270             break;
271         case E2BIG:
272             reason="No more room";
273             break;
274         case EILSEQ:
275             reason="Illegal multibyte sequence";
276             break;
277         }
278         LOG(log_debug, logtype_default,"Conversion error: %s",reason);
279         return (size_t)-1;
280     }
281
282     /* Terminate the string */
283     return add_null( to, o_save, o_len, destlen -o_len);
284 }
285
286
287 size_t convert_string(charset_t from, charset_t to,
288                       void const *src, size_t srclen,
289                       void *dest, size_t destlen)
290 {
291     size_t i_len, o_len;
292     ucs2_t *u;
293     ucs2_t buffer[MAXPATHLEN];
294     ucs2_t buffer2[MAXPATHLEN];
295
296     /* convert from_set to UCS2 */
297     if ((size_t)-1 == ( o_len = convert_string_internal( from, CH_UCS2, src, srclen,
298                                                            (char*) buffer, sizeof(buffer))) ) {
299         LOG(log_error, logtype_default, "Conversion failed ( %s to CH_UCS2 )", charset_name(from));
300         return (size_t) -1;
301     }
302
303     /* Do pre/decomposition */
304     i_len = sizeof(buffer2);
305     u = buffer2;
306     if (charsets[to] && (charsets[to]->flags & CHARSET_DECOMPOSED) ) {
307         if ( (size_t)-1 == (i_len = decompose_w(buffer, o_len, u, &i_len)) )
308             return (size_t)-1;
309     }
310     else if (!charsets[from] || (charsets[from]->flags & CHARSET_DECOMPOSED)) {
311         if ( (size_t)-1 == (i_len = precompose_w(buffer, o_len, u, &i_len)) )
312             return (size_t)-1;
313     }
314     else {
315         u = buffer;
316         i_len = o_len;
317     }
318     /* Convert UCS2 to to_set */
319     if ((size_t)(-1) == ( o_len = convert_string_internal( CH_UCS2, to, (char*) u, i_len, dest, destlen)) ) {
320         LOG(log_error, logtype_default, "Conversion failed (CH_UCS2 to %s):%s", charset_name(to), strerror(errno));
321         return (size_t) -1;
322     }
323
324     return o_len;
325 }
326
327
328
329 /**
330  * Convert between character sets, allocating a new buffer for the result.
331  *
332  * @param srclen length of source buffer.
333  * @param dest always set at least to NULL
334  * @note -1 is not accepted for srclen.
335  *
336  * @returns Size in bytes of the converted string; or -1 in case of error.
337  **/
338
339 static size_t convert_string_allocate_internal(charset_t from, charset_t to,
340                                                void const *src, size_t srclen, char **dest)
341 {
342     size_t i_len, o_len, destlen;
343     size_t retval;
344     const char *inbuf = (const char *)src;
345     char *outbuf = NULL, *ob = NULL;
346     atalk_iconv_t descriptor;
347
348     *dest = NULL;
349
350     if (src == NULL || srclen == (size_t)-1)
351         return (size_t)-1;
352
353     lazy_initialize_conv();
354
355     descriptor = conv_handles[from][to];
356
357     if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
358         /* conversion not supported, return -1*/
359         LOG(log_debug, logtype_default, "convert_string_allocate: conversion not supported!");
360         return -1;
361     }
362
363     destlen = MAX(srclen, 512);
364 convert:
365     destlen = destlen * 2;
366     outbuf = (char *)realloc(ob, destlen);
367     if (!outbuf) {
368         LOG(log_debug, logtype_default,"convert_string_allocate: realloc failed!");
369         SAFE_FREE(ob);
370         return (size_t)-1;
371     } else {
372         ob = outbuf;
373     }
374     inbuf = src;   /* this restarts the whole conversion if buffer needed to be increased */
375     i_len = srclen;
376     o_len = destlen;
377     retval = atalk_iconv(descriptor,
378                          &inbuf, &i_len,
379                          &outbuf, &o_len);
380     if(retval == (size_t)-1)        {
381         const char *reason="unknown error";
382         switch(errno) {
383         case EINVAL:
384             reason="Incomplete multibyte sequence";
385             break;
386         case E2BIG:
387             goto convert;
388         case EILSEQ:
389             reason="Illegal multibyte sequence";
390             break;
391         }
392         LOG(log_debug, logtype_default,"Conversion error: %s(%s)",reason,inbuf);
393         SAFE_FREE(ob);
394         return (size_t)-1;
395     }
396
397
398     destlen = destlen - o_len;
399
400     /* Terminate the string */
401     if (to == CH_UCS2 && o_len >= 2) {
402         ob[destlen] = 0;
403         ob[destlen+1] = 0;
404         *dest = (char *)realloc(ob,destlen+2);
405     }
406     else if ( to != CH_UCS2 && o_len > 0 ) {
407         ob[destlen] = 0;
408         *dest = (char *)realloc(ob,destlen+1);
409     }
410     else {
411         goto convert; /* realloc */
412     }
413
414     if (destlen && !*dest) {
415         LOG(log_debug, logtype_default, "convert_string_allocate: out of memory!");
416         SAFE_FREE(ob);
417         return (size_t)-1;
418     }
419
420     return destlen;
421 }
422
423
424 size_t convert_string_allocate(charset_t from, charset_t to,
425                                void const *src, size_t srclen,
426                                char ** dest)
427 {
428     size_t i_len, o_len;
429     ucs2_t *u;
430     ucs2_t buffer[MAXPATHLEN];
431     ucs2_t buffer2[MAXPATHLEN];
432
433     *dest = NULL;
434
435     /* convert from_set to UCS2 */
436     if ((size_t)(-1) == ( o_len = convert_string_internal( from, CH_UCS2, src, srclen,
437                                                            buffer, sizeof(buffer))) ) {
438         LOG(log_error, logtype_default, "Conversion failed ( %s to CH_UCS2 )", charset_name(from));
439         return (size_t) -1;
440     }
441
442     /* Do pre/decomposition */
443     i_len = sizeof(buffer2);
444     u = buffer2;
445     if (charsets[to] && (charsets[to]->flags & CHARSET_DECOMPOSED) ) {
446         if ( (size_t)-1 == (i_len = decompose_w(buffer, o_len, u, &i_len)) )
447             return (size_t)-1;
448     }
449     else if ( !charsets[from] || (charsets[from]->flags & CHARSET_DECOMPOSED) ) {
450         if ( (size_t)-1 == (i_len = precompose_w(buffer, o_len, u, &i_len)) )
451             return (size_t)-1;
452     }
453     else {
454         u = buffer;
455         i_len = o_len;
456     }
457
458     /* Convert UCS2 to to_set */
459     if ((size_t)-1 == ( o_len = convert_string_allocate_internal( CH_UCS2, to, (char*)u, i_len, dest)) )
460         LOG(log_error, logtype_default, "Conversion failed (CH_UCS2 to %s):%s", charset_name(to), strerror(errno));
461
462     return o_len;
463
464 }
465
466 size_t charset_strupper(charset_t ch, const char *src, size_t srclen, char *dest, size_t destlen)
467 {
468     size_t size;
469     char *buffer;
470
471     size = convert_string_allocate_internal(ch, CH_UCS2, src, srclen,
472                                             (char**) &buffer);
473     if (size == (size_t)-1) {
474         SAFE_FREE(buffer);
475         return size;
476     }
477     if (!strupper_w((ucs2_t *)buffer) && (dest == src)) {
478         free(buffer);
479         return srclen;
480     }
481
482     size = convert_string_internal(CH_UCS2, ch, buffer, size, dest, destlen);
483     free(buffer);
484     return size;
485 }
486
487 size_t charset_strlower(charset_t ch, const char *src, size_t srclen, char *dest, size_t destlen)
488 {
489     size_t size;
490     char *buffer;
491
492     size = convert_string_allocate_internal(ch, CH_UCS2, src, srclen,
493                                             (char **) &buffer);
494     if (size == (size_t)-1) {
495         SAFE_FREE(buffer);
496         return size;
497     }
498     if (!strlower_w((ucs2_t *)buffer) && (dest == src)) {
499         free(buffer);
500         return srclen;
501     }
502
503     size = convert_string_internal(CH_UCS2, ch, buffer, size, dest, destlen);
504     free(buffer);
505     return size;
506 }
507
508
509 size_t unix_strupper(const char *src, size_t srclen, char *dest, size_t destlen)
510 {
511     return charset_strupper( CH_UNIX, src, srclen, dest, destlen);
512 }
513
514 size_t unix_strlower(const char *src, size_t srclen, char *dest, size_t destlen)
515 {
516     return charset_strlower( CH_UNIX, src, srclen, dest, destlen);
517 }
518
519 size_t utf8_strupper(const char *src, size_t srclen, char *dest, size_t destlen)
520 {
521     return charset_strupper( CH_UTF8, src, srclen, dest, destlen);
522 }
523
524 size_t utf8_strlower(const char *src, size_t srclen, char *dest, size_t destlen)
525 {
526     return charset_strlower( CH_UTF8, src, srclen, dest, destlen);
527 }
528
529 /**
530  * Copy a string from a charset_t char* src to a UCS2 destination, allocating a buffer
531  *
532  * @param dest always set at least to NULL
533  *
534  * @returns The number of bytes occupied by the string in the destination
535  *         or -1 in case of error.
536  **/
537
538 size_t charset_to_ucs2_allocate(charset_t ch, ucs2_t **dest, const char *src)
539 {
540     size_t src_len = strlen(src);
541
542     *dest = NULL;
543     return convert_string_allocate(ch, CH_UCS2, src, src_len, (char**) dest);
544 }
545
546 /** -----------------------------------
547  * Copy a string from a charset_t char* src to a UTF-8 destination, allocating a buffer
548  *
549  * @param dest always set at least to NULL
550  *
551  * @returns The number of bytes occupied by the string in the destination
552  **/
553
554 size_t charset_to_utf8_allocate(charset_t ch, char **dest, const char *src)
555 {
556     size_t src_len = strlen(src);
557
558     *dest = NULL;
559     return convert_string_allocate(ch, CH_UTF8, src, src_len, dest);
560 }
561
562 /** -----------------------------------
563  * Copy a string from a UCS2 src to a unix char * destination, allocating a buffer
564  *
565  * @param dest always set at least to NULL
566  *
567  * @returns The number of bytes occupied by the string in the destination
568  **/
569
570 size_t ucs2_to_charset(charset_t ch, const ucs2_t *src, char *dest, size_t destlen)
571 {
572     size_t src_len = (strlen_w(src)) * sizeof(ucs2_t);
573     return convert_string(CH_UCS2, ch, src, src_len, dest, destlen);
574 }
575
576 /* --------------------------------- */
577 size_t ucs2_to_charset_allocate(charset_t ch, char **dest, const ucs2_t *src)
578 {
579     size_t src_len = (strlen_w(src)) * sizeof(ucs2_t);
580     *dest = NULL;
581     return convert_string_allocate(CH_UCS2, ch, src, src_len, dest);
582 }
583
584 /** ---------------------------------
585  * Copy a string from a UTF-8 src to a unix char * destination, allocating a buffer
586  *
587  * @param dest always set at least to NULL
588  *
589  * @returns The number of bytes occupied by the string in the destination
590  **/
591
592 size_t utf8_to_charset_allocate(charset_t ch, char **dest, const char *src)
593 {
594     size_t src_len = strlen(src);
595     *dest = NULL;
596     return convert_string_allocate(CH_UTF8, ch, src, src_len, dest);
597 }
598
599 size_t charset_precompose ( charset_t ch, char * src, size_t inlen, char * dst, size_t outlen)
600 {
601     char *buffer;
602     ucs2_t u[MAXPATHLEN];
603     size_t len;
604     size_t ilen;
605
606     if ((size_t)(-1) == (len = convert_string_allocate_internal(ch, CH_UCS2, src, inlen, &buffer)) )
607         return len;
608
609     ilen=sizeof(u);
610
611     if ( (size_t)-1 == (ilen = precompose_w((ucs2_t *)buffer, len, u, &ilen)) ) {
612         free (buffer);
613         return (size_t)(-1);
614     }
615
616     if ((size_t)(-1) == (len = convert_string_internal( CH_UCS2, ch, (char*)u, ilen, dst, outlen)) ) {
617         free (buffer);
618         return (size_t)(-1);
619     }
620
621     free(buffer);
622     return (len);
623 }
624
625 size_t charset_decompose ( charset_t ch, char * src, size_t inlen, char * dst, size_t outlen)
626 {
627     char *buffer;
628     ucs2_t u[MAXPATHLEN];
629     size_t len;
630     size_t ilen;
631
632     if ((size_t)(-1) == (len = convert_string_allocate_internal(ch, CH_UCS2, src, inlen, &buffer)) )
633         return len;
634
635     ilen=sizeof(u);
636
637     if ( (size_t)-1 == (ilen = decompose_w((ucs2_t *)buffer, len, u, &ilen)) ) {
638         free (buffer);
639         return (size_t)(-1);
640     }
641
642     if ((size_t)(-1) == (len = convert_string_internal( CH_UCS2, ch, (char*)u, ilen, dst, outlen)) ) {
643         free (buffer);
644         return (size_t)(-1);
645     }
646
647     free(buffer);
648     return (len);
649 }
650
651 size_t utf8_precompose ( char * src, size_t inlen, char * dst, size_t outlen)
652 {
653     return charset_precompose ( CH_UTF8, src, inlen, dst, outlen);
654 }
655
656 size_t utf8_decompose ( char * src, size_t inlen, char * dst, size_t outlen)
657 {
658     return charset_decompose ( CH_UTF8, src, inlen, dst, outlen);
659 }
660
661 #if 0
662 static char  debugbuf[ MAXPATHLEN +1 ];
663 char * debug_out ( char * seq, size_t len)
664 {
665     size_t i = 0;
666     unsigned char *p;
667     char *q;
668
669     p = (unsigned char*) seq;
670     q = debugbuf;
671
672     for ( i = 0; i<=(len-1); i++)
673     {
674         sprintf(q, "%2.2x.", *p);
675         q += 3;
676         p++;
677     }
678     *q=0;
679     q = debugbuf;
680     return q;
681 }
682 #endif
683
684 /*
685  * Convert from MB to UCS2 charset
686  * Flags:
687  *      CONV_UNESCAPEHEX:    ':XX' will be converted to an UCS2 character
688  *      CONV_IGNORE:         return the first convertable characters.
689  *      CONV_FORCE:  force convertion
690  * FIXME:
691  *      This will *not* work if the destination charset is not multibyte, i.e. UCS2->UCS2 will fail
692  *      The (un)escape scheme is not compatible to the old cap style escape. This is bad, we need it
693  *      for e.g. HFS cdroms.
694  */
695
696 static size_t pull_charset_flags (charset_t from_set, charset_t cap_set, const char *src, size_t srclen, char* dest, size_t destlen, uint16_t *flags)
697 {
698     const uint16_t option = (flags ? *flags : 0);
699     size_t i_len, o_len;
700     size_t j = 0;
701     const char* inbuf = (const char*)src;
702     char* outbuf = dest;
703     atalk_iconv_t descriptor;
704     atalk_iconv_t descriptor_cap;
705
706     if (srclen == (size_t)-1)
707         srclen = strlen(src) + 1;
708
709     descriptor = conv_handles[from_set][CH_UCS2];
710     descriptor_cap = conv_handles[cap_set][CH_UCS2];
711
712     if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
713         errno = EINVAL;
714         return (size_t)-1;
715     }
716
717     i_len=srclen;
718     o_len=destlen;
719
720     while (i_len > 0) {
721         for (j = 0; j < i_len; ++j)
722             if (inbuf[j] == ':')
723                 break;
724         j = i_len - j;
725         i_len -= j;
726
727         if (i_len > 0 &&
728             atalk_iconv(descriptor, &inbuf, &i_len, &outbuf, &o_len) == (size_t)-1) {
729             if (errno == EILSEQ || errno == EINVAL) {
730                 errno = EILSEQ;
731                 if ((option & CONV_IGNORE)) {
732                     *flags |= CONV_REQMANGLE;
733                     return destlen - o_len;
734                 }
735                 if ((option & CONV__EILSEQ)) {
736                     if (o_len < 2) {
737                         errno = E2BIG;
738                         goto end;
739                     }
740                     *((ucs2_t *)outbuf) = (ucs2_t) IGNORE_CHAR; /**inbuf */
741                     inbuf++;
742                     i_len--;
743                     outbuf += 2;
744                     o_len -= 2;
745                     /* FIXME reset stat ? */
746                     continue;
747                 }
748             }
749             goto end;
750         }
751
752         if (j) {
753             /* we have a ':' */
754             i_len = j, j = 0;
755
756             if ((option & CONV_UNESCAPEHEX)) {
757                 /* treat it as a CAP hex encoded char */
758                 char h[MAXPATHLEN];
759                 size_t hlen = 0;
760
761                 while (i_len >= 3 && inbuf[0] == ':' &&
762                        isxdigit(inbuf[1]) && isxdigit(inbuf[2])) {
763                     h[hlen++] = (hextoint(inbuf[1]) << 4) | hextoint(inbuf[2]);
764                     inbuf += 3;
765                     i_len -= 3;
766                 }
767                 if (hlen) {
768                     const char *h_buf = h;
769                     if (atalk_iconv(descriptor_cap, &h_buf, &hlen, &outbuf, &o_len) == (size_t)-1) {
770                         i_len += hlen * 3;
771                         inbuf -= hlen * 3;
772                         if (errno == EILSEQ && (option & CONV_IGNORE)) {
773                             *flags |= CONV_REQMANGLE;
774                             return destlen - o_len;
775                         }
776                         goto end;
777                     }
778                 } else {
779                     /* We have an invalid :xx sequence */
780                     errno = EILSEQ;
781                     if ((option & CONV_IGNORE)) {
782                         *flags |= CONV_REQMANGLE;
783                         return destlen - o_len;
784                     }
785                     goto end;
786                 }
787             } else {
788                 /* a ':' that we just convert to a '/' */
789                 ucs2_t slash = 0x002f;
790                 memcpy(outbuf, &slash, sizeof(ucs2_t));
791                 outbuf += 2;
792                 o_len -= 2;
793                 inbuf++;
794                 i_len--;
795             }
796         }
797     }
798 end:
799     return (i_len + j == 0 || (option & CONV_FORCE)) ? destlen - o_len : (size_t)-1;
800 }
801
802 /*
803  * Convert from UCS2 to MB charset
804  * Flags:
805  *      CONV_ESCAPEDOTS: escape leading dots
806  *      CONV_ESCAPEHEX:  unconvertable characters and '/' will be escaped to :XX
807  *      CONV_IGNORE:     return the first convertable characters.
808  *      CONV__EILSEQ:    unconvertable characters will be replaced with '_'
809  *      CONV_FORCE:  force convertion
810  * FIXME:
811  *      CONV_IGNORE and CONV_ESCAPEHEX can't work together. Should we check this ?
812  *      This will *not* work if the destination charset is not multibyte, i.e. UCS2->UCS2 will fail
813  *      The escape scheme is not compatible to the old cap style escape. This is bad, we need it
814  *      for e.g. HFS cdroms.
815  */
816
817
818 static size_t push_charset_flags (charset_t to_set, charset_t cap_set, char* src, size_t srclen, char* dest, size_t destlen, uint16_t *flags)
819 {
820     const uint16_t option = (flags ? *flags : 0);
821     size_t i_len, o_len, i;
822     size_t j = 0;
823     const char* inbuf = (const char*)src;
824     char* outbuf = (char*)dest;
825     atalk_iconv_t descriptor;
826     atalk_iconv_t descriptor_cap;
827     char escch;                 /* 150210: uninitialized OK, depends on j */
828
829     descriptor = conv_handles[CH_UCS2][to_set];
830     descriptor_cap = conv_handles[CH_UCS2][cap_set];
831
832     if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
833         errno = EINVAL;
834         return (size_t) -1;
835     }
836
837     i_len=srclen;
838     o_len=destlen;
839
840     if ((option & CONV_ESCAPEDOTS) &&
841         i_len >= 2 && SVAL(inbuf, 0) == 0x002e) { /* 0x002e = . */
842         if (o_len < 3) {
843             errno = E2BIG;
844             goto end;
845         }
846         *outbuf++ = ':';
847         *outbuf++ = '2';
848         *outbuf++ = 'e';
849         o_len -= 3;
850         inbuf += 2;
851         i_len -= 2;
852         *flags |= CONV_REQESCAPE;
853     }
854
855     while (i_len >= 2) {
856         for (i = 0; i < i_len; i += 2) {
857             ucs2_t c = SVAL(inbuf, i);
858             switch (c) {
859             case 0x003a: /* 0x003a = ':' */
860                 if ( ! (option & CONV_ALLOW_COLON)) {
861                     errno = EILSEQ;
862                     goto end;
863                 }
864                 escch = c;
865                 j = i_len - i;
866                 i_len = i;
867                 break;
868             case 0x002f: /* 0x002f = '/' */
869                 if (option & CONV_ALLOW_SLASH) break;
870                 escch = c;
871                 j = i_len - i;
872                 i_len = i;
873                 break;
874             }
875         }
876         while (i_len > 0 &&
877                atalk_iconv(descriptor, &inbuf, &i_len, &outbuf, &o_len) == (size_t)-1) {
878             if (errno == EILSEQ) {
879                 if ((option & CONV_IGNORE)) {
880                     *flags |= CONV_REQMANGLE;
881                     return destlen - o_len;
882                 }
883                 if ((option & CONV_ESCAPEHEX)) {
884                     const size_t bufsiz = o_len / 3 + 1;
885                     char *buf = malloc(bufsiz);
886                     size_t buflen;
887
888                     if (!buf)
889                         goto end;
890                     i = i_len;
891                     for (buflen = 1; buflen <= bufsiz; ++buflen) {
892                         char *b = buf;
893                         size_t o = buflen;
894                         if (atalk_iconv(descriptor_cap, &inbuf, &i, &b, &o) != (size_t)-1) {
895                             buflen -= o;
896                             break;
897                         } else if (errno != E2BIG) {
898                             SAFE_FREE(buf);
899                             goto end;
900                         } else if (o < buflen) {
901                             buflen -= o;
902                             break;
903                         }
904                     }
905                     if (o_len < buflen * 3) {
906                         SAFE_FREE(buf);
907                         errno = E2BIG;
908                         goto end;
909                     }
910                     o_len -= buflen * 3;
911                     i_len = i;
912                     for (i = 0; i < buflen; ++i) {
913                         *outbuf++ = ':';
914                         *outbuf++ = hexdig[(buf[i] >> 4) & 0x0f];
915                         *outbuf++ = hexdig[buf[i] & 0x0f];
916                     }
917                     SAFE_FREE(buf);
918                     *flags |= CONV_REQESCAPE;
919                     continue;
920                 }
921             }
922             goto end;
923         }
924
925         if (j) {
926             /* we have a ':' or '/' */
927             i_len = j, j = 0;
928
929             if ((option & CONV_ESCAPEHEX)) {
930                 /* CAP hex encode it */
931                 if (o_len < 3) {
932                     errno = E2BIG;
933                     goto end;
934                 }
935                 switch (escch) {
936                 case '/':
937                     *outbuf++ = ':';
938                     *outbuf++ = '2';
939                     *outbuf++ = 'f';
940                     break;
941                 case ':':
942                     *outbuf++ = ':';
943                     *outbuf++ = '3';
944                     *outbuf++ = 'a';
945                     break;
946                 default:
947                     /*
948                      *  THIS SHOULD NEVER BE REACHED !!!
949                      *  As a safety net I put in a ' ' here
950                      */
951                     *outbuf++ = ':';
952                     *outbuf++ = '2';
953                     *outbuf++ = '0';
954                     break;
955                 }
956                 o_len -= 3;
957                 inbuf += 2;
958                 i_len -= 2;
959             } else {
960                 switch (escch) {
961                 case '/':
962                 case ':':
963                     *outbuf++ = ':';
964                     break;
965                 default: /* should never be reached */
966                     *outbuf++ = ' ';
967                     break;
968                 }
969                 o_len--;
970                 inbuf += 2;
971                 i_len -= 2;
972             }
973         }
974     }
975     if (i_len > 0) errno = EINVAL;
976 end:
977     return (i_len + j == 0 || (option & CONV_FORCE)) ? destlen - o_len : (size_t)-1;
978 }
979
980 /*
981  * FIXME the size is a mess we really need a malloc/free logic
982  *`dest size must be dest_len +2
983  */
984 size_t convert_charset ( charset_t from_set, charset_t to_set, charset_t cap_charset, const char *src, size_t src_len, char *dest, size_t dest_len, uint16_t *flags)
985 {
986     size_t i_len, o_len;
987     ucs2_t *u;
988     ucs2_t buffer[MAXPATHLEN +2];
989     ucs2_t buffer2[MAXPATHLEN +2];
990
991     lazy_initialize_conv();
992
993     /* convert from_set to UCS2 */
994     if ((size_t)(-1) == ( o_len = pull_charset_flags( from_set, cap_charset, src, src_len,
995                                                       (char *) buffer, sizeof(buffer) -2, flags)) ) {
996         LOG(log_error, logtype_default, "Conversion failed ( %s to CH_UCS2 )", charset_name(from_set));
997         return (size_t) -1;
998     }
999
1000     if ( o_len == 0)
1001         return o_len;
1002
1003     /* Do pre/decomposition */
1004     i_len = sizeof(buffer2) -2;
1005     u = buffer2;
1006     if (CHECK_FLAGS(flags, CONV_DECOMPOSE) || (charsets[to_set] && (charsets[to_set]->flags & CHARSET_DECOMPOSED)) ) {
1007         if ( (size_t)-1 == (i_len = decompose_w(buffer, o_len, u, &i_len)) )
1008             return (size_t)(-1);
1009     }
1010     else if (CHECK_FLAGS(flags, CONV_PRECOMPOSE) || !charsets[from_set] || (charsets[from_set]->flags & CHARSET_DECOMPOSED)) {
1011         if ( (size_t)-1 == (i_len = precompose_w(buffer, o_len, u, &i_len)) )
1012             return (size_t)(-1);
1013     }
1014     else {
1015         u = buffer;
1016         i_len = o_len;
1017     }
1018     /* null terminate */
1019     u[i_len] = 0;
1020     u[i_len +1] = 0;
1021
1022     /* Do case conversions */
1023     if (CHECK_FLAGS(flags, CONV_TOUPPER)) {
1024         strupper_w(u);
1025     }
1026     else if (CHECK_FLAGS(flags, CONV_TOLOWER)) {
1027         strlower_w(u);
1028     }
1029
1030     /* Convert UCS2 to to_set */
1031     if ((size_t)(-1) == ( o_len = push_charset_flags( to_set, cap_charset, (char *)u, i_len, dest, dest_len, flags )) ) {
1032         LOG(log_error, logtype_default,
1033             "Conversion failed (CH_UCS2 to %s):%s", charset_name(to_set), strerror(errno));
1034         return (size_t) -1;
1035     }
1036     /* null terminate */
1037     dest[o_len] = 0;
1038     dest[o_len +1] = 0;
1039
1040     return o_len;
1041 }