Commit bc72d967 authored by Jean-Philippe Andre's avatar Jean-Philippe Andre

Zip: add a stream_filter and an access

The stream_filter does the following job:
[zip stream] -->filter--> [xspf playlist]

The access is called zip or unzip and takes MRLs of the form:
zip://[web-encoded-path]![file-in-zip]

As there is some automatic web-form decoding of the MRL,
the MRLs in the playlist generated by zipstream are encoded
two times.
parent 7032aafa
if HAVE_ZLIB
SOURCES_zip = \
zipstream.c \
zipaccess.c \
$(NULL)
endif
/*****************************************************************************
* zip.h: Module (access+demux) to extract different archives, based on zlib
*****************************************************************************
* Copyright (C) 2007 the VideoLAN team
* $Id$
*
* Authors: Jean-Philippe André <jpeg@videolan.org>
*
* 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.
*****************************************************************************/
/** **************************************************************************
* Common includes and shared headers
*****************************************************************************/
#ifndef ZIP_ACCESSDEMUX_H
#define ZIP_ACCESSDEMUX_H
#include <vlc/vlc.h>
#include <vlc_common.h>
#include <vlc_url.h>
#include <vlc_strings.h>
#include <vlc_arrays.h>
#include <vlc_plugin.h>
#include <vlc_stream.h>
#include "unzip.h"
#include "ioapi.h"
#include <assert.h>
#define ZIP_FILENAME_LEN 512
#define ZIP_BUFFER_LEN 32768
#define ZIP_SEP "|"
#define ZIP_SEP_CHAR '|'
/** **************************************************************************
* Module access points: stream_filter
*****************************************************************************/
int StreamOpen( vlc_object_t* );
void StreamClose( vlc_object_t* );
/** **************************************************************************
* Module access points: access
*****************************************************************************/
int AccessOpen( vlc_object_t *p_this );
void AccessClose( vlc_object_t *p_this );
/** **************************************************************************
* zipIO function headers : how to use vlc_stream to read the zip
* Note: static because the implementations differ
*****************************************************************************/
static void* ZCALLBACK ZipIO_Open( void* opaque, const char* filename, int m );
static uLong ZCALLBACK ZipIO_Read( void*, void* stream, void* buf, uLong sz );
static uLong ZCALLBACK ZipIO_Write( void*, void* stream, const void*, uLong );
static long ZCALLBACK ZipIO_Tell( void*, void* stream );
static long ZCALLBACK ZipIO_Seek( void*, void* stream, uLong offset, int ori );
static int ZCALLBACK ZipIO_Close( void*, void* stream );
static int ZCALLBACK ZipIO_Error( void*, void* stream );
#endif /* ZIP_ACCESSDEMUX_H */
/*****************************************************************************
* zipaccess.c: Module (access) to extract different archives, based on zlib
*****************************************************************************
* Copyright (C) 2007 the VideoLAN team
* $Id$
*
* Authors: Jean-Philippe André <jpeg@videolan.org>
*
* 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.
*****************************************************************************/
/** @todo:
* - implement crypto (using url zip://user:password@path-to-archive#ZIP#file
* - read files in zip with long name (use unz_file_info.size_filename)
* - multi-volume archive support ?
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef HAVE_ZLIB_H
#include "zip.h"
#include <vlc_access.h>
/** **************************************************************************
* This is our own access_sys_t for zip files
*****************************************************************************/
struct access_sys_t
{
/* zlib / unzip members */
unzFile zipFile;
zlib_filefunc_def *fileFunctions;
/* file in zip information */
char *psz_fileInzip;
};
static int AccessControl( access_t *p_access, int i_query, va_list args );
static ssize_t AccessRead( access_t *, uint8_t *, size_t );
static int AccessSeek( access_t *, int64_t );
static int OpenFileInZip( access_t *p_access, int i_pos );
/** **************************************************************************
* \brief Open access
*****************************************************************************/
int AccessOpen( vlc_object_t *p_this )
{
access_t *p_access = (access_t*)p_this;
access_sys_t *p_sys;
int i_ret = VLC_EGENERIC;
unzFile file = 0;
char *psz_pathToZip = NULL, *psz_path = NULL, *psz_sep = NULL;
p_access->p_sys = p_sys = (access_sys_t*)
calloc( sizeof( access_sys_t ), 1 );
if( !p_sys )
return VLC_ENOMEM;
/* Split the MRL */
psz_path = strdup( p_access->psz_path );
psz_sep = strchr( psz_path, ZIP_SEP_CHAR );
if( !psz_sep )
return VLC_EGENERIC;
*psz_sep = '\0';
psz_pathToZip = unescape_URI_duplicate( psz_path );
p_sys->psz_fileInzip = strdup( psz_sep + 1 );
/* Define IO functions */
zlib_filefunc_def *p_func = (zlib_filefunc_def*)
calloc( 1, sizeof( zlib_filefunc_def ) );
p_func->zopen_file = ZipIO_Open;
p_func->zread_file = ZipIO_Read;
p_func->zwrite_file = ZipIO_Write; // see comment
p_func->ztell_file = ZipIO_Tell;
p_func->zseek_file = ZipIO_Seek;
p_func->zclose_file = ZipIO_Close;
p_func->zerror_file = ZipIO_Error;
p_func->opaque = p_access;
/* Open zip archive */
file = p_access->p_sys->zipFile = unzOpen2( psz_pathToZip, p_func );
if( !file )
{
msg_Err( p_access, "not a valid zip archive: '%s'", psz_pathToZip );
goto exit;
}
/* Open file in zip */
OpenFileInZip( p_access, 0 );
/* Set callback */
ACCESS_SET_CALLBACKS( AccessRead, NULL, AccessControl, AccessSeek );
/* Get some infos about current file. Maybe we could want some more ? */
unz_file_info z_info;
unzGetCurrentFileInfo( file, &z_info, NULL, 0, NULL, 0, NULL, 0 );
/* Set access informations: size is needed for AccessSeek */
p_access->info.i_size = z_info.uncompressed_size;
p_access->info.i_pos = 0;
p_access->info.b_eof = false;
i_ret = VLC_SUCCESS;
exit:
if( i_ret != VLC_SUCCESS )
{
if( file )
{
unzCloseCurrentFile( file );
unzClose( file );
}
free( p_sys->psz_fileInzip );
free( p_sys->fileFunctions );
free( p_sys );
}
free( psz_pathToZip );
free( psz_path );
return i_ret;
}
/** **************************************************************************
* \brief Close access: free structures
*****************************************************************************/
void AccessClose( vlc_object_t *p_this )
{
access_t *p_access = (access_t*)p_this;
access_sys_t *p_sys = p_access->p_sys;
if( p_sys )
{
unzFile file = p_sys->zipFile;
if( file )
{
unzCloseCurrentFile( file );
unzClose( file );
}
free( p_sys->psz_fileInzip );
free( p_sys->fileFunctions );
free( p_sys );
}
var_Destroy( p_access, "zip-no-xspf" );
}
/** **************************************************************************
* \brief Control access
*****************************************************************************/
static int AccessControl( access_t *p_access, int i_query, va_list args )
{
bool *pb_bool;
int *pi_int;
int64_t *pi_64;
switch( i_query )
{
/* */
case ACCESS_CAN_SEEK:
case ACCESS_CAN_PAUSE:
case ACCESS_CAN_CONTROL_PACE:
pb_bool = (bool*)va_arg( args, bool* );
*pb_bool = true;
break;
case ACCESS_CAN_FASTSEEK:
pb_bool = (bool*)va_arg( args, bool* );
*pb_bool = false;
break;
/* */
case ACCESS_GET_MTU:
pi_int = (int*)va_arg( args, int * );
*pi_int = 0;
break;
case ACCESS_GET_PTS_DELAY:
pi_64 = (int64_t*)va_arg( args, int64_t * );
*pi_64 = DEFAULT_PTS_DELAY;
break;
/* */
case ACCESS_SET_PAUSE_STATE:
/* Nothing to do */
break;
case ACCESS_GET_TITLE_INFO:
case ACCESS_SET_TITLE:
case ACCESS_SET_SEEKPOINT:
case ACCESS_SET_PRIVATE_ID_STATE:
case ACCESS_GET_META:
case ACCESS_GET_PRIVATE_ID_STATE:
case ACCESS_GET_CONTENT_TYPE:
return VLC_EGENERIC;
default:
msg_Warn( p_access, "unimplemented query %d in control", i_query );
return VLC_EGENERIC;
}
return VLC_SUCCESS;
}
/** **************************************************************************
* \brief Read access
* Reads current opened file in zip. This does not open the file in zip.
* Return -1 if no data yet, 0 if no more data, else real data read
*****************************************************************************/
static ssize_t AccessRead( access_t *p_access, uint8_t *p_buffer, size_t sz )
{
access_sys_t *p_sys = p_access->p_sys;
assert( p_sys );
unzFile file = p_sys->zipFile;
if( !file )
{
msg_Err( p_access, "archive not opened !" );
return VLC_EGENERIC;
}
int i_read = 0;
i_read = unzReadCurrentFile( file, p_buffer, sz );
p_access->info.i_pos = unztell( file );
return ( i_read >= 0 ? i_read : VLC_EGENERIC );
}
/** **************************************************************************
* \brief Seek inside zip file
*****************************************************************************/
static int AccessSeek( access_t *p_access, int64_t seek_len )
{
access_sys_t *p_sys = p_access->p_sys;
assert( p_sys );
unzFile file = p_sys->zipFile;
if( !file )
{
msg_Err( p_access, "archive not opened !" );
return VLC_EGENERIC;
}
/* Reopen file in zip if needed */
if( p_access->info.i_pos != 0 )
{
OpenFileInZip( p_access, p_access->info.i_pos + seek_len );
}
/* Read seek_len data and drop it */
int i_seek = 0;
int i_read = 1;
char *p_buffer = ( char* ) calloc( 1, ZIP_BUFFER_LEN );
while( ( i_seek < seek_len ) && ( i_read > 0 ) )
{
i_read = ( seek_len - i_seek < ZIP_BUFFER_LEN )
? ( seek_len - i_seek ) : ZIP_BUFFER_LEN;
i_read = unzReadCurrentFile( file, p_buffer, i_read );
if( i_read < 0 )
{
msg_Warn( p_access, "could not seek in file" );
free( p_buffer );
return VLC_EGENERIC;
}
else
{
i_seek += i_read;
}
}
free( p_buffer );
p_access->info.i_pos = unztell( file );
return VLC_SUCCESS;
}
/** **************************************************************************
* \brief Open file in zip
*****************************************************************************/
static int OpenFileInZip( access_t *p_access, int i_pos )
{
access_sys_t *p_sys = p_access->p_sys;
unzFile file = p_sys->zipFile;
if( !p_sys->psz_fileInzip )
{
return VLC_EGENERIC;
}
i_pos = __MIN( i_pos, 0 );
p_access->info.i_pos = 0;
unzCloseCurrentFile( file ); /* returns UNZ_PARAMERROR if file not opened */
if( unzLocateFile( file, p_sys->psz_fileInzip, 0 ) != UNZ_OK )
{
msg_Err( p_access, "could not [re]locate file in zip: '%s'",
p_sys->psz_fileInzip );
return VLC_EGENERIC;
}
if( unzOpenCurrentFile( file ) != UNZ_OK )
{
msg_Err( p_access, "could not [re]open file in zip: '%s'",
p_sys->psz_fileInzip );
return VLC_EGENERIC;
}
if( i_pos > 0 )
return AccessSeek( p_access, i_pos );
else
return VLC_SUCCESS;
}
/** **************************************************************************
* \brief I/O functions for the ioapi: open (read only)
*****************************************************************************/
static void* ZCALLBACK ZipIO_Open( void* opaque, const char* file, int mode )
{
assert(opaque != NULL);
assert(mode == (ZLIB_FILEFUNC_MODE_READ | ZLIB_FILEFUNC_MODE_EXISTING));
access_t *p_access = (access_t*) opaque;
return stream_UrlNew( p_access, file );
}
/** **************************************************************************
* \brief I/O functions for the ioapi: read
*****************************************************************************/
static uLong ZCALLBACK ZipIO_Read( void* opaque, void* stream,
void* buf, uLong size )
{
(void)opaque;
//access_t *p_access = (access_t*) opaque;
//msg_Dbg(p_access, "read %d", size);
return stream_Read( (stream_t*) stream, buf, size );
}
/** **************************************************************************
* \brief I/O functions for the ioapi: write (assert insteadof segfault)
*****************************************************************************/
static uLong ZCALLBACK ZipIO_Write( void* opaque, void* stream,
const void* buf, uLong size )
{
(void)opaque; (void)stream; (void)buf; (void)size;
int zip_access_cannot_write_this_should_not_happen = 0;
assert(zip_access_cannot_write_this_should_not_happen);
return 0;
}
/** **************************************************************************
* \brief I/O functions for the ioapi: tell
*****************************************************************************/
static long ZCALLBACK ZipIO_Tell( void* opaque, void* stream )
{
(void)opaque;
int64_t i64_tell = stream_Tell( (stream_t*) stream );
//access_t *p_access = (access_t*) opaque;
//msg_Dbg(p_access, "tell %" PRIu64, i64_tell);
return (long)i64_tell;
}
/** **************************************************************************
* \brief I/O functions for the ioapi: seek
*****************************************************************************/
static long ZCALLBACK ZipIO_Seek( void* opaque, void* stream,
uLong offset, int origin )
{
(void)opaque;
//access_t *p_access = (access_t*) opaque;
int64_t pos = offset;
switch( origin )
{
case SEEK_CUR:
pos += stream_Tell( (stream_t*) stream );
break;
case SEEK_SET:
break;
case SEEK_END:
pos += stream_Size( (stream_t*) stream );
break;
default:
return -1;
}
//msg_Dbg( p_access, "seek (%d,%d): %" PRIu64, offset, origin, pos );
stream_Seek( (stream_t*) stream, pos );
/* Note: in unzip.c, unzlocal_SearchCentralDir seeks to the end of
the stream, which is doable but returns an error in VLC.
That's why we always assume this was OK. FIXME */
return 0;
}
/** **************************************************************************
* \brief I/O functions for the ioapi: close
*****************************************************************************/
static int ZCALLBACK ZipIO_Close( void* opaque, void* stream )
{
(void)opaque;
stream_Delete( (stream_t*) stream );
return 0;
}
/** **************************************************************************
* \brief I/O functions for the ioapi: test error (man 3 ferror)
*****************************************************************************/
static int ZCALLBACK ZipIO_Error( void* opaque, void* stream )
{
(void)opaque;
(void)stream;
//msg_Dbg( p_access, "error" );
return 0;
}
#else
# error Can not compile zip demuxer without zlib support
#endif
/*****************************************************************************
* zipstream.c: stream_filter that creates a XSPF playlist from a Zip archive
*****************************************************************************
* Copyright (C) 2007 the VideoLAN team
* $Id$
*
* Authors: Jean-Philippe André <jpeg@videolan.org>
*
* 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.
*****************************************************************************/
/** **************************************************************************
* Preamble
*****************************************************************************/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "zip.h"
#include <stddef.h>
/* FIXME remove */
#include <vlc_input.h>
#define FILENAME_TEXT N_( "Media in Zip" )
#define FILENAME_LONGTEXT N_( "Path to the media in the Zip archive" )
/** **************************************************************************
* Module descriptor
*****************************************************************************/
vlc_module_begin()
set_shortname( "Zip" )
set_category( CAT_INPUT )
set_subcategory( SUBCAT_INPUT_STREAM_FILTER )
set_description( _( "Zip files filter" ) )
set_capability( "stream_filter", 1 )
set_callbacks( StreamOpen, StreamClose )
add_submodule()
set_subcategory( SUBCAT_INPUT_ACCESS )
set_description( _( "Zip access" ) )
set_capability( "access", 70 )
add_shortcut( "unzip" )
add_shortcut( "zip" )
set_callbacks( AccessOpen, AccessClose )
vlc_module_end()
/** *************************************************************************
* Local prototypes
****************************************************************************/
static int Read ( stream_t *, void *p_read, unsigned int i_read );
static int Peek ( stream_t *, const uint8_t **pp_peek, unsigned int i_peek );
static int Control( stream_t *, int i_query, va_list );
typedef struct node node;
typedef struct item item;
static int CreatePlaylist( stream_t *s, char **pp_buffer );
static int GetFilesInZip( stream_t*, unzFile, vlc_array_t*, vlc_array_t* );
static node* findOrCreateParentNode( node *root, const char *fullpath );
static int WriteXSPF( char **pp_buffer, vlc_array_t *p_filenames,
const char *psz_zippath );
static int nodeToXSPF( char **pp_buffer, node *n, bool b_root );
static node* findOrCreateParentNode( node *root, const char *fullpath );
/** **************************************************************************
* Struct definitions
*****************************************************************************/
struct stream_sys_t
{
/* zlib / unzip members */
unzFile zipFile;
zlib_filefunc_def *fileFunctions;
char *psz_path;
/* xspf data */
char *psz_xspf;
size_t i_len;
size_t i_pos;
};
struct item {
int id;
item *next;
};
struct node {
char *name;
item *media;
node *child;
node *next;
};
/** **************************************************************************
* Some helpers
*****************************************************************************/
inline static node* new_node( char *name )
{
node *n = (node*) calloc( 1, sizeof(node) );
n->name = convert_xml_special_chars( name );
return n;
}
inline static item* new_item( int id )
{
item *media = (item*) calloc( 1, sizeof(item) );
media->id = id;
return media;
}
inline static void free_all_node( node *root )
{
while( root )
{
free_all_node( root->child );
free( root->name );
node *tmp = root->next;
free( root );
root = tmp;
}
}
/* Allocate strcat and format */
static int astrcatf( char **ppsz_dest, const char *psz_fmt_src, ... )
{
va_list args;
va_start( args, psz_fmt_src );
char *psz_tmp;
int i_ret = vasprintf( &psz_tmp, psz_fmt_src, args );
if( i_ret == -1 ) return -1;
va_end( args );
int i_len = strlen( *ppsz_dest ) + strlen( psz_tmp ) + 1;
char *psz_out = realloc( *ppsz_dest, i_len );
if( !psz_out ) return -1;
strcat( psz_out, psz_tmp );
free( psz_tmp );
*ppsz_dest = psz_out;
return i_len;
}
/** **************************************************************************
* Zip file identifier
*****************************************************************************/
static const uint8_t p_zip_marker[] = { 0x50, 0x4b, 0x03, 0x04 }; // "PK^C^D"
static const int i_zip_marker = 4;
/** **************************************************************************
* Open
*****************************************************************************/
int StreamOpen( vlc_object_t *p_this )
{
stream_t *s = (stream_t*) p_this;
stream_sys_t *p_sys;
/* Verify file format */
const uint8_t *p_peek;
if( stream_Peek( s->p_source, &p_peek, i_zip_marker ) < i_zip_marker )
return VLC_EGENERIC;
if( memcmp( p_peek, p_zip_marker, i_zip_marker ) )
return VLC_EGENERIC;
s->p_sys = p_sys = calloc( sizeof( *p_sys ), 1 );
if( !p_sys )
return VLC_ENOMEM;
s->pf_read = Read;
s->pf_peek = Peek;
s->pf_control = Control;
p_sys->fileFunctions = ( zlib_filefunc_def * )
calloc( 1, sizeof( zlib_filefunc_def ) );
if( !p_sys->fileFunctions )
{
free( p_sys );
return VLC_ENOMEM;
}
p_sys->fileFunctions->zopen_file = ZipIO_Open;
p_sys->fileFunctions->zread_file = ZipIO_Read;
p_sys->fileFunctions->zwrite_file = ZipIO_Write;
p_sys->fileFunctions->ztell_file = ZipIO_Tell;
p_sys->fileFunctions->zseek_file = ZipIO_Seek;
p_sys->fileFunctions->zclose_file = ZipIO_Close;
p_sys->fileFunctions->zerror_file = ZipIO_Error;
p_sys->fileFunctions->opaque = ( void * ) s;
p_sys->zipFile = unzOpen2( NULL /* path */, p_sys->fileFunctions );
if( !p_sys->zipFile )
{
msg_Warn( s, "unable to open file" );
free( p_sys );
free( p_sys->fileFunctions );
return VLC_EGENERIC;
}
/* Find the stream uri */
/* FIXME FIXME FIXME */
input_thread_t *p_input_thread = (input_thread_t*)
vlc_object_find( p_this, VLC_OBJECT_INPUT, FIND_PARENT );
if( !p_input_thread )
{
free( p_sys );
free( p_sys->fileFunctions );
return VLC_EGENERIC;
}
input_item_t *p_input_item = input_GetItem( p_input_thread );
if( !p_input_item )
{
free( p_sys );
free( p_sys->fileFunctions );
return VLC_EGENERIC;
}
s->p_sys->psz_path = strdup( p_input_item->psz_uri );
vlc_gc_decref( p_input_item );
// vlc_object_release( p_input_thread );