239 lines
9.1 KiB
C++
239 lines
9.1 KiB
C++
#include <curl/curl.h>
|
|
#include <kore/kore.h>
|
|
#include <kore/http.h>
|
|
#include <kore/curl.h>
|
|
#include <kore/tasks.h>
|
|
#include <string>
|
|
#include "utils.hpp"
|
|
#include "config.hpp"
|
|
|
|
extern "C" {
|
|
int album_or_image(struct http_request*);
|
|
static int album_or_image_start(struct http_request*);
|
|
static int album_or_image_end(struct http_request*);
|
|
}
|
|
|
|
static struct http_state states[] = {
|
|
KORE_HTTP_STATE(album_or_image_start),
|
|
KORE_HTTP_STATE(album_or_image_end)
|
|
};
|
|
|
|
int album_or_image(struct http_request* req) {
|
|
return http_state_run(states, 2, req);
|
|
}
|
|
|
|
static bool send_hget_and_http_resp(struct http_request* req, const char* id, bool is_album) {
|
|
DeterminedKeys keys = determine_keys(id, is_album);
|
|
redis_mutex.lock();
|
|
redisReply* reply = (redisReply*)redisCommand(redis, "HGET %s %s", keys.key.c_str(), keys.field.c_str());
|
|
if (!reply) {
|
|
redis_mutex.unlock();
|
|
kore_log(LOG_WARNING, "received nullptr while sending HGET to redis");
|
|
return false;
|
|
}
|
|
if (reply->type == REDIS_REPLY_ERROR) {
|
|
kore_log(LOG_WARNING, "received error while sending HGET to redis: %s", reply->str);
|
|
freeReplyObject(reply);
|
|
redis_mutex.unlock();
|
|
return false;
|
|
}
|
|
if (reply->type == REDIS_REPLY_NIL) {
|
|
freeReplyObject(reply);
|
|
redis_mutex.unlock();
|
|
return false;
|
|
}
|
|
if (!reply->len) {
|
|
freeReplyObject(reply);
|
|
redis_mutex.unlock();
|
|
char* error = "404: media not found";
|
|
if (is_album) {
|
|
error = "404: album not found";
|
|
}
|
|
std::string error_page = build_error_page(error);
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 404, error_page.c_str(), error_page.length());
|
|
return true;
|
|
}
|
|
Album album = {false, std::nullopt, {}};
|
|
std::optional<std::string> error = unpack_album_from_string(&album, reply->str);
|
|
freeReplyObject(reply);
|
|
redis_mutex.unlock();
|
|
if (error) {
|
|
kore_log(LOG_WARNING, "received error while parsing packed album: %s", (*error).c_str());
|
|
return false;
|
|
}
|
|
send_album_page(req, std::move(album));
|
|
return true;
|
|
}
|
|
|
|
static int album_or_image_start(struct http_request* req) {
|
|
const char* id = &(req->path[1]);
|
|
bool is_album = false;
|
|
const char* slash_in_path = strchr(id, '/');
|
|
if (slash_in_path) {
|
|
id = &(slash_in_path[1]);
|
|
is_album = true;
|
|
}
|
|
if (redis) {
|
|
if (send_hget_and_http_resp(req, id, is_album)) {
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
}
|
|
std::string api_url = "https://api.imgur.com/post/v1/";
|
|
if (is_album) {
|
|
api_url.append("albums/");
|
|
} else {
|
|
api_url.append("media/");
|
|
}
|
|
api_url.append(id);
|
|
api_url.append("?client_id=546c25a59c58ad7&include=media");
|
|
struct kore_curl* client = (kore_curl*)http_state_create(req, sizeof(*client), NULL);
|
|
if (!kore_curl_init(client, api_url.c_str(), KORE_CURL_ASYNC)) {
|
|
http_state_cleanup(req);
|
|
kore_log(LOG_ERR, "failed to initialize curl client");
|
|
std::string error_page = build_error_page("Failed to initialize curl client");
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 500, error_page.c_str(), error_page.length());
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
// handle compressed responses automatically
|
|
curl_easy_setopt(client->handle, CURLOPT_ACCEPT_ENCODING, "");
|
|
kore_curl_http_setup(client, HTTP_METHOD_GET, NULL, 0);
|
|
kore_curl_bind_request(client, req);
|
|
kore_curl_run(client);
|
|
req->fsm_state = 1;
|
|
return HTTP_STATE_RETRY;
|
|
}
|
|
|
|
static void send_hset(const char* id, bool is_album, const char* packed) {
|
|
DeterminedKeys keys = determine_keys(id, is_album);
|
|
redis_mutex.lock();
|
|
redisReply* reply = (redisReply*)redisCommand(redis, "HSET %s %s %s", keys.key.c_str(), keys.field.c_str(), packed);
|
|
if (!reply) {
|
|
redis_mutex.unlock();
|
|
kore_log(LOG_WARNING, "received nullptr while sending HSET to redis");
|
|
return;
|
|
}
|
|
if (reply->type == REDIS_REPLY_ERROR) {
|
|
kore_log(LOG_WARNING, "received error while sending HSET to redis: %s", reply->str);
|
|
freeReplyObject(reply);
|
|
redis_mutex.unlock();
|
|
return;
|
|
}
|
|
freeReplyObject(reply);
|
|
redis_mutex.unlock();
|
|
return;
|
|
}
|
|
|
|
static int album_or_image_end(struct http_request* req) {
|
|
const char* id = &(req->path[1]);
|
|
bool is_album = false;
|
|
const char* slash_in_path = strchr(id, '/');
|
|
if (slash_in_path) {
|
|
id = &(slash_in_path[1]);
|
|
is_album = true;
|
|
}
|
|
struct kore_curl* client = (kore_curl*)http_state_get(req);
|
|
if (!kore_curl_success(client)) {
|
|
kore_curl_logerror(client);
|
|
std::string error = "Failed to contact API: ";
|
|
error.append(kore_curl_strerror(client));
|
|
kore_curl_cleanup(client);
|
|
http_state_cleanup(req);
|
|
std::string error_page = build_error_page(error.c_str());
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 500, error_page.c_str(), error_page.length());
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
char* body = kore_curl_response_as_string(client);
|
|
kore_json json;
|
|
kore_json_init(&json, body, strlen(body));
|
|
if (kore_json_parse(&json) != KORE_RESULT_OK) {
|
|
kore_log(LOG_ERR, "failed to parse json: %s", kore_json_strerror(&json));
|
|
std::string error = "Failed to parse response: ";
|
|
error.append(kore_json_strerror(&json));
|
|
kore_json_cleanup(&json);
|
|
kore_curl_cleanup(client);
|
|
http_state_cleanup(req);
|
|
std::string error_page = build_error_page(error.c_str());
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 500, error_page.c_str(), error_page.length());
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
kore_json_item* errors = kore_json_find_array(json.root, "errors");
|
|
if (errors) {
|
|
std::string error = "Received error(s) from API:<pre><code>";
|
|
struct kore_json_item* error_json;
|
|
if (!TAILQ_EMPTY(&errors->data.items)) {
|
|
error_json = TAILQ_FIRST(&errors->data.items);
|
|
kore_json_item* error_code_json = kore_json_find_string(error_json, "code");
|
|
kore_json_item* error_detail_json = kore_json_find_string(error_json, "detail");
|
|
if (error_code_json && error_detail_json && !strcmp(error_code_json->data.string, "404")) {
|
|
error = "404: ";
|
|
error.append(error_detail_json->data.string);
|
|
kore_json_cleanup(&json);
|
|
kore_curl_cleanup(client);
|
|
http_state_cleanup(req);
|
|
if (redis) {
|
|
send_hset(id, is_album, "");
|
|
}
|
|
std::string error_page = build_error_page(error.c_str());
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 404, error_page.c_str(), error_page.length());
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
}
|
|
bool needs_newline = false;
|
|
TAILQ_FOREACH(error_json, &errors->data.items, list) {
|
|
// istg if string.append mutates strings passed
|
|
char* error_code = "unknown error code";
|
|
char* error_detail = "unknown error detail";
|
|
kore_json_item* error_code_json = kore_json_find_string(error_json, "code");
|
|
kore_json_item* error_detail_json = kore_json_find_string(error_json, "detail");
|
|
if (error_code_json) {
|
|
error_code = error_code_json->data.string;
|
|
}
|
|
if (error_detail_json) {
|
|
error_detail = error_detail_json->data.string;
|
|
}
|
|
kore_log(LOG_ERR, "received error from api: %s: %s", error_code, error_detail);
|
|
std::string to_append;
|
|
if (needs_newline) {
|
|
to_append.append("\n");
|
|
}
|
|
to_append.append(error_code);
|
|
to_append.append(": ");
|
|
to_append.append(error_detail);
|
|
escape_xml(&to_append);
|
|
error.append(std::move(to_append));
|
|
needs_newline = true;
|
|
}
|
|
error.append("</code></pre>");
|
|
kore_json_cleanup(&json);
|
|
kore_curl_cleanup(client);
|
|
http_state_cleanup(req);
|
|
std::string error_page = build_error_page(error.c_str(), true);
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 500, error_page.c_str(), error_page.length());
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
Album album;
|
|
std::optional<std::string> error = deserialize_album(json, &album);
|
|
kore_json_cleanup(&json);
|
|
kore_curl_cleanup(client);
|
|
http_state_cleanup(req);
|
|
if (error) {
|
|
kore_log(LOG_ERR, "%s", (*error).c_str());
|
|
std::string error_page = build_error_page((*error).c_str());
|
|
http_response_header(req, "Content-Type", "text/html");
|
|
http_response(req, 500, error_page.c_str(), error_page.length());
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|
|
if (redis) {
|
|
std::string packed = pack_album_into_string(album);
|
|
send_hset(id, is_album, packed.c_str());
|
|
}
|
|
send_album_page(req, std::move(album));
|
|
return HTTP_STATE_COMPLETE;
|
|
}
|