1 /*****************************************************************************
2 * rar.c: uncompressed RAR parser
3 *****************************************************************************
4 * Copyright (C) 2008-2010 Laurent Aimar
7 * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
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.
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.
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 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_stream.h>
41 static const uint8_t rar_marker[] = {
42 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00
44 static const int rar_marker_size = sizeof(rar_marker);
46 void RarFileDelete(rar_file_t *file)
48 for (int i = 0; i < file->chunk_count; i++) {
49 free(file->chunk[i]->mrl);
66 RAR_BLOCK_MARKER = 0x72,
67 RAR_BLOCK_ARCHIVE = 0x73,
68 RAR_BLOCK_FILE = 0x74,
69 RAR_BLOCK_SUBBLOCK = 0x7a,
73 RAR_BLOCK_END_HAS_NEXT = 0x0001,
76 RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001,
77 RAR_BLOCK_FILE_HAS_NEXT = 0x0002,
78 RAR_BLOCK_FILE_HAS_HIGH = 0x0100,
81 static int PeekBlock(stream_t *s, rar_block_t *hdr)
84 int peek_size = stream_Peek(s, &peek, 11);
89 hdr->crc = GetWLE(&peek[0]);
91 hdr->flags = GetWLE(&peek[3]);
92 hdr->size = GetWLE(&peek[5]);
94 if ((hdr->flags & 0x8000) ||
95 hdr->type == RAR_BLOCK_FILE ||
96 hdr->type == RAR_BLOCK_SUBBLOCK) {
99 hdr->add_size = GetDWLE(&peek[7]);
106 static int SkipBlock(stream_t *s, const rar_block_t *hdr)
108 uint64_t size = (uint64_t)hdr->size + hdr->add_size;
111 int skip = __MIN(size, INT_MAX);
112 if (stream_Read(s, NULL, skip) < skip)
120 static int IgnoreBlock(stream_t *s, int block)
124 if (PeekBlock(s, &bk) || bk.type != block)
126 return SkipBlock(s, &bk);
129 static int SkipEnd(stream_t *s, const rar_block_t *hdr)
131 if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT))
134 if (SkipBlock(s, hdr))
137 /* Now, we need to look for a marker block,
138 * It seems that there is garbage at EOF */
142 if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
145 if (!memcmp(peek, rar_marker, rar_marker_size))
148 if (stream_Read(s, NULL, 1) != 1)
152 /* Skip marker and archive blocks */
153 if (IgnoreBlock(s, RAR_BLOCK_MARKER))
155 if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
161 static int SkipFile(stream_t *s, int *count, rar_file_t ***file,
162 const rar_block_t *hdr, const char *volume_mrl)
167 if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
169 if (hdr->size < (unsigned)min_size)
172 if (stream_Peek(s, &peek, min_size) < min_size)
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;
184 char *name = calloc(1, name_size + 1);
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) {
195 memcpy(name, &peek[name_offset], name_size);
198 rar_file_t *current = NULL;
199 if (method != 0x30) {
200 msg_Warn(s, "Ignoring compressed file %s (method=0x%2.2x)", name, method);
206 current = (*file)[*count - 1];
209 (current->is_complete ||
210 strcmp(current->name, name) ||
211 (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0))
215 if (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS)
217 current = malloc(sizeof(*current));
220 TAB_APPEND(*count, *file, current);
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);
232 rar_file_chunk_t *chunk = malloc(sizeof(*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];
241 chunk->cummulated_size += previous->cummulated_size +
245 TAB_APPEND(current->chunk_count, current->chunk, chunk);
247 current->real_size += hdr->add_size;
249 if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0)
250 current->is_complete = true;
256 /* We stop on the first non empty file if we cannot seek */
258 bool can_seek = false;
259 stream_Control(s, STREAM_CAN_SEEK, &can_seek);
260 if (!can_seek && current->size > 0)
264 if (SkipBlock(s, hdr))
269 int RarProbe(stream_t *s)
272 if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size)
274 if (memcmp(peek, rar_marker, rar_marker_size))
286 static const rar_pattern_t *FindVolumePattern(const char *location)
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 },
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);
300 if (location_size < match_size)
302 if (!strcmp(&location[location_size - match_size], patterns[i].match))
308 int RarParse(stream_t *s, int *count, rar_file_t ***file)
313 const rar_pattern_t *pattern = FindVolumePattern(s->psz_path);
314 int volume_offset = 0;
317 if (asprintf(&volume_mrl, "%s://%s",
318 s->psz_access, s->psz_path) < 0)
323 /* Skip marker & archive */
324 if (IgnoreBlock(vol, RAR_BLOCK_MARKER) ||
325 IgnoreBlock(vol, RAR_BLOCK_ARCHIVE)) {
338 if (PeekBlock(vol, &bk))
343 ret = SkipEnd(vol, &bk);
344 has_next = ret && (bk.flags & RAR_BLOCK_END_HAS_NEXT);
347 ret = SkipFile(vol, count, file, &bk, volume_mrl);
350 ret = SkipBlock(vol, &bk);
356 if (has_next < 0 && *count > 0 && !(*file)[*count -1]->is_complete)
361 if (!has_next || !pattern) {
366 /* Open next volume */
367 const int volume_index = pattern->start + volume_offset++;
368 if (volume_index > pattern->stop) {
374 if (asprintf(&volume_base, "%s://%.*s",
376 (int)(strlen(s->psz_path) - strlen(pattern->match)), s->psz_path) < 0) {
382 if (pattern->start) {
383 if (asprintf(&volume_mrl, pattern->format, volume_base, volume_index) < 0)
386 if (asprintf(&volume_mrl, pattern->format, volume_base,
387 'r' + volume_index / 100, volume_index % 100) < 0)
395 const int s_flags = s->i_flags;
397 s->i_flags |= OBJECT_FLAGS_NOINTERACT;
398 vol = stream_UrlNew(s, volume_mrl);
399 s->i_flags = s_flags;