From fdf2f0fcecccf3f40b1dd2ed5922e04a056091b7 Mon Sep 17 00:00:00 2001 From: Laurent Aimar Date: Wed, 30 Jun 2010 21:47:27 +0200 Subject: [PATCH] Added support for multiple files in RAR archives. It is not yet perfect as it should probably create a XSPF playlist by reusing some code from the zip modules. --- modules/access/Modules.am | 5 + modules/access/rar/access.c | 214 +++++++++++ modules/access/rar/rar.c | 305 ++++++++++++++++ modules/access/rar/rar.h | 43 +++ modules/access/rar/stream.c | 164 +++++++++ modules/stream_filter/Modules.am | 2 - modules/stream_filter/rar.c | 608 ------------------------------- 7 files changed, 731 insertions(+), 610 deletions(-) create mode 100644 modules/access/rar/access.c create mode 100644 modules/access/rar/rar.c create mode 100644 modules/access/rar/rar.h create mode 100644 modules/access/rar/stream.c delete mode 100644 modules/stream_filter/rar.c diff --git a/modules/access/Modules.am b/modules/access/Modules.am index b7eefdf0ac..03ea157457 100644 --- a/modules/access/Modules.am +++ b/modules/access/Modules.am @@ -53,6 +53,9 @@ SOURCES_access_imem = imem.c SOURCES_access_avio = avio.c avio.h SOURCES_access_attachment = attachment.c +libstream_filter_rar_plugin_la_SOURCES = rar/rar.c rar/rar.h rar/stream.c +libaccess_rar_plugin_la_SOURCES = rar/rar.c rar/rar.h rar/access.c + libaccess_rtmp_plugin_la_SOURCES = \ rtmp/access.c \ rtmp/rtmp_amf_flv.c \ @@ -71,6 +74,8 @@ libvlc_LTLIBRARIES += \ libaccess_ftp_plugin.la \ libaccess_imem_plugin.la \ libaccess_attachment_plugin.la \ + libaccess_rar_plugin.la \ + libstream_filter_rar_plugin.la \ $(NULL) libxcb_screen_plugin_la_SOURCES = screen/xcb.c diff --git a/modules/access/rar/access.c b/modules/access/rar/access.c new file mode 100644 index 0000000000..92dd94364f --- /dev/null +++ b/modules/access/rar/access.c @@ -0,0 +1,214 @@ +/***************************************************************************** + * rar.c: uncompressed RAR access + ***************************************************************************** + * Copyright (C) 2008-2010 Laurent Aimar + * $Id$ + * + * Author: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include +#include + +#include +#include + +#include "rar.h" + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +static int Open (vlc_object_t *); +static void Close(vlc_object_t *); + +vlc_module_begin() + set_category(CAT_INPUT) + set_subcategory(SUBCAT_INPUT_STREAM_FILTER) + set_description(N_("Uncompressed RAR")) + set_capability("access", 0) + set_callbacks(Open, Close) + add_shortcut("rar") +vlc_module_end() + +/***************************************************************************** + * Local definitions/prototypes + *****************************************************************************/ +struct access_sys_t { + stream_t *s; + rar_file_t *file; + const rar_file_chunk_t *chunk; +}; + +static int Seek(access_t *access, uint64_t position) +{ + access_sys_t *sys = access->p_sys; + const rar_file_t *file = sys->file; + + if (position > file->real_size) + position = file->real_size; + + /* Search the chunk */ + for (int i = 0; i < file->chunk_count; i++) { + sys->chunk = file->chunk[i]; + if (position < sys->chunk->cummulated_size + sys->chunk->size) + break; + } + access->info.i_pos = position; + access->info.b_eof = false; + + const uint64_t offset = sys->chunk->offset + + (position - sys->chunk->cummulated_size); + return stream_Seek(sys->s, offset); +} + +static ssize_t Read(access_t *access, uint8_t *data, size_t size) +{ + access_sys_t *sys = access->p_sys; + + size_t total = 0; + while (total < size) { + const uint64_t chunk_end = sys->chunk->cummulated_size + sys->chunk->size; + int max = __MIN(__MIN((int64_t)(size - total), (int64_t)(chunk_end - access->info.i_pos)), INT_MAX); + if (max <= 0) + break; + + int r = stream_Read(sys->s, data, max); + if (r <= 0) + break; + + total += r; + if( data ) + data += r; + access->info.i_pos += r; + if (access->info.i_pos >= chunk_end && + Seek(access, access->info.i_pos)) + break; + } + if (size > 0 && total <= 0) + access->info.b_eof = true; + return total; + +} + +static int Control(access_t *access, int query, va_list args) +{ + stream_t *s = access->p_sys->s; + switch (query) { + case ACCESS_CAN_SEEK: { + bool *b = va_arg(args, bool *); + return stream_Control(s, STREAM_CAN_SEEK, b); + } + case ACCESS_CAN_FASTSEEK: { + bool *b = va_arg(args, bool *); + return stream_Control(s, STREAM_CAN_FASTSEEK, b); + } + /* FIXME the following request should ask the underlying access object */ + case ACCESS_CAN_PAUSE: + case ACCESS_CAN_CONTROL_PACE: { + bool *b = va_arg(args, bool *); + *b = true; + return VLC_SUCCESS; + } + case ACCESS_GET_PTS_DELAY: { + int64_t *delay = va_arg(args, int64_t *); + *delay = DEFAULT_PTS_DELAY; + return VLC_SUCCESS; + } + case ACCESS_SET_PAUSE_STATE: + return VLC_SUCCESS; + + default: + return VLC_EGENERIC; + } +} + +static int Open(vlc_object_t *object) +{ + access_t *access = (access_t*)object; + + if (!strchr(access->psz_location, '|')) + return VLC_EGENERIC; + + char *base = strdup(access->psz_location); + if (!base) + return VLC_EGENERIC; + char *name = strchr(base, '|'); + *name++ = '\0'; + decode_URI(base); + + stream_t *s = stream_UrlNew(access, base); + if (!s) + goto error; + int count; + rar_file_t **files; + if (RarProbe(s) || RarParse(s, &count, &files) || count <= 0) + goto error; + rar_file_t *file = NULL; + for (int i = 0; i < count; i++) { + if (!file && !strcmp(files[i]->name, name)) + file = files[i]; + else + RarFileDelete(files[i]); + } + free(files); + if (!file) + goto error; + + access_sys_t *sys = access->p_sys = malloc(sizeof(*sys)); + sys->s = s; + sys->file = file; + + access->pf_read = Read; + access->pf_block = NULL; + access->pf_control = Control; + access->pf_seek = Seek; + + access_InitFields(access); + access->info.i_size = file->size; + + Seek(access, 0); + + free(base); + return VLC_SUCCESS; + +error: + if (s) + stream_Delete(s); + free(base); + return VLC_EGENERIC; +} + +static void Close(vlc_object_t *object) +{ + access_t *access = (access_t*)object; + access_sys_t *sys = access->p_sys; + + stream_Delete(sys->s); + RarFileDelete(sys->file); + free(sys); +} + diff --git a/modules/access/rar/rar.c b/modules/access/rar/rar.c new file mode 100644 index 0000000000..517c54f908 --- /dev/null +++ b/modules/access/rar/rar.c @@ -0,0 +1,305 @@ +/***************************************************************************** + * rar.h: uncompressed RAR parser + ***************************************************************************** + * Copyright (C) 2008-2010 Laurent Aimar + * $Id$ + * + * Author: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include + +#include +#include + +#include "rar.h" + +static const uint8_t rar_marker[] = { + 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 +}; +static const int rar_marker_size = sizeof(rar_marker); + +void RarFileDelete(rar_file_t *file) +{ + for (int i = 0; i < file->chunk_count; i++) + free(file->chunk[i]); + free(file->chunk); + free(file->name); + free(file); +} + +typedef struct { + uint16_t crc; + uint8_t type; + uint16_t flags; + uint16_t size; + uint32_t add_size; +} rar_block_t; + +enum { + RAR_BLOCK_MARKER = 0x72, + RAR_BLOCK_ARCHIVE = 0x73, + RAR_BLOCK_FILE = 0x74, + RAR_BLOCK_END = 0x7b, +}; +enum { + RAR_BLOCK_END_HAS_NEXT = 0x0001, +}; +enum { + RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001, + RAR_BLOCK_FILE_HAS_NEXT = 0x0002, + RAR_BLOCK_FILE_HAS_HIGH = 0x0100, +}; + +static int PeekBlock(stream_t *s, rar_block_t *hdr) +{ + const uint8_t *peek; + int peek_size = stream_Peek(s, &peek, 11); + + if (peek_size < 7) + return VLC_EGENERIC; + + hdr->crc = GetWLE(&peek[0]); + hdr->type = peek[2]; + hdr->flags = GetWLE(&peek[3]); + hdr->size = GetWLE(&peek[5]); + hdr->add_size = 0; + if (hdr->flags & 0x8000) { + if (peek_size < 11) + return VLC_EGENERIC; + hdr->add_size = GetDWLE(&peek[7]); + } + + if (hdr->size < 7) + return VLC_EGENERIC; + return VLC_SUCCESS; +} +static int SkipBlock(stream_t *s, const rar_block_t *hdr) +{ + uint64_t size = (uint64_t)hdr->size + hdr->add_size; + + while (size > 0) { + int skip = __MIN(size, INT_MAX); + if (stream_Read(s, NULL, skip) < skip) + return VLC_EGENERIC; + + size -= skip; + } + return VLC_SUCCESS; +} + +static int IgnoreBlock(stream_t *s, int block) +{ + /* */ + rar_block_t bk; + if (PeekBlock(s, &bk) || bk.type != block) + return VLC_EGENERIC; + return SkipBlock(s, &bk); +} + +static int SkipEnd(stream_t *s, const rar_block_t *hdr) +{ + if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT)) + return VLC_EGENERIC; + + if (SkipBlock(s, hdr)) + return VLC_EGENERIC; + + /* Now, we need to look for a marker block, + * It seems that there is garbage at EOF */ + for (;;) { + const uint8_t *peek; + + if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size) + return VLC_EGENERIC; + + if (!memcmp(peek, rar_marker, rar_marker_size)) + break; + + if (stream_Read(s, NULL, 1) != 1) + return VLC_EGENERIC; + } + + /* Skip marker and archive blocks */ + if (IgnoreBlock(s, RAR_BLOCK_MARKER)) + return VLC_EGENERIC; + if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE)) + return VLC_EGENERIC; + + return VLC_SUCCESS; +} + +static int SkipFile(stream_t *s, int *count, rar_file_t ***file, const rar_block_t *hdr) +{ + const uint8_t *peek; + + int min_size = 7+21; + if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) + min_size += 8; + if (hdr->size < (unsigned)min_size) + return VLC_EGENERIC; + + if (stream_Peek(s, &peek, min_size) < min_size) + return VLC_EGENERIC; + + /* */ + uint32_t file_size_low = GetDWLE(&peek[7+4]); + uint8_t method = peek[7+18]; + uint16_t name_size = GetWLE(&peek[7+19]); + uint32_t file_size_high = 0; + if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) + file_size_high = GetDWLE(&peek[7+25]); + const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low; + + char *name = calloc(1, name_size + 1); + if (!name) + return VLC_EGENERIC; + + const int name_offset = (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25); + if (name_offset + name_size <= hdr->size) { + const int max_size = name_offset + name_size; + if (stream_Peek(s, &peek, max_size) < max_size) { + free(name); + return VLC_EGENERIC; + } + memcpy(name, &peek[name_offset], name_size); + } + + if (method != 0x30) { + msg_Warn(s, "Ignoring compressed file %s (method=0x%2.2x)", name, method); + goto exit; + } + + /* */ + rar_file_t *current = *count > 0 ? (*file)[*count - 1] : NULL; + if (current && + (current->is_complete || + current->size != file_size || + strcmp(current->name, name) || + (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0)) + current = NULL; + + if (!current) { + current = malloc(sizeof(*current)); + if (!current) + goto exit; + TAB_APPEND(*count, *file, current); + + current->name = name; + current->size = file_size; + current->is_complete = false; + current->real_size = 0; + TAB_INIT(current->chunk_count, current->chunk); + + name = NULL; + } + + /* Append chunks */ + rar_file_chunk_t *chunk = malloc(sizeof(*chunk)); + if (chunk) { + chunk->offset = stream_Tell(s) + hdr->size; + chunk->size = hdr->add_size; + chunk->cummulated_size = 0; + if (current->chunk_count > 0) { + rar_file_chunk_t *previous = current->chunk[current->chunk_count-1]; + + chunk->cummulated_size += previous->cummulated_size + + previous->size; + } + + TAB_APPEND(current->chunk_count, current->chunk, chunk); + + current->real_size += hdr->add_size; + } + if ((hdr->flags & RAR_BLOCK_FILE_HAS_NEXT) == 0) + current->is_complete = true; + +exit: + /* */ + free(name); + + /* We stop on the first non empty file if we cannot seek */ + if (current) { + bool can_seek = false; + stream_Control(s, STREAM_CAN_SEEK, &can_seek); + if (!can_seek && current->size > 0) + return VLC_EGENERIC; + } + + if (SkipBlock(s, hdr)) + return VLC_EGENERIC; + return VLC_SUCCESS; +} + +int RarProbe(stream_t *s) +{ + const uint8_t *peek; + if (stream_Peek(s, &peek, rar_marker_size) < rar_marker_size) + return VLC_EGENERIC; + if (memcmp(peek, rar_marker, rar_marker_size)) + return VLC_EGENERIC; + return VLC_SUCCESS; +} + +int RarParse(stream_t *s, int *count, rar_file_t ***file) +{ + *count = 0; + *file = NULL; + + /* Skip marker */ + if (IgnoreBlock(s, RAR_BLOCK_MARKER)) + return VLC_EGENERIC; + + /* Skip archive */ + if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE)) + return VLC_EGENERIC; + + /* */ + for (;;) { + rar_block_t bk; + int ret; + + if (PeekBlock(s, &bk)) + break; + + switch(bk.type) { + case RAR_BLOCK_END: + ret = SkipEnd(s, &bk); + break; + case RAR_BLOCK_FILE: + ret = SkipFile(s, count, file, &bk); + break; + default: + ret = SkipBlock(s, &bk); + break; + } + if (ret) + break; + } + + return VLC_SUCCESS; +} + diff --git a/modules/access/rar/rar.h b/modules/access/rar/rar.h new file mode 100644 index 0000000000..750693491c --- /dev/null +++ b/modules/access/rar/rar.h @@ -0,0 +1,43 @@ +/***************************************************************************** + * rar.h: uncompressed RAR parser + ***************************************************************************** + * Copyright (C) 2008-2010 Laurent Aimar + * $Id$ + * + * Author: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +typedef struct { + uint64_t offset; + uint64_t size; + uint64_t cummulated_size; +} rar_file_chunk_t; + +typedef struct { + char *name; + uint64_t size; + bool is_complete; + + int chunk_count; + rar_file_chunk_t **chunk; + uint64_t real_size; /* Gathered size */ +} rar_file_t; + +int RarProbe(stream_t *); +void RarFileDelete(rar_file_t *); +int RarParse(stream_t *, int *, rar_file_t ***); + diff --git a/modules/access/rar/stream.c b/modules/access/rar/stream.c new file mode 100644 index 0000000000..e7c92a49ad --- /dev/null +++ b/modules/access/rar/stream.c @@ -0,0 +1,164 @@ +/***************************************************************************** + * rar.c: uncompressed RAR stream filter + ***************************************************************************** + * Copyright (C) 2008-2010 Laurent Aimar + * $Id$ + * + * Author: Laurent Aimar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +/***************************************************************************** + * Preamble + *****************************************************************************/ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#include +#include + +#include "rar.h" + +/***************************************************************************** + * Module descriptor + *****************************************************************************/ +static int Open (vlc_object_t *); +static void Close(vlc_object_t *); + +vlc_module_begin() + set_category(CAT_INPUT) + set_subcategory(SUBCAT_INPUT_STREAM_FILTER) + set_description(N_("Uncompressed RAR")) + set_capability("stream_filter", 1) + set_callbacks(Open, Close) + add_shortcut("rar") +vlc_module_end() + +/**************************************************************************** + * Local definitions/prototypes + ****************************************************************************/ +struct stream_sys_t { + stream_t *payload; +}; + +static int Read(stream_t *s, void *data, unsigned size) +{ + return stream_Read(s->p_sys->payload, data, size); +} + +static int Peek( stream_t *s, const uint8_t **data, unsigned size) +{ + return stream_Peek(s->p_sys->payload, data, size); +} + +static int Control(stream_t *s, int query, va_list args) +{ + switch (query) { + case STREAM_GET_CONTENT_TYPE: { + char **mime = va_arg(args, char **); + *mime = strdup("audio/x-mpegurl"); + return VLC_EGENERIC; + } + default: + return stream_vaControl(s->p_sys->payload, query, args); + } +} + +static int Open(vlc_object_t *object) +{ + stream_t *s = (stream_t*)object; + + if (RarProbe(s->p_source)) + return VLC_EGENERIC; + + int count; + rar_file_t **files; + if (RarParse(s->p_source, &count, &files) || count <= 0) { + msg_Err(s, "Invalid or unsupported RAR archive"); + free(files); + return VLC_EGENERIC; + } + + /* TODO use xspf to have node for directories + * Reusing WriteXSPF from the zip access is probably a good idea + * (becareful about '\' and '/'. + */ + char *base; + char *encoded = encode_URI_component(s->psz_path); + if (!encoded || asprintf(&base, "rar://%s", encoded) < 0) + base = NULL; + free(encoded); + + char *data = strdup("#EXTM3U\n"); + for (int i = 0; i < count; i++) { + rar_file_t *f = files[i]; + char *next; + if (base && data && + asprintf(&next, "%s" + "#EXTINF:,,%s\n" + "%s|%s\n", + data, f->name, base, f->name) >= 0) { + free(data); + data = next; + } + RarFileDelete(f); + } + free(files); + if (!data) + return VLC_EGENERIC; + stream_t *payload = stream_MemoryNew(s, (uint8_t*)data, strlen(data), false); + if (!payload) { + free(data); + return VLC_EGENERIC; + } + + s->pf_read = Read; + s->pf_peek = Peek; + s->pf_control = Control; + + stream_sys_t *sys = s->p_sys = malloc(sizeof(*sys)); + if (!sys) { + stream_Delete(payload); + return VLC_ENOMEM; + } + sys->payload = payload; + + char *tmp; + if (asprintf(&tmp, "%s.m3u", s->psz_path) < 0) { + Close(object); + return VLC_ENOMEM; + } + free(s->psz_path); + s->psz_path = tmp; + + return VLC_SUCCESS; +} + +static void Close(vlc_object_t *object) +{ + stream_t *s = (stream_t*)object; + stream_sys_t *sys = s->p_sys; + + stream_Delete(sys->payload); + free(sys); +} + diff --git a/modules/stream_filter/Modules.am b/modules/stream_filter/Modules.am index 14bc22f2aa..91bce81ba4 100644 --- a/modules/stream_filter/Modules.am +++ b/modules/stream_filter/Modules.am @@ -1,10 +1,8 @@ SOURCES_decomp = decomp.c SOURCES_stream_filter_record = record.c -SOURCES_stream_filter_rar = rar.c libvlc_LTLIBRARIES += \ libstream_filter_record_plugin.la \ - libstream_filter_rar_plugin.la \ $(NULL) if !HAVE_WIN32 if !HAVE_WINCE diff --git a/modules/stream_filter/rar.c b/modules/stream_filter/rar.c deleted file mode 100644 index ddc3c46f5d..0000000000 --- a/modules/stream_filter/rar.c +++ /dev/null @@ -1,608 +0,0 @@ -/***************************************************************************** - * rar.c: uncompressed RAR stream filter (only the biggest file is extracted) - ***************************************************************************** - * Copyright (C) 2008 Laurent Aimar - * $Id$ - * - * Author: Laurent Aimar - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. - *****************************************************************************/ - -/***************************************************************************** - * Preamble - *****************************************************************************/ -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include -#include -#include - -#include -#include - -/***************************************************************************** - * Module descriptor - *****************************************************************************/ -static int Open ( vlc_object_t * ); -static void Close( vlc_object_t * ); - -vlc_module_begin() - set_category( CAT_INPUT ) - set_subcategory( SUBCAT_INPUT_STREAM_FILTER ) - set_description( N_("Uncompressed RAR") ) - set_capability( "stream_filter", 1 ) - set_callbacks( Open, Close ) - add_shortcut( "rar" ) -vlc_module_end() - -/***************************************************************************** - * - *****************************************************************************/ -static const uint8_t p_rar_marker[] = { - 0x52, 0x61, 0x72, 0x21, 0x1a, 0x07, 0x00 -}; -static const int i_rar_marker = sizeof(p_rar_marker); - -typedef struct -{ - uint64_t i_offset; - uint64_t i_size; - uint64_t i_cummulated_size; -} rar_file_chunk_t; -typedef struct -{ - char *psz_name; - uint64_t i_size; - bool b_complete; - - int i_chunk; - rar_file_chunk_t **pp_chunk; - uint64_t i_real_size; /* Gathered size */ -} rar_file_t; - -static void RarFileDelete( rar_file_t * ); -static int RarParse( stream_t *, int *pi_count, rar_file_t ***ppp_file ); - -struct stream_sys_t -{ - rar_file_t *p_file; - const rar_file_chunk_t *p_chunk; - - uint64_t i_position; - - uint8_t *p_peek_alloc; - uint8_t *p_peek; - unsigned int i_peek; -}; - - -/**************************************************************************** - * Local prototypes - ****************************************************************************/ -static int Read ( stream_t *, void *p_read, unsigned int i_read ); -static int Peek ( stream_t *, const uint8_t **pp_peek, unsigned int i_peek ); -static int Control( stream_t *, int i_query, va_list ); -static int Seek ( stream_t *s, uint64_t i_position ); - -/**************************************************************************** - * Open - ****************************************************************************/ -static int Open ( vlc_object_t *p_this ) -{ - stream_t *s = (stream_t*)p_this; - stream_sys_t *p_sys; - - /* */ - const uint8_t *p_peek; - if( stream_Peek( s->p_source, &p_peek, i_rar_marker ) < i_rar_marker ) - return VLC_EGENERIC; - if( memcmp( p_peek, p_rar_marker, i_rar_marker ) ) - return VLC_EGENERIC; - - /* */ - s->pf_read = Read; - s->pf_peek = Peek; - s->pf_control = Control; - - s->p_sys = p_sys = malloc( sizeof( *p_sys ) ); - if( !p_sys ) - return VLC_ENOMEM; - - /* */ - p_sys->p_file = NULL; - p_sys->i_position = 0; - p_sys->p_chunk = NULL; - - p_sys->p_peek_alloc = NULL; - p_sys->p_peek = NULL; - p_sys->i_peek = 0; - - /* */ - int i_count; - rar_file_t **pp_file; - if( RarParse( s, &i_count, &pp_file ) ) - { - i_count = 0; - pp_file = NULL; - } - - /* Select the longest file */ - p_sys->p_file = NULL; - for( int i = 0; i < i_count; i++ ) - { - if( !p_sys->p_file || p_sys->p_file->i_size < pp_file[i]->i_size ) - p_sys->p_file = pp_file[i]; - } - for( int i = 0; i < i_count; i++ ) - { - if( pp_file[i] != p_sys->p_file ) - RarFileDelete( pp_file[i] ); - } - free( pp_file ); - - if( !p_sys->p_file || p_sys->p_file->i_chunk <= 0 || p_sys->p_file->i_size <= 0 ) - { - msg_Err( s, "Invalid or unsupported RAR archive" ); - if( p_sys->p_file ) - RarFileDelete( p_sys->p_file ); - free( p_sys ); - return VLC_EGENERIC; - } - - /* */ - Seek( s, 0 ); - - /* */ - const rar_file_t *p_file = p_sys->p_file; - msg_Dbg( s, "Using RAR stream filter for '%s' %"PRId64"(expected %"PRId64") bytes in %d chunks", - p_file->psz_name, p_file->i_real_size, p_file->i_size, p_file->i_chunk ); - - return VLC_SUCCESS; -} - -/**************************************************************************** - * Close - ****************************************************************************/ -static void Close( vlc_object_t *p_this ) -{ - stream_t *s = (stream_t*)p_this; - stream_sys_t *p_sys = s->p_sys; - - RarFileDelete( p_sys->p_file ); - free( p_sys->p_peek_alloc ); - free( p_sys ); -} - -/**************************************************************************** - * Stream filters functions - ****************************************************************************/ -static int Read( stream_t *s, void *p_read, unsigned int i_read ) -{ - stream_sys_t *p_sys = s->p_sys; - uint8_t *p_data = p_read; - unsigned int i_total = 0; - - if( p_sys->i_peek > 0 && i_read > 0 ) - { - const unsigned int i_copy = __MIN( i_read, p_sys->i_peek ); - - if( p_data ) - { - memcpy( p_data, p_sys->p_peek, i_copy ); - p_data += i_copy; - } - - p_sys->i_peek -= i_copy; - p_sys->p_peek += i_copy; - i_total += i_copy; - } - - while( i_total < i_read ) - { - const uint64_t i_chunk_end = p_sys->p_chunk->i_cummulated_size + p_sys->p_chunk->i_size; - - int i_max = __MIN( i_read - i_total, i_chunk_end - p_sys->i_position ); - if( i_max <= 0 ) - break; - - int i_real = stream_Read( s->p_source, p_data, i_max ); - if( i_real <= 0 ) - break; - - i_total += i_real; - if( p_data ) - p_data += i_real; - p_sys->i_position += i_real; - if( p_sys->i_position >= i_chunk_end ) - { - if( Seek( s, p_sys->i_position ) ) - break; - } - } - return i_total; -} - -static int Peek( stream_t *s, const uint8_t **pp_peek, unsigned int i_peek ) -{ - stream_sys_t *p_sys = s->p_sys; - - if( i_peek <= p_sys->i_peek ) - { - *pp_peek = p_sys->p_peek; - return i_peek; - } - - /* */ - uint8_t *p_peek = malloc( i_peek ); - if( !p_peek ) - return 0; - - /* XXX yes stream_Read on ourself */ - int i_read = stream_Read( s, p_peek, i_peek ); - if( i_read <= 0 ) - { - free( p_peek ); - return i_read; - } - - free( p_sys->p_peek_alloc ); - - p_sys->p_peek_alloc = - p_sys->p_peek = p_peek; - p_sys->i_peek = i_read; - - *pp_peek = p_sys->p_peek; - return p_sys->i_peek; -} - -static int Control( stream_t *s, int i_query, va_list args ) -{ - stream_sys_t *p_sys = s->p_sys; - - switch( i_query ) - { - /* */ - case STREAM_SET_POSITION: - { - uint64_t i_position = va_arg( args, uint64_t ); - return Seek( s, i_position ); - } - - case STREAM_GET_POSITION: - { - uint64_t *pi_position = va_arg( args, uint64_t* ); - *pi_position = p_sys->i_position - p_sys->i_peek; - return VLC_SUCCESS; - } - - case STREAM_GET_SIZE: - { - uint64_t *pi_size = (uint64_t*)va_arg( args, uint64_t* ); - *pi_size = p_sys->p_file->i_real_size; - return VLC_SUCCESS; - } - - /* */ - case STREAM_GET_CONTENT_TYPE: /* arg1= char ** */ - return VLC_EGENERIC; - - case STREAM_UPDATE_SIZE: /* TODO maybe we should update i_real_size from file size and chunk offset ? */ - case STREAM_CONTROL_ACCESS: - case STREAM_CAN_SEEK: - case STREAM_CAN_FASTSEEK: - case STREAM_SET_RECORD_STATE: - return stream_vaControl( s->p_source, i_query, args ); - default: - return VLC_EGENERIC; - } -} - -/**************************************************************************** - * Helpers - ****************************************************************************/ -static int Seek( stream_t *s, uint64_t i_position ) -{ - stream_sys_t *p_sys = s->p_sys; - - if( i_position > p_sys->p_file->i_real_size ) - i_position = p_sys->p_file->i_real_size; - - /* Search the chunk */ - const rar_file_t *p_file = p_sys->p_file; - for( int i = 0; i < p_file->i_chunk; i++ ) - { - p_sys->p_chunk = p_file->pp_chunk[i]; - if( i_position < p_sys->p_chunk->i_cummulated_size + p_sys->p_chunk->i_size ) - break; - } - p_sys->i_position = i_position; - p_sys->i_peek = 0; - - const uint64_t i_seek = p_sys->p_chunk->i_offset + - ( i_position - p_sys->p_chunk->i_cummulated_size ); - return stream_Seek( s->p_source, i_seek ); -} - -/* Rar parser */ -static void RarFileDelete( rar_file_t *p_file ) -{ - for( int i = 0; i < p_file->i_chunk; i++ ) - free( p_file->pp_chunk[i] ); - free( p_file->pp_chunk ); - free( p_file->psz_name ); - free( p_file ); -} - -typedef struct -{ - uint16_t i_crc; - uint8_t i_type; - uint16_t i_flags; - uint16_t i_size; - uint32_t i_add_size; -} rar_block_t; - -enum -{ - RAR_BLOCK_MARKER = 0x72, - RAR_BLOCK_ARCHIVE = 0x73, - RAR_BLOCK_FILE = 0x74, - RAR_BLOCK_END = 0x7b, -}; -enum -{ - RAR_BLOCK_END_HAS_NEXT = 0x0001, -}; -enum -{ - RAR_BLOCK_FILE_HAS_PREVIOUS = 0x0001, - RAR_BLOCK_FILE_HAS_NEXT = 0x0002, - RAR_BLOCK_FILE_HAS_HIGH = 0x0100, -}; - -static int PeekBlock( stream_t *s, rar_block_t *p_hdr ) -{ - const uint8_t *p_peek; - int i_peek = stream_Peek( s->p_source, &p_peek, 11 ); - - if( i_peek < 7 ) - return VLC_EGENERIC; - - p_hdr->i_crc = GetWLE( &p_peek[0] ); - p_hdr->i_type = p_peek[2]; - p_hdr->i_flags = GetWLE( &p_peek[3] ); - p_hdr->i_size = GetWLE( &p_peek[5] ); - p_hdr->i_add_size = 0; - if( p_hdr->i_flags & 0x8000 ) - { - if( i_peek < 11 ) - return VLC_EGENERIC; - p_hdr->i_add_size = GetDWLE( &p_peek[7] ); - } - - if( p_hdr->i_size < 7 ) - return VLC_EGENERIC; - return VLC_SUCCESS; -} -static int SkipBlock( stream_t *s, const rar_block_t *p_hdr ) -{ - uint64_t i_size = (uint64_t)p_hdr->i_size + p_hdr->i_add_size; - - while( i_size > 0 ) - { - int i_skip = __MIN( i_size, INT_MAX ); - if( stream_Read( s->p_source, NULL, i_skip ) < i_skip ) - return VLC_EGENERIC; - - i_size -= i_skip; - } - return VLC_SUCCESS; -} - -static int IgnoreBlock( stream_t *s, int i_block ) -{ - /* */ - rar_block_t bk; - if( PeekBlock( s, &bk ) || bk.i_type != i_block ) - return VLC_EGENERIC; - return SkipBlock( s, &bk ); -} - -static int SkipEnd( stream_t *s, const rar_block_t *p_hdr ) -{ - if( !(p_hdr->i_flags & RAR_BLOCK_END_HAS_NEXT) ) - return VLC_EGENERIC; - - if( SkipBlock( s, p_hdr ) ) - return VLC_EGENERIC; - - /* Now, we need to look for a marker block, - * It seems that there is garbage at EOF */ - for( ;; ) - { - const uint8_t *p_peek; - - if( stream_Peek( s->p_source, &p_peek, i_rar_marker ) < i_rar_marker ) - return VLC_EGENERIC; - - if( !memcmp( p_peek, p_rar_marker, i_rar_marker ) ) - break; - - if( stream_Read( s->p_source, NULL, 1 ) != 1 ) - return VLC_EGENERIC; - } - - /* Skip marker and archive blocks */ - if( IgnoreBlock( s, RAR_BLOCK_MARKER ) ) - return VLC_EGENERIC; - if( IgnoreBlock( s, RAR_BLOCK_ARCHIVE ) ) - return VLC_EGENERIC; - - return VLC_SUCCESS; -} - -static int SkipFile( stream_t *s, int *pi_count, rar_file_t ***ppp_file, const rar_block_t *p_hdr ) -{ - const uint8_t *p_peek; - - int i_min_size = 7+21; - if( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH ) - i_min_size += 8; - if( p_hdr->i_size < (unsigned)i_min_size ) - return VLC_EGENERIC; - - if( stream_Peek( s->p_source, &p_peek, i_min_size ) < i_min_size ) - return VLC_EGENERIC; - - /* */ - uint32_t i_file_size_low = GetDWLE( &p_peek[7+4] ); - uint8_t i_method = p_peek[7+18]; - uint16_t i_name_size = GetWLE( &p_peek[7+19] ); - uint32_t i_file_size_high = 0; - if( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH ) - i_file_size_high = GetDWLE( &p_peek[7+25] ); - const uint64_t i_file_size = ((uint64_t)i_file_size_high << 32) | i_file_size_low; - - char *psz_name = calloc( 1, i_name_size + 1 ); - if( !psz_name ) - return VLC_EGENERIC; - - const int i_name_offset = (p_hdr->i_flags & RAR_BLOCK_FILE_HAS_HIGH) ? (7+33) : (7+25); - if( i_name_offset + i_name_size <= p_hdr->i_size ) - { - const int i_max_size = i_name_offset + i_name_size; - if( stream_Peek( s->p_source, &p_peek, i_max_size ) < i_max_size ) - { - free( psz_name ); - return VLC_EGENERIC; - } - memcpy( psz_name, &p_peek[i_name_offset], i_name_size ); - } - - if( i_method != 0x30 ) - { - msg_Warn( s, "Ignoring compressed file %s (method=0x%2.2x)", psz_name, i_method ); - goto exit; - } - - /* */ - rar_file_t *p_current = *pi_count > 0 ? (*ppp_file)[*pi_count - 1] : NULL; - if( p_current && - ( p_current->b_complete || - p_current->i_size != i_file_size || - strcmp( p_current->psz_name, psz_name ) || - ( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_PREVIOUS ) == 0 ) ) - p_current = NULL; - - if( !p_current ) - { - p_current = malloc( sizeof( *p_current ) ); - if( !p_current ) - goto exit; - TAB_APPEND( *pi_count, *ppp_file, p_current ); - - p_current->psz_name = psz_name; - p_current->i_size = i_file_size; - p_current->b_complete = false; - p_current->i_real_size = 0; - TAB_INIT( p_current->i_chunk, p_current->pp_chunk ); - - psz_name = NULL; - } - - /* Append chunks */ - rar_file_chunk_t *p_chunk = malloc( sizeof( *p_chunk ) ); - if( p_chunk ) - { - p_chunk->i_offset = stream_Tell( s->p_source ) + p_hdr->i_size; - p_chunk->i_size = p_hdr->i_add_size; - p_chunk->i_cummulated_size = 0; - if( p_current->i_chunk > 0 ) - { - rar_file_chunk_t *p_previous = p_current->pp_chunk[p_current->i_chunk-1]; - - p_chunk->i_cummulated_size += p_previous->i_cummulated_size + - p_previous->i_size; - } - - TAB_APPEND( p_current->i_chunk, p_current->pp_chunk, p_chunk ); - - p_current->i_real_size += p_hdr->i_add_size; - } - if( ( p_hdr->i_flags & RAR_BLOCK_FILE_HAS_NEXT ) == 0 ) - p_current->b_complete = true; - -exit: - /* */ - free( psz_name ); - - /* We stop on the first non empty file if we cannot seek */ - if( p_current ) - { - bool b_can_seek = false; - stream_Control( s->p_source, STREAM_CAN_SEEK, &b_can_seek ); - if( !b_can_seek && p_current->i_size > 0 ) - return VLC_EGENERIC; - } - - if( SkipBlock( s, p_hdr ) ) - return VLC_EGENERIC; - return VLC_SUCCESS; -} - -static int RarParse( stream_t *s, int *pi_count, rar_file_t ***ppp_file ) -{ - *pi_count = 0; - *ppp_file = NULL; - - /* Skip marker */ - if( IgnoreBlock( s, RAR_BLOCK_MARKER ) ) - return VLC_EGENERIC; - - /* Skip archive */ - if( IgnoreBlock( s, RAR_BLOCK_ARCHIVE ) ) - return VLC_EGENERIC; - - /* */ - for( ;; ) - { - rar_block_t bk; - int i_ret; - - if( PeekBlock( s, &bk ) ) - break; - - switch( bk.i_type ) - { - case RAR_BLOCK_END: - i_ret = SkipEnd( s, &bk ); - break; - case RAR_BLOCK_FILE: - i_ret = SkipFile( s, pi_count, ppp_file, &bk ); - break; - default: - i_ret = SkipBlock( s, &bk ); - break; - } - if( i_ret ) - break; - } - - return VLC_SUCCESS; -} - -- 2.39.2