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
#include "server/forum_user_handler.hpp"
#include "server/mysql_prepared_statement.ipp"
#include "hash.hpp"
#include "log.hpp"
#include "config.hpp"
@ -115,9 +116,8 @@ bool fuh::user_exists(const std::string& name) {
// Make a test query for this username
try {
mysql_result res = db_query("SELECT username FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
return mysql_fetch_row(res.get());
} catch (error& e) {
return !prepared_statement<std::string>(conn, "SELECT username FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER(?)", name).empty();
} catch (sql_error& e) {
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
return false;
@ -126,9 +126,9 @@ bool fuh::user_exists(const std::string& name) {
bool fuh::user_is_active(const std::string& name) {
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;
} catch (error& e) {
} catch (sql_error& e) {
ERR_UH << "Could not retrieve user type for user '" << name << "' :" << e.message << std::endl;
return false;
}
@ -139,8 +139,8 @@ bool fuh::user_is_moderator(const std::string& name) {
if(!user_exists(name)) return false;
try {
return get_writable_detail_for_user(name, "user_is_moderator") == "1";
} catch (error& e) {
return get_writable_detail_for_user<int>(name, "user_is_moderator") == 1;
} catch (sql_error& e) {
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
return false;
@ -152,8 +152,8 @@ void fuh::set_is_moderator(const std::string& name, const bool& is_moderator) {
if(!user_exists(name)) return;
try {
write_detail(name, "user_is_moderator", is_moderator ? "1" : "0");
} catch (error& e) {
write_detail(name, "user_is_moderator", int(is_moderator));
} catch (sql_error& e) {
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) {
try {
return get_detail_for_user(user, "user_password");
} catch (error& e) {
return get_detail_for_user<std::string>(user, "user_password");
} catch (sql_error& e) {
ERR_UH << "Could not retrieve password for user '" << user << "' :" << e.message << std::endl;
return "";
}
@ -210,8 +210,8 @@ std::string fuh::get_hash(const std::string& user) {
std::string fuh::get_mail(const std::string& user) {
try {
return get_detail_for_user(user, "user_email");
} catch (error& e) {
return get_detail_for_user<std::string>(user, "user_email");
} catch (sql_error& e) {
ERR_UH << "Could not retrieve email for user '" << user << "' :" << e.message << std::endl;
return "";
}
@ -219,9 +219,9 @@ std::string fuh::get_mail(const std::string& user) {
time_t fuh::get_lastlogin(const std::string& user) {
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);
} catch (error& e) {
} catch (sql_error& e) {
ERR_UH << "Could not retrieve last visit for user '" << user << "' :" << e.message << std::endl;
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) {
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);
} catch (error& e) {
} catch (sql_error& e) {
ERR_UH << "Could not retrieve registration date for user '" << user << "' :" << e.message << std::endl;
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) {
std::stringstream ss;
ss << lastlogin;
try {
write_detail(user, "user_lastvisit", ss.str());
} catch (error& e) {
write_detail(user, "user_lastvisit", int(lastlogin));
} catch (sql_error& e) {
ERR_UH << "Could not set last visit for user '" << user << "' :" << e.message << std::endl;
}
}
fuh::mysql_result fuh::db_query(const std::string& sql) {
if(mysql_query(conn, sql.c_str())) {
WRN_UH << "not connected to database, reconnecting..." << std::endl;
//Try to reconnect and execute query again
if(!mysql_real_connect(conn, db_host_.c_str(), db_user_.c_str(), db_password_.c_str(), db_name_.c_str(), 0, nullptr, 0)
|| 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);
template<typename T>
T fuh::get_detail_for_user(const std::string& name, const std::string& detail) {
return prepared_statement<T>(conn,
"SELECT " + detail + " FROM " + db_users_table_ + " WHERE UPPER(username)=UPPER(?)",
name);
}
std::string fuh::db_query_to_string(const std::string& sql) {
mysql_result res = db_query(sql);
MYSQL_ROW row = mysql_fetch_row(res.get());
if(row == NULL)
throw error("query returned no rows");
if(row[0] == NULL)
throw error("got null value from the database");
return std::string(row[0]);
template<typename T>
T fuh::get_writable_detail_for_user(const std::string& name, const std::string& detail) {
if(!extra_row_exists(name)) throw sql_error("row doesn't exist");
return prepared_statement<T>(conn,
"SELECT " + detail + " FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER(?)",
name);
}
std::string fuh::get_detail_for_user(const std::string& name, const std::string& detail) {
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) {
template<typename T>
void fuh::write_detail(const std::string& name, const std::string& detail, T&& value) {
try {
// Check if we do already have a row for this user in the extra table
if(!extra_row_exists(name)) {
// 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 + "')");
} catch (error& e) {
prepared_statement<void>(conn, "UPDATE " + db_extra_table_ + " SET " + detail + "=? WHERE UPPER(username)=UPPER(?)", std::forward<T>(value), name);
} catch (sql_error& e) {
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
try {
mysql_result res = db_query("SELECT username FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER('" + name + "')");
return mysql_fetch_row(res.get());
} catch (error& e) {
return !prepared_statement<std::string>(conn, "SELECT username FROM " + db_extra_table_ + " WHERE UPPER(username)=UPPER(?)", name).empty();
} catch (sql_error& e) {
ERR_UH << "Could not execute test query for user '" << name << "' :" << e.message << std::endl;
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;
// 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;
// Query a detail for a particular user from the database
std::string get_detail_for_user(const std::string& name, const std::string& detail);
std::string get_writable_detail_for_user(const std::string& name, const std::string& detail);
template<typename T>
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
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
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