2013-08-25 20:58:29 +00:00
|
|
|
/*****************************************************************************
|
|
|
|
* 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>
|
|
|
|
|
2013-12-17 01:18:16 +00:00
|
|
|
#include "talloc.h"
|
2013-12-17 01:39:45 +00:00
|
|
|
#include "common/common.h"
|
2013-08-25 20:58:29 +00:00
|
|
|
#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;
|
2013-11-01 12:00:15 +00:00
|
|
|
bstr namedata = stream_peek(s, max_size);
|
|
|
|
if (namedata.len < max_size) {
|
2013-08-25 20:58:29 +00:00
|
|
|
free(name);
|
|
|
|
return -1;
|
|
|
|
}
|
2013-11-01 12:00:15 +00:00
|
|
|
memcpy(name, &namedata.start[name_offset], name_size);
|
2013-08-25 20:58:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
rar_file_t *current = NULL;
|
|
|
|
if (method != 0x30) {
|
2013-12-21 19:36:45 +00:00
|
|
|
MP_WARN(s, "Ignoring compressed file %s (method=0x%2.2x)\n", name, method);
|
2013-08-25 20:58:29 +00:00
|
|
|
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;
|
|
|
|
|
stream: redo playback abort handling
This mechanism originates from MPlayer's way of dealing with blocking
network, but it's still useful. On opening and closing, mpv waits for
network synchronously, and also some obscure commands and use-cases can
lead to such blocking. In these situations, the stream is asynchronously
forced to stop by "interrupting" it.
The old design interrupting I/O was a bit broken: polling with a
callback, instead of actively interrupting it. Change the direction of
this. There is no callback anymore, and the player calls
mp_cancel_trigger() to force the stream to return.
libavformat (via stream_lavf.c) has the old broken design, and fixing it
would require fixing libavformat, which won't happen so quickly. So we
have to keep that part. But everything above the stream layer is
prepared for a better design, and more sophisticated methods than
mp_cancel_test() could be easily introduced.
There's still one problem: commands are still run in the central
playback loop, which we assume can block on I/O in the worst case.
That's not a problem yet, because we simply mark some commands as being
able to stop playback of the current file ("quit" etc.), so input.c
could abort playback as soon as such a command is queued. But there are
also commands abort playback only conditionally, and the logic for that
is in the playback core and thus "unreachable". For example,
"playlist_next" aborts playback only if there's a next file. We don't
want it to always abort playback.
As a quite ugly hack, abort playback only if at least 2 abort commands
are queued - this pretty much happens only if the core is frozen and
doesn't react to input.
2014-09-13 12:23:08 +00:00
|
|
|
vol = stream_create(volume_mrl, STREAM_READ | STREAM_NO_FILTERS,
|
|
|
|
s->cancel, s->global);
|
2013-08-25 20:58:29 +00:00
|
|
|
|
|
|
|
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,
|
stream: redo playback abort handling
This mechanism originates from MPlayer's way of dealing with blocking
network, but it's still useful. On opening and closing, mpv waits for
network synchronously, and also some obscure commands and use-cases can
lead to such blocking. In these situations, the stream is asynchronously
forced to stop by "interrupting" it.
The old design interrupting I/O was a bit broken: polling with a
callback, instead of actively interrupting it. Change the direction of
this. There is no callback anymore, and the player calls
mp_cancel_trigger() to force the stream to return.
libavformat (via stream_lavf.c) has the old broken design, and fixing it
would require fixing libavformat, which won't happen so quickly. So we
have to keep that part. But everything above the stream layer is
prepared for a better design, and more sophisticated methods than
mp_cancel_test() could be easily introduced.
There's still one problem: commands are still run in the central
playback loop, which we assume can block on I/O in the worst case.
That's not a problem yet, because we simply mark some commands as being
able to stop playback of the current file ("quit" etc.), so input.c
could abort playback as soon as such a command is queued. But there are
also commands abort playback only conditionally, and the logic for that
is in the playback core and thus "unreachable". For example,
"playlist_next" aborts playback only if there's a next file. We don't
want it to always abort playback.
As a quite ugly hack, abort playback only if at least 2 abort commands
are queued - this pretty much happens only if the core is frozen and
doesn't react to input.
2014-09-13 12:23:08 +00:00
|
|
|
file->cancel, file->global);
|
2013-08-25 20:58:29 +00:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
|
2014-11-21 08:58:09 +00:00
|
|
|
int r = stream_read(file->s, data, max);
|
2013-08-25 20:58:29 +00:00
|
|
|
if (r <= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
total += r;
|
2014-11-21 08:58:09 +00:00
|
|
|
data = (char *)data + r;
|
2013-08-25 20:58:29 +00:00
|
|
|
file->i_pos += r;
|
|
|
|
if (file->i_pos >= chunk_end &&
|
|
|
|
RarSeek(file, file->i_pos))
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return total;
|
|
|
|
|
|
|
|
}
|