[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

@@ -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