diff options
-rw-r--r-- | DOCS/man/en/options.rst | 16 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | demux/demux.c | 4 | ||||
-rw-r--r-- | demux/demux.h | 3 | ||||
-rw-r--r-- | demux/demux_playlist.c | 136 | ||||
-rw-r--r-- | mpvcore/asxparser.c | 13 | ||||
-rw-r--r-- | mpvcore/command.c | 2 | ||||
-rw-r--r-- | mpvcore/mplayer.c | 45 | ||||
-rw-r--r-- | mpvcore/options.c | 2 | ||||
-rw-r--r-- | mpvcore/options.h | 1 | ||||
-rw-r--r-- | mpvcore/parser-mpcmd.c | 3 | ||||
-rw-r--r-- | mpvcore/playlist_parser.c | 69 | ||||
-rw-r--r-- | mpvcore/playlist_parser.h | 9 | ||||
-rw-r--r-- | mpvcore/timeline/tl_cue.c | 2 | ||||
-rw-r--r-- | stream/stream.c | 18 | ||||
-rw-r--r-- | stream/stream.h | 2 |
16 files changed, 240 insertions, 88 deletions
diff --git a/DOCS/man/en/options.rst b/DOCS/man/en/options.rst index 37335154f4..61fa750b76 100644 --- a/DOCS/man/en/options.rst +++ b/DOCS/man/en/options.rst @@ -1198,6 +1198,22 @@ ``--list-properties`` Print a list of the available properties. +``--load-unsafe-playlists`` + Normally, something like ``mpv playlist.m3u`` won't load the playlist. This + is because the playlist code is unsafe. (This is the same in all other + variations of MPlayer.) + + See ``--playlist`` for details. + + Note: this option will allow opening playlists using the ``playlist`` + special demuxer. The ``--playlist`` uses different code, and supports more + playlist formats than the playlist demuxer. This means that for now, the + ``--playlist`` option should always be used if you intend to open playlists. + Background: the special demuxer contains newly written code, while the + ``--playlist`` option uses the old MPlayer code. Adding support for more + playlist formats to the special demuxer is work in progress, and eventually + the old code should disappear. + ``--loop=<number|inf|no>`` Loops playback ``<number>`` times. ``inf`` means forever and ``no`` disables looping. If several files are specified on command line, the entire playlist @@ -171,8 +171,9 @@ SOURCES = talloc.c \ demux/demux_lavf.c \ demux/demux_mf.c \ demux/demux_mkv.c \ - demux/demux_subreader.c \ + demux/demux_playlist.c \ demux/demux_raw.c \ + demux/demux_subreader.c \ demux/ebml.c \ demux/mf.c \ mpvcore/asxparser.c \ diff --git a/demux/demux.c b/demux/demux.c index 272b089871..404050a420 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -59,6 +59,7 @@ extern const demuxer_desc_t demuxer_desc_lavf; extern const demuxer_desc_t demuxer_desc_mng; extern const demuxer_desc_t demuxer_desc_libass; extern const demuxer_desc_t demuxer_desc_subreader; +extern const demuxer_desc_t demuxer_desc_playlist; /* Please do not add any new demuxers here. If you want to implement a new * demuxer, add it to libavformat, except for wrappers around external @@ -81,6 +82,7 @@ const demuxer_desc_t *const demuxer_list[] = { #ifdef CONFIG_MNG &demuxer_desc_mng, #endif + &demuxer_desc_playlist, // Pretty aggressive, so should be last. &demuxer_desc_subreader, /* Please do not add any new demuxers here. If you want to implement a new @@ -309,6 +311,8 @@ static void free_sh_stream(struct sh_stream *sh) void free_demuxer(demuxer_t *demuxer) { + if (!demuxer) + return; if (demuxer->desc->close) demuxer->desc->close(demuxer); // free streams: diff --git a/demux/demux.h b/demux/demux.h index 06bf65809b..6976982e15 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -182,6 +182,9 @@ typedef struct demuxer { // for trivial demuxers which just read the whole file for codec to use struct bstr file_contents; + // If the file is a playlist file + struct playlist *playlist; + void *priv; // demuxer-specific internal data char **info; // metadata struct MPOpts *opts; diff --git a/demux/demux_playlist.c b/demux/demux_playlist.c new file mode 100644 index 0000000000..b924140497 --- /dev/null +++ b/demux/demux_playlist.c @@ -0,0 +1,136 @@ +/* + * This file is part of mpv. + * + * mpv 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. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "mpvcore/mp_common.h" +#include "mpvcore/options.h" +#include "mpvcore/mp_msg.h" +#include "mpvcore/playlist.h" +#include "mpvcore/path.h" +#include "stream/stream.h" +#include "demux.h" + +#define PROBE_SIZE (8 * 1024) + +struct pl_parser { + struct stream *s; + char buffer[8 * 1024]; + int utf16; + struct playlist *pl; + bool probing; +}; + +static char *pl_get_line0(struct pl_parser *p) +{ + char *res = stream_read_line(p->s, p->buffer, sizeof(p->buffer), p->utf16); + if (res) { + int len = strlen(res); + if (len > 0 && res[len - 1] == '\n') + res[len - 1] = '\0'; + } + return res; +} + +static bstr pl_get_line(struct pl_parser *p) +{ + return bstr0(pl_get_line0(p)); +} + +static void pl_add(struct pl_parser *p, bstr entry) +{ + char *s = bstrto0(NULL, entry); + playlist_add_file(p->pl, s); + talloc_free(s); +} + +static bool pl_eof(struct pl_parser *p) +{ + return p->s->eof; +} + +static int parse_m3u(struct pl_parser *p) +{ + bstr line = bstr_strip(pl_get_line(p)); + if (!bstr_equals0(line, "#EXTM3U")) + return -1; + if (p->probing) + return 0; + while (!pl_eof(p)) { + line = bstr_lstrip(pl_get_line(p)); + if (line.len == 0 || bstr_startswith0(line, "#")) + continue; + pl_add(p, line); + } + return 0; +} + +struct pl_format { + const char *name; + int (*parse)(struct pl_parser *p); +}; + +static const struct pl_format formats[] = { + {"m3u", parse_m3u}, +}; + +static const struct pl_format *probe_pl(struct pl_parser *p, bool force) +{ + int64_t start = stream_tell(p->s); + for (int n = 0; n < MP_ARRAY_SIZE(formats); n++) { + const struct pl_format *fmt = &formats[n]; + stream_seek(p->s, start); + if (fmt->parse(p) >= 0) + return fmt; + } + return NULL; +} + +static int open_file(struct demuxer *demuxer, enum demux_check check) +{ + bool force = check < DEMUX_CHECK_UNSAFE || check == DEMUX_CHECK_REQUEST; + + struct pl_parser *p = talloc_zero(NULL, struct pl_parser); + p->pl = talloc_zero(p, struct playlist); + + bstr probe_buf = stream_peek(demuxer->stream, PROBE_SIZE); + p->s = open_memory_stream(probe_buf.start, probe_buf.len); + p->utf16 = stream_skip_bom(p->s); + p->probing = true; + const struct pl_format *fmt = probe_pl(p, force); + free_stream(p->s); + playlist_clear(p->pl); + if (!fmt) { + talloc_free(p); + return -1; + } + + p->probing = false; + p->s = demuxer->stream; + p->utf16 = stream_skip_bom(p->s); + bool ok = fmt->parse(p) >= 0; + if (ok) + playlist_add_base_path(p->pl, mp_dirname(demuxer->filename)); + demuxer->playlist = talloc_steal(demuxer, p->pl); + demuxer->filetype = fmt->name; + talloc_free(p); + return ok ? 0 : -1; +} + +const struct demuxer_desc demuxer_desc_playlist = { + .name = "playlist", + .desc = "Playlist file", + .open = open_file, +}; diff --git a/mpvcore/asxparser.c b/mpvcore/asxparser.c index 804e796f85..16646b9347 100644 --- a/mpvcore/asxparser.c +++ b/mpvcore/asxparser.c @@ -449,7 +449,6 @@ asx_parse_ref(ASX_Parser_t* parser, char** attribs) { static void asx_parse_entryref(ASX_Parser_t* parser,char* buffer,char** _attribs) { char *href; - stream_t* stream; if(parser->deep > 0) return; @@ -459,16 +458,8 @@ static void asx_parse_entryref(ASX_Parser_t* parser,char* buffer,char** _attribs asx_warning_attrib_required(parser,"ENTRYREF" ,"HREF" ); return; } - stream=stream_open(href, NULL); - if(!stream) { - mp_msg(MSGT_PLAYTREE,MSGL_WARN,"Can't open playlist %s\n",href); - free(href); - return; - } - - mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Not recursively loading playlist %s\n",href); - - free_stream(stream); + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Recursive playlist %s\n", href); + playlist_add_file(parser->pl, href); free(href); //mp_msg(MSGT_PLAYTREE,MSGL_INFO,"Need to implement entryref\n"); } diff --git a/mpvcore/command.c b/mpvcore/command.c index a9a96e6ebb..db4b2a7a9c 100644 --- a/mpvcore/command.c +++ b/mpvcore/command.c @@ -2319,7 +2319,7 @@ void run_command(MPContext *mpctx, mp_cmd_t *cmd) case MP_CMD_LOADLIST: { char *filename = cmd->args[0].v.s; bool append = cmd->args[1].v.i; - struct playlist *pl = playlist_parse_file(filename); + struct playlist *pl = playlist_parse_file(filename, opts); if (pl) { if (!append) playlist_clear(mpctx->playlist); diff --git a/mpvcore/mplayer.c b/mpvcore/mplayer.c index 2d5265d41b..43a7b5d9b0 100644 --- a/mpvcore/mplayer.c +++ b/mpvcore/mplayer.c @@ -4133,6 +4133,19 @@ static void stream_dump(struct MPContext *mpctx) } } +// Replace the current playlist entry with playlist contents. Moves the entries +// from the given playlist pl, so the entries don't actually need to be copied. +static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl) +{ + if (mpctx->demuxer->playlist->first) { + playlist_transfer_entries(mpctx->playlist, mpctx->demuxer->playlist); + if (mpctx->playlist->current) + playlist_remove(mpctx->playlist, mpctx->playlist->current); + } else { + MP_WARN(mpctx, "Empty playlist!\n"); + } +} + // Start playing the current playlist entry. // Handle initialization and deinitialization. static void play_current_file(struct MPContext *mpctx) @@ -4213,11 +4226,7 @@ static void play_current_file(struct MPContext *mpctx) mpctx->resolve_result = resolve_url(stream_filename, opts); if (mpctx->resolve_result) { if (mpctx->resolve_result->playlist) { - // Replace entry with playlist contents - playlist_transfer_entries(mpctx->playlist, - mpctx->resolve_result->playlist); - if (mpctx->playlist->current) - playlist_remove(mpctx->playlist, mpctx->playlist->current); + transfer_playlist(mpctx, mpctx->resolve_result->playlist); goto terminate_playback; } stream_filename = mpctx->resolve_result->url; @@ -4258,12 +4267,29 @@ goto_reopen_demuxer: ; mpctx->demuxer = demux_open(mpctx->stream, opts->demuxer_name, NULL, opts); mpctx->master_demuxer = mpctx->demuxer; - if (!mpctx->demuxer) { mp_tmsg(MSGT_CPLAYER, MSGL_ERR, "Failed to recognize file format.\n"); goto terminate_playback; } + MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, mpctx->demuxer); + + mpctx->initialized_flags |= INITIALIZED_DEMUXER; + + if (mpctx->demuxer->playlist) { + if (mpctx->demuxer->stream->safe_origin || opts->load_unsafe_playlists) { + transfer_playlist(mpctx, mpctx->demuxer->playlist); + } else { + MP_ERR(mpctx, "\nThis looks like a playlist, but playlist support " + "will not be used automatically.\nThe main problem with " + "playlist safety is that playlist entries can be arbitrary,\n" + "and an attacker could make mpv poke around in your local " + "filesystem or network.\nUse --playlist=file or the " + "--load-unsafe-playlists option to load them anyway.\n"); + } + goto terminate_playback; + } + if (mpctx->demuxer->matroska_data.ordered_chapters) build_ordered_chapter_timeline(mpctx); @@ -4275,11 +4301,6 @@ goto_reopen_demuxer: ; print_timeline(mpctx); - if (!mpctx->num_sources) { - MP_TARRAY_APPEND(NULL, mpctx->sources, mpctx->num_sources, - mpctx->demuxer); - } - if (mpctx->timeline) { // With Matroska, the "master" file usually dictates track layout etc. // On the contrary, the EDL and CUE demuxers are empty wrappers, as @@ -4298,8 +4319,6 @@ goto_reopen_demuxer: ; if (mpctx->timeline) timeline_set_part(mpctx, mpctx->timeline_part, true); - mpctx->initialized_flags |= INITIALIZED_DEMUXER; - add_subtitle_fonts_from_sources(mpctx); open_subtitles_from_options(mpctx); diff --git a/mpvcore/options.c b/mpvcore/options.c index 733b104a46..e508262378 100644 --- a/mpvcore/options.c +++ b/mpvcore/options.c @@ -669,6 +669,8 @@ const m_option_t mp_opts[] = { OPT_DOUBLE("chapter-seek-threshold", chapter_seek_threshold, 0), + OPT_FLAG("load-unsafe-playlists", load_unsafe_playlists, 0), + // a-v sync stuff: OPT_FLAG("correct-pts", correct_pts, 0), OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0, diff --git a/mpvcore/options.h b/mpvcore/options.h index 1ff50688ce..033d3c89c6 100644 --- a/mpvcore/options.h +++ b/mpvcore/options.h @@ -87,6 +87,7 @@ typedef struct MPOpts { int ordered_chapters; int chapter_merge_threshold; double chapter_seek_threshold; + int load_unsafe_playlists; int quiet; int load_config; int use_filedir_conf; diff --git a/mpvcore/parser-mpcmd.c b/mpvcore/parser-mpcmd.c index 55615d950e..e85085b808 100644 --- a/mpvcore/parser-mpcmd.c +++ b/mpvcore/parser-mpcmd.c @@ -114,6 +114,7 @@ static bool split_opt(struct parse_state *p) int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, int argc, char **argv) { + struct MPOpts *opts = config->optstruct; int ret = M_OPT_UNKNOWN; int mode = 0; struct playlist_entry *local_start = NULL; @@ -187,7 +188,7 @@ int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files, if (bstrcmp0(p.arg, "playlist") == 0) { // append the playlist to the local args char *param0 = bstrdup0(NULL, p.param); - struct playlist *pl = playlist_parse_file(param0); + struct playlist *pl = playlist_parse_file(param0, opts); talloc_free(param0); if (!pl) { mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL, diff --git a/mpvcore/playlist_parser.c b/mpvcore/playlist_parser.c index 4573e133ae..920a004c50 100644 --- a/mpvcore/playlist_parser.c +++ b/mpvcore/playlist_parser.c @@ -16,6 +16,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +/* + * Warning: this is outdated, crappy code. It is used only for --playlist. + * New or cleaned up code should be added to demux_playlist.c instead. + */ + #include "config.h" #include <stdlib.h> #include <stdio.h> @@ -406,43 +411,6 @@ static bool parse_ref_ini(play_tree_parser_t* p) { return true; } -static bool parse_m3u(play_tree_parser_t* p) { - char* line; - - mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying extended m3u playlist...\n"); - if (!(line = play_tree_parser_get_line(p))) - return NULL; - strstrip(line); - if(strcasecmp(line,"#EXTM3U")) - return NULL; - mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected extended m3u playlist format\n"); - play_tree_parser_stop_keeping(p); - - while((line = play_tree_parser_get_line(p)) != NULL) { - strstrip(line); - if(line[0] == '\0') - continue; - /* EXTM3U files contain such lines: - * #EXTINF:<seconds>, <title> - * followed by a line with the filename - * for now we have no place to put that - * so we just skip that extra-info ::atmos - */ - if(line[0] == '#') { -#if 0 /* code functional */ - if(strncasecmp(line,"#EXTINF:",8) == 0) { - mp_msg(MSGT_PLAYTREE,MSGL_INFO,"[M3U] Duration: %dsec Title: %s\n", - strtol(line+8,&line,10), line+2); - } -#endif - continue; - } - playlist_add_file(p->pl, line); - } - - return true; -} - static bool parse_smil(play_tree_parser_t* p) { int entrymode=0; char* line,source[512],*pos,*s_start,*s_end,*src_line; @@ -697,9 +665,11 @@ err_out: return success; } -struct playlist *playlist_parse_file(const char *file) +static struct playlist *do_parse(struct stream* stream, bool forced); + +struct playlist *playlist_parse_file(const char *file, struct MPOpts *opts) { - stream_t *stream = stream_open(file, NULL); + stream_t *stream = stream_open(file, opts); if(!stream) { mp_msg(MSGT_PLAYTREE,MSGL_ERR, "Error while opening playlist file %s: %s\n", @@ -710,7 +680,7 @@ struct playlist *playlist_parse_file(const char *file) mp_msg(MSGT_PLAYTREE, MSGL_V, "Parsing playlist file %s...\n", file); - struct playlist *ret = playlist_parse(stream); + struct playlist *ret = do_parse(stream, true); free_stream(stream); playlist_add_base_path(ret, mp_dirname(file)); @@ -723,7 +693,6 @@ typedef bool (*parser_fn)(play_tree_parser_t *); static const parser_fn pl_parsers[] = { parse_asx, parse_pls, - parse_m3u, parse_ref_ini, parse_smil, parse_nsc, @@ -740,7 +709,13 @@ static struct playlist *do_parse(struct stream* stream, bool forced) }; bool success = false; - if (play_tree_parser_get_line(&p) != NULL) { + struct demuxer *pl_demux = demux_open(stream, "playlist", NULL, stream->opts); + if (pl_demux && pl_demux->playlist) { + playlist_transfer_entries(p.pl, pl_demux->playlist); + success = true; + } + free_demuxer(pl_demux); + if (!success && play_tree_parser_get_line(&p) != NULL) { for (int n = 0; n < sizeof(pl_parsers) / sizeof(pl_parsers[0]); n++) { play_tree_parser_reset(&p); if (pl_parsers[n] == parse_textplain && !forced) @@ -765,13 +740,3 @@ static struct playlist *do_parse(struct stream* stream, bool forced) return p.pl; } - -struct playlist *playlist_parse(struct stream* stream) -{ - return do_parse(stream, true); -} - -struct playlist *playlist_probe_and_parse(struct stream* stream) -{ - return do_parse(stream, false); -} diff --git a/mpvcore/playlist_parser.h b/mpvcore/playlist_parser.h index 3ceb95c460..a541aa2cb4 100644 --- a/mpvcore/playlist_parser.h +++ b/mpvcore/playlist_parser.h @@ -21,14 +21,9 @@ #include <stdbool.h> -struct stream; +struct MPOpts; struct playlist; -// Parse the given stream as playlist. Append entries to pl. Return whether -// there was an error when parsing. -// deep = Parser depth. Some formats allow including other files, -struct playlist *playlist_parse(struct stream* stream); -struct playlist *playlist_probe_and_parse(struct stream* stream); -struct playlist *playlist_parse_file(const char *file); +struct playlist *playlist_parse_file(const char *file, struct MPOpts *opts); #endif diff --git a/mpvcore/timeline/tl_cue.c b/mpvcore/timeline/tl_cue.c index f7e13ec681..634a6de5f4 100644 --- a/mpvcore/timeline/tl_cue.c +++ b/mpvcore/timeline/tl_cue.c @@ -356,8 +356,6 @@ void build_cue_timeline(struct MPContext *mpctx) } } - add_source(mpctx, mpctx->demuxer); - for (size_t i = 0; i < file_count; i++) { if (!open_source(mpctx, files[i])) goto out; diff --git a/stream/stream.c b/stream/stream.c index b17b7fc19a..4650535890 100644 --- a/stream/stream.c +++ b/stream/stream.c @@ -742,6 +742,7 @@ static int stream_enable_cache(stream_t **stream, int64_t size, int64_t min, cache->url = talloc_strdup(cache, orig->url); cache->mime_type = talloc_strdup(cache, orig->mime_type); cache->lavf_type = talloc_strdup(cache, orig->lavf_type); + cache->safe_origin = orig->safe_origin; cache->opts = orig->opts; cache->start_pos = orig->start_pos; cache->end_pos = orig->end_pos; @@ -862,6 +863,8 @@ unsigned char *stream_read_line(stream_t *s, unsigned char *mem, int max, int len; const unsigned char *end; unsigned char *ptr = mem; + if (utf16 == -1) + utf16 = 0; if (max < 1) return NULL; max--; // reserve one for 0-termination @@ -891,6 +894,21 @@ unsigned char *stream_read_line(stream_t *s, unsigned char *mem, int max, return mem; } +static const char *bom[3] = {"\xEF\xBB\xBF", "\xFF\xFE", "\xFE\xFF"}; + +// Return utf16 argument for stream_read_line +int stream_skip_bom(struct stream *s) +{ + bstr data = stream_peek(s, 4); + for (int n = 0; n < 3; n++) { + if (bstr_startswith0(data, bom[n])) { + stream_skip(s, strlen(bom[n])); + return n; + } + } + return -1; // default to 8 bit codepages +} + // Read the rest of the stream into memory (current pos to EOF), and return it. // talloc_ctx: used as talloc parent for the returned allocation // max_size: must be set to >0. If the file is larger than that, it is treated diff --git a/stream/stream.h b/stream/stream.h index f19ab4203f..27043206c9 100644 --- a/stream/stream.h +++ b/stream/stream.h @@ -150,6 +150,7 @@ typedef struct stream { char *mime_type; // when HTTP streaming is used char *demuxer; // request demuxer to be used char *lavf_type; // name of expected demuxer type for lavf + bool safe_origin; // used for playlists that can be opened safely struct MPOpts *opts; FILE *capture_file; @@ -208,6 +209,7 @@ inline static uint64_t stream_read_qword(stream_t *s) unsigned char *stream_read_line(stream_t *s, unsigned char *mem, int max, int utf16); +int stream_skip_bom(struct stream *s); inline static int stream_eof(stream_t *s) { |