]> git.sesse.net Git - vlc/blobdiff - src/text/unicode.c
Factorize the localtime_r replacement
[vlc] / src / text / unicode.c
index f28b63d66b1855998146461dbbeeb178c9c59dbc..310b56adf1f3d7297a74199c81e3573ccab4b781 100644 (file)
 /*****************************************************************************
  * Preamble
  *****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
 #include <vlc/vlc.h>
-#include "charset.h"
+#include <vlc_charset.h>
+#include "libvlc.h" /* utf8_mkdir */
 
 #include <assert.h>
 
 # define ASSUME_UTF8 1
 #endif
 
-#ifndef ASSUME_UTF8
-# if defined (HAVE_ICONV)
-/* libiconv is more powerful than Win32 API (it has translit) */
-#  define USE_ICONV 1
-# elif defined (WIN32) || defined (UNDER_CE)
-#  define USE_MB2MB 1
-# else
-#  error No UTF8 charset conversion implemented on this platform!
-# endif
+#if defined (ASSUME_UTF8)
+/* Cool */
+#elif defined (WIN32) || defined (UNDER_CE)
+# define USE_MB2MB 1
+#elif defined (HAVE_ICONV)
+# define USE_ICONV 1
+#else
+# error No UTF8 charset conversion implemented on this platform!
 #endif
 
-typedef struct locale_data_t
-{
 #if defined (USE_ICONV)
-    vlc_iconv_t hd;
-    vlc_mutex_t lock;
-#elif defined (USE_MB2MB)
-    UINT fromCP;
-    UINT toCP;
-#endif
-} locale_data_t;
+static char charset[sizeof ("CSISO11SWEDISHFORNAMES//translit")] = "";
 
-static locale_data_t from_locale, to_locale;
-
-
-void LocaleInit( vlc_object_t *p_this )
+static void find_charset_once (void)
 {
-#if defined USE_ICONV
     char *psz_charset;
-
-    if( vlc_current_charset( &psz_charset ) )
-        /* UTF-8 */
-        from_locale.hd = to_locale.hd = (vlc_iconv_t)(-1);
-    else
-    {
-        /* not UTF-8 */
-        char psz_buf[strlen( psz_charset ) + sizeof( "//translit" )];
-        const char *psz_conv;
-
-        /*
-         * Still allow non-ASCII characters when the locale is not set.
-         * Western Europeans are being favored for historical reasons.
-         */
-        if( strcmp( psz_charset, "ASCII" ) )
-        {
-            sprintf( psz_buf, "%s//translit", psz_charset );
-            psz_conv = psz_buf;
-        }
-        else
-            psz_conv = "ISO-8859-1//translit";
-
-        vlc_mutex_init( p_this, &from_locale.lock );
-        vlc_mutex_init( p_this, &to_locale.lock );
-        from_locale.hd = vlc_iconv_open( "UTF-8", psz_conv );
-        to_locale.hd = vlc_iconv_open( psz_conv, "UTF-8" );
-    }
-
-    free( psz_charset );
-
-    assert( (from_locale.hd == (vlc_iconv_t)(-1))
-            == (to_locale.hd == (vlc_iconv_t)(-1)) );
-
-#elif defined (USE_MB2MB)
-    to_locale.toCP = from_locale.fromCP = CP_ACP;
-    from_locale.toCP = to_locale.fromCP = CP_UTF8;
-#else
-    (void)p_this;
-#endif
+    if (vlc_current_charset (&psz_charset)
+     || (psz_charset == NULL)
+     || (strcmp (psz_charset, "ASCII") == 0)
+     || ((size_t)snprintf (charset, sizeof (charset), "%s//translit",
+                           psz_charset) >= sizeof (charset)))
+        strcpy (charset, "UTF-8");
+
+    free (psz_charset);
 }
 
-void LocaleDeinit( void )
+static int find_charset (void)
 {
-#ifdef USE_ICONV
-    if( to_locale.hd != (vlc_iconv_t)(-1) )
-    {
-        vlc_iconv_close( to_locale.hd );
-        vlc_mutex_destroy( &to_locale.lock );
-    }
-
-    if( from_locale.hd != (vlc_iconv_t)(-1) )
-    {
-        vlc_iconv_close( from_locale.hd );
-        vlc_mutex_destroy( &from_locale.lock );
-    }
-#endif
+    static pthread_once_t once = PTHREAD_ONCE_INIT;
+    pthread_once (&once, find_charset_once);
+    return !strcmp (charset, "UTF-8");
 }
+#endif
+
 
-static char *locale_fast (const char *string, locale_data_t *p)
+static char *locale_fast (const char *string, vlc_bool_t from)
 {
 #if defined (USE_ICONV)
-    vlc_iconv_t hd = p->hd;
+    if (find_charset ())
+        return (char *)string;
 
+    vlc_iconv_t hd = vlc_iconv_open (from ? "UTF-8" : charset,
+                                     from ? charset : "UTF-8");
     if (hd == (vlc_iconv_t)(-1))
-        return (char *)string;
+        return strdup (string); /* Uho! */
 
     const char *iptr = string;
     size_t inb = strlen (string);
@@ -165,9 +121,6 @@ static char *locale_fast (const char *string, locale_data_t *p)
     if (string == NULL)
         return NULL;
 
-    vlc_mutex_lock (&p->lock);
-    vlc_iconv (hd, NULL, NULL, NULL, NULL);
-
     while (vlc_iconv (hd, &iptr, &inb, &optr, &outb) == (size_t)(-1))
     {
         *optr++ = '?';
@@ -176,8 +129,8 @@ static char *locale_fast (const char *string, locale_data_t *p)
         inb--;
         vlc_iconv (hd, NULL, NULL, NULL, NULL);
     }
-    vlc_mutex_unlock (&p->lock);
     *optr = '\0';
+    vlc_iconv_close (hd);
 
     assert (inb == 0);
     assert (*iptr == '\0');
@@ -186,41 +139,40 @@ static char *locale_fast (const char *string, locale_data_t *p)
     return strdup (output);
 #elif defined (USE_MB2MB)
     char *out;
-    wchar_t *wide;
     int len;
 
     if (string == NULL)
         return NULL;
 
-    len = MultiByteToWideChar (p->fromCP, 0, string, -1, NULL, 0);
-    if (len == 0)
-        return NULL;
-
+    len = 1 + MultiByteToWideChar (from ? CP_ACP : CP_UTF8,
+                                   0, string, -1, NULL, 0);
     wchar_t wide[len];
 
-    MultiByteToWideChar (p->fromCP, 0, string, -1, wide, len);
-    len = WideCharToMultiByte (p->toCP, 0, wide, -1, NULL, 0, NULL, NULL);
-    if (len == 0)
-        return NULL;
+    MultiByteToWideChar (from ? CP_ACP : CP_UTF8, 0, string, -1, wide, len);
+    len = 1 + WideCharToMultiByte (from ? CP_UTF8 : CP_ACP, 0, wide, -1, NULL, 0, NULL, NULL);
     out = malloc (len);
+    if (out == NULL)
+        return NULL;
 
-    WideCharToMultiByte (p->toCP, 0, wide, -1, out, len, NULL, NULL);
+    WideCharToMultiByte (from ? CP_UTF8 : CP_ACP, 0, wide, -1, out, len, NULL, NULL);
     return out;
 #else
+    VLC_UNUSED(from);
     return (char *)string;
 #endif
 }
 
 
-static inline char *locale_dup (const char *string, locale_data_t *p)
+static inline char *locale_dup (const char *string, vlc_bool_t from)
 {
 #if defined (USE_ICONV)
-    return (p->hd == (vlc_iconv_t)(-1))
-            ? strdup (string)
-            : locale_fast (string, p);
+    if (find_charset ())
+        return strdup (string);
+    return locale_fast (string, from);
 #elif defined (USE_MB2MB)
-    return locale_fast (string, p);
+    return locale_fast (string, from);
 #else
+    VLC_UNUSED(from);
     return strdup (string);
 #endif
 }
@@ -229,13 +181,12 @@ static inline char *locale_dup (const char *string, locale_data_t *p)
 void LocaleFree (const char *str)
 {
 #if defined (USE_ICONV)
-    assert ((to_locale.hd == (vlc_iconv_t)(-1))
-         == (from_locale.hd == (vlc_iconv_t)(-1)));
-
-    if( to_locale.hd != (vlc_iconv_t)(-1) )
+    if (!find_charset ())
         free ((char *)str);
 #elif defined (USE_MB2MB)
     free ((char *)str);
+#else
+    VLC_UNUSED(str);
 #endif
 }
 
@@ -251,12 +202,12 @@ void LocaleFree (const char *str)
  */
 char *FromLocale (const char *locale)
 {
-    return locale_fast (locale, &from_locale);
+    return locale_fast (locale, VLC_TRUE);
 }
 
 char *FromLocaleDup (const char *locale)
 {
-    return locale_dup (locale, &from_locale);
+    return locale_dup (locale, VLC_TRUE);
 }
 
 
@@ -271,13 +222,13 @@ char *FromLocaleDup (const char *locale)
  */
 char *ToLocale (const char *utf8)
 {
-    return locale_fast (utf8, &to_locale);
+    return locale_fast (utf8, VLC_FALSE);
 }
 
 
 static char *ToLocaleDup (const char *utf8)
 {
-    return locale_dup (utf8, &to_locale);
+    return locale_dup (utf8, VLC_FALSE);
 }
 
 
@@ -349,6 +300,12 @@ FILE *utf8_fopen (const char *filename, const char *mode)
             case '+':
                 rwflags = O_RDWR;
                 break;
+
+#ifdef O_TEXT
+            case 't':
+                oflags |= O_TEXT;
+                break;
+#endif
         }
     }
 
@@ -371,10 +328,17 @@ FILE *utf8_fopen (const char *filename, const char *mode)
 
 /**
  * utf8_mkdir: Calls mkdir() after conversion of file name to OS locale
+ *
+ * @param dirname a UTF-8 string with the name of the directory that you
+ *        want to create.
+ * @return A 0 return value indicates success. A -1 return value indicates an
+ *        error, and an error code is stored in errno
  */
-int utf8_mkdir( const char *dirname )
+int utf8_mkdir( const char *dirname, mode_t mode )
 {
 #if defined (UNDER_CE) || defined (WIN32)
+    VLC_UNUSED( mode );
+
     wchar_t wname[MAX_PATH + 1];
     char mod[MAX_PATH + 1];
     int i;
@@ -419,17 +383,31 @@ int utf8_mkdir( const char *dirname )
         errno = ENOENT;
         return -1;
     }
-    res = mkdir( locname, 0755 );
+    res = mkdir( locname, mode );
 
     LocaleFree( locname );
     return res;
 #endif
 }
 
-
+/**
+ * utf8_opendir: wrapper that converts dirname to the locale in use by the OS
+ *
+ * @param dirname UTF-8 representation of the directory name
+ *
+ * @return a pointer to the DIR struct. Release with closedir().
+ */
 DIR *utf8_opendir( const char *dirname )
 {
-    /* TODO: support for WinNT non-ACP filenames */
+#ifdef WIN32
+    wchar_t wname[MAX_PATH + 1];
+
+    if (MultiByteToWideChar (CP_UTF8, 0, dirname, -1, wname, MAX_PATH))
+    {
+        wname[MAX_PATH] = L'\0';
+        return (DIR *)vlc_wopendir (wname);
+    }
+#else
     const char *local_name = ToLocale( dirname );
 
     if( local_name != NULL )
@@ -438,14 +416,29 @@ DIR *utf8_opendir( const char *dirname )
         LocaleFree( local_name );
         return dir;
     }
-    else
-        errno = ENOENT;
+#endif
+
+    errno = ENOENT;
     return NULL;
 }
 
-
-char *utf8_readdir( void *dir )
+/**
+ * utf8_readdir: a readdir wrapper that returns the name of the next entry
+ *     in the directory as a UTF-8 string.
+ *
+ * @param dir The directory that is being read
+ *
+ * @return a UTF-8 string of the directory entry. Use free() to free this memory.
+ */
+char *utf8_readdir( DIR *dir )
 {
+#ifdef WIN32
+    struct _wdirent *ent = vlc_wreaddir (dir);
+    if (ent == NULL)
+        return NULL;
+
+    return FromWide (ent->d_name);
+#else
     struct dirent *ent;
 
     ent = readdir( (DIR *)dir );
@@ -453,6 +446,7 @@ char *utf8_readdir( void *dir )
         return NULL;
 
     return vlc_fix_readdir( ent->d_name );
+#endif
 }
 
 static int dummy_select( const char *str )
@@ -461,12 +455,10 @@ static int dummy_select( const char *str )
     return 1;
 }
 
-int utf8_scandir( const char *dirname, char ***namelist,
+int utf8_loaddir( DIR *dir, char ***namelist,
                   int (*select)( const char * ),
                   int (*compar)( const char **, const char ** ) )
 {
-    DIR *dir = utf8_opendir( dirname );
-
     if( select == NULL )
         select = dummy_select;
 
@@ -478,6 +470,8 @@ int utf8_scandir( const char *dirname, char ***namelist,
         char *entry;
         unsigned num = 0;
 
+        rewinddir( dir );
+
         while( ( entry = utf8_readdir( dir ) ) != NULL )
         {
             char **newtab;
@@ -497,7 +491,6 @@ int utf8_scandir( const char *dirname, char ***namelist,
             tab = newtab;
             tab[num++] = entry;
         }
-        vlc_closedir_wrapper( dir );
 
         if( compar != NULL )
             qsort( tab, num, sizeof( tab[0] ),
@@ -513,10 +506,25 @@ int utf8_scandir( const char *dirname, char ***namelist,
             free( tab[i] );
         if( tab != NULL )
             free( tab );
-        return -1;}
+        }
     }
+    return -1;
 }
 
+int utf8_scandir( const char *dirname, char ***namelist,
+                  int (*select)( const char * ),
+                  int (*compar)( const char **, const char ** ) )
+{
+    DIR *dir = utf8_opendir (dirname);
+    int val = -1;
+
+    if (dir != NULL)
+    {
+        val = utf8_loaddir (dir, namelist, select, compar);
+        closedir (dir);
+    }
+    return val;
+}
 
 static int utf8_statEx( const char *filename, struct stat *buf,
                         vlc_bool_t deref )
@@ -564,6 +572,50 @@ int utf8_lstat( const char *filename, struct stat *buf)
     return utf8_statEx( filename, buf, VLC_FALSE );
 }
 
+/**
+ * utf8_unlink: Calls unlink() after conversion of file name to OS locale
+ *
+ * @param filename a UTF-8 string with the name of the file you want to delete.
+ * @return A 0 return value indicates success. A -1 return value indicates an
+ *        error, and an error code is stored in errno
+ */
+int utf8_unlink( const char *filename )
+{
+#if defined (WIN32) || defined (UNDER_CE)
+    if( GetVersion() < 0x80000000 )
+    {
+        /* for Windows NT and above */
+        wchar_t wpath[MAX_PATH + 1];
+
+        if( !MultiByteToWideChar( CP_UTF8, 0, filename, -1, wpath, MAX_PATH ) )
+        {
+            errno = ENOENT;
+            return -1;
+        }
+        wpath[MAX_PATH] = L'\0';
+
+        /*
+         * unlink() cannot open files with non-“ANSI” characters on Windows.
+         * We use _wunlink() instead.
+         */
+        return _wunlink( wpath );
+    }
+#endif
+    const char *local_name = ToLocale( filename );
+
+    if( local_name == NULL )
+    {
+        errno = ENOENT;
+        return -1;
+    }
+
+    int ret = unlink( local_name );
+    LocaleFree( local_name );
+    return ret;
+}
+
+
+
 /**
  * utf8_*printf: *printf with conversion from UTF-8 to local encoding
  */
@@ -604,147 +656,63 @@ int utf8_fprintf( FILE *stream, const char *fmt, ... )
 
 
 static char *CheckUTF8( char *str, char rep )
-#define isutf8cont( c ) (((c) >= 0x80) && ((c) <= 0xBF)) 
 {
-    unsigned char *ptr, c;
-
+    uint8_t *ptr = (uint8_t *)str;
     assert (str != NULL);
 
-    ptr = (unsigned char *)str;
-    while( (c = *ptr) != '\0' )
+    for (;;)
     {
-        /* US-ASCII, 1 byte */
-        if( c <= 0x7F )
-            ptr++; /* OK */
-        else
-        /* 2 bytes */
-        if( ( c >= 0xC2 ) && ( c <= 0xDF ) )
-        {
-            c = ptr[1];
-            if( isutf8cont( c ) )
-                ptr += 2; /* OK */
-            else
-                goto error;
-        }
-        else
-        /* 3 bytes */
-        if( c == 0xE0 )
-        {
-            c = ptr[1];
-            if( ( c >= 0xA0 ) && ( c <= 0xBF ) )
-            {
-                c = ptr[2];
-                if( isutf8cont( c ) )
-                    ptr += 3; /* OK */
-                else
-                    goto error;
-            }
-            else
-                goto error;
-        }
-        else
-        if( ( ( c >= 0xE1 ) && ( c <= 0xEC ) ) || ( c == 0xEC )
-         || ( c == 0xEE ) || ( c == 0xEF ) )
-        {
-            c = ptr[1];
-            if( isutf8cont( c ) )
-            {
-                c = ptr[2];
-                if( isutf8cont( c ) )
-                    ptr += 3; /* OK */
-                else
-                    goto error;
-            }
-            else
-                goto error;
-        }
-        else
-        if( c == 0xED )
-        {
-            c = ptr[1];
-            if( ( c >= 0x80 ) && ( c <= 0x9F ) )
-            {
-                c = ptr[2];
-                if( isutf8cont( c ) )
-                    ptr += 3; /* OK */
-                else
-                    goto error;
-            }
-            else
-                goto error;
-        }
-        else
-        /* 4 bytes */
-        if( c == 0xF0 )
-        {
-            c = ptr[1];
-            if( ( c >= 0x90 ) && ( c <= 0xBF ) )
+        uint8_t c = ptr[0];
+        int charlen = -1;
+
+        if (c == '\0')
+            break;
+
+        for (int i = 0; i < 7; i++)
+            if ((c >> (7 - i)) == ((0xff >> (7 - i)) ^ 1))
             {
-                c = ptr[2];
-                if( isutf8cont( c ) )
-                {
-                    c = ptr[3];
-                    if( isutf8cont( c ) )
-                        ptr += 4; /* OK */
-                    else
-                        goto error;
-                }
-                else
-                    goto error;
+                charlen = i;
+                break;
             }
-            else
-                goto error;
-        }
-        else
-        if( ( c >= 0xF1 ) && ( c <= 0xF3 ) )
+
+        switch (charlen)
         {
-            c = ptr[1];
-            if( isutf8cont( c ) )
-            {
-                c = ptr[2];
-                if( isutf8cont( c ) )
-                {
-                    c = ptr[3];
-                    if( isutf8cont( c ) )
-                        ptr += 4; /* OK */
-                    goto error;
-                }
-                else
-                    goto error;
-            }
-            else
+            case 0: // 7-bit ASCII character -> OK
+                ptr++;
+                continue;
+
+            case -1: // 1111111x -> error
+            case 1: // continuation byte -> error
                 goto error;
         }
-        else
-        if( c == 0xF4 )
+
+        assert (charlen >= 2);
+
+        uint32_t cp = c & ~((0xff >> (7 - charlen)) << (7 - charlen));
+        for (int i = 1; i < charlen; i++)
         {
-            c = ptr[1];
-            if( ( c >= 0x80 ) && ( c <= 0x8F ) )
-            {
-                c = ptr[2];
-                if( isutf8cont( c ) )
-                {
-                    c = ptr[3];
-                    if( isutf8cont( c ) )
-                        ptr += 4; /* OK */
-                    else
-                        goto error;
-                }
-                else
-                    goto error;
-            }
-            else
+            assert (cp < (1 << 26));
+            c = ptr[i];
+
+            if ((c == '\0') // unexpected end of string
+             || ((c >> 6) != 2)) // not a continuation byte
                 goto error;
+
+            cp = (cp << 6) | (ptr[i] & 0x3f);
         }
-        else
+
+        if (cp < 128) // overlong (special case for ASCII)
+            goto error;
+        if (cp < (1u << (5 * charlen - 3))) // overlong
             goto error;
 
+        ptr += charlen;
         continue;
 
-error:
-        if( rep == 0 )
+    error:
+        if (rep == 0)
             return NULL;
-        *ptr++ = '?';
+        *ptr++ = rep;
         str = NULL;
     }