]> arthur.barton.de Git - netatalk.git/blob - libatalk/unicode/charcnv.c
e5f063b90a5af7dfe01453e568c0dae30d98fa10
[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 <sys/stat.h>
34 #include <atalk/logger.h>
35 #include <errno.h>
36
37 #include <netatalk/endian.h>
38 #include <atalk/unicode.h>
39
40 #ifdef HAVE_USABLE_ICONV
41 #include <iconv.h>
42 #endif
43
44
45 /**
46  * @file
47  *
48  * @brief Character-set conversion routines built on our iconv.
49  * 
50  * @note Samba's internal character set (at least in the 3.0 series)
51  * is always the same as the one for the Unix filesystem.  It is
52  * <b>not</b> necessarily UTF-8 and may be different on machines that
53  * need i18n filenames to be compatible with Unix software.  It does
54  * have to be a superset of ASCII.  All multibyte sequences must start
55  * with a byte with the high bit set.
56  *
57  * @sa lib/iconv.c
58  */
59
60
61 #define MAX_CHARSETS 10
62
63 static atalk_iconv_t conv_handles[MAX_CHARSETS][MAX_CHARSETS];
64
65 static char* charset_names[MAX_CHARSETS];
66
67 struct charset {
68         const char *name;
69         charset_t ch_charset_t;
70         struct charset *prev, *next;
71 };
72
73 /**
74  * Return the name of a charset to give to iconv().
75  **/
76 static const char *charset_name(charset_t ch)
77 {
78         const char *ret = NULL;
79
80         if (ch == CH_UCS2) ret = "UCS-2LE";
81         else if (ch == CH_UNIX) ret = "ASCII"; /*lp_unix_charset();*/
82         else if (ch == CH_MAC) ret = "MAC"; /*lp_display_charset();*/
83         else if (ch == CH_UTF8) ret = "UTF8";
84
85         if (!ret)
86                 ret = charset_names[ch];
87
88         if (!ret || !*ret) ret = "ASCII";
89         return ret;
90 }
91
92 void lazy_initialize_conv(void)
93 {
94         static int initialized = 0;
95
96         if (!initialized) {
97                 initialized = 1;
98                 init_iconv();
99         }
100 }
101
102 charset_t add_charset(char* name)
103 {
104         static charset_t max_charset_t = NUM_CHARSETS-1;
105         charset_t cur_charset_t = max_charset_t+1;
106         int c1, c2;
107
108         for (c1=0; c1<=max_charset_t;c1++) {
109                 if ( strcmp(name, charset_name(c1)) == 0)
110                         return (c1);
111         }
112
113         if ( cur_charset_t >= MAX_CHARSETS )  {
114                 LOG (log_debug, logtype_default, "Adding charset %s failed, too many charsets (max. %u allowed)", 
115                         name, MAX_CHARSETS);
116                 return 0;
117         }
118
119         /* First try to setup the required conversions */
120
121         conv_handles[cur_charset_t][CH_UCS2] = atalk_iconv_open( charset_name(CH_UCS2), name);
122         if (conv_handles[cur_charset_t][CH_UCS2] == (atalk_iconv_t)-1) {
123                 LOG(log_error, logtype_default, "Required conversion from %s to %s not supported\n",
124                         name,  charset_name(CH_UCS2));
125                 conv_handles[cur_charset_t][CH_UCS2] = NULL;
126                 return 0;
127         }
128
129         conv_handles[CH_UCS2][cur_charset_t] = atalk_iconv_open( name, charset_name(CH_UCS2));
130         if (conv_handles[CH_UCS2][cur_charset_t] == (atalk_iconv_t)-1) {
131                 LOG(log_error, logtype_default, "Required conversion from %s to %s not supported\n",
132                         charset_name(CH_UCS2), name);
133                 conv_handles[CH_UCS2][cur_charset_t] = NULL;
134                 return 0;
135         }
136
137         /* register the new charset_t name */
138         charset_names[cur_charset_t] = strdup(name);
139
140         
141         for (c1=0;c1<=cur_charset_t;c1++) {
142                 for (c2=0;c2<=cur_charset_t;c2++) {
143                         const char *n1 = charset_name((charset_t)c1);
144                         const char *n2 = charset_name((charset_t)c2);
145                         if (conv_handles[c1][c2] &&
146                             strcmp(n1, conv_handles[c1][c2]->from_name) == 0 &&
147                             strcmp(n2, conv_handles[c1][c2]->to_name) == 0)
148                                 continue;
149
150                         if (conv_handles[c1][c2])
151                                 atalk_iconv_close(conv_handles[c1][c2]);
152
153                         conv_handles[c1][c2] = atalk_iconv_open(n2,n1);
154                         if (conv_handles[c1][c2] == (atalk_iconv_t)-1) {
155                                 LOG(log_debug, logtype_default, "Conversion from %s to %s not supported\n",
156                                          charset_name((charset_t)c1), charset_name((charset_t)c2));
157                                 conv_handles[c1][c2] = NULL;
158                         }
159                 }
160         }
161
162         max_charset_t++;
163
164         LOG(log_debug, logtype_default, "Added charset %s with handle %u", name, cur_charset_t);
165         return (cur_charset_t);
166 }
167
168 /**
169  * Initialize iconv conversion descriptors.
170  *
171  * This is called the first time it is needed, and also called again
172  * every time the configuration is reloaded, because the charset or
173  * codepage might have changed.
174  **/
175 void init_iconv(void)
176 {
177         int c1, c2;
178
179         /* so that charset_name() works we need to get the UNIX<->UCS2 going
180            first */
181         if (!conv_handles[CH_UNIX][CH_UCS2])
182                 conv_handles[CH_UNIX][CH_UCS2] = atalk_iconv_open("UCS-2LE", "ASCII");
183
184         if (!conv_handles[CH_UCS2][CH_UNIX])
185                 conv_handles[CH_UCS2][CH_UNIX] = atalk_iconv_open("ASCII", "UCS-2LE");
186
187         for (c1=0;c1<NUM_CHARSETS;c1++) {
188                 for (c2=0;c2<NUM_CHARSETS;c2++) {
189                         const char *n1 = charset_name((charset_t)c1);
190                         const char *n2 = charset_name((charset_t)c2);
191                         if (conv_handles[c1][c2] &&
192                             strcmp(n1, conv_handles[c1][c2]->from_name) == 0 &&
193                             strcmp(n2, conv_handles[c1][c2]->to_name) == 0)
194                                 continue;
195
196                         if (conv_handles[c1][c2])
197                                 atalk_iconv_close(conv_handles[c1][c2]);
198
199                         conv_handles[c1][c2] = atalk_iconv_open(n2,n1);
200                         if (conv_handles[c1][c2] == (atalk_iconv_t)-1) {
201                                 LOG(log_debug, logtype_default, "Conversion from %s to %s not supported\n",
202                                          charset_name((charset_t)c1), charset_name((charset_t)c2));
203                                 conv_handles[c1][c2] = NULL;
204                         }
205                 }
206         }
207 }
208
209 /**
210  * Convert string from one encoding to another, making error checking etc
211  *
212  * @param src pointer to source string (multibyte or singlebyte)
213  * @param srclen length of the source string in bytes
214  * @param dest pointer to destination string (multibyte or singlebyte)
215  * @param destlen maximal length allowed for string
216  * @returns the number of bytes occupied in the destination
217  **/
218 size_t convert_string(charset_t from, charset_t to,
219                       void const *src, size_t srclen, 
220                       void *dest, size_t destlen)
221 {
222         size_t i_len, o_len;
223         size_t retval;
224         const char* inbuf = (const char*)src;
225         char* outbuf = (char*)dest;
226         atalk_iconv_t descriptor;
227
228         if (srclen == (size_t)-1)
229                 srclen = strlen(src)+1;
230
231         lazy_initialize_conv();
232
233         descriptor = conv_handles[from][to];
234
235         if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
236                 /* conversion not supported, use as is */
237                 size_t len = MIN(srclen,destlen);
238                 memcpy(dest,src,len);
239                 return len;
240         }
241
242         i_len=srclen;
243         o_len=destlen;
244         retval = atalk_iconv(descriptor,  &inbuf, &i_len, &outbuf, &o_len);
245         if(retval==(size_t)-1) {
246                 const char *reason="unknown error";
247                 switch(errno) {
248                         case EINVAL:
249                                 reason="Incomplete multibyte sequence";
250                                 break;
251                         case E2BIG:
252                                 reason="No more room"; 
253                                 LOG(log_debug, logtype_default, "convert_string: Required %d, available %d\n",
254                                         srclen, destlen);
255                                 /* we are not sure we need srclen bytes,
256                                   may be more, may be less.
257                                   We only know we need more than destlen
258                                   bytes ---simo */
259                                break;
260                         case EILSEQ:
261                                reason="Illegal multibyte sequence";
262                                break;
263                 }
264                 return (size_t)-1;
265                 /* smb_panic(reason); */
266         }
267         return destlen-o_len;
268 }
269
270 /**
271  * Convert between character sets, allocating a new buffer for the result.
272  *
273  * @param srclen length of source buffer.
274  * @param dest always set at least to NULL
275  * @note -1 is not accepted for srclen.
276  *
277  * @returns Size in bytes of the converted string; or -1 in case of error.
278  **/
279
280 size_t convert_string_allocate(charset_t from, charset_t to,
281                                void const *src, size_t srclen, void **dest)
282 {
283         size_t i_len, o_len, destlen;
284         size_t retval;
285         const char *inbuf = (const char *)src;
286         char *outbuf, *ob;
287         atalk_iconv_t descriptor;
288
289         *dest = NULL;
290
291         if (src == NULL || srclen == (size_t)-1)
292                 return (size_t)-1;
293
294         lazy_initialize_conv();
295
296         descriptor = conv_handles[from][to];
297
298         if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
299                 /* conversion not supported, return -1*/
300                 LOG(log_debug, logtype_default, "convert_string_allocate: conversion not supported!\n");
301                 return -1;
302         }
303
304         destlen = MAX(srclen, 512);
305         outbuf = NULL;
306 convert:
307         destlen = destlen * 2;
308         ob = (char *)realloc(outbuf, destlen);
309         if (!ob) {
310                 LOG(log_debug, logtype_default,"convert_string_allocate: realloc failed!\n");
311                 SAFE_FREE(outbuf);
312                 return (size_t)-1;
313         } else {
314                 outbuf = ob;
315         }
316         i_len = srclen;
317         o_len = destlen;
318         retval = atalk_iconv(descriptor,
319                            &inbuf, &i_len,
320                            &outbuf, &o_len);
321         if(retval == (size_t)-1)                {
322                 const char *reason="unknown error";
323                 switch(errno) {
324                         case EINVAL:
325                                 reason="Incomplete multibyte sequence";
326                                 break;
327                         case E2BIG:
328                                 goto convert;           
329                         case EILSEQ:
330                                 reason="Illegal multibyte sequence";
331                                 break;
332                 }
333                 LOG(log_debug, logtype_default,"Conversion error: %s(%s)\n",reason,inbuf);
334                 /* smb_panic(reason); */
335                 return (size_t)-1;
336         }
337         
338         destlen = destlen - o_len;
339         *dest = (char *)realloc(ob,destlen);
340         if (destlen && !*dest) {
341                 LOG(log_debug, logtype_default, "convert_string_allocate: out of memory!\n");
342                 SAFE_FREE(ob);
343                 return (size_t)-1;
344         }
345
346         return destlen;
347 }
348
349
350 size_t unix_strupper(const char *src, size_t srclen, char *dest, size_t destlen)
351 {
352         size_t size;
353         ucs2_t *buffer;
354         
355         size = convert_string_allocate(CH_UNIX, CH_UCS2, src, srclen,
356                                        (void **) &buffer);
357         if (size == -1) {
358                 free(buffer);
359                 return size;
360         }
361         if (!strupper_w(buffer) && (dest == src)) {
362                 free(buffer);
363                 return srclen;
364         }
365         
366         size = convert_string(CH_UCS2, CH_UNIX, buffer, size, dest, destlen);
367         free(buffer);
368         return size;
369 }
370
371 size_t unix_strlower(const char *src, size_t srclen, char *dest, size_t destlen)
372 {
373         size_t size;
374         ucs2_t *buffer;
375         
376         size = convert_string_allocate(CH_UNIX, CH_UCS2, src, srclen,
377                                        (void **) &buffer);
378         if (size == -1) {
379                 free(buffer);
380                 return size;
381         /*      smb_panic("failed to create UCS2 buffer");*/
382         }
383         if (!strlower_w(buffer) && (dest == src)) {
384                 free(buffer);
385                 return srclen;
386         }
387         size = convert_string(CH_UCS2, CH_UNIX, buffer, size, dest, destlen);
388         free(buffer);
389         return size;
390 }
391
392 size_t utf8_strupper(const char *src, size_t srclen, char *dest, size_t destlen)
393 {
394         size_t size;
395         ucs2_t *buffer;
396         
397         size = convert_string_allocate(CH_UTF8, CH_UCS2, src, srclen,
398                                        (void **) &buffer);
399         if (size == -1) {
400                 free(buffer);
401                 return size;
402         }
403         if (!strupper_w(buffer) && (dest == src)) {
404                 free(buffer);
405                 return srclen;
406         }
407         
408         size = convert_string(CH_UCS2, CH_UTF8, buffer, size, dest, destlen);
409         free(buffer);
410         return size;
411 }
412
413 size_t utf8_strlower(const char *src, size_t srclen, char *dest, size_t destlen)
414 {
415         size_t size;
416         ucs2_t *buffer;
417         
418         size = convert_string_allocate(CH_UTF8, CH_UCS2, src, srclen,
419                                        (void **) &buffer);
420         if (size == -1) {
421                 free(buffer);
422                 return size;
423         }
424         if (!strlower_w(buffer) && (dest == src)) {
425                 free(buffer);
426                 return srclen;
427         }
428         
429         size = convert_string(CH_UCS2, CH_UTF8, buffer, size, dest, destlen);
430         free(buffer);
431         return size;
432 }
433
434 size_t mac_strupper(const char *src, size_t srclen, char *dest, size_t destlen)
435 {
436         size_t size;
437         ucs2_t *buffer;
438         
439         size = convert_string_allocate(CH_MAC, CH_UCS2, src, srclen,
440                                        (void **) &buffer);
441         if (size == -1) {
442                 free(buffer);
443                 return size;
444         }
445         if (!strupper_w(buffer) && (dest == src)) {
446                 free(buffer);
447                 return srclen;
448         }
449         
450         size = convert_string(CH_UCS2, CH_MAC, buffer, size, dest, destlen);
451         free(buffer);
452         return size;
453 }
454
455 size_t mac_strlower(const char *src, size_t srclen, char *dest, size_t destlen)
456 {
457         size_t size;
458         ucs2_t *buffer;
459         
460         size = convert_string_allocate(CH_MAC, CH_UCS2, src, srclen,
461                                        (void **) &buffer);
462         if (size == -1) {
463                 free(buffer);
464                 return size;
465         }
466         if (!strlower_w(buffer) && (dest == src)) {
467                 free(buffer);
468                 return srclen;
469         }
470         
471         size = convert_string(CH_UCS2, CH_MAC, buffer, size, dest, destlen);
472         free(buffer);
473         return size;
474 }
475
476 /**
477  * Copy a string from a mac char* src to a UCS2 destination, allocating a buffer
478  *
479  * @param dest always set at least to NULL 
480  *
481  * @returns The number of bytes occupied by the string in the destination
482  *         or -1 in case of error.
483  **/
484
485 size_t mac_to_ucs2_allocate(ucs2_t **dest, const char *src)
486 {
487         size_t src_len = strlen(src)+1;
488
489         *dest = NULL;
490         return convert_string_allocate(CH_MAC, CH_UCS2, src, src_len, (void **)dest);   
491 }
492
493 /**
494  * Copy a string from a unix char* src to a UTF-8 destination, allocating a buffer
495  *
496  * @param dest always set at least to NULL 
497  *
498  * @returns The number of bytes occupied by the string in the destination
499  **/
500
501 size_t mac_to_utf8_allocate(char **dest, const char *src)
502 {
503         size_t src_len = strlen(src)+1;
504
505         *dest = NULL;
506         return convert_string_allocate(CH_MAC, CH_UTF8, src, src_len, (void **)dest);   
507 }
508
509 /**
510  * Copy a string from a UCS2 src to a unix char * destination, allocating a buffer
511  *
512  * @param dest always set at least to NULL 
513  *
514  * @returns The number of bytes occupied by the string in the destination
515  **/
516
517 size_t ucs2_to_mac_allocate(char **dest, const ucs2_t *src)
518 {
519         size_t src_len = (strlen_w(src)+1) * sizeof(ucs2_t);
520         *dest = NULL;
521         return convert_string_allocate(CH_UCS2, CH_MAC, src, src_len, (void **)dest);   
522 }
523
524 /**
525  * Copy a string from a UTF-8 src to a unix char * destination, allocating a buffer
526  *
527  * @param dest always set at least to NULL 
528  *
529  * @returns The number of bytes occupied by the string in the destination
530  **/
531
532 static char convbuf[MAXPATHLEN+1];
533 size_t utf8_to_mac_allocate(void **dest, const char *src)
534 {
535         size_t src_len = strlen(src)+1;
536         *dest = NULL;
537
538         src_len = utf8_precompose ( (char *) src, src_len, convbuf, MAXPATHLEN);        
539         return convert_string_allocate(CH_UTF8, CH_MAC, convbuf, src_len, dest);        
540 }
541
542 size_t utf8_to_mac ( char* src, size_t src_len, char* dest, size_t dest_len)
543 {
544         src_len = utf8_precompose ( (char *) src, src_len, convbuf, MAXPATHLEN);        
545         return convert_string(CH_UTF8, CH_MAC, convbuf, src_len, dest, dest_len);       
546 }
547
548 static char  debugbuf[ MAXPATHLEN +1 ];
549 char * debug_out ( char * seq, size_t len)
550 {
551         size_t i = 0;
552         unsigned char *p;
553         char *q;
554
555         p = (unsigned char*) seq;
556         q = debugbuf;
557
558         for ( i = 0; i<=(len-1); i++)
559         {
560                 sprintf(q, "%2.2x.", *p);
561                 q += 3;
562                 p++;
563         }
564         *q=0;
565         q = debugbuf;
566         return q;
567 }
568
569
570 size_t utf8_precompose ( char * src, size_t inlen, char * dst, size_t outlen)
571 {
572         char *u;
573         size_t len;
574         size_t ilen;
575
576         if ((size_t)(-1) == (len =  convert_string(CH_UTF8, CH_UCS2, src, inlen, convbuf, MAXPATHLEN)) )
577             return len;
578
579         if ( NULL == (u = precompose_w((ucs2_t *)convbuf, len, &ilen)) )
580             return (size_t)(-1);
581
582         if ((size_t)(-1) == (len = convert_string( CH_UCS2, CH_UTF8, u, ilen, dst, outlen)) )
583             return (size_t)(-1);
584
585         dst[len] = 0;
586         return (len);
587 }
588
589 size_t utf8_decompose ( char * src, size_t inlen, char * dst, size_t outlen)
590 {
591         char *u;
592         size_t len;
593         size_t ilen;
594
595         if ((size_t)(-1) == (len =  convert_string(CH_UTF8, CH_UCS2, src, inlen, convbuf, MAXPATHLEN)) )
596             return len;
597
598         if ( NULL == (u = decompose_w((ucs2_t *)convbuf, len, &ilen)) )
599             return (size_t)(-1);
600
601         if ((size_t)(-1) == (len = convert_string( CH_UCS2, CH_UTF8, u, ilen, dst, outlen)) )
602             return (size_t)(-1);
603
604         dst[len] = 0;
605         return (len);
606 }
607
608
609 size_t utf8_to_mac_charset ( charset_t ch, char* src, size_t src_len, char* dest, size_t dest_len, int* mangle)
610 {
611         size_t i_len, o_len;
612         size_t retval;
613         const char* inbuf;
614         char* outbuf = (char*)dest;
615         atalk_iconv_t descriptor;
616
617         lazy_initialize_conv();
618
619         src_len = utf8_precompose ( (char *) src, src_len+1, convbuf, MAXPATHLEN);
620
621         descriptor = conv_handles[CH_UTF8][ch];
622
623         if (descriptor == (atalk_iconv_t)-1 || descriptor == (atalk_iconv_t)0) {
624                 LOG(log_error, logtype_default, "Conversion not supported ( UTF8 to %s )", charset_name(ch));
625                 return (size_t)(-1);    
626         }
627
628         inbuf = (const char*) convbuf;
629         i_len=src_len;
630         o_len=dest_len;
631
632         retval = atalk_iconv_ignore(descriptor,  &inbuf, &i_len, &outbuf, &o_len, mangle);
633
634         if(retval==(size_t)-1) 
635                 return (size_t)(-1);    
636         
637         dest[dest_len-o_len] = 0;
638         return dest_len-o_len;
639 }
640