]> git.sesse.net Git - vlc/blob - modules/access/rar/rar.c
55afd212951026c78a247ddc4ac7edfcb330284f
[vlc] / modules / access / rar / rar.c
1 /*****************************************************************************
2  * rar.c: uncompressed RAR parser
3  *****************************************************************************
4  * Copyright (C) 2008-2010 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 it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_stream.h>
35
36 #include <assert.h>
37 #include <limits.h>
38
39 #include "rar.h"
40
41 static const uint8_t rar_marker[] = {
42     0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00
43 };
44 static const int rar_marker_size = sizeof(rar_marker);
45
46 void RarFileDelete(rar_file_t *file)
47 {
48     for (int i = 0; i < file->chunk_count; i++) {
49         free(file->chunk[i]->mrl);
50         free(file->chunk[i]);
51     }
52     free(file->chunk);
53     free(file->name);
54     free(file);
55 }
56
57 typedef struct {
58     uint16_t crc;
59     uint8_t  type;
60     uint16_t flags;
61     uint16_t size;
62     uint32_t add_size;
63 } rar_block_t;
64
65 enum {
66     RAR_BLOCK_MARKER = 0x72,
67     RAR_BLOCK_ARCHIVE = 0x73,
68     RAR_BLOCK_FILE = 0x74,
69     RAR_BLOCK_SUBBLOCK = 0x7a,
70     RAR_BLOCK_END = 0x7b,
71 };
72 enum {
73     RAR_BLOCK_END_HAS_NEXT = 0x0001,
74 };
75 enum {
76     RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
77     RAR_BLOCK_FILE_HAS_NEXT     = 0x0002,
78     RAR_BLOCK_FILE_HAS_HIGH     = 0x0100,
79 };
80
81 static int PeekBlock(stream_t *s, rar_block_t *hdr)
82 {
83     const uint8_t *peek;
84     int peek_size = stream_Peek(s, &peek, 11);
85
86     if (peek_size < 7)
87         return VLC_EGENERIC;
88
89     hdr->crc   = GetWLE(&peek[0]);
90     hdr->type  = peek[2];
91     hdr->flags = GetWLE(&peek[3]);
92     hdr->size  = GetWLE(&peek[5]);
93     hdr->add_size = 0;
94     if ((hdr->flags & 0x8000) ||
95         hdr->type == RAR_BLOCK_FILE ||
96         hdr->type == RAR_BLOCK_SUBBLOCK) {
97         if (peek_size < 11)
98             return VLC_EGENERIC;
99         hdr->add_size = GetDWLE(&peek[7]);
100     }
101
102     if (hdr->size < 7)
103         return VLC_EGENERIC;
104     return VLC_SUCCESS;
105 }
106 static int SkipBlock(stream_t *s, const rar_block_t *hdr)
107 {
108     uint64_t size = (uint64_t)hdr->size + hdr->add_size;
109
110     while (size > 0) {
111         int skip = __MIN(size, INT_MAX);
112         if (stream_Read(s, NULL, skip) < skip)
113             return VLC_EGENERIC;
114
115         size -= skip;
116     }
117     return VLC_SUCCESS;
118 }
119
120 static int IgnoreBlock(stream_t *s, int block)
121 {
122     /* */
123     rar_block_t bk;
124     if (PeekBlock(s, &bk) || bk.type != block)
125         return VLC_EGENERIC;
126     return SkipBlock(s, &bk);
127 }
128
129 static int SkipEnd(stream_t *s, const rar_block_t *hdr)
130 {
131     if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT))
132         return VLC_EGENERIC;
133
134     if (SkipBlock(s, hdr))
135         return VLC_EGENERIC;
136
137     /* Now, we need to look for a marker block,
138      * It seems that there is garbage at EOF */
139     for (;;) {
140         const uint8_t *peek;
141
142         if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
143             return VLC_EGENERIC;
144
145         if (!memcmp(peek, rar_marker, rar_marker_size))
146             break;
147
148         if (stream_Read(s, NULL, 1) != 1)
149             return VLC_EGENERIC;
150     }
151
152     /* Skip marker and archive blocks */
153     if (IgnoreBlock(s, RAR_BLOCK_MARKER))
154         return VLC_EGENERIC;
155     if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
156         return VLC_EGENERIC;
157
158     return VLC_SUCCESS;
159 }
160
161 static int SkipFile(stream_t *s, int *count, rar_file_t ***file,
162                     const rar_block_t *hdr, const char *volume_mrl)
163 {
164     const uint8_t *peek;
165
166     int min_size = 7+21;
167     if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
168         min_size += 8;
169     if (hdr->size < (unsigned)min_size)
170         return VLC_EGENERIC;
171
172     if (stream_Peek(s, &peek, min_size) < min_size)
173         return VLC_EGENERIC;
174
175     /* */
176     uint32_t file_size_low = GetDWLE(&peek[7+4]);
177     uint8_t  method = peek[7+18];
178     uint16_t name_size = GetWLE(&peek[7+19]);
179     uint32_t file_size_high = 0;
180     if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
181         file_size_high = GetDWLE(&peek[7+29]);
182     const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low;
183
184     char *name = calloc(1, name_size + 1);
185     if (!name)
186         return VLC_EGENERIC;
187
188     const int name_offset = (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25);
189     if (name_offset + name_size <= hdr->size) {
190         const int max_size = name_offset + name_size;
191         if (stream_Peek(s, &peek, max_size) < max_size) {
192             free(name);
193             return VLC_EGENERIC;
194         }
195         memcpy(name, &peek[name_offset], name_size);
196     }
197
198     rar_file_t *current = NULL;
199     if (method != 0x30) {
200         msg_Warn(s, "Ignoring compressed file %s (method=0x%2.2x)", name, method);
201         goto exit;
202     }
203
204     /* */
205     if( *count > 0 )
206         current = (*file)[*count - 1];
207
208     if (current &&
209         (current->is_complete ||
210           strcmp(current->name, name) ||
211           (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0))
212         current = NULL;
213
214     if (!current) {
215         if (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS)
216             goto exit;
217         current = malloc(sizeof(*current));
218         if (!current)
219             goto exit;
220         TAB_APPEND(*count, *file, current);
221
222         current->name = name;
223         current->size = file_size;
224         current->is_complete = false;
225         current->real_size = 0;
226         TAB_INIT(current->chunk_count, current->chunk);
227
228         name = NULL;
229     }
230
231     /* Append chunks */
232     rar_file_chunk_t *chunk = malloc(sizeof(*chunk));
233     if (chunk) {
234         chunk->mrl = strdup(volume_mrl);
235         chunk->offset = stream_Tell(s) + hdr->size;
236         chunk->size = hdr->add_size;
237         chunk->cummulated_size = 0;
238         if (current->chunk_count > 0) {
239             rar_file_chunk_t *previous = current->chunk[current->chunk_count-1];
240
241             chunk->cummulated_size += previous->cummulated_size +
242                                       previous->size;
243         }
244
245         TAB_APPEND(current->chunk_count, current->chunk, chunk);
246
247         current->real_size += hdr->add_size;
248     }
249     if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0)
250         current->is_complete = true;
251
252 exit:
253     /* */
254     free(name);
255
256     /* We stop on the first non empty file if we cannot seek */
257     if (current) {
258         bool can_seek = false;
259         stream_Control(s, STREAM_CAN_SEEK, &can_seek);
260         if (!can_seek && current->size > 0)
261             return VLC_EGENERIC;
262     }
263
264     if (SkipBlock(s, hdr))
265         return VLC_EGENERIC;
266     return VLC_SUCCESS;
267 }
268
269 int RarProbe(stream_t *s)
270 {
271     const uint8_t *peek;
272     if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
273         return VLC_EGENERIC;
274     if (memcmp(peek, rar_marker, rar_marker_size))
275         return VLC_EGENERIC;
276     return VLC_SUCCESS;
277 }
278
279 typedef struct {
280     const char *match;
281     const char *format;
282     int start;
283     int stop;
284     bool b_extonly;
285 } rar_pattern_t;
286
287 static const rar_pattern_t *FindVolumePattern(const char *location, bool b_extonly )
288 {
289     static const rar_pattern_t patterns[] = {
290         { ".part01.rar",  "%s.part%.2d.rar", 2,  99, false }, // new naming
291         { ".part001.rar", "%s.part%.3d.rar", 2, 999, false }, // new
292         { ".rar",         "%s.%c%.2d",       0, 999, true },  // old
293         { NULL, NULL, 0, 0, false },
294     };
295
296     const size_t location_size = strlen(location);
297     for (int i = 0; patterns[i].match != NULL; i++) {
298         const size_t match_size = strlen(patterns[i].match);
299
300         if (location_size < match_size)
301             continue;
302
303         if ( b_extonly && !patterns[i].b_extonly )
304             continue;
305
306         if (!strcmp(&location[location_size - match_size], patterns[i].match))
307             return &patterns[i];
308     }
309     return NULL;
310 }
311
312 int RarParse(stream_t *s, int *count, rar_file_t ***file, bool b_extonly)
313 {
314     *count = 0;
315     *file = NULL;
316
317     const rar_pattern_t *pattern = FindVolumePattern(s->psz_path, b_extonly);
318     int volume_offset = 0;
319
320     char *volume_mrl;
321     if (asprintf(&volume_mrl, "%s://%s",
322                  s->psz_access, s->psz_path) < 0)
323         return VLC_EGENERIC;
324
325     stream_t *vol = s;
326     for (;;) {
327         /* Skip marker & archive */
328         if (IgnoreBlock(vol, RAR_BLOCK_MARKER) ||
329             IgnoreBlock(vol, RAR_BLOCK_ARCHIVE)) {
330             if (vol != s)
331                 stream_Delete(vol);
332             free(volume_mrl);
333             return VLC_EGENERIC;
334         }
335
336         /* */
337         int has_next = -1;
338         for (;;) {
339             rar_block_t bk;
340             int ret;
341
342             if (PeekBlock(vol, &bk))
343                 break;
344
345             switch(bk.type) {
346             case RAR_BLOCK_END:
347                 ret = SkipEnd(vol, &bk);
348                 has_next = ret && (bk.flags & RAR_BLOCK_END_HAS_NEXT);
349                 break;
350             case RAR_BLOCK_FILE:
351                 ret = SkipFile(vol, count, file, &bk, volume_mrl);
352                 break;
353             default:
354                 ret = SkipBlock(vol, &bk);
355                 break;
356             }
357             if (ret)
358                 break;
359         }
360         if (has_next < 0 && *count > 0 && !(*file)[*count -1]->is_complete)
361             has_next = 1;
362         if (vol != s)
363             stream_Delete(vol);
364
365         if (!has_next || !pattern) {
366             free(volume_mrl);
367             return VLC_SUCCESS;
368         }
369
370         /* Open next volume */
371         const int volume_index = pattern->start + volume_offset++;
372         if (volume_index > pattern->stop) {
373             free(volume_mrl);
374             return VLC_SUCCESS;
375         }
376
377         char *volume_base;
378         if (asprintf(&volume_base, "%s://%.*s",
379                      s->psz_access,
380                      (int)(strlen(s->psz_path) - strlen(pattern->match)), s->psz_path) < 0) {
381             free(volume_mrl);
382             return VLC_SUCCESS;
383         }
384
385         free(volume_mrl);
386         if (pattern->start) {
387             if (asprintf(&volume_mrl, pattern->format, volume_base, volume_index) < 0)
388                 volume_mrl = NULL;
389         } else {
390             if (asprintf(&volume_mrl, pattern->format, volume_base,
391                          'r' + volume_index / 100, volume_index % 100) < 0)
392                 volume_mrl = NULL;
393         }
394         free(volume_base);
395
396         if (!volume_mrl)
397             return VLC_SUCCESS;
398
399         const int s_flags = s->i_flags;
400         if (has_next < 0)
401             s->i_flags |= OBJECT_FLAGS_NOINTERACT;
402         vol = stream_UrlNew(s, volume_mrl);
403         s->i_flags = s_flags;
404
405         if (!vol) {
406             free(volume_mrl);
407             return VLC_SUCCESS;
408         }
409     }
410 }
411