diff --git a/doc/demuxers.texi b/doc/demuxers.texi index cc81c615dd..2324b3b469 100644 --- a/doc/demuxers.texi +++ b/doc/demuxers.texi @@ -564,6 +564,13 @@ prefer to use #EXT-X-START if it's in playlist instead of live_start_index. @item allowed_extensions ',' separated list of file extensions that hls is allowed to access. +@item extension_picky +This blocks disallowed extensions from probing +It also requires all available segments to have matching extensions to the format +except mpegts, which is always allowed. +It is recommended to set the whitelists correctly instead of depending on extensions +Enabled by default. + @item max_reload Maximum number of times a insufficient list is attempted to be reloaded. Default value is 1000. diff --git a/libavformat/hls.c b/libavformat/hls.c index 045741c3b4..93f6d1f102 100644 --- a/libavformat/hls.c +++ b/libavformat/hls.c @@ -223,6 +223,7 @@ typedef struct HLSContext { AVDictionary *avio_opts; AVDictionary *seg_format_opts; char *allowed_extensions; + int extension_picky; int max_reload; int http_persistent; int http_multiple; @@ -731,6 +732,40 @@ static int open_url(AVFormatContext *s, AVIOContext **pb, const char *url, return ret; } +static int test_segment(AVFormatContext *s, const AVInputFormat *in_fmt, struct playlist *pls, struct segment *seg) +{ + HLSContext *c = s->priv_data; + int matchA = 3; + int matchF = 0; + + if (!c->extension_picky) + return 0; + + if (strcmp(c->allowed_extensions, "ALL")) + matchA = av_match_ext (seg->url, c->allowed_extensions) + + 2*(ff_match_url_ext(seg->url, c->allowed_extensions) > 0); + + if (!matchA) { + av_log(s, AV_LOG_ERROR, "URL %s is not in allowed_extensions\n", seg->url); + return AVERROR_INVALIDDATA; + } + + if (in_fmt) { + if (in_fmt->extensions) { + matchF = av_match_ext( seg->url, in_fmt->extensions) + + 2*(ff_match_url_ext(seg->url, in_fmt->extensions) > 0); + } else if (!strcmp(in_fmt->name, "mpegts")) + matchF = 3; + + if (!(matchA & matchF)) { + av_log(s, AV_LOG_ERROR, "detected format extension %s mismatches allowed extensions in url %s\n", in_fmt->extensions ? in_fmt->extensions : "none", seg->url); + return AVERROR_INVALIDDATA; + } + } + + return 0; +} + static int parse_playlist(HLSContext *c, const char *url, struct playlist *pls, AVIOContext *in) { @@ -989,6 +1024,14 @@ static int parse_playlist(HLSContext *c, const char *url, goto fail; } + ret = test_segment(c->ctx, pls->ctx ? pls->ctx->iformat : NULL, pls, seg); + if (ret < 0) { + av_free(seg->url); + av_free(seg->key); + av_free(seg); + goto fail; + } + if (duration < 0.001 * AV_TIME_BASE) { av_log(c->ctx, AV_LOG_WARNING, "Cannot get correct #EXTINF value of segment %s," " set to default value to 1ms.\n", seg->url); @@ -2114,6 +2157,11 @@ static int hls_read_header(AVFormatContext *s) pls->ctx->interrupt_callback = s->interrupt_callback; url = av_strdup(pls->segments[0]->url); ret = av_probe_input_buffer(&pls->pb.pub, &in_fmt, url, NULL, 0, 0); + + for (int n = 0; n < pls->n_segments; n++) + if (ret >= 0) + ret = test_segment(s, in_fmt, pls, pls->segments[n]); + if (ret < 0) { /* Free the ctx - it isn't initialized properly at this point, * so avformat_close_input shouldn't be called. If @@ -2576,6 +2624,8 @@ static const AVOption hls_options[] = { OFFSET(allowed_extensions), AV_OPT_TYPE_STRING, {.str = "3gp,aac,avi,ac3,eac3,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,wav"}, INT_MIN, INT_MAX, FLAGS}, + {"extension_picky", "Be picky with all extensions matching", + OFFSET(extension_picky), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS}, {"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded", OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 3}, 0, INT_MAX, FLAGS}, {"m3u8_hold_counters", "The maximum number of times to load m3u8 when it refreshes without new segments",