]> arthur.barton.de Git - netatalk.git/blob - libatalk/unicode/charcnv.c
bugfixes: last byte was overwritten with 0x0 in convert_string, increase inbuf on...
[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);
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                                 if (o_len < 2) {
755                                         errno = E2BIG;
756                                         return (size_t) -1;
757                                 }
758                                 o_save[destlen-o_len]   = '_';
759                                 o_save[destlen-o_len+1] = 0x0;
760                                 o_len -= 2;
761                                 outbuf = o_save + destlen - o_len;
762                                 inbuf += 1;
763                                 i_len -= 1;
764                                 *flags |= CONV_REQMANGLE;
765                                 goto conversion_loop;
766             }
767             else
768                 return (size_t) -1;
769     }
770     
771 unhex_char:
772         if (j && flags && (*flags & CONV_UNESCAPEHEX )) {
773                 /* we're at the start on an hex encoded ucs2 char */
774                 if (o_len < 2) {
775                         errno = E2BIG;
776                         return (size_t) -1;
777                 }
778                 if ( j >= 3 && 
779                         isxdigit( *(inbuf+1)) && isxdigit( *(inbuf+2)) ) {
780                         hlen = 0;
781                         while ( *inbuf == ':' && j >=3 && 
782                                 isxdigit( *(inbuf+1)) && isxdigit( *(inbuf+2)) ) {
783                                 inbuf++;
784                                 h[hlen]   = hextoint( *inbuf ) << 4;
785                                 inbuf++;
786                                 h[hlen++] |= hextoint( *inbuf );                        
787                                 inbuf++;
788                                 j -= 3;
789                         }
790                         h_buf = (const char*) h;
791                         if ((size_t) -1 == (retval = atalk_iconv(descriptor_cap, &h_buf, &hlen, &outbuf, &o_len)) ) {
792                                 if (errno == EILSEQ && CHECK_FLAGS(flags, CONV_IGNORE)) {
793                                         if (o_len < hlen) {
794                                                 errno = E2BIG;
795                                                 return retval;
796                                         }
797                                         while ( hlen > 0) {
798                                                 *outbuf++ = '_';
799                                                 *outbuf++ = 0;
800                                                 o_len -= 2;
801                                                 hlen -= 1;
802                                                 *flags |= CONV_REQMANGLE;
803                                         }
804                                 }
805                                 else {
806                                         return retval;
807                                 }
808                         }
809                 }
810                 else {
811                         /* We have an invalid :xx sequence */
812                         if (CHECK_FLAGS(flags, CONV_IGNORE)) {
813                                 *outbuf++ = '_';
814                                 *outbuf++ = 0;
815                                 inbuf++;
816                                 o_len -= 2;
817                                 j -= 1;
818                                 *flags |= CONV_REQMANGLE;
819                         }
820                         else {
821                                 errno=EILSEQ;
822                                 return (size_t) -1;
823                         }
824                 }
825                 i_len = j;
826                 j = 0;
827                 if (i_len > 0)
828                         goto conversion_loop;
829         }
830
831         return destlen-o_len;
832 }
833
834 /* 
835  * Convert from UCS2 to MB charset 
836  * Flags:
837  *              CONV_ESCAPEDOTS: escape leading dots
838  *              CONV_ESCAPEHEX:  unconvertable characters and '/' will be escaped to :XXXX
839  *              CONV_IGNORE:     unconvertable characters will be replaced with '_'
840  * FIXME:
841  *              CONV_IGNORE and CONV_ESCAPEHEX can't work together. Should we check this ?
842  *              This will *not* work if the destination charset is not multibyte, i.e. UCS2->UCS2 will fail
843  *              The escape scheme is not compatible to the old cap style escape. This is bad, we need it 
844  *              for e.g. HFS cdroms.
845  */
846
847
848 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)
849 {
850     size_t i_len, o_len, i;
851     size_t retval, j = 0;
852     const char* inbuf = (const char*)src;
853     char* outbuf = (char*)dest;
854     atalk_iconv_t descriptor;
855     char *o_save;
856     char *buf, *buf_save;
857     size_t buflen;
858     
859     lazy_initialize_conv();
860     
861     descriptor = conv_handles[CH_UCS2][to_set];
862     
863     if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
864         return (size_t) -1;
865     }
866     
867     i_len=srclen;
868     o_len=destlen;
869     o_save=outbuf;
870     
871     if (*inbuf == '.' && flags && (*flags & CONV_ESCAPEDOTS)) {
872         if (o_len < 3) {
873             errno = E2BIG;
874             return (size_t) -1;
875         }
876         o_save[0] = ':';
877         o_save[1] = '2';
878         o_save[2] = 'e';
879         o_len -= 3;
880         inbuf += 2;
881         i_len -= 2;
882         outbuf = o_save + 3;
883         if (flags) *flags |= CONV_REQESCAPE;
884     }
885         
886 conversion_loop:
887     if ( flags && (*flags & CONV_ESCAPEHEX)) {
888         for ( i = 0; i < i_len; i+=2) {
889             if ( inbuf[i] == '/' ) {
890                 j = i_len - i;
891                 if ( 0 == ( i_len = i))
892                     goto escape_slash;
893                 break;
894             }
895         }
896     }
897     
898     retval = atalk_iconv(descriptor,  &inbuf, &i_len, &outbuf, &o_len);
899     if (retval==(size_t)-1) {
900         if (errno == EILSEQ && flags && (*flags & CONV_IGNORE)) {
901             if (o_len == 0) {
902                 errno = E2BIG;
903                 return (size_t) -1;
904             }
905             o_save[destlen-o_len] = '_';
906             o_len -=1;
907             outbuf = o_save + destlen - o_len;
908             inbuf += 2;
909             i_len -= 2;
910             if (flags ) 
911                 *flags |= CONV_REQMANGLE;
912             if (i_len)
913                 goto conversion_loop;
914         }
915         else if ( errno == EILSEQ && flags && (*flags & CONV_ESCAPEHEX)) {
916             if (o_len < 3) {
917                 errno = E2BIG;
918                 return (size_t) -1;
919             }
920             if ((size_t) -1 == (buflen = convert_string_allocate_internal(CH_UCS2, cap_set, inbuf, 2, &buf)) ) 
921                 return buflen;
922             buf_save = buf;
923             while (buflen > 0) {
924                 if ( o_len < 3) {
925                         errno = E2BIG;
926                         return (size_t) -1;
927                 }
928                 *outbuf++ = ':';
929                 *outbuf++ = hexdig[ ( *buf & 0xf0 ) >> 4 ];
930                 *outbuf++ = hexdig[ *buf & 0x0f ];
931                 buf++;
932                 buflen--;
933                 o_len -= 3;
934             }
935             SAFE_FREE(buf_save);
936             buflen = 0;
937             i_len -= 2;
938             inbuf += 2;
939             if (flags) *flags |= CONV_REQESCAPE;
940             if ( i_len > 0)
941                 goto conversion_loop;
942         }
943         else
944            return (size_t)(-1); 
945     }
946         
947 escape_slash:
948     if (j && flags && (*flags & CONV_ESCAPEHEX)) {
949         if (o_len < 3) {
950             errno = E2BIG;
951             return (size_t) -1;
952         }
953         o_save[destlen -o_len]   = ':';
954         o_save[destlen -o_len+1] = '2';
955         o_save[destlen -o_len+2] = 'f';
956         inbuf  += 2;
957         i_len   = j-2;
958         o_len  -= 3;
959         outbuf += 3;
960         j = 0;
961         if ( i_len > 0)
962                 goto conversion_loop;
963     }
964     return destlen -o_len;
965 }
966
967 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)
968 {
969         size_t i_len, o_len;
970         char *u;
971         char buffer[MAXPATHLEN];
972         char buffer2[MAXPATHLEN];
973         int composition = 0;
974         
975         lazy_initialize_conv();
976
977         /* convert from_set to UCS2 */
978         if ((size_t)(-1) == ( o_len = pull_charset_flags( from_set, cap_charset, src, src_len, buffer, MAXPATHLEN, flags)) ) {
979                 LOG(log_error, logtype_default, "Conversion failed ( %s to CH_UCS2 )", charset_name(from_set));
980                 return (size_t) -1;
981         }
982
983         /* Do pre/decomposition */
984         if (CHECK_FLAGS(flags, CONV_PRECOMPOSE) || 
985                 ((!(charsets[to_set])   || !(charsets[to_set]->flags & CHARSET_DECOMPOSED)) && 
986                 (!(charsets[from_set]) || (charsets[from_set]->flags & CHARSET_DECOMPOSED))))
987             composition = 1;
988         if (CHECK_FLAGS(flags, CONV_DECOMPOSE) || (charsets[to_set] && charsets[to_set]->flags & CHARSET_DECOMPOSED) )
989             composition = 2;
990  
991         i_len = MAXPATHLEN;
992         u = buffer2;
993
994         switch (composition) {
995         case 0:
996             u = buffer;
997             i_len = o_len;
998             break;
999         case 1:
1000             if ( (size_t)-1 == (i_len = precompose_w((ucs2_t *)buffer, o_len, (ucs2_t *)u, &i_len)) )
1001                 return (size_t)(-1);
1002             break;
1003         case 2:
1004             if ( (size_t)-1 == (i_len = decompose_w((ucs2_t *)buffer, o_len, (ucs2_t *)u, &i_len)) )
1005                 return (size_t)(-1);
1006             break;
1007         }
1008                 
1009         /* Do case conversions */       
1010         if (CHECK_FLAGS(flags, CONV_TOUPPER)) {
1011             if (!strupper_w((ucs2_t *) u)) 
1012                 return (size_t)(-1);
1013         }
1014         if (CHECK_FLAGS(flags, CONV_TOLOWER)) {
1015             if (!strlower_w((ucs2_t *) u)) 
1016                 return (size_t)(-1);
1017         }
1018
1019         /* Convert UCS2 to to_set */
1020         if ((size_t)(-1) == ( o_len = push_charset_flags( to_set, cap_charset, u, i_len, dest, dest_len, flags )) ) {
1021                 LOG(log_error, logtype_default, 
1022                        "Conversion failed (CH_UCS2 to %s):%s", charset_name(to_set), strerror(errno));
1023                 return (size_t) -1;
1024         }
1025
1026         return o_len;
1027 }