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