742 lines
31 KiB
C++
742 lines
31 KiB
C++
#include <string>
|
|
#include <WS2tcpip.h>
|
|
#include <WinSock2.h>
|
|
#include <windows.h>
|
|
#include <iostream>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <random>
|
|
#include <sstream>
|
|
#include <fstream>
|
|
#include <openssl\\rand.h>
|
|
#include <sqlite3.h>
|
|
#include <filesystem>
|
|
#include <mutex>
|
|
#include "Modules\\Logger.h"
|
|
#include "Modules\\inicpp.hpp"
|
|
#include "Modules\\dbActions.h"
|
|
// #include "Modules/serverFrontEndBridge.h"
|
|
//#include "Modules/datapackProcess.h"
|
|
#pragma comment(lib, "ws2_32.lib")
|
|
#pragma comment(lib, "sqlite3.lib")
|
|
|
|
using inicpp::ini;
|
|
using inicpp::IniManager;
|
|
using std::string;
|
|
using std::to_string;
|
|
using std::thread;
|
|
using std::vector;
|
|
using std::mt19937;
|
|
using std::random_device;
|
|
using std::uniform_int_distribution;
|
|
using std::filesystem::create_hard_link;
|
|
using std::filesystem::read_symlink;
|
|
using std::stoi;
|
|
|
|
int isDebug = 1;
|
|
string db_path = ".\\database.db";
|
|
sqlite3* db;
|
|
int tokenLength = 30; // Length of user token
|
|
|
|
struct structUserInfo {
|
|
int ID;
|
|
string name[65525];
|
|
string passwd[65525];
|
|
int port_IDMap[65525];
|
|
int ID_PortMap[65525];
|
|
string ID_TokenMap[65525];
|
|
} userInfo;
|
|
|
|
struct structIpAddr {
|
|
const char* server_cstr = "127.0.0.1";
|
|
struct in_addr server;
|
|
vector<char> server_vec;
|
|
int listenPort = 23000;
|
|
} ipAddr;
|
|
|
|
struct publicSock {
|
|
SOCKET client;
|
|
SOCKET service;
|
|
SOCKET sysAdmin;
|
|
} globalSock;
|
|
|
|
int serverInit_Sockets(); // Initialize socket
|
|
void userExit(int userID, int clientPort); // Process user exit event
|
|
string getTextMiddle(string source, string left, string right); // Get a text from a text
|
|
bool tcpSend(SOCKET sock, string str);
|
|
bool verifyTimestamp(const string& timestampStr);
|
|
string getRandomStr(const int len);
|
|
int serverInit_SQLite_Open();
|
|
int serverInit_Sockets();
|
|
|
|
// UTF-8 encoding helper functions
|
|
bool isValidUtf8(const char* str, size_t length);
|
|
string ensureUtf8(const string& str);
|
|
|
|
int main() {
|
|
postLog("[System] Starting Teacher Salary Management Server...", 1);
|
|
if (serverInit_SQLite_Open() != 0) {
|
|
postLog("[System] Failed to initialize SQLite database.", 4);
|
|
return 1;
|
|
}
|
|
serverInit_Sockets();
|
|
|
|
closesocket(globalSock.client);
|
|
WSACleanup();
|
|
}
|
|
|
|
// Check if the given string is valid UTF-8
|
|
bool isValidUtf8(const char* str, size_t length) {
|
|
size_t i = 0;
|
|
while (i < length) {
|
|
unsigned char c = static_cast<unsigned char>(str[i]);
|
|
if (c <= 0x7F) {
|
|
i++;
|
|
}
|
|
else if (c >= 0xC2 && c <= 0xDF) {
|
|
if (i + 1 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80) return false;
|
|
i += 2;
|
|
}
|
|
else if (c == 0xE0) {
|
|
if (i + 2 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
static_cast<unsigned char>(str[i + 1]) < 0xA0 ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80) return false;
|
|
i += 3;
|
|
}
|
|
else if (c >= 0xE1 && c <= 0xEC) {
|
|
if (i + 2 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80) return false;
|
|
i += 3;
|
|
}
|
|
else if (c == 0xED) {
|
|
if (i + 2 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
static_cast<unsigned char>(str[i + 1]) > 0x9F ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80) return false;
|
|
i += 3;
|
|
}
|
|
else if (c >= 0xEE && c <= 0xEF) {
|
|
if (i + 2 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80) return false;
|
|
i += 3;
|
|
}
|
|
else if (c == 0xF0) {
|
|
if (i + 3 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
static_cast<unsigned char>(str[i + 1]) < 0x90 ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80 ||
|
|
(static_cast<unsigned char>(str[i + 3]) & 0xC0) != 0x80) return false;
|
|
i += 4;
|
|
}
|
|
else if (c >= 0xF1 && c <= 0xF3) {
|
|
if (i + 3 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80 ||
|
|
(static_cast<unsigned char>(str[i + 3]) & 0xC0) != 0x80) return false;
|
|
i += 4;
|
|
}
|
|
else if (c == 0xF4) {
|
|
if (i + 3 >= length || (static_cast<unsigned char>(str[i + 1]) & 0xC0) != 0x80 ||
|
|
static_cast<unsigned char>(str[i + 1]) > 0x8F ||
|
|
(static_cast<unsigned char>(str[i + 2]) & 0xC0) != 0x80 ||
|
|
(static_cast<unsigned char>(str[i + 3]) & 0xC0) != 0x80) return false;
|
|
i += 4;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Ensure string is valid UTF-8, replace invalid sequences if needed
|
|
string ensureUtf8(const string& str) {
|
|
if (str.empty()) return str;
|
|
|
|
// Check if the string is already valid UTF-8
|
|
if (isValidUtf8(str.c_str(), str.length())) {
|
|
return str;
|
|
}
|
|
|
|
// If invalid UTF-8, try to convert from common encodings
|
|
// This is a simple fallback for invalid UTF-8
|
|
string result;
|
|
for (size_t i = 0; i < str.length(); i++) {
|
|
unsigned char c = static_cast<unsigned char>(str[i]);
|
|
if (c <= 0x7F) {
|
|
result += str[i];
|
|
}
|
|
else {
|
|
// Replace non-ASCII characters with '?' for simplicity
|
|
// In a production system, you'd want proper encoding detection and conversion
|
|
result += '?';
|
|
}
|
|
}
|
|
|
|
postLog("[Encoding] Invalid UTF-8 detected, replaced non-ASCII characters", 2);
|
|
return result;
|
|
}
|
|
|
|
void handle_client(SOCKET sock, string clientIP, int clientPort) {
|
|
constexpr int MAX_USERS = 65525;
|
|
auto inRange = [&](int idx) { return idx >= 0 && idx < MAX_USERS; };
|
|
|
|
char buffer[1024];
|
|
int bytesReceived;
|
|
postLog("[Socket] New connection from " + clientIP + ":" + to_string(clientPort), 0);
|
|
|
|
bool keepRunning = true;
|
|
while (keepRunning) {
|
|
bytesReceived = recv(sock, buffer, sizeof(buffer), 0);
|
|
|
|
if (bytesReceived > 0) {
|
|
// Normal data received
|
|
string received(buffer, bytesReceived);
|
|
if (received.empty()) {
|
|
postLog("[Socket] Received empty payload, closing socket.", 3);
|
|
break;
|
|
}
|
|
|
|
// Ensure received data is valid UTF-8
|
|
string processedReceived = ensureUtf8(received);
|
|
if (processedReceived != received) {
|
|
postLog("[Socket] Received data contains invalid UTF-8, processed", 2);
|
|
}
|
|
|
|
size_t leftControlSign = processedReceived.find("[");
|
|
size_t rightControlSign = processedReceived.find("]");
|
|
if (leftControlSign != string::npos && rightControlSign != string::npos && rightControlSign > leftControlSign) {
|
|
string cmdType = processedReceived.substr(leftControlSign + 1, rightControlSign - leftControlSign - 1);
|
|
postLog("[Socket] Command received: " + cmdType, 0);
|
|
size_t argStart = processedReceived.find_first_not_of(" ", rightControlSign + 1);
|
|
string argument = (argStart == string::npos) ? string() : processedReceived.substr(argStart);
|
|
postLog("[Socket] Argument received: " + argument, 0);
|
|
|
|
// debug — keep connection open
|
|
if (cmdType == "debug") {
|
|
continue;
|
|
}
|
|
|
|
// new user registration — keep connection open on errors, close after successful register
|
|
if (cmdType == "newUserRegist") {
|
|
string newUserName = getTextMiddle(argument, "<userName>", "</userName>");
|
|
string newUserPasswd = getTextMiddle(argument, "<userPasswd>", "</userPasswd>");
|
|
string newUsertimeStamp = getTextMiddle(argument, "<timeStamp>", "</timeStamp>");
|
|
int newUserID = -1;
|
|
if (newUserName.empty() || newUserPasswd.empty() || newUsertimeStamp.empty()) {
|
|
tcpSend(sock, "[Regist_Rejected] Invalid registration information.");
|
|
postLog("[User.Auth] Received invalid regist request.", 2);
|
|
continue;
|
|
}
|
|
if (!verifyTimestamp(newUsertimeStamp)) {
|
|
tcpSend(sock, "[Regist_Rejected] Invalid timestamp.");
|
|
postLog("[User.Auth] Received invalid timestamp: " + newUsertimeStamp, 2);
|
|
continue;
|
|
}
|
|
if (newUserName.length() > 20 || newUserPasswd.length() > 20) {
|
|
tcpSend(sock, "[Regist_Rejected] Username or password too long.");
|
|
postLog("[User.Auth] Received too long username or password: " + newUserName + " / " + newUserPasswd, 2);
|
|
continue;
|
|
}
|
|
for (int i = 1; i < MAX_USERS; i++) {
|
|
if (db_getItem(db, "users", "userID", i) != "") {
|
|
continue;
|
|
}
|
|
else {
|
|
newUserID = i;
|
|
break;
|
|
}
|
|
}
|
|
if (newUserID == -1) {
|
|
tcpSend(sock, "[Regist_Rejected] User limit reached.");
|
|
postLog("[User.Auth] No available userID found, user limit reached.", 2);
|
|
continue;
|
|
}
|
|
if (db_getItem(db, "users", "userName", newUserID) != "") {
|
|
tcpSend(sock, "[Regist_Rejected] Username already existed.");
|
|
postLog("[User.Auth] Username already existed.", 2);
|
|
continue;
|
|
}
|
|
postLog("[Socket] Receive legal regist request - ID: " + to_string(newUserID) + ", Name: " + newUserName + ", userPasswd: " + newUserPasswd, 1);
|
|
//db_addItem(db, "users", newUserID, "userName", newUserName);
|
|
//db_addItem(db, "users", newUserID, "Passwd", newUserPasswd);
|
|
tcpSend(sock, "[Regist_Accepted] User registered successfully.");
|
|
postLog("[Socket] New user registed: " + newUserName, 1);
|
|
// close connection after successful registration
|
|
break;
|
|
}
|
|
|
|
/*
|
|
User Login Args:
|
|
- userName
|
|
- userPasswd
|
|
- timeStamp
|
|
*/
|
|
if (cmdType == "newUserEntry") {
|
|
int newUserID;
|
|
string newUserName = getTextMiddle(argument, "<userName>", "</userName>");
|
|
string newUserPasswd = getTextMiddle(argument, "<userPasswd>", "</userPasswd>");
|
|
string newUsertimeStamp = getTextMiddle(argument, "<timeStamp>", "</timeStamp>");
|
|
if (newUserName == "" || newUserPasswd == "" || newUsertimeStamp == "") {
|
|
tcpSend(sock, "[Login_Rejected] Invalid registration information.");
|
|
postLog("[Socket] Received invalid regist request.", 2);
|
|
continue;
|
|
}
|
|
if (!verifyTimestamp(newUsertimeStamp)) {
|
|
tcpSend(sock, "[Login_Rejected] Invalid timestamp.");
|
|
postLog("[Socket] Received invalid timestamp: " + newUsertimeStamp, 2);
|
|
continue;
|
|
}
|
|
if (newUserName.length() > 20 || newUserPasswd.length() > 20) {
|
|
tcpSend(sock, "[Login_Rejected] Username or password too long.");
|
|
postLog("[Socket] Received too long username or password: " + newUserName + " / " + newUserPasswd, 2);
|
|
continue;
|
|
}
|
|
newUserID = stoi(db_findIdBy(db, "users", "userName", newUserName));
|
|
if (newUserID != 0) {
|
|
if (!(newUserName == db_getItem(db, "users", "userName", newUserID) && newUserPasswd == db_getItem(db, "users", "Passwd", newUserID))) {
|
|
tcpSend(sock, "[Login_Rejected] Invalid username or password.");
|
|
postLog("[Socket] Invalid username or password for user: " + newUserName, 2);
|
|
continue;
|
|
}
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Login_Rejected] User does not exist.");
|
|
postLog("[Socket] User does not exist: " + newUserName, 2);
|
|
continue;
|
|
}
|
|
|
|
postLog("[Socket] Receive loggin request - ID: " + to_string(newUserID) + ", Name: " + newUserName + ", userPasswd: " + newUserPasswd, 1);
|
|
// New user logged in successfully — keep connection open
|
|
postLog("[Socket] New user entry: " + newUserName, 1);
|
|
postLog("[Socket] userInfo.ID_PortMap[" + to_string(clientPort) + "] = " + to_string(userInfo.ID_PortMap[clientPort]), 0);
|
|
if (userInfo.ID_PortMap[newUserID] == 0) { // Record user info to specific port
|
|
userInfo.name[newUserID] = newUserName;
|
|
userInfo.passwd[newUserID] = newUserPasswd;
|
|
userInfo.port_IDMap[clientPort] = newUserID;
|
|
userInfo.ID_PortMap[newUserID] = clientPort;
|
|
userInfo.ID_TokenMap[newUserID] = getRandomStr(tokenLength); // Generate token
|
|
tcpSend(sock, "[Login_Accepted] <userName>" + userInfo.name[newUserID] + "</userName> <userID>" + to_string(newUserID) + "</userID> <token>" + userInfo.ID_TokenMap[newUserID] + "</token>");
|
|
postLog("[Socket] User [" + to_string(newUserID) + "]" + newUserName + ":" + to_string(clientPort) + " logged in successfully.", 1);
|
|
// keep the connection active to continue monitoring disconnects
|
|
continue;
|
|
}
|
|
else {
|
|
postLog("[Socket] User [" + to_string(newUserID) + "]" + newUserName + ":" + to_string(clientPort) + " already existed: [" + to_string(newUserID) + "]" + userInfo.name[newUserID] + ":" + to_string(userInfo.ID_PortMap[newUserID]), 2);
|
|
tcpSend(sock, "[Login_Rejected] User already logged in.");
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (cmdType == "userExit") { // User logout
|
|
int uid = userInfo.port_IDMap[clientPort];
|
|
if (getTextMiddle(argument, "<token>", "</token>") != userInfo.ID_TokenMap[uid]) { // Token verification failed
|
|
tcpSend(sock, "[Exit_Rejected] Invalid token.");
|
|
postLog("[Socket] User [" + to_string(uid) + "]" + userInfo.name[uid] + " exit rejected due to invalid token.", 2);
|
|
continue;
|
|
}
|
|
if (getTextMiddle(argument, "<userID>", "</userID>") != to_string(uid)) { // UserID verification failed
|
|
tcpSend(sock, "[Exit_Rejected] Invalid userID.");
|
|
postLog("[Socket] User [" + to_string(uid) + "]" + userInfo.name[uid] + " exit rejected due to invalid userID.", 2);
|
|
continue;
|
|
}
|
|
postLog("[Socket] User [" + userInfo.name[uid] + "] Exited normally.", 1);
|
|
userExit(userInfo.port_IDMap[clientPort], clientPort);
|
|
tcpSend(sock, "[Exit_Accepted] User exited successfully.");
|
|
// close connection after normal exit
|
|
break;
|
|
}
|
|
|
|
if (cmdType == "userGet") {
|
|
int getUserID = stoi(getTextMiddle(argument, "<userID>", "</userID>")); // Get request parameters
|
|
string getToken = getTextMiddle(argument, "<token>", "</token>");
|
|
string getTimeStamp = getTextMiddle(argument, "<timeStamp>", "</timeStamp>");
|
|
string getTable = getTextMiddle(argument, "<table>", "</table>");
|
|
string getItemID = getTextMiddle(argument, "<itemID>", "</itemID>");
|
|
string getValueType = getTextMiddle(argument, "<valueType>", "</valueType>");
|
|
|
|
if (getUserID == 0 || getToken == "" || getTimeStamp == "" || getTable == "" || getItemID == "" || getValueType == "") {
|
|
tcpSend(sock, "[Get_Rejected] Invalid get information.");
|
|
postLog("[Socket] Received invalid get request.", 2);
|
|
continue;
|
|
}
|
|
if (!verifyTimestamp(getTimeStamp)) {
|
|
tcpSend(sock, "[Get_Rejected] Invalid timestamp.");
|
|
postLog("[Socket] Received invalid timestamp: " + getTimeStamp, 2);
|
|
continue;
|
|
}
|
|
|
|
postLog("[Socket] Receive get request - userID: " + to_string(getUserID) + ", table: " + getTable + ", itemID: " + getItemID + ", valueType: " + getValueType, 1);
|
|
if (getToken != userInfo.ID_TokenMap[getUserID]) {
|
|
tcpSend(sock, "[Get_Rejected] Invalid token.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to invalid token.", 2);
|
|
continue;
|
|
}
|
|
if ((char)getItemID.c_str() <= '9' && (char)getItemID.c_str() >= '0') {
|
|
getItemID = db_findIdBy(db, getTable, "teacherName", getItemID);
|
|
if (getItemID == "0") {
|
|
tcpSend(sock, "[Get_Rejected] ItemID does not exist.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to non-existent itemID: " + getItemID, 2);
|
|
continue;
|
|
}
|
|
}
|
|
if (db_getItem(db, getTable, "teacherName", stoi(getItemID)) == "err: no specific id") {
|
|
tcpSend(sock, "[Get_Rejected] ItemID does not exist.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to non-existent itemID: " + getItemID, 2);
|
|
continue;
|
|
}
|
|
if (getTable == "salaryForm") {
|
|
if (getValueType == "all") {
|
|
if (db_getItem(db, "salaryForm", "teacherName", stoi(getItemID)) != "") {
|
|
string basicSalary = db_getItem(db, "salaryForm", "basicSalary", stoi(getItemID));
|
|
string allowances = db_getItem(db, "salaryForm", "allowances", stoi(getItemID));
|
|
string livingAllowances = db_getItem(db, "salaryForm", "livingAllowances", stoi(getItemID));
|
|
string phoneCost = db_getItem(db, "salaryForm", "phoneCost", stoi(getItemID));
|
|
string waterPowerCost = db_getItem(db, "salaryForm", "waterPowerCost", stoi(getItemID));
|
|
string houseRent = db_getItem(db, "salaryForm", "houseRent", stoi(getItemID));
|
|
string incomeTax = db_getItem(db, "salaryForm", "incomeTax", stoi(getItemID));
|
|
string cleaningCost = db_getItem(db, "salaryForm", "cleaningCost", stoi(getItemID));
|
|
string housingFund = db_getItem(db, "salaryForm", "housingFund", stoi(getItemID));
|
|
tcpSend(sock, "[Get_Accepted] <itemID>" + getItemID + "</itemID> <basicSalary>" + basicSalary + "</basicSalary> <allowances>" + allowances + "</allowances> <livingAllowances>" + livingAllowances + "</livingAllowances> <phoneCost>" + phoneCost + "</phoneCost> <waterPowerCost>" + waterPowerCost + "</waterPowerCost> <houseRent>" + houseRent + "</houseRent> <incomeTax>" + incomeTax + "</incomeTax> <cleaningCost>" + cleaningCost + "</cleaningCost> <housingFund>" + housingFund + "</housingFund>");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get all values successfully.", 1);
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Get_Rejected] ItemID does not exist.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to non-existent itemID: " + getItemID, 2);
|
|
}
|
|
}
|
|
else {
|
|
if (db_getItem(db, "salaryForm", getValueType, stoi(getItemID)) != "") {
|
|
string getValue = db_getItem(db, "salaryForm", getValueType, stoi(getItemID));
|
|
tcpSend(sock, "[Get_Accepted] <value>" + getValue + "</value>");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get value successfully: " + getValue, 1);
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Get_Rejected] ItemID does not exist.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to non-existent itemID: " + getItemID, 2);
|
|
}
|
|
}
|
|
}
|
|
if (getTable == "teacherInfo") {
|
|
if (getValueType == "all") {
|
|
if (db_getItem(db, "teacherInfo", "teacherName", stoi(getItemID)) != "") {
|
|
string teacherName = db_getItem(db, "teacherInfo", "teacherName", stoi(getItemID));
|
|
string gender = db_getItem(db, "teacherInfo", "gender", stoi(getItemID));
|
|
string companyName = db_getItem(db, "teacherInfo", "companyName", stoi(getItemID));
|
|
string address = db_getItem(db, "teacherInfo", "address", stoi(getItemID));
|
|
string tel = db_getItem(db, "teacherInfo", "tel", stoi(getItemID));
|
|
tcpSend(sock, "[Get_Accepted] <itemID>" + getItemID + "</itemID> <teacherName>" + teacherName + "</teacherName> <gender>" + gender + "</gender> <companyName>" + companyName + "</companyName> <address>" + address + "</address> <tel>" + tel + "</tel>");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get all values successfully.", 1);
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Get_Rejected] ItemID does not exist.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to non-existent itemID: " + getItemID, 2);
|
|
}
|
|
}
|
|
else {
|
|
if (db_getItem(db, "teacherInfo", getValueType, stoi(getItemID)) != "") {
|
|
string getValue = db_getItem(db, "teacherInfo", getValueType, stoi(getItemID));
|
|
tcpSend(sock, "[Get_Accepted] <value>" + getValue + "</value>");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get value successfully: " + getValue, 1);
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Get_Rejected] ItemID does not exist.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to non-existent itemID: " + getItemID, 2);
|
|
}
|
|
}
|
|
|
|
}
|
|
continue;
|
|
}
|
|
if (cmdType == "userSet") {
|
|
int getUserID = stoi(getTextMiddle(argument, "<userID>", "</userID>"));
|
|
string getToken = getTextMiddle(argument, "<token>", "</token> ");
|
|
string getTimeStamp = getTextMiddle(argument, "<timeStamp>", "</timeStamp>");
|
|
string getTable = getTextMiddle(argument, "<table>", "</table>");
|
|
string getItemID = getTextMiddle(argument, "<itemID>", "</itemID>");
|
|
string getValueType = getTextMiddle(argument, "<valueType>", "</valueType>");
|
|
string getValue = getTextMiddle(argument, "<value>", "</value>");
|
|
if (getUserID == 0 || getToken == "" || getTimeStamp == "" || getTable == "" || getItemID == "" || getValueType == "" || getValue == "") {
|
|
tcpSend(sock, "[Set_Rejected] Invalid set information.");
|
|
postLog("[Socket] Received invalid set request.", 2);
|
|
continue;
|
|
}
|
|
if (!verifyTimestamp(getTimeStamp)) {
|
|
tcpSend(sock, "[Set_Rejected] Invalid timestamp.");
|
|
postLog("[Socket] Received invalid timestamp: " + getTimeStamp, 2);
|
|
continue;
|
|
}
|
|
|
|
postLog("[Socket] Receive set request - userID: " + to_string(getUserID) + ", table: " + getTable + ", itemID: " + getItemID + ", valueType: " + getValueType, 1);
|
|
if (getToken != userInfo.ID_TokenMap[getUserID]) {
|
|
tcpSend(sock, "[Set_Rejected] Invalid token.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " get rejected due to invalid token.", 2);
|
|
continue;
|
|
}
|
|
|
|
if (db_getItem(db, getTable, "teacherName", stoi(getItemID)) == "err: no specific id") {
|
|
postLog("[Database] No such itemID[" + getItemID + "] in " + getTable + " table: " + getTable + ", create a new one.", 2);
|
|
db_addItem(db, getTable, stoi(getItemID), "teacherID", getItemID);
|
|
// for (int i = 0; i < MAX_USERS; i++) {
|
|
// if (db_getItem(db, "salaryForm", "teacherName", i) == "err: no specific id") {
|
|
// getItemID = i;
|
|
//
|
|
// break;
|
|
// }
|
|
// else {
|
|
// tcpSend(sock, "[Set_Rejected] Database is full.");
|
|
// postLog("[Database] salaryForm table is full, cannot add new item.", 2);
|
|
// return;
|
|
// }
|
|
// }
|
|
}
|
|
if (db_updateItem(db, getTable, stoi(getItemID), getValueType, getValue)) {
|
|
tcpSend(sock, "[Set_Accepted] Value set successfully.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " set value successfully: " + getValueType + " = " + getValue, 1);
|
|
continue;
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Set_Rejected] Failed to set value.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " set value failed: " + getValueType + " = " + getValue, 2);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (cmdType == "userDel") {
|
|
int getUserID = stoi(getTextMiddle(argument, "<userID>", "</userID>"));
|
|
string getToken = getTextMiddle(argument, "<token>", "</token> ");
|
|
string getTimeStamp = getTextMiddle(argument, "<timeStamp>", "</timeStamp>");
|
|
string getTable = getTextMiddle(argument, "<table>", "</table>");
|
|
string getItemID = getTextMiddle(argument, "<itemID>", "</itemID>");
|
|
if (getUserID == 0 || getToken == "" || getTimeStamp == "" || getTable == "" || getItemID == "") {
|
|
tcpSend(sock, "[Del_Rejected] Invalid delete information.");
|
|
postLog("[Socket] Received invalid delete request.", 2);
|
|
continue;
|
|
}
|
|
if (!verifyTimestamp(getTimeStamp)) {
|
|
tcpSend(sock, "[Del_Rejected] Invalid timestamp.");
|
|
postLog("[Socket] Received invalid timestamp: " + getTimeStamp, 2);
|
|
continue;
|
|
}
|
|
postLog("[Socket] Receive delete request - userID: " + to_string(getUserID) + ", table: " + getTable + ", itemID: " + getItemID, 1);
|
|
if (getToken != userInfo.ID_TokenMap[getUserID]) {
|
|
tcpSend(sock, "[Del_Rejected] Invalid token.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " delete rejected due to invalid token.", 2);
|
|
continue;
|
|
}
|
|
if (db_rmItem(db, getTable, stoi(getItemID))) {
|
|
tcpSend(sock, "[Del_Accepted] Item deleted successfully.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " deleted itemID " + getItemID + " successfully.", 1);
|
|
continue;
|
|
}
|
|
else {
|
|
tcpSend(sock, "[Del_Rejected] Failed to delete item.");
|
|
postLog("[Socket] User [" + to_string(getUserID) + "]" + userInfo.name[getUserID] + " failed to delete itemID " + getItemID + ".", 2);
|
|
continue;
|
|
}
|
|
}
|
|
// Add cmdType handlers above this line
|
|
tcpSend(sock, "[Error] Unknown command: " + cmdType);
|
|
postLog("[Socket] Unknown command received: " + cmdType, 2);
|
|
continue;
|
|
}
|
|
else {
|
|
// Non-command payload; ignore but do not treat as disconnect.
|
|
postLog("[Socket] Non-command data received from " + clientIP + ":" + to_string(clientPort) + " (" + to_string(bytesReceived) + " bytes).", 1);
|
|
continue;
|
|
}
|
|
}
|
|
else if (bytesReceived == 0) {
|
|
// orderly shutdown by peer
|
|
postLog("[Socket] Client closed connection (graceful): " + clientIP + ":" + to_string(clientPort), 0);
|
|
break;
|
|
}
|
|
else { // bytesReceived == SOCKET_ERROR
|
|
int err = WSAGetLastError();
|
|
postLog("[Socket] recv() returned SOCKET_ERROR: " + to_string(err), 2);
|
|
|
|
if (err == WSAEWOULDBLOCK) {
|
|
// If using non-blocking sockets, wait with select() to avoid busy-looping.
|
|
fd_set readfds;
|
|
FD_ZERO(&readfds);
|
|
FD_SET(sock, &readfds);
|
|
timeval tv;
|
|
tv.tv_sec = 5;
|
|
tv.tv_usec = 0;
|
|
int sel = select(0, &readfds, NULL, NULL, &tv);
|
|
if (sel == 0) {
|
|
continue;
|
|
}
|
|
if (sel == SOCKET_ERROR) {
|
|
postLog("[Socket] select() failed: " + to_string(WSAGetLastError()), 2);
|
|
break;
|
|
}
|
|
continue;
|
|
}
|
|
else if (err == WSAECONNRESET) {
|
|
postLog("[Socket] Connection reset by peer: " + clientIP + ":" + to_string(clientPort), 2);
|
|
break;
|
|
}
|
|
else {
|
|
postLog("[Socket] recv() fatal error, closing connection: " + to_string(err), 2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Common cleanup path
|
|
postLog("[Socket] Connection cleanup for " + clientIP + ":" + to_string(clientPort), 0);
|
|
postLog("[Socket] userPortMap[" + to_string(clientPort) + "] = " + to_string(userInfo.port_IDMap[clientPort]), 0);
|
|
if (userInfo.port_IDMap[clientPort] != 0) {
|
|
postLog("[Socket] User [" + userInfo.name[userInfo.port_IDMap[clientPort]] + "] Exited without notification!", 2);
|
|
userExit(userInfo.port_IDMap[clientPort], clientPort);
|
|
}
|
|
|
|
shutdown(sock, SD_BOTH);
|
|
closesocket(sock);
|
|
}
|
|
|
|
int serverInit_Sockets() {
|
|
if (inet_pton(AF_INET, ipAddr.server_cstr, &ipAddr.server) != 1) {
|
|
postLog("[Socket] Failed to convert IP Address.", 4);
|
|
return 1;
|
|
}
|
|
|
|
WSADATA wsaData;
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
|
postLog("[Socket] WSAStartup failed.", 4);
|
|
return 1;
|
|
}
|
|
|
|
SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0); // Create main server socket
|
|
if (listenSocket == INVALID_SOCKET) {
|
|
postLog("[Socket] Failed to create socket.", 4);
|
|
WSACleanup();
|
|
return 1;
|
|
}
|
|
|
|
sockaddr_in serverAddr = {};
|
|
serverAddr.sin_family = AF_INET;
|
|
serverAddr.sin_addr = ipAddr.server;
|
|
serverAddr.sin_port = htons(ipAddr.listenPort);
|
|
|
|
if (bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
|
|
postLog("[Socket.service] Bind failed.", 4);
|
|
closesocket(listenSocket);
|
|
WSACleanup();
|
|
return 1;
|
|
}
|
|
|
|
if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
|
|
postLog("[Socket] Listen failed.", 4);
|
|
closesocket(listenSocket);
|
|
WSACleanup();
|
|
return 1;
|
|
}
|
|
|
|
// log the configured server IP string instead of unused server_vec
|
|
postLog("[Socket] Server is listening on " + string(ipAddr.server_cstr) + ":" + to_string(ipAddr.listenPort) + ".", 1);
|
|
postLog("[System] Initialization complete. Server is now running.", 1);
|
|
|
|
// Accept loop: create and detach threads immediately
|
|
while (true) {
|
|
sockaddr_in clientAddr;
|
|
int clientAddrSize = sizeof(clientAddr);
|
|
SOCKET clientSocket_serviceSock = accept(listenSocket, (sockaddr*)&clientAddr, &clientAddrSize);
|
|
if (clientSocket_serviceSock != INVALID_SOCKET) {
|
|
globalSock.service = clientSocket_serviceSock;
|
|
char ipStr[INET_ADDRSTRLEN];
|
|
inet_ntop(AF_INET, &(clientAddr.sin_addr), ipStr, INET_ADDRSTRLEN);
|
|
int clientPort = ntohs(clientAddr.sin_port);
|
|
std::thread t(handle_client, clientSocket_serviceSock, string(ipStr), clientPort);
|
|
t.detach(); // let thread run independently
|
|
}
|
|
}
|
|
}
|
|
|
|
void userExit(int userID, int clientPort) { // User logout processing function
|
|
userInfo.name[userID] = "";
|
|
userInfo.ID_PortMap[userID] = 0;
|
|
userInfo.ID_TokenMap[userID] = "";
|
|
userInfo.port_IDMap[clientPort] = 0;
|
|
}
|
|
|
|
string getTextMiddle(string source, string left, string right) { // Get content between left and right strings
|
|
size_t leftPos = source.find(left);
|
|
if (leftPos == string::npos) return "";
|
|
leftPos += left.length();
|
|
size_t rightPos = source.find(right, leftPos);
|
|
if (rightPos == string::npos) return "";
|
|
return source.substr(leftPos, rightPos - leftPos);
|
|
}
|
|
|
|
bool tcpSend(SOCKET sock, string str) {
|
|
// Ensure the string is valid UTF-8 before sending
|
|
string strToSend = ensureUtf8(str);
|
|
if (strToSend != str) {
|
|
postLog("[Encoding] Fixed UTF-8 encoding before sending", 1);
|
|
}
|
|
|
|
int r = send(sock, strToSend.c_str(), (int)strToSend.size(), 0);
|
|
if (r == SOCKET_ERROR) {
|
|
postLog("[method.tcpSend] send() failed: " + to_string(WSAGetLastError()), 4);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool verifyTimestamp(const string& timestampStr) { // Verify timestamp validity
|
|
try {
|
|
int timestamp = stoi(timestampStr);
|
|
int currentTimestamp = static_cast<int>(time(nullptr));
|
|
if (timestamp <= currentTimestamp + 300 && timestamp >= currentTimestamp - 300) {
|
|
return true; // Timestamp within allowed range
|
|
}
|
|
else {
|
|
return false; // Invalid timestamp
|
|
}
|
|
}
|
|
catch (...) {
|
|
return false; // Conversion failed, invalid timestamp
|
|
}
|
|
}
|
|
|
|
int serverInit_SQLite_Open() {
|
|
char* errMsg = nullptr;
|
|
|
|
int if_db_opened = sqlite3_open(db_path.c_str(), &db);
|
|
|
|
if (if_db_opened) {
|
|
postLog("[SQLite] Failed to open database", 4);
|
|
return if_db_opened;
|
|
}
|
|
|
|
postLog("[SQLite] Creating databases...", 1);
|
|
const char* sql_create_users = "CREATE TABLE IF NOT EXISTS users (userId INT, userName TEXT, Passwd TEXT, PRIMARY KEY(userID, userName));";
|
|
const char* sql_create_salaryForm = "CREATE TABLE IF NOT EXISTS salaryForm (teacherID INT , teacherName TEXT, basicSalary INT, allowances INT, livingAllowances INT, phoneCost INT, waterPowerCost INT, houseRent INT, incomeTax INT, cleaningCost INT, housingFund INT, PRIMARY KEY(teacherID, teacherName));";
|
|
const char* sql_create_teacherInfo = "CREATE TABLE IF NOT EXISTS teacherInfo (teacherID INT , teacherName TEXT, gender TXT, companyName TXT, address TXT, tel TXT, PRIMARY KEY(teacherID, teacherName));";
|
|
if (sqlite3_exec(db, sql_create_users, 0, 0, &errMsg) != SQLITE_OK) {
|
|
postLog("[SQLite] SQL error (users): " + string(errMsg), 4);
|
|
sqlite3_free(errMsg);
|
|
}
|
|
if (sqlite3_exec(db, sql_create_salaryForm, 0, 0, &errMsg) != SQLITE_OK) {
|
|
postLog("[SQLite] SQL error (salaryForm): " + string(errMsg), 4);
|
|
sqlite3_free(errMsg);
|
|
}
|
|
if (sqlite3_exec(db, sql_create_teacherInfo, 0, 0, &errMsg) != SQLITE_OK) {
|
|
postLog("[SQLite] SQL error (teacherInfo): " + string(errMsg), 4);
|
|
sqlite3_free(errMsg);
|
|
}
|
|
}
|
|
|
|
string getRandomStr(const int len) { // Generate random string
|
|
static const char charset[] =
|
|
"0123456789"
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
"abcdefghijklmnopqrstuvwxyz";
|
|
static thread_local mt19937 rg{ random_device{}() };
|
|
static thread_local uniform_int_distribution<> pick(0, sizeof(charset) - 2);
|
|
|
|
string s;
|
|
s.reserve(len);
|
|
for (int i = 0; i < len; ++i)
|
|
s += charset[pick(rg)];
|
|
return s;
|
|
} |