#include #include #include #include #include #include #include #include "utils.hpp" #define ERROR_PAGE_START "\n" \ "\n" \ " \n" \ " \n" \ " \n" \ " ImgurXP\n" \ " \n" \ " \n" \ " \n" \ "
" #define ERROR_PAGE_END "
\n \n" #define ALBUM_PAGE_START "\n" \ "\n" \ " \n" \ " \n" \ " \n" \ " " #define ALBUM_PAGE_START_2 "\n" \ " \n" \ " \n" \ " " #define ALBUM_PAGE_END "\n \n" std::string build_error_page(const char* error, bool already_escaped) { std::string output = ERROR_PAGE_START; if (already_escaped) { output.append(error); } else { std::string error_string = error; escape_xml(&error_string); output.append(std::move(error_string)); } output.append(ERROR_PAGE_END); return output; } // behold: the why // oh and this is stolen from https://stackoverflow.com/a/3418285 void escape_xml(std::string* str) { if (str->empty()) { return; } size_t start = 0; while ((start = str->find("&", start)) != std::string::npos) { str->replace(start, 1, "&"); start += 5; } start = 0; while ((start = str->find("<", start)) != std::string::npos) { str->replace(start, 1, "<"); start += 4; } start = 0; while ((start = str->find(">", start)) != std::string::npos) { str->replace(start, 1, ">"); start += 4; } start = 0; while ((start = str->find("\"", start)) != std::string::npos) { str->replace(start, 1, """); start += 6; } start = 0; while ((start = str->find("'", start)) != std::string::npos) { str->replace(start, 1, "'"); start += 6; } } std::optional deserialize_album(kore_json json, Album* album) { *album = {false, std::nullopt, {}}; kore_json_item* title_json = kore_json_find_string(json.root, "title"); if (title_json && *title_json->data.string) { album->title = title_json->data.string; } kore_json_item* is_mature_json = kore_json_find_literal(json.root, "is_mature"); if (is_mature_json) { album->is_mature = is_mature_json->data.literal == KORE_JSON_TRUE; } kore_json_item* medias_json = kore_json_find_array(json.root, "media"); if (!medias_json) { return "missing media key in response"; } kore_json_item* media_json; TAILQ_FOREACH(media_json, &medias_json->data.items, list) { kore_json_item* media_type_json = kore_json_find_string(media_json, "type"); kore_json_item* id_json = kore_json_find_string(media_json, "id"); kore_json_item* ext_json = kore_json_find_string(media_json, "ext"); kore_json_item* width_json = kore_json_find_integer_u64(media_json, "width"); kore_json_item* height_json = kore_json_find_integer_u64(media_json, "height"); if (!id_json) { return "missing id key from response"; } if (!media_type_json) { std::string error = "missing type key from response (id: "; error.append(id_json->data.string); error.append(")"); return error; } if (!ext_json) { std::string error = "missing ext key from response (id: "; error.append(id_json->data.string); error.append(")"); return error; } if (!width_json) { std::string error = "missing width key from response (id: "; error.append(id_json->data.string); error.append(")"); return error; } if (!height_json) { std::string error = "missing height key from response (id: "; error.append(id_json->data.string); error.append(")"); return error; } MediaType media_type = MediaType::Image; if (!strcmp(media_type_json->data.string, "image")) { // already set as image } else if (!strcmp(media_type_json->data.string, "video")) { media_type = MediaType::Video; } else { std::string error = "unknown media type (id: "; error.append(id_json->data.string); error.append(", type: "); error.append(media_type_json->data.string); error.append(")"); return error; } Media media = {std::nullopt, media_type, id_json->data.string, ext_json->data.string, width_json->data.u64, height_json->data.u64}; kore_json_item* metadata_json = kore_json_find_object(media_json, "metadata"); if (metadata_json) { kore_json_item* description_json = kore_json_find_string(metadata_json, "description"); if (description_json && *description_json->data.string) { media.description = description_json->data.string; } } album->media.push_back(std::move(media)); } return std::nullopt; } DeterminedKeys determine_keys(const char* id, bool album) { std::string id_str; if (album) { id_str.push_back('+'); id_str.append(id); } else { id_str = id; } if (id_str.length() <= 2) { return {"imgurxp:", std::move(id_str)}; } char s1 = id_str.back(); id_str.pop_back(); char s0 = id_str.back(); id_str.pop_back(); id_str.insert(0, "imgurxp:"); return {std::move(id_str), {s0, s1}}; } void escape_newline(std::string* str) { if (str->empty()) { return; } size_t start = 0; while ((start = str->find("\\", start)) != std::string::npos) { str->replace(start, 1, "\\\\"); start += 2; } start = 0; while ((start = str->find("\n", start)) != std::string::npos) { str->replace(start, 1, "\\n"); start += 2; } } void unescape_newline(std::string* str) { if (str->empty()) { return; } size_t start = 0; while ((start = str->find('\\', start)) != std::string::npos) { if (str->length() - 1 >= start + 1) { char c = str->at(start + 1); if (c == 'n') { c = '\n'; } str->replace(start, 2, &c, 1); } start++; } } std::string pack_album_into_string(Album album) { std::string packed; if (album.is_mature) { packed.push_back('1'); } else { packed.push_back('0'); } if (album.title) { std::string title = (*album.title).substr(); escape_newline(&title); packed.append(std::move(title)); } for (size_t i=0; i < album.media.size(); i++) { packed.push_back('\n'); Media media = album.media[i]; switch (media.type) { case MediaType::Image: packed.push_back('0'); break; case MediaType::Video: packed.push_back('1'); break; } packed.push_back(' '); packed.append(media.id); packed.push_back(' '); packed.append(media.ext); packed.push_back(' '); packed.append(std::to_string(media.width)); packed.push_back(' '); packed.append(std::to_string(media.height)); if (media.description) { packed.push_back(' '); std::string description = (*media.description).substr(); escape_newline(&description); packed.append(std::move(description)); } } return packed; } static std::optional parse_packed_media_line(char* line, Media* media) { switch (line[0]) { case '0': media->type = MediaType::Image; break; case '1': media->type = MediaType::Video; break; default: std::string error = "unknown media type ("; error.push_back(line[0]); error.push_back(')'); return error; } media->id = std::string(&(line[2]), strchr(&(line[2]), ' ') - line - 2); size_t offset = 2 + media->id.length() + 1; media->ext = std::string(&(line[offset]), strchr(&(line[offset]), ' ') - line - offset); offset += media->ext.length() + 1; size_t width_offset = strchr(&(line[offset]), ' ') - line; std::from_chars_result char_res = std::from_chars(&(line[offset]), &(line[width_offset]), media->width); if (char_res.ec != std::errc()) { std::string error = "failed to parse width: "; error.append(std::make_error_code(char_res.ec).message()); return error; } offset = width_offset + 1; size_t height_offset = strchr(&(line[offset]), ' ') - line; if (!height_offset) { height_offset = strlen(line); } char_res = std::from_chars(&(line[offset]), &(line[height_offset]), media->height); if (char_res.ec != std::errc()) { std::string error = "failed to parse height: "; error.append(std::make_error_code(char_res.ec).message()); return error; } offset = height_offset + 1; char* line_end = strchr(line, '\n'); if (!line_end) { line_end = &(line[strlen(line)]); } if ((size_t)(line_end - line) > offset) { offset++; std::string description = std::string(&(line[offset]), line_end - line - offset); unescape_newline(&description); media->description = std::move(description); } return std::nullopt; } std::optional unpack_album_from_string(Album* album, char* str) { album->is_mature = str[0] != '0'; std::string title; char* title_end = strchr(&(str[1]), '\n'); if (title_end) { title = std::string(&(str[1]), title_end - str - 1); } else { title = &(str[1]); } if (!title.empty()) { unescape_newline(&title); album->title = std::move(title); } if (!title_end) { return std::nullopt; } str = &(title_end[1]); while (true) { char spaces = 0; for (size_t i=0; str[i] && str[i] != '\n'; i++) { if (str[i] == ' ') { spaces++; if (spaces == 4) { break; } } } if (spaces < 4) { return "media line missing required spaces"; } Media media = {std::nullopt, MediaType::Image, "", "", 0, 0}; std::optional error = parse_packed_media_line(str, &media); if (error) { return error; } album->media.push_back(std::move(media)); char* line_end = strchr(str, '\n'); if (!line_end) { break; } str = &(line_end[1]); } return std::nullopt; } int send_album_page(struct http_request* req, Album album) { std::string response = ALBUM_PAGE_START; std::optional escaped_title; if (album.title) { escaped_title = album.title; escape_xml(&(*escaped_title)); response.append((*escaped_title)); } else { response.append("ImgurX"); } response.append(ALBUM_PAGE_START_2); if (album.is_mature) { response.append("\n
Marked as Mature
"); } if (escaped_title) { response.append("\n

"); response.append(std::move((*escaped_title))); response.append("

"); } for (size_t i=0; i < album.media.size(); i++ ) { Media* media = &(album.media[i]); switch (media->type) { case MediaType::Image: response.append("\n id); response.append("."); response.append(media->ext); response.append("\" width=\""); response.append(std::to_string(media->width)); response.append("\" height=\""); response.append(std::to_string(media->height)); response.append("\" />"); break; case MediaType::Video: response.append("\n