mirror of
https://github.com/mpv-player/mpv.git
synced 2026-10-17 06:50:45 +08:00
36abaa32d0
Fixes: #18507
285 lines
9.0 KiB
C
285 lines
9.0 KiB
C
/*
|
|
* This file is part of mpv.
|
|
*
|
|
* mpv 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.
|
|
*
|
|
* 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 Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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
|
|
|
|
const struct m_sub_options mp_network_conf = {
|
|
.opts = (const m_option_t[]) {
|
|
{"http-header-fields", OPT_STRINGLIST(http_header_fields)},
|
|
{"user-agent", OPT_STRING(useragent)},
|
|
{"referrer", OPT_STRING(referrer)},
|
|
{"cookies", OPT_BOOL(cookies_enabled)},
|
|
{"cookies-file", OPT_STRING(cookies_file), .flags = M_OPT_FILE},
|
|
{"tls-verify", OPT_BOOL(tls_verify)},
|
|
{"tls-ca-file", OPT_STRING(tls_ca_file), .flags = M_OPT_FILE},
|
|
{"tls-cert-file", OPT_STRING(tls_cert_file), .flags = M_OPT_FILE},
|
|
{"tls-key-file", OPT_STRING(tls_key_file), .flags = M_OPT_FILE},
|
|
{"network-timeout", OPT_DOUBLE(timeout), M_RANGE(0, DBL_MAX)},
|
|
{"http-proxy", OPT_STRING(http_proxy)},
|
|
{0}
|
|
},
|
|
.size = sizeof(struct mp_network_opts),
|
|
.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;
|
|
val = bstr_strip(val);
|
|
|
|
if (bstrcasecmp0(name, "Icy-MetaInt") == 0) {
|
|
long long mi = bstrtoll(val, NULL, 10);
|
|
if (mi > 0)
|
|
i->metaint = mi;
|
|
return;
|
|
}
|
|
// 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));
|
|
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;
|
|
}
|
|
|
|
static bstr header_to_utf8(void *tmp, struct stream *s, bstr text, const char *cp)
|
|
{
|
|
const char *charset = mp_charset_guess(tmp, s->log, text, cp, 0);
|
|
bstr conv = mp_iconv_to_utf8(s->log, text, charset, 0);
|
|
if (conv.start != text.start)
|
|
talloc_steal(tmp, conv.start);
|
|
return conv;
|
|
}
|
|
|
|
char *mp_parse_content_disposition(void *talloc_ctx, struct stream *s, bstr value)
|
|
{
|
|
void *tmp = talloc_new(NULL);
|
|
bstr plain = {0}, ext = {0};
|
|
|
|
// disposition-type *( ";" disposition-parm ). A quoted value
|
|
// may contain ";" or "=", so find its closing quote before splitting.
|
|
while (value.len) {
|
|
int eq = bstrchr(value, '=');
|
|
int semi = bstrchr(value, ';');
|
|
if (eq < 0 || (semi >= 0 && semi < eq)) {
|
|
if (semi < 0)
|
|
break;
|
|
value = bstr_cut(value, semi + 1);
|
|
continue;
|
|
}
|
|
bstr name = bstr_strip(bstr_splice(value, 0, eq));
|
|
value = bstr_lstrip(bstr_cut(value, eq + 1));
|
|
bstr val;
|
|
if (bstr_eatstart0(&value, "\"")) {
|
|
int end = bstrchr(value, '"');
|
|
val = end < 0 ? value : bstr_splice(value, 0, end);
|
|
value = bstr_cut(value, val.len);
|
|
semi = bstrchr(value, ';');
|
|
} else {
|
|
semi = bstrchr(value, ';');
|
|
val = bstr_strip(semi < 0 ? value : bstr_splice(value, 0, semi));
|
|
}
|
|
value = semi < 0 ? (bstr){0} : bstr_cut(value, semi + 1);
|
|
|
|
if (bstrcasecmp0(name, "filename*") == 0) {
|
|
ext = val;
|
|
} else if (bstrcasecmp0(name, "filename") == 0) {
|
|
plain = val;
|
|
}
|
|
}
|
|
|
|
bstr name = {0};
|
|
bstr charset, rest;
|
|
if (bstr_split_tok(ext, "'", &charset, &rest) &&
|
|
bstr_split_tok(rest, "'", &(bstr){0}, &rest))
|
|
{
|
|
bstr decoded = bstr0(mp_url_unescape(tmp, bstrto0(tmp, rest)));
|
|
name = header_to_utf8(tmp, s, decoded, bstrto0(tmp, charset));
|
|
}
|
|
if (!name.len && plain.len) {
|
|
struct demux_opts *opts = mp_get_config_group(tmp, s->global, &demux_conf);
|
|
name = header_to_utf8(tmp, s, plain, opts->meta_cp);
|
|
}
|
|
int sep = MPMAX(bstrrchr(name, '/'), bstrrchr(name, '\\'));
|
|
name = bstr_strip(bstr_cut(name, sep + 1));
|
|
char *res = name.len ? bstrto0(talloc_ctx, name) : NULL;
|
|
talloc_free(tmp);
|
|
return res;
|
|
}
|