#include #include #include #include #include #include #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_NOTICE, "received nullptr while sending HGET to redis"); return false; } if (reply->type == REDIS_REPLY_ERROR) { kore_log(LOG_NOTICE, "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(req, 404, error_page.c_str(), error_page.length()); return true; } Album album = {false, std::nullopt, {}}; std::optional error = unpack_album_from_string(&album, reply->str); freeReplyObject(reply); redis_mutex.unlock(); if (error) { kore_log(LOG_NOTICE, "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_NOTICE, "failed to initialize curl client"); std::string error_page = build_error_page("Failed to initialize curl client"); 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_NOTICE, "received nullptr while sending HSET to redis"); return; } if (reply->type == REDIS_REPLY_ERROR) { kore_log(LOG_NOTICE, "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(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_NOTICE, "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(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:
";
        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(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_NOTICE, "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("
"); 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(req, 500, error_page.c_str(), error_page.length()); return HTTP_STATE_COMPLETE; } Album album; std::optional error = deserialize_album(json, &album); kore_json_cleanup(&json); kore_curl_cleanup(client); http_state_cleanup(req); if (error) { kore_log(LOG_NOTICE, "%s", (*error).c_str()); std::string error_page = build_error_page((*error).c_str()); 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; }