From 8c0db10e6752f5d344227f70249b229cf468e429 Mon Sep 17 00:00:00 2001 From: ejurgensen Date: Sat, 5 Nov 2016 13:43:35 +0100 Subject: [PATCH] [http] Move Curl https client from lastfm to http so it is available for other modules --- src/artwork.c | 4 +- src/http.c | 164 ++++++++++++++++++++++++++++++++++++++++++++++---- src/http.h | 25 ++++++-- src/lastfm.c | 150 ++++++--------------------------------------- 4 files changed, 195 insertions(+), 148 deletions(-) diff --git a/src/artwork.c b/src/artwork.c index 525a8396..84be2942 100644 --- a/src/artwork.c +++ b/src/artwork.c @@ -1129,8 +1129,8 @@ source_item_stream_get(struct artwork_ctx *ctx) memset(&client, 0, sizeof(struct http_client_ctx)); client.url = url; - client.headers = kv; - client.body = ctx->evbuf; + client.input_headers = kv; + client.input_body = ctx->evbuf; if (http_client_request(&client) < 0) goto out_kv; diff --git a/src/http.c b/src/http.c index 26c478c6..d45dd34b 100644 --- a/src/http.c +++ b/src/http.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Espen Jürgensen + * Copyright (C) 2016 Espen Jürgensen * * 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 @@ -36,6 +36,10 @@ #include +#ifdef HAVE_LIBCURL +#include +#endif + #include "http.h" #include "logger.h" #include "misc.h" @@ -128,10 +132,10 @@ request_cb(struct evhttp_request *req, void *arg) ctx->ret = 0; - if (ctx->headers) - headers_save(ctx->headers, evhttp_request_get_input_headers(req)); - if (ctx->body) - evbuffer_add_buffer(ctx->body, evhttp_request_get_input_buffer(req)); + 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); @@ -171,8 +175,8 @@ request_header_cb(struct evhttp_request *req, void *arg) } #endif -int -http_client_request(struct http_client_ctx *ctx) +static int +http_client_request_impl(struct http_client_ctx *ctx) { struct evhttp_connection *evcon; struct evhttp_request *req; @@ -269,6 +273,144 @@ http_client_request(struct http_client_ctx *ctx) return ctx->ret; } +#ifdef HAVE_LIBCURL +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; +} + +static int +https_client_request_impl(struct http_client_ctx *ctx) +{ + CURL *curl; + CURLcode res; + struct curl_slist *headers; + struct onekeyval *okv; + char header[1024]; + + curl = curl_easy_init(); + if (!curl) + { + DPRINTF(E_LOG, L_HTTP, "Error: Could not get curl handle\n"); + return -1; + } + + curl_easy_setopt(curl, CURLOPT_URL, ctx->url); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "forked-daapd/" VERSION); + + if (ctx->output_headers) + { + headers = NULL; + 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); + } + + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } + + if (ctx->output_body) + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->output_body); + + 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); + + /* 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)); + curl_easy_cleanup(curl); + return -1; + } + + curl_easy_cleanup(curl); + + 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) +{ + 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; +} + int http_stream_setup(char **stream, const char *url) { @@ -296,7 +438,7 @@ http_stream_setup(char **stream, const char *url) return -1; ctx.url = url; - ctx.body = evbuf; + ctx.input_body = evbuf; ret = http_client_request(&ctx); if (ret < 0) @@ -308,13 +450,13 @@ http_stream_setup(char **stream, const char *url) } // Pad with CRLF because evbuffer_readln() might not read the last line otherwise - evbuffer_add(ctx.body, "\r\n", 2); + evbuffer_add(ctx.input_body, "\r\n", 2); /* Read the playlist until the first stream link is found, but give up if * nothing is found in the first 10 lines */ n = 0; - while ((line = evbuffer_readln(ctx.body, NULL, EVBUFFER_EOL_ANY)) && (n < 10)) + while ((line = evbuffer_readln(ctx.input_body, NULL, EVBUFFER_EOL_ANY)) && (n < 10)) { n++; if (strncasecmp(line, "http://", strlen("http://")) == 0) @@ -328,7 +470,7 @@ http_stream_setup(char **stream, const char *url) free(line); } - evbuffer_free(ctx.body); + evbuffer_free(ctx.input_body); if (n != -1) { diff --git a/src/http.h b/src/http.h index 62ba912b..6999be95 100644 --- a/src/http.h +++ b/src/http.h @@ -10,13 +10,18 @@ struct http_client_ctx { + /* Destination URL, header and body of outgoing request body (headers and + * body is currently only supported for https) + */ const char *url; + struct keyval *output_headers; + char *output_body; /* A keyval/evbuf to store response headers and body. * Can be set to NULL to ignore that part of the response. */ - struct keyval *headers; - struct evbuffer *body; + 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 @@ -47,15 +52,27 @@ struct http_icy_metadata }; -/* Generic HTTP client. No support for https. +/* 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 + * or better, which is still a bit too new to be in the major distros. * * @param ctx HTTP request params, see above - * @return 0 if successful, -1 if an error occurred + * @return 0 if successful, -1 if an error occurred (e.g. no libcurl) */ int http_client_request(struct http_client_ctx *ctx); +/* Converts the keyval dictionary to a application/x-www-form-urlencoded string. + * The values will be uri_encoded. Example output: "key1=foo%20bar&key2=123". + * + * @param kv is the struct containing the parameters + * @return encoded string if succesful, NULL if an error occurred + */ +char * +http_form_urlencode(struct keyval *kv); + + /* Returns a newly allocated string with the first stream in the m3u given in * url. If url is not a m3u, the string will be a copy of url. * diff --git a/src/lastfm.c b/src/lastfm.c index 73990639..f3ee1d8e 100644 --- a/src/lastfm.c +++ b/src/lastfm.c @@ -39,14 +39,7 @@ #include "lastfm.h" #include "logger.h" #include "misc.h" - - -struct https_client_ctx -{ - const char *url; - const char *body; - struct evbuffer *data; -}; +#include "http.h" // LastFM becomes disabled if we get a scrobble, try initialising session, // but can't (probably no session key in db because user does not use LastFM) @@ -172,51 +165,6 @@ credentials_read(char *path, char **username, char **password) return 0; } -/* Converts parameters to a string in application/x-www-form-urlencoded format */ -static int -body_print(char **body, struct keyval *kv) -{ - struct evbuffer *evbuf; - struct onekeyval *okv; - 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(evbuf, k, strlen(k)); - evbuffer_add(evbuf, "=", 1); - evbuffer_add(evbuf, v, strlen(v)); - if (okv->next) - evbuffer_add(evbuf, "&", 1); - - free(k); - free(v); - } - - evbuffer_add(evbuf, "\n", 1); - - *body = evbuffer_readln(evbuf, NULL, EVBUFFER_EOL_ANY); - - evbuffer_free(evbuf); - - DPRINTF(E_DBG, L_LASTFM, "Parameters in request are: %s\n", *body); - - return 0; -} - /* Creates an md5 signature of the concatenated parameters and adds it to keyval */ static int param_sign(struct keyval *kv) @@ -288,28 +236,8 @@ mxmlGetOpaque(mxml_node_t *node) /* I - Node to get */ /* --------------------------------- MAIN --------------------------------- */ -static size_t -request_cb(char *ptr, size_t size, size_t nmemb, void *userdata) -{ - size_t realsize; - struct https_client_ctx *ctx; - int ret; - - realsize = size * nmemb; - ctx = (struct https_client_ctx *)userdata; - - ret = evbuffer_add(ctx->data, ptr, realsize); - if (ret < 0) - { - DPRINTF(E_LOG, L_LASTFM, "Error adding reply from LastFM to data buffer\n"); - return 0; - } - - return realsize; -} - static void -response_proces(struct https_client_ctx *ctx) +response_proces(struct http_client_ctx *ctx) { mxml_node_t *tree; mxml_node_t *s_node; @@ -319,9 +247,9 @@ response_proces(struct https_client_ctx *ctx) char *sk; // NULL-terminate the buffer - evbuffer_add(ctx->data, "", 1); + evbuffer_add(ctx->input_body, "", 1); - body = (char *)evbuffer_pullup(ctx->data, -1); + body = (char *)evbuffer_pullup(ctx->input_body, -1); if (!body || (strlen(body) == 0)) { DPRINTF(E_LOG, L_LASTFM, "Empty response\n"); @@ -380,59 +308,10 @@ response_proces(struct https_client_ctx *ctx) mxmlDelete(tree); } -// We use libcurl to make the request. We could use libevent and avoid the -// dependency, but for SSL, libevent needs to be v2.1 or better, which is still -// a bit too new to be in the major distros -static int -https_client_request(struct https_client_ctx *ctx) -{ - CURL *curl; - CURLcode res; - - curl = curl_easy_init(); - if (!curl) - { - DPRINTF(E_LOG, L_LASTFM, "Error: Could not get curl handle\n"); - return -1; - } - - curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, ctx->body); - curl_easy_setopt(curl, CURLOPT_URL, ctx->url); - - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, request_cb); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, ctx); - - ctx->data = evbuffer_new(); - if (!ctx->data) - { - DPRINTF(E_LOG, L_LASTFM, "Could not create evbuffer for LastFM response\n"); - curl_easy_cleanup(curl); - return -1; - } - - res = curl_easy_perform(curl); - if (res != CURLE_OK) - { - DPRINTF(E_LOG, L_LASTFM, "Request to %s failed: %s\n", ctx->url, curl_easy_strerror(res)); - curl_easy_cleanup(curl); - return -1; - } - - response_proces(ctx); - - evbuffer_free(ctx->data); - - curl_easy_cleanup(curl); - - return 0; -} - static int request_post(char *method, struct keyval *kv, int auth) { - struct https_client_ctx ctx; - char *body; + struct http_client_ctx ctx; int ret; ret = keyval_add(kv, "method", method); @@ -453,18 +332,27 @@ request_post(char *method, struct keyval *kv, int auth) return -1; } - ret = body_print(&body, kv); + memset(&ctx, 0, sizeof(struct http_client_ctx)); + + ctx.output_body = http_form_urlencode(kv); if (ret < 0) { - DPRINTF(E_LOG, L_LASTFM, "Aborting request, body_print failed\n"); + DPRINTF(E_LOG, L_LASTFM, "Aborting request, http_form_urlencode failed\n"); return -1; } - memset(&ctx, 0, sizeof(struct https_client_ctx)); ctx.url = auth ? auth_url : api_url; - ctx.body = body; + ctx.input_body = evbuffer_new(); - ret = https_client_request(&ctx); + ret = http_client_request(&ctx); + if (ret < 0) + goto out_free_ctx; + + response_proces(&ctx); + + out_free_ctx: + free(ctx.output_body); + evbuffer_free(ctx.input_body); return ret; }