1 /*****************************************************************************
2 * zipstream.c: stream_filter that creates a XSPF playlist from a Zip archive
3 *****************************************************************************
4 * Copyright (C) 2009 the VideoLAN team
7 * Authors: Jean-Philippe André <jpeg@videolan.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /** **************************************************************************
26 *****************************************************************************/
36 #include <vlc_input.h>
38 #define FILENAME_TEXT N_( "Media in Zip" )
39 #define FILENAME_LONGTEXT N_( "Path to the media in the Zip archive" )
41 /** **************************************************************************
43 *****************************************************************************/
45 set_shortname( "Zip" )
46 set_category( CAT_INPUT )
47 set_subcategory( SUBCAT_INPUT_STREAM_FILTER )
48 set_description( N_( "Zip files filter" ) )
49 set_capability( "stream_filter", 1 )
50 set_callbacks( StreamOpen, StreamClose )
52 set_subcategory( SUBCAT_INPUT_ACCESS )
53 set_description( N_( "Zip access" ) )
54 set_capability( "access", 0 )
55 add_shortcut( "unzip" )
57 set_callbacks( AccessOpen, AccessClose )
60 /** *************************************************************************
62 ****************************************************************************/
63 static int Read ( stream_t *, void *p_read, unsigned int i_read );
64 static int Peek ( stream_t *, const uint8_t **pp_peek, unsigned int i_peek );
65 static int Control( stream_t *, int i_query, va_list );
67 typedef struct node node;
68 typedef struct item item;
70 static int CreatePlaylist( stream_t *s, char **pp_buffer );
71 static int GetFilesInZip( stream_t*, unzFile, vlc_array_t*, vlc_array_t* );
72 static node* findOrCreateParentNode( node *root, const char *fullpath );
73 static int WriteXSPF( char **pp_buffer, vlc_array_t *p_filenames,
74 const char *psz_zippath );
75 static int nodeToXSPF( char **pp_buffer, node *n, bool b_root );
76 static node* findOrCreateParentNode( node *root, const char *fullpath );
78 /** **************************************************************************
80 *****************************************************************************/
83 /* zlib / unzip members */
85 zlib_filefunc_def *fileFunctions;
106 /** **************************************************************************
108 *****************************************************************************/
110 inline static node* new_node( char *name )
112 node *n = (node*) calloc( 1, sizeof(node) );
113 n->name = convert_xml_special_chars( name );
117 inline static item* new_item( int id )
119 item *media = (item*) calloc( 1, sizeof(item) );
124 inline static void free_all_node( node *root )
128 free_all_node( root->child );
130 node *tmp = root->next;
136 /* Allocate strcat and format */
137 static int astrcatf( char **ppsz_dest, const char *psz_fmt_src, ... )
140 va_start( args, psz_fmt_src );
143 int i_ret = vasprintf( &psz_tmp, psz_fmt_src, args );
144 if( i_ret == -1 ) return -1;
148 int i_len = strlen( *ppsz_dest ) + strlen( psz_tmp ) + 1;
149 char *psz_out = realloc( *ppsz_dest, i_len );
150 if( !psz_out ) return -1;
152 strcat( psz_out, psz_tmp );
155 *ppsz_dest = psz_out;
159 /** **************************************************************************
160 * Zip file identifier
161 *****************************************************************************/
162 static const uint8_t p_zip_marker[] = { 0x50, 0x4b, 0x03, 0x04 }; // "PK^C^D"
163 static const int i_zip_marker = 4;
166 /** **************************************************************************
168 *****************************************************************************/
169 int StreamOpen( vlc_object_t *p_this )
171 stream_t *s = (stream_t*) p_this;
174 /* Verify file format */
175 const uint8_t *p_peek;
176 if( stream_Peek( s->p_source, &p_peek, i_zip_marker ) < i_zip_marker )
178 if( memcmp( p_peek, p_zip_marker, i_zip_marker ) )
181 s->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
187 s->pf_control = Control;
189 p_sys->fileFunctions = ( zlib_filefunc_def * )
190 calloc( 1, sizeof( zlib_filefunc_def ) );
191 if( !p_sys->fileFunctions )
196 p_sys->fileFunctions->zopen_file = ZipIO_Open;
197 p_sys->fileFunctions->zread_file = ZipIO_Read;
198 p_sys->fileFunctions->zwrite_file = ZipIO_Write;
199 p_sys->fileFunctions->ztell_file = ZipIO_Tell;
200 p_sys->fileFunctions->zseek_file = ZipIO_Seek;
201 p_sys->fileFunctions->zclose_file = ZipIO_Close;
202 p_sys->fileFunctions->zerror_file = ZipIO_Error;
203 p_sys->fileFunctions->opaque = ( void * ) s;
204 p_sys->zipFile = unzOpen2( NULL /* path */, p_sys->fileFunctions );
205 if( !p_sys->zipFile )
207 msg_Warn( s, "unable to open file" );
208 free( p_sys->fileFunctions );
213 /* Find the stream uri */
215 if( asprintf( &psz_tmp, "%s.xspf", s->psz_path ) == -1 )
217 free( p_sys->fileFunctions );
221 p_sys->psz_path = s->psz_path;
222 s->psz_path = psz_tmp;
227 /** *************************************************************************
229 ****************************************************************************/
230 void StreamClose( vlc_object_t *p_this )
232 stream_t *s = (stream_t*)p_this;
233 stream_sys_t *p_sys = s->p_sys;
236 unzClose( p_sys->zipFile );
238 free( p_sys->fileFunctions );
239 free( p_sys->psz_xspf );
240 free( p_sys->psz_path );
244 /** *************************************************************************
245 * Stream filters functions
246 ****************************************************************************/
248 /** *************************************************************************
250 ****************************************************************************/
251 static int Read( stream_t *s, void *p_read, unsigned int i_read )
253 stream_sys_t *p_sys = s->p_sys;
255 if( !p_read ) return 0;
257 /* Fill the buffer */
258 if( p_sys->psz_xspf == NULL )
260 int i_ret = CreatePlaylist( s, &p_sys->psz_xspf );
263 p_sys->i_len = strlen( p_sys->psz_xspf );
267 /* Read the buffer */
268 int i_len = __MIN( i_read, p_sys->i_len - p_sys->i_pos );
269 memcpy( p_read, p_sys->psz_xspf + p_sys->i_pos, i_len );
270 p_sys->i_pos += i_len;
275 /** *************************************************************************
277 ****************************************************************************/
278 static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned int i_peek )
280 stream_sys_t *p_sys = s->p_sys;
282 /* Fill the buffer */
283 if( p_sys->psz_xspf == NULL )
285 int i_ret = CreatePlaylist( s, &p_sys->psz_xspf );
288 p_sys->i_len = strlen( p_sys->psz_xspf );
293 /* Point to the buffer */
294 int i_len = __MIN( i_peek, p_sys->i_len - p_sys->i_pos );
295 *pp_peek = (uint8_t*) p_sys->psz_xspf + p_sys->i_pos;
300 /** *************************************************************************
302 ****************************************************************************/
303 static int Control( stream_t *s, int i_query, va_list args )
305 stream_sys_t *p_sys = s->p_sys;
309 case STREAM_SET_POSITION:
311 uint64_t i_position = va_arg( args, uint64_t );
312 if( i_position >= p_sys->i_len )
316 p_sys->i_pos = (size_t) i_position;
321 case STREAM_GET_POSITION:
323 uint64_t *pi_position = va_arg( args, uint64_t* );
324 *pi_position = p_sys->i_pos;
328 case STREAM_GET_SIZE:
330 uint64_t *pi_size = va_arg( args, uint64_t* );
331 *pi_size = p_sys->i_len;
335 case STREAM_GET_CONTENT_TYPE:
338 case STREAM_UPDATE_SIZE:
339 case STREAM_CONTROL_ACCESS:
340 case STREAM_CAN_SEEK:
341 case STREAM_CAN_FASTSEEK:
342 case STREAM_SET_RECORD_STATE:
343 return stream_vaControl( s->p_source, i_query, args );
350 static int CreatePlaylist( stream_t *s, char **pp_buffer )
352 /* Get some infos about zip archive */
354 unzFile file = s->p_sys->zipFile;
355 vlc_array_t *p_filenames = vlc_array_new(); /* Will contain char* */
357 /* List all file names in Zip archive */
358 i_ret = GetFilesInZip( s, file, p_filenames, NULL );
368 s->p_sys->zipFile = NULL;
370 /* Construct the xspf playlist */
371 i_ret = WriteXSPF( pp_buffer, p_filenames, s->p_sys->psz_path );
378 for( int i = 0; i < vlc_array_count( p_filenames ); i++ )
380 free( vlc_array_item_at_index( p_filenames, i ) );
382 vlc_array_destroy( p_filenames );
387 /** **************************************************************************
388 * Zip utility functions
389 *****************************************************************************/
391 /** **************************************************************************
392 * \brief List files in zip and append their names to p_array
394 * \param file Opened zip file
395 * \param p_array vlc_array_t which will receive all filenames
397 * In case of error, returns VLC_EGENERIC.
398 * In case of success, returns number of files found, and goes back to first file.
399 *****************************************************************************/
400 static int GetFilesInZip( stream_t *p_this, unzFile file,
401 vlc_array_t *p_filenames, vlc_array_t *p_fileinfos )
403 if( !p_filenames || !p_this )
408 /* Get global info */
409 unz_global_info info;
411 if( unzGetGlobalInfo( file, &info ) != UNZ_OK )
413 msg_Warn( p_this, "this is not a valid zip archive" );
417 /* Go to first file in archive */
418 unzGoToFirstFile( file );
420 /* Get info about each file */
421 for( unsigned long i = 0; i < info.number_entry; i++ )
423 char *psz_fileName = calloc( ZIP_FILENAME_LEN, 1 );
424 unz_file_info *p_fileInfo = calloc( 1, sizeof( unz_file_info ) );
426 if( !p_fileInfo || !psz_fileName )
428 free( psz_fileName );
433 if( unzGetCurrentFileInfo( file, p_fileInfo, psz_fileName,
434 ZIP_FILENAME_LEN, NULL, 0, NULL, 0 )
437 msg_Warn( p_this, "can't get info about file in zip" );
442 vlc_array_append( p_filenames, strdup( psz_fileName ) );
443 free( psz_fileName );
446 vlc_array_append( p_fileinfos, p_fileInfo );
450 if( i < ( info.number_entry - 1 ) )
452 /* Go the next file in the archive */
453 if( unzGoToNextFile( file ) != UNZ_OK )
455 msg_Warn( p_this, "can't go to next file in zip" );
463 /* i_ret should be equal to info.number_entry */
464 unzGoToFirstFile( file );
469 /** **************************************************************************
470 * XSPF generation functions
471 *****************************************************************************/
473 /** **************************************************************************
474 * \brief Check a character for allowance in the Xml.
475 * Allowed chars are: a-z, A-Z, 0-9, \, /, ., ' ', _ and :
476 *****************************************************************************/
477 bool isAllowedChar( char c )
479 return ( c >= 'a' && c <= 'z' )
480 || ( c >= 'A' && c <= 'Z' )
481 || ( c >= '0' && c <= '9' )
482 || ( c == ':' ) || ( c == '/' )
483 || ( c == '\\' ) || ( c == '.' )
484 || ( c == ' ' ) || ( c == '_' );
487 /** **************************************************************************
488 * \brief Escape string to be XML valid
489 * Allowed chars are defined by the above function isAllowedChar()
490 * Invalid chars are escaped using non standard '?XX' notation.
491 * NOTE: We cannot trust VLC internal Web encoding functions
492 * because they are not able to encode and decode some rare utf-8
493 * characters properly. Also, we don't control exactly when they are
494 * called (from this module).
495 *****************************************************************************/
496 static int escapeToXml( char **ppsz_encoded, const char *psz_url )
498 char *psz_iter, *psz_tmp;
500 /* Count number of unallowed characters in psz_url */
501 size_t i_num = 0, i_len = 0;
502 for( psz_iter = (char*) psz_url; *psz_iter; ++psz_iter )
504 if( isAllowedChar( *psz_iter ) )
518 *ppsz_encoded = malloc( i_len + 1 );
519 memcpy( *ppsz_encoded, psz_url, i_len + 1 );
523 /* Copy string, replacing invalid characters */
524 char *psz_ret = malloc( i_len + 3*i_num + 2 );
525 if( !psz_ret ) return VLC_ENOMEM;
527 for( psz_iter = (char*) psz_url, psz_tmp = psz_ret;
528 *psz_iter; ++psz_iter, ++psz_tmp )
530 if( isAllowedChar( *psz_iter ) )
532 *psz_tmp = *psz_iter;
537 snprintf( psz_tmp, 3, "%02x", ( *psz_iter & 0x000000FF ) );
544 *ppsz_encoded = psz_ret;
548 /** **************************************************************************
549 * \brief Write the XSPF playlist given the list of files
550 *****************************************************************************/
551 static int WriteXSPF( char **pp_buffer, vlc_array_t *p_filenames,
552 const char *psz_zippath )
554 char *psz_zip = strrchr( psz_zippath, DIR_SEP_CHAR );
555 psz_zip = convert_xml_special_chars( psz_zip ? (psz_zip+1) : psz_zippath );
557 if( asprintf( pp_buffer, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
558 "<playlist version=\"1\" xmlns=\"http://xspf.org/ns/0/\" "
559 "xmlns:vlc=\"http://www.videolan.org/vlc/playlist/ns/0/\">\n"
560 " <title>%s</title>\n"
561 " <trackList>\n", psz_zip ) == -1)
565 node *playlist = new_node( psz_zip );
567 /* Encode the URI and append ZIP_SEP */
569 escapeToXml( &psz_pathtozip, psz_zippath );
570 if( astrcatf( &psz_pathtozip, "%s", ZIP_SEP ) < 0 ) return -1;
573 for( int i = 0; i < vlc_array_count( p_filenames ); ++i )
575 char *psz_name = (char*) vlc_array_item_at_index( p_filenames, i );
576 int i_len = strlen( psz_name );
578 if( !i_len ) continue;
580 /* Is it a folder ? */
581 if( psz_name[i_len-1] == '/' )
587 /* Extract file name */
588 char *psz_file = strrchr( psz_name, '/' );
589 psz_file = convert_xml_special_chars( psz_file ?
590 (psz_file+1) : psz_name );
593 char *psz_path = strdup( psz_pathtozip );
594 char *psz_escapedName;
595 escapeToXml( &psz_escapedName, psz_name );
596 if( astrcatf( &psz_path, "%s", psz_escapedName ) < 0 ) return -1;
598 /* Track information */
599 if( astrcatf( pp_buffer,
601 " <location>zip://%s</location>\n"
602 " <title>%s</title>\n"
603 " <extension application=\"http://www.videolan.org/vlc/playlist/0\">\n"
604 " <vlc:id>%d</vlc:id>\n"
607 psz_path, psz_file, i_track ) < 0 ) return -1;
612 /* Find the parent node */
613 node *parent = findOrCreateParentNode( playlist, psz_name );
616 /* Add the item to this node */
617 item *tmp = parent->media;
620 parent->media = new_item( i_track );
628 tmp->next = new_item( i_track );
635 free( psz_pathtozip );
637 /* Close tracklist, open the extension */
638 if( astrcatf( pp_buffer,
640 " <extension application=\"http://www.videolan.org/vlc/playlist/0\">\n"
644 if( nodeToXSPF( pp_buffer, playlist, true ) < 0 ) return -1;
646 /* Close extension and playlist */
647 if( astrcatf( pp_buffer, " </extension>\n</playlist>\n" ) < 0 ) return -1;
649 /* printf( "%s", *pp_buffer ); */
651 free_all_node( playlist );
656 /** **************************************************************************
657 * \brief Recursively convert a node to its XSPF representation
658 *****************************************************************************/
659 static int nodeToXSPF( char **pp_buffer, node *n, bool b_root )
663 if( astrcatf( pp_buffer, " <vlc:node title=\"%s\">\n", n->name ) < 0 )
667 nodeToXSPF( pp_buffer, n->child, false );
671 if( astrcatf( pp_buffer, " <vlc:item tid=\"%d\" />\n", i->id ) < 0 )
677 if( astrcatf( pp_buffer, " </vlc:node>\n" ) < 0 )
683 /** **************************************************************************
684 * \brief Either create or find the parent node of the item
685 *****************************************************************************/
686 static node* findOrCreateParentNode( node *root, const char *fullpath )
689 char *path = strdup( fullpath );
694 char *sep = strchr( folder, '/' );
704 node *current = root->child;
708 if( !strcmp( current->name, folder ) )
710 /* We found the folder, go recursively deeper */
711 return findOrCreateParentNode( current, sep );
713 current = current->next;
716 /* If we are here, then it means that we did not find the parent */
717 node *ret = new_node( folder );
722 current = root->child;
723 while( current->next )
725 current = current->next;
730 /* And now, create the subfolders */
731 ret = findOrCreateParentNode( ret, sep );
738 /** **************************************************************************
739 * ZipIO function definitions : how to use vlc_stream to read the zip
740 *****************************************************************************/
742 /** **************************************************************************
743 * \brief interface for unzip module to open a file using a vlc_stream
746 * \param mode how to open the file (read/write ?). We support only read
748 *****************************************************************************/
749 static void ZCALLBACK *ZipIO_Open( void *opaque, const char *file, int mode )
752 stream_t *s = (stream_t*) opaque;
753 if( mode & ( ZLIB_FILEFUNC_MODE_CREATE | ZLIB_FILEFUNC_MODE_WRITE ) )
755 msg_Dbg( s, "ZipIO_Open: we cannot write into zip files" );
761 /** **************************************************************************
762 * \brief read something from stream into buffer
763 * \param opaque should be the stream
764 * \param stream stream created by ZipIO_Open
765 * \param buf buffer to read the file
766 * \param size length of this buffer
767 * \return return the number of bytes read (<= size)
768 *****************************************************************************/
769 static unsigned long ZCALLBACK ZipIO_Read( void *opaque, void *stream,
770 void *buf, unsigned long size )
773 stream_t *s = (stream_t*) opaque;
774 return (unsigned long) stream_Read( s->p_source, buf, (int) size );
777 /** **************************************************************************
778 * \brief tell size of stream
779 * \param opaque should be the stream
780 * \param stream stream created by ZipIO_Open
781 * \return size of the file / stream
782 * ATTENTION: this is not stream_Tell, but stream_Size !
783 *****************************************************************************/
784 static long ZCALLBACK ZipIO_Tell( void *opaque, void *stream )
787 stream_t *s = (stream_t*) opaque;
788 return (long) stream_Size( s->p_source ); /* /!\ not stream_Tell /!\ */
791 /** **************************************************************************
792 * \brief seek in the stream
793 * \param opaque should be the stream
794 * \param stream stream created by ZipIO_Open
795 * \param offset positive offset to seek
796 * \param origin current position in stream
797 * \return ¿ VLC_SUCCESS or an error code ?
798 *****************************************************************************/
799 static long ZCALLBACK ZipIO_Seek ( void *opaque, void *stream,
800 unsigned long offset, int origin )
803 stream_t *s = (stream_t*) opaque;
806 uint64_t pos = offset + origin;
807 l_ret = (long) stream_Seek( s->p_source, pos );
811 /** **************************************************************************
812 * \brief close the stream
813 * \param opaque should be the stream
814 * \param stream stream created by ZipIO_Open
815 * \return always VLC_SUCCESS
816 * This closes zip archive
817 *****************************************************************************/
818 static int ZCALLBACK ZipIO_Close ( void *opaque, void *stream )
822 // stream_t *s = (stream_t*) opaque;
823 // if( p_demux->p_sys && p_demux->p_sys->zipFile )
824 // p_demux->p_sys->zipFile = NULL;
825 // stream_Seek( s->p_source, 0 );
829 /** **************************************************************************
830 * \brief I/O functions for the ioapi: write (assert insteadof segfault)
831 *****************************************************************************/
832 static uLong ZCALLBACK ZipIO_Write( void* opaque, void* stream,
833 const void* buf, uLong size )
835 (void)opaque; (void)stream; (void)buf; (void)size;
836 int ERROR_zip_cannot_write_this_should_not_happen = 0;
837 assert( ERROR_zip_cannot_write_this_should_not_happen );
841 /** **************************************************************************
842 * \brief I/O functions for the ioapi: test error (man 3 ferror)
843 *****************************************************************************/
844 static int ZCALLBACK ZipIO_Error( void* opaque, void* stream )