#include "actions.h" #include #include #include #include #include #include #include #include #include #include #include "sqlite3_wrapper.h" #include "str_fields.h" #include "find_db.h" #include "login_cookie.h" #include namespace iu9cawebchat { bool termination = false; void sigterm_action(int) { termination = true; } void run_website(const json::JSON& config) { een9_ASSERT(config["assets"].g().isString(), "config[\"assets\"] is not string"); std::string assets_dir = config["assets"].g().asString(); een9_ASSERT(een9::isDirectory(assets_dir), "\"" + assets_dir + "\" is not a directory"); een9::StaticAssetManagerSlaveModule samI; samI.update({ een9::StaticAssetManagerRule{assets_dir + "/css", "/assets/css", {{".css", "text/css"}} }, een9::StaticAssetManagerRule{assets_dir + "/js", "/assets/js", {{".js", "text/javascript"}} }, een9::StaticAssetManagerRule{assets_dir + "/img", "/assets/img", { {".jpg", "image/jpg"}, {".png", "image/png"}, {".svg", "image/svg+xml"} } }, }); const json::JSON& config_presentation = config["presentation"].g(); int64_t slave_number = config["server"]["workers"].g().asInteger().get_int(); een9_ASSERT(slave_number > 0 && slave_number <= 200, "E"); std::string sqlite_db_path; int ret = find_db_sqlite_file_path(config, sqlite_db_path); een9_ASSERT(ret == 0, "Can't find database file"); struct WorkerGuestData { /* Because templaters use libjsonincpp, they can't be READ by two thread simultaneously */ std::unique_ptr templater; std::unique_ptr db; }; std::vector worker_guest_data(slave_number); for (int i = 0; i < slave_number; i++) { worker_guest_data[i].templater = std::make_unique( nytl::TemplaterSettings{nytl::TemplaterDetourRules{assets_dir + "/HypertextPages"}}); worker_guest_data[i].templater->update(); worker_guest_data[i].db = std::make_unique(sqlite_db_path); } een9::MainloopParameters params; params.guest_core = [&samI, &worker_guest_data, config_presentation] (const een9::SlaveTask& task, const een9::ClientRequest& req, een9::worker_id_t worker_id) -> std::string { een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); WorkerGuestData& wgd = worker_guest_data[worker_id]; nytl::Templater& templater = *wgd.templater; een9::StaticAsset sa; int ret; auto find_user_by_credentials = [&](const std::string& nickname, const std::string& password) -> int64_t { SqliteStatement sql_req(*wgd.db, "SELECT `id` FROM `user` WHERE `nickname` = ?1 AND `password` = ?2", {}, {{1, nickname}, {2, password}}); fsql_integer_or_null id_col; int status = sqlite_stmt_step(sql_req, {{0, &id_col}}, {}); if (status == SQLITE_ROW) { een9_ASSERT_pl(id_col.exist & id_col.value >= 0); return id_col.value; } return -1; }; auto find_user_name = [&](int64_t uid) -> std::string { een9_ASSERT(uid >= 0, "Are you crazy?"); SqliteStatement sql_req(*wgd.db, "SELECT `name` FROM `user` WHERE `id` = ?1", {{1, uid}}, {}); fsql_text8_or_null name_col; int status = sqlite_stmt_step(sql_req, {}, {{0, &name_col}}); if (status == SQLITE_ROW) { een9_ASSERT_pl(name_col.exist); return name_col.value; } return ""; }; std::vector> cookies = een9::findAllClientCookies(req.headers); std::vector login_cookies = select_login_cookies(cookies); json::JSON userinfo; userinfo["uid"] = json::JSON("-1"); userinfo["nickname"] = json::JSON(""); userinfo["name"] = json::JSON(""); int64_t logged_in_user = -1; /* Negative means that user is not logged in */ if (!login_cookies.empty()){ size_t oldest_ind = select_oldest_login_cookie(login_cookies); LoginCookie& tried = login_cookies[oldest_ind]; logged_in_user = find_user_by_credentials(tried.nickname, tried.password); if (logged_in_user >= 0) { userinfo["uid"] = json::JSON(std::to_string(logged_in_user)); userinfo["nickname"] = json::JSON(tried.nickname); userinfo["name"] = json::JSON(find_user_name(logged_in_user)); } } auto RTEE = [&](const std::string& el_name) -> std::string { std::string page = templater.render(el_name, {&config_presentation, &userinfo}); return een9::form_http_server_response_200("text/html", page); }; if (req.uri_path == "/" || req.uri_path == "/list-rooms") { printf("DEBUG:::: %d\n", logged_in_user); if (logged_in_user < 0) return een9::form_http_server_response_307("/login"); return RTEE("list-rooms"); } if (req.uri_path == "/login") { if (req.method == "POST") { std::vector> query = een9::split_html_query(req.body); std::string nickname; std::string password; for (const std::pair& cmp: query) { if (cmp.first == "nickname") nickname = cmp.second; if (cmp.first == "password") password = cmp.second; } een9_ASSERT(check_nickname(nickname), "/login/accpet-data rejected impossible nickname"); een9_ASSERT(check_password(password), "/login/accpet-data rejected impossible password"); int64_t uid = find_user_by_credentials(nickname, password); if (uid < 0) { /* todo: Here I need to tell somehow to user (through fancy red box, maybe), that login was incorrect */ return RTEE("login"); } std::vector> response_hlines; LoginCookie new_login_cookie = create_login_cookie(nickname, password); add_set_cookie_headers_to_login(login_cookies, response_hlines, new_login_cookie); return een9::form_http_server_response_307_spec_head("/", response_hlines); } return RTEE("login"); } if (req.uri_path == "/chat") { return RTEE("chat"); } if (req.uri_path == "/profile") { return RTEE("profile"); } if (req.uri_path == "/registration") { return RTEE("registration"); } /* Trying to interpret request as asset lookup */ ret = samI.get_asset(req.uri_path, sa); if (ret >= 0) { return een9::form_http_server_response_200(sa.type, sa.content); } return een9::form_http_server_response_404("text/html", "

Not found!

"); }; params.guest_core_admin_control = [&worker_guest_data] (const een9::SlaveTask& task, const std::string& req, een9::worker_id_t worker_id) -> std::string { een9_ASSERT_pl(0 <= worker_id && worker_id < worker_guest_data.size()); WorkerGuestData& wgd = worker_guest_data[worker_id]; try { if (req == "hello") { return ":0 omg! hiii!! Hewwou :3 !!!!"; } if (req == "8") { termination = true; return "Bye"; } std::string updaterootpw_pref = "updaterootpw"; if (een9::beginsWith(req, "updaterootpw")) { size_t nid = updaterootpw_pref.size(); if (nid >= req.size() || !isSPACE(req[nid])) return "Bad command syntax. Missing whitespace"; std::string new_password = req.substr(nid + 1); if (!check_password(new_password)) een9_THROW("Bad password"); sqlite_nooutput(wgd.db->hand, "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", {}, {{1, new_password}}); return "Successul update"; } return "Incorrect command"; } catch (std::exception& e) { return std::string("Server error\n") + e.what(); } }; params.slave_number = slave_number; een9::SocketAddressParser sap; auto translate_addr_list_conf = [&sap](std::vector& dest, const std::vector& source) { size_t N = source.size(); dest.resize(N); for (size_t i = 0; i < N; i++) { int ret = een9::parse_socket_address(source[i].asString(), dest[i], sap); een9_ASSERT(ret == 0, "Incorrect ear address: " + source[i].asString()); } }; translate_addr_list_conf(params.client_regular_listened, config["server"]["http-listen"].g().asArray()); translate_addr_list_conf(params.admin_control_listened, config["server"]["admin-command-listen"].g().asArray()); signal(SIGINT, sigterm_action); signal(SIGTERM, sigterm_action); een9::electric_boogaloo(params, termination); } }