]> git.sesse.net Git - vlc/blob - modules/access/rar/rar.c
Added support for multiple files in RAR archives.
[vlc] / modules / access / rar / rar.c
1 /*****************************************************************************
2  * rar.h: 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 #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 #include "rar.h"
39
40 static const uint8_t rar_marker[] = {
41     0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00
42 };
43 static const int rar_marker_size = sizeof(rar_marker);
44
45 void RarFileDelete(rar_file_t *file)
46 {
47     for (int i = 0; i < file->chunk_count; i++)
48         free(file->chunk[i]);
49     free(file->chunk);
50     free(file->name);
51     free(file);
52 }
53
54 typedef struct {
55     uint16_t crc;
56     uint8_t  type;
57     uint16_t flags;
58     uint16_t size;
59     uint32_t add_size;
60 } rar_block_t;
61
62 enum {
63     RAR_BLOCK_MARKER = 0x72,
64     RAR_BLOCK_ARCHIVE = 0x73,
65     RAR_BLOCK_FILE = 0x74,
66     RAR_BLOCK_END = 0x7b,
67 };
68 enum {
69     RAR_BLOCK_END_HAS_NEXT = 0x0001,
70 };
71 enum {
72     RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
73     RAR_BLOCK_FILE_HAS_NEXT     = 0x0002,
74     RAR_BLOCK_FILE_HAS_HIGH     = 0x0100,
75 };
76
77 static int PeekBlock(stream_t *s, rar_block_t *hdr)
78 {
79     const uint8_t *peek;
80     int peek_size = stream_Peek(s, &peek, 11);
81
82     if (peek_size < 7)
83         return VLC_EGENERIC;
84
85     hdr->crc   = GetWLE(&peek[0]);
86     hdr->type  = peek[2];
87     hdr->flags = GetWLE(&peek[3]);
88     hdr->size  = GetWLE(&peek[5]);
89     hdr->add_size = 0;
90     if (hdr->flags & 0x8000) {
91         if (peek_size < 11)
92             return VLC_EGENERIC;
93         hdr->add_size = GetDWLE(&peek[7]);
94     }
95
96     if (hdr->size < 7)
97         return VLC_EGENERIC;
98     return VLC_SUCCESS;
99 }
100 static int SkipBlock(stream_t *s, const rar_block_t *hdr)
101 {
102     uint64_t size = (uint64_t)hdr->size + hdr->add_size;
103
104     while (size > 0) {
105         int skip = __MIN(size, INT_MAX);
106         if (stream_Read(s, NULL, skip) < skip)
107             return VLC_EGENERIC;
108
109         size -= skip;
110     }
111     return VLC_SUCCESS;
112 }
113
114 static int IgnoreBlock(stream_t *s, int block)
115 {
116     /* */
117     rar_block_t bk;
118     if (PeekBlock(s, &bk) || bk.type != block)
119         return VLC_EGENERIC;
120     return SkipBlock(s, &bk);
121 }
122
123 static int SkipEnd(stream_t *s, const rar_block_t *hdr)
124 {
125     if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT))
126         return VLC_EGENERIC;
127
128     if (SkipBlock(s, hdr))
129         return VLC_EGENERIC;
130
131     /* Now, we need to look for a marker block,
132      * It seems that there is garbage at EOF */
133     for (;;) {
134         const uint8_t *peek;
135
136         if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
137             return VLC_EGENERIC;
138
139         if (!memcmp(peek, rar_marker, rar_marker_size))
140             break;
141
142         if (stream_Read(s, NULL, 1) != 1)
143             return VLC_EGENERIC;
144     }
145
146     /* Skip marker and archive blocks */
147     if (IgnoreBlock(s, RAR_BLOCK_MARKER))
148         return VLC_EGENERIC;
149     if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
150         return VLC_EGENERIC;
151
152     return VLC_SUCCESS;
153 }
154
155 static int SkipFile(stream_t *s, int *count, rar_file_t ***file, const rar_block_t *hdr)
156 {
157     const uint8_t *peek;
158
159     int min_size = 7+21;
160     if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
161         min_size += 8;
162     if (hdr->size < (unsigned)min_size)
163         return VLC_EGENERIC;
164
165     if (stream_Peek(s, &peek, min_size) < min_size)
166         return VLC_EGENERIC;
167
168     /* */
169     uint32_t file_size_low = GetDWLE(&peek[7+4]);
170     uint8_t  method = peek[7+18];
171     uint16_t name_size = GetWLE(&peek[7+19]);
172     uint32_t file_size_high = 0;
173     if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
174         file_size_high = GetDWLE(&peek[7+25]);
175     const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low;
176
177     char *name = calloc(1, name_size + 1);
178     if (!name)
179         return VLC_EGENERIC;
180
181     const int name_offset = (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25);
182     if (name_offset + name_size <= hdr->size) {
183         const int max_size = name_offset + name_size;
184         if (stream_Peek(s, &peek, max_size) < max_size) {
185             free(name);
186             return VLC_EGENERIC;
187         }
188         memcpy(name, &peek[name_offset], name_size);
189     }
190
191     if (method != 0x30) {
192         msg_Warn(s, "Ignoring compressed file %s (method=0x%2.2x)", name, method);
193         goto exit;
194     }
195
196     /* */
197     rar_file_t *current = *count > 0 ? (*file)[*count - 1] : NULL;
198     if (current &&
199         (current->is_complete ||
200           current->size != file_size ||
201           strcmp(current->name, name) ||
202           (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0))
203         current = NULL;
204
205     if (!current) {
206         current = malloc(sizeof(*current));
207         if (!current)
208             goto exit;
209         TAB_APPEND(*count, *file, current);
210
211         current->name = name;
212         current->size = file_size;
213         current->is_complete = false;
214         current->real_size = 0;
215         TAB_INIT(current->chunk_count, current->chunk);
216
217         name = NULL;
218     }
219
220     /* Append chunks */
221     rar_file_chunk_t *chunk = malloc(sizeof(*chunk));
222     if (chunk) {
223         chunk->offset = stream_Tell(s) + hdr->size;
224         chunk->size = hdr->add_size;
225         chunk->cummulated_size = 0;
226         if (current->chunk_count > 0) {
227             rar_file_chunk_t *previous = current->chunk[current->chunk_count-1];
228
229             chunk->cummulated_size += previous->cummulated_size +
230                                       previous->size;
231         }
232
233         TAB_APPEND(current->chunk_count, current->chunk, chunk);
234
235         current->real_size += hdr->add_size;
236     }
237     if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0)
238         current->is_complete = true;
239
240 exit:
241     /* */
242     free(name);
243
244     /* We stop on the first non empty file if we cannot seek */
245     if (current) {
246         bool can_seek = false;
247         stream_Control(s, STREAM_CAN_SEEK, &can_seek);
248         if (!can_seek && current->size > 0)
249             return VLC_EGENERIC;
250     }
251
252     if (SkipBlock(s, hdr))
253         return VLC_EGENERIC;
254     return VLC_SUCCESS;
255 }
256
257 int RarProbe(stream_t *s)
258 {
259     const uint8_t *peek;
260     if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
261         return VLC_EGENERIC;
262     if (memcmp(peek, rar_marker, rar_marker_size))
263         return VLC_EGENERIC;
264     return VLC_SUCCESS;
265 }
266
267 int RarParse(stream_t *s, int *count, rar_file_t ***file)
268 {
269     *count = 0;
270     *file = NULL;
271
272     /* Skip marker */
273     if (IgnoreBlock(s, RAR_BLOCK_MARKER))
274         return VLC_EGENERIC;
275
276     /* Skip archive  */
277     if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
278         return VLC_EGENERIC;
279
280     /* */
281     for (;;) {
282         rar_block_t bk;
283         int ret;
284
285         if (PeekBlock(s, &bk))
286             break;
287
288         switch(bk.type) {
289         case RAR_BLOCK_END:
290             ret = SkipEnd(s, &bk);
291             break;
292         case RAR_BLOCK_FILE:
293             ret = SkipFile(s, count, file, &bk);
294             break;
295         default:
296             ret = SkipBlock(s, &bk);
297             break;
298         }
299         if (ret)
300             break;
301     }
302
303     return VLC_SUCCESS;
304 }
305