]> git.sesse.net Git - vlc/blob - modules/stream_filter/rar.c
No functiona changes (rar).
[vlc] / modules / stream_filter / rar.c
1 /*****************************************************************************
2  * rar.c: uncompressed RAR stream filter (only the biggest file is extracted)
3  *****************************************************************************
4  * Copyright (C) 2008 Laurent Aimar
5  * $Id$
6  *
7  * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
8  *
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.
13  *
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.
18  *
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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27 #ifdef HAVE_CONFIG_H
28 # include "config.h"
29 #endif
30
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_stream.h>
34
35 #include <assert.h>
36 #include <limits.h>
37
38 /*****************************************************************************
39  * Module descriptor
40  *****************************************************************************/
41 static int  Open ( vlc_object_t * );
42 static void Close( vlc_object_t * );
43
44 vlc_module_begin()
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 )
50     add_shortcut( "rar" )
51 vlc_module_end()
52
53 /*****************************************************************************
54  *
55  *****************************************************************************/
56 static const uint8_t p_rar_marker[] = {
57     0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00
58 };
59 static const int i_rar_marker = sizeof(p_rar_marker);
60
61 typedef struct
62 {
63     uint64_t i_offset;
64     uint64_t i_size;
65     uint64_t i_cummulated_size;
66 } rar_file_chunk_t;
67 typedef struct
68 {
69     char     *psz_name;
70     uint64_t i_size;
71     bool     b_complete;
72
73     int              i_chunk;
74     rar_file_chunk_t **pp_chunk;
75     uint64_t         i_real_size;  /* Gathered size */
76 } rar_file_t;
77
78 static void RarFileDelete( rar_file_t * );
79 static int  RarParse( stream_t *, int *pi_count, rar_file_t ***ppp_file );
80
81 struct stream_sys_t
82 {
83     rar_file_t *p_file;
84     const rar_file_chunk_t *p_chunk;
85
86     uint64_t i_position;
87
88     uint8_t *p_peek_alloc;
89     uint8_t *p_peek;
90     unsigned int i_peek;
91 };
92
93
94 /****************************************************************************
95  * Local prototypes
96  ****************************************************************************/
97 static int  Read   ( stream_t *, void *p_read, unsigned int i_read );
98 static int  Peek   ( stream_t *, const uint8_t **pp_peek, unsigned int i_peek );
99 static int  Control( stream_t *, int i_query, va_list );
100 static int  Seek   ( stream_t *s, uint64_t i_position );
101
102 /****************************************************************************
103  * Open
104  ****************************************************************************/
105 static int Open ( vlc_object_t *p_this )
106 {
107     stream_t *s = (stream_t*)p_this;
108     stream_sys_t *p_sys;
109
110     /* */
111     const uint8_t *p_peek;
112     if( stream_Peek( s->p_source, &p_peek, i_rar_marker ) < i_rar_marker )
113         return VLC_EGENERIC;
114     if( memcmp( p_peek, p_rar_marker, i_rar_marker ) )
115         return VLC_EGENERIC;
116
117     /* */
118     s->pf_read = Read;
119     s->pf_peek = Peek;
120     s->pf_control = Control;
121
122     s->p_sys = p_sys = malloc( sizeof( *p_sys ) );
123     if( !p_sys )
124         return VLC_ENOMEM;
125
126     /* */
127     p_sys->p_file = NULL;
128     p_sys->i_position = 0;
129     p_sys->p_chunk = NULL;
130
131     p_sys->p_peek_alloc = NULL;
132     p_sys->p_peek = NULL;
133     p_sys->i_peek = 0;
134
135     /* */
136     int i_count;
137     rar_file_t **pp_file;
138     if( RarParse( s, &i_count, &pp_file ) )
139     {
140         i_count = 0;
141         pp_file = NULL;
142     }
143
144     /* Select the longest file */
145     p_sys->p_file = NULL;
146     for( int i = 0; i < i_count; i++ )
147     {
148         if( !p_sys->p_file || p_sys->p_file->i_size < pp_file[i]->i_size )
149             p_sys->p_file = pp_file[i];
150     }
151     for( int i = 0; i < i_count; i++ )
152     {
153         if( pp_file[i] != p_sys->p_file )
154             RarFileDelete( pp_file[i] );
155     }
156     free( pp_file );
157
158     if( !p_sys->p_file || p_sys->p_file->i_chunk <= 0 || p_sys->p_file->i_size <= 0 )
159     {
160         msg_Err( s, "Invalid or unsupported RAR archive" );
161         if( p_sys->p_file )
162             RarFileDelete( p_sys->p_file );
163         free( p_sys );
164         return VLC_EGENERIC;
165     }
166
167     /* */
168     Seek( s, 0 );
169
170     /* */
171     const rar_file_t *p_file = p_sys->p_file;
172     msg_Dbg( s, "Using RAR stream filter for '%s' %"PRId64"(expected %"PRId64") bytes in %d chunks",
173              p_file->psz_name, p_file->i_real_size, p_file->i_size, p_file->i_chunk );
174
175     return VLC_SUCCESS;
176 }
177
178 /****************************************************************************
179  * Close
180  ****************************************************************************/
181 static void Close( vlc_object_t *p_this )
182 {
183     stream_t *s = (stream_t*)p_this;
184     stream_sys_t *p_sys = s->p_sys;
185
186     RarFileDelete( p_sys->p_file );
187     free( p_sys->p_peek_alloc );
188     free( p_sys );
189 }
190
191 /****************************************************************************
192  * Stream filters functions
193  ****************************************************************************/
194 static int Read( stream_t *s, void *p_read, unsigned int i_read )
195 {
196     stream_sys_t *p_sys = s->p_sys;
197     uint8_t *p_data = p_read;
198     unsigned int i_total = 0;
199
200     if( p_sys->i_peek > 0 && i_read > 0 )
201     {
202         const unsigned int i_copy = __MIN( i_read, p_sys->i_peek );
203
204         if( p_data )
205         {
206             memcpy( p_data, p_sys->p_peek, i_copy );
207             p_data += i_copy;
208         }
209
210         p_sys->i_peek -= i_copy;
211         p_sys->p_peek += i_copy;
212         i_total += i_copy;
213     }
214
215     while( i_total < i_read )
216     {
217         const uint64_t i_chunk_end = p_sys->p_chunk->i_cummulated_size + p_sys->p_chunk->i_size;
218
219         int i_max = __MIN( i_read - i_total, i_chunk_end - p_sys->i_position );
220         if( i_max <= 0 )
221             break;
222
223         int i_real = stream_Read( s->p_source, p_data, i_max );
224         if( i_real <= 0 )
225             break;
226
227         i_total += i_real;
228         if( p_data )
229             p_data += i_real;
230         p_sys->i_position += i_real;
231         if( p_sys->i_position >= i_chunk_end )
232         {
233             if( Seek( s, p_sys->i_position ) )
234                 break;
235         }
236     }
237     return i_total;
238 }
239
240 static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned int i_peek )
241 {
242     stream_sys_t *p_sys = s->p_sys;
243
244     if( i_peek <= p_sys->i_peek )
245     {
246         *pp_peek = p_sys->p_peek;
247         return i_peek;
248     }
249
250     /* */
251     uint8_t *p_peek = malloc( i_peek );
252     if( !p_peek )
253         return 0;
254
255     /* XXX yes stream_Read on ourself */
256     int i_read = stream_Read( s, p_peek, i_peek );
257     if( i_read <= 0 )
258     {
259         free( p_peek );
260         return i_read;
261     }
262
263     free( p_sys->p_peek_alloc );
264
265     p_sys->p_peek_alloc =
266     p_sys->p_peek       = p_peek;
267     p_sys->i_peek       = i_read;
268
269     *pp_peek = p_sys->p_peek;
270     return p_sys->i_peek;
271 }
272
273 static int Control( stream_t *s, int i_query, va_list args )
274 {
275     stream_sys_t *p_sys = s->p_sys;
276
277     switch( i_query )
278     {
279     /* */
280     case STREAM_SET_POSITION:
281     {
282         uint64_t i_position = va_arg( args, uint64_t );
283         return Seek( s, i_position );
284     }
285
286     case STREAM_GET_POSITION:
287     {
288         uint64_t *pi_position = va_arg( args, uint64_t* );
289         *pi_position = p_sys->i_position - p_sys->i_peek;
290         return VLC_SUCCESS;
291     }
292
293     case STREAM_GET_SIZE:
294     {
295         uint64_t *pi_size = (uint64_t*)va_arg( args, uint64_t* );
296         *pi_size = p_sys->p_file->i_real_size;
297         return VLC_SUCCESS;
298     }
299
300     /* */
301     case STREAM_GET_CONTENT_TYPE: /* arg1= char ** */
302         return VLC_EGENERIC;
303
304     case STREAM_UPDATE_SIZE: /* TODO maybe we should update i_real_size from file size and chunk offset ? */
305     case STREAM_CONTROL_ACCESS:
306     case STREAM_CAN_SEEK:
307     case STREAM_CAN_FASTSEEK:
308     case STREAM_SET_RECORD_STATE:
309         return stream_vaControl( s->p_source, i_query, args );
310     default:
311         return VLC_EGENERIC;
312     }
313 }
314
315 /****************************************************************************
316  * Helpers
317  ****************************************************************************/
318 static int Seek( stream_t *s, uint64_t i_position )
319 {
320     stream_sys_t *p_sys = s->p_sys;
321
322     if( i_position > p_sys->p_file->i_real_size )
323         i_position = p_sys->p_file->i_real_size;
324
325     /* Search the chunk */
326     const rar_file_t *p_file = p_sys->p_file;
327     for( int i = 0; i < p_file->i_chunk; i++ )
328     {
329         p_sys->p_chunk = p_file->pp_chunk[i];
330         if( i_position < p_sys->p_chunk->i_cummulated_size + p_sys->p_chunk->i_size )
331             break;
332     }
333     p_sys->i_position = i_position;
334     p_sys->i_peek     = 0;
335
336     const uint64_t i_seek = p_sys->p_chunk->i_offset +
337                             ( i_position - p_sys->p_chunk->i_cummulated_size );
338     return stream_Seek( s->p_source, i_seek );
339 }
340
341 /* Rar parser */
342 static void RarFileDelete( rar_file_t *p_file )
343 {
344     for( int i = 0; i < p_file->i_chunk; i++ )
345         free( p_file->pp_chunk[i] );
346     free( p_file->pp_chunk );
347     free( p_file->psz_name );
348     free( p_file );
349 }
350
351 typedef struct
352 {
353     uint16_t i_crc;
354     uint8_t  i_type;
355     uint16_t i_flags;
356     uint16_t i_size;
357     uint32_t i_add_size;
358 } rar_block_t;
359
360 enum
361 {
362     RAR_BLOCK_MARKER = 0x72,
363     RAR_BLOCK_ARCHIVE = 0x73,
364     RAR_BLOCK_FILE = 0x74,
365     RAR_BLOCK_END = 0x7b,
366 };
367 enum
368 {
369     RAR_BLOCK_END_HAS_NEXT = 0x0001,
370 };
371 enum
372 {
373     RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
374     RAR_BLOCK_FILE_HAS_NEXT     = 0x0002,
375     RAR_BLOCK_FILE_HAS_HIGH     = 0x0100,
376 };
377
378 static int PeekBlock( stream_t *s, rar_block_t *p_hdr )
379 {
380     const uint8_t *p_peek;
381     int i_peek = stream_Peek( s->p_source, &p_peek, 11 );
382
383     if( i_peek < 7 )
384         return VLC_EGENERIC;
385
386     p_hdr->i_crc   = GetWLE( &p_peek[0] );
387     p_hdr->i_type  = p_peek[2];
388     p_hdr->i_flags = GetWLE( &p_peek[3] );
389     p_hdr->i_size  = GetWLE( &p_peek[5] );
390     p_hdr->i_add_size = 0;
391     if( p_hdr->i_flags & 0x8000 )
392     {
393         if( i_peek < 11 )
394             return VLC_EGENERIC;
395         p_hdr->i_add_size = GetDWLE( &p_peek[7] );
396     }
397
398     if( p_hdr->i_size < 7 )
399         return VLC_EGENERIC;
400     return VLC_SUCCESS;
401 }
402 static int SkipBlock( stream_t *s, const rar_block_t *p_hdr )
403 {
404     uint64_t i_size = (uint64_t)p_hdr->i_size + p_hdr->i_add_size;
405
406     while( i_size > 0 )
407     {
408         int i_skip = __MIN( i_size, INT_MAX );
409         if( stream_Read( s->p_source, NULL, i_skip ) < i_skip )
410             return VLC_EGENERIC;
411
412         i_size -= i_skip;
413     }
414     return VLC_SUCCESS;
415 }
416
417 static int IgnoreBlock( stream_t *s, int i_block )
418 {
419     /* */
420     rar_block_t bk;
421     if( PeekBlock( s, &bk ) || bk.i_type != i_block )
422         return VLC_EGENERIC;
423     return SkipBlock( s, &bk );
424 }
425
426 static int SkipEnd( stream_t *s, const rar_block_t *p_hdr )
427 {
428     if( !(p_hdr->i_flags & RAR_BLOCK_END_HAS_NEXT) )
429         return VLC_EGENERIC;
430
431     if( SkipBlock( s, p_hdr ) )
432         return VLC_EGENERIC;
433
434     /* Now, we need to look for a marker block,
435      * It seems that there is garbage at EOF */
436     for( ;; )
437     {
438         const uint8_t *p_peek;
439
440         if( stream_Peek( s->p_source, &p_peek, i_rar_marker ) < i_rar_marker )
441             return VLC_EGENERIC;
442
443         if( !memcmp( p_peek, p_rar_marker, i_rar_marker ) )
444             break;
445
446         if( stream_Read( s->p_source, NULL, 1 ) != 1 )
447             return VLC_EGENERIC;
448     }
449
450     /* Skip marker and archive blocks */
451     if( IgnoreBlock( s, RAR_BLOCK_MARKER ) )
452         return VLC_EGENERIC;
453     if( IgnoreBlock( s, RAR_BLOCK_ARCHIVE ) )
454         return VLC_EGENERIC;
455
456     return VLC_SUCCESS;
457 }
458
459 static int SkipFile( stream_t *s, int *pi_count, rar_file_t ***ppp_file, const rar_block_t *p_hdr )
460 {
461     const uint8_t *p_peek;
462
463     int i_min_size = 7+21;
464     if( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH )
465         i_min_size += 8;
466     if( p_hdr->i_size < (unsigned)i_min_size )
467         return VLC_EGENERIC;
468
469     if( stream_Peek( s->p_source, &p_peek, i_min_size ) < i_min_size )
470         return VLC_EGENERIC;
471
472     /* */
473     uint32_t i_file_size_low = GetDWLE( &p_peek[7+4] );
474     uint8_t  i_method = p_peek[7+18];
475     uint16_t i_name_size = GetWLE( &p_peek[7+19] );
476     uint32_t i_file_size_high = 0;
477     if( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH )
478         i_file_size_high = GetDWLE( &p_peek[7+25] );
479     const uint64_t i_file_size = ((uint64_t)i_file_size_high << 32) | i_file_size_low;
480
481     char *psz_name = calloc( 1, i_name_size + 1 );
482     if( !psz_name )
483         return VLC_EGENERIC;
484
485     const int i_name_offset = (p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25);
486     if( i_name_offset + i_name_size <= p_hdr->i_size )
487     {
488         const int i_max_size = i_name_offset + i_name_size;
489         if( stream_Peek( s->p_source, &p_peek, i_max_size ) < i_max_size )
490         {
491             free( psz_name );
492             return VLC_EGENERIC;
493         }
494         memcpy( psz_name, &p_peek[i_name_offset], i_name_size );
495     }
496
497     if( i_method != 0x30 )
498     {
499         msg_Warn( s, "Ignoring compressed file %s (method=0x%2.2x)", psz_name, i_method );
500         goto exit;
501     }
502
503     /* */
504     rar_file_t *p_current = *pi_count > 0 ? (*ppp_file)[*pi_count - 1] : NULL;
505     if( p_current &&
506         ( p_current->b_complete ||
507           p_current->i_size != i_file_size ||
508           strcmp( p_current->psz_name, psz_name ) ||
509           ( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_PREVIOUS ) == 0 ) )
510         p_current = NULL;
511
512     if( !p_current )
513     {
514         p_current = malloc( sizeof( *p_current ) );
515         if( !p_current )
516             goto exit;
517         TAB_APPEND( *pi_count, *ppp_file, p_current );
518
519         p_current->psz_name = psz_name;
520         p_current->i_size = i_file_size;
521         p_current->b_complete = false;
522         p_current->i_real_size = 0;
523         TAB_INIT( p_current->i_chunk, p_current->pp_chunk );
524
525         psz_name = NULL;
526     }
527
528     /* Append chunks */
529     rar_file_chunk_t *p_chunk = malloc( sizeof( *p_chunk ) );
530     if( p_chunk )
531     {
532         p_chunk->i_offset = stream_Tell( s->p_source ) + p_hdr->i_size;
533         p_chunk->i_size = p_hdr->i_add_size;
534         p_chunk->i_cummulated_size = 0;
535         if( p_current->i_chunk > 0 )
536         {
537             rar_file_chunk_t *p_previous = p_current->pp_chunk[p_current->i_chunk-1];
538
539             p_chunk->i_cummulated_size += p_previous->i_cummulated_size +
540                                           p_previous->i_size;
541         }
542
543         TAB_APPEND( p_current->i_chunk, p_current->pp_chunk, p_chunk );
544
545         p_current->i_real_size += p_hdr->i_add_size;
546     }
547     if( ( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_NEXT ) == 0 )
548         p_current->b_complete = true;
549
550 exit:
551     /* */
552     free( psz_name );
553
554     /* We stop on the first non empty file if we cannot seek */
555     if( p_current )
556     {
557         bool b_can_seek = false;
558         stream_Control( s->p_source, STREAM_CAN_SEEK, &b_can_seek );
559         if( !b_can_seek && p_current->i_size > 0 )
560             return VLC_EGENERIC;
561     }
562
563     if( SkipBlock( s, p_hdr ) )
564         return VLC_EGENERIC;
565     return VLC_SUCCESS;
566 }
567
568 static int RarParse( stream_t *s, int *pi_count, rar_file_t ***ppp_file )
569 {
570     *pi_count = 0;
571     *ppp_file = NULL;
572
573     /* Skip marker */
574     if( IgnoreBlock( s, RAR_BLOCK_MARKER ) )
575         return VLC_EGENERIC;
576
577     /* Skip archive  */
578     if( IgnoreBlock( s, RAR_BLOCK_ARCHIVE ) )
579         return VLC_EGENERIC;
580
581     /* */
582     for( ;; )
583     {
584         rar_block_t bk;
585         int i_ret;
586
587         if( PeekBlock( s, &bk ) )
588             break;
589
590         switch( bk.i_type )
591         {
592         case RAR_BLOCK_END:
593             i_ret = SkipEnd( s, &bk );
594             break;
595         case RAR_BLOCK_FILE:
596             i_ret = SkipFile( s, pi_count, ppp_file, &bk );
597             break;
598         default:
599             i_ret = SkipBlock( s, &bk );
600             break;
601         }
602         if( i_ret )
603             break;
604     }
605
606     return VLC_SUCCESS;
607 }
608