diff --git a/.github/workflows/doesItCompile.yml b/.github/workflows/doesItCompile.yml new file mode 100644 index 0000000..00699d1 --- /dev/null +++ b/.github/workflows/doesItCompile.yml @@ -0,0 +1,18 @@ +name: Makefile CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Compile it + run: make diff --git a/includes/Server.hpp b/includes/Server.hpp index 5212b8b..d519b19 100644 --- a/includes/Server.hpp +++ b/includes/Server.hpp @@ -21,6 +21,9 @@ # define BUFFER_SIZE 512 # define MAX_CLIENTS 42 +class User; +class Channel; + /** * A class that represents the server. */ @@ -55,6 +58,7 @@ class Server { bool isNicknameInUse(const std::string& nickname); bool userHasCheckedPassword(int clientFd); void removeUser(int clientFd); + void attemptUserRegistration(int clientFd); void addChannel(Channel channel); void removeChannel(std::string channelName); diff --git a/includes/User.hpp b/includes/User.hpp index 0f05c4b..275b162 100644 --- a/includes/User.hpp +++ b/includes/User.hpp @@ -3,10 +3,12 @@ # include "libsUtils.hpp" # include "Channel.hpp" +# include "Server.hpp" # define MAX_CHANNELS 10 class Channel; +class Server; /** * A class that represents an user. @@ -15,11 +17,13 @@ class User { private: int _fd; bool _passwordChecked; + bool _registered; std::string _username; std::string _hostname; std::string _serverName; std::string _realName; std::string _nickname; + std::string _password; std::vector _channels; std::vector::const_iterator findChannel(std::string channelName) const; @@ -33,9 +37,11 @@ class User { // Getters int getFd() const; std::string getNickname() const; + std::string getUsername() const; bool isPasswordChecked() const; bool isUserInMaxChannels() const; bool isAlreadyInChannel(std::string channelName) const; + bool isRegistered() const; // Setters void setUsername(const std::string& username); @@ -43,9 +49,12 @@ class User { void setServerName(const std::string& serverName); void setRealName(const std::string& realName); void setNickname(const std::string& nickname); + void setPassword(const std::string& password); // Operations void checkPassword(); + void makeRegistration(Server &server); + bool canRegister(); void joinChannel(Channel channel); void leaveChannel(std::string channelName); }; diff --git a/includes/commands/ICommand.hpp b/includes/commands/ICommand.hpp index 40f9e80..945c83e 100644 --- a/includes/commands/ICommand.hpp +++ b/includes/commands/ICommand.hpp @@ -7,8 +7,11 @@ class Server; * An interface that represents a command that can be executed by the server. */ class ICommand { + protected: + bool _needValidate; public: - virtual void execute(Server &server, int fd) = 0; + virtual void execute(Server &server, int clientFd) = 0; + ICommand(bool needValidate) : _needValidate(needValidate) {}; }; #endif \ No newline at end of file diff --git a/includes/commands/NickCommand.hpp b/includes/commands/NickCommand.hpp index 6838d64..4ade1b7 100644 --- a/includes/commands/NickCommand.hpp +++ b/includes/commands/NickCommand.hpp @@ -25,7 +25,7 @@ class NickCommand : public ICommand { NickCommand(const std::string& nickname); ~NickCommand(); - void execute(Server &server, int fd); + void execute(Server &server, int clientFd); }; #endif \ No newline at end of file diff --git a/includes/commands/PassCommand.hpp b/includes/commands/PassCommand.hpp index e5ff2e6..ca4c9f9 100644 --- a/includes/commands/PassCommand.hpp +++ b/includes/commands/PassCommand.hpp @@ -19,7 +19,7 @@ class PassCommand : public ICommand { PassCommand(const std::string& password); ~PassCommand(); - void execute(Server &server, int fd); + void execute(Server &server, int clientFd); }; #endif \ No newline at end of file diff --git a/includes/commands/UserCommand.hpp b/includes/commands/UserCommand.hpp index 638a591..56ec1c4 100644 --- a/includes/commands/UserCommand.hpp +++ b/includes/commands/UserCommand.hpp @@ -20,7 +20,7 @@ class UserCommand : public ICommand { public: UserCommand(); UserCommand(const std::string& username, const std::string& hostname, const std::string& serverName, const std::string& realName); - void execute(Server &server, int fd); + void execute(Server &server, int clientFd); ~UserCommand(); }; diff --git a/includes/exceptions/IRCException.hpp b/includes/exceptions/IRCException.hpp index b530e0a..c29f744 100644 --- a/includes/exceptions/IRCException.hpp +++ b/includes/exceptions/IRCException.hpp @@ -12,7 +12,7 @@ class IRCException : public std::exception { std::string _message; public: - IRCException(std::string errorCode, const std::string& msg): _errorCode(errorCode), _message(msg) {} + IRCException(std::string errorCode, const std::string& msg) : _errorCode(errorCode), _message(msg) {} ~IRCException() throw() {} virtual const char* what() const throw() { diff --git a/includes/libsUtils.hpp b/includes/libsUtils.hpp index a10f129..03a7cf2 100644 --- a/includes/libsUtils.hpp +++ b/includes/libsUtils.hpp @@ -14,7 +14,7 @@ # include "utils.hpp" -#include "exceptions.hpp" +# include "exceptions.hpp" // ================================================================================= diff --git a/includes/parser/CommandParser.hpp b/includes/parser/CommandParser.hpp index 68c467b..6c121af 100644 --- a/includes/parser/CommandParser.hpp +++ b/includes/parser/CommandParser.hpp @@ -35,10 +35,10 @@ enum Commands { class CommandParser { private: static std::vector tokenize(const std::string& command); - static IParser* getParser(std::string command, int fd, Server &server); + static IParser* getParser(std::string command); public: - static ICommand* parse(const std::string& command, int fd, Server &server); + static ICommand* parse(const std::string& command); }; #endif \ No newline at end of file diff --git a/includes/parser/QuitParser.hpp b/includes/parser/QuitParser.hpp index 5641da8..36d007b 100644 --- a/includes/parser/QuitParser.hpp +++ b/includes/parser/QuitParser.hpp @@ -1,11 +1,11 @@ #ifndef QUIT_PARSER_HPP # define QUIT_PARSER_HPP -#include "IParser.hpp" +# include "IParser.hpp" -#include "QuitCommand.hpp" +# include "QuitCommand.hpp" -#include "libsUtils.hpp" +# include "libsUtils.hpp" /** * A class that is responsible for parsing the QUIT command. diff --git a/src/Server.cpp b/src/Server.cpp index 7616757..69d3244 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -24,7 +24,7 @@ bool Server::isValidPort(const std::string port) { * * @throws `ServerException` if the port is out of range. */ -Server::Server(const std::string port, const std::string password): _password(password) { +Server::Server(const std::string port, const std::string password) : _password(password) { if (!this->isValidPort(port)) throw ServerException(PORT_OUT_OF_RANGE_ERR); _port = std::atoi(port.c_str()); @@ -169,7 +169,7 @@ void Server::handleExistingConnection(int clientFd) { return; try { - ICommand* command = CommandParser::parse(std::string(buffer, readBytes), clientFd, *this); + ICommand* command = CommandParser::parse(std::string(buffer, readBytes)); command->execute(*this, clientFd); } catch (IRCException& e) { std::string clientNickname = getUserByFd(clientFd).getNickname(); @@ -185,7 +185,7 @@ void Server::handleExistingConnection(int clientFd) { } /** - * This function aims to validate the password provided by the client. + * This function validates if the user's password is the same as the server's password. * * @param password The password provided by the client. * @return `true` if the password is valid, `false` otherwise. @@ -256,6 +256,15 @@ void Server::removeUser(int fd) { } } +/** + * This function attempt to register a user. + * + * @param clientFd The file descriptor of the user. + */ +void Server::attemptUserRegistration(int clientFd) { + this->getUserByFd(clientFd).makeRegistration(*this); +} + /** * This function aims to find a user by the file descriptor. * diff --git a/src/User.cpp b/src/User.cpp index 826b6de..deb7e6e 100644 --- a/src/User.cpp +++ b/src/User.cpp @@ -5,7 +5,7 @@ * * @param fd The file descriptor of the user */ -User::User(int fd): _fd(fd), _passwordChecked(false) {} +User::User(int fd) : _fd(fd), _passwordChecked(false) {} /** * User destructor @@ -91,6 +91,15 @@ int User::getFd() const { /** * This function aims to get the username of the user. + * + * @return The username of the user. + */ +std::string User::getUsername() const { + return this->_username; +} + +/** + * This function aims to set the username of the user. * * @param username The username of the user. */ @@ -99,7 +108,7 @@ void User::setUsername(const std::string& username) { } /** - * This function aims to get the hostname of the user. + * This function aims to set the hostname of the user. * * @param hostname The hostname of the user. */ @@ -108,7 +117,7 @@ void User::setHostname(const std::string& hostname) { } /** - * This function aims to get the server name of the user. + * This function aims to set the server name of the user. * * @param serverName The server name of the user. */ @@ -117,7 +126,7 @@ void User::setServerName(const std::string& serverName) { } /** - * This function aims to get the real name of the user. + * This function aims to set the real name of the user. * * @param realName The real name of the user. */ @@ -137,11 +146,50 @@ std::string User::getNickname() const { /** * This function aims to set the nickname of the user. * + * @param nickname The nickname of the user. */ -void User::setNickname(const std::string& nickname){ +void User::setNickname(const std::string& nickname) { this->_nickname = nickname; } +/** + * This function aims to set the password of the user. + * + * @param password The password the user wants to use. + */ +void User::setPassword(const std::string& password) { + this->_password = password; +} + +/** + * This function aims to check if the user can register. + * + * @return `true` if the user can register, `false` otherwise. + */ +bool User::canRegister() { + return !(this->_username.empty() || this->_hostname.empty() || + this->_serverName.empty() || this->_realName.empty() || this->_nickname.empty()); +} + +/** + * This function try to register the user and verify that it is the same password as on the server. + * + * @param server The server where the user is trying to register. + */ +void User::makeRegistration(Server &server) { + if (!server.isValidPassword(_password)) + throw PasswordMismatchException(); + this->_registered = true; +} + +/** + * This function aims to check if the user has registered. + * + * @return `true` if the user has registered, `false` otherwise. + */ +bool User::isRegistered() const { + return this->_registered; +} /** * This function aims to join a channel. diff --git a/src/commands/NickCommand.cpp b/src/commands/NickCommand.cpp index 122aa45..d1023b7 100644 --- a/src/commands/NickCommand.cpp +++ b/src/commands/NickCommand.cpp @@ -3,14 +3,14 @@ /** * NickCommand default constructor. */ -NickCommand::NickCommand(): _nickname("") {} +NickCommand::NickCommand() : ICommand(false), _nickname("") {} /** * NickCommand nickname constructor. * * @param nickname The nickname */ -NickCommand::NickCommand(const std::string& nickname): _nickname(nickname) {} +NickCommand::NickCommand(const std::string& nickname) : ICommand(false), _nickname(nickname) {} /** * NickCommand destructor. @@ -21,31 +21,35 @@ NickCommand::~NickCommand() {} * Execute the command NICK. * * @param server The server where the command will be executed - * @param fd The socket file descriptor of the client + * @param clientFd The socket file descriptor of the client * - * @throws `CommandException` If the nickname is empty, too long, already in use or invalid + * @throws `NoNicknameGivenException` If the nickname is empty + * @throws `ErroneousNicknameException` If the nickname is too long or invalid + * @throws `NickCollisionException` If the nickname is already in use and the user is not registered + * @throws `NicknameInUseException` If the nickname is already in use and the user is registered * */ -void NickCommand::execute(Server &server, int fd) { +void NickCommand::execute(Server &server, int clientFd) { if (this->_nickname.empty()) throw NoNicknameGivenException(); if (this->_nickname.size() > MAX_NICKNAME_SIZE) throw ErroneousNicknameException(this->_nickname); - if (server.isNicknameInUse(this->_nickname)) { - // TODO: If user is not registered, we throw NickCollisionException - throw NickCollisionException(this->_nickname); - // TODO: If user is registered, we throw NicknameInUseException - // throw NicknameInUseException(_nickname); + User &user = server.getUserByFd(clientFd); + if (server.isNicknameInUse(this->_nickname)) { + user.isRegistered() + ? throw NicknameInUseException(this->_nickname) + : throw NickCollisionException(this->_nickname); } if (!NickCommand::isValidNickname()) throw ErroneousNicknameException(this->_nickname); - User user = server.getUserByFd(fd); user.setNickname(this->_nickname); + if (user.canRegister()) + server.attemptUserRegistration(clientFd); } /** diff --git a/src/commands/PassCommand.cpp b/src/commands/PassCommand.cpp index 96a5d7b..ca9fdf0 100644 --- a/src/commands/PassCommand.cpp +++ b/src/commands/PassCommand.cpp @@ -3,14 +3,14 @@ /** * PassCommand default constructor. */ -PassCommand::PassCommand(): _password("") {} +PassCommand::PassCommand() : ICommand(false), _password("") {} /** * PassCommand password constructor. * * @param password The password */ -PassCommand::PassCommand(const std::string& password): _password(password) {} +PassCommand::PassCommand(const std::string& password) : ICommand(false), _password(password) {} /** * PassCommand destructor. @@ -23,15 +23,11 @@ PassCommand::~PassCommand() {} * @param server The server where the command will be executed * @param fd The socket file descriptor of the client * - * @throws `CommandException` If the password is invalid + * @throws `AlreadyRegisteredException` If the user is already registered * */ -void PassCommand::execute(Server &server, int fd) { - std::cout << "Password: " << this->_password << std::endl; - if (server.getUserByFd(fd).isPasswordChecked()) +void PassCommand::execute(Server &server, int clientFd) { + if (server.getUserByFd(clientFd).isRegistered()) throw AlreadyRegisteredException(); - if (server.isValidPassword(this->_password)) - server.getUserByFd(fd).checkPassword(); - else - throw PasswordMismatchException(); + server.getUserByFd(clientFd).setPassword(_password); } diff --git a/src/commands/QuitCommand.cpp b/src/commands/QuitCommand.cpp index 73fb5f4..510c232 100644 --- a/src/commands/QuitCommand.cpp +++ b/src/commands/QuitCommand.cpp @@ -3,12 +3,14 @@ /** * QuitCommand default constructor. */ -QuitCommand::QuitCommand() : _msg("") {} +QuitCommand::QuitCommand() : ICommand(false), _msg("") {} /** * QuitCommand message constructor. -*/ -QuitCommand::QuitCommand(std::string msg) : _msg(msg) {} + * + * @param msg The message + */ +QuitCommand::QuitCommand(std::string msg) : ICommand(false), _msg(msg) {} /** * QuitCommand destructor. @@ -19,9 +21,8 @@ QuitCommand::~QuitCommand() {} * Execute the command QUIT. * * @param server The server where the command will be executed - * @param fd The socket file descriptor of the client + * @param clientFd The socket file descriptor of the client * - * @throws `ServerException` If the message could not be sent */ void QuitCommand::execute(Server &server, int clientFd) { if (_msg.length() == 0) { diff --git a/src/commands/UserCommand.cpp b/src/commands/UserCommand.cpp index de619d2..a631cf3 100644 --- a/src/commands/UserCommand.cpp +++ b/src/commands/UserCommand.cpp @@ -4,7 +4,7 @@ * Command User default constructor * */ -UserCommand::UserCommand(): _username(""), _hostname(""), _serverName(""), _realName("") {} +UserCommand::UserCommand() : ICommand(false), _username(""), _hostname(""), _serverName(""), _realName("") {} /** * Command User constructor @@ -15,7 +15,7 @@ UserCommand::UserCommand(): _username(""), _hostname(""), _serverName(""), _real * @param realName The real name of the client */ UserCommand::UserCommand(const std::string& username, const std::string& hostname, const std::string& serverName, - const std::string& realName) : _username(username), _hostname(hostname), + const std::string& realName) : ICommand(false), _username(username), _hostname(hostname), _serverName(serverName), _realName(realName) {} /** @@ -28,14 +28,19 @@ UserCommand::~UserCommand() {} * Execute the command USER. * * @param server The server where the command will be executed - * @param fd The socket file descriptor of the client + * @param clientFd The socket file descriptor of the client + * + * @throws `AlreadyRegisteredException` If the user has already used the USER command * */ -void UserCommand::execute(Server &server, int fd) { - // TODO: If user is already registered, throw exception (AlreadyRegisteredException) - User user = server.getUserByFd(fd); +void UserCommand::execute(Server &server, int clientFd) { + User user = server.getUserByFd(clientFd); + if (!user.getUsername().empty()) + throw AlreadyRegisteredException(); user.setUsername(this->_username); user.setHostname(this->_hostname); user.setServerName(this->_serverName); user.setRealName(this->_realName); + if (server.getUserByFd(clientFd).canRegister()) + server.attemptUserRegistration(clientFd); } diff --git a/src/parser/CommandParser.cpp b/src/parser/CommandParser.cpp index b52feed..891318e 100644 --- a/src/parser/CommandParser.cpp +++ b/src/parser/CommandParser.cpp @@ -7,9 +7,9 @@ * * @return The parsed command. */ -ICommand* CommandParser::parse(const std::string& input, int fd, Server &server) { +ICommand* CommandParser::parse(const std::string& input) { std::vector tokens = CommandParser::tokenize(input); - IParser *parser = CommandParser::getParser(tokens[0], fd, server); + IParser *parser = CommandParser::getParser(tokens[0]); try { ICommand *command = parser->parse(tokens); delete parser; @@ -25,16 +25,14 @@ ICommand* CommandParser::parse(const std::string& input, int fd, Server &server) * * @param command The command to parse * - * @throws `CommandException` if the command is invalid + * @throws `CommandNotFoundException` if the command is invalid * @return The parser for the command */ -IParser* CommandParser::getParser(std::string command, int fd, Server &server) { +IParser* CommandParser::getParser(std::string command) { if (command == "QUIT") return new QuitParser(); if (command == "PASS") return new PassParser(); - if (!server.userHasCheckedPassword(fd)) - throw NotRegisteredException(); if (command == "USER") return new UserParser(); if (command == "NICK") @@ -57,6 +55,5 @@ std::vector CommandParser::tokenize(const std::string& command) { while (std::getline(tokenStream, token, ' ')) { tokens.push_back(trim(token)); } - return tokens; } \ No newline at end of file diff --git a/src/parser/NickParser.cpp b/src/parser/NickParser.cpp index e92d7d1..3bb5200 100644 --- a/src/parser/NickParser.cpp +++ b/src/parser/NickParser.cpp @@ -10,12 +10,11 @@ * * @param tokens The parameters of the command. * - * @throws `ParserException` if the number of arguments is different than the expected. + * @throws `NoNicknameGivenException` if the nickname is not specified. * @return The parsed command. */ ICommand *NickParser::parse(const std::vector& tokens) { if (tokens.size() < 2) throw NoNicknameGivenException(); - return new NickCommand(tokens[1]); } \ No newline at end of file diff --git a/src/parser/PassParser.cpp b/src/parser/PassParser.cpp index 7ec2a85..bb13cbf 100644 --- a/src/parser/PassParser.cpp +++ b/src/parser/PassParser.cpp @@ -10,12 +10,11 @@ * * @param tokens The parameters of the command. * - * @throws `ParserException` if the number of arguments is different than the expected. + * @throws `NeedMoreParamsException` if the number of arguments is less than the expected. * @return The parsed command. */ ICommand* PassParser::parse(const std::vector& tokens) { - if (tokens.size() < 2) { + if (tokens.size() < 2) throw NeedMoreParamsException("PASS"); - } return new PassCommand(tokens[1]); } \ No newline at end of file diff --git a/src/parser/QuitParser.cpp b/src/parser/QuitParser.cpp index 0d82387..7c0489e 100644 --- a/src/parser/QuitParser.cpp +++ b/src/parser/QuitParser.cpp @@ -10,7 +10,6 @@ * * @param tokens The parameters of the command. * - * @throws `ParserException` if the number of arguments is different than the expected. * @return The parsed command. */ ICommand *QuitParser::parse(const std::vector& tokens) { diff --git a/src/parser/UserParser.cpp b/src/parser/UserParser.cpp index 363cb9e..b385414 100644 --- a/src/parser/UserParser.cpp +++ b/src/parser/UserParser.cpp @@ -10,13 +10,12 @@ * * @param tokens The parameters of the command. * - * @throws `ParserException` if the number of arguments is less than the expected. + * @throws `NeedMoreParamsException` if the number of arguments is less than the expected. * @return The parsed command. */ ICommand *UserParser::parse(const std::vector& tokens) { - if (tokens.size() < 5) { + if (tokens.size() < 5) throw NeedMoreParamsException("USER"); - } std::string username = tokens[1]; std::string hostname = tokens[2]; std::string serverName = tokens[3]; @@ -24,8 +23,7 @@ ICommand *UserParser::parse(const std::vector& tokens) { // remove the ':' from the real name: realName = realName.substr(1); - for (size_t i = 5; i < tokens.size(); i++) { + for (size_t i = 5; i < tokens.size(); i++) realName += " " + tokens[i]; - } return new UserCommand(username, hostname, serverName, realName); } \ No newline at end of file