1 /*****************************************************************************
2 * rar.c: uncompressed RAR stream filter (only the biggest file is extracted)
3 *****************************************************************************
4 * Copyright (C) 2008 Laurent Aimar
7 * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ 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 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_stream.h>
38 /*****************************************************************************
40 *****************************************************************************/
41 static int Open ( vlc_object_t * );
42 static void Close( vlc_object_t * );
45 set_category( CAT_INPUT )
46 set_subcategory( SUBCAT_INPUT_STREAM_FILTER )
47 set_description( N_("Uncompressed RAR") )
48 set_capability( "stream_filter", 1 )
49 set_callbacks( Open, Close )
52 /*****************************************************************************
54 *****************************************************************************/
55 static const uint8_t p_rar_marker[] = {
56 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00
58 static const int i_rar_marker = sizeof(p_rar_marker);
64 int64_t i_cummulated_size;
73 rar_file_chunk_t **pp_chunk;
74 int64_t i_real_size; /* Gathered size */
77 static void RarFileDelete( rar_file_t * );
82 const rar_file_chunk_t *p_chunk;
86 uint8_t *p_peek_alloc;
92 /****************************************************************************
94 ****************************************************************************/
95 static int Read ( stream_t *, void *p_read, unsigned int i_read );
96 static int Peek ( stream_t *, const uint8_t **pp_peek, unsigned int i_peek );
97 static int Control( stream_t *, int i_query, va_list );
99 static int Parse ( stream_t * );
100 static int Seek ( stream_t *s, int64_t i_position );
102 /****************************************************************************
104 ****************************************************************************/
105 static int Open ( vlc_object_t *p_this )
107 stream_t *s = (stream_t*)p_this;
111 const uint8_t *p_peek;
112 if( stream_Peek( s->p_source, &p_peek, i_rar_marker ) < i_rar_marker )
114 if( memcmp( p_peek, p_rar_marker, i_rar_marker ) )
120 s->pf_control = Control;
122 s->p_sys = p_sys = malloc( sizeof( *p_sys ) );
127 p_sys->p_file = NULL;
128 p_sys->i_position = 0;
129 p_sys->p_chunk = NULL;
131 p_sys->p_peek_alloc = NULL;
132 p_sys->p_peek = NULL;
136 if( Parse( s ) || !p_sys->p_file || p_sys->p_file->i_chunk <= 0 )
138 msg_Err( s, "Invalid or unsupported RAR archive" );
140 RarFileDelete( p_sys->p_file );
149 const rar_file_t *p_file = p_sys->p_file;
150 msg_Dbg( s, "Using RAR stream filter for '%s' %"PRId64"(expected %"PRId64") bytes in %d chunks",
151 p_file->psz_name, p_file->i_real_size, p_file->i_size, p_file->i_chunk );
156 /****************************************************************************
158 ****************************************************************************/
159 static void Close( vlc_object_t *p_this )
161 stream_t *s = (stream_t*)p_this;
162 stream_sys_t *p_sys = s->p_sys;
164 RarFileDelete( p_sys->p_file );
165 free( p_sys->p_peek_alloc );
169 /****************************************************************************
170 * Stream filters functions
171 ****************************************************************************/
172 static int Read( stream_t *s, void *p_read, unsigned int i_read )
174 stream_sys_t *p_sys = s->p_sys;
175 uint8_t *p_data = p_read;
176 unsigned int i_total = 0;
178 if( p_sys->i_peek > 0 && i_read > 0 )
180 const unsigned int i_copy = __MIN( i_read, p_sys->i_peek );
184 memcpy( p_data, p_sys->p_peek, i_copy );
188 p_sys->i_peek -= i_copy;
189 p_sys->p_peek += i_copy;
193 while( i_total < i_read )
195 const int64_t i_chunk_end = p_sys->p_chunk->i_cummulated_size + p_sys->p_chunk->i_size;
197 int i_max = __MIN( i_read - i_total, i_chunk_end - p_sys->i_position );
201 int i_real = stream_Read( s->p_source, p_data, i_max );
208 p_sys->i_position += i_real;
209 if( p_sys->i_position >= i_chunk_end )
211 if( Seek( s, p_sys->i_position ) )
218 static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned int i_peek )
220 stream_sys_t *p_sys = s->p_sys;
222 if( i_peek <= p_sys->i_peek )
224 *pp_peek = p_sys->p_peek;
229 uint8_t *p_peek = malloc( i_peek );
233 /* XXX yes stream_Read on ourself */
234 int i_read = stream_Read( s, p_peek, i_peek );
241 if( p_sys->p_peek_alloc )
242 free( p_sys->p_peek_alloc );
244 p_sys->p_peek_alloc =
245 p_sys->p_peek = p_peek;
246 p_sys->i_peek = i_read;
248 *pp_peek = p_sys->p_peek;
249 return p_sys->i_peek;
252 static int Control( stream_t *s, int i_query, va_list args )
254 stream_sys_t *p_sys = s->p_sys;
259 case STREAM_SET_POSITION:
261 int64_t i_position = (int64_t)va_arg( args, int64_t );
262 return Seek( s, i_position );
265 case STREAM_GET_POSITION:
267 int64_t *pi_position = (int64_t*)va_arg( args, int64_t* );
268 *pi_position = p_sys->i_position - p_sys->i_peek;
272 case STREAM_GET_SIZE:
274 int64_t *pi_size = (int64_t*)va_arg( args, int64_t* );
275 *pi_size = p_sys->p_file->i_real_size;
280 case STREAM_GET_CONTENT_TYPE: /* arg1= char ** */
283 case STREAM_UPDATE_SIZE: /* TODO maybe we should update i_real_size from file size and chunk offset ? */
284 case STREAM_CONTROL_ACCESS:
285 case STREAM_CAN_SEEK:
286 case STREAM_CAN_FASTSEEK:
287 case STREAM_SET_RECORD_STATE:
288 return stream_vaControl( s->p_source, i_query, args );
294 /****************************************************************************
296 ****************************************************************************/
297 static int Seek( stream_t *s, int64_t i_position )
299 stream_sys_t *p_sys = s->p_sys;
303 else if( i_position > p_sys->p_file->i_real_size )
304 i_position = p_sys->p_file->i_real_size;
306 /* Search the chunk */
307 const rar_file_t *p_file = p_sys->p_file;
308 for( int i = 0; i < p_file->i_chunk; i++ )
310 p_sys->p_chunk = p_file->pp_chunk[i];
311 if( i_position < p_sys->p_chunk->i_cummulated_size + p_sys->p_chunk->i_size )
314 p_sys->i_position = i_position;
317 const int64_t i_seek = p_sys->p_chunk->i_offset +
318 ( i_position - p_sys->p_chunk->i_cummulated_size );
319 return stream_Seek( s->p_source, i_seek );
322 static void RarFileDelete( rar_file_t *p_file )
324 for( int i = 0; i < p_file->i_chunk; i++ )
325 free( p_file->pp_chunk[i] );
326 free( p_file->pp_chunk );
327 free( p_file->psz_name );
342 RAR_BLOCK_MARKER = 0x72,
343 RAR_BLOCK_ARCHIVE = 0x73,
344 RAR_BLOCK_FILE = 0x74,
345 RAR_BLOCK_END = 0x7b,
349 RAR_BLOCK_END_HAS_NEXT = 0x0001,
353 RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
354 RAR_BLOCK_FILE_HAS_NEXT = 0x0002,
355 RAR_BLOCK_FILE_HAS_HIGH = 0x0100,
358 static int PeekBlock( stream_t *s, rar_block_t *p_hdr )
360 const uint8_t *p_peek;
361 int i_peek = stream_Peek( s->p_source, &p_peek, 11 );
366 p_hdr->i_crc = GetWLE( &p_peek[0] );
367 p_hdr->i_type = p_peek[2];
368 p_hdr->i_flags = GetWLE( &p_peek[3] );
369 p_hdr->i_size = GetWLE( &p_peek[5] );
370 p_hdr->i_add_size = 0;
371 if( p_hdr->i_flags & 0x8000 )
375 p_hdr->i_add_size = GetDWLE( &p_peek[7] );
378 if( p_hdr->i_size < 7 )
382 static int SkipBlock( stream_t *s, const rar_block_t *p_hdr )
384 int64_t i_size = (int64_t)p_hdr->i_size + p_hdr->i_add_size;
386 assert( i_size >= 0 );
389 int i_skip = __MIN( i_size, INT_MAX );
390 if( stream_Read( s->p_source, NULL, i_skip ) < i_skip )
398 static int IgnoreBlock( stream_t *s, int i_block )
402 if( PeekBlock( s, &bk ) || bk.i_type != i_block )
404 return SkipBlock( s, &bk );
407 static int SkipEnd( stream_t *s, const rar_block_t *p_hdr )
409 if( !(p_hdr->i_flags & RAR_BLOCK_END_HAS_NEXT) )
412 if( SkipBlock( s, p_hdr ) )
415 /* Now, we need to look for a marker block,
416 * It seems that there is garbage at EOF */
419 const uint8_t *p_peek;
421 if( stream_Peek( s->p_source, &p_peek, i_rar_marker ) < i_rar_marker )
424 if( !memcmp( p_peek, p_rar_marker, i_rar_marker ) )
427 if( stream_Read( s->p_source, NULL, 1 ) != 1 )
431 /* Skip marker and archive blocks */
432 if( IgnoreBlock( s, RAR_BLOCK_MARKER ) )
434 if( IgnoreBlock( s, RAR_BLOCK_ARCHIVE ) )
440 static int SkipFile( stream_t *s,const rar_block_t *p_hdr )
442 stream_sys_t *p_sys = s->p_sys;
443 const uint8_t *p_peek;
445 int i_min_size = 7+21;
446 if( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH )
448 if( p_hdr->i_size < i_min_size )
451 if( stream_Peek( s->p_source, &p_peek, i_min_size ) < i_min_size )
455 uint32_t i_file_size_low = GetDWLE( &p_peek[7+4] );
456 uint8_t i_method = p_peek[7+18];
457 uint16_t i_name_size = GetWLE( &p_peek[7+19] );
458 uint32_t i_file_size_high = 0;
459 if( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH )
460 i_file_size_high = GetDWLE( &p_peek[7+25] );
462 char *psz_name = calloc( 1, i_name_size + 1 );
466 const int i_name_offset = (p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25);
467 if( i_name_offset + i_name_size <= p_hdr->i_size )
469 const int i_max_size = i_name_offset + i_name_size;
470 if( stream_Peek( s->p_source, &p_peek, i_max_size ) < i_max_size )
475 memcpy( psz_name, &p_peek[i_name_offset], i_name_size );
478 if( i_method != 0x30 )
480 msg_Warn( s, "Ignoring compressed file %s (method=0x%2.2x)", psz_name, i_method );
484 /* Ignore smaller files */
485 const int64_t i_file_size = ((int64_t)i_file_size_high << 32) | i_file_size_low;
487 p_sys->p_file->i_size < i_file_size )
489 RarFileDelete( p_sys->p_file );
490 p_sys->p_file = NULL;
493 rar_file_t *p_current = p_sys->p_file;
496 p_sys->p_file = p_current = malloc( sizeof( *p_sys->p_file ) );
501 p_current->psz_name = psz_name;
502 p_current->i_size = i_file_size;
503 p_current->b_complete = false;
504 p_current->i_real_size = 0;
505 TAB_INIT( p_current->i_chunk, p_current->pp_chunk );
511 if( !p_current->b_complete )
513 bool b_append = false;
514 /* Append if first chunk */
515 if( p_current->i_chunk <= 0 )
517 /* Append if it is really a continuous chunck */
518 if( p_current->i_size == i_file_size &&
519 ( !psz_name || !strcmp( p_current->psz_name, psz_name ) ) &&
520 ( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_PREVIOUS ) )
525 rar_file_chunk_t *p_chunk = malloc( sizeof( *p_chunk ) );
528 p_chunk->i_offset = stream_Tell( s->p_source );
529 p_chunk->i_size = p_hdr->i_add_size;
530 p_chunk->i_cummulated_size = 0;
531 if( p_current->i_chunk > 0 )
533 rar_file_chunk_t *p_previous = p_current->pp_chunk[p_current->i_chunk-1];
535 p_chunk->i_cummulated_size += p_previous->i_cummulated_size +
539 TAB_APPEND( p_current->i_chunk, p_current->pp_chunk, p_chunk );
541 p_current->i_real_size += p_hdr->i_add_size;
545 if( !(p_hdr->i_flags & RAR_BLOCK_FILE_HAS_NEXT ) )
546 p_current->b_complete = true;
553 /* We stop on the first non empty file if we cannot seek */
556 bool b_can_seek = false;
557 stream_Control( s->p_source, STREAM_CAN_SEEK, &b_can_seek );
558 if( !b_can_seek && p_current->i_size > 0 )
562 if( SkipBlock( s, p_hdr ) )
567 static int Parse( stream_t *s )
570 if( IgnoreBlock( s, RAR_BLOCK_MARKER ) )
574 if( IgnoreBlock( s, RAR_BLOCK_ARCHIVE ) )
583 if( PeekBlock( s, &bk ) )
589 i_ret = SkipEnd( s, &bk );
592 i_ret = SkipFile( s, &bk );
595 i_ret = SkipBlock( s, &bk );