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