2015-03-20 18:40:42 -04:00
|
|
|
|
/*
|
2016-11-05 08:43:35 -04:00
|
|
|
|
* Copyright (C) 2016 Espen Jürgensen <espenjurgensen@gmail.com>
|
2015-03-20 18:40:42 -04:00
|
|
|
|
*
|
|
|
|
|
* This program 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.
|
|
|
|
|
*
|
|
|
|
|
* 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 General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
|
# include <config.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <unistd.h>
|
2015-06-05 17:55:57 -04:00
|
|
|
|
#include <uniconv.h>
|
2020-11-23 10:27:03 -05:00
|
|
|
|
#include <unistr.h>
|
2015-03-20 18:40:42 -04:00
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <ctype.h>
|
|
|
|
|
#include <limits.h>
|
|
|
|
|
#include <sys/param.h>
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/stat.h>
|
|
|
|
|
#include <errno.h>
|
|
|
|
|
|
|
|
|
|
#include <libavutil/opt.h>
|
|
|
|
|
|
|
|
|
|
#include <event2/event.h>
|
|
|
|
|
|
2016-11-05 08:43:35 -04:00
|
|
|
|
#include <curl/curl.h>
|
|
|
|
|
|
2015-03-20 18:40:42 -04:00
|
|
|
|
#include "http.h"
|
|
|
|
|
#include "logger.h"
|
|
|
|
|
#include "misc.h"
|
2018-08-12 13:49:23 -04:00
|
|
|
|
#include "conffile.h"
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
2017-11-27 03:48:17 -05:00
|
|
|
|
/* Formats we can read so far */
|
|
|
|
|
#define PLAYLIST_UNK 0
|
|
|
|
|
#define PLAYLIST_PLS 1
|
|
|
|
|
#define PLAYLIST_M3U 2
|
|
|
|
|
|
2015-03-20 18:40:42 -04:00
|
|
|
|
/* ======================= libevent HTTP client =============================*/
|
|
|
|
|
|
|
|
|
|
// Number of seconds the client will wait for a response before aborting
|
2015-03-30 18:08:29 -04:00
|
|
|
|
#define HTTP_CLIENT_TIMEOUT 8
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
2019-02-09 11:04:25 -05:00
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
http_client_session_init(struct http_client_session *session)
|
|
|
|
|
{
|
|
|
|
|
session->curl = curl_easy_init();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
http_client_session_deinit(struct http_client_session *session)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_cleanup(session->curl);
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-09 02:35:20 -05:00
|
|
|
|
static void
|
|
|
|
|
curl_headers_save(struct keyval *kv, CURL *curl)
|
|
|
|
|
{
|
|
|
|
|
char *content_type;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
if (!kv || !curl)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
ret = curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
|
|
|
|
|
if (ret == CURLE_OK && content_type)
|
|
|
|
|
{
|
|
|
|
|
keyval_add(kv, "Content-Type", content_type);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-05 08:43:35 -04:00
|
|
|
|
static size_t
|
|
|
|
|
curl_request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
|
|
|
|
|
{
|
|
|
|
|
size_t realsize;
|
|
|
|
|
struct http_client_ctx *ctx;
|
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
|
|
realsize = size * nmemb;
|
|
|
|
|
ctx = (struct http_client_ctx *)userdata;
|
|
|
|
|
|
|
|
|
|
if (!ctx->input_body)
|
|
|
|
|
return realsize;
|
|
|
|
|
|
|
|
|
|
ret = evbuffer_add(ctx->input_body, ptr, realsize);
|
|
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Error adding reply from %s to input buffer\n", ctx->url);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return realsize;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 08:15:59 -04:00
|
|
|
|
int
|
2019-02-09 11:04:25 -05:00
|
|
|
|
http_client_request(struct http_client_ctx *ctx, struct http_client_session *session)
|
2016-11-05 08:43:35 -04:00
|
|
|
|
{
|
|
|
|
|
CURL *curl;
|
|
|
|
|
CURLcode res;
|
|
|
|
|
struct curl_slist *headers;
|
|
|
|
|
struct onekeyval *okv;
|
2018-08-12 13:49:23 -04:00
|
|
|
|
const char *user_agent;
|
2021-12-18 11:56:20 -05:00
|
|
|
|
long verifypeer;
|
2016-11-05 08:43:35 -04:00
|
|
|
|
char header[1024];
|
2019-02-09 02:35:20 -05:00
|
|
|
|
long response_code;
|
2016-11-05 08:43:35 -04:00
|
|
|
|
|
2019-02-09 11:04:25 -05:00
|
|
|
|
if (session)
|
|
|
|
|
{
|
|
|
|
|
curl = session->curl;
|
|
|
|
|
curl_easy_reset(curl);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
curl = curl_easy_init();
|
|
|
|
|
}
|
2016-11-05 08:43:35 -04:00
|
|
|
|
if (!curl)
|
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Error: Could not get curl handle\n");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2018-08-12 13:49:23 -04:00
|
|
|
|
user_agent = cfg_getstr(cfg_getsec(cfg, "general"), "user_agent");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
|
2016-11-05 08:43:35 -04:00
|
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, ctx->url);
|
|
|
|
|
|
2021-12-18 11:56:20 -05:00
|
|
|
|
verifypeer = cfg_getbool(cfg_getsec(cfg, "general"), "ssl_verifypeer");
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, verifypeer);
|
|
|
|
|
|
2020-07-16 18:02:11 -04:00
|
|
|
|
headers = NULL;
|
2016-11-05 08:43:35 -04:00
|
|
|
|
if (ctx->output_headers)
|
|
|
|
|
{
|
|
|
|
|
for (okv = ctx->output_headers->head; okv; okv = okv->next)
|
|
|
|
|
{
|
|
|
|
|
snprintf(header, sizeof(header), "%s: %s", okv->name, okv->value);
|
|
|
|
|
headers = curl_slist_append(headers, header);
|
|
|
|
|
}
|
2020-07-16 18:02:11 -04:00
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
2016-11-05 08:43:35 -04:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 08:15:59 -04:00
|
|
|
|
if (ctx->headers_only)
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_NOBODY, 1L); // Makes curl make a HEAD request
|
|
|
|
|
else if (ctx->output_body)
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->output_body); // POST request
|
2016-11-05 08:43:35 -04:00
|
|
|
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_CLIENT_TIMEOUT);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_request_cb);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
|
|
|
|
|
|
2020-02-11 08:15:23 -05:00
|
|
|
|
// Artwork and playlist requests might require redirects
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5);
|
|
|
|
|
|
2016-11-05 08:43:35 -04:00
|
|
|
|
/* Make request */
|
|
|
|
|
DPRINTF(E_INFO, L_HTTP, "Making request for %s\n", ctx->url);
|
|
|
|
|
|
|
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
|
if (res != CURLE_OK)
|
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
|
2020-02-28 16:57:10 -05:00
|
|
|
|
curl_slist_free_all(headers);
|
2019-02-09 11:04:25 -05:00
|
|
|
|
if (!session)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
|
}
|
2016-11-05 08:43:35 -04:00
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2019-02-09 02:35:20 -05:00
|
|
|
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
|
|
|
|
|
ctx->response_code = (int) response_code;
|
|
|
|
|
curl_headers_save(ctx->input_headers, curl);
|
|
|
|
|
|
2020-02-28 16:57:10 -05:00
|
|
|
|
curl_slist_free_all(headers);
|
2019-02-09 11:04:25 -05:00
|
|
|
|
if (!session)
|
|
|
|
|
{
|
|
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
|
}
|
2016-11-05 08:43:35 -04:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *
|
|
|
|
|
http_form_urlencode(struct keyval *kv)
|
|
|
|
|
{
|
|
|
|
|
struct evbuffer *evbuf;
|
|
|
|
|
struct onekeyval *okv;
|
|
|
|
|
char *body;
|
|
|
|
|
char *k;
|
|
|
|
|
char *v;
|
|
|
|
|
|
|
|
|
|
evbuf = evbuffer_new();
|
|
|
|
|
|
|
|
|
|
for (okv = kv->head; okv; okv = okv->next)
|
|
|
|
|
{
|
|
|
|
|
k = evhttp_encode_uri(okv->name);
|
|
|
|
|
if (!k)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
v = evhttp_encode_uri(okv->value);
|
|
|
|
|
if (!v)
|
|
|
|
|
{
|
|
|
|
|
free(k);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
evbuffer_add_printf(evbuf, "%s=%s", k, v);
|
|
|
|
|
if (okv->next)
|
|
|
|
|
evbuffer_add_printf(evbuf, "&");
|
|
|
|
|
|
|
|
|
|
free(k);
|
|
|
|
|
free(v);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
evbuffer_add(evbuf, "\n", 1);
|
|
|
|
|
|
|
|
|
|
body = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_ANY);
|
|
|
|
|
|
|
|
|
|
evbuffer_free(evbuf);
|
|
|
|
|
|
|
|
|
|
DPRINTF(E_DBG, L_HTTP, "Parameters in request are: %s\n", body);
|
|
|
|
|
|
|
|
|
|
return body;
|
|
|
|
|
}
|
|
|
|
|
|
2015-03-20 18:40:42 -04:00
|
|
|
|
int
|
|
|
|
|
http_stream_setup(char **stream, const char *url)
|
|
|
|
|
{
|
2022-12-19 17:53:37 -05:00
|
|
|
|
CURLU *url_handle;
|
|
|
|
|
CURLUcode rc;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
struct http_client_ctx ctx;
|
|
|
|
|
struct evbuffer *evbuf;
|
2022-12-19 17:53:37 -05:00
|
|
|
|
char *path;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
const char *ext;
|
|
|
|
|
char *line;
|
2018-01-02 15:20:28 -05:00
|
|
|
|
char *pos;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
int ret;
|
|
|
|
|
int n;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
int pl_format;
|
|
|
|
|
bool in_playlist;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
|
|
|
|
*stream = NULL;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2022-12-19 17:53:37 -05:00
|
|
|
|
CHECK_NULL(L_HTTP, url_handle = curl_url());
|
|
|
|
|
|
|
|
|
|
rc = curl_url_set(url_handle, CURLUPART_URL, url, 0);
|
|
|
|
|
if (rc != 0)
|
2017-11-27 03:48:17 -05:00
|
|
|
|
{
|
2018-01-02 15:20:28 -05:00
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Couldn't parse internet playlist: '%s'\n", url);
|
2022-12-19 17:53:37 -05:00
|
|
|
|
curl_url_cleanup(url_handle);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rc = curl_url_get(url_handle, CURLUPART_PATH, &path, 0);
|
|
|
|
|
if (rc != 0)
|
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Couldn't find internet playlist path: '%s'\n", url);
|
|
|
|
|
curl_url_cleanup(url_handle);
|
2018-01-02 15:20:28 -05:00
|
|
|
|
return -1;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-19 17:53:37 -05:00
|
|
|
|
// path does not include query or fragment, so should work with any url's
|
2018-01-02 15:20:28 -05:00
|
|
|
|
// e.g. http://yp.shoutcast.com/sbin/tunein-station.pls?id=99179772#Air Jazz
|
|
|
|
|
pl_format = PLAYLIST_UNK;
|
2022-12-19 17:53:37 -05:00
|
|
|
|
if (path && (ext = strrchr(path, '.')))
|
2017-11-27 03:48:17 -05:00
|
|
|
|
{
|
2018-01-02 15:20:28 -05:00
|
|
|
|
if (strcasecmp(ext, ".m3u") == 0)
|
2017-11-27 11:53:59 -05:00
|
|
|
|
pl_format = PLAYLIST_M3U;
|
2018-01-02 15:20:28 -05:00
|
|
|
|
else if (strcasecmp(ext, ".pls") == 0)
|
2017-11-27 11:53:59 -05:00
|
|
|
|
pl_format = PLAYLIST_PLS;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
}
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
2022-12-19 17:53:37 -05:00
|
|
|
|
curl_free(path);
|
|
|
|
|
curl_url_cleanup(url_handle);
|
|
|
|
|
|
2018-01-02 15:20:28 -05:00
|
|
|
|
|
2017-11-27 03:48:17 -05:00
|
|
|
|
if (pl_format==PLAYLIST_UNK)
|
2015-03-20 18:40:42 -04:00
|
|
|
|
{
|
|
|
|
|
*stream = strdup(url);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-27 03:48:17 -05:00
|
|
|
|
// It was a m3u or pls playlist, so now retrieve it
|
2015-03-20 18:40:42 -04:00
|
|
|
|
memset(&ctx, 0, sizeof(struct http_client_ctx));
|
|
|
|
|
|
|
|
|
|
evbuf = evbuffer_new();
|
|
|
|
|
if (!evbuf)
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
ctx.url = url;
|
2016-11-05 08:43:35 -04:00
|
|
|
|
ctx.input_body = evbuf;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
2019-02-09 11:04:25 -05:00
|
|
|
|
ret = http_client_request(&ctx, NULL);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
if (ret < 0)
|
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Couldn't fetch internet playlist: %s\n", url);
|
|
|
|
|
|
|
|
|
|
evbuffer_free(evbuf);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2015-06-18 17:03:11 -04:00
|
|
|
|
// Pad with CRLF because evbuffer_readln() might not read the last line otherwise
|
2016-11-05 08:43:35 -04:00
|
|
|
|
evbuffer_add(ctx.input_body, "\r\n", 2);
|
2015-06-18 17:03:11 -04:00
|
|
|
|
|
2015-03-20 18:40:42 -04:00
|
|
|
|
/* Read the playlist until the first stream link is found, but give up if
|
|
|
|
|
* nothing is found in the first 10 lines
|
|
|
|
|
*/
|
2017-11-27 03:48:17 -05:00
|
|
|
|
in_playlist = false;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
n = 0;
|
2016-11-05 08:43:35 -04:00
|
|
|
|
while ((line = evbuffer_readln(ctx.input_body, NULL, EVBUFFER_EOL_ANY)) && (n < 10))
|
2015-03-20 18:40:42 -04:00
|
|
|
|
{
|
2017-11-27 03:48:17 -05:00
|
|
|
|
// Skip comments and blank lines without counting for the limit
|
|
|
|
|
if (pl_format == PLAYLIST_M3U && (line[0] == '#' || line[0] == '\0'))
|
2017-11-27 11:53:59 -05:00
|
|
|
|
goto line_done;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2015-03-20 18:40:42 -04:00
|
|
|
|
n++;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
|
|
|
|
if (pl_format == PLAYLIST_PLS && !in_playlist)
|
2017-11-27 11:53:59 -05:00
|
|
|
|
{
|
|
|
|
|
if (strncasecmp(line, "[playlist]", strlen("[playlist]")) == 0)
|
|
|
|
|
{
|
|
|
|
|
in_playlist = true;
|
|
|
|
|
n = 0;
|
|
|
|
|
}
|
|
|
|
|
goto line_done;
|
|
|
|
|
}
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
|
|
|
|
if (pl_format == PLAYLIST_PLS)
|
2017-11-27 11:53:59 -05:00
|
|
|
|
{
|
|
|
|
|
pos = line;
|
|
|
|
|
while (*pos == ' ')
|
|
|
|
|
++pos;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
|
|
|
|
// We are only interested in `FileN=http://foo/bar.mp3` entries
|
2017-11-27 11:53:59 -05:00
|
|
|
|
if (strncasecmp(pos, "file", strlen("file")) != 0)
|
|
|
|
|
goto line_done;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2017-11-27 11:53:59 -05:00
|
|
|
|
while (*pos != '=' && *pos != '\0')
|
|
|
|
|
++pos;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2017-11-27 11:53:59 -05:00
|
|
|
|
if (*pos == '\0')
|
|
|
|
|
goto line_done;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2017-11-27 11:53:59 -05:00
|
|
|
|
++pos;
|
|
|
|
|
while (*pos == ' ')
|
|
|
|
|
++pos;
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2017-11-27 11:53:59 -05:00
|
|
|
|
// allocate the value part and proceed as with m3u
|
|
|
|
|
pos = strdup(pos);
|
|
|
|
|
free(line);
|
2018-01-02 15:20:28 -05:00
|
|
|
|
line = pos;
|
2017-11-27 11:53:59 -05:00
|
|
|
|
}
|
2017-11-27 03:48:17 -05:00
|
|
|
|
|
2021-05-19 16:53:25 -04:00
|
|
|
|
if (net_is_http_or_https(line))
|
2015-03-20 18:40:42 -04:00
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_DBG, L_HTTP, "Found internet playlist stream (line %d): %s\n", n, line);
|
|
|
|
|
|
|
|
|
|
n = -1;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2017-11-27 03:48:17 -05:00
|
|
|
|
line_done:
|
2015-03-20 18:40:42 -04:00
|
|
|
|
free(line);
|
|
|
|
|
}
|
|
|
|
|
|
2016-11-05 08:43:35 -04:00
|
|
|
|
evbuffer_free(ctx.input_body);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
|
|
|
|
if (n != -1)
|
|
|
|
|
{
|
|
|
|
|
DPRINTF(E_LOG, L_HTTP, "Couldn't find stream in internet playlist: %s\n", url);
|
|
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*stream = line;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* ======================= ICY metadata handling =============================*/
|
|
|
|
|
|
2015-03-28 19:29:06 -04:00
|
|
|
|
|
2015-03-20 18:40:42 -04:00
|
|
|
|
static int
|
|
|
|
|
metadata_packet_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *buffer;
|
2020-11-23 10:27:03 -05:00
|
|
|
|
uint8_t *utf;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
char *icy_token;
|
2018-12-31 02:26:40 -05:00
|
|
|
|
char *save_pr;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
char *ptr;
|
|
|
|
|
char *end;
|
|
|
|
|
|
|
|
|
|
av_opt_get(fmtctx, "icy_metadata_packet", AV_OPT_SEARCH_CHILDREN, &buffer);
|
|
|
|
|
if (!buffer)
|
|
|
|
|
return -1;
|
|
|
|
|
|
2020-11-23 10:27:03 -05:00
|
|
|
|
/* Some servers send ISO-8859-1 instead of UTF-8 */
|
|
|
|
|
if (u8_check(buffer, strlen((char *)buffer)))
|
|
|
|
|
{
|
|
|
|
|
utf = u8_strconv_from_encoding((char *)buffer, "ISO−8859−1", iconveh_question_mark);
|
|
|
|
|
av_free(buffer);
|
|
|
|
|
if (utf == NULL)
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
utf = buffer;
|
|
|
|
|
|
|
|
|
|
icy_token = strtok_r((char *)utf, ";", &save_pr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
while (icy_token != NULL)
|
|
|
|
|
{
|
|
|
|
|
ptr = strchr(icy_token, '=');
|
|
|
|
|
if (!ptr || (ptr[1] == '\0'))
|
|
|
|
|
{
|
2018-12-31 02:26:40 -05:00
|
|
|
|
icy_token = strtok_r(NULL, ";", &save_pr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ptr++;
|
|
|
|
|
if (ptr[0] == '\'')
|
|
|
|
|
ptr++;
|
|
|
|
|
|
|
|
|
|
end = strrchr(ptr, '\'');
|
|
|
|
|
if (end)
|
|
|
|
|
*end = '\0';
|
|
|
|
|
|
|
|
|
|
if ((strncmp(icy_token, "StreamTitle", strlen("StreamTitle")) == 0) && !metadata->title)
|
|
|
|
|
{
|
|
|
|
|
metadata->title = ptr;
|
|
|
|
|
|
|
|
|
|
/* Dash separates artist from title, if no dash assume all is title */
|
|
|
|
|
ptr = strstr(ptr, " - ");
|
|
|
|
|
if (ptr)
|
|
|
|
|
{
|
|
|
|
|
*ptr = '\0';
|
2015-11-28 18:55:30 -05:00
|
|
|
|
metadata->artist = strdup(metadata->title);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
*ptr = ' ';
|
|
|
|
|
|
2015-11-28 18:55:30 -05:00
|
|
|
|
metadata->title = strdup(ptr + 3);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
}
|
2020-02-19 17:03:50 -05:00
|
|
|
|
else if (strlen(metadata->title) == 0)
|
|
|
|
|
metadata->title = NULL;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
else
|
|
|
|
|
metadata->title = strdup(metadata->title);
|
|
|
|
|
}
|
2020-05-13 17:20:14 -04:00
|
|
|
|
else if ((strncmp(icy_token, "StreamUrl", strlen("StreamUrl")) == 0) && !metadata->url && strlen(ptr) > 0)
|
2015-03-20 18:40:42 -04:00
|
|
|
|
{
|
2020-05-13 17:20:14 -04:00
|
|
|
|
metadata->url = strdup(ptr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (end)
|
|
|
|
|
*end = '\'';
|
|
|
|
|
|
2018-12-31 02:26:40 -05:00
|
|
|
|
icy_token = strtok_r(NULL, ";", &save_pr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
}
|
2020-11-23 10:27:03 -05:00
|
|
|
|
av_free(utf);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
|
|
|
|
if (metadata->title)
|
|
|
|
|
metadata->hash = djb_hash(metadata->title, strlen(metadata->title));
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
metadata_header_get(struct http_icy_metadata *metadata, AVFormatContext *fmtctx)
|
|
|
|
|
{
|
|
|
|
|
uint8_t *buffer;
|
2015-06-05 17:55:57 -04:00
|
|
|
|
uint8_t *utf;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
char *icy_token;
|
2018-12-31 02:26:40 -05:00
|
|
|
|
char *save_pr;
|
2015-03-20 18:40:42 -04:00
|
|
|
|
char *ptr;
|
|
|
|
|
|
|
|
|
|
av_opt_get(fmtctx, "icy_metadata_headers", AV_OPT_SEARCH_CHILDREN, &buffer);
|
|
|
|
|
if (!buffer)
|
|
|
|
|
return -1;
|
|
|
|
|
|
2015-06-05 17:55:57 -04:00
|
|
|
|
/* Headers are ascii or iso-8859-1 according to:
|
|
|
|
|
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2
|
|
|
|
|
*/
|
|
|
|
|
utf = u8_strconv_from_encoding((char *)buffer, "ISO−8859−1", iconveh_question_mark);
|
|
|
|
|
av_free(buffer);
|
|
|
|
|
if (!utf)
|
|
|
|
|
return -1;
|
|
|
|
|
|
2018-12-31 02:26:40 -05:00
|
|
|
|
icy_token = strtok_r((char *)utf, "\r\n", &save_pr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
while (icy_token != NULL)
|
|
|
|
|
{
|
|
|
|
|
ptr = strchr(icy_token, ':');
|
|
|
|
|
if (!ptr || (ptr[1] == '\0'))
|
|
|
|
|
{
|
2018-12-31 02:26:40 -05:00
|
|
|
|
icy_token = strtok_r(NULL, "\r\n", &save_pr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ptr++;
|
|
|
|
|
if (ptr[0] == ' ')
|
|
|
|
|
ptr++;
|
|
|
|
|
|
2020-05-17 17:28:05 -04:00
|
|
|
|
if ((strncmp(icy_token, "icy-name", strlen("icy-name")) == 0) && ptr[0] != '\0' && !metadata->name)
|
2015-06-05 17:55:57 -04:00
|
|
|
|
metadata->name = strdup(ptr);
|
2020-05-17 17:28:05 -04:00
|
|
|
|
else if ((strncmp(icy_token, "icy-description", strlen("icy-description")) == 0) && ptr[0] != '\0' && !metadata->description)
|
2015-06-05 17:55:57 -04:00
|
|
|
|
metadata->description = strdup(ptr);
|
2020-05-17 17:28:05 -04:00
|
|
|
|
else if ((strncmp(icy_token, "icy-genre", strlen("icy-genre")) == 0) && ptr[0] != '\0' && !metadata->genre)
|
2015-06-05 17:55:57 -04:00
|
|
|
|
metadata->genre = strdup(ptr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
2018-12-31 02:26:40 -05:00
|
|
|
|
icy_token = strtok_r(NULL, "\r\n", &save_pr);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
}
|
2015-06-05 17:55:57 -04:00
|
|
|
|
free(utf);
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct http_icy_metadata *
|
|
|
|
|
http_icy_metadata_get(AVFormatContext *fmtctx, int packet_only)
|
|
|
|
|
{
|
|
|
|
|
struct http_icy_metadata *metadata;
|
|
|
|
|
int got_packet;
|
|
|
|
|
int got_header;
|
|
|
|
|
|
2020-05-13 17:20:14 -04:00
|
|
|
|
CHECK_NULL(L_HTTP, metadata = calloc(1, sizeof(struct http_icy_metadata)));
|
2015-03-20 18:40:42 -04:00
|
|
|
|
|
|
|
|
|
got_packet = (metadata_packet_get(metadata, fmtctx) == 0);
|
|
|
|
|
got_header = (!packet_only) && (metadata_header_get(metadata, fmtctx) == 0);
|
|
|
|
|
|
|
|
|
|
if (!got_packet && !got_header)
|
|
|
|
|
{
|
|
|
|
|
free(metadata);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* DPRINTF(E_DBG, L_HTTP, "Found ICY: N %s, D %s, G %s, T %s, A %s, U %s, I %" PRIu32 "\n",
|
|
|
|
|
metadata->name,
|
|
|
|
|
metadata->description,
|
|
|
|
|
metadata->genre,
|
|
|
|
|
metadata->title,
|
|
|
|
|
metadata->artist,
|
2020-05-13 17:20:14 -04:00
|
|
|
|
metadata->url,
|
2015-03-20 18:40:42 -04:00
|
|
|
|
metadata->hash
|
|
|
|
|
);
|
|
|
|
|
*/
|
|
|
|
|
return metadata;
|
|
|
|
|
}
|
2015-03-28 19:29:06 -04:00
|
|
|
|
|
|
|
|
|
void
|
2015-03-31 17:05:24 -04:00
|
|
|
|
http_icy_metadata_free(struct http_icy_metadata *metadata, int content_only)
|
2015-03-28 19:29:06 -04:00
|
|
|
|
{
|
2020-05-13 17:20:14 -04:00
|
|
|
|
if (!metadata)
|
|
|
|
|
return;
|
2015-03-28 19:29:06 -04:00
|
|
|
|
|
2020-05-13 17:20:14 -04:00
|
|
|
|
free(metadata->name);
|
|
|
|
|
free(metadata->description);
|
|
|
|
|
free(metadata->genre);
|
|
|
|
|
free(metadata->title);
|
|
|
|
|
free(metadata->artist);
|
|
|
|
|
free(metadata->url);
|
|
|
|
|
free(metadata);
|
2015-03-28 19:29:06 -04:00
|
|
|
|
}
|