mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
[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:
parent
941fb47c1d
commit
3e707c4060
13
INSTALL.md
13
INSTALL.md
@ -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)
|
||||
|
16
configure.ac
16
configure.ac
@ -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
|
||||
|
286
src/http.c
286
src/http.c
@ -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, "ISO−8859−1", 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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user