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