<commit_before>/*****************************************************************************
 * util.c : Utility functions for HTTP interface
 *****************************************************************************
 * Copyright (C) 2001-2005 the VideoLAN team
 * $Id$
 *
 * Authors: <NAME> <<EMAIL>>
 *          <NAME> <<EMAIL>>
 *          <NAME> <<EMAIL>>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#include "http.h"
#include "vlc_strings.h"

/****************************************************************************
 * File and directory functions
 ****************************************************************************/

/* ToUrl: create a good name for an url from filename */
char *E_(FileToUrl)( char *name, vlc_bool_t *pb_index )
{
    char *url, *p;

    url = p = malloc( strlen( name ) + 1 );

    *pb_index = VLC_FALSE;
    if( !url || !p )
    {
        return NULL;
    }

#ifdef WIN32
    while( *name == '\\' || *name == '/' )
#else
    while( *name == '/' )
#endif
    {
        name++;
    }

    *p++ = '/';
    strcpy( p, name );

#ifdef WIN32
    /* convert '\\' into '/' */
    name = p;
    while( *name )
    {
        if( *name == '\\' )
            *name = '/';
        name++;
    }
#endif

    /* index.* -> / */
    if( ( p = strrchr( url, '/' ) ) != NULL )
    {
        if( !strncmp( p, "/index.", 7 ) )
        {
            p[1] = '\0';
            *pb_index = VLC_TRUE;
        }
    }
    return url;
}

/* Load a file */
int E_(FileLoad)( FILE *f, char **pp_data, int *pi_data )
{
    int i_read;

    /* just load the file */
    *pi_data = 0;
    *pp_data = malloc( 1025 );  /* +1 for \0 */
    while( ( i_read = fread( &(*pp_data)[*pi_data], 1, 1024, f ) ) == 1024 )
    {
        *pi_data += 1024;
        *pp_data = realloc( *pp_data, *pi_data  + 1025 );
    }
    if( i_read > 0 )
    {
        *pi_data += i_read;
    }
    (*pp_data)[*pi_data] = '\0';

    return VLC_SUCCESS;
}

/* Parse a directory and recursively add files */
int E_(ParseDirectory)( intf_thread_t *p_intf, char *psz_root,
                        char *psz_dir )
{
    intf_sys_t     *p_sys = p_intf->p_sys;
    char           dir[MAX_DIR_SIZE];
#ifdef HAVE_SYS_STAT_H
    struct stat   stat_info;
#endif
    DIR           *p_dir;
    vlc_acl_t     *p_acl;
    FILE          *file;

    char          *user = NULL;
    char          *password = NULL;

    int           i_dirlen;

    char sep;

#if defined( WIN32 )
    sep = '\\';
#else
    sep = '/';
#endif

#ifdef HAVE_SYS_STAT_H
    if( utf8_stat( psz_dir, &stat_info ) == -1 || !S_ISDIR( stat_info.st_mode ) )
    {
        return VLC_EGENERIC;
    }
#endif

    if( ( p_dir = utf8_opendir( psz_dir ) ) == NULL )
    {
        msg_Err( p_intf, "cannot open dir (%s)", psz_dir );
        return VLC_EGENERIC;
    }

    i_dirlen = strlen( psz_dir );
    if( i_dirlen + 10 > MAX_DIR_SIZE )
    {
        msg_Warn( p_intf, "skipping too deep dir (%s)", psz_dir );
        return 0;
    }

    msg_Dbg( p_intf, "dir=%s", psz_dir );

    sprintf( dir, "%s%c.access", psz_dir, sep );
    if( ( file = utf8_fopen( dir, "r" ) ) != NULL )
    {
        char line[1024];
        int  i_size;

        msg_Dbg( p_intf, "find .access in dir=%s", psz_dir );

        i_size = fread( line, 1, 1023, file );
        if( i_size > 0 )
        {
            char *p;
            while( i_size > 0 && ( line[i_size-1] == '\n' ||
                   line[i_size-1] == '\r' ) )
            {
                i_size--;
            }

            line[i_size] = '\0';

            p = strchr( line, ':' );
            if( p )
            {
                *p++ = '\0';
                user = strdup( line );
                password = strdup( p );
            }
        }
        msg_Dbg( p_intf, "using user=%s password=%s (read=%d)",
                 user, password, i_size );

        fclose( file );
    }

    sprintf( dir, "%s%c.hosts", psz_dir, sep );
    p_acl = ACL_Create( p_intf, VLC_FALSE );
    if( ACL_LoadFile( p_acl, dir ) )
    {
        ACL_Destroy( p_acl );
        p_acl = NULL;
    }

    for( ;; )
    {
        const char *psz_filename;
        /* parse psz_src dir */
        if( ( psz_filename = utf8_readdir( p_dir ) ) == NULL )
        {
            break;
        }

        if( ( psz_filename[0] == '.' )
         || ( i_dirlen + strlen( psz_filename ) > MAX_DIR_SIZE ) )
            continue;

        sprintf( dir, "%s%c%s", psz_dir, sep, psz_filename );
        LocaleFree( psz_filename );

        if( E_(ParseDirectory)( p_intf, psz_root, dir ) )
        {
            httpd_file_sys_t *f = NULL;
            httpd_handler_sys_t *h = NULL;
            vlc_bool_t b_index;
            char *psz_tmp, *psz_file, *psz_name, *psz_ext;

            psz_tmp = vlc_fix_readdir_charset( p_intf, dir );
            psz_file = E_(FromUTF8)( p_intf, psz_tmp );
            free( psz_tmp );
            psz_tmp = vlc_fix_readdir_charset( p_intf,
                                               &dir[strlen( psz_root )] );
            psz_name = E_(FileToUrl)( psz_tmp, &b_index );
            free( psz_tmp );
            psz_ext = strrchr( psz_file, '.' );
            if( psz_ext != NULL )
            {
                int i;
                psz_ext++;
                for( i = 0; i < p_sys->i_handlers; i++ )
                    if( !strcmp( p_sys->pp_handlers[i]->psz_ext, psz_ext ) )
                        break;
                if( i < p_sys->i_handlers )
                {
                    f = malloc( sizeof( httpd_handler_sys_t ) );
                    h = (httpd_handler_sys_t *)f;
                    f->b_handler = VLC_TRUE;
                    h->p_association = p_sys->pp_handlers[i];
                }
            }
            if( f == NULL )
            {
                f = malloc( sizeof( httpd_file_sys_t ) );
                f->b_handler = VLC_FALSE;
            }

            f->p_intf  = p_intf;
            f->p_file = NULL;
            f->p_redir = NULL;
            f->p_redir2 = NULL;
            f->file = psz_file;
            f->name = psz_name;
            f->b_html = strstr( &dir[strlen( psz_root )], ".htm" ) || strstr( &dir[strlen( psz_root )], ".xml" ) ? VLC_TRUE : VLC_FALSE;

            if( !f->name )
            {
                msg_Err( p_intf , "unable to parse directory" );
                closedir( p_dir );
                free( f );
                return( VLC_ENOMEM );
            }
            msg_Dbg( p_intf, "file=%s (url=%s)",
                     f->file, f->name );

            if( !f->b_handler )
            {
                f->p_file = httpd_FileNew( p_sys->p_httpd_host,
                                           f->name,
                                           f->b_html ? ( strstr( &dir[strlen( psz_root )], ".xml" ) ? "text/xml; charset=UTF-8" : p_sys->psz_html_type ) :
                                            NULL,
                                           user, password, p_acl,
                                           E_(HttpCallback), f );
                if( f->p_file != NULL )
                {
                    TAB_APPEND( p_sys->i_files, p_sys->pp_files, f );
                }
            }
            else
            {
                h->p_handler = httpd_HandlerNew( p_sys->p_httpd_host,
                                                 f->name,
                                                 user, password, p_acl,
                                                 E_(HandlerCallback), h );
                if( h->p_handler != NULL )
                {
                    TAB_APPEND( p_sys->i_files, p_sys->pp_files,
                                (httpd_file_sys_t *)h );
                }
            }

            /* for url that ends by / add
             *  - a redirect from rep to rep/
             *  - in case of index.* rep/index.html to rep/ */
            if( f && f->name[strlen(f->name) - 1] == '/' )
            {
                char *psz_redir = strdup( f->name );
                char *p;
                psz_redir[strlen( psz_redir ) - 1] = '\0';

                msg_Dbg( p_intf, "redir=%s -> %s", psz_redir, f->name );
                f->p_redir = httpd_RedirectNew( p_sys->p_httpd_host, f->name, psz_redir );
                free( psz_redir );

                if( b_index && ( p = strstr( f->file, "index." ) ) )
                {
                    asprintf( &psz_redir, "%s%s", f->name, p );

                    msg_Dbg( p_intf, "redir=%s -> %s", psz_redir, f->name );
                    f->p_redir2 = httpd_RedirectNew( p_sys->p_httpd_host,
                                                     f->name, psz_redir );

                    free( psz_redir );
                }
            }
        }
    }

    if( user )
    {
        free( user );
    }
    if( password )
    {
        free( password );
    }

    ACL_Destroy( p_acl );
    closedir( p_dir );

    return VLC_SUCCESS;
}


/**************************************************************************
 * Locale functions
 **************************************************************************/
char *E_(FromUTF8)( intf_thread_t *p_intf, char *psz_utf8 )
{
    intf_sys_t    *p_sys = p_intf->p_sys;

    if ( p_sys->iconv_from_utf8 != (vlc_iconv_t)-1 )
    {
        size_t i_in = strlen(psz_utf8);
        size_t i_out = i_in * 2;
        char *psz_local = malloc(i_out + 1);
        char *psz_out = psz_local;
        size_t i_ret;
        char psz_tmp[i_in + 1];
        char *psz_in = psz_tmp;
        uint8_t *p = (uint8_t *)psz_tmp;
        strcpy( psz_tmp, psz_utf8 );

        /* Fix Unicode quotes. If we are here we are probably converting
         * to an inferior charset not understanding Unicode quotes. */
        while( *p )
        {
            if( p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x99 )
            {
                *p = '\'';
                memmove( &p[1], &p[3], strlen((char *)&p[3]) + 1 );
            }
            if( p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x9a )
            {
                *p = '"';
                memmove( &p[1], &p[3], strlen((char *)&p[3]) + 1 );
            }
            p++;
        }
        i_in = strlen( psz_tmp );

        i_ret = vlc_iconv( p_sys->iconv_from_utf8, &psz_in, &i_in,
                           &psz_out, &i_out );
        if( i_ret == (size_t)-1 || i_in )
        {
            msg_Warn( p_intf,
                      "failed to convert \"%s\" to desired charset (%s)",
                      psz_utf8, strerror(errno) );
            free( psz_local );
            return strdup( psz_utf8 );
        }

        *psz_out = '\0';
        return psz_local;
    }
    else
        return strdup( psz_utf8 );
}

char *E_(ToUTF8)( intf_thread_t *p_intf, char *psz_local )
{
    intf_sys_t    *p_sys = p_intf->p_sys;

    if ( p_sys->iconv_to_utf8 != (vlc_iconv_t)-1 )
    {
        char *psz_in = psz_local;
        size_t i_in = strlen(psz_in);
        size_t i_out = i_in * 6;
        char *psz_utf8 = malloc(i_out + 1);
        char *psz_out = psz_utf8;

        size_t i_ret = vlc_iconv( p_sys->iconv_to_utf8, &psz_in, &i_in,
                                  &psz_out, &i_out );
        if( i_ret == (size_t)-1 || i_in )
        {
            msg_Warn( p_intf,
                      "failed to convert \"%s\" to desired charset (%s)",
                      psz_local, strerror(errno) );
            free( psz_utf8 );
            return strdup( psz_local );
        }

        *psz_out = '\0';
        return psz_utf8;
    }
    else
        return strdup( psz_local );
}

/*************************************************************************
 * Playlist stuff
 *************************************************************************/
void E_(PlaylistListNode)( intf_thread_t *p_intf, playlist_t *p_pl,
                           playlist_item_t *p_node, char *name, mvar_t *s,
                           int i_depth )
{
    if( p_node != NULL )
    {
        if( p_node->i_children == -1 )
        {
            char value[512];
            char *psz;
            mvar_t *itm = E_(mvar_New)( name, "set" );

            sprintf( value, "%d", ( p_pl->status.p_item == p_node )? 1 : 0 );
            E_(mvar_AppendNewVar)( itm, "current", value );

            sprintf( value, "%d", p_node->input.i_id );
            E_(mvar_AppendNewVar)( itm, "index", value );

            psz = E_(FromUTF8)( p_intf, p_node->input.psz_name );
            E_(mvar_AppendNewVar)( itm, "name", psz );
            free( psz );

            psz = E_(FromUTF8)( p_intf, p_node->input.psz_uri );
            E_(mvar_AppendNewVar)( itm, "uri", psz );
            free( psz );

            sprintf( value, "Item");
            E_(mvar_AppendNewVar)( itm, "type", value );

            sprintf( value, "%d", i_depth );
            E_(mvar_AppendNewVar)( itm, "depth", value );

            E_(mvar_AppendVar)( s, itm );
        }
        else
        {
            char value[512];
            char *psz;
            int i_child;
            mvar_t *itm = E_(mvar_New)( name, "set" );

            psz = E_(FromUTF8)( p_intf, p_node->input.psz_name );
            E_(mvar_AppendNewVar)( itm, "name", psz );
            E_(mvar_AppendNewVar)( itm, "uri", psz );
            free( psz );

            sprintf( value, "Node" );
            E_(mvar_AppendNewVar)( itm, "type", value );

            sprintf( value, "%d", p_node->input.i_id );
            E_(mvar_AppendNewVar)( itm, "index", value );

            sprintf( value, "%d", p_node->i_children);
            E_(mvar_AppendNewVar)( itm, "i_children", value );

            sprintf( value, "%d", i_depth );
            E_(mvar_AppendNewVar)( itm, "depth", value );

            E_(mvar_AppendVar)( s, itm );

            for (i_child = 0 ; i_child < p_node->i_children ; i_child++)
                E_(PlaylistListNode)( p_intf, p_pl,
                                      p_node->pp_children[i_child],
                                      name, s, i_depth + 1);

        }
    }<commit_msg>Export the fact that a playlist item is read only or can be deleted<commit_after>/*****************************************************************************
 * util.c : Utility functions for HTTP interface
 *****************************************************************************
 * Copyright (C) 2001-2005 the VideoLAN team
 * $Id$
 *
 * Authors: <NAME> <<EMAIL>>
 *          <NAME> <<EMAIL>>
 *          <NAME> <<EMAIL>>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#include "http.h"
#include "vlc_strings.h"

/****************************************************************************
 * File and directory functions
 ****************************************************************************/

/* ToUrl: create a good name for an url from filename */
char *E_(FileToUrl)( char *name, vlc_bool_t *pb_index )
{
    char *url, *p;

    url = p = malloc( strlen( name ) + 1 );

    *pb_index = VLC_FALSE;
    if( !url || !p )
    {
        return NULL;
    }

#ifdef WIN32
    while( *name == '\\' || *name == '/' )
#else
    while( *name == '/' )
#endif
    {
        name++;
    }

    *p++ = '/';
    strcpy( p, name );

#ifdef WIN32
    /* convert '\\' into '/' */
    name = p;
    while( *name )
    {
        if( *name == '\\' )
            *name = '/';
        name++;
    }
#endif

    /* index.* -> / */
    if( ( p = strrchr( url, '/' ) ) != NULL )
    {
        if( !strncmp( p, "/index.", 7 ) )
        {
            p[1] = '\0';
            *pb_index = VLC_TRUE;
        }
    }
    return url;
}

/* Load a file */
int E_(FileLoad)( FILE *f, char **pp_data, int *pi_data )
{
    int i_read;

    /* just load the file */
    *pi_data = 0;
    *pp_data = malloc( 1025 );  /* +1 for \0 */
    while( ( i_read = fread( &(*pp_data)[*pi_data], 1, 1024, f ) ) == 1024 )
    {
        *pi_data += 1024;
        *pp_data = realloc( *pp_data, *pi_data  + 1025 );
    }
    if( i_read > 0 )
    {
        *pi_data += i_read;
    }
    (*pp_data)[*pi_data] = '\0';

    return VLC_SUCCESS;
}

/* Parse a directory and recursively add files */
int E_(ParseDirectory)( intf_thread_t *p_intf, char *psz_root,
                        char *psz_dir )
{
    intf_sys_t     *p_sys = p_intf->p_sys;
    char           dir[MAX_DIR_SIZE];
#ifdef HAVE_SYS_STAT_H
    struct stat   stat_info;
#endif
    DIR           *p_dir;
    vlc_acl_t     *p_acl;
    FILE          *file;

    char          *user = NULL;
    char          *password = NULL;

    int           i_dirlen;

    char sep;

#if defined( WIN32 )
    sep = '\\';
#else
    sep = '/';
#endif

#ifdef HAVE_SYS_STAT_H
    if( utf8_stat( psz_dir, &stat_info ) == -1 || !S_ISDIR( stat_info.st_mode ) )
    {
        return VLC_EGENERIC;
    }
#endif

    if( ( p_dir = utf8_opendir( psz_dir ) ) == NULL )
    {
        msg_Err( p_intf, "cannot open dir (%s)", psz_dir );
        return VLC_EGENERIC;
    }

    i_dirlen = strlen( psz_dir );
    if( i_dirlen + 10 > MAX_DIR_SIZE )
    {
        msg_Warn( p_intf, "skipping too deep dir (%s)", psz_dir );
        return 0;
    }

    msg_Dbg( p_intf, "dir=%s", psz_dir );

    sprintf( dir, "%s%c.access", psz_dir, sep );
    if( ( file = utf8_fopen( dir, "r" ) ) != NULL )
    {
        char line[1024];
        int  i_size;

        msg_Dbg( p_intf, "find .access in dir=%s", psz_dir );

        i_size = fread( line, 1, 1023, file );
        if( i_size > 0 )
        {
            char *p;
            while( i_size > 0 && ( line[i_size-1] == '\n' ||
                   line[i_size-1] == '\r' ) )
            {
                i_size--;
            }

            line[i_size] = '\0';

            p = strchr( line, ':' );
            if( p )
            {
                *p++ = '\0';
                user = strdup( line );
                password = strdup( p );
            }
        }
        msg_Dbg( p_intf, "using user=%s password=%s (read=%d)",
                 user, password, i_size );

        fclose( file );
    }

    sprintf( dir, "%s%c.hosts", psz_dir, sep );
    p_acl = ACL_Create( p_intf, VLC_FALSE );
    if( ACL_LoadFile( p_acl, dir ) )
    {
        ACL_Destroy( p_acl );
        p_acl = NULL;
    }

    for( ;; )
    {
        const char *psz_filename;
        /* parse psz_src dir */
        if( ( psz_filename = utf8_readdir( p_dir ) ) == NULL )
        {
            break;
        }

        if( ( psz_filename[0] == '.' )
         || ( i_dirlen + strlen( psz_filename ) > MAX_DIR_SIZE ) )
            continue;

        sprintf( dir, "%s%c%s", psz_dir, sep, psz_filename );
        LocaleFree( psz_filename );

        if( E_(ParseDirectory)( p_intf, psz_root, dir ) )
        {
            httpd_file_sys_t *f = NULL;
            httpd_handler_sys_t *h = NULL;
            vlc_bool_t b_index;
            char *psz_tmp, *psz_file, *psz_name, *psz_ext;

            psz_tmp = vlc_fix_readdir_charset( p_intf, dir );
            psz_file = E_(FromUTF8)( p_intf, psz_tmp );
            free( psz_tmp );
            psz_tmp = vlc_fix_readdir_charset( p_intf,
                                               &dir[strlen( psz_root )] );
            psz_name = E_(FileToUrl)( psz_tmp, &b_index );
            free( psz_tmp );
            psz_ext = strrchr( psz_file, '.' );
            if( psz_ext != NULL )
            {
                int i;
                psz_ext++;
                for( i = 0; i < p_sys->i_handlers; i++ )
                    if( !strcmp( p_sys->pp_handlers[i]->psz_ext, psz_ext ) )
                        break;
                if( i < p_sys->i_handlers )
                {
                    f = malloc( sizeof( httpd_handler_sys_t ) );
                    h = (httpd_handler_sys_t *)f;
                    f->b_handler = VLC_TRUE;
                    h->p_association = p_sys->pp_handlers[i];
                }
            }
            if( f == NULL )
            {
                f = malloc( sizeof( httpd_file_sys_t ) );
                f->b_handler = VLC_FALSE;
            }

            f->p_intf  = p_intf;
            f->p_file = NULL;
            f->p_redir = NULL;
            f->p_redir2 = NULL;
            f->file = psz_file;
            f->name = psz_name;
            f->b_html = strstr( &dir[strlen( psz_root )], ".htm" ) || strstr( &dir[strlen( psz_root )], ".xml" ) ? VLC_TRUE : VLC_FALSE;

            if( !f->name )
            {
                msg_Err( p_intf , "unable to parse directory" );
                closedir( p_dir );
                free( f );
                return( VLC_ENOMEM );
            }
            msg_Dbg( p_intf, "file=%s (url=%s)",
                     f->file, f->name );

            if( !f->b_handler )
            {
                f->p_file = httpd_FileNew( p_sys->p_httpd_host,
                                           f->name,
                                           f->b_html ? ( strstr( &dir[strlen( psz_root )], ".xml" ) ? "text/xml; charset=UTF-8" : p_sys->psz_html_type ) :
                                            NULL,
                                           user, password, p_acl,
                                           E_(HttpCallback), f );
                if( f->p_file != NULL )
                {
                    TAB_APPEND( p_sys->i_files, p_sys->pp_files, f );
                }
            }
            else
            {
                h->p_handler = httpd_HandlerNew( p_sys->p_httpd_host,
                                                 f->name,
                                                 user, password, p_acl,
                                                 E_(HandlerCallback), h );
                if( h->p_handler != NULL )
                {
                    TAB_APPEND( p_sys->i_files, p_sys->pp_files,
                                (httpd_file_sys_t *)h );
                }
            }

            /* for url that ends by / add
             *  - a redirect from rep to rep/
             *  - in case of index.* rep/index.html to rep/ */
            if( f && f->name[strlen(f->name) - 1] == '/' )
            {
                char *psz_redir = strdup( f->name );
                char *p;
                psz_redir[strlen( psz_redir ) - 1] = '\0';

                msg_Dbg( p_intf, "redir=%s -> %s", psz_redir, f->name );
                f->p_redir = httpd_RedirectNew( p_sys->p_httpd_host, f->name, psz_redir );
                free( psz_redir );

                if( b_index && ( p = strstr( f->file, "index." ) ) )
                {
                    asprintf( &psz_redir, "%s%s", f->name, p );

                    msg_Dbg( p_intf, "redir=%s -> %s", psz_redir, f->name );
                    f->p_redir2 = httpd_RedirectNew( p_sys->p_httpd_host,
                                                     f->name, psz_redir );

                    free( psz_redir );
                }
            }
        }
    }

    if( user )
    {
        free( user );
    }
    if( password )
    {
        free( password );
    }

    ACL_Destroy( p_acl );
    closedir( p_dir );

    return VLC_SUCCESS;
}


/**************************************************************************
 * Locale functions
 **************************************************************************/
char *E_(FromUTF8)( intf_thread_t *p_intf, char *psz_utf8 )
{
    intf_sys_t    *p_sys = p_intf->p_sys;

    if ( p_sys->iconv_from_utf8 != (vlc_iconv_t)-1 )
    {
        size_t i_in = strlen(psz_utf8);
        size_t i_out = i_in * 2;
        char *psz_local = malloc(i_out + 1);
        char *psz_out = psz_local;
        size_t i_ret;
        char psz_tmp[i_in + 1];
        char *psz_in = psz_tmp;
        uint8_t *p = (uint8_t *)psz_tmp;
        strcpy( psz_tmp, psz_utf8 );

        /* Fix Unicode quotes. If we are here we are probably converting
         * to an inferior charset not understanding Unicode quotes. */
        while( *p )
        {
            if( p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x99 )
            {
                *p = '\'';
                memmove( &p[1], &p[3], strlen((char *)&p[3]) + 1 );
            }
            if( p[0] == 0xe2 && p[1] == 0x80 && p[2] == 0x9a )
            {
                *p = '"';
                memmove( &p[1], &p[3], strlen((char *)&p[3]) + 1 );
            }
            p++;
        }
        i_in = strlen( psz_tmp );

        i_ret = vlc_iconv( p_sys->iconv_from_utf8, &psz_in, &i_in,
                           &psz_out, &i_out );
        if( i_ret == (size_t)-1 || i_in )
        {
            msg_Warn( p_intf,
                      "failed to convert \"%s\" to desired charset (%s)",
                      psz_utf8, strerror(errno) );
            free( psz_local );
            return strdup( psz_utf8 );
        }

        *psz_out = '\0';
        return psz_local;
    }
    else
        return strdup( psz_utf8 );
}

char *E_(ToUTF8)( intf_thread_t *p_intf, char *psz_local )
{
    intf_sys_t    *p_sys = p_intf->p_sys;

    if ( p_sys->iconv_to_utf8 != (vlc_iconv_t)-1 )
    {
        char *psz_in = psz_local;
        size_t i_in = strlen(psz_in);
        size_t i_out = i_in * 6;
        char *psz_utf8 = malloc(i_out + 1);
        char *psz_out = psz_utf8;

        size_t i_ret = vlc_iconv( p_sys->iconv_to_utf8, &psz_in, &i_in,
                                  &psz_out, &i_out );
        if( i_ret == (size_t)-1 || i_in )
        {
            msg_Warn( p_intf,
                      "failed to convert \"%s\" to desired charset (%s)",
                      psz_local, strerror(errno) );
            free( psz_utf8 );
            return strdup( psz_local );
        }

        *psz_out = '\0';
        return psz_utf8;
    }
    else
        return strdup( psz_local );
}

/*************************************************************************
 * Playlist stuff
 *************************************************************************/
void E_(PlaylistListNode)( intf_thread_t *p_intf, playlist_t *p_pl,
                           playlist_item_t *p_node, char *name, mvar_t *s,
                           int i_depth )
{
    if( p_node != NULL )
    {
        if( p_node->i_children == -1 )
        {
            char value[512];
            char *psz;
            mvar_t *itm = E_(mvar_New)( name, "set" );

            sprintf( value, "%d", ( p_pl->status.p_item == p_node )? 1 : 0 );
            E_(mvar_AppendNewVar)( itm, "current", value );

            sprintf( value, "%d", p_node->input.i_id );
            E_(mvar_AppendNewVar)( itm, "index", value );

            psz = E_(FromUTF8)( p_intf, p_node->input.psz_name );
            E_(mvar_AppendNewVar)( itm, "name", psz );
            free( psz );

            psz = E_(FromUTF8)( p_intf, p_node->input.psz_uri );
            E_(mvar_AppendNewVar)( itm, "uri", psz );
            free( psz );

            sprintf( value, "Item");
            E_(mvar_AppendNewVar)( itm, "type", value );

            sprintf( value, "%d", i_depth );
            E_(mvar_AppendNewVar)( itm, "depth", value );

            if( p_node->i_flags & PLAYLIST_RO_FLAG )
            {
                E_(mvar_AppendNewVar)( itm, "ro", "ro" );
            }
            else
            {
                E_(mvar_AppendNewVar)( itm, "ro", "rw" );
            }

            E_(mvar_AppendVar)( s, itm );
        }
        else
        {
            char value[512];
            char *psz;
            int i_child;
            mvar_t *itm = E_(mvar_New)( name, "set" );

            psz = E_(FromUTF8)( p_intf, p_node->input.psz_name );
            E_(mvar_AppendNewVar)( itm, "name", psz );
            E_(mvar_AppendNewVar)( itm, "uri", psz );
            free( psz );

            sprintf( value, "Node" );
            E_(mvar_AppendNewVar)( itm, "type", value );

            sprintf( value, "%d", p_node->input.i_id );
            E_(mvar_AppendNewVar)( itm, "index", value );

            sprintf( value, "%d", p_node->i_children);
            E_(mvar_AppendNewVar)( itm, "i_children", value );

            sprintf( value, "%d", i_depth );
            E_(mvar_AppendNewVar)( itm, "depth", value );

            if( p_node->i_flags & PLAYLIST_RO_FLAG )
            {
                E_(mvar_AppendNewVar)( itm, "ro", "ro" );
            }
            else
            {
                E_(mvar_AppendNewVar)( itm, "ro", "rw" );
            }

            E_(mvar_AppendVar)( s, itm );

            for (i_child = 0 ; i_child < p_node->i_children ; i_child++)
                E_(PlaylistListNode)( p_intf, p_pl,
                                      p_node->pp_children[i_child],
                                      name, s, i_depth + 1);

        }
    }
{'fraction_non_alphanumeric': 0.17213387450928205, 'fraction_numerical': 0.010646084237141527, 'mean_word_length': 1.4731775547144972, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 165, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 6}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 46, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7120736', 'n_tokens_mistral': 10191, 'n_tokens_neox': 9717, 'n_words': 2217}