[http] Make libcurl a hard requirement

Always using libcurl makes the code simpler, plus makes sure we always have a
https client
This commit is contained in:
ejurgensen 2020-07-04 14:15:59 +02:00
parent 941fb47c1d
commit 3e707c4060
4 changed files with 20 additions and 304 deletions

View File

@ -26,7 +26,8 @@ sudo apt-get install \
gperf antlr3 libantlr3c-dev libconfuse-dev libunistring-dev libsqlite3-dev \
libavcodec-dev libavformat-dev libavfilter-dev libswscale-dev libavutil-dev \
libasound2-dev libmxml-dev libgcrypt20-dev libavahi-client-dev zlib1g-dev \
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev
libevent-dev libplist-dev libsodium-dev libjson-c-dev libwebsockets-dev \
libcurl*-dev
```
Optional packages:
@ -34,8 +35,7 @@ Optional packages:
Feature | Configure argument | Packages
--------------------|--------------------------|------------------------------------------------
Chromecast | `--enable-chromecast` | libgnutls*-dev libprotobuf-c-dev
LastFM | `--enable-lastfm` | libcurl4-\[gnutls\|openssl\]-dev
Spotify | `--enable-spotify` | libcurl4-\[gnutls\|openssl\]-dev libspotify-dev
Spotify | `--enable-spotify` | libspotify-dev
iTunes XML | `--disable-itunes` | libplist-dev
Device verification | `--disable-verification` | libplist-dev libsodium-dev
Player web UI | `--disable-webinterface` | libwebsockets-dev
@ -136,7 +136,7 @@ Afterwards, you can optionally install Oracle's newer version, and then
```bash
sudo port install \
autoconf automake libtool pkgconfig git gperf libgcrypt \
libunistring libconfuse ffmpeg libevent json-c libwebsockets
libunistring libconfuse ffmpeg libevent json-c libwebsockets curl
```
Download, configure, build and install the Mini-XML library:
@ -160,7 +160,6 @@ Optional features require the following additional ports:
Feature | Configure argument | Ports
--------------------|--------------------------|-------------------
Chromecast | `--enable-chromecast` | gnutls protobuf-c
LastFM | `--enable-lastfm` | curl
iTunes XML | `--disable-itunes` | libplist
Device verification | `--disable-verification` | libplist libsodium
Pulseaudio | `--with-pulseaudio` | pulseaudio
@ -252,6 +251,8 @@ Libraries:
from <http://www.gnu.org/software/libunistring/#downloading>
- libjson-c
from <https://github.com/json-c/json-c/wiki>
- libcurl
from <http://curl.haxx.se/libcurl/>
- libasound (optional - ALSA local audio)
often already installed as part of your distro
- libpulse (optional - Pulseaudio local audio)
@ -262,8 +263,6 @@ Libraries:
from <https://download.libsodium.org/doc/>
- libspotify (optional - Spotify support)
from <https://developer.spotify.com>
- libcurl (optional - LastFM support)
from <http://curl.haxx.se/libcurl/>
- libgnutls (optional - Chromecast support)
from <http://www.gnutls.org/>
- libprotobuf-c (optional - Chromecast support)

View File

@ -113,6 +113,7 @@ FORK_FUNC_REQUIRE([COMMON], [GNU libunistring], [LIBUNISTRING], [unistring],
FORK_MODULES_CHECK([FORKED], [ZLIB], [zlib], [deflate], [zlib.h])
FORK_MODULES_CHECK([FORKED], [CONFUSE], [libconfuse >= 3.0], [cfg_init], [confuse.h])
FORK_MODULES_CHECK([FORKED], [LIBCURL], [libcurl], [curl_global_init], [curl/curl.h])
FORK_MODULES_CHECK([FORKED], [MINIXML], [mxml],
[mxmlNewElement], [mxml.h],
@ -248,10 +249,6 @@ FORK_ARG_WITH_CHECK([FORKED_OPTS], [Pulseaudio support], [pulseaudio], [LIBPULSE
[AC_CHECK_FUNCS([pa_threaded_mainloop_set_name])])
AM_CONDITIONAL([COND_PULSEAUDIO], [[test "x$with_pulseaudio" = "xyes"]])
dnl Build with libcurl
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libcurl support], [libcurl], [LIBCURL],
[libcurl], [curl_global_init], [curl/curl.h])
dnl Build with libwebsockets
FORK_ARG_WITH_CHECK([FORKED_OPTS], [libwebsockets support], [libwebsockets], [LIBWEBSOCKETS],
[libwebsockets >= 2.0.2])
@ -284,9 +281,7 @@ AM_CONDITIONAL([COND_AVAHI], [[test "x$with_avahi" = "xyes"]])
dnl Spotify with dynamic linking to libspotify
FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
[AS_IF([[test "x$with_libcurl" = "xno"]],
[AC_MSG_ERROR([[Spotify support requires libcurl]])])
AS_IF([[test "x$with_libevent_pthreads" = "xno"]],
[AS_IF([[test "x$with_libevent_pthreads" = "xno"]],
[AC_MSG_ERROR([[Spotify support requires libevent_pthreads]])])
FORK_MODULES_CHECK([SPOTIFY], [LIBSPOTIFY], [libspotify],
[], [libspotify/api.h])
@ -300,11 +295,8 @@ FORK_ARG_ENABLE([Spotify support], [spotify], [SPOTIFY],
])
AM_CONDITIONAL([COND_SPOTIFY], [[test "x$enable_spotify" = "xyes"]])
dnl LastFM support with libcurl
FORK_ARG_ENABLE([LastFM support], [lastfm], [LASTFM],
[AS_IF([[test "x$with_libcurl" = "xno"]],
[AC_MSG_ERROR([[LastFM support requires libcurl]])])
])
dnl LastFM support
FORK_ARG_DISABLE([LastFM support], [lastfm], [LASTFM])
AM_CONDITIONAL([COND_LASTFM], [[test "x$enable_lastfm" = "xyes"]])
dnl ChromeCast support with libprotobuf-c

View File

@ -36,9 +36,7 @@
#include <event2/event.h>
#ifdef HAVE_LIBCURL
#include <curl/curl.h>
#endif
#include "http.h"
#include "httpd.h"
@ -56,259 +54,6 @@
// Number of seconds the client will wait for a response before aborting
#define HTTP_CLIENT_TIMEOUT 8
/* The strict libevent api does not permit walking through an evkeyvalq and saving
* all the http headers, so we predefine what we are looking for. You can add
* extra headers here that you would like to save.
*/
static char *header_list[] =
{
"icy-name",
"icy-description",
"icy-metaint",
"icy-genre",
"Content-Type",
};
/* Copies headers we are searching for from one keyval struct to another
*
*/
static void
headers_save(struct keyval *kv, struct evkeyvalq *headers)
{
const char *value;
uint8_t *utf;
int i;
if (!kv || !headers)
return;
for (i = 0; i < (sizeof(header_list) / sizeof(header_list[0])); i++)
if ( (value = evhttp_find_header(headers, header_list[i])) )
{
utf = u8_strconv_from_encoding(value, "ISO88591", iconveh_question_mark);
if (!utf)
continue;
keyval_add(kv, header_list[i], (char *)utf);
free(utf);
}
}
static void
request_cb(struct evhttp_request *req, void *arg)
{
struct http_client_ctx *ctx;
const char *response_code_line;
ctx = (struct http_client_ctx *)arg;
if (ctx->headers_only)
{
ctx->ret = 0;
event_base_loopbreak(ctx->evbase);
return;
}
if (!req)
{
DPRINTF(E_WARN, L_HTTP, "Connection to %s failed: Connection timed out\n", ctx->url);
goto connection_error;
}
ctx->response_code = evhttp_request_get_response_code(req);
#ifndef HAVE_LIBEVENT2_OLD
response_code_line = evhttp_request_get_response_code_line(req);
#else
response_code_line = "no error text";
#endif
if (ctx->response_code == 0)
{
DPRINTF(E_WARN, L_HTTP, "Connection to %s failed: Connection refused\n", ctx->url);
goto connection_error;
}
else if (ctx->response_code != 200)
{
DPRINTF(E_WARN, L_HTTP, "Connection to %s failed: %s (error %d)\n", ctx->url, response_code_line, ctx->response_code);
goto connection_error;
}
ctx->ret = 0;
if (ctx->input_headers)
headers_save(ctx->input_headers, evhttp_request_get_input_headers(req));
if (ctx->input_body)
evbuffer_add_buffer(ctx->input_body, evhttp_request_get_input_buffer(req));
event_base_loopbreak(ctx->evbase);
return;
connection_error:
ctx->ret = -1;
event_base_loopbreak(ctx->evbase);
return;
}
/* This callback is only invoked if ctx->headers_only is set. Since that means
* we only want headers, it will always return -1 to make evhttp close the
* connection. The headers will be saved in a keyval struct in ctx, since we
* cannot address the *evkeyvalq after the connection is free'd.
*/
#ifndef HAVE_LIBEVENT2_OLD
static int
request_header_cb(struct evhttp_request *req, void *arg)
{
struct http_client_ctx *ctx;
ctx = (struct http_client_ctx *)arg;
if (!ctx->input_headers)
{
DPRINTF(E_LOG, L_HTTP, "BUG: Header callback invoked but caller did not say where to save the headers\n");
return -1;
}
headers_save(ctx->input_headers, evhttp_request_get_input_headers(req));
return -1;
}
#endif
static int
http_client_request_impl(struct http_client_ctx *ctx)
{
struct evhttp_connection *evcon;
struct evhttp_request *req;
struct evkeyvalq *headers;
struct evbuffer *output_buffer;
struct onekeyval *okv;
enum evhttp_cmd_type method;
const char *user_agent;
char host[512];
char host_port[1024];
char path[2048];
char tmp[128];
int port;
int ret;
ctx->ret = -1;
av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, path, sizeof(path), ctx->url);
if (strlen(host) == 0)
{
DPRINTF(E_LOG, L_HTTP, "Error extracting hostname from URL: %s\n", ctx->url);
return ctx->ret;
}
if (port <= 0)
snprintf(host_port, sizeof(host_port), "%s", host);
else
snprintf(host_port, sizeof(host_port), "%s:%d", host, port);
if (port <= 0)
port = 80;
if (strlen(path) == 0)
{
path[0] = '/';
path[1] = '\0';
}
ctx->evbase = event_base_new();
if (!ctx->evbase)
{
DPRINTF(E_LOG, L_HTTP, "Could not create or find http client event base\n");
return ctx->ret;
}
evcon = evhttp_connection_base_new(ctx->evbase, NULL, host, (unsigned short)port);
if (!evcon)
{
DPRINTF(E_LOG, L_HTTP, "Could not create connection to %s\n", host_port);
event_base_free(ctx->evbase);
return ctx->ret;
}
evhttp_connection_set_timeout(evcon, HTTP_CLIENT_TIMEOUT);
/* Set up request */
req = evhttp_request_new(request_cb, ctx);
if (!req)
{
DPRINTF(E_LOG, L_HTTP, "Could not create request to %s\n", host_port);
evhttp_connection_free(evcon);
event_base_free(ctx->evbase);
return ctx->ret;
}
#ifndef HAVE_LIBEVENT2_OLD
if (ctx->headers_only)
evhttp_request_set_header_cb(req, request_header_cb);
#endif
headers = evhttp_request_get_output_headers(req);
evhttp_add_header(headers, "Host", host_port);
user_agent = cfg_getstr(cfg_getsec(cfg, "general"), "user_agent");
evhttp_add_header(headers, "User-Agent", user_agent);
evhttp_add_header(headers, "Icy-MetaData", "1");
if (ctx->output_headers)
{
for (okv = ctx->output_headers->head; okv; okv = okv->next)
evhttp_add_header(headers, okv->name, okv->value);
}
if (ctx->output_body)
{
output_buffer = evhttp_request_get_output_buffer(req);
evbuffer_add(output_buffer, ctx->output_body, strlen(ctx->output_body));
evbuffer_add_printf(output_buffer, "\n");
snprintf(tmp, sizeof(tmp), "%zu", evbuffer_get_length(output_buffer));
evhttp_add_header(headers, "Content-Length", tmp);
method = EVHTTP_REQ_POST;
}
else
{
evhttp_add_header(headers, "Content-Length", "0");
method = EVHTTP_REQ_GET;
}
/* Make request */
DPRINTF(E_INFO, L_HTTP, "Making %s request for http://%s%s\n", ((method==EVHTTP_REQ_GET) ? "GET" : "POST"), host_port, path);
ret = evhttp_make_request(evcon, req, method, path);
if (ret < 0)
{
DPRINTF(E_LOG, L_HTTP, "Error making request for http://%s%s\n", host_port, path);
evhttp_connection_free(evcon);
event_base_free(ctx->evbase);
return ctx->ret;
}
event_base_dispatch(ctx->evbase);
evhttp_connection_free(evcon);
event_base_free(ctx->evbase);
return ctx->ret;
}
#ifdef HAVE_LIBCURL
static void
curl_headers_save(struct keyval *kv, CURL *curl)
{
@ -348,8 +93,8 @@ curl_request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
return realsize;
}
static int
https_client_request_impl(struct http_client_ctx *ctx)
int
http_client_request(struct http_client_ctx *ctx)
{
CURL *curl;
CURLcode res;
@ -370,7 +115,8 @@ https_client_request_impl(struct http_client_ctx *ctx)
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
curl_easy_setopt(curl, CURLOPT_URL, ctx->url);
headers = NULL;
snprintf(header, sizeof(header), "Icy-MetaData: 1");
headers = curl_slist_append(NULL, header);
if (ctx->output_headers)
{
for (okv = ctx->output_headers->head; okv; okv = okv->next)
@ -378,12 +124,13 @@ https_client_request_impl(struct http_client_ctx *ctx)
snprintf(header, sizeof(header), "%s: %s", okv->name, okv->value);
headers = curl_slist_append(headers, header);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
}
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
if (ctx->output_body)
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->output_body);
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
curl_easy_setopt(curl, CURLOPT_TIMEOUT, HTTP_CLIENT_TIMEOUT);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_request_cb);
@ -414,21 +161,6 @@ https_client_request_impl(struct http_client_ctx *ctx)
return 0;
}
#endif /* HAVE_LIBCURL */
int
http_client_request(struct http_client_ctx *ctx)
{
if (strncmp(ctx->url, "http:", strlen("http:")) == 0)
return http_client_request_impl(ctx);
#ifdef HAVE_LIBCURL
if (strncmp(ctx->url, "https:", strlen("https:")) == 0)
return https_client_request_impl(ctx);
#endif
DPRINTF(E_LOG, L_HTTP, "Request for %s is not supported (not built with libcurl?)\n", ctx->url);
return -1;
}
char *
http_form_urlencode(struct keyval *kv)

View File

@ -54,6 +54,7 @@
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <curl/curl.h>
#include <pthread.h>
#include <gcrypt.h>
@ -75,10 +76,6 @@ GCRY_THREAD_OPTION_PTHREAD_IMPL;
# include "lastfm.h"
#endif
#ifdef HAVE_LIBCURL
# include <curl/curl.h>
#endif
#define PIDFILE STATEDIR "/run/" PACKAGE ".pid"
#define WEB_ROOT DATADIR "/htdocs"
@ -684,10 +681,8 @@ main(int argc, char **argv)
#endif
av_log_set_callback(logger_ffmpeg);
#ifdef HAVE_LIBCURL
/* Initialize libcurl */
curl_global_init(CURL_GLOBAL_DEFAULT);
#endif
/* Initialize libgcrypt */
gcry_control(GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
@ -991,9 +986,7 @@ main(int argc, char **argv)
signal_block_fail:
gcrypt_init_fail:
#ifdef HAVE_LIBCURL
curl_global_cleanup();
#endif
#if HAVE_DECL_AVFORMAT_NETWORK_INIT
avformat_network_deinit();
#endif