]> git.sesse.net Git - vlc/blob - src/misc/unicode.c
fileinfo.hpp: we need to use b_stats in Update()
[vlc] / src / misc / unicode.c
1 /*****************************************************************************
2  * unicode.c: UTF8 <-> locale functions
3  *****************************************************************************
4  * Copyright (C) 2005-2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Rémi Denis-Courmont <rem # videolan.org>
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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #include <vlc/vlc.h>
28 #include "charset.h"
29
30 #include <assert.h>
31
32 #include <stdio.h>
33 #include <stdarg.h>
34 #include <errno.h>
35 #include <sys/types.h>
36 #ifdef HAVE_DIRENT_H
37 #  include <dirent.h>
38 #endif
39 #ifdef UNDER_CE
40 #  include <tchar.h>
41 #endif
42 #ifdef HAVE_SYS_STAT_H
43 # include <sys/stat.h>
44 #endif
45 #ifndef HAVE_LSTAT
46 # define lstat( a, b ) stat(a, b)
47 #endif
48
49 #ifdef __APPLE__
50 /* Define this if the OS always use UTF-8 internally */
51 # define ASSUME_UTF8 1
52 #endif
53
54 #ifndef ASSUME_UTF8
55 # if defined (HAVE_ICONV)
56 /* libiconv is more powerful than Win32 API (it has translit) */
57 #  define USE_ICONV 1
58 # elif defined (WIN32) || defined (UNDER_CE)
59 #  define USE_MB2MB 1
60 # else
61 #  error No UTF8 charset conversion implemented on this platform!
62 # endif
63 #endif
64
65 #ifdef USE_ICONV
66 static struct {
67     vlc_iconv_t hd;
68     vlc_mutex_t lock;
69 } from_locale, to_locale;
70 #endif
71
72 void LocaleInit( vlc_object_t *p_this )
73 {
74 #ifdef USE_ICONV
75     char *psz_charset;
76
77     if( vlc_current_charset( &psz_charset ) )
78         /* UTF-8 */
79         from_locale.hd = to_locale.hd = (vlc_iconv_t)(-1);
80     else
81     {
82         /* not UTF-8 */
83         char psz_buf[strlen( psz_charset ) + sizeof( "//translit" )];
84         const char *psz_conv;
85
86         /*
87          * Still allow non-ASCII characters when the locale is not set.
88          * Western Europeans are being favored for historical reasons.
89          */
90         if( strcmp( psz_charset, "ASCII" ) )
91         {
92             sprintf( psz_buf, "%s//translit", psz_charset );
93             psz_conv = psz_buf;
94         }
95         else
96             psz_conv = "ISO-8859-1//translit";
97
98         vlc_mutex_init( p_this, &from_locale.lock );
99         vlc_mutex_init( p_this, &to_locale.lock );
100         from_locale.hd = vlc_iconv_open( "UTF-8", psz_conv );
101         to_locale.hd = vlc_iconv_open( psz_conv, "UTF-8" );
102     }
103
104     free( psz_charset );
105
106     assert( (from_locale.hd == (vlc_iconv_t)(-1))
107             == (to_locale.hd == (vlc_iconv_t)(-1)) );
108 #else
109     (void)p_this;
110 #endif
111 }
112
113 void LocaleDeinit( void )
114 {
115 #ifdef USE_ICONV
116     if( to_locale.hd != (vlc_iconv_t)(-1) )
117     {
118         vlc_iconv_close( to_locale.hd );
119         vlc_mutex_destroy( &to_locale.lock );
120     }
121
122     if( from_locale.hd != (vlc_iconv_t)(-1) )
123     {
124         vlc_iconv_close( from_locale.hd );
125         vlc_mutex_destroy( &from_locale.lock );
126     }
127 #endif
128 }
129
130 #ifdef USE_MB2MB
131 static char *MB2MB( const char *string, UINT fromCP, UINT toCP )
132 {
133     char *out;
134     wchar_t *wide;
135     int len;
136
137     len = MultiByteToWideChar( fromCP, 0, string, -1, NULL, 0 );
138     assert( len > 0 );
139     wide = (wchar_t *)malloc (len * sizeof (wchar_t));
140     if( wide == NULL )
141         return NULL;
142
143     MultiByteToWideChar( fromCP, 0, string, -1, wide, len );
144     len = WideCharToMultiByte( toCP, 0, wide, -1, NULL, 0, NULL, NULL );
145     assert( len > 0 );
146     out = malloc( len );
147
148     WideCharToMultiByte( toCP, 0, wide, -1, out, len, NULL, NULL );
149     free( wide );
150     return out;
151 }
152 #endif
153
154 /*****************************************************************************
155  * FromLocale: converts a locale string to UTF-8
156  *****************************************************************************/
157 char *FromLocale( const char *locale )
158 {
159     if( locale == NULL )
160         return NULL;
161
162 #ifndef USE_MB2MB
163 # ifdef USE_ICONV
164     if( from_locale.hd != (vlc_iconv_t)(-1) )
165     {
166         char *iptr = (char *)locale, *output, *optr;
167         size_t inb, outb;
168
169         /*
170          * We are not allowed to modify the locale pointer, even if we cast it
171          * to non-const.
172          */
173         inb = strlen( locale );
174         /* FIXME: I'm not sure about the value for the multiplication
175          * (for western people, multiplication by 3 (Latin9) is needed).
176          * While UTF-8 could reach 6 bytes, no existing code point exceeds
177          * 4 bytes. */
178         outb = inb * 4 + 1;
179
180         optr = output = malloc( outb );
181
182         vlc_mutex_lock( &from_locale.lock );
183         vlc_iconv( from_locale.hd, NULL, NULL, NULL, NULL );
184
185         while( vlc_iconv( from_locale.hd, &iptr, &inb, &optr, &outb )
186                == (size_t)-1 )
187         {
188             *optr++ = '?';
189             outb--;
190             iptr++;
191             inb--;
192             vlc_iconv( from_locale.hd, NULL, NULL, NULL, NULL );
193         }
194         vlc_mutex_unlock( &from_locale.lock );
195         *optr = '\0';
196
197         assert (inb == 0);
198         assert (*iptr == '\0');
199         assert (*optr == '\0');
200         assert (strlen( output ) == (size_t)(optr - output));
201         return realloc( output, optr - output + 1 );
202     }
203 # endif /* USE_ICONV */
204     return (char *)locale;
205 #else /* MB2MB */
206     return MB2MB( locale, CP_ACP, CP_UTF8 );
207 #endif
208 }
209
210 char *FromLocaleDup( const char *locale )
211 {
212 #if defined (ASSUME_UTF8)
213     return strdup( locale );
214 #else
215 # ifdef USE_ICONV
216     if (from_locale.hd == (vlc_iconv_t)(-1))
217         return strdup( locale );
218 # endif
219     return FromLocale( locale );
220 #endif
221 }
222
223
224 /*****************************************************************************
225  * ToLocale: converts an UTF-8 string to locale
226  *****************************************************************************/
227 char *ToLocale( const char *utf8 )
228 {
229     if( utf8 == NULL )
230         return NULL;
231
232 #ifndef USE_MB2MB
233 # ifdef USE_ICONV
234     if( to_locale.hd != (vlc_iconv_t)(-1) )
235     {
236         char *iptr = (char *)utf8, *output, *optr;
237         size_t inb, outb;
238
239         /*
240         * We are not allowed to modify the locale pointer, even if we cast it
241         * to non-const.
242         */
243         inb = strlen( utf8 );
244         /* FIXME: I'm not sure about the value for the multiplication
245         * (for western people, multiplication is not needed) */
246         outb = inb * 2 + 1;
247
248         optr = output = malloc( outb );
249         vlc_mutex_lock( &to_locale.lock );
250         vlc_iconv( to_locale.hd, NULL, NULL, NULL, NULL );
251
252         while( vlc_iconv( to_locale.hd, &iptr, &inb, &optr, &outb )
253                == (size_t)-1 )
254         {
255             *optr++ = '?'; /* should not happen, and yes, it sucks */
256             outb--;
257             iptr++;
258             inb--;
259             vlc_iconv( to_locale.hd, NULL, NULL, NULL, NULL );
260         }
261         vlc_mutex_unlock( &to_locale.lock );
262         *optr = '\0';
263
264         assert (inb == 0);
265         assert (*iptr == '\0');
266         assert (*optr == '\0');
267         assert (strlen( output ) == (size_t)(optr - output));
268         return realloc( output, optr - output + 1 );
269     }
270 # endif /* USE_ICONV */
271     return (char *)utf8;
272 #else /* MB2MB */
273     return MB2MB( utf8, CP_UTF8, CP_ACP );
274 #endif
275 }
276
277 char *ToLocaleDup( const char *utf8 )
278 {
279 #if defined (ASSUME_UTF8)
280     return strdup( utf8 );
281 #else
282 # ifdef USE_ICONV
283     if (to_locale.hd == (vlc_iconv_t)(-1))
284         return strdup( utf8 );
285 # endif
286     return ToLocale( utf8 );
287 #endif
288 }
289
290 void LocaleFree( const char *str )
291 {
292 #ifdef USE_ICONV
293     if( to_locale.hd == (vlc_iconv_t)(-1) )
294         return;
295 #endif
296
297 #ifndef ASSUME_UTF8
298     if( str != NULL )
299         free( (char *)str );
300 #endif
301 }
302
303 /*****************************************************************************
304  * utf8_fopen: Calls fopen() after conversion of file name to OS locale
305  *****************************************************************************/
306 FILE *utf8_fopen( const char *filename, const char *mode )
307 {
308 #if !(defined (WIN32) || defined (UNDER_CE))
309     const char *local_name = ToLocale( filename );
310
311     if( local_name != NULL )
312     {
313         FILE *stream = fopen( local_name, mode );
314         LocaleFree( local_name );
315         return stream;
316     }
317     else
318         errno = ENOENT;
319     return NULL;
320 #else
321     wchar_t wpath[MAX_PATH + 1];
322     size_t len = strlen( mode ) + 1;
323     wchar_t wmode[len];
324
325     if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH )
326      || !MultiByteToWideChar( CP_ACP, 0, mode, len, wmode, len ) )
327     {
328         errno = ENOENT;
329         return NULL;
330     }
331     wpath[MAX_PATH] = L'\0';
332
333     /*
334      * fopen() cannot open files with non-“ANSI” characters on Windows.
335      * We use _wfopen() instead. Same thing for mkdir() and stat().
336      */
337     return _wfopen( wpath, wmode );
338 #endif
339 }
340
341 /*****************************************************************************
342  * utf8_mkdir: Calls mkdir() after conversion of file name to OS locale
343  *****************************************************************************/
344 int utf8_mkdir( const char *dirname )
345 {
346 #if defined (UNDER_CE) || defined (WIN32)
347     wchar_t wname[MAX_PATH + 1];
348     char mod[MAX_PATH + 1];
349     int i;
350
351     /* Convert '/' into '\' */
352     for( i = 0; *dirname; i++ )
353     {
354         if( i == MAX_PATH )
355             return -1; /* overflow */
356
357         if( *dirname == '/' )
358             mod[i] = '\\';
359         else
360             mod[i] = *dirname;
361         dirname++;
362
363     }
364     mod[i] = 0;
365
366     if( MultiByteToWideChar( CP_UTF8, 0, mod, -1, wname, MAX_PATH ) == 0 )
367     {
368         errno = ENOENT;
369         return -1;
370     }
371     wname[MAX_PATH] = L'\0';
372
373     if( CreateDirectoryW( wname, NULL ) == 0 )
374     {
375         if( GetLastError( ) == ERROR_ALREADY_EXISTS )
376             errno = EEXIST;
377         errno = ENOENT;
378         return -1;
379     }
380     return 0;
381 #else
382     char *locname = ToLocale( dirname );
383     int res;
384
385     if( locname == NULL )
386     {
387         errno = ENOENT;
388         return -1;
389     }
390     res = mkdir( locname, 0755 );
391
392     LocaleFree( locname );
393     return res;
394 #endif
395 }
396
397
398 void *utf8_opendir( const char *dirname )
399 {
400     const char *local_name = ToLocale( dirname );
401
402     if( local_name != NULL )
403     {
404         DIR *dir = opendir( local_name );
405         LocaleFree( local_name );
406         return dir;
407     }
408     else
409         errno = ENOENT;
410     return NULL;
411 }
412
413 const char *utf8_readdir( void *dir )
414 {
415     struct dirent *ent;
416
417     ent = readdir( (DIR *)dir );
418     if( ent == NULL )
419         return NULL;
420
421     return FromLocale( ent->d_name );
422 }
423
424
425 static int utf8_statEx( const char *filename, void *buf,
426                         vlc_bool_t deref )
427 {
428 #if !(defined (WIN32) || defined (UNDER_CE))
429 # ifdef HAVE_SYS_STAT_H
430     const char *local_name = ToLocale( filename );
431
432     if( local_name != NULL )
433     {
434         int res = deref ? stat( local_name, (struct stat *)buf )
435                        : lstat( local_name, (struct stat *)buf );
436         LocaleFree( local_name );
437         return res;
438     }
439     errno = ENOENT;
440 # endif
441     return -1;
442 #else
443     wchar_t wpath[MAX_PATH + 1];
444
445     if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH ) )
446     {
447         errno = ENOENT;
448         return -1;
449     }
450     wpath[MAX_PATH] = L'\0';
451
452     /* struct _stat is just a silly Microsoft alias for struct stat */
453     return _wstat( wpath, (struct _stat *)buf );
454 #endif
455 }
456
457
458 int utf8_stat( const char *filename, void *buf)
459 {
460     return utf8_statEx( filename, buf, VLC_TRUE );
461 }
462
463 int utf8_lstat( const char *filename, void *buf)
464 {
465     return utf8_statEx( filename, buf, VLC_FALSE );
466 }
467
468 /*****************************************************************************
469  * utf8_*printf: *printf with conversion from UTF-8 to local encoding
470  *****************************************************************************/
471 static int utf8_vasprintf( char **str, const char *fmt, va_list ap )
472 {
473         char *utf8;
474         int res = vasprintf( &utf8, fmt, ap );
475         if( res == -1 )
476                 return -1;
477
478         *str = ToLocaleDup( utf8 );
479         free( utf8 );
480         return res;
481 }
482
483 static int utf8_vfprintf( FILE *stream, const char *fmt, va_list ap )
484 {
485         char *str;
486         int res = utf8_vasprintf( &str, fmt, ap );
487         if( res == -1 )
488                 return -1;
489
490         fputs( str, stream );
491         free( str );
492         return res;
493 }
494
495 int utf8_fprintf( FILE *stream, const char *fmt, ... )
496 {
497         va_list ap;
498         int res;
499
500         va_start( ap, fmt );
501         res = utf8_vfprintf( stream, fmt, ap );
502         va_end( ap );
503         return res;
504 }
505
506 /*****************************************************************************
507  * EnsureUTF8: replaces invalid/overlong UTF-8 sequences with question marks
508  *****************************************************************************
509  * Not Todo : convert Latin1 to UTF-8 on the flu
510  * It is not possible given UTF-8 needs more space
511  * Returns str if it was valid UTF-8, NULL if not.
512  *****************************************************************************/
513 #define isutf8cont( c ) (((c) >= 0x80) && ((c) <= 0xBF)) 
514 char *EnsureUTF8( char *str )
515 {
516     unsigned char *ptr, c;
517
518     ptr = (unsigned char *)str;
519     while( (c = *ptr) != '\0' )
520     {
521         /* US-ASCII, 1 byte */
522         if( ( ( c >= 0x20 ) && ( c <= 0x7F ) )
523          || ( c == 0x09 ) || ( c == 0x0A ) || ( c == 0x0D ) )
524         {
525             ptr++; /* OK */
526         }
527         else
528         /* 2 bytes */
529         if( ( c >= 0xC2 ) && ( c <= 0xDF ) )
530         {
531             c = ptr[1];
532             if( isutf8cont( c ) )
533                 ptr += 2; /* OK */
534             else
535             {
536                 *ptr++ = '?'; /* invalid */
537                 str = NULL;
538             }
539         }
540         else
541         /* 3 bytes */
542         if( c == 0xE0 )
543         {
544             c = ptr[1];
545             if( ( c >= 0xA0 ) && ( c <= 0xBF ) )
546             {
547                 c = ptr[2];
548                 if( isutf8cont( c ) )
549                     ptr += 3; /* OK */
550                 else
551                 {
552                     *ptr++ = '?';
553                     str = NULL;
554                 }
555             }
556             else
557             {
558                 *ptr++ = '?';
559                 str = NULL;
560             }
561         }
562         else
563         if( ( ( c >= 0xE1 ) && ( c <= 0xEC ) ) || ( c == 0xEC )
564          || ( c == 0xEE ) || ( c == 0xEF ) )
565         {
566             c = ptr[1];
567             if( isutf8cont( c ) )
568             {
569                 c = ptr[2];
570                 if( isutf8cont( c ) )
571                     ptr += 3; /* OK */
572                 else
573                 {
574                     *ptr++ = '?';
575                     str = NULL;
576                 }
577             }
578             else
579             {
580                 *ptr++ = '?';
581                 str = NULL;
582             }
583         }
584         else
585         if( c == 0xED )
586         {
587             c = ptr[1];
588             if( ( c >= 0x80 ) && ( c <= 0x9F ) )
589             {
590                 c = ptr[2];
591                 if( isutf8cont( c ) )
592                     ptr += 3; /* OK */
593                 else
594                 {
595                     *ptr++ = '?';
596                     str = NULL;
597                 }
598             }
599             else
600             {
601                 *ptr++ = '?';
602                 str = NULL;
603             }
604         }
605         else
606         /* 4 bytes */
607         if( c == 0xF0 )
608         {
609             c = ptr[1];
610             if( ( c >= 0x90 ) && ( c <= 0xBF ) )
611             {
612                 c = ptr[2];
613                 if( isutf8cont( c ) )
614                 {
615                     c = ptr[3];
616                     if( isutf8cont( c ) )
617                         ptr += 4; /* OK */
618                     else
619                     {
620                         *ptr++ = '?';
621                         str = NULL;
622                     }
623                 }
624                 else
625                 {
626                     *ptr++ = '?';
627                     str = NULL;
628                 }
629             }
630             else
631             {
632                 *ptr++ = '?';
633                 str = NULL;
634             }
635         }
636         else
637         if( ( c >= 0xF1 ) && ( c <= 0xF3 ) )
638         {
639             c = ptr[1];
640             if( isutf8cont( c ) )
641             {
642                 c = ptr[2];
643                 if( isutf8cont( c ) )
644                 {
645                     c = ptr[3];
646                     if( isutf8cont( c ) )
647                         ptr += 4; /* OK */
648                     else
649                     {
650                         *ptr++ = '?';
651                         str = NULL;
652                     }
653                 }
654                 else
655                 {
656                     *ptr++ = '?';
657                     str = NULL;
658                 }
659             }
660             else
661             {
662                 *ptr++ = '?';
663                 str = NULL;
664             }
665         }
666         else
667         if( c == 0xF4 )
668         {
669             c = ptr[1];
670             if( ( c >= 0x80 ) && ( c <= 0x8F ) )
671             {
672                 c = ptr[2];
673                 if( isutf8cont( c ) )
674                 {
675                     c = ptr[3];
676                     if( isutf8cont( c ) )
677                         ptr += 4; /* OK */
678                     else
679                     {
680                         *ptr++ = '?';
681                         str = NULL;
682                     }
683                 }
684                 else
685                 {
686                     *ptr++ = '?';
687                     str = NULL;
688                 }
689             }
690             else
691             {
692                 *ptr++ = '?';
693                 str = NULL;
694             }
695         }
696         else
697         {
698             *ptr++ = '?';
699             str = NULL;
700         }
701     }
702
703     return str;
704 }
705
706 /**********************************************************************
707  * UTF32toUTF8: converts an array from UTF-32 to UTF-8
708  *********************************************************************/
709 char *UTF32toUTF8( const wchar_t *src, size_t len, size_t *newlen )
710 {
711     char *res, *out;
712
713     /* allocate memory */
714     out = res = (char *)malloc( 6 * len );
715     if( res == NULL )
716         return NULL;
717
718     while( len > 0 )
719     {
720         uint32_t uv = *src++;
721         len--;
722
723         if( uv < 0x80 )
724         {
725             *out++ = uv;
726             continue;
727         }
728         else
729         if( uv < 0x800 )
730         {
731             *out++ = (( uv >>  6)         | 0xc0);
732             *out++ = (( uv        & 0x3f) | 0x80);
733             continue;
734         }
735         else
736         if( uv < 0x10000 )
737         {
738             *out++ = (( uv >> 12)         | 0xe0);
739             *out++ = (((uv >>  6) & 0x3f) | 0x80);
740             *out++ = (( uv        & 0x3f) | 0x80);
741             continue;
742         }
743         else
744         {
745             *out++ = (( uv >> 18)         | 0xf0);
746             *out++ = (((uv >> 12) & 0x3f) | 0x80);
747             *out++ = (((uv >>  6) & 0x3f) | 0x80);
748             *out++ = (( uv        & 0x3f) | 0x80);
749             continue;
750         }
751     }
752     len = out - res;
753     res = realloc( res, len );
754     if( newlen != NULL )
755         *newlen = len;
756     return res;
757 }
758
759 /**********************************************************************
760  * FromUTF32: converts an UTF-32 string to UTF-8
761  **********************************************************************
762  * The result must be free()'d. NULL on error.
763  *********************************************************************/
764 char *FromUTF32( const wchar_t *src )
765 {
766     size_t len;
767     const wchar_t *in;
768
769     /* determine the size of the string */
770     for( len = 1, in = src; GetWBE( in ); len++ )
771         in++;
772
773     return UTF32toUTF8( src, len, NULL );
774 }