]> git.sesse.net Git - vlc/blob - src/text/unicode.c
Don't include config.h from the headers - refs #297.
[vlc] / src / text / unicode.c
1 /*****************************************************************************
2  * unicode.c: Unicode <-> locale functions
3  *****************************************************************************
4  * Copyright (C) 2005-2006 the VideoLAN team
5  * Copyright © 2005-2006 Rémi Denis-Courmont
6  * $Id$
7  *
8  * Authors: Rémi Denis-Courmont <rem # videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc/vlc.h>
33 #include <vlc_charset.h>
34 #include "libvlc.h" /* utf8_mkdir */
35
36 #include <assert.h>
37
38 #include <stdio.h>
39 #include <stdarg.h>
40 #include <stdlib.h>
41 #include <errno.h>
42 #include <sys/types.h>
43 #ifdef HAVE_DIRENT_H
44 #  include <dirent.h>
45 #endif
46 #ifdef UNDER_CE
47 #  include <tchar.h>
48 #endif
49 #ifdef HAVE_SYS_STAT_H
50 # include <sys/stat.h>
51 #endif
52 #ifdef HAVE_FCNTL_H
53 # include <fcntl.h>
54 #endif
55 #ifdef WIN32
56 # include <io.h>
57 #else
58 # include <unistd.h>
59 #endif
60
61 #ifndef HAVE_LSTAT
62 # define lstat( a, b ) stat(a, b)
63 #endif
64
65 #ifdef __APPLE__
66 /* Define this if the OS always use UTF-8 internally */
67 # define ASSUME_UTF8 1
68 #endif
69
70 #if defined (ASSUME_UTF8)
71 /* Cool */
72 #elif defined (WIN32) || defined (UNDER_CE)
73 # define USE_MB2MB 1
74 #elif defined (HAVE_ICONV)
75 # define USE_ICONV 1
76 #else
77 # error No UTF8 charset conversion implemented on this platform!
78 #endif
79
80 #if defined (USE_ICONV)
81 static char charset[sizeof ("CSISO11SWEDISHFORNAMES//translit")] = "";
82
83 static void find_charset_once (void)
84 {
85     char *psz_charset;
86     if (vlc_current_charset (&psz_charset)
87      || (psz_charset == NULL)
88      || (strcmp (psz_charset, "ASCII") == 0)
89      || ((size_t)snprintf (charset, sizeof (charset), "%s//translit",
90                            psz_charset) >= sizeof (charset)))
91         strcpy (charset, "UTF-8");
92
93     free (psz_charset);
94 }
95
96 static int find_charset (void)
97 {
98     static pthread_once_t once = PTHREAD_ONCE_INIT;
99     pthread_once (&once, find_charset_once);
100     return !strcmp (charset, "UTF-8");
101 }
102 #endif
103
104
105 static char *locale_fast (const char *string, vlc_bool_t from)
106 {
107 #if defined (USE_ICONV)
108     if (find_charset ())
109         return (char *)string;
110
111     vlc_iconv_t hd = vlc_iconv_open (from ? "UTF-8" : charset,
112                                      from ? charset : "UTF-8");
113     if (hd == (vlc_iconv_t)(-1))
114         return strdup (string); /* Uho! */
115
116     const char *iptr = string;
117     size_t inb = strlen (string);
118     size_t outb = inb * 6 + 1;
119     char output[outb], *optr = output;
120
121     if (string == NULL)
122         return NULL;
123
124     while (vlc_iconv (hd, &iptr, &inb, &optr, &outb) == (size_t)(-1))
125     {
126         *optr++ = '?';
127         outb--;
128         iptr++;
129         inb--;
130         vlc_iconv (hd, NULL, NULL, NULL, NULL);
131     }
132     *optr = '\0';
133     vlc_iconv_close (hd);
134
135     assert (inb == 0);
136     assert (*iptr == '\0');
137     assert (*optr == '\0');
138     assert (strlen (output) == (size_t)(optr - output));
139     return strdup (output);
140 #elif defined (USE_MB2MB)
141     char *out;
142     int len;
143
144     if (string == NULL)
145         return NULL;
146
147     len = 1 + MultiByteToWideChar (from ? CP_ACP : CP_UTF8,
148                                    0, string, -1, NULL, 0);
149     wchar_t wide[len];
150
151     MultiByteToWideChar (from ? CP_ACP : CP_UTF8, 0, string, -1, wide, len);
152     len = 1 + WideCharToMultiByte (from ? CP_UTF8 : CP_ACP, 0, wide, -1, NULL, 0, NULL, NULL);
153     out = malloc (len);
154     if (out == NULL)
155         return NULL;
156
157     WideCharToMultiByte (from ? CP_UTF8 : CP_ACP, 0, wide, -1, out, len, NULL, NULL);
158     return out;
159 #else
160     return (char *)string;
161 #endif
162 }
163
164
165 static inline char *locale_dup (const char *string, vlc_bool_t from)
166 {
167 #if defined (USE_ICONV)
168     if (find_charset ())
169         return strdup (string);
170     return locale_fast (string, from);
171 #elif defined (USE_MB2MB)
172     return locale_fast (string, from);
173 #else
174     return strdup (string);
175 #endif
176 }
177
178
179 void LocaleFree (const char *str)
180 {
181 #if defined (USE_ICONV)
182     if (!find_charset ())
183         free ((char *)str);
184 #elif defined (USE_MB2MB)
185     free ((char *)str);
186 #endif
187 }
188
189
190 /**
191  * FromLocale: converts a locale string to UTF-8
192  *
193  * @param locale nul-terminated string to be converted
194  *
195  * @return a nul-terminated UTF-8 string, or NULL in case of error.
196  * To avoid memory leak, you have to pass the result to LocaleFree()
197  * when it is no longer needed.
198  */
199 char *FromLocale (const char *locale)
200 {
201     return locale_fast (locale, VLC_TRUE);
202 }
203
204 char *FromLocaleDup (const char *locale)
205 {
206     return locale_dup (locale, VLC_TRUE);
207 }
208
209
210 /**
211  * ToLocale: converts a UTF-8 string to local system encoding.
212  *
213  * @param utf8 nul-terminated string to be converted
214  *
215  * @return a nul-terminated string, or NULL in case of error.
216  * To avoid memory leak, you have to pass the result to LocaleFree()
217  * when it is no longer needed.
218  */
219 char *ToLocale (const char *utf8)
220 {
221     return locale_fast (utf8, VLC_FALSE);
222 }
223
224
225 static char *ToLocaleDup (const char *utf8)
226 {
227     return locale_dup (utf8, VLC_FALSE);
228 }
229
230
231 /**
232  * utf8_open: open() wrapper for UTF-8 filenames
233  */
234 int utf8_open (const char *filename, int flags, mode_t mode)
235 {
236 #if defined (WIN32) || defined (UNDER_CE)
237     if (GetVersion() < 0x80000000)
238     {
239         /* for Windows NT and above */
240         wchar_t wpath[MAX_PATH + 1];
241
242         if (!MultiByteToWideChar (CP_UTF8, 0, filename, -1, wpath, MAX_PATH))
243         {
244             errno = ENOENT;
245             return -1;
246         }
247         wpath[MAX_PATH] = L'\0';
248
249         /*
250          * open() cannot open files with non-“ANSI” characters on Windows.
251          * We use _wopen() instead. Same thing for mkdir() and stat().
252          */
253         return _wopen (wpath, flags, mode);
254     }
255 #endif
256     const char *local_name = ToLocale (filename);
257
258     if (local_name == NULL)
259     {
260         errno = ENOENT;
261         return -1;
262     }
263
264     int fd = open (local_name, flags, mode);
265     LocaleFree (local_name);
266     return fd;
267 }
268
269 /**
270  * utf8_fopen: fopen() wrapper for UTF-8 filenames
271  */
272 FILE *utf8_fopen (const char *filename, const char *mode)
273 {
274     int rwflags = 0, oflags = 0;
275     vlc_bool_t append = VLC_FALSE;
276
277     for (const char *ptr = mode; *ptr; ptr++)
278     {
279         switch (*ptr)
280         {
281             case 'r':
282                 rwflags = O_RDONLY;
283                 break;
284
285             case 'a':
286                 rwflags = O_WRONLY;
287                 oflags |= O_CREAT;
288                 append = VLC_TRUE;
289                 break;
290
291             case 'w':
292                 rwflags = O_WRONLY;
293                 oflags |= O_CREAT | O_TRUNC;
294                 break;
295
296             case '+':
297                 rwflags = O_RDWR;
298                 break;
299
300 #ifdef O_TEXT
301             case 't':
302                 oflags |= O_TEXT;
303                 break;
304 #endif
305         }
306     }
307
308     int fd = utf8_open (filename, rwflags | oflags, 0666);
309     if (fd == -1)
310         return NULL;
311
312     if (append && (lseek (fd, 0, SEEK_END) == -1))
313     {
314         close (fd);
315         return NULL;
316     }
317
318     FILE *stream = fdopen (fd, mode);
319     if (stream == NULL)
320         close (fd);
321
322     return stream;
323 }
324
325 /**
326  * utf8_mkdir: Calls mkdir() after conversion of file name to OS locale
327  *
328  * @param dirname a UTF-8 string with the name of the directory that you
329  *        want to create.
330  * @return A 0 return value indicates success. A -1 return value indicates an
331  *        error, and an error code is stored in errno
332  */
333 int utf8_mkdir( const char *dirname, mode_t mode )
334 {
335 #if defined (UNDER_CE) || defined (WIN32)
336     wchar_t wname[MAX_PATH + 1];
337     char mod[MAX_PATH + 1];
338     int i;
339
340     /* Convert '/' into '\' */
341     for( i = 0; *dirname; i++ )
342     {
343         if( i == MAX_PATH )
344             return -1; /* overflow */
345
346         if( *dirname == '/' )
347             mod[i] = '\\';
348         else
349             mod[i] = *dirname;
350         dirname++;
351
352     }
353     mod[i] = 0;
354
355     if( MultiByteToWideChar( CP_UTF8, 0, mod, -1, wname, MAX_PATH ) == 0 )
356     {
357         errno = ENOENT;
358         return -1;
359     }
360     wname[MAX_PATH] = L'\0';
361
362     if( CreateDirectoryW( wname, NULL ) == 0 )
363     {
364         if( GetLastError( ) == ERROR_ALREADY_EXISTS )
365             errno = EEXIST;
366         else
367             errno = ENOENT;
368         return -1;
369     }
370     return 0;
371 #else
372     char *locname = ToLocale( dirname );
373     int res;
374
375     if( locname == NULL )
376     {
377         errno = ENOENT;
378         return -1;
379     }
380     res = mkdir( locname, mode );
381
382     LocaleFree( locname );
383     return res;
384 #endif
385 }
386
387 /**
388  * utf8_opendir: wrapper that converts dirname to the locale in use by the OS
389  *
390  * @param dirname UTF-8 representation of the directory name
391  *
392  * @return a pointer to the DIR struct. Release with closedir().
393  */
394 DIR *utf8_opendir( const char *dirname )
395 {
396 #ifdef WIN32
397     wchar_t wname[MAX_PATH + 1];
398
399     if (MultiByteToWideChar (CP_UTF8, 0, dirname, -1, wname, MAX_PATH))
400     {
401         wname[MAX_PATH] = L'\0';
402         return (DIR *)vlc_wopendir (wname);
403     }
404 #else
405     const char *local_name = ToLocale( dirname );
406
407     if( local_name != NULL )
408     {
409         DIR *dir = opendir( local_name );
410         LocaleFree( local_name );
411         return dir;
412     }
413 #endif
414
415     errno = ENOENT;
416     return NULL;
417 }
418
419 /**
420  * utf8_readdir: a readdir wrapper that returns the name of the next entry
421  *     in the directory as a UTF-8 string.
422  *
423  * @param dir The directory that is being read
424  *
425  * @return a UTF-8 string of the directory entry. Use free() to free this memory.
426  */
427 char *utf8_readdir( DIR *dir )
428 {
429 #ifdef WIN32
430     struct _wdirent *ent = vlc_wreaddir (dir);
431     if (ent == NULL)
432         return NULL;
433
434     return FromWide (ent->d_name);
435 #else
436     struct dirent *ent;
437
438     ent = readdir( (DIR *)dir );
439     if( ent == NULL )
440         return NULL;
441
442     return vlc_fix_readdir( ent->d_name );
443 #endif
444 }
445
446 static int dummy_select( const char *str )
447 {
448     (void)str;
449     return 1;
450 }
451
452 int utf8_loaddir( DIR *dir, char ***namelist,
453                   int (*select)( const char * ),
454                   int (*compar)( const char **, const char ** ) )
455 {
456     if( select == NULL )
457         select = dummy_select;
458
459     if( dir == NULL )
460         return -1;
461     else
462     {
463         char **tab = NULL;
464         char *entry;
465         unsigned num = 0;
466
467         rewinddir( dir );
468
469         while( ( entry = utf8_readdir( dir ) ) != NULL )
470         {
471             char **newtab;
472
473             if( !select( entry ) )
474             {
475                 free( entry );
476                 continue;
477             }
478
479             newtab = realloc( tab, sizeof( char * ) * (num + 1) );
480             if( newtab == NULL )
481             {
482                 free( entry );
483                 goto error;
484             }
485             tab = newtab;
486             tab[num++] = entry;
487         }
488
489         if( compar != NULL )
490             qsort( tab, num, sizeof( tab[0] ),
491                    (int (*)( const void *, const void *))compar );
492
493         *namelist = tab;
494         return num;
495
496     error:{
497         unsigned i;
498
499         for( i = 0; i < num; i++ )
500             free( tab[i] );
501         if( tab != NULL )
502             free( tab );
503         }
504     }
505     return -1;
506 }
507
508 int utf8_scandir( const char *dirname, char ***namelist,
509                   int (*select)( const char * ),
510                   int (*compar)( const char **, const char ** ) )
511 {
512     DIR *dir = utf8_opendir (dirname);
513     int val = -1;
514
515     if (dir != NULL)
516     {
517         val = utf8_loaddir (dir, namelist, select, compar);
518         closedir (dir);
519     }
520     return val;
521 }
522
523 static int utf8_statEx( const char *filename, struct stat *buf,
524                         vlc_bool_t deref )
525 {
526 #if defined (WIN32) || defined (UNDER_CE)
527     /* retrieve Windows OS version */
528     if( GetVersion() < 0x80000000 )
529     {
530         /* for Windows NT and above */
531         wchar_t wpath[MAX_PATH + 1];
532
533         if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH ) )
534         {
535             errno = ENOENT;
536             return -1;
537         }
538         wpath[MAX_PATH] = L'\0';
539
540         return _wstati64( wpath, buf );
541     }
542 #endif
543 #ifdef HAVE_SYS_STAT_H
544     const char *local_name = ToLocale( filename );
545
546     if( local_name != NULL )
547     {
548         int res = deref ? stat( local_name, buf )
549                        : lstat( local_name, buf );
550         LocaleFree( local_name );
551         return res;
552     }
553     errno = ENOENT;
554 #endif
555     return -1;
556 }
557
558
559 int utf8_stat( const char *filename, struct stat *buf)
560 {
561     return utf8_statEx( filename, buf, VLC_TRUE );
562 }
563
564 int utf8_lstat( const char *filename, struct stat *buf)
565 {
566     return utf8_statEx( filename, buf, VLC_FALSE );
567 }
568
569 /**
570  * utf8_unlink: Calls unlink() after conversion of file name to OS locale
571  *
572  * @param filename a UTF-8 string with the name of the file you want to delete.
573  * @return A 0 return value indicates success. A -1 return value indicates an
574  *        error, and an error code is stored in errno
575  */
576 int utf8_unlink( const char *filename )
577 {
578 #if defined (WIN32) || defined (UNDER_CE)
579     if( GetVersion() < 0x80000000 )
580     {
581         /* for Windows NT and above */
582         wchar_t wpath[MAX_PATH + 1];
583
584         if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH ) )
585         {
586             errno = ENOENT;
587             return -1;
588         }
589         wpath[MAX_PATH] = L'\0';
590
591         /*
592          * unlink() cannot open files with non-“ANSI” characters on Windows.
593          * We use _wunlink() instead.
594          */
595         return _wunlink( wpath );
596     }
597 #endif
598     const char *local_name = ToLocale( filename );
599
600     if( local_name == NULL )
601     {
602         errno = ENOENT;
603         return -1;
604     }
605
606     int ret = unlink( local_name );
607     LocaleFree( local_name );
608     return ret;
609 }
610
611
612
613 /**
614  * utf8_*printf: *printf with conversion from UTF-8 to local encoding
615  */
616 static int utf8_vasprintf( char **str, const char *fmt, va_list ap )
617 {
618     char *utf8;
619     int res = vasprintf( &utf8, fmt, ap );
620     if( res == -1 )
621         return -1;
622
623     *str = ToLocaleDup( utf8 );
624     free( utf8 );
625     return res;
626 }
627
628 int utf8_vfprintf( FILE *stream, const char *fmt, va_list ap )
629 {
630     char *str;
631     int res = utf8_vasprintf( &str, fmt, ap );
632     if( res == -1 )
633         return -1;
634
635     fputs( str, stream );
636     free( str );
637     return res;
638 }
639
640 int utf8_fprintf( FILE *stream, const char *fmt, ... )
641 {
642     va_list ap;
643     int res;
644
645     va_start( ap, fmt );
646     res = utf8_vfprintf( stream, fmt, ap );
647     va_end( ap );
648     return res;
649 }
650
651
652 static char *CheckUTF8( char *str, char rep )
653 {
654     uint8_t *ptr = (uint8_t *)str;
655     assert (str != NULL);
656
657     for (;;)
658     {
659         uint8_t c = ptr[0];
660         int charlen = -1;
661
662         if (c == '\0')
663             break;
664
665         for (int i = 0; i < 7; i++)
666             if ((c >> (7 - i)) == ((0xff >> (7 - i)) ^ 1))
667             {
668                 charlen = i;
669                 break;
670             }
671
672         switch (charlen)
673         {
674             case 0: // 7-bit ASCII character -> OK
675                 ptr++;
676                 continue;
677
678             case -1: // 1111111x -> error
679             case 1: // continuation byte -> error
680                 goto error;
681         }
682
683         assert (charlen >= 2);
684
685         uint32_t cp = c & ~((0xff >> (7 - charlen)) << (7 - charlen));
686         for (int i = 1; i < charlen; i++)
687         {
688             assert (cp < (1 << 26));
689             c = ptr[i];
690
691             if ((c == '\0') // unexpected end of string
692              || ((c >> 6) != 2)) // not a continuation byte
693                 goto error;
694
695             cp = (cp << 6) | (ptr[i] & 0x3f);
696         }
697
698         if (cp < 128) // overlong (special case for ASCII)
699             goto error;
700         if (cp < (1u << (5 * charlen - 3))) // overlong
701             goto error;
702
703         ptr += charlen;
704         continue;
705
706     error:
707         if (rep == 0)
708             return NULL;
709         *ptr++ = rep;
710         str = NULL;
711     }
712
713     return str;
714 }
715
716 /**
717  * EnsureUTF8: replaces invalid/overlong UTF-8 sequences with question marks
718  * Note that it is not possible to convert from Latin-1 to UTF-8 on the fly,
719  * so we don't try that, even though it would be less disruptive.
720  *
721  * @return str if it was valid UTF-8, NULL if not.
722  */
723 char *EnsureUTF8( char *str )
724 {
725     return CheckUTF8( str, '?' );
726 }
727
728
729 /**
730  * IsUTF8: checks whether a string is a valid UTF-8 byte sequence.
731  *
732  * @param str nul-terminated string to be checked
733  *
734  * @return str if it was valid UTF-8, NULL if not.
735  */
736 const char *IsUTF8( const char *str )
737 {
738     return CheckUTF8( (char *)str, 0 );
739 }