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