diff --git a/assets/HypertextPages/chat.html b/assets/HypertextPages/chat.nytl.html similarity index 93% rename from assets/HypertextPages/chat.html rename to assets/HypertextPages/chat.nytl.html index 5d888f7..cc6617b 100644 --- a/assets/HypertextPages/chat.html +++ b/assets/HypertextPages/chat.nytl.html @@ -1,3 +1,4 @@ +{% ELDEF main JSON pres JSON userinfo %} @@ -36,3 +37,4 @@ +{% ENDELDEF %} diff --git a/assets/HypertextPages/list-rooms.html b/assets/HypertextPages/list-rooms.nytl.html similarity index 94% rename from assets/HypertextPages/list-rooms.html rename to assets/HypertextPages/list-rooms.nytl.html index 4a041f5..4a638ce 100644 --- a/assets/HypertextPages/list-rooms.html +++ b/assets/HypertextPages/list-rooms.nytl.html @@ -1,3 +1,4 @@ +{% ELDEF main JSON pres JSON userinfo %} @@ -7,6 +8,7 @@ +{% PUT pass-userinfo userinfo %}

Выберите Чат-Комнату

- + +{% ENDELDEF %} \ No newline at end of file diff --git a/assets/HypertextPages/login.nytl.html b/assets/HypertextPages/login.nytl.html index f4d2caa..700e8b8 100644 --- a/assets/HypertextPages/login.nytl.html +++ b/assets/HypertextPages/login.nytl.html @@ -1,4 +1,4 @@ -{% ELDEF main JSON pres %} +{% ELDEF main JSON pres JSON userinfo %} @@ -10,11 +10,12 @@ + {% PUT pass-userinfo userinfo %}

{% WRITE pres.phr.decl.enter %}

-
- -
+ + +

diff --git a/assets/HypertextPages/pass-userinfo.nytl.html b/assets/HypertextPages/pass-userinfo.nytl.html new file mode 100644 index 0000000..164366f --- /dev/null +++ b/assets/HypertextPages/pass-userinfo.nytl.html @@ -0,0 +1,5 @@ +{% ELDEF main JSON userinfo %} + + + +{% ENDELDEF %} diff --git a/assets/HypertextPages/profile.html b/assets/HypertextPages/profile.nytl.html similarity index 92% rename from assets/HypertextPages/profile.html rename to assets/HypertextPages/profile.nytl.html index 135a34f..5c9505a 100644 --- a/assets/HypertextPages/profile.html +++ b/assets/HypertextPages/profile.nytl.html @@ -1,3 +1,4 @@ +{% ELDEF main JSON pres JSON userinfo %} @@ -9,7 +10,7 @@

Профиль пользователя

- Назад + Назад
@@ -34,4 +35,5 @@ - \ No newline at end of file + +{% ENDELDEF%} diff --git a/assets/HypertextPages/registration.html b/assets/HypertextPages/registration.nytl.html similarity index 93% rename from assets/HypertextPages/registration.html rename to assets/HypertextPages/registration.nytl.html index 9cd441b..e9810ba 100644 --- a/assets/HypertextPages/registration.html +++ b/assets/HypertextPages/registration.nytl.html @@ -1,3 +1,4 @@ +{% ELDEF main JSON pres JSON userinfo %} @@ -22,4 +23,5 @@ - \ No newline at end of file + +{% ENDELDEF %} diff --git a/building/main.cpp b/building/main.cpp index f75a8e2..9b03229 100644 --- a/building/main.cpp +++ b/building/main.cpp @@ -146,6 +146,7 @@ struct CAWebChat { "str_fields.cpp", "find_db.cpp", "sqlite3_wrapper.cpp", + "login_cookie.cpp", }; for (std::string& u: T.units) u = "web_chat/iu9_ca_web_chat_lib/" + u; diff --git a/src/http_server/engine_engine_number_9/http_structures/cookies.cpp b/src/http_server/engine_engine_number_9/http_structures/cookies.cpp index 417c63c..30ee695 100644 --- a/src/http_server/engine_engine_number_9/http_structures/cookies.cpp +++ b/src/http_server/engine_engine_number_9/http_structures/cookies.cpp @@ -27,7 +27,7 @@ namespace een9 { if (ch <= 32 || ch == 0x22 || ch == 0x2C || ch == 0x3B || ch == 0x5C || ch >= 0x7F) return false; } - return !str.empty(); + return true; } std::vector> parseCookieHeader(const std::string &hv) { @@ -37,18 +37,25 @@ namespace een9 { while (hv.size() > pos && isSPACE(hv[pos])) pos++; }; - auto read_to_space_or_ch = [&](char sch) -> std::string { + auto read_to_space_or_eq = [&]() -> std::string { size_t S = pos; - while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != sch) + while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '=') pos++; return hv.substr(S, pos - S); }; + auto read_to_space_or_dq_or_semc = [&]() -> std::string { + size_t S = pos; + while (hv.size() > pos && !isSPACE(hv[pos]) && hv[pos] != '"' && hv[pos] != ';') + pos++; + return hv.substr(S, pos - S); + }; + auto isThis = [&](char ch) -> bool { return pos < hv.size() && hv[pos] == ch; }; skip_ows(); while (pos < hv.size()) { - std::string name_of_pechenye = read_to_space_or_ch('='); + std::string name_of_pechenye = read_to_space_or_eq(); ASSERT(isCookieName(name_of_pechenye), "Incorrect Cookie name"); skip_ows(); ASSERT(isThis('='), "Incorrect Cookie header line, missing ="); @@ -57,11 +64,11 @@ namespace een9 { std::string value_of_pechenye; if (isThis('"')) { pos++; - value_of_pechenye = read_to_space_or_ch('"'); + value_of_pechenye = read_to_space_or_dq_or_semc(); ASSERT(isThis('"'), "Incorrect Cookie header line, missing \""); pos++; } else { - value_of_pechenye = read_to_space_or_ch('"');; + value_of_pechenye = read_to_space_or_dq_or_semc(); } ASSERT(isCookieValue(value_of_pechenye), "Incorrect Cookie value"); if (result.empty()) @@ -73,11 +80,24 @@ namespace een9 { return result; } + std::vector> + findAllClientCookies(const std::vector>& header) { + std::vector> result; + for (const std::pair& line: header) { + if (line.first == "Cookie") { + std::vector> new_cookies = parseCookieHeader(line.second); + result.reserve(result.size() + new_cookies.size()); + result.insert(result.end(), new_cookies.begin(), new_cookies.end()); + } + } + return result; + } + void set_cookie(const std::vector>& new_cookies, - std::vector>& res_header_lines) { + std::vector>& res_header_lines) { for (const std::pair& cookie : new_cookies) { ASSERT_pl(isCookieName(cookie.first) && isCookieValue(cookie.second)); - res_header_lines.emplace_back("Set-Cookie", cookie.first + "=" + cookie.second + ";SameSite=Strict"); + res_header_lines.emplace_back("Set-Cookie", cookie.first + "=\"" + cookie.second + "\";SameSite=Strict;Path=/"); } } } diff --git a/src/http_server/engine_engine_number_9/http_structures/cookies.h b/src/http_server/engine_engine_number_9/http_structures/cookies.h index 59f34f4..4991e3b 100644 --- a/src/http_server/engine_engine_number_9/http_structures/cookies.h +++ b/src/http_server/engine_engine_number_9/http_structures/cookies.h @@ -14,6 +14,10 @@ namespace een9 { /* Throws een9::ServerError on failure */ std::vector> parseCookieHeader(const std::string& hv); + /* Header is header. Throws een9::ServerError on failure. Concatenates output of een9::parseCookieHeader */ + std::vector> + findAllClientCookies(const std::vector>& header); + /* Can throw een9::ServerError (if check for a value failed). res_header_lines is mutated accordingle */ void set_cookie(const std::vector>& new_cookies, std::vector>& res_header_lines); diff --git a/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp b/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp index ca3547d..e194e3f 100644 --- a/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp +++ b/src/http_server/engine_engine_number_9/http_structures/response_gen.cpp @@ -5,7 +5,8 @@ namespace een9 { - std::string form_http_server_response_header(const char* code, const std::map& headers) { + std::string form_http_server_response_header(const char* code, + const std::vector>& headers) { assert(strlen(code) == 3); std::string result = std::string("HTTP/1.0 ") + code + " " + (code[0] < '4' ? "OK" : "ERROR") + "\r\n"; for (auto& p: headers) @@ -13,12 +14,13 @@ namespace een9 { return result; } - std::string form_http_server_reponse_header_only(const char* code, const std::map& headers) { + std::string form_http_server_reponse_header_only(const char* code, + const std::vector>& headers) { return form_http_server_response_header(code, headers) + "\r\n"; } std::string form_http_server_response_with_body(const char* code, - const std::map& headers, + const std::vector>& headers, const std::string& body) { std::string result = form_http_server_response_header(code, headers) @@ -38,4 +40,15 @@ namespace een9 { {"Content-Type", Content_Type} }, body); } + + std::string form_http_server_response_307(const std::string& Location) { + return form_http_server_reponse_header_only("307", {{"Location", Location}}); + } + + std::string form_http_server_response_307_spec_head(const std::string &Location, + const std::vector>& headers) { + std::vector> cp = headers; + cp.emplace_back("Location", Location); + return form_http_server_reponse_header_only("307", cp); + } } diff --git a/src/http_server/engine_engine_number_9/http_structures/response_gen.h b/src/http_server/engine_engine_number_9/http_structures/response_gen.h index 898be0b..0e9034f 100644 --- a/src/http_server/engine_engine_number_9/http_structures/response_gen.h +++ b/src/http_server/engine_engine_number_9/http_structures/response_gen.h @@ -1,21 +1,29 @@ #ifndef ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H #define ENGINE_ENGINE_NUMBER_9_HTTP_STRUCTURES_RESPONSE_GEN_H -#include +#include #include -namespace een9 { - std::string form_http_server_response_header(const char* code, const std::map& headers); - std::string form_http_server_reponse_header_only(const char* code, const std::map& headers); +namespace een9 { + std::string form_http_server_response_header(const char* code, + const std::vector>& headers); + + std::string form_http_server_reponse_header_only(const char* code, + const std::vector>& headers); std::string form_http_server_response_with_body(const char* code, - const std::map& headers, + const std::vector>& headers, const std::string& body); std::string form_http_server_response_200(const std::string& Content_Type, const std::string& body); std::string form_http_server_response_404(const std::string& Content_Type, const std::string& body); + + std::string form_http_server_response_307(const std::string& Location); + + std::string form_http_server_response_307_spec_head(const std::string &Location, + const std::vector>& headers); } #endif diff --git a/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp b/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp index c85b6ba..2c621b1 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/initialize.cpp @@ -35,31 +35,31 @@ namespace iu9cawebchat { * If chat.lastMsgId is NULL, chat is empty * If message.previous is NULL, this message is first in it's chat */ - sqlite_single_statement(conn.hand, "PRAGMA foreign_keys = true"); - sqlite_single_statement(conn.hand, "BEGIN"); - sqlite_single_statement(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); - sqlite_single_statement(conn.hand, "CREATE TABLE `user` (" + sqlite_nooutput(conn.hand, "PRAGMA foreign_keys = true"); + sqlite_nooutput(conn.hand, "BEGIN"); + sqlite_nooutput(conn.hand, "CREATE TABLE `nickname` (`it` TEXT PRIMARY KEY NOT NULL) WITHOUT ROWID"); + sqlite_nooutput(conn.hand, "CREATE TABLE `user` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," "`name` TEXT NOT NULL," "`chatList_HistoryId` INTEGER NOT NULL," "`password` TEXT NOT NULL" ")"); - sqlite_single_statement(conn.hand, "CREATE TABLE `chat` (" + sqlite_nooutput(conn.hand, "CREATE TABLE `chat` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," "`nickname` TEXT UNIQUE REFERENCES `nickname` NOT NULL," "`name` TEXT NOT NULL," "`it_HistoryId` INTEGER NOT NULL," "`lastMsgId` INTEGER REFERENCES `message`" ")"); - sqlite_single_statement(conn.hand, "CREATE TABLE `user_chat_membership` (" + sqlite_nooutput(conn.hand, "CREATE TABLE `user_chat_membership` (" "`userId` INTEGER REFERENCES `user` NOT NULL," "`chatId` INTEGER REFERENCES `chat` NOT NULL," "`user_chatList_IncHistoryId` INTEGER NOT NULL," "`chat_IncHistoryId` INTEGER NOT NULL," "`role` INTEGER NOT NULL" ")"); - sqlite_single_statement(conn.hand, "CREATE TABLE `message` (" + sqlite_nooutput(conn.hand, "CREATE TABLE `message` (" "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," "`chatId` INTEGER REFERENCES `chat` NOT NULL," "`previous` INTEGER REFERENCES `message`," @@ -69,10 +69,10 @@ namespace iu9cawebchat { "`text` TEXT NOT NULL," "`chat_IncHistoryId` INTEGER NOT NULL" ")"); - sqlite_single_statement(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); - sqlite_single_statement(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " + sqlite_nooutput(conn.hand, "INSERT INTO `nickname` VALUES (?1)", {}, {{1, "root"}}); + sqlite_nooutput(conn.hand, "INSERT INTO `user` (`id`, `nickname`, `name`, `chatList_HistoryId`, `password`) VALUES " "(0, ?1, ?2, 0, ?3)", {}, {{1, "root"}, {2, "Rootov Root Rootovich"}, {3, root_pw}}); - sqlite_single_statement(conn.hand, "END"); + sqlite_nooutput(conn.hand, "END"); } } diff --git a/src/web_chat/iu9_ca_web_chat_lib/login_cookie.cpp b/src/web_chat/iu9_ca_web_chat_lib/login_cookie.cpp new file mode 100644 index 0000000..193a8e5 --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/login_cookie.cpp @@ -0,0 +1,89 @@ +#include "login_cookie.h" +#include +#include "str_fields.h" +#include +#include +#include + +namespace iu9cawebchat { + bool is_login_cookie(const std::pair& any_cookie_encoded) { + return een9::beginsWith(any_cookie_encoded.first, "login_") && !any_cookie_encoded.second.empty(); + } + + /* Should be verified with iu9cawebchat::is_login_cookie. Can throw std::exception anyway */ + LoginCookie decode_login_cookie(const std::pair& login_cookie_encoded) { + const std::string& ft = login_cookie_encoded.first; + size_t bg = strlen("login_"); + een9_ASSERT_pl(ft.size() >= bg); + size_t s_ = bg; + while (s_ < ft.size() && ft[s_] != '_') + s_++; + een9_ASSERT_pl(s_ + 1 < ft.size()) + uint64_t sec = std::stoull(ft.substr(bg, s_ - bg)); + uint64_t nsec = std::stoull(ft.substr(s_ + 1, ft.size() - s_ - 1)); + een9_ASSERT_pl(nsec < 1000000000); + const json::JSON cnt = json::parse_str_flawless(base64_decode(login_cookie_encoded.second)); + std::string nickname = cnt[0].g().asString(); + std::string password = cnt[1].g().asString(); + return LoginCookie{{(time_t)sec, (time_t)nsec}, nickname, password}; + } + + LoginCookie create_login_cookie(const std::string& nickname, const std::string& password) { + ns_time moment; + int ret = clock_gettime(CLOCK_REALTIME, &moment); + een9_ASSERT_on_iret(ret, "Can't get time"); + return {moment, nickname, password}; + } + + std::pair encode_login_cookie(const LoginCookie& cookie) { + json::JSON cnt; + cnt[1].g() = cookie.password; + cnt[0].g() = cookie.nickname; + return {"login_" + std::to_string(cookie.login_time.tv_sec) + "_" + std::to_string(cookie.login_time.tv_nsec), + base64_encode(json::generate_str(cnt, json::print_compact))}; + } + + bool login_cookie_age_less(const LoginCookie& A, const LoginCookie& B) { + if (A.login_time.tv_sec < B.login_time.tv_sec) + return true; + if (A.login_time.tv_sec > B.login_time.tv_sec) + return false; + return A.login_time.tv_nsec < B.login_time.tv_nsec; + } + + std::vector select_login_cookies(const std::vector> &cookie_list) { + std::vector needed; + try { + for (const std::pair& C: cookie_list) + if (is_login_cookie(C)) + needed.push_back(decode_login_cookie(C)); + } catch (const std::exception& e) { + return {}; + } + return needed; + } + + size_t select_oldest_login_cookie(const std::vector &login_cokie_list) { + size_t oldest = 0; + for (size_t i = 1; i < login_cokie_list.size(); i++) + if (login_cookie_age_less(login_cokie_list[i], login_cokie_list[oldest])) + oldest = i; + return login_cokie_list.empty() ? 0 : oldest; + } + + void add_set_cookie_headers_to_login(const std::vector &old_login_cookies, + std::vector> &response_headers, const LoginCookie& new_login_cookie) { + add_set_cookie_headers_to_logout(old_login_cookies, response_headers); + een9::set_cookie({encode_login_cookie(new_login_cookie)}, response_headers); + } + + void add_set_cookie_headers_to_logout(const std::vector &old_login_cookies, + std::vector> &response_headers) { + std::vector> anti_cookies(old_login_cookies.size()); + for (size_t i = 0; i < old_login_cookies.size(); i++) { + anti_cookies[i] = {"login_" + std::to_string(old_login_cookies[i].login_time.tv_sec) + + "_" + std::to_string(old_login_cookies[i].login_time.tv_nsec), ""}; + } + een9::set_cookie(anti_cookies, response_headers); + } +} diff --git a/src/web_chat/iu9_ca_web_chat_lib/login_cookie.h b/src/web_chat/iu9_ca_web_chat_lib/login_cookie.h new file mode 100644 index 0000000..487c661 --- /dev/null +++ b/src/web_chat/iu9_ca_web_chat_lib/login_cookie.h @@ -0,0 +1,34 @@ +#ifndef IU9_CA_WEB_CHAT_LIB_LOGIN_COOKIE_H +#define IU9_CA_WEB_CHAT_LIB_LOGIN_COOKIE_H + +#include +#include +#include + +namespace iu9cawebchat { + typedef struct timespec ns_time; + + struct LoginCookie { + ns_time login_time; + std::string nickname; + std::string password; + }; + + bool is_login_cookie(const std::pair& any_cookie_encoded); + LoginCookie decode_login_cookie(const std::pair& login_cookie_encoded); + LoginCookie create_login_cookie(const std::string& nickname, const std::string& password); + std::pair encode_login_cookie(const LoginCookie& cookie); + + /* (incorrect cookie set is treated as unloginned user ) */ + std::vector select_login_cookies(const std::vector> &cookie_list); + size_t select_oldest_login_cookie(const std::vector& login_cokie_list); + + /* Populates response headers */ + void add_set_cookie_headers_to_login(const std::vector& old_login_cookies, + std::vector>& response_headers, const LoginCookie& new_login_cookie); + void add_set_cookie_headers_to_logout(const std::vector& old_login_cookies, + std::vector>& response_headers); + +} + +#endif diff --git a/src/web_chat/iu9_ca_web_chat_lib/run.cpp b/src/web_chat/iu9_ca_web_chat_lib/run.cpp index e43d06d..082c7aa 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/run.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/run.cpp @@ -13,6 +13,8 @@ #include "sqlite3_wrapper.h" #include "str_fields.h" #include "find_db.h" +#include "login_cookie.h" +#include namespace iu9cawebchat { bool termination = false; @@ -64,25 +66,94 @@ namespace iu9cawebchat { nytl::Templater& templater = *wgd.templater; een9::StaticAsset sa; int ret; - auto rteee = [&](const std::string& el_name, bool pass_phr) -> std::string { - std::string page = templater.render(el_name, - pass_phr ? std::vector{&config_presentation} : std::vector{}); + 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") { - return rteee("list-rooms", false); + 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") { - return rteee("login", true); + 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 rteee("chat", false); + return RTEE("chat"); } if (req.uri_path == "/profile") { - return rteee("profile", false); + return RTEE("profile"); } if (req.uri_path == "/registration") { - return rteee("registration", false); + return RTEE("registration"); } /* Trying to interpret request as asset lookup */ ret = samI.get_asset(req.uri_path, sa); @@ -112,7 +183,7 @@ namespace iu9cawebchat { std::string new_password = req.substr(nid + 1); if (!check_password(new_password)) een9_THROW("Bad password"); - sqlite_single_statement(wgd.db->hand, + sqlite_nooutput(wgd.db->hand, "UPDATE `user` SET `password` = ?1 WHERE `id` = 0 ", {}, {{1, new_password}}); return "Successul update"; diff --git a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp index ebbe4e3..f5a6190 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp +++ b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.cpp @@ -16,7 +16,7 @@ namespace iu9cawebchat { if (sqlite3_close(hand) != 0) {abort();} } - void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement, + void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, const std::vector>& int64_binds, const std::vector>& text8_binds) { sqlite3_stmt* stmt_obj = NULL; @@ -26,27 +26,24 @@ namespace iu9cawebchat { een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); } - een9_ASSERT(ret == 0, "Can't compile request expression"); assert(sqlite3_errcode(db_hand) == SQLITE_OK); struct Guard1{sqlite3_stmt*& r; ~Guard1(){if (sqlite3_finalize(r) != 0) {abort();}}} guard1{stmt_obj}; for (const std::pair& bv: int64_binds) { ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second); - een9_ASSERT(ret, "Can't bind to parameter #" + std::to_string(bv.first)); + een9_ASSERT(ret == 0, "sqlite3_bind_int64"); } for (const std::pair& bv: text8_binds) { een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter"); een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge"); ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC); + een9_ASSERT(ret == 0, "sqlite3_bind_text"); } while (true) { ret = sqlite3_step(stmt_obj); if (ret == SQLITE_DONE) break; - if (ret != SQLITE_ROW) { - printf("sqlite_row error!!!\n"); - printf("%s\n", sqlite3_errmsg(db_hand)); - break; - } + if (ret != SQLITE_ROW) + een9_THROW(std::string("sqlite_row ") + sqlite3_errstr(ret)); int cc = sqlite3_column_count(stmt_obj); std::vector types(cc); for (int i = 0; i < cc; i++) { @@ -84,11 +81,79 @@ namespace iu9cawebchat { const unsigned char* text = sqlite3_column_text(stmt_obj, i); een9_ASSERT(text, "oom in sqlite3_column_text"); printf("%s | ", (const char*)text); - // todo: THIS F. B.S. IS NOT SAFE + // todo: print only if string is safe to print } } printf("\n"); } printf("Request steps are done\n"); } + + + SqliteStatement::SqliteStatement(SqliteConnection &connection, const std::string &req_statement, + const std::vector> &int64_binds, + const std::vector> &text8_binds): conn(connection) { + + int ret = sqlite3_prepare_v2(connection.hand, req_statement.c_str(), -1, &stmt_obj, NULL); + if (ret != 0) { + int err_pos = sqlite3_error_offset(connection.hand); + een9_THROW("Compilation of request\n" + req_statement + "\nfailed" + + ((err_pos >= 0) ? " with offset " + std::to_string(err_pos) : "")); + } + try { + for (const std::pair& bv: int64_binds) { + ret = sqlite3_bind_int64(stmt_obj, bv.first, bv.second); + een9_ASSERT(ret == 0, "sqlite3_bind_int64"); + } + for (const std::pair& bv: text8_binds) { + een9_ASSERT(is_orthodox_string(bv.second), "Can't bind this string to parameter"); + een9_ASSERT(bv.second.size() + 1 < INT_MAX, "Ah, oh, senpai, your string is toooo huge"); + ret = sqlite3_bind_text(stmt_obj, bv.first, bv.second.c_str(), (int)bv.second.size(), SQLITE_STATIC); + een9_ASSERT(ret == 0, "sqlite3_bind_text"); + } + } catch (const std::exception& e) { + sqlite3_finalize(stmt_obj); + throw; + } + } + + SqliteStatement::~SqliteStatement() { + sqlite3_finalize(stmt_obj); + } + + int sqlite_stmt_step(SqliteStatement &stmt, + const std::vector> &ret_of_integer_or_null, + const std::vector> &ret_of_text8_or_null) { + int ret = sqlite3_step(stmt.stmt_obj); + if (ret == SQLITE_DONE) + return ret; + een9_ASSERT(ret == SQLITE_ROW, std::string("sqlite3_step ") + sqlite3_errstr(ret)); + int cc = sqlite3_column_count(stmt.stmt_obj); + for (auto& resp: ret_of_integer_or_null) { + if (resp.first >= cc) + een9_THROW("Not enough"); + int type = sqlite3_column_type(stmt.stmt_obj, resp.first); + if (type == SQLITE_INTEGER) { + *resp.second = fsql_integer_or_null{true, (int64_t)sqlite3_column_int64(stmt.stmt_obj, resp.first)}; + } else if (type == SQLITE_NULL) { + *resp.second = fsql_integer_or_null{false, 0}; + } else + een9_THROW("sqlite3_column_type. Incorrect type"); + } + for (auto& resp: ret_of_text8_or_null) { + if (resp.first >= cc) + een9_THROW("Not enough"); + int type = sqlite3_column_type(stmt.stmt_obj, resp.first); + if (type == SQLITE_TEXT) { + /* Hm, yeah, I am reinterpret_casting between char and unsigned char again */ + const unsigned char* text = sqlite3_column_text(stmt.stmt_obj, resp.first); + een9_ASSERT(text, "oom in sqlite3_column_text"); + *resp.second = fsql_text8_or_null{true, reinterpret_cast(text)}; + } else if (type == SQLITE_NULL) { + *resp.second = fsql_text8_or_null{false, ""}; + } else + een9_THROW("sqlite3_column_type. Incorrect type"); + } + return SQLITE_ROW; + } } diff --git a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h index 12b2494..620203f 100644 --- a/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h +++ b/src/web_chat/iu9_ca_web_chat_lib/sqlite3_wrapper.h @@ -15,9 +15,35 @@ namespace iu9cawebchat { ~SqliteConnection(); }; - void sqlite_single_statement(sqlite3* db_hand, const std::string& req_statement, + void sqlite_nooutput(sqlite3* db_hand, const std::string& req_statement, const std::vector>& int64_binds= {}, const std::vector>& text8_binds = {}); + + struct fsql_integer_or_null { + bool exist; + int64_t value; + }; + + struct fsql_text8_or_null { + bool exist; + std::string value; + }; + + struct SqliteStatement { + SqliteConnection& conn; + sqlite3_stmt* stmt_obj = NULL; + SqliteStatement(SqliteConnection& connection, const std::string& req_statement, + const std::vector>& int64_binds= {}, + const std::vector>& text8_binds = {}); + SqliteStatement(SqliteStatement&) = delete; + SqliteStatement& operator=(SqliteStatement&) = delete; + + ~SqliteStatement(); + }; + + int sqlite_stmt_step(SqliteStatement& stmt, + const std::vector>& ret_of_integer_or_null, + const std::vector>& ret_of_text8_or_null); } #endif