]> git.sesse.net Git - vlc/blobdiff - modules/lua/vlc.c
LUA is still GPLv2+
[vlc] / modules / lua / vlc.c
index d698b0823234db187b00dab5a487f7bdd39ecd7e..4340038bd048b8d0012fe19448b0165037b5b0c2 100644 (file)
 /*****************************************************************************
  * Preamble
  *****************************************************************************/
-#ifndef  _GNU_SOURCE
-#   define  _GNU_SOURCE
-#endif
-
 #ifdef HAVE_CONFIG_H
 # include "config.h"
 #endif
 
 #include <assert.h>
+#include <sys/stat.h>
+
+#define VLC_MODULE_LICENSE VLC_LICENSE_GPL_2_PLUS
+
+#include "vlc.h"
 
-#include <vlc_common.h>
 #include <vlc_plugin.h>
-#include <vlc_meta.h>
 #include <vlc_charset.h>
 #include <vlc_fs.h>
-#include <vlc_aout.h>
 #include <vlc_services_discovery.h>
 #include <vlc_stream.h>
-#include <sys/stat.h>
-
-#include <lua.h>        /* Low level lua C API */
-#include <lauxlib.h>    /* Higher level C API */
-#include <lualib.h>     /* Lua libs */
-
-#include "vlc.h"
 
 /*****************************************************************************
  * Module descriptor
 
 #define CONFIG_TEXT N_("Lua interface configuration")
 #define CONFIG_LONGTEXT N_("Lua interface configuration string. Format is: '[\"<interface module name>\"] = { <option> = <value>, ...}, ...'.")
-#define HOST_TEXT N_( "Host address" )
-#define HOST_LONGTEXT N_( \
-    "Address and port the HTTP interface will listen on. It defaults to " \
-    "all network interfaces (0.0.0.0)." \
-    " If you want the HTTP interface to be available only on the local " \
-    "machine, enter 127.0.0.1" )
+#define PASS_TEXT N_( "Password" )
+#define PASS_LONGTEXT N_( "A single password restricts access " \
+    "to this interface." )
 #define SRC_TEXT N_( "Source directory" )
 #define SRC_LONGTEXT N_( "Source directory" )
 #define INDEX_TEXT N_( "Directory index" )
 #define TELNETPORT_LONGTEXT N_( "This is the TCP port on which this " \
     "interface will listen. It defaults to 4212." )
 #define TELNETPWD_TEXT N_( "Password" )
-#define TELNETPWD_LONGTEXT N_( "A single administration password is used " \
-    "to protect this interface. The default value is \"admin\"." )
-#define TELNETPWD_DEFAULT "admin"
+#define TELNETPWD_LONGTEXT N_( "A single password restricts access " \
+    "to this interface." )
 #define RCHOST_TEXT N_("TCP command input")
 #define RCHOST_LONGTEXT N_("Accept commands over a socket rather than stdin. " \
             "You can set the address and port the interface will bind to." )
+#define CLIHOST_TEXT N_("CLI input")
+#define CLIHOST_LONGTEXT N_( "Accept commands from this source. " \
+    "The CLI defaults to stdin (\"*console\"), but can also bind to a " \
+    "plain TCP socket (\"localhost:4212\") or use the telnet protocol " \
+    "(\"telnet://0.0.0.0:4212\")" )
 
 static int vlc_sd_probe_Open( vlc_object_t * );
 
 vlc_module_begin ()
-        set_shortname( N_("Lua Interface Module") )
-        set_description( N_("Interfaces implemented using lua scripts") )
-        add_shortcut( "luaintf" )
-        add_shortcut( "luahttp" )
-        /* add_shortcut( "http" ) */
-        add_shortcut( "luatelnet" )
-        add_shortcut( "telnet" )
-        add_shortcut( "luahotkeys" )
-        /* add_shortcut( "hotkeys" ) */
-        set_capability( "interface", 0 )
+        set_shortname( N_("Lua") )
+        set_description( N_("Lua interpreter") )
         set_category( CAT_INTERFACE )
-        set_subcategory( SUBCAT_INTERFACE_CONTROL )
-        add_string( "lua-intf", "dummy",
-                    INTF_TEXT, INTF_LONGTEXT, false )
-        add_string( "lua-config", "",
-                    CONFIG_TEXT, CONFIG_LONGTEXT, false )
+        set_subcategory( SUBCAT_INTERFACE_MAIN )
+
+        add_string( "lua-intf", "dummy", INTF_TEXT, INTF_LONGTEXT, false )
+        add_string( "lua-config", "", CONFIG_TEXT, CONFIG_LONGTEXT, false )
+        set_capability( "interface", 0 )
+        set_callbacks( Open_LuaIntf, Close_LuaIntf )
+        add_shortcut( "luaintf" )
+
+    add_submodule ()
         set_section( N_("Lua HTTP"), 0 )
-            add_string ( "http-host", NULL, HOST_TEXT, HOST_LONGTEXT, true )
+            add_password ( "http-password", NULL, PASS_TEXT, PASS_LONGTEXT, false )
             add_string ( "http-src",  NULL, SRC_TEXT,  SRC_LONGTEXT,  true )
             add_bool   ( "http-index", false, INDEX_TEXT, INDEX_LONGTEXT, true )
-        set_section( N_("Lua RC"), 0 )
+        set_capability( "interface", 0 )
+        set_callbacks( Open_LuaHTTP, Close_LuaIntf )
+        add_shortcut( "luahttp", "http" )
+        set_description( N_("Lua HTTP") )
+
+    add_submodule ()
+        set_section( N_("Lua CLI"), 0 )
             add_string( "rc-host", NULL, RCHOST_TEXT, RCHOST_LONGTEXT, true )
+            add_string( "cli-host", NULL, CLIHOST_TEXT, CLIHOST_LONGTEXT, true )
+        set_capability( "interface", 25 )
+        set_description( N_("Command-line interface") )
+        set_callbacks( Open_LuaCLI, Close_LuaIntf )
+#ifndef _WIN32
+        add_shortcut( "luacli", "luarc", "cli", "rc" )
+#else
+        add_shortcut( "luacli", "luarc" )
+#endif
+
+    add_submodule ()
         set_section( N_("Lua Telnet"), 0 )
             add_string( "telnet-host", "localhost", TELNETHOST_TEXT,
                         TELNETHOST_LONGTEXT, true )
             add_integer( "telnet-port", TELNETPORT_DEFAULT, TELNETPORT_TEXT,
                          TELNETPORT_LONGTEXT, true )
-            add_password( "telnet-password", TELNETPWD_DEFAULT, TELNETPWD_TEXT,
-                          TELNETPWD_LONGTEXT, true )
+                change_integer_range( 1, 65535 )
+            add_password( "telnet-password", NULL, TELNETPWD_TEXT,
 
-        set_callbacks( Open_LuaIntf, Close_LuaIntf )
+                          TELNETPWD_LONGTEXT, true )
+        set_capability( "interface", 0 )
+        set_callbacks( Open_LuaTelnet, Close_LuaIntf )
+        set_description( N_("Lua Telnet") )
+        add_shortcut( "luatelnet", "telnet" )
 
     add_submodule ()
         set_shortname( N_( "Lua Meta Fetcher" ) )
@@ -140,15 +149,6 @@ vlc_module_begin ()
         set_capability( "demux", 2 )
         set_callbacks( Import_LuaPlaylist, Close_LuaPlaylist )
 
-    add_submodule ()
-        set_description( N_("Lua Interface Module (shortcuts)") )
-        add_shortcut( "luarc" )
-#ifndef WIN32
-        add_shortcut( "rc" )
-#endif
-        set_capability( "interface", 25 )
-        set_callbacks( Open_LuaIntf, Close_LuaIntf )
-
     add_submodule ()
         set_shortname( N_( "Lua Art" ) )
         set_description( N_("Fetch artwork using lua scripts") )
@@ -157,6 +157,7 @@ vlc_module_begin ()
 
     add_submodule ()
         set_shortname( N_("Lua Extension") )
+        set_description( N_("Lua Extension") )
         add_shortcut( "luaextension" )
         set_capability( "extension", 1 )
         set_callbacks( Open_Extension, Close_Extension )
@@ -171,18 +172,6 @@ vlc_module_begin ()
             change_volatile()
         set_callbacks( Open_LuaSD, Close_LuaSD )
 
-    add_submodule ()
-        set_description( N_("Freebox TV") )
-        add_shortcut( "freebox" )
-        set_capability( "services_discovery", 0 )
-        set_callbacks( Open_LuaSD, Close_LuaSD )
-
-    add_submodule ()
-        set_description( N_("French TV") )
-        add_shortcut( "frenchtv" )
-        set_capability( "services_discovery", 0 )
-        set_callbacks( Open_LuaSD, Close_LuaSD )
-
     VLC_SD_PROBE_SUBMODULE
 
 vlc_module_end ()
@@ -209,8 +198,7 @@ static int file_compare( const char **a, const char **b )
     return strcmp( *a, *b );
 }
 
-int vlclua_dir_list( vlc_object_t *p_this, const char *luadirname,
-                     char ***pppsz_dir_list )
+int vlclua_dir_list( const char *luadirname, char ***pppsz_dir_list )
 {
 #define MAX_DIR_LIST_SIZE 5
     *pppsz_dir_list = malloc(MAX_DIR_LIST_SIZE*sizeof(char *));
@@ -227,13 +215,18 @@ int vlclua_dir_list( vlc_object_t *p_this, const char *luadirname,
         i++;
     free( datadir );
 
-#if !(defined(__APPLE__) || defined(WIN32))
-    if( likely(asprintf( &ppsz_dir_list[i], "%s"DIR_SEP"lua"DIR_SEP"%s",
-                         config_GetLibDir(), luadirname ) != -1) )
+#if !(defined(__APPLE__) || defined(_WIN32))
+    char *psz_libpath = config_GetLibDir();
+    if( likely(psz_libpath != NULL) )
+    {
+        if( likely(asprintf( &ppsz_dir_list[i], "%s"DIR_SEP"lua"DIR_SEP"%s",
+                             psz_libpath, luadirname ) != -1) )
             i++;
+        free( psz_libpath );
+    }
 #endif
 
-    char *psz_datapath = config_GetDataDir( p_this );
+    char *psz_datapath = config_GetDataDir();
     if( likely(psz_datapath != NULL) )
     {
         if( likely(asprintf( &ppsz_dir_list[i], "%s"DIR_SEP"lua"DIR_SEP"%s",
@@ -269,13 +262,13 @@ void vlclua_dir_list_free( char **ppsz_dir_list )
  *****************************************************************************/
 int vlclua_scripts_batch_execute( vlc_object_t *p_this,
                                   const char * luadirname,
-                                  int (*func)(vlc_object_t *, const char *, void *),
+                                  int (*func)(vlc_object_t *, const char *, const luabatch_context_t *),
                                   void * user_data)
 {
     char **ppsz_dir_list = NULL;
     int i_ret;
 
-    if((i_ret = vlclua_dir_list( p_this, luadirname, &ppsz_dir_list ) != VLC_SUCCESS))
+    if( (i_ret = vlclua_dir_list( luadirname, &ppsz_dir_list )) != VLC_SUCCESS)
         return i_ret;
 
     i_ret = VLC_EGENERIC;
@@ -303,8 +296,7 @@ int vlclua_scripts_batch_execute( vlc_object_t *p_this,
 
             if( likely(psz_filename != NULL) )
             {
-                msg_Dbg( p_this, "Trying Lua playlist script %s",
-                         psz_filename );
+                msg_Dbg( p_this, "Trying Lua playlist script %s", psz_filename );
                 i_ret = func( p_this, psz_filename, user_data );
                 free( psz_filename );
                 if( i_ret == VLC_SUCCESS )
@@ -323,10 +315,10 @@ int vlclua_scripts_batch_execute( vlc_object_t *p_this,
     return i_ret;
 }
 
-char *vlclua_find_file( vlc_object_t *p_this, const char *psz_luadirname, const char *psz_name )
+char *vlclua_find_file( const char *psz_luadirname, const char *psz_name )
 {
     char **ppsz_dir_list = NULL;
-    vlclua_dir_list( p_this, psz_luadirname, &ppsz_dir_list );
+    vlclua_dir_list( psz_luadirname, &ppsz_dir_list );
 
     for( char **ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
     {
@@ -359,8 +351,9 @@ char *vlclua_find_file( vlc_object_t *p_this, const char *psz_luadirname, const
  * Meta data setters utility.
  * Playlist item table should be on top of the stack when these are called
  *****************************************************************************/
-void __vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
-                              input_item_t *p_input )
+#undef vlclua_read_meta_data
+void vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
+                            input_item_t *p_input )
 {
 #define TRY_META( a, b )                                        \
     lua_getfield( L, -1, a );                                   \
@@ -385,17 +378,29 @@ void __vlclua_read_meta_data( vlc_object_t *p_this, lua_State *L,
     TRY_META( "date", Date );
     TRY_META( "setting", Setting );
     TRY_META( "url", URL );
-    TRY_META( "language", Language );
+    TRY_META( "language",  Language );
     TRY_META( "nowplaying", NowPlaying );
-    TRY_META( "publisher", Publisher );
-    TRY_META( "encodedby", EncodedBy );
-    TRY_META( "arturl", ArtURL );
-    TRY_META( "trackid", TrackID );
+    TRY_META( "publisher",  Publisher );
+    TRY_META( "encodedby",  EncodedBy );
+    TRY_META( "arturl",     ArtURL );
+    TRY_META( "trackid",    TrackID );
+    TRY_META( "director",   Director );
+    TRY_META( "season",     Season );
+    TRY_META( "episode",    Episode );
+    TRY_META( "show_name",  ShowName );
+    TRY_META( "actors",     Actors );
 }
 
-void __vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
+#undef vlclua_read_custom_meta_data
+void vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
                                      input_item_t *p_input )
 {
+    /* Lock the input item and create the meta table if needed */
+    vlc_mutex_lock( &p_input->lock );
+
+    if( !p_input->p_meta )
+        p_input->p_meta = vlc_meta_New();
+
     /* ... item */
     lua_getfield( L, -1, "meta" );
     /* ... item meta */
@@ -406,60 +411,24 @@ void __vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
         while( lua_next( L, -2 ) )
         {
             /* ... item meta key value */
-            if( !lua_isstring( L, -2 ) )
+            if( !lua_isstring( L, -2 ) || !lua_isstring( L, -1 ) )
             {
-                msg_Warn( p_this, "Custom meta data category name must be "
-                                   "a string" );
-            }
-            else if( !lua_istable( L, -1 ) )
-            {
-                msg_Warn( p_this, "Custom meta data category contents "
-                                   "must be a table" );
-            }
-            else
-            {
-                const char *psz_meta_category = lua_tostring( L, -2 );
-                msg_Dbg( p_this, "Found custom meta data category: %s",
-                         psz_meta_category );
-                lua_pushnil( L );
-                /* ... item meta key value nil */
-                while( lua_next( L, -2 ) )
-                {
-                    /* ... item meta key value key2 value2 */
-                    if( !lua_isstring( L, -2 ) )
-                    {
-                        msg_Warn( p_this, "Custom meta category item name "
-                                           "must be a string." );
-                    }
-                    else if( !lua_isstring( L, -1 ) )
-                    {
-                        msg_Warn( p_this, "Custom meta category item value "
-                                           "must be a string." );
-                    }
-                    else
-                    {
-                        const char *psz_meta_name =
-                            lua_tostring( L, -2 );
-                        const char *psz_meta_value =
-                            lua_tostring( L, -1 );
-                        msg_Dbg( p_this, "Custom meta %s, %s: %s",
-                                 psz_meta_category, psz_meta_name,
-                                 psz_meta_value );
-                        input_item_AddInfo( p_input, psz_meta_category,
-                                           psz_meta_name, "%s", psz_meta_value );
-                    }
-                    lua_pop( L, 1 ); /* pop item */
-                    /* ... item meta key value key2 */
-                }
-                /* ... item meta key value */
+                msg_Err( p_this, "'meta' keys and values must be strings");
+                lua_pop( L, 1 ); /* pop "value" */
+                continue;
             }
-            lua_pop( L, 1 ); /* pop category */
-            /* ... item meta key */
+            const char *psz_key = lua_tostring( L, -2 );
+            const char *psz_value = lua_tostring( L, -1 );
+
+            vlc_meta_AddExtra( p_input->p_meta, psz_key, psz_value );
+
+            lua_pop( L, 1 ); /* pop "value" */
         }
-        /* ... item meta */
     }
     lua_pop( L, 1 ); /* pop "meta" */
     /* ... item -> back to original stack */
+
+    vlc_mutex_unlock( &p_input->lock );
 }
 
 /*****************************************************************************
@@ -468,7 +437,8 @@ void __vlclua_read_custom_meta_data( vlc_object_t *p_this, lua_State *L,
 /**
  * Playlist item table should be on top of the stack when this is called
  */
-void __vlclua_read_options( vlc_object_t *p_this, lua_State *L,
+#undef vlclua_read_options
+void vlclua_read_options( vlc_object_t *p_this, lua_State *L,
                             int *pi_options, char ***pppsz_options )
 {
     lua_getfield( L, -1, "options" );
@@ -494,7 +464,8 @@ void __vlclua_read_options( vlc_object_t *p_this, lua_State *L,
     lua_pop( L, 1 ); /* pop "options" */
 }
 
-int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
+#undef vlclua_playlist_add_internal
+int vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                                     playlist_t *p_playlist,
                                     input_item_t *p_parent, bool b_play )
 {
@@ -519,7 +490,9 @@ int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                 /* playlist key item path */
                 if( lua_isstring( L, -1 ) )
                 {
+                    char         *psz_oldurl   = NULL;
                     const char   *psz_path     = NULL;
+                    char         *psz_u8path   = NULL;
                     const char   *psz_name     = NULL;
                     char        **ppsz_options = NULL;
                     int           i_options    = 0;
@@ -527,6 +500,10 @@ int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                     input_item_t *p_input;
 
                     /* Read path and name */
+                    if (p_parent) {
+                        psz_oldurl = input_item_GetURI( p_parent );
+                        msg_Dbg( p_this, "old path: %s", psz_oldurl );
+                    }
                     psz_path = lua_tostring( L, -1 );
                     msg_Dbg( p_this, "Path: %s", psz_path );
                     lua_getfield( L, -2, "name" );
@@ -540,7 +517,7 @@ int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                     {
                         if( !lua_isnil( L, -1 ) )
                             msg_Warn( p_this, "Playlist item name should be a string." );
-                        psz_name = psz_path;
+                        psz_name = NULL;
                     }
 
                     /* Read duration */
@@ -564,8 +541,7 @@ int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                     vlclua_read_options( p_this, L, &i_options, &ppsz_options );
 
                     /* Create input item */
-                    p_input = input_item_NewExt( p_playlist, psz_path,
-                                                psz_name, i_options,
+                    p_input = input_item_NewExt( psz_path, psz_name, i_options,
                                                 (const char **)ppsz_options,
                                                 VLC_INPUT_OPTION_TRUSTED,
                                                 i_duration );
@@ -575,6 +551,23 @@ int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                     /* Read meta data: item must be on top of stack */
                     vlclua_read_meta_data( p_this, L, p_input );
 
+                    /* copy the original URL to the meta data, if "URL" is still empty */
+                    char* url = input_item_GetURL( p_input );
+                    if( url == NULL && p_parent)
+                    {
+                        EnsureUTF8( psz_oldurl );
+                        msg_Dbg( p_this, "meta-URL: %s", psz_oldurl );
+                        input_item_SetURL ( p_input, psz_oldurl );
+                    }
+                    free( psz_oldurl );
+                    free( url );
+
+                    /* copy the psz_name to the meta data, if "Title" is still empty */
+                    char* title = input_item_GetTitle( p_input );
+                    if( title == NULL )
+                        input_item_SetTitle ( p_input, psz_name );
+                    free( title );
+
                     /* Read custom meta data: item must be on top of stack*/
                     vlclua_read_custom_meta_data( p_this, L, p_input );
 
@@ -595,6 +588,7 @@ int __vlclua_playlist_add_internal( vlc_object_t *p_this, lua_State *L,
                     while( i_options > 0 )
                         free( ppsz_options[--i_options] );
                     free( ppsz_options );
+                    free( psz_u8path );
                 }
                 else
                 {
@@ -637,7 +631,7 @@ static int vlc_sd_probe_Open( vlc_object_t *obj )
     char **ppsz_dir_list = NULL;
     char **ppsz_dir;
     lua_State *L = NULL;
-    vlclua_dir_list( obj, "sd", &ppsz_dir_list );
+    vlclua_dir_list( "sd", &ppsz_dir_list );
     for( ppsz_dir = ppsz_dir_list; *ppsz_dir; ppsz_dir++ )
     {
         int i_files;
@@ -669,14 +663,14 @@ static int vlc_sd_probe_Open( vlc_object_t *obj )
                 goto error;
             }
             luaL_openlibs( L );
-            if( vlclua_add_modules_path( probe, L, psz_filename ) )
+            if( vlclua_add_modules_path( L, psz_filename ) )
             {
                 msg_Err( probe, "Error while setting the module search path for %s",
                           psz_filename );
                 free( psz_filename );
                 goto error;
             }
-            if( luaL_dofile( L, psz_filename ) )
+            if( vlclua_dofile( VLC_OBJECT(probe), L, psz_filename ) )
             {
 
                 msg_Err( probe, "Error loading script %s: %s", psz_filename,
@@ -768,7 +762,7 @@ static int vlclua_add_modules_path_inner( lua_State *L, const char *psz_path )
     return count;
 }
 
-int __vlclua_add_modules_path( vlc_object_t *obj, lua_State *L, const char *psz_filename )
+int vlclua_add_modules_path( lua_State *L, const char *psz_filename )
 {
     /* Setup the module search path:
      *   * "The script's directory"/modules
@@ -808,7 +802,7 @@ int __vlclua_add_modules_path( vlc_object_t *obj, lua_State *L, const char *psz_
     count += vlclua_add_modules_path_inner( L, psz_path );
 
     char **ppsz_dir_list = NULL;
-    vlclua_dir_list( obj, psz_char+1/* gruik? */, &ppsz_dir_list );
+    vlclua_dir_list( psz_char+1/* gruik? */, &ppsz_dir_list );
     char **ppsz_dir = ppsz_dir_list;
 
     for( ; *ppsz_dir && strcmp( *ppsz_dir, psz_path ); ppsz_dir++ );
@@ -842,15 +836,23 @@ int __vlclua_add_modules_path( vlc_object_t *obj, lua_State *L, const char *psz_
 }
 
 /** Replacement for luaL_dofile, using VLC's input capabilities */
-int vlclua_dofile( vlc_object_t *p_this, lua_State *L, const char *uri )
+int vlclua_dofile( vlc_object_t *p_this, lua_State *L, const char *curi )
 {
-    if( !strstr( uri, "://" ) )
-        return luaL_dofile( L, uri );
-    if( !strncasecmp( uri, "file://", 7 ) )
-        return luaL_dofile( L, uri + 7 );
+    char *uri = ToLocaleDup( curi );
+    if( !strstr( uri, "://" ) ) {
+        int ret = luaL_dofile( L, uri );
+        free( uri );
+        return ret;
+    }
+    if( !strncasecmp( uri, "file://", 7 ) ) {
+        int ret = luaL_dofile( L, uri + 7 );
+        free( uri );
+        return ret;
+    }
     stream_t *s = stream_UrlNew( p_this, uri );
     if( !s )
     {
+        free( uri );
         return 1;
     }
     int64_t i_size = stream_Size( s );
@@ -859,6 +861,7 @@ int vlclua_dofile( vlc_object_t *p_this, lua_State *L, const char *uri )
     {
         // FIXME: read the whole stream until we reach the end (if no size)
         stream_Delete( s );
+        free( uri );
         return 1;
     }
     int64_t i_read = stream_Read( s, p_buffer, (int) i_size );
@@ -869,5 +872,6 @@ int vlclua_dofile( vlc_object_t *p_this, lua_State *L, const char *uri )
         i_ret = lua_pcall( L, 0, LUA_MULTRET, 0 );
     stream_Delete( s );
     free( p_buffer );
+    free( uri );
     return i_ret;
 }