Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DOCS/interface-changes/tls-verify-default.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
change `--tls-verify` default to `yes`
13 changes: 10 additions & 3 deletions DOCS/man/options.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5666,9 +5666,16 @@ Network
Certificate authority database file for use with TLS. (Silently fails with
older FFmpeg versions.)

``--tls-verify``
Verify peer certificates when using TLS (e.g. with ``https://...``).
(Silently fails with older FFmpeg versions.)
``--tls-verify=<yes|no>``
Verify peer certificates when using TLS (e.g. with ``https://...``)
(default: yes*). Disabling this option allows man-in-the-middle attacks
to silently substitute the content of an HTTPS stream and is only
recommended as a per-stream override when verification fails for a
known-good reason (e.g. an outdated CA bundle, a corporate proxy, a
development server with a self-signed certificate).

This is disabled by default, if mpv is built without libcurl and
libavformat is older than 63.0.100.

``--tls-cert-file``
A file containing a certificate to use in the handshake with the
Expand Down
173 changes: 173 additions & 0 deletions stream/network.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,19 @@
*/

#include <float.h>
#include <string.h>

#include <libavformat/version.h>

#include "common/common.h"
#include "common/tags.h"
#include "demux/demux.h"
#include "misc/charset_conv.h"
#include "mpv_talloc.h"
#include "network.h"
#include "options/m_config.h"
#include "options/m_option.h"
#include "stream.h"

#define OPT_BASE_STRUCT struct mp_network_opts

Expand All @@ -41,5 +51,168 @@ const struct m_sub_options mp_network_conf = {
.defaults = &(const struct mp_network_opts){
.useragent = "libmpv",
.timeout = 60,
#if HAVE_LIBCURL || LIBAVFORMAT_VERSION_MAJOR >= 63
.tls_verify = true,
#endif
},
};

struct mp_icy {
uint64_t metaint; // bytes between metadata blocks (0 = no ICY)
uint64_t data_read; // data bytes since last metadata block
enum {
ICY_DATA = 0,
ICY_LEN,
ICY_META,
} state;
size_t meta_pending; // bytes left in the current metadata block
size_t meta_pos; // bytes already accumulated in meta_buf
char meta_buf[255 * 16 + 1];
bstr headers; // accumulated "Icy-Name: value\n" lines
bstr packet; // last metadata payload
bool dirty; // new metadata to deliver
};

struct mp_icy *mp_icy_new(void *ta_parent)
{
return talloc_zero(ta_parent, struct mp_icy);
}

void mp_icy_reset(struct mp_icy *i)
{
i->metaint = 0;
i->data_read = 0;
i->state = ICY_DATA;
i->meta_pending = 0;
i->meta_pos = 0;
i->headers.len = 0;
i->packet.len = 0;
i->dirty = false;
}

void mp_icy_add_header(struct mp_icy *i, bstr line)
{
bstr name, val;
if (!bstr_split_tok(line, ": ", &name, &val))
return;
if (!bstr_case_startswith(name, bstr0("Icy-")))
return;

if (bstrcasecmp0(name, "Icy-MetaInt") == 0) {
long long mi = bstrtoll(val, NULL, 10);
if (mi > 0)
i->metaint = mi;
}
// This may look a bit weird, that we join headers again, but it's done
// to share common parse function with lavf format later.
bstr_xappend_asprintf(i, &i->headers, "%.*s: %.*s\n", BSTR_P(name), BSTR_P(val));
printf("ICY header: %.*s: %.*s\n", BSTR_P(name), BSTR_P(val));
i->dirty = true;
}

bool mp_icy_active(const struct mp_icy *i)
{
return i->metaint > 0;
}

void mp_icy_process(struct mp_icy *i, const char *buf, size_t len,
mp_icy_write_fn write_cb, void *ctx)
{
if (!mp_icy_active(i)) {
if (len)
write_cb(ctx, buf, len);
return;
}
size_t pos = 0;
while (pos < len) {
switch (i->state) {
case ICY_DATA: {
size_t budget = i->metaint - i->data_read;
size_t take = MPMIN(len - pos, budget);
write_cb(ctx, buf + pos, take);
pos += take;
i->data_read += take;
if (i->data_read == i->metaint)
i->state = ICY_LEN;
break;
}
case ICY_LEN: {
uint8_t n = (uint8_t)buf[pos++];
if (n == 0) {
i->state = ICY_DATA;
i->data_read = 0;
} else {
i->meta_pending = (size_t)n * 16;
i->meta_pos = 0;
i->state = ICY_META;
}
break;
}
case ICY_META: {
size_t take = MPMIN(len - pos, i->meta_pending);
memcpy(i->meta_buf + i->meta_pos, buf + pos, take);
i->meta_pos += take;
i->meta_pending -= take;
pos += take;
if (i->meta_pending == 0) {
i->meta_buf[i->meta_pos] = '\0';
i->packet.len = 0;
bstr_xappend_asprintf(i, &i->packet, "%s\n", i->meta_buf);
i->dirty = true;
i->state = ICY_DATA;
i->data_read = 0;
}
break;
}
}
}
}

struct mp_tags *mp_icy_get_metadata(struct mp_icy *i, struct stream *s)
{
if (!i->dirty)
return NULL;
i->dirty = false;
return mp_parse_icy_metadata(s, i->headers, i->packet);
}

struct mp_tags *mp_parse_icy_metadata(struct stream *s, bstr headers, bstr packet)
{
if (!headers.len && !packet.len)
return NULL;

struct mp_tags *res = talloc_zero(NULL, struct mp_tags);

while (headers.len) {
bstr line = bstr_strip_linebreaks(bstr_getline(headers, &headers));
bstr name, val;
if (bstr_split_tok(line, ": ", &name, &val))
mp_tags_set_bstr(res, name, val);
}

bstr head = bstr0("StreamTitle='");
int i = bstr_find(packet, head);
if (i >= 0) {
packet = bstr_cut(packet, i + head.len);
int end = bstr_find(packet, bstr0("\';"));
if (end >= 0)
packet = bstr_splice(packet, 0, end);

bool allocated = false;
struct demux_opts *opts = mp_get_config_group(NULL, s->global, &demux_conf);
const char *charset = mp_charset_guess(s, s->log, packet, opts->meta_cp, 0);
if (charset && !mp_charset_is_utf8(charset)) {
bstr conv = mp_iconv_to_utf8(s->log, packet, charset, 0);
if (conv.start && conv.start != packet.start) {
allocated = true;
packet = conv;
}
}
mp_tags_set_bstr(res, bstr0("icy-title"), packet);
talloc_free(opts);
if (allocated)
talloc_free(packet.start);
}

return res;
}
40 changes: 40 additions & 0 deletions stream/network.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
#include <stdbool.h>

struct m_sub_options;
struct mp_tags;
struct stream;
typedef struct bstr bstr;

struct mp_network_opts {
bool cookies_enabled;
Expand All @@ -36,3 +39,40 @@ struct mp_network_opts {
};

extern const struct m_sub_options mp_network_conf;

// Build mp_tags from accumulated ICY metadata. `headers` is a buffer of
// "Icy-*: value\n" lines collected from the response. `packet` is the most
// recent in-band metadata payload. Returns NULL when both are empty.
// Returned value has to be freed by the caller.
struct mp_tags *mp_parse_icy_metadata(struct stream *s, bstr headers,
bstr packet);

// Opaque state for receiving ICY (Shoutcast/Icecast) metadata over an HTTP
// stream. This wrapper is not thread safe.
struct mp_icy;

// Allocate a new ICY context as a talloc child of `ta_parent`.
struct mp_icy *mp_icy_new(void *ta_parent);

// Reset all ICY state. Use between responses (e.g. across redirects).
void mp_icy_reset(struct mp_icy *icy);

// Feed a single response header line (without trailing CRLF). Lines that
// don't start with "Icy-" are silently ignored.
void mp_icy_add_header(struct mp_icy *icy, bstr line);

// True if Icy-MetaInt was seen and the body must be filtered.
bool mp_icy_active(const struct mp_icy *icy);

// Body callback used by mp_icy_process(). Invoked once per contiguous
// stretch of non-metadata bytes.
typedef void (*mp_icy_write_fn)(void *ctx, const char *data, size_t len);

// Process a body chunk. When ICY is active, metadata bytes are stripped and
// stashed internally, remaining data is delivered via `write_cb`. Otherwise
// the chunk is forwarded as a single call to `write_cb`.
void mp_icy_process(struct mp_icy *icy, const char *buf, size_t len,
mp_icy_write_fn write_cb, void *ctx);

// Returns talloc-allocated mp_tags.
struct mp_tags *mp_icy_get_metadata(struct mp_icy *icy, struct stream *s);
Loading
Loading