#include #include #include #include #include #include #include "httplib/httplib.h" #include "bcrypt/bcrypt.h" #include "db.h" #include "post.h" #include "generated/e404.h" #include "generated/header_top.h" #include "generated/header_bottom.h" #include "generated/footer.h" #include "generated/login.h" #include "generated/style.h" #include "generated/script.h" std::optional getLoggedInUser(const httplib::Request& request, Database& database) { if (!request.has_header("Cookie")) { return {}; } std::string cookieHeader = request.get_header_value("Cookie"); const std::string key = "session="; size_t pos = cookieHeader.find(key); if (pos == std::string::npos) { return {}; } pos += key.length(); size_t end = cookieHeader.find(';', pos); std::string token = cookieHeader.substr(pos, end == std::string::npos ? std::string::npos : end - pos); return database.getUserByToken(token); } int main() { const bin2cpp::File& headerTopFile = bin2cpp::getHeader_topHtmlFile(); const bin2cpp::File& headerBottomFile = bin2cpp::getHeader_bottomHtmlFile(); const bin2cpp::File& footerfile = bin2cpp::getFooterHtmlFile(); const bin2cpp::File& loginfile = bin2cpp::getLoginHtmlFile(); const bin2cpp::File& e404file = bin2cpp::getE404HtmlFile(); const bin2cpp::File& stylefile = bin2cpp::getStyleCssFile(); const bin2cpp::File& scriptfile = bin2cpp::getScriptJsFile(); std::string headerTop{headerTopFile.getBuffer(), headerTopFile.getSize()}; std::string headerBottom{headerBottomFile.getBuffer(), headerBottomFile.getSize()}; std::string footer{footerfile.getBuffer(), footerfile.getSize()}; std::string login{loginfile.getBuffer(), loginfile.getSize()}; std::string e404{e404file.getBuffer(), e404file.getSize()}; std::string style{stylefile.getBuffer(), stylefile.getSize()}; std::string script{scriptfile.getBuffer(), scriptfile.getSize()}; Database database{"chookchat.db"}; std::mutex data_mutex; httplib::Server svr; svr.Get("/style.css", [&style](const httplib::Request& request, httplib::Response& response) { response.set_content(style, "text/css"); }); svr.Get("/script.js", [&script](const httplib::Request& request, httplib::Response& response) { response.set_content(script, "text/css"); }); svr.Get("/login", [&login](const httplib::Request& request, httplib::Response& response) { response.set_content(login, "text/html"); }); svr.Get("/login.html", [&login](const httplib::Request& request, httplib::Response& response) { response.set_content(login, "text/html"); }); svr.Post("/login", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::string username = request.form.get_field("username"); std::string password = request.form.get_field("password"); std::lock_guard lock(data_mutex); try { std::optional user = database.getUserByName(username); if (user.has_value()) { if (!bcrypt::validatePassword(password, user->passwordHash)) { response.status = 401; response.set_content("

wrong password lmao

your stupid lol", "text/html"); return; } } else { if (username.empty()) { response.status = 400; response.set_content("

hey you can't have an empty username!!!!1!!!1! >:(

", "text/html"); return; } // register on the fly, same as make_post currently does User newUser{0, username, bcrypt::generateHash(password)}; database.addUser(newUser); user = newUser; } std::optional token = database.createNewToken(user->id); if (!token.has_value()) { response.status = 500; response.set_content("

couldn't create a session, sorry

", "text/html"); return; } // HttpOnly so script.js can't read/leak it, SameSite=Lax so it // isn't sent on cross-site POSTs (basic CSRF mitigation), // Max-Age matches the 30 day expiry stored in the DB response.set_header( "Set-Cookie", "session=" + *token + "; Path=/; HttpOnly; SameSite=Lax; Max-Age=2592000" ); response.set_redirect("/"); } catch (const std::runtime_error& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } }); svr.Get("/", [&headerTop, &headerBottom, &footer, &database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::stringstream ss; ss << headerTop << "" << headerBottom; std::lock_guard lock(data_mutex); try { std::vector posts = database.getTopPosts(100); for (const auto& post : posts) { ss << post.genHtml(); } ss << footer; response.set_content(ss.str(), "text/html"); } catch (const std::runtime_error& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } }); svr.Get("/posts/:id", [&headerTop, &headerBottom, &footer, &e404, &database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::string postId = request.path_params.at("id"); try { uint64_t postIdNum = std::stoll(postId); std::optional post = database.getPost(postIdNum); if (!post.has_value()) { response.status = 404; response.set_content(e404, "text/html"); return; } std::stringstream ss; ss << headerTop << post->genHtmlMeta() << headerBottom << post->genHtml() << footer; response.set_content(ss.str(), "text/html"); } catch (const std::runtime_error& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } catch (const std::exception& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } }); svr.Post("/make_post", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::string username = request.get_param_value("username"); std::string password = request.get_param_value("password"); std::string post = request.get_param_value("post"); std::lock_guard lock(data_mutex); try { uint64_t userId = 0; std::optional user = database.getUserByName(username); if (user.has_value()) { if (!bcrypt::validatePassword(password, user->passwordHash)) { // noooo wrong password response.set_content("

wrong password lmao

", "text/html"); return; } userId = user->id; } else { if (username.empty()) { response.set_content("

hey you can't have an empty username!!!!1!!!1! >:(

", "text/html"); return; } // create user User newUser{0, username, bcrypt::generateHash(password)}; database.addUser(newUser); userId = newUser.id; } // and now we add their post database.addPost(Post(post, userId)); response.set_redirect("/"); } catch (const std::runtime_error& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } }); svr.Post("/like", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { try { std::string username = request.form.get_field("username"); std::string password = request.form.get_field("password"); std::string post = request.form.get_field("post"); std::lock_guard lock(data_mutex); uint64_t postNum = std::stoull(post); database.addLike(postNum, 0); } catch (const std::runtime_error& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } catch (const std::exception& e) { response.status = 500; response.set_content("

there was an error :( it is: " + std::string(e.what()) + "

", "text/html"); } }); svr.Get("/me", [&database, &data_mutex](const httplib::Request& request, httplib::Response& response) { std::optional user = getLoggedInUser(request, database); if (!user.has_value()) { response.set_header("X-Logged-In", "false"); return; } response.set_header("X-Logged-In", "false"); response.set_header("X-Username", user->name); }); svr.listen("0.0.0.0", 8080); }