Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 95 additions & 52 deletions src/http.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@

#include <curl/curl.h>

#include "conffile.h"
#include "http.h"
#include "logger.h"
#include "misc.h"
#include "conffile.h"

/* Formats we can read so far */
#define PLAYLIST_UNK 0
Expand All @@ -55,17 +55,30 @@
// Number of seconds the client will wait for a response before aborting
#define HTTP_CLIENT_TIMEOUT 8

struct http_client_session {
CURL *curl;
const char *user_agent;
long verifypeer;
long timeout_sec;
};

void
http_client_session_init(struct http_client_session *session)
struct http_client_session *
http_client_session_new(void)
{
session->curl = curl_easy_init();
struct http_client_session *session;
CHECK_NULL(L_HTTP, session = calloc(1, sizeof(struct http_client_session)));
CHECK_NULL(L_HTTP, session->curl = curl_easy_init());
session->user_agent = cfg_getstr(cfg_getsec(cfg, "general"), "user_agent");
session->verifypeer = cfg_getbool(cfg_getsec(cfg, "general"), "ssl_verifypeer");
session->timeout_sec = HTTP_CLIENT_TIMEOUT;
return session;
}

void
http_client_session_deinit(struct http_client_session *session)
http_client_session_free(struct http_client_session *session)
{
curl_easy_cleanup(session->curl);
free(session);
}

static void
Expand Down Expand Up @@ -107,91 +120,121 @@ curl_request_cb(char *ptr, size_t size, size_t nmemb, void *userdata)
return realsize;
}

static int
curl_debug_cb(CURL *handle, curl_infotype type, char *data, size_t size, void *ctx)
{
switch (type)
{
case CURLINFO_TEXT:
DPRINTF(E_SPAM, L_HTTP, "curl - %.*s", (int) size, data);
break;
case CURLINFO_HEADER_OUT:
DPRINTF(E_SPAM, L_HTTP, "curl > Request-Header - %.*s", (int) size, data);
break;
case CURLINFO_DATA_OUT:
DHEXDUMP(E_SPAM, L_HTTP, (unsigned char *) data, (int) size, "curl > Request-Body\n");
break;
case CURLINFO_SSL_DATA_OUT:
DHEXDUMP(E_SPAM, L_HTTP, (unsigned char *) data, (int) size, "curl > SSL Out\n");
break;
case CURLINFO_HEADER_IN:
DPRINTF(E_SPAM, L_HTTP, "curl < Response-Header - %.*s", (int) size, data);
break;
case CURLINFO_DATA_IN:
DHEXDUMP(E_SPAM, L_HTTP, (unsigned char *) data, (int) size, "curl < Response-Body\n");
break;
case CURLINFO_SSL_DATA_IN:
DHEXDUMP(E_SPAM, L_HTTP, (unsigned char *) data, (int) size, "curl < SSL In\n");
break;
default:
// Ignore unknown types
break;
}

return 0;
}

int
http_client_request(struct http_client_ctx *ctx, struct http_client_session *session)
http_client_request(struct http_client_ctx *ctx, struct http_client_session *client_session)
{
CURL *curl;
struct http_client_session *session;
CURLcode res;
struct curl_slist *headers;
struct curl_slist *headers = NULL;
struct onekeyval *okv;
const char *user_agent;
long verifypeer;
char header[1024];
long response_code;
int ret;

if (session)
if (!client_session)
{
curl = session->curl;
curl_easy_reset(curl);
session = http_client_session_new();
}
else
{
curl = curl_easy_init();
session = client_session;
curl_easy_reset(session->curl);
}
if (!curl)
{
DPRINTF(E_LOG, L_HTTP, "Error: Could not get curl handle\n");
return -1;
}

user_agent = cfg_getstr(cfg_getsec(cfg, "general"), "user_agent");
curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent);
curl_easy_setopt(curl, CURLOPT_URL, ctx->url);

verifypeer = cfg_getbool(cfg_getsec(cfg, "general"), "ssl_verifypeer");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, verifypeer);
curl_easy_setopt(session->curl, CURLOPT_USERAGENT, session->user_agent);
curl_easy_setopt(session->curl, CURLOPT_URL, ctx->url);
curl_easy_setopt(session->curl, CURLOPT_SSL_VERIFYPEER, session->verifypeer);

headers = NULL;
if (ctx->output_headers)
{
for (okv = ctx->output_headers->head; okv; okv = okv->next)
{
snprintf(header, sizeof(header), "%s: %s", okv->name, okv->value);
ret = snprintf(header, sizeof(header), "%s: %s", okv->name, okv->value);
if (ret < 0 || ret >= sizeof(header))
{
DPRINTF(E_LOG, L_HTTP, "Could not add header, value has more than %zd chars: '%s: %s'\n", sizeof(header),
okv->name, okv->value);
res = CURLE_FAILED_INIT;
goto out;
}
headers = curl_slist_append(headers, header);
}

curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(session->curl, CURLOPT_HTTPHEADER, headers);
}

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
if (ctx->output_body)
curl_easy_setopt(session->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);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx);
curl_easy_setopt(session->curl, CURLOPT_TIMEOUT, session->timeout_sec);
curl_easy_setopt(session->curl, CURLOPT_WRITEFUNCTION, curl_request_cb);
curl_easy_setopt(session->curl, CURLOPT_WRITEDATA, ctx);

// Artwork and playlist requests might require redirects
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5);
curl_easy_setopt(session->curl, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(session->curl, CURLOPT_MAXREDIRS, 5);

/* Make request */
if (logger_severity() >= E_SPAM)
{
curl_easy_setopt(session->curl, CURLOPT_DEBUGFUNCTION, curl_debug_cb);
curl_easy_setopt(session->curl, CURLOPT_VERBOSE, 1);
}

// Make request
DPRINTF(E_INFO, L_HTTP, "Making request for %s\n", ctx->url);

res = curl_easy_perform(curl);
res = curl_easy_perform(session->curl);

if (res != CURLE_OK)
{
DPRINTF(E_WARN, L_HTTP, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res));
curl_slist_free_all(headers);
if (!session)
{
curl_easy_cleanup(curl);
}
return -1;
goto out;
}

curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
curl_easy_getinfo(session->curl, CURLINFO_RESPONSE_CODE, &response_code);
ctx->response_code = (int) response_code;
curl_headers_save(ctx->input_headers, curl);
curl_headers_save(ctx->input_headers, session->curl);

out:
if (!client_session)
http_client_session_free(session);
curl_slist_free_all(headers);
if (!session)
{
curl_easy_cleanup(curl);
}

return 0;
return res == CURLE_OK ? 0 : -1;
}

int
Expand Down
18 changes: 4 additions & 14 deletions src/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,11 @@

#include <event2/buffer.h>
#include <event2/http.h>
#include <curl/curl.h>
#include "misc.h"

#include <libavformat/avformat.h>

struct http_client_session
{
CURL *curl;
};
struct http_client_session;

struct http_client_ctx
{
Expand All @@ -29,12 +25,6 @@ struct http_client_ctx
struct keyval *input_headers;
struct evbuffer *input_body;

/* Cut the connection after the headers have been received
* Used for getting Shoutcast/ICY headers for old versions of libav/ffmpeg
* (requires libevent 1 or 2.1.4+)
*/
int headers_only;

/* HTTP Response code */
int response_code;
};
Expand All @@ -56,11 +46,11 @@ struct http_icy_metadata
uint32_t hash;
};

void
http_client_session_init(struct http_client_session *session);
struct http_client_session *
http_client_session_new(void);

void
http_client_session_deinit(struct http_client_session *session);
http_client_session_free(struct http_client_session *session);

/* Make a http(s) request. We use libcurl to make https requests. We could use
* libevent and avoid the dependency, but for SSL, libevent needs to be v2.1
Expand Down
33 changes: 19 additions & 14 deletions src/library/spotify_webapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,12 @@ struct spotify_credentials
time_t token_time_requested;
};

struct spotify_http_session
{
struct spotify_http_session {
pthread_mutex_t lock;
struct http_client_session session;
struct http_client_session *session;
};

static struct spotify_http_session spotify_http_session = { .lock = PTHREAD_MUTEX_INITIALIZER };

static struct spotify_credentials spotify_credentials;
static pthread_mutex_t spotify_credentials_lock = PTHREAD_MUTEX_INITIALIZER;

Expand Down Expand Up @@ -394,6 +392,18 @@ free_http_client_ctx(struct http_client_ctx *ctx)
free(ctx);
}

static int
session_http_request(struct http_client_ctx *ctx)
{
int ret;

CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_http_session.lock));
ret = http_client_request(ctx, spotify_http_session.session);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_http_session.lock));

return ret;
}

static int
request_access_tokens(struct keyval *kv, const char **err)
{
Expand All @@ -420,7 +430,7 @@ request_access_tokens(struct keyval *kv, const char **err)
ctx.output_body = param;
ctx.input_body = evbuffer_new();

ret = http_client_request(&ctx, NULL);
ret = session_http_request(&ctx);
if (ret < 0)
{
*err = "Did not get a reply from Spotify";
Expand Down Expand Up @@ -509,9 +519,7 @@ request_endpoint(const char *uri)

DPRINTF(E_DBG, L_SPOTIFY, "Making request to '%s'\n", uri);

CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_http_session.lock));
ret = http_client_request(ctx, &spotify_http_session.session);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_http_session.lock));
ret = session_http_request(ctx);
if (ret < 0)
{
DPRINTF(E_LOG, L_SPOTIFY, "Request for '%s' failed\n", uri);
Expand Down Expand Up @@ -2077,9 +2085,7 @@ spotifywebapi_library_init()
if (ret < 0)
return -1;

CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_http_session.lock));
http_client_session_init(&spotify_http_session.session);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_http_session.lock));
spotify_http_session.session = http_client_session_new();
return 0;
}

Expand All @@ -2088,9 +2094,8 @@ spotifywebapi_library_deinit()
{
spotify_deinit();

CHECK_ERR(L_SPOTIFY, pthread_mutex_lock(&spotify_http_session.lock));
http_client_session_deinit(&spotify_http_session.session);
CHECK_ERR(L_SPOTIFY, pthread_mutex_unlock(&spotify_http_session.lock));
http_client_session_free(spotify_http_session.session);
spotify_http_session.session = NULL;

credentials_clear();
}
Expand Down
Loading