Refit forum_user_handler to use sql prepared statements

This commit is contained in:
loonycyborg 2017-01-28 15:04:50 +03:00
parent e158c63a95
commit 567dba1ae4
3 changed files with 204 additions and 66 deletions

View file

@ -15,6 +15,7 @@
#ifdef HAVE_MYSQLPP #ifdef HAVE_MYSQLPP
#include "server/forum_user_handler.hpp" #include "server/forum_user_handler.hpp"
#include "server/mysql_prepared_statement.ipp"
#include "hash.hpp" #include "hash.hpp"
#include "log.hpp" #include "log.hpp"
#include "config.hpp" #include "config.hpp"
@ -115,9 +116,8 @@ bool fuh::user_exists(const std::string& name) {
// Make a test query for this username // Make a test query for this username
try { try {
mysql_result res = db_query("SELECT username FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER('" + name + "')"); return !prepared_statement<std::string>(conn, "SELECT username FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER(?)", name).empty();
return mysql_fetch_row(res.get()); } catch (sql_error& e) {
} catch (error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl; ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
// If the database is down just let all usernames log in // If the database is down just let all usernames log in
return false; return false;
@ -126,9 +126,9 @@ bool fuh::user_exists(const std::string& name) {
bool fuh::user_is_active(const std::string& name) { bool fuh::user_is_active(const std::string& name) {
try { try {
int user_type = std::stoi(get_detail_for_user(name, "user_type")); int user_type = get_detail_for_user<int>(name, "user_type");
return user_type != USER_INACTIVE && user_type != USER_IGNORE; return user_type != USER_INACTIVE && user_type != USER_IGNORE;
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not retrieve user type for user '" << name << "' :" << e.message << std::endl; ERR_UH << "Could not retrieve user type for user '" << name << "' :" << e.message << std::endl;
return false; return false;
} }
@ -139,8 +139,8 @@ bool fuh::user_is_moderator(const std::string& name) {
if(!user_exists(name)) return false; if(!user_exists(name)) return false;
try { try {
return get_writable_detail_for_user(name, "user_is_moderator") == "1"; return get_writable_detail_for_user<int>(name, "user_is_moderator") == 1;
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not query user_is_moderator for user '" << name << "' :" << e.message << std::endl; ERR_UH << "Could not query user_is_moderator for user '" << name << "' :" << e.message << std::endl;
// If the database is down mark nobody as a mod // If the database is down mark nobody as a mod
return false; return false;
@ -152,8 +152,8 @@ void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
if(!user_exists(name)) return; if(!user_exists(name)) return;
try { try {
write_detail(name, "user_is_moderator", is_moderator ? "1" : "0"); write_detail(name, "user_is_moderator", int(is_moderator));
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not set is_moderator for user '" << name << "' :" << e.message << std::endl; ERR_UH << "Could not set is_moderator for user '" << name << "' :" << e.message << std::endl;
} }
} }
@ -201,8 +201,8 @@ std::string fuh::get_valid_details() {
std::string fuh::get_hash(const std::string& user) { std::string fuh::get_hash(const std::string& user) {
try { try {
return get_detail_for_user(user, "user_password"); return get_detail_for_user<std::string>(user, "user_password");
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not retrieve password for user '" << user << "' :" << e.message << std::endl; ERR_UH << "Could not retrieve password for user '" << user << "' :" << e.message << std::endl;
return ""; return "";
} }
@ -210,8 +210,8 @@ std::string fuh::get_hash(const std::string& user) {
std::string fuh::get_mail(const std::string& user) { std::string fuh::get_mail(const std::string& user) {
try { try {
return get_detail_for_user(user, "user_email"); return get_detail_for_user<std::string>(user, "user_email");
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not retrieve email for user '" << user << "' :" << e.message << std::endl; ERR_UH << "Could not retrieve email for user '" << user << "' :" << e.message << std::endl;
return ""; return "";
} }
@ -219,9 +219,9 @@ std::string fuh::get_mail(const std::string& user) {
time_t fuh::get_lastlogin(const std::string& user) { time_t fuh::get_lastlogin(const std::string& user) {
try { try {
int time_int = std::stoi(get_writable_detail_for_user(user, "user_lastvisit")); int time_int = get_writable_detail_for_user<int>(user, "user_lastvisit");
return time_t(time_int); return time_t(time_int);
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not retrieve last visit for user '" << user << "' :" << e.message << std::endl; ERR_UH << "Could not retrieve last visit for user '" << user << "' :" << e.message << std::endl;
return time_t(0); return time_t(0);
} }
@ -229,9 +229,9 @@ time_t fuh::get_lastlogin(const std::string& user) {
time_t fuh::get_registrationdate(const std::string& user) { time_t fuh::get_registrationdate(const std::string& user) {
try { try {
int time_int = std::stoi(get_detail_for_user(user, "user_regdate")); int time_int = get_detail_for_user<int>(user, "user_regdate");
return time_t(time_int); return time_t(time_int);
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not retrieve registration date for user '" << user << "' :" << e.message << std::endl; ERR_UH << "Could not retrieve registration date for user '" << user << "' :" << e.message << std::endl;
return time_t(0); return time_t(0);
} }
@ -239,58 +239,38 @@ time_t fuh::get_registrationdate(const std::string& user) {
void fuh::set_lastlogin(const std::string& user, const time_t& lastlogin) { void fuh::set_lastlogin(const std::string& user, const time_t& lastlogin) {
std::stringstream ss;
ss << lastlogin;
try { try {
write_detail(user, "user_lastvisit", ss.str()); write_detail(user, "user_lastvisit", int(lastlogin));
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not set last visit for user '" << user << "' :" << e.message << std::endl; ERR_UH << "Could not set last visit for user '" << user << "' :" << e.message << std::endl;
} }
} }
fuh::mysql_result fuh::db_query(const std::string& sql) { template<typename T>
if(mysql_query(conn, sql.c_str())) { T fuh::get_detail_for_user(const std::string& name, const std::string& detail) {
WRN_UH << "not connected to database, reconnecting..." << std::endl; return prepared_statement<T>(conn,
//Try to reconnect and execute query again "SELECT " + detail + " FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER(?)",
if(!mysql_real_connect(conn, db_host_.c_str(), db_user_.c_str(), db_password_.c_str(), db_name_.c_str(), 0, nullptr, 0) name);
|| mysql_query(conn, sql.c_str())) {
ERR_UH << "Could not connect to database: " << mysql_errno(conn) << ": " << mysql_error(conn) << std::endl;
throw error("Error querying database.");
}
}
return mysql_result(mysql_store_result(conn), mysql_free_result);
} }
std::string fuh::db_query_to_string(const std::string& sql) { template<typename T>
mysql_result res = db_query(sql); T fuh::get_writable_detail_for_user(const std::string& name, const std::string& detail) {
MYSQL_ROW row = mysql_fetch_row(res.get()); if(!extra_row_exists(name)) throw sql_error("row doesn't exist");
if(row == NULL) return prepared_statement<T>(conn,
throw error("query returned no rows"); "SELECT " + detail + " FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER(?)",
if(row[0] == NULL) name);
throw error("got null value from the database");
return std::string(row[0]);
} }
template<typename T>
std::string fuh::get_detail_for_user(const std::string& name, const std::string& detail) { void fuh::write_detail(const std::string& name, const std::string& detail, T&& value) {
return db_query_to_string("SELECT " + detail + " FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
}
std::string fuh::get_writable_detail_for_user(const std::string& name, const std::string& detail) {
if(!extra_row_exists(name)) throw error("row doesn't exist");
return db_query_to_string("SELECT " + detail + " FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
}
void fuh::write_detail(const std::string& name, const std::string& detail, const std::string& value) {
try { try {
// Check if we do already have a row for this user in the extra table // Check if we do already have a row for this user in the extra table
if(!extra_row_exists(name)) { if(!extra_row_exists(name)) {
// If not create the row // If not create the row
db_query("INSERT INTO " + db_extra_table_ + " VALUES('" + name + "','" + value + "','0')"); prepared_statement<void>(conn, "INSERT INTO " + db_extra_table_ + " VALUES(?,?,'0')", name, std::forward<T>(value));
} }
db_query("UPDATE " + db_extra_table_ + " SET " + detail + "='" + value + "' WHERE UPPER(username)=UPPER('" + name + "')"); prepared_statement<void>(conn, "UPDATE " + db_extra_table_ + " SET " + detail + "=? WHERE UPPER(username)=UPPER(?)", std::forward<T>(value), name);
} catch (error& e) { } catch (sql_error& e) {
ERR_UH << "Could not set detail for user '" << name << "': " << e.message << std::endl; ERR_UH << "Could not set detail for user '" << name << "': " << e.message << std::endl;
} }
} }
@ -299,9 +279,8 @@ bool fuh::extra_row_exists(const std::string& name) {
// Make a test query for this username // Make a test query for this username
try { try {
mysql_result res = db_query("SELECT username FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER('" + name + "')"); return !prepared_statement<std::string>(conn, "SELECT username FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER(?)", name).empty();
return mysql_fetch_row(res.get()); } catch (sql_error& e) {
} catch (error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl; ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
return false; return false;
} }

View file

@ -96,19 +96,17 @@ class fuh : public user_handler {
typedef std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> mysql_result; typedef std::unique_ptr<MYSQL_RES, decltype(&mysql_free_result)> mysql_result;
// Throws user_handler::error
mysql_result db_query(const std::string& query);
// Throws user_handler::error via db_query()
std::string db_query_to_string(const std::string& query);
MYSQL *conn; MYSQL *conn;
// Query a detail for a particular user from the database // Query a detail for a particular user from the database
std::string get_detail_for_user(const std::string& name, const std::string& detail); template<typename T>
std::string get_writable_detail_for_user(const std::string& name, const std::string& detail); T get_detail_for_user(const std::string& name, const std::string& detail);
template<typename T>
T get_writable_detail_for_user(const std::string& name, const std::string& detail);
// Write something to the write table // Write something to the write table
void write_detail(const std::string& name, const std::string& detail, const std::string& value); template<typename T>
void write_detail(const std::string& name, const std::string& detail, T&& value);
// Same as user_exists() but checks if we have a row for this user in the extra table // Same as user_exists() but checks if we have a row for this user in the extra table
bool extra_row_exists(const std::string& name); bool extra_row_exists(const std::string& name);

View file

@ -0,0 +1,161 @@
/*
Copyright (C) 2016 by Sergey Popov <loonycyborg@gmail.com>
Part of the Battle for Wesnoth Project http://www.wesnoth.org
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License 2
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef MYSQL_PREPARED_STATEMENT_IPP
#define MYSQL_PREPARED_STATEMENT_IPP
#include <array>
#include <utility>
#include <string>
#include <string.h>
#include <iostream>
#include <mysql/mysql.h>
#include "exceptions.hpp"
struct sql_error : public game::error
{
sql_error(const std::string& message) : game::error(message) {}
};
MYSQL_BIND make_bind(const std::string& str, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_STRING;
result.buffer = const_cast<void*>(static_cast<const void*>(str.c_str()));
result.buffer_length = str.size();
result.is_unsigned = 0;
result.is_null = is_null;
result.length = 0;
return result;
}
MYSQL_BIND make_bind(char* str, std::size_t* len, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_STRING;
result.buffer = static_cast<void*>(str);
result.buffer_length = *len;
result.is_unsigned = 0;
result.is_null = is_null;
result.length = len;
return result;
}
MYSQL_BIND make_bind(int& i, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_LONG;
result.buffer = static_cast<void*>(&i);
result.is_unsigned = 0;
result.is_null = is_null;
return result;
}
MYSQL_BIND make_bind(int&& i, my_bool* is_null = 0)
{
MYSQL_BIND result;
memset(&result, 0, sizeof (MYSQL_BIND));
result.buffer_type = MYSQL_TYPE_LONG;
result.buffer = static_cast<void*>(&i);
result.is_unsigned = 0;
result.is_null = is_null;
return result;
}
template<typename... Args> constexpr auto make_binds(Args&&... args)
-> std::array<MYSQL_BIND, sizeof...(Args)>
{
return { (make_bind(std::forward<Args>(args)))... };
}
template<typename T> T fetch_result(MYSQL_STMT* stmt);
template<> std::string fetch_result<std::string>(MYSQL_STMT* stmt)
{
char* buf = new char[200];
std::size_t len = 200;
my_bool is_null;
MYSQL_BIND result_bind[1] = { make_bind(buf, &len, &is_null) };
if(mysql_stmt_bind_result(stmt, result_bind) != 0)
throw sql_error(mysql_stmt_error(stmt));
int res = mysql_stmt_fetch(stmt);
if(res == MYSQL_NO_DATA)
throw sql_error("no data returned");
if(is_null)
throw sql_error("null value returned");
if(res != 0)
throw sql_error(mysql_stmt_error(stmt));
mysql_stmt_free_result(stmt);
mysql_stmt_close(stmt);
std::cout << "Result: " << buf << std::endl;
return buf;
}
template<> int fetch_result<int>(MYSQL_STMT* stmt)
{
int result;
my_bool is_null;
MYSQL_BIND result_bind[1] = { make_bind(result, &is_null) };
if(mysql_stmt_bind_result(stmt, result_bind) != 0)
throw sql_error(mysql_stmt_error(stmt));
int res = mysql_stmt_fetch(stmt);
if(res == MYSQL_NO_DATA)
throw sql_error("no data returned");
if(is_null)
throw sql_error("null value returned");
if(res != 0)
throw sql_error(mysql_stmt_error(stmt));
mysql_stmt_free_result(stmt);
mysql_stmt_close(stmt);
std::cout << "Result: " << result << std::endl;
return result;
}
template<> void fetch_result<void>(MYSQL_STMT*)
{
}
template<typename R, typename... Args>
R prepared_statement(MYSQL* conn, const std::string& sql, Args&&... args)
{
MYSQL_STMT* stmt;
auto arg_binds = make_binds(args...);
stmt = mysql_stmt_init(conn);
if(stmt == NULL)
throw sql_error("mysql_stmt_init failed");
if(mysql_stmt_prepare(stmt, sql.c_str(), sql.size()) != 0)
throw sql_error(mysql_stmt_error(stmt));
if(mysql_stmt_bind_param(stmt, arg_binds.data()) != 0)
throw sql_error(mysql_stmt_error(stmt));
if(mysql_stmt_execute(stmt) != 0)
throw sql_error(mysql_stmt_error(stmt));
std::cout << "SQL: " << sql << std::endl;
return fetch_result<R>(stmt);
}
#endif // MYSQL_PREPARED_STATEMENT_IPP