#include #include #include "config.h" #include "client.h" #include "hiredis_wrapper.h" #include "servehelper.h" #include "routes/routes.h" #define DOMAIN_RE "(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,}" // https://docs.joinmastodon.org/methods/accounts/#422-unprocessable-entity // https://akko.erincandescent.net/@postmaster-emeritus #define USERNAME_RE "[a-zA-Z0-9\\-_]+" #define ACCT_RE USERNAME_RE "(?:@" DOMAIN_RE ")?" int main(int argc, char** argv) { if (argc != 2) { fprintf(stderr, "Usage: %s \n", argc > 0 ? argv[0] : "coyote"); return 1; } if (setenv("TZ", "UTC", true)) { perror("Failed to set TZ=UTC: setenv()"); return 1; } try { load_config(argv[1]); } catch (const std::exception& e) { fprintf(stderr, "Failed to load config: %s\n", e.what()); return 1; } try { init_redis(); } catch (const std::exception& e) { fprintf(stderr, "Failed to init redis: %s\n", e.what()); return 1; } MastodonClient::init(); atexit(MastodonClient::cleanup); httplib::Server server; server.set_payload_max_length(8192); server.Get("/", home_route); server.Get("/style.css", css_route); server.Get("/favicon.ico", [](const httplib::Request& req, httplib::Response& res) { res.status = 404; serve_error(req, res, "404: Page not found"); }); server.Get("/settings", user_settings_route); server.Post("/settings", user_settings_route); server.Get("/(" DOMAIN_RE ")/@(" ACCT_RE ")(|/with_replies|/media)", user_route); server.Get("/(" DOMAIN_RE ")/users/(" ACCT_RE ")(|/with_replies|/media)", [](const httplib::Request& req, httplib::Response& res) { serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + req.matches.str(3), true); }); server.Get("/(" DOMAIN_RE ")/@" ACCT_RE "/([a-zA-Z0-9]+)", status_route); server.Get("/(" DOMAIN_RE ")/users/(" ACCT_RE ")/statuses/([a-zA-Z0-9]+)", [](const httplib::Request& req, httplib::Response& res) { serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + '/' + req.matches.str(3), true); }); // protect against .. server.Get("/(" DOMAIN_RE ")/tags/(?!\\.|%2E|%2e)(.+)", tags_route); server.Get("/(" DOMAIN_RE ")/about", about_route); server.Get("/(" DOMAIN_RE ")/?", [](const httplib::Request& req, httplib::Response& res) { serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/about", false); }); server.Get("/https://?(.+)", [](const httplib::Request& req, httplib::Response& res) { serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1), true); }); #ifndef NDEBUG server.Get("/debug/exception/known", [](const httplib::Request& req, httplib::Response& res) { throw std::runtime_error("awoo"); }); server.Get("/debug/exception/unknown", [](const httplib::Request& req, httplib::Response& res) { throw "cope"; }); #endif server.Get(".*", [&](const httplib::Request& req, httplib::Response& res) { res.status = 404; serve_error(req, res, "404: Page not found"); }); server.set_exception_handler([&](const httplib::Request& req, httplib::Response& res, std::exception_ptr ep) { res.status = 500; try { std::rethrow_exception(ep); } catch (const std::exception& e) { fprintf(stderr, "Exception thrown on %s: %s\n", req.path.c_str(), e.what()); serve_error(req, res, "500: Internal server error", std::nullopt, e.what()); } catch (...) { fprintf(stderr, "Exception thrown on %s: Unknown exception\n", req.path.c_str()); serve_error(req, res, "500: Internal server error", std::nullopt, "Unknown exception"); } }); if (config.bind_port != 0) { if (!server.bind_to_port(config.bind_host, config.bind_port)) { fprintf(stderr, "Failed to bind to %s:%d\n", config.bind_host.c_str(), config.bind_port); return 1; } } else { int port = server.bind_to_any_port(config.bind_host); if (port == -1) { fprintf(stderr, "Failed to bind to %s:\n", config.bind_host.c_str()); return 1; } config.bind_port = port; } printf("Listening on %s:%d...\n", config.bind_host.c_str(), config.bind_port); if (!server.listen_after_bind()) { fprintf(stderr, "Failed to listen on %s:%d\n", config.bind_host.c_str(), config.bind_port); return 1; } }