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