1 /*****************************************************************************
2  * theme_loader.cpp
3  *****************************************************************************
4  * Copyright (C) 2003 the VideoLAN team
5  * $Id: d018b4e92eb389897b02f72888dd79e67726a3a9 $
6  *
7  * Authors: Cyril Deguet     <asmax@via.ecp.fr>
8  *          Olivier Teulière <ipkiss@via.ecp.fr>
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 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
28 
29 #include <fcntl.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fstream>
33 #include <memory>
34 
35 #include <vlc_common.h>
36 #include <vlc_fs.h>
37 #include <vlc_url.h>
38 #include <vlc_stream_extractor.h>
39 
40 #include "theme_loader.hpp"
41 #include "theme.hpp"
42 #include "../parser/builder.hpp"
43 #include "../parser/skin_parser.hpp"
44 #include "../src/os_factory.hpp"
45 #include "../src/vlcproc.hpp"
46 #include "../src/window_manager.hpp"
47 
48 #define DEFAULT_XML_FILE "theme.xml"
49 #define WINAMP2_XML_FILE "winamp2.xml"
50 
51 /* Recursive make directory
52  * Abort if you get an ENOENT errno somewhere in the middle
53  * e.g. ignore error "mkdir on existing directory"
54  *
55  * return 1 if OK, 0 on error
56  */
makedir(const char * newdir)57 static int makedir( const char *newdir )
58 {
59     char *p, *buffer = strdup( newdir );
60     int  len = strlen( buffer );
61 
62     if( len <= 0 )
63     {
64         free( buffer );
65         return 0;
66     }
67 
68     if( buffer[len-1] == '/' )
69     {
70         buffer[len-1] = '\0';
71     }
72 
73     if( vlc_mkdir( buffer, 0775 ) == 0 )
74     {
75         free( buffer );
76         return 1;
77     }
78 
79     p = buffer + 1;
80     while( 1 )
81     {
82         char hold;
83 
84         while( *p && *p != '\\' && *p != '/' ) p++;
85         hold = *p;
86         *p = 0;
87         if( ( vlc_mkdir( buffer, 0775 ) == -1 ) && ( errno == ENOENT ) )
88         {
89             fprintf( stderr, "couldn't create directory %s\n", buffer );
90             free( buffer );
91             return 0;
92         }
93         if( hold == 0 ) break;
94         *p++ = hold;
95     }
96     free( buffer );
97     return 1;
98 }
99 
load(const std::string & fileName)100 bool ThemeLoader::load( const std::string &fileName )
101 {
102     std::string path = getFilePath( fileName );
103 
104     //Before all, let's see if the file is present
105     struct stat p_stat;
106     if( vlc_stat( fileName.c_str(), &p_stat ) )
107         return false;
108 
109     // First, we try to un-targz the file, and if it fails we hope it's a XML
110     // file...
111 
112     if( ! extract( fileName ) && ! parse( path, fileName ) )
113         return false;
114 
115     Theme *pNewTheme = getIntf()->p_sys->p_theme;
116     if( !pNewTheme )
117         return false;
118 
119     // Restore the theme configuration
120     getIntf()->p_sys->p_theme->loadConfig();
121 
122     // Retain new loaded skins in config
123     config_PutPsz( getIntf(), "skins2-last", fileName.c_str() );
124 
125     return true;
126 }
127 
extract(const std::string & fileName)128 bool ThemeLoader::extract( const std::string &fileName )
129 {
130     bool result = true;
131     std::string tempPath = getTmpDir();
132     if( tempPath.empty() )
133         return false;
134 
135     if( unarchive( fileName, tempPath ) == false )
136     {
137         msg_Err( getIntf(), "extraction from %s failed", fileName.c_str() );
138         return false;
139     }
140 
141     std::string path;
142     std::string xmlFile;
143     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
144     // Find the XML file in the theme
145     if( findFile( tempPath, DEFAULT_XML_FILE, xmlFile ) )
146     {
147         path = getFilePath( xmlFile );
148     }
149     else
150     {
151         // No XML file, check if it is a winamp2 skin
152         std::string mainBmp;
153         if( findFile( tempPath, "main.bmp", mainBmp ) )
154         {
155             msg_Dbg( getIntf(), "trying to load a winamp2 skin" );
156             path = getFilePath( mainBmp );
157 
158             // Look for winamp2.xml in the resource path
159             std::list<std::string> resPath = pOsFactory->getResourcePath();
160             std::list<std::string>::const_iterator it;
161             for( it = resPath.begin(); it != resPath.end(); ++it )
162             {
163                 if( findFile( *it, WINAMP2_XML_FILE, xmlFile ) )
164                     break;
165             }
166         }
167     }
168 
169     if( !xmlFile.empty() )
170     {
171         // Parse the XML file
172         if (! parse( path, xmlFile ) )
173         {
174             msg_Err( getIntf(), "error while parsing %s", xmlFile.c_str() );
175             result = false;
176         }
177     }
178     else
179     {
180         msg_Err( getIntf(), "no XML found in theme %s", fileName.c_str() );
181         result = false;
182     }
183 
184     // Clean-up
185     deleteTempFiles( tempPath );
186     return result;
187 }
188 
unarchive(const std::string & fileName,const std::string & tempPath)189 bool ThemeLoader::unarchive( const std::string& fileName, const std::string &tempPath )
190 {
191 #define UPTR_HELPER(type,deleter) []( type * data ) { \
192         return std::unique_ptr< type, decltype( deleter )> ( data, deleter ); }
193 
194     auto make_input_node_ptr = UPTR_HELPER( input_item_node_t, &input_item_node_Delete );
195     auto make_input_item_ptr = UPTR_HELPER( input_item_t, &input_item_Release );
196     auto make_stream_ptr = UPTR_HELPER( stream_t, &vlc_stream_Delete );
197     auto make_cstr_ptr = UPTR_HELPER( char, &std::free );
198 
199 #undef UPTR_HELPER
200 
201     auto uri = make_cstr_ptr( vlc_path2uri( fileName.c_str(), "file" ) );
202     if( !uri )
203     {
204         msg_Err( getIntf(), "unable to convert %s to local URI",
205                             fileName.c_str() );
206         return false;
207     }
208 
209     auto input = make_stream_ptr( vlc_stream_NewURL( getIntf(), uri.get() ) );
210     if( !input )
211     {
212         msg_Err( getIntf(), "unable to open %s", uri.get() );
213         return false;
214     }
215 
216     stream_t* stream = input.get();
217     if( vlc_stream_directory_Attach( &stream, NULL ) )
218     {
219         msg_Err( getIntf(), "unable to attach stream_directory, treat as XML!" );
220     }
221     else
222     {
223         input.release();
224         input.reset( stream );
225 
226         auto item = make_input_item_ptr( input_item_New( "vlc://dummy", "vlc://dummy" ) );
227         auto node = make_input_node_ptr( (input_item_node_t*)std::calloc( 1, sizeof( input_item_node_t ) ) );
228 
229         if( !item || !node )
230             return false;
231 
232         input_item_AddOption( item.get(), "ignore-filetypes=\"\"", VLC_INPUT_OPTION_TRUSTED );
233         input_item_AddOption( item.get(), "extractor-flatten", VLC_INPUT_OPTION_TRUSTED );
234         node->p_item = item.release();
235 
236         if( vlc_stream_ReadDir( input.get(), node.get() ) )
237         {
238             msg_Err( getIntf(), "unable to read items in %s", uri.get() );
239             return false;
240         }
241 
242         for( int i = 0; i < node->i_children; ++i )
243         {
244             auto child = node->pp_children[i]->p_item;
245             auto child_stream = make_stream_ptr( vlc_stream_NewMRL( getIntf(), child->psz_uri ) );
246             if( !child_stream )
247             {
248                 msg_Err( getIntf(), "unable to open %s for reading", child->psz_name );
249                 return false;
250             }
251 
252             auto out_path = tempPath + "/" + child->psz_name;
253 
254             { /* create directory tree */
255                 auto out_directory = out_path.substr( 0, out_path.find_last_of( '/' ) );
256 
257                 if( makedir( out_directory.c_str() ) == false )
258                 {
259                     msg_Err( getIntf(), "failed to create directory tree for %s (%s)",
260                              out_path.c_str(), out_directory.c_str() );
261 
262                     return false;
263                 }
264             }
265 
266             { /* write data to disk */
267                 std::string contents;
268 
269                 char buf[1024];
270                 ssize_t n;
271 
272                 while( ( n = vlc_stream_Read( child_stream.get(), buf, sizeof buf ) ) > 0 )
273                     contents.append( buf, n );
274 
275                 std::ofstream out_stream( out_path, std::ios::binary );
276 
277                 if( out_stream.write( contents.data(), contents.size() ) )
278                 {
279                     msg_Dbg( getIntf(), "finished writing %zu bytes to %s",
280                         size_t{ contents.size() }, out_path.c_str() );
281                 }
282                 else
283                 {
284                     msg_Err( getIntf(), "unable to write %zu bytes to %s",
285                         size_t{ contents.size() }, out_path.c_str() );
286                     return false;
287                 }
288             }
289         }
290     }
291 
292     return true;
293 }
294 
deleteTempFiles(const std::string & path)295 void ThemeLoader::deleteTempFiles( const std::string &path )
296 {
297     OSFactory::instance( getIntf() )->rmDir( path );
298 }
299 
parse(const std::string & path,const std::string & xmlFile)300 bool ThemeLoader::parse( const std::string &path, const std::string &xmlFile )
301 {
302     // File loaded
303     msg_Dbg( getIntf(), "using skin file: %s", xmlFile.c_str() );
304 
305     // Start the parser
306     SkinParser parser( getIntf(), xmlFile, path );
307     if( ! parser.parse() )
308         return false;
309 
310     // Build and store the theme
311     Builder builder( getIntf(), parser.getData(), path );
312     getIntf()->p_sys->p_theme = builder.build();
313 
314     return true;
315 }
316 
317 
getFilePath(const std::string & rFullPath)318 std::string ThemeLoader::getFilePath( const std::string &rFullPath )
319 {
320     OSFactory *pOsFactory = OSFactory::instance( getIntf() );
321     const std::string &sep = pOsFactory->getDirSeparator();
322     // Find the last separator ('/' or '\')
323     std::string::size_type p = rFullPath.rfind( sep, rFullPath.size() );
324     std::string basePath;
325     if( p != std::string::npos )
326     {
327         if( p < rFullPath.size() - 1)
328         {
329             basePath = rFullPath.substr( 0, p );
330         }
331         else
332         {
333             basePath = rFullPath;
334         }
335     }
336     return basePath;
337 }
338 
findFile(const std::string & rootDir,const std::string & rFileName,std::string & themeFilePath)339 bool ThemeLoader::findFile( const std::string &rootDir, const std::string &rFileName,
340                             std::string &themeFilePath )
341 {
342     // Path separator
343     const std::string &sep = OSFactory::instance( getIntf() )->getDirSeparator();
344 
345     const char *pszDirContent;
346 
347     // Open the dir
348     DIR *pCurrDir = vlc_opendir( rootDir.c_str() );
349 
350     if( pCurrDir == NULL )
351     {
352         // An error occurred
353         msg_Dbg( getIntf(), "cannot open directory %s", rootDir.c_str() );
354         return false;
355     }
356 
357     // While we still have entries in the directory
358     while( ( pszDirContent = vlc_readdir( pCurrDir ) ) != NULL )
359     {
360         std::string newURI = rootDir + sep + pszDirContent;
361 
362         // Skip . and ..
363         if( std::string( pszDirContent ) != "." &&
364             std::string( pszDirContent ) != ".." )
365         {
366 #if defined( S_ISDIR )
367             struct stat stat_data;
368 
369             if( ( vlc_stat( newURI.c_str(), &stat_data ) == 0 )
370              && S_ISDIR(stat_data.st_mode) )
371 #elif defined( DT_DIR )
372             if( pDirContent->d_type & DT_DIR )
373 #else
374             if( 0 )
375 #endif
376             {
377                 // Can we find the file in this subdirectory?
378                 if( findFile( newURI, rFileName, themeFilePath ) )
379                 {
380                     closedir( pCurrDir );
381                     return true;
382                 }
383             }
384             else
385             {
386                 // Found the theme file?
387                 if( rFileName == std::string( pszDirContent ) )
388                 {
389                     themeFilePath = newURI;
390                     closedir( pCurrDir );
391                     return true;
392                 }
393             }
394         }
395     }
396 
397     closedir( pCurrDir );
398     return false;
399 }
400 
401 // FIXME: could become a skins2 OS factory function or a vlc core function
getTmpDir()402 std::string ThemeLoader::getTmpDir( )
403 {
404 #if defined( _WIN32 )
405     wchar_t *tmpdir = _wtempnam( NULL, L"vlt" );
406     if( tmpdir == NULL )
407         return "";
408     char* utf8 = FromWide( tmpdir );
409     free( tmpdir );
410     std::string tempPath( utf8 ? utf8 : "" );
411     free( utf8 );
412     return tempPath;
413 
414 #elif defined( __OS2__ )
415     char *tmpdir = tempnam( NULL, "vlt" );
416     if( tmpdir == NULL )
417         return "";
418     std::string tempPath( sFromLocale( tmpdir ));
419     free( tmpdir );
420     return tempPath;
421 
422 #else
423     char templ[] = "/tmp/vltXXXXXX";
424     char *tmpdir = mkdtemp( templ );
425     return std::string( tmpdir ? tmpdir : "");
426 #endif
427 }
428 
429 
430