]> git.sesse.net Git - vlc/blob - src/misc/unicode.c
- mkdir Unicode wrapper (refs #543)
[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 #ifdef HAVE_ASSERT
31 # include <assert.h>
32 #else
33 # define assert( c ) ((void)0)
34 #endif
35
36 #include <stdio.h>
37 #include <errno.h>
38 #include <sys/types.h>
39 #include <dirent.h>
40 #ifdef HAVE_SYS_STAT_H
41 # include <sys/stat.h>
42 #endif
43 #ifndef HAVE_LSTAT
44 # define lstat( a, b ) stat(a, b)
45 #endif
46
47 #ifdef __APPLE__
48 /* Define this if the OS always use UTF-8 internally */
49 # define ASSUME_UTF8 1
50 #endif
51
52 #if !(defined (WIN32) && defined (ASSUME_UTF8))
53 # define USE_ICONV 1
54 #endif
55
56 #if defined (USE_ICONV) && !defined (HAVE_ICONV)
57 # error No UTF8 charset conversion implemented on this platform!
58 #endif
59
60
61
62 #ifdef USE_ICONV
63 static struct {
64     vlc_iconv_t hd;
65     vlc_mutex_t lock;
66 } from_locale, to_locale;
67 #endif
68
69 void LocaleInit( vlc_object_t *p_this )
70 {
71 #ifdef USE_ICONV
72     char *psz_charset;
73
74     if( vlc_current_charset( &psz_charset ) )
75         /* UTF-8 */
76         from_locale.hd = to_locale.hd = (vlc_iconv_t)(-1);
77     else
78     {
79         /* not UTF-8 */
80         char *psz_conv = psz_charset;
81
82         /*
83          * Still allow non-ASCII characters when the locale is not set.
84          * Western Europeans are being favored for historical reasons.
85          */
86         psz_conv = strcmp( psz_charset, "ASCII" )
87                 ? psz_charset : "ISO-8859-1";
88
89         vlc_mutex_init( p_this, &from_locale.lock );
90         vlc_mutex_init( p_this, &to_locale.lock );
91         from_locale.hd = vlc_iconv_open( "UTF-8", psz_charset );
92         to_locale.hd = vlc_iconv_open( psz_charset, "UTF-8" );
93     }
94
95     free( psz_charset );
96
97     assert( (from_locale.hd == (vlc_iconv_t)(-1))
98             == (to_locale.hd == (vlc_iconv_t)(-1)) );
99 #else
100     (void)p_this;
101 #endif
102 }
103
104 void LocaleDeinit( void )
105 {
106 #ifdef USE_ICONV
107     if( to_locale.hd != (vlc_iconv_t)(-1) )
108     {
109         vlc_iconv_close( to_locale.hd );
110         vlc_mutex_destroy( &to_locale.lock );
111     }
112
113     if( from_locale.hd != (vlc_iconv_t)(-1) )
114     {
115         vlc_iconv_close( from_locale.hd );
116         vlc_mutex_destroy( &from_locale.lock );
117     }
118 #endif
119 }
120
121 #ifdef WIN32
122 static char *MB2MB( const char *string, UINT fromCP, UINT toCP )
123 {
124     char *out;
125     int ilen = strlen( string ), olen = (4 / sizeof (wchar_t)) * ilen + 1;
126     wchar_t wide[olen];
127
128     ilen = MultiByteToWideChar( fromCP, 0, string, ilen + 1, wide, olen );
129     if( ilen == 0 )
130         return NULL;
131
132     olen = 4 * ilen + 1;
133     out = malloc( olen );
134
135     olen = WideCharToMultiByte( toCP, 0, wide, ilen, out, olen, NULL, NULL );
136     if( olen == 0 )
137     {
138         free( out );
139         return NULL;
140     }
141     return realloc( out, olen );
142 }
143 #endif
144
145 /*****************************************************************************
146  * FromLocale: converts a locale string to UTF-8
147  *****************************************************************************/
148 char *FromLocale( const char *locale )
149 {
150     if( locale == NULL )
151         return NULL;
152
153 #ifndef WIN32
154 # ifdef USE_ICONV
155     if( from_locale.hd != (vlc_iconv_t)(-1) )
156     {
157         char *iptr = (char *)locale, *output, *optr;
158         size_t inb, outb;
159
160         /*
161          * We are not allowed to modify the locale pointer, even if we cast it
162          * to non-const.
163          */
164         inb = strlen( locale );
165         /* FIXME: I'm not sure about the value for the multiplication
166          * (for western people, multiplication by 3 (Latin9) is needed).
167          * While UTF-8 could reach 6 bytes, no existing code point exceeds
168          * 4 bytes. */
169         outb = inb * 4 + 1;
170
171         optr = output = malloc( outb );
172
173         vlc_mutex_lock( &from_locale.lock );
174         vlc_iconv( from_locale.hd, NULL, NULL, NULL, NULL );
175
176         while( vlc_iconv( from_locale.hd, &iptr, &inb, &optr, &outb )
177                == (size_t)-1 )
178         {
179             *optr++ = '?';
180             outb--;
181             iptr++;
182             inb--;
183             vlc_iconv( from_locale.hd, NULL, NULL, NULL, NULL );
184         }
185         vlc_mutex_unlock( &from_locale.lock );
186
187         assert (inb == 0);
188         assert (*iptr == '\0');
189         assert (*optr == '\0');
190         assert (strlen( output ) == (size_t)(optr - output));
191         return realloc( output, optr - output + 1 );
192     }
193 # endif /* USE_ICONV */
194     return (char *)locale;
195 #else /* WIN32 */
196     return MB2MB( locale, CP_ACP, CP_UTF8 );
197 #endif
198 }
199
200 char *FromLocaleDup( const char *locale )
201 {
202 #if defined (ASSUME_UTF8)
203     return strdup( locale );
204 #else
205 # ifdef USE_ICONV
206     if (from_locale.hd == (vlc_iconv_t)(-1))
207         return strdup( locale );
208 # endif
209     return FromLocale( locale );
210 #endif
211 }
212
213
214 /*****************************************************************************
215  * ToLocale: converts an UTF-8 string to locale
216  *****************************************************************************/
217 char *ToLocale( const char *utf8 )
218 {
219     if( utf8 == NULL )
220         return NULL;
221
222 #ifndef WIN32
223 # ifdef USE_ICONV
224     if( to_locale.hd != (vlc_iconv_t)(-1) )
225     {
226         char *iptr = (char *)utf8, *output, *optr;
227         size_t inb, outb;
228
229         /*
230         * We are not allowed to modify the locale pointer, even if we cast it
231         * to non-const.
232         */
233         inb = strlen( utf8 );
234         /* FIXME: I'm not sure about the value for the multiplication
235         * (for western people, multiplication is not needed) */
236         outb = inb * 2 + 1;
237
238         optr = output = malloc( outb );
239         vlc_mutex_lock( &to_locale.lock );
240         vlc_iconv( to_locale.hd, NULL, NULL, NULL, NULL );
241
242         while( vlc_iconv( to_locale.hd, &iptr, &inb, &optr, &outb )
243                == (size_t)-1 )
244         {
245             *optr++ = '?'; /* should not happen, and yes, it sucks */
246             outb--;
247             iptr++;
248             inb--;
249             vlc_iconv( to_locale.hd, NULL, NULL, NULL, NULL );
250         }
251         vlc_mutex_unlock( &to_locale.lock );
252
253         assert (inb == 0);
254         assert (*iptr == '\0');
255         assert (*optr == '\0');
256         assert (strlen( output ) == (size_t)(optr - output));
257         return realloc( output, optr - output + 1 );
258     }
259 # endif /* USE_ICONV */
260     return (char *)utf8;
261 #else /* WIN32 */
262     return MB2MB( utf8, CP_UTF8, CP_ACP );
263 #endif
264 }
265
266 void LocaleFree( const char *str )
267 {
268 #ifdef USE_ICONV
269     if( to_locale.hd == (vlc_iconv_t)(-1) )
270         return;
271 #endif
272
273 #ifndef ASSUME_UTF8
274     if( str != NULL )
275         free( (char *)str );
276 #endif
277 }
278
279 /*****************************************************************************
280  * utf8_fopen: Calls fopen() after conversion of file name to OS locale
281  *****************************************************************************/
282 FILE *utf8_fopen( const char *filename, const char *mode )
283 {
284 #if !defined WIN32 /*|| !defined UNICODE*/
285     const char *local_name = ToLocale( filename );
286
287     if( local_name != NULL )
288     {
289         FILE *stream = fopen( local_name, mode );
290         LocaleFree( local_name );
291         return stream;
292     }
293     else
294         errno = ENOENT;
295     return NULL;
296 #else
297     wchar_t wpath[MAX_PATH];
298     wchar_t wmode[4];
299
300     if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH - 1)
301      || !MultiByteToWideChar( CP_ACP, 0, mode, -1, wmode, 3 ) )
302     {
303         errno = ENOENT;
304         return NULL;
305     }
306
307     return _wfopen( wpath, wmode );
308 #endif
309 }
310
311 /*****************************************************************************
312  * utf8_mkdir: Calls mkdir() after conversion of file name to OS locale
313  *****************************************************************************/
314 int utf8_mkdir( const char *dirname )
315 {
316 #if defined( UNDER_CE ) || defined( WIN32 )
317     wchar_t wname[MAX_PATH];
318     char mod[MAX_PATH];
319     int i;
320
321     /* Convert '/' into '\' */
322     for( i = 0; *dirname; i++ )
323     {
324         if( i == MAX_PATH )
325             return -1; /* overflow */
326
327         if( *dirname == '/' )
328             mod[i] = '\\';
329         else
330             mod[i] = *dirname;
331         dirname++;
332
333     }
334     mod[i] = 0;
335
336     if( MultiByteToWideChar( CP_UTF8, 0, mod, -1, wname, MAX_PATH ) == 0 )
337     {
338         errno = ENOENT;
339         return -1;
340     }
341
342     if( CreateDirectoryW( wname, NULL ) == 0 )
343     {
344         if( GetLastError( ) == ERROR_ALREADY_EXISTS )
345             errno = EEXIST;
346         errno = ENOENT;
347         return -1;
348     }
349     return 0;
350 #else
351     char *locname = ToLocale( dirname );
352     int res;
353
354     if( locname == NULL )
355     {
356         errno = ENOENT;
357         return -1;
358     }
359     res = mkdir( locname, 0755 );
360
361     LocaleFree( locname );
362     return res;
363 #endif
364 }
365
366
367 void *utf8_opendir( const char *dirname )
368 {
369     const char *local_name = ToLocale( dirname );
370
371     if( local_name != NULL )
372     {
373         DIR *dir = opendir( local_name );
374         LocaleFree( local_name );
375         return dir;
376     }
377     else
378         errno = ENOENT;
379     return NULL;
380 }
381
382 const char *utf8_readdir( void *dir )
383 {
384     struct dirent *ent;
385
386     ent = readdir( (DIR *)dir );
387     if( ent == NULL )
388         return NULL;
389
390     return FromLocale( ent->d_name );
391 }
392
393
394 static int utf8_statEx( const char *filename, void *buf,
395                         vlc_bool_t deref )
396 {
397 #ifdef HAVE_SYS_STAT_H
398     const char *local_name = ToLocale( filename );
399
400     if( local_name != NULL )
401     {
402         int res = deref ? stat( local_name, (struct stat *)buf )
403                        : lstat( local_name, (struct stat *)buf );
404         LocaleFree( local_name );
405         return res;
406     }
407     errno = ENOENT;
408 #endif
409     return -1;
410 }
411
412
413 int utf8_stat( const char *filename, void *buf)
414 {
415     return utf8_statEx( filename, buf, VLC_TRUE );
416 }
417
418 int utf8_lstat( const char *filename, void *buf)
419 {
420     return utf8_statEx( filename, buf, VLC_FALSE );
421 }
422
423 /*****************************************************************************
424  * EnsureUTF8: replaces invalid/overlong UTF-8 sequences with question marks
425  *****************************************************************************
426  * Not Todo : convert Latin1 to UTF-8 on the flu
427  * It is not possible given UTF-8 needs more space
428  *****************************************************************************/
429 #define isutf8cont( c ) (((c) >= 0x80) && ((c) <= 0xBF)) 
430 char *EnsureUTF8( char *str )
431 {
432     unsigned char *ptr, c;
433
434     ptr = (unsigned char *)str;
435     while( (c = *ptr) != '\0' )
436     {
437         /* US-ASCII, 1 byte */
438         if( ( ( c >= 0x20 ) && ( c <= 0x7F ) )
439          || ( c == 0x09 ) || ( c == 0x0A ) || ( c == 0x0D ) )
440         {
441             ptr++; /* OK */
442         }
443         else
444         /* 2 bytes */
445         if( ( c >= 0xC2 ) && ( c <= 0xDF ) )
446         {
447             c = ptr[1];
448             if( isutf8cont( c ) )
449                 ptr += 2; /* OK */
450             else
451                 *ptr++ = '?'; /* invalid */
452         }
453         else
454         /* 3 bytes */
455         if( c == 0xE0 )
456         {
457             c = ptr[1];
458             if( ( c >= 0xA0 ) && ( c <= 0xBF ) )
459             {
460                 c = ptr[2];
461                 if( isutf8cont( c ) )
462                     ptr += 3; /* OK */
463                 else
464                     *ptr++ = '?';
465             }
466             else
467                 *ptr++ = '?';
468         }
469         else
470         if( ( ( c >= 0xE1 ) && ( c <= 0xEC ) ) || ( c == 0xEC )
471          || ( c == 0xEE ) || ( c == 0xEF ) )
472         {
473             c = ptr[1];
474             if( isutf8cont( c ) )
475             {
476                 c = ptr[2];
477                 if( isutf8cont( c ) )
478                     ptr += 3; /* OK */
479                 else
480                     *ptr++ = '?';
481             }
482             else
483                 *ptr++ = '?';
484         }
485         else
486         if( c == 0xED )
487         {
488             c = ptr[1];
489             if( ( c >= 0x80 ) && ( c <= 0x9F ) )
490             {
491                 c = ptr[2];
492                 if( isutf8cont( c ) )
493                     ptr += 3; /* OK */
494                 else
495                     *ptr++ = '?';
496             }
497             else
498                 *ptr++ = '?';
499         }
500         else
501         /* 4 bytes */
502         if( c == 0xF0 )
503         {
504             c = ptr[1];
505             if( ( c >= 0x90 ) && ( c <= 0xBF ) )
506             {
507                 c = ptr[2];
508                 if( isutf8cont( c ) )
509                 {
510                     c = ptr[3];
511                     if( isutf8cont( c ) )
512                         ptr += 4; /* OK */
513                     else
514                         *ptr++ = '?';
515                 }
516                 else
517                     *ptr++ = '?';
518             }
519             else
520                 *ptr++ = '?';
521         }
522         else
523         if( ( c >= 0xF1 ) && ( c <= 0xF3 ) )
524         {
525             c = ptr[1];
526             if( isutf8cont( c ) )
527             {
528                 c = ptr[2];
529                 if( isutf8cont( c ) )
530                 {
531                     c = ptr[3];
532                     if( isutf8cont( c ) )
533                         ptr += 4; /* OK */
534                     else
535                         *ptr++ = '?';
536                 }
537                 else
538                     *ptr++ = '?';
539             }
540             else
541                 *ptr++ = '?';
542         }
543         else
544         if( c == 0xF4 )
545         {
546             c = ptr[1];
547             if( ( c >= 0x80 ) && ( c <= 0x8F ) )
548             {
549                 c = ptr[2];
550                 if( isutf8cont( c ) )
551                 {
552                     c = ptr[3];
553                     if( isutf8cont( c ) )
554                         ptr += 4; /* OK */
555                     else
556                         *ptr++ = '?';
557                 }
558                 else
559                     *ptr++ = '?';
560             }
561             else
562                 *ptr++ = '?';
563         }
564         else
565             *ptr++ = '?';
566     }
567
568     return str;
569 }
570
571 /**********************************************************************
572  * UTF32toUTF8: converts an array from UTF-32 to UTF-8
573  *********************************************************************/
574 char *UTF32toUTF8( const wchar_t *src, size_t len, size_t *newlen )
575 {
576     char *res, *out;
577
578     /* allocate memory */
579     out = res = (char *)malloc( 6 * len );
580     if( res == NULL )
581         return NULL;
582
583     while( len > 0 )
584     {
585         uint32_t uv = *src++;
586         len--;
587
588         if( uv < 0x80 )
589         {
590             *out++ = uv;
591             continue;
592         }
593         else
594         if( uv < 0x800 )
595         {
596             *out++ = (( uv >>  6)         | 0xc0);
597             *out++ = (( uv        & 0x3f) | 0x80);
598             continue;
599         }
600         else
601         if( uv < 0x10000 )
602         {
603             *out++ = (( uv >> 12)         | 0xe0);
604             *out++ = (((uv >>  6) & 0x3f) | 0x80);
605             *out++ = (( uv        & 0x3f) | 0x80);
606             continue;
607         }
608         else
609         {
610             *out++ = (( uv >> 18)         | 0xf0);
611             *out++ = (((uv >> 12) & 0x3f) | 0x80);
612             *out++ = (((uv >>  6) & 0x3f) | 0x80);
613             *out++ = (( uv        & 0x3f) | 0x80);
614             continue;
615         }
616     }
617     len = out - res;
618     res = realloc( res, len );
619     if( newlen != NULL )
620         *newlen = len;
621     return res;
622 }
623
624 /**********************************************************************
625  * FromUTF32: converts an UTF-32 string to UTF-8
626  **********************************************************************
627  * The result must be free()'d. NULL on error.
628  *********************************************************************/
629 char *FromUTF32( const wchar_t *src )
630 {
631     size_t len;
632     const wchar_t *in;
633
634     /* determine the size of the string */
635     for( len = 1, in = src; GetWBE( in ); len++ )
636         in++;
637
638     return UTF32toUTF8( src, len, NULL );
639 }