2023-11-22 08:39:24 +00:00
|
|
|
#include <cstdio>
|
|
|
|
#include <httplib/httplib.h>
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "client.h"
|
2023-11-25 03:39:35 +00:00
|
|
|
#include "hiredis_wrapper.h"
|
2023-11-22 08:39:24 +00:00
|
|
|
#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
|
|
|
|
#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 <path/to/config/file.json>\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;
|
|
|
|
}
|
2023-11-25 03:39:35 +00:00
|
|
|
try {
|
|
|
|
init_redis();
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
fprintf(stderr, "Failed to init redis: %s\n", e.what());
|
|
|
|
return 1;
|
|
|
|
}
|
2023-11-22 08:39:24 +00:00
|
|
|
|
|
|
|
MastodonClient::init();
|
|
|
|
atexit(MastodonClient::cleanup);
|
|
|
|
|
|
|
|
httplib::Server server;
|
2023-11-30 05:33:17 +00:00
|
|
|
server.set_payload_max_length(8192);
|
2023-11-22 08:39:24 +00:00
|
|
|
|
2023-11-24 04:29:33 +00:00
|
|
|
server.Get("/", home_route);
|
2023-11-22 08:39:24 +00:00
|
|
|
server.Get("/style.css", css_route);
|
2023-11-30 05:33:17 +00:00
|
|
|
server.Get("/settings", user_settings_route);
|
|
|
|
server.Post("/settings", user_settings_route);
|
|
|
|
|
2023-11-22 08:39:24 +00:00
|
|
|
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) {
|
2023-11-23 23:59:22 +00:00
|
|
|
serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + req.matches.str(3), true);
|
2023-11-22 08:39:24 +00:00
|
|
|
});
|
|
|
|
|
2023-11-29 04:31:26 +00:00
|
|
|
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) {
|
2023-11-23 23:59:22 +00:00
|
|
|
serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1) + "/@" + req.matches.str(2) + '/' + req.matches.str(3), true);
|
|
|
|
});
|
2023-11-23 06:05:17 +00:00
|
|
|
|
2023-11-24 11:43:53 +00:00
|
|
|
// protect against ..
|
|
|
|
server.Get("/(" DOMAIN_RE ")/tags/(?!\\.|%2E|%2e)(.+)", tags_route);
|
|
|
|
|
2023-11-24 12:46:00 +00:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
|
2023-11-23 23:59:22 +00:00
|
|
|
server.Get("/https://?(.+)", [](const httplib::Request& req, httplib::Response& res) {
|
|
|
|
serve_redirect(req, res, get_origin(req) + '/' + req.matches.str(1), true);
|
2023-11-23 06:05:17 +00:00
|
|
|
});
|
|
|
|
|
2023-11-22 08:39:24 +00:00
|
|
|
#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:<any>\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;
|
|
|
|
}
|
|
|
|
}
|