]> git.sesse.net Git - vlc/blob - modules/access/rar/rar.c
0a6cac4e51bb85d967d8a2edcd33311a716df1eb
[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
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
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_END = 0x7b,
70 };
71 enum {
72     RAR_BLOCK_END_HAS_NEXT = 0x0001,
73 };
74 enum {
75     RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
76     RAR_BLOCK_FILE_HAS_NEXT     = 0x0002,
77     RAR_BLOCK_FILE_HAS_HIGH     = 0x0100,
78 };
79
80 static int PeekBlock(stream_t *s, rar_block_t *hdr)
81 {
82     const uint8_t *peek;
83     int peek_size = stream_Peek(s, &peek, 11);
84
85     if (peek_size < 7)
86         return VLC_EGENERIC;
87
88     hdr->crc   = GetWLE(&peek[0]);
89     hdr->type  = peek[2];
90     hdr->flags = GetWLE(&peek[3]);
91     hdr->size  = GetWLE(&peek[5]);
92     hdr->add_size = 0;
93     if (hdr->flags & 0x8000) {
94         if (peek_size < 11)
95             return VLC_EGENERIC;
96         hdr->add_size = GetDWLE(&peek[7]);
97     }
98
99     if (hdr->size < 7)
100         return VLC_EGENERIC;
101     return VLC_SUCCESS;
102 }
103 static int SkipBlock(stream_t *s, const rar_block_t *hdr)
104 {
105     uint64_t size = (uint64_t)hdr->size + hdr->add_size;
106
107     while (size > 0) {
108         int skip = __MIN(size, INT_MAX);
109         if (stream_Read(s, NULL, skip) < skip)
110             return VLC_EGENERIC;
111
112         size -= skip;
113     }
114     return VLC_SUCCESS;
115 }
116
117 static int IgnoreBlock(stream_t *s, int block)
118 {
119     /* */
120     rar_block_t bk;
121     if (PeekBlock(s, &bk) || bk.type != block)
122         return VLC_EGENERIC;
123     return SkipBlock(s, &bk);
124 }
125
126 static int SkipEnd(stream_t *s, const rar_block_t *hdr)
127 {
128     if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT))
129         return VLC_EGENERIC;
130
131     if (SkipBlock(s, hdr))
132         return VLC_EGENERIC;
133
134     /* Now, we need to look for a marker block,
135      * It seems that there is garbage at EOF */
136     for (;;) {
137         const uint8_t *peek;
138
139         if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
140             return VLC_EGENERIC;
141
142         if (!memcmp(peek, rar_marker, rar_marker_size))
143             break;
144
145         if (stream_Read(s, NULL, 1) != 1)
146             return VLC_EGENERIC;
147     }
148
149     /* Skip marker and archive blocks */
150     if (IgnoreBlock(s, RAR_BLOCK_MARKER))
151         return VLC_EGENERIC;
152     if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
153         return VLC_EGENERIC;
154
155     return VLC_SUCCESS;
156 }
157
158 static int SkipFile(stream_t *s, int *count, rar_file_t ***file,
159                     const rar_block_t *hdr, const char *volume_mrl)
160 {
161     const uint8_t *peek;
162
163     int min_size = 7+21;
164     if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
165         min_size += 8;
166     if (hdr->size < (unsigned)min_size)
167         return VLC_EGENERIC;
168
169     if (stream_Peek(s, &peek, min_size) < min_size)
170         return VLC_EGENERIC;
171
172     /* */
173     uint32_t file_size_low = GetDWLE(&peek[7+4]);
174     uint8_t  method = peek[7+18];
175     uint16_t name_size = GetWLE(&peek[7+19]);
176     uint32_t file_size_high = 0;
177     if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
178         file_size_high = GetDWLE(&peek[7+25]);
179     const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low;
180
181     char *name = calloc(1, name_size + 1);
182     if (!name)
183         return VLC_EGENERIC;
184
185     const int name_offset = (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25);
186     if (name_offset + name_size <= hdr->size) {
187         const int max_size = name_offset + name_size;
188         if (stream_Peek(s, &peek, max_size) < max_size) {
189             free(name);
190             return VLC_EGENERIC;
191         }
192         memcpy(name, &peek[name_offset], name_size);
193     }
194
195     rar_file_t *current = NULL;
196     if (method != 0x30) {
197         msg_Warn(s, "Ignoring compressed file %s (method=0x%2.2x)", name, method);
198         goto exit;
199     }
200
201     /* */
202     if( *count > 0 )
203         current = (*file)[*count - 1];
204
205     if (current &&
206         (current->is_complete ||
207           strcmp(current->name, name) ||
208           (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0))
209         current = NULL;
210
211     if (!current) {
212         if (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS)
213             goto exit;
214         current = malloc(sizeof(*current));
215         if (!current)
216             goto exit;
217         TAB_APPEND(*count, *file, current);
218
219         current->name = name;
220         current->size = file_size;
221         current->is_complete = false;
222         current->real_size = 0;
223         TAB_INIT(current->chunk_count, current->chunk);
224
225         name = NULL;
226     }
227
228     /* Append chunks */
229     rar_file_chunk_t *chunk = malloc(sizeof(*chunk));
230     if (chunk) {
231         chunk->mrl = strdup(volume_mrl);
232         chunk->offset = stream_Tell(s) + hdr->size;
233         chunk->size = hdr->add_size;
234         chunk->cummulated_size = 0;
235         if (current->chunk_count > 0) {
236             rar_file_chunk_t *previous = current->chunk[current->chunk_count-1];
237
238             chunk->cummulated_size += previous->cummulated_size +
239                                       previous->size;
240         }
241
242         TAB_APPEND(current->chunk_count, current->chunk, chunk);
243
244         current->real_size += hdr->add_size;
245     }
246     if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0)
247         current->is_complete = true;
248
249 exit:
250     /* */
251     free(name);
252
253     /* We stop on the first non empty file if we cannot seek */
254     if (current) {
255         bool can_seek = false;
256         stream_Control(s, STREAM_CAN_SEEK, &can_seek);
257         if (!can_seek && current->size > 0)
258             return VLC_EGENERIC;
259     }
260
261     if (SkipBlock(s, hdr))
262         return VLC_EGENERIC;
263     return VLC_SUCCESS;
264 }
265
266 int RarProbe(stream_t *s)
267 {
268     const uint8_t *peek;
269     if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
270         return VLC_EGENERIC;
271     if (memcmp(peek, rar_marker, rar_marker_size))
272         return VLC_EGENERIC;
273     return VLC_SUCCESS;
274 }
275
276 typedef struct {
277     const char *match;
278     const char *format;
279     int start;
280     int stop;
281 } rar_pattern_t;
282
283 static const rar_pattern_t *FindVolumePattern(const char *location)
284 {
285     static const rar_pattern_t patterns[] = {
286         { ".part1.rar",   "%s.part%.1d.rar", 2,   9 },
287         { ".part01.rar",  "%s.part%.2d.rar", 2,  99, },
288         { ".part001.rar", "%s.part%.3d.rar", 2, 999 },
289         { ".rar",         "%s.r%.2d",        0,  99 },
290         { NULL, NULL, 0, 0 },
291     };
292
293     const size_t location_size = strlen(location);
294     for (int i = 0; patterns[i].match != NULL; i++) {
295         const size_t match_size = strlen(patterns[i].match);
296
297         if (location_size < match_size)
298             continue;
299         if (!strcmp(&location[location_size - match_size], patterns[i].match))
300             return &patterns[i];
301     }
302     return NULL;
303 }
304
305 int RarParse(stream_t *s, int *count, rar_file_t ***file)
306 {
307     *count = 0;
308     *file = NULL;
309
310     const rar_pattern_t *pattern = FindVolumePattern(s->psz_path);
311     int volume_offset = 0;
312
313     char *volume_mrl;
314     if (asprintf(&volume_mrl, "%s://%s",
315                  s->psz_access, s->psz_path) < 0)
316         return VLC_EGENERIC;
317
318     stream_t *vol = s;
319     for (;;) {
320         /* Skip marker & archive */
321         if (IgnoreBlock(vol, RAR_BLOCK_MARKER) ||
322             IgnoreBlock(vol, RAR_BLOCK_ARCHIVE)) {
323             if (vol != s)
324                 stream_Delete(vol);
325             free(volume_mrl);
326             return VLC_EGENERIC;
327         }
328
329         /* */
330         bool has_next = false;
331         for (;;) {
332             rar_block_t bk;
333             int ret;
334
335             if (PeekBlock(vol, &bk))
336                 break;
337
338             switch(bk.type) {
339             case RAR_BLOCK_END:
340                 ret = SkipEnd(vol, &bk);
341                 has_next = ret && (bk.flags & RAR_BLOCK_END_HAS_NEXT);
342                 break;
343             case RAR_BLOCK_FILE:
344                 ret = SkipFile(vol, count, file, &bk, volume_mrl);
345                 break;
346             default:
347                 ret = SkipBlock(vol, &bk);
348                 break;
349             }
350             if (ret)
351                 break;
352         }
353         if (vol != s)
354             stream_Delete(vol);
355
356         if (!has_next || !pattern ||
357             (*count > 0 && !(*file)[*count -1]->is_complete)) {
358             free(volume_mrl);
359             return VLC_SUCCESS;
360         }
361
362         /* Open next volume */
363         const int volume_index = pattern->start + volume_offset++;
364         if (volume_index > pattern->stop) {
365             free(volume_mrl);
366             return VLC_SUCCESS;
367         }
368
369         char *volume_base;
370         if (asprintf(&volume_base, "%s://%.*s",
371                      s->psz_access,
372                      (int)(strlen(s->psz_path) - strlen(pattern->match)), s->psz_path) < 0) {
373             free(volume_mrl);
374             return VLC_SUCCESS;
375         }
376
377         free(volume_mrl);
378         if (asprintf(&volume_mrl, pattern->format, volume_base, volume_index) < 0)
379             volume_mrl = NULL;
380         free(volume_base);
381
382         if (!volume_mrl)
383             return VLC_SUCCESS;
384         vol = stream_UrlNew(s, volume_mrl);
385
386         if (!vol) {
387             free(volume_mrl);
388             return VLC_SUCCESS;
389         }
390     }
391 }
392