summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--demux/demux.c4
-rw-r--r--stream/rar.c454
-rw-r--r--stream/rar.h60
-rw-r--r--stream/stream.c34
-rw-r--r--stream/stream.h1
-rw-r--r--stream/stream_rar.c198
7 files changed, 753 insertions, 0 deletions
diff --git a/Makefile b/Makefile
index a2bb6b7300..440820b281 100644
--- a/Makefile
+++ b/Makefile
@@ -208,6 +208,7 @@ SOURCES = talloc.c \
osdep/numcores.c \
osdep/timer.c \
stream/cookies.c \
+ stream/rar.c \
stream/stream.c \
stream/stream_avdevice.c \
stream/stream_file.c \
@@ -215,6 +216,7 @@ SOURCES = talloc.c \
stream/stream_memory.c \
stream/stream_mf.c \
stream/stream_null.c \
+ stream/stream_rar.c \
sub/dec_sub.c \
sub/draw_bmp.c \
sub/find_subfiles.c \
diff --git a/demux/demux.c b/demux/demux.c
index 404050a420..f8653b4411 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -604,6 +604,10 @@ struct demuxer *demux_open(struct stream *stream, char *force_format,
}
}
+ // Peek this much data to avoid that stream_read() run by some demuxers
+ // or stream filters will flush previous peeked data.
+ stream_peek(stream, STREAM_BUFFER_SIZE);
+
// Test demuxers from first to last, one pass for each check_levels[] entry
for (int pass = 0; check_levels[pass] != -1; pass++) {
enum demux_check level = check_levels[pass];
diff --git a/stream/rar.c b/stream/rar.c
new file mode 100644
index 0000000000..fd4dbf008d
--- /dev/null
+++ b/stream/rar.c
@@ -0,0 +1,454 @@
+/*****************************************************************************
+ * rar.c: uncompressed RAR parser
+ *****************************************************************************
+ * Copyright (C) 2008-2010 Laurent Aimar
+ * $Id: f368245f4260f913f5c211e09b7dd511a96525e6 $
+ *
+ * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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
+ *****************************************************************************/
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+
+#include <libavutil/intreadwrite.h>
+
+#include "mpvcore/mp_common.h"
+#include "mpvcore/mp_talloc.h"
+#include "stream.h"
+#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]->mrl);
+ free(file->chunk[i]);
+ }
+ talloc_free(file->chunk);
+ free(file->name);
+ free_stream(file->s);
+ 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_SUBBLOCK = 0x7a,
+ 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(struct stream *s, rar_block_t *hdr)
+{
+ bstr data = stream_peek(s, 11);
+ const uint8_t *peek = (uint8_t *)data.start;
+ int peek_size = data.len;
+
+ if (peek_size < 7)
+ return -1;
+
+ hdr->crc = AV_RL16(&peek[0]);
+ hdr->type = peek[2];
+ hdr->flags = AV_RL16(&peek[3]);
+ hdr->size = AV_RL16(&peek[5]);
+ hdr->add_size = 0;
+ if ((hdr->flags & 0x8000) ||
+ hdr->type == RAR_BLOCK_FILE ||
+ hdr->type == RAR_BLOCK_SUBBLOCK) {
+ if (peek_size < 11)
+ return -1;
+ hdr->add_size = AV_RL32(&peek[7]);
+ }
+
+ if (hdr->size < 7)
+ return -1;
+ return 0;
+}
+static int SkipBlock(struct stream *s, const rar_block_t *hdr)
+{
+ uint64_t size = (uint64_t)hdr->size + hdr->add_size;
+
+ while (size > 0) {
+ int skip = MPMIN(size, INT_MAX);
+ if (!stream_skip(s, skip))
+ return -1;
+
+ size -= skip;
+ }
+ return 0;
+}
+
+static int IgnoreBlock(struct stream *s, int block)
+{
+ /* */
+ rar_block_t bk;
+ if (PeekBlock(s, &bk) || bk.type != block)
+ return -1;
+ return SkipBlock(s, &bk);
+}
+
+static int SkipEnd(struct stream *s, const rar_block_t *hdr)
+{
+ if (!(hdr->flags & RAR_BLOCK_END_HAS_NEXT))
+ return -1;
+
+ if (SkipBlock(s, hdr))
+ return -1;
+
+ /* Now, we need to look for a marker block,
+ * It seems that there is garbage at EOF */
+ for (;;) {
+ bstr peek = stream_peek(s, rar_marker_size);
+
+ if (peek.len < rar_marker_size)
+ return -1;
+
+ if (!memcmp(peek.start, rar_marker, rar_marker_size))
+ break;
+
+ if (!stream_skip(s, 1))
+ return -1;
+ }
+
+ /* Skip marker and archive blocks */
+ if (IgnoreBlock(s, RAR_BLOCK_MARKER))
+ return -1;
+ if (IgnoreBlock(s, RAR_BLOCK_ARCHIVE))
+ return -1;
+
+ return 0;
+}
+
+static int SkipFile(struct stream *s, int *count, rar_file_t ***file,
+ const rar_block_t *hdr, const char *volume_mrl)
+{
+ int min_size = 7+21;
+ if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
+ min_size += 8;
+ if (hdr->size < (unsigned)min_size)
+ return -1;
+
+ bstr data = stream_peek(s, min_size);
+ if (data.len < min_size)
+ return -1;
+ const uint8_t *peek = (uint8_t *)data.start;
+
+ /* */
+ uint32_t file_size_low = AV_RL32(&peek[7+4]);
+ uint8_t method = peek[7+18];
+ uint16_t name_size = AV_RL16(&peek[7+19]);
+ uint32_t file_size_high = 0;
+ if (hdr->flags & RAR_BLOCK_FILE_HAS_HIGH)
+ file_size_high = AV_RL32(&peek[7+29]);
+ const uint64_t file_size = ((uint64_t)file_size_high << 32) | file_size_low;
+
+ char *name = calloc(1, name_size + 1);
+ if (!name)
+ return -1;
+
+ 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;
+ bstr data = stream_peek(s, max_size);
+ if (data.len < max_size) {
+ free(name);
+ return -1;
+ }
+ memcpy(name, &data.start[name_offset], name_size);
+ }
+
+ rar_file_t *current = NULL;
+ if (method != 0x30) {
+ mp_msg(MSGT_STREAM, MSGL_WARN, "Ignoring compressed file %s (method=0x%2.2x)\n", name, method);
+ goto exit;
+ }
+
+ /* */
+ if( *count > 0 )
+ current = (*file)[*count - 1];
+
+ if (current &&
+ (current->is_complete ||
+ strcmp(current->name, name) ||
+ (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS) == 0))
+ current = NULL;
+
+ if (!current) {
+ if (hdr->flags & RAR_BLOCK_FILE_HAS_PREVIOUS)
+ goto exit;
+ current = calloc(1, sizeof(*current));
+ if (!current)
+ goto exit;
+ MP_TARRAY_APPEND(NULL, *file, *count, current);
+
+ current->name = name;
+ current->size = file_size;
+ current->is_complete = false;
+ current->real_size = 0;
+ current->chunk_count = 0;
+ current->chunk = NULL;
+
+ name = NULL;
+ }
+
+ /* Append chunks */
+ rar_file_chunk_t *chunk = malloc(sizeof(*chunk));
+ if (chunk) {
+ chunk->mrl = strdup(volume_mrl);
+ 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;
+ }
+
+ MP_TARRAY_APPEND(NULL, current->chunk, current->chunk_count, 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 = s->end_pos > 0;
+ if (!can_seek && current->size > 0)
+ return -1;
+ }
+
+ if (SkipBlock(s, hdr))
+ return -1;
+ return 0;
+}
+
+int RarProbe(struct stream *s)
+{
+ bstr peek = stream_peek(s, rar_marker_size);
+ if (peek.len < rar_marker_size)
+ return -1;
+ if (memcmp(peek.start, rar_marker, rar_marker_size))
+ return -1;
+ return 0;
+}
+
+typedef struct {
+ const char *match;
+ const char *format;
+ int start;
+ int stop;
+} rar_pattern_t;
+
+static const rar_pattern_t *FindVolumePattern(const char *location)
+{
+ static const rar_pattern_t patterns[] = {
+ { ".part1.rar", "%s.part%.1d.rar", 2, 9 },
+ { ".part01.rar", "%s.part%.2d.rar", 2, 99, },
+ { ".part001.rar", "%s.part%.3d.rar", 2, 999 },
+ { ".rar", "%s.%c%.2d", 0, 999 },
+ { NULL, NULL, 0, 0 },
+ };
+
+ const size_t location_size = strlen(location);
+ for (int i = 0; patterns[i].match != NULL; i++) {
+ const size_t match_size = strlen(patterns[i].match);
+
+ if (location_size < match_size)
+ continue;
+ if (!strcmp(&location[location_size - match_size], patterns[i].match))
+ return &patterns[i];
+ }
+ return NULL;
+}
+
+int RarParse(struct stream *s, int *count, rar_file_t ***file)
+{
+ *count = 0;
+ *file = NULL;
+
+ const rar_pattern_t *pattern = FindVolumePattern(s->url);
+ int volume_offset = 0;
+
+ char *volume_mrl;
+ if (asprintf(&volume_mrl, "%s", s->url) < 0)
+ return -1;
+
+ struct stream *vol = s;
+ for (;;) {
+ /* Skip marker & archive */
+ if (IgnoreBlock(vol, RAR_BLOCK_MARKER) ||
+ IgnoreBlock(vol, RAR_BLOCK_ARCHIVE)) {
+ if (vol != s)
+ free_stream(vol);
+ free(volume_mrl);
+ return -1;
+ }
+
+ /* */
+ int has_next = -1;
+ for (;;) {
+ rar_block_t bk;
+ int ret;
+
+ if (PeekBlock(vol, &bk))
+ break;
+
+ switch(bk.type) {
+ case RAR_BLOCK_END:
+ ret = SkipEnd(vol, &bk);
+ has_next = ret && (bk.flags & RAR_BLOCK_END_HAS_NEXT);
+ break;
+ case RAR_BLOCK_FILE:
+ ret = SkipFile(vol, count, file, &bk, volume_mrl);
+ break;
+ default:
+ ret = SkipBlock(vol, &bk);
+ break;
+ }
+ if (ret)
+ break;
+ }
+ if (has_next < 0 && *count > 0 && !(*file)[*count -1]->is_complete)
+ has_next = 1;
+ if (vol != s)
+ free_stream(vol);
+
+ if (!has_next || !pattern)
+ goto done;
+
+ /* Open next volume */
+ const int volume_index = pattern->start + volume_offset++;
+ if (volume_index > pattern->stop)
+ goto done;
+
+ char *volume_base;
+ if (asprintf(&volume_base, "%.*s",
+ (int)(strlen(s->url) - strlen(pattern->match)), s->url) < 0) {
+ goto done;
+ }
+
+ free(volume_mrl);
+ if (pattern->start) {
+ if (asprintf(&volume_mrl, pattern->format, volume_base, volume_index) < 0)
+ volume_mrl = NULL;
+ } else {
+ if (asprintf(&volume_mrl, pattern->format, volume_base,
+ 'r' + volume_index / 100, volume_index % 100) < 0)
+ volume_mrl = NULL;
+ }
+ free(volume_base);
+
+ if (!volume_mrl)
+ goto done;
+
+ vol = stream_create(volume_mrl, STREAM_READ | STREAM_NO_FILTERS, s->opts);
+
+ if (!vol)
+ goto done;
+ }
+
+done:
+ free(volume_mrl);
+ if (*count == 0) {
+ talloc_free(*file);
+ return -1;
+ }
+ return 0;
+}
+
+int RarSeek(rar_file_t *file, uint64_t position)
+{
+ if (position > file->real_size)
+ position = file->real_size;
+
+ /* Search the chunk */
+ const rar_file_chunk_t *old_chunk = file->current_chunk;
+ for (int i = 0; i < file->chunk_count; i++) {
+ file->current_chunk = file->chunk[i];
+ if (position < file->current_chunk->cummulated_size + file->current_chunk->size)
+ break;
+ }
+ file->i_pos = position;
+
+ const uint64_t offset = file->current_chunk->offset +
+ (position - file->current_chunk->cummulated_size);
+
+ if (strcmp(old_chunk->mrl, file->current_chunk->mrl)) {
+ if (file->s)
+ free_stream(file->s);
+ file->s = stream_create(file->current_chunk->mrl,
+ STREAM_READ | STREAM_NO_FILTERS,
+ file->opts);
+ }
+ return file->s ? stream_seek(file->s, offset) : 0;
+}
+
+ssize_t RarRead(rar_file_t *file, void *data, size_t size)
+{
+ size_t total = 0;
+ while (total < size) {
+ const uint64_t chunk_end = file->current_chunk->cummulated_size + file->current_chunk->size;
+ int max = MPMIN(MPMIN((int64_t)(size - total), (int64_t)(chunk_end - file->i_pos)), INT_MAX);
+ if (max <= 0)
+ break;
+
+ int r = file->s ? stream_read(file->s, data, max) : -1;
+ if (r <= 0)
+ break;
+
+ total += r;
+ if( data )
+ data = (char *)data + r;
+ file->i_pos += r;
+ if (file->i_pos >= chunk_end &&
+ RarSeek(file, file->i_pos))
+ break;
+ }
+ return total;
+
+}
diff --git a/stream/rar.h b/stream/rar.h
new file mode 100644
index 0000000000..6b001244a5
--- /dev/null
+++ b/stream/rar.h
@@ -0,0 +1,60 @@
+/*****************************************************************************
+ * rar.h: uncompressed RAR parser
+ *****************************************************************************
+ * Copyright (C) 2008-2010 Laurent Aimar
+ * $Id: 4dea45925c2d8f319d692475bc0307fdd9f6cfe7 $
+ *
+ * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifndef MP_RAR_H
+#define MP_RAR_H
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+typedef struct {
+ char *mrl;
+ 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 */
+
+ // When actually reading the data
+ struct MPOpts *opts;
+ uint64_t i_pos;
+ stream_t *s;
+ rar_file_chunk_t *current_chunk;
+} rar_file_t;
+
+int RarProbe(struct stream *);
+void RarFileDelete(rar_file_t *);
+int RarParse(struct stream *, int *, rar_file_t ***);
+
+int RarSeek(rar_file_t *file, uint64_t position);
+ssize_t RarRead(rar_file_t *file, void *data, size_t size);
+
+#endif
diff --git a/stream/stream.c b/stream/stream.c
index 474e46d96a..ebd582de73 100644
--- a/stream/stream.c
+++ b/stream/stream.c
@@ -75,6 +75,8 @@ extern const stream_info_t stream_info_file;
extern const stream_info_t stream_info_ifo;
extern const stream_info_t stream_info_dvd;
extern const stream_info_t stream_info_bluray;
+extern const stream_info_t stream_info_rar_filter;
+extern const stream_info_t stream_info_rar_entry;
static const stream_info_t *const stream_list[] = {
#ifdef CONFIG_VCD
@@ -111,6 +113,8 @@ static const stream_info_t *const stream_list[] = {
&stream_info_memory,
&stream_info_null,
&stream_info_mf,
+ &stream_info_rar_filter,
+ &stream_info_rar_entry,
&stream_info_file,
NULL
};
@@ -148,6 +152,36 @@ void mp_url_unescape_inplace(char *buf)
buf[o++] = '\0';
}
+// Escape according to http://tools.ietf.org/html/rfc3986#section-2.1
+// Only unreserved characters are not escaped.
+// The argument ok (if not NULL) is as follows:
+// ok[0] != '~': additional characters that are not escaped
+// ok[0] == '~': do not escape anything but these characters
+// (can't override the unreserved characters, which are
+// never escaped, and '%', which is always escaped)
+char *mp_url_escape(void *talloc_ctx, const char *s, const char *ok)
+{
+ int len = strlen(s);
+ char *buf = talloc_array(talloc_ctx, char, len * 3 + 1);
+ int o = 0;
+ for (int i = 0; i < len; i++) {
+ unsigned char c = s[i];
+ if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') || strchr("-._~", c) ||
+ (ok && ((ok[0] != '~') == !!strchr(ok, c)) && c != '%'))
+ {
+ buf[o++] = c;
+ } else {
+ const char hex[] = "0123456789ABCDEF";
+ buf[o++] = '%';
+ buf[o++] = hex[c / 16];
+ buf[o++] = hex[c % 16];
+ }
+ }
+ buf[o++] = '\0';
+ return buf;
+}
+
static const char *find_url_opt(struct stream *s, const char *opt)
{
for (int n = 0; s->info->url_options && s->info->url_options[n]; n++) {
diff --git a/stream/stream.h b/stream/stream.h
index 6f389ceb04..525265b52c 100644
--- a/stream/stream.h
+++ b/stream/stream.h
@@ -271,5 +271,6 @@ typedef struct {
} stream_language_t;
void mp_url_unescape_inplace(char *buf);
+char *mp_url_escape(void *talloc_ctx, const char *s, const char *ok);
#endif /* MPLAYER_STREAM_H */
diff --git a/stream/stream_rar.c b/stream/stream_rar.c
new file mode 100644
index 0000000000..d3ccfa3bf3
--- /dev/null
+++ b/stream/stream_rar.c
@@ -0,0 +1,198 @@
+// Major parts based on:
+/*****************************************************************************
+ * access.c: uncompressed RAR access
+ *****************************************************************************
+ * Copyright (C) 2008-2010 Laurent Aimar
+ * $Id: dcd973529e0029abe326d31f8d58cd13bbcc276c $
+ *
+ * Author: Laurent Aimar <fenrir _AT_ videolan _DOT_ org>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "osdep/io.h"
+
+#include "mpvcore/mp_msg.h"
+#include "stream.h"
+#include "mpvcore/m_option.h"
+#include "rar.h"
+
+/*
+This works as follows:
+
+- stream_open() with file01.rar
+ - is opened as normal file (stream_file.c or others) first
+ - stream_info_rar_filter stream filter applies
+ - leads to rar_filter_open()
+ - if multi-part, opens file02.rar, file03.rar, etc. as actual streams
+ (recursive opening is prevented with the STREAM_NO_FILTERS flag)
+ - read accesses return a m3u playlist with entries like:
+ rar://bla01.rar|subfile.mkv
+ (one such entry for each file contained in the rar)
+- stream_open() with the playlist entry, e.g. rar://bla01.rar|subfile.mkv
+ - leads to rar_entry_open()
+ - opens bla01.rar etc. again as actual streams
+ (again, STREAM_NO_FILTERS to open the actual files)
+ - read accesses go into subfile.mkv contained in the rar file(s)
+*/
+
+static int rar_entry_fill_buffer(stream_t *s, char *buffer, int max_len)
+{
+ rar_file_t *rar_file = s->priv;
+ return RarRead(rar_file, buffer, max_len);
+}
+
+static int rar_entry_seek(stream_t *s, int64_t newpos)
+{
+ rar_file_t *rar_file = s->priv;
+ return RarSeek(rar_file, newpos);
+}
+
+static void rar_entry_close(stream_t *s)
+{
+ rar_file_t *rar_file = s->priv;
+ RarFileDelete(rar_file);
+}
+
+static int rar_entry_open(stream_t *stream, int mode)
+{
+ if (!strchr(stream->path, '|'))
+ return STREAM_ERROR;
+
+ char *base = talloc_strdup(stream, stream->path);
+ char *name = strchr(base, '|');
+ *name++ = '\0';
+ mp_url_unescape_inplace(base);
+
+ struct stream *rar =
+ stream_create(base, STREAM_READ | STREAM_NO_FILTERS, stream->opts);
+ if (!rar)
+ return STREAM_ERROR;
+
+ int count;
+ rar_file_t **files;
+ if (RarProbe(rar) || RarParse(rar, &count, &files)) {
+ free_stream(rar);
+ return STREAM_ERROR;
+ }
+
+ rar_file_t *file = NULL;
+ for (int i = 0; i < count; i++) {
+ if (!file && strcmp(files[i]->name, name) == 0)
+ file = files[i];
+ else
+ RarFileDelete(files[i]);
+ }
+ talloc_free(files);
+ if (!file) {
+ free_stream(rar);
+ return STREAM_ERROR;
+ }
+
+ rar_file_chunk_t dummy = {
+ .mrl = base,
+ };
+ file->current_chunk = &dummy;
+ file->s = rar; // transfer ownership
+ file->opts = stream->opts;
+ RarSeek(file, 0);
+
+ stream->priv = file;
+ stream->end_pos = file->size;
+ stream->fill_buffer = rar_entry_fill_buffer;
+ stream->seek = rar_entry_seek;
+ stream->close = rar_entry_close;
+
+ return STREAM_OK;
+}
+
+static int rar_filter_fill_buffer(stream_t *s, char *buffer, int max_len)
+{
+ struct stream *m = s->priv;
+ return stream_read_partial(m, buffer, max_len);
+}
+
+static int rar_filter_seek(stream_t *s, int64_t newpos)
+{
+ struct stream *m = s->priv;
+ return stream_seek(m, newpos);
+}
+
+static void rar_filter_close(stream_t *s)
+{
+ struct stream *m = s->priv;
+ free_stream(m);
+}
+
+static int rar_filter_open(stream_t *stream, int mode)
+{
+ if (mode != STREAM_READ)
+ return STREAM_UNSUPPORTED;
+
+ struct stream *rar = stream->source;
+ if (!rar)
+ return STREAM_UNSUPPORTED;
+
+ int count;
+ rar_file_t **files;
+ if (!rar || RarProbe(rar) || RarParse(rar, &count, &files))
+ return STREAM_UNSUPPORTED;
+
+ void *tmp = talloc_new(NULL);
+
+ // Create a playlist containing all entries of the .rar file. The URLs
+ // link to rar_entry_open().
+ char *prefix = mp_url_escape(tmp, stream->url, "~|");
+ char *pl = talloc_strdup(tmp, "#EXTM3U\n");
+ for (int n = 0; n < count; n++) {
+ pl = talloc_asprintf_append_buffer(pl, "rar://%s|%s\n",
+ prefix, files[n]->name);
+ RarFileDelete(files[n]);
+ }
+ talloc_free(files);
+
+ struct stream *m = open_memory_stream(pl, strlen(pl));
+
+ stream->priv = m;
+ stream->end_pos = m->end_pos;
+ stream->fill_buffer = rar_filter_fill_buffer;
+ stream->seek = rar_filter_seek;
+ stream->close = rar_filter_close;
+ stream->safe_origin = true;
+
+ talloc_free(tmp);
+ return STREAM_OK;
+}
+
+const stream_info_t stream_info_rar_entry = {
+ .name = "rar_entry",
+ .open = rar_entry_open,
+ .protocols = (const char*[]){ "rar", NULL },
+};
+
+const stream_info_t stream_info_rar_filter = {
+ .name = "rar_filter",
+ .open = rar_filter_open,
+ .stream_filter = true,
+};