]> git.sesse.net Git - vlc/blob - modules/access/rar/rar.c
LGPL
[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 } rar_pattern_t;
285
286 static const rar_pattern_t *FindVolumePattern(const char *location)
287 {
288     static const rar_pattern_t patterns[] = {
289         { ".part1.rar",   "%s.part%.1d.rar", 2,   9 },
290         { ".part01.rar",  "%s.part%.2d.rar", 2,  99, },
291         { ".part001.rar", "%s.part%.3d.rar", 2, 999 },
292         { ".rar",         "%s.%c%.2d",       0, 999 },
293         { NULL, NULL, 0, 0 },
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         if (!strcmp(&location[location_size - match_size], patterns[i].match))
303             return &patterns[i];
304     }
305     return NULL;
306 }
307
308 int RarParse(stream_t *s, int *count, rar_file_t ***file)
309 {
310     *count = 0;
311     *file = NULL;
312
313     const rar_pattern_t *pattern = FindVolumePattern(s->psz_path);
314     int volume_offset = 0;
315
316     char *volume_mrl;
317     if (asprintf(&volume_mrl, "%s://%s",
318                  s->psz_access, s->psz_path) < 0)
319         return VLC_EGENERIC;
320
321     stream_t *vol = s;
322     for (;;) {
323         /* Skip marker & archive */
324         if (IgnoreBlock(vol, RAR_BLOCK_MARKER) ||
325             IgnoreBlock(vol, RAR_BLOCK_ARCHIVE)) {
326             if (vol != s)
327                 stream_Delete(vol);
328             free(volume_mrl);
329             return VLC_EGENERIC;
330         }
331
332         /* */
333         int has_next = -1;
334         for (;;) {
335             rar_block_t bk;
336             int ret;
337
338             if (PeekBlock(vol, &bk))
339                 break;
340
341             switch(bk.type) {
342             case RAR_BLOCK_END:
343                 ret = SkipEnd(vol, &bk);
344                 has_next = ret && (bk.flags & RAR_BLOCK_END_HAS_NEXT);
345                 break;
346             case RAR_BLOCK_FILE:
347                 ret = SkipFile(vol, count, file, &bk, volume_mrl);
348                 break;
349             default:
350                 ret = SkipBlock(vol, &bk);
351                 break;
352             }
353             if (ret)
354                 break;
355         }
356         if (has_next < 0 && *count > 0 && !(*file)[*count -1]->is_complete)
357             has_next = 1;
358         if (vol != s)
359             stream_Delete(vol);
360
361         if (!has_next || !pattern) {
362             free(volume_mrl);
363             return VLC_SUCCESS;
364         }
365
366         /* Open next volume */
367         const int volume_index = pattern->start + volume_offset++;
368         if (volume_index > pattern->stop) {
369             free(volume_mrl);
370             return VLC_SUCCESS;
371         }
372
373         char *volume_base;
374         if (asprintf(&volume_base, "%s://%.*s",
375                      s->psz_access,
376                      (int)(strlen(s->psz_path) - strlen(pattern->match)), s->psz_path) < 0) {
377             free(volume_mrl);
378             return VLC_SUCCESS;
379         }
380
381         free(volume_mrl);
382         if (pattern->start) {
383             if (asprintf(&volume_mrl, pattern->format, volume_base, volume_index) < 0)
384                 volume_mrl = NULL;
385         } else {
386             if (asprintf(&volume_mrl, pattern->format, volume_base,
387                          'r' + volume_index / 100, volume_index % 100) < 0)
388                 volume_mrl = NULL;
389         }
390         free(volume_base);
391
392         if (!volume_mrl)
393             return VLC_SUCCESS;
394
395         const int s_flags = s->i_flags;
396         if (has_next < 0)
397             s->i_flags |= OBJECT_FLAGS_NOINTERACT;
398         vol = stream_UrlNew(s, volume_mrl);
399         s->i_flags = s_flags;
400
401         if (!vol) {
402             free(volume_mrl);
403             return VLC_SUCCESS;
404         }
405     }
406 }
407