/* This file is part of FediBlock-backend. * Copyright © 2020, 2021 tastytea * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ #include "rss.hpp" #include "cgi.hpp" #include "files.hpp" #include "git.hpp" #include "time.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace FediBlock::rss { using fmt::format; using std::find; using std::getenv; using std::none_of; using std::ostream; using std::string; using std::string_view; using std::uint8_t; using std::vector; using std::chrono::system_clock; void write_rss(ostream &out, const vector &entries, const vector &tags) { string selfurl; char *env{getenv("HTTPS")}; if (env == nullptr || string(env) != "on") { selfurl = "http://"; } else { selfurl = "https://"; } env = getenv("SERVER_NAME"); if (env != nullptr) { selfurl += env; } const string baseurl{selfurl + "/blocklist/"}; env = getenv("REQUEST_URI"); if (env != nullptr) { selfurl += env; } constexpr string_view rss_time_format{"%a, %d %b %Y %T %z"}; pugi::xml_document doc; auto rss{doc.append_child("rss")}; rss.append_attribute("version") = "2.0"; rss.append_attribute("xmlns:atom") = "http://www.w3.org/2005/Atom"; auto channel{rss.append_child("channel")}; auto atom_link{channel.append_child("atom:link")}; atom_link.append_attribute("href") = selfurl.c_str(); atom_link.append_attribute("rel") = "self"; atom_link.append_attribute("type") = "application/rss+xml"; string tmp_title{"FediBlock: "}; string tmp_description{"The newest FediBlock entries. "}; if (tags.empty()) { tmp_title += "All tags"; tmp_description += "All tags"; } else { tmp_description += "Tags: "; } for (const auto &tag : tags) { if (tag != *tags.begin()) { tmp_title += ", "; tmp_description += ", "; } tmp_title += tag; tmp_description += tag; } tmp_description += "."; channel.append_child("title").text() = tmp_title.c_str(); channel.append_child("link").text() = baseurl.c_str(); channel.append_child("description").text() = tmp_description.c_str(); channel.append_child("lastBuildDate") .text() = time::to_string(system_clock::now(), rss_time_format).c_str(); for (const auto &entry : entries) { // If tags are specified and none of them match, go to the next entry. if (!tags.empty()) { // clang-format off if (none_of(entry.tags.begin(), entry.tags.end(), [&tags](const auto &tag) { return find(tags.begin(), tags.end(), tag) != tags.end(); })) { continue; } // clang-format on } auto item{channel.append_child("item")}; item.append_child("title").text() = entry.instance.c_str(); auto item_guid{item.append_child("guid")}; item_guid.append_attribute("isPermaLink") = "false"; item_guid.text() = format("FediBlock: {:s} {:s}", entry.report_time, entry.instance) .c_str(); item.append_child("pubDate").text() = time::to_string(entry.report_time, rss_time_format) .c_str(); item.append_child("link") .text() = format("{:s}#{:s}", baseurl, entry.instance).c_str(); string tmp_item_description{format("

{:s}

" "

Tags: ", cgi::text2html(entry.description))}; for (const auto &tag : entry.tags) { if (tag != *entry.tags.begin()) { tmp_item_description += ", "; } tmp_item_description += tag; } tmp_item_description += "

Receipts:
    "; for (const auto &receipt : entry.receipts) { tmp_item_description += format("
  • " "{0:s}
  • ", receipt); } tmp_item_description += "
"; if (!entry.screenshot_filepaths.empty()) { tmp_item_description += "

Screenshots:
"; for (const auto &screenshot : entry.screenshot_filepaths) { tmp_item_description += format("" " ", baseurl, screenshot); } tmp_item_description += "

"; } auto item_description{item.append_child("description")}; item_description.append_child(pugi::node_cdata) .set_value(tmp_item_description.c_str()); } doc.print(out); } } // namespace FediBlock::rss int main(int /*argc*/, char * /*argv*/[]) { using namespace FediBlock; using namespace FediBlock::rss; using std::cerr; using std::cout; using std::exception; using std::getenv; using std::runtime_error; cout << "Content-Type: application/rss+xml\r\n\r\n"; char *env{getenv("REQUEST_METHOD")}; if (env != nullptr && string(env) != "GET") { return 0; } git::init(true); bool remove_lockfile{false}; try { try { git::update_cached_repo(); remove_lockfile = true; } catch (const runtime_error &e) { cerr << "Warning: " << e.what() << '\n'; // Ignore, use old version of repo. } const auto entries{files::read_json_files(true)}; cgicc::Cgicc cgi; write_rss(cout, entries, cgi::get_array("tags[]", cgi)); } catch (const exception &e) { cerr << "Error: " << e.what() << '\n'; } git::cleanup(remove_lockfile); return 0; }