first commit

This commit is contained in:
lfirmin
2026-05-25 10:24:09 +02:00
commit 332cb7501a
12 changed files with 763 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
#include "CommandValidator.hpp"
CommandValidator::CommandValidator(void) {}
CommandValidator::CommandValidator(const CommandValidator &other) { (void)other; }
CommandValidator &CommandValidator::operator=(const CommandValidator &other) { (void)other; return (*this); }
CommandValidator::~CommandValidator(void) {}
bool CommandValidator::isKnownCommand(const std::string &cmd)
{
return (cmd == "PASS"
|| cmd == "NICK"
|| cmd == "USER"
|| cmd == "JOIN"
|| cmd == "PART"
|| cmd == "PRIVMSG"
|| cmd == "NOTICE"
|| cmd == "QUIT"
|| cmd == "PING"
|| cmd == "PONG"
|| cmd == "KICK"
|| cmd == "INVITE"
|| cmd == "TOPIC"
|| cmd == "MODE");
}
/*
This is only syntactic/minimal validation.
The real command handler still checks:
- password correctness
- nickname already used
- channel exists
- permissions/operator status
- invite-only/key/limit modes
*/
bool CommandValidator::hasEnoughParams(const IrcMessage &msg)
{
const std::string &cmd = msg.getCommand();
if (cmd == "PASS") return (msg.paramCount() >= 1);
if (cmd == "NICK") return (msg.paramCount() >= 1);
if (cmd == "USER") return (msg.paramCount() >= 4);
if (cmd == "JOIN") return (msg.paramCount() >= 1);
if (cmd == "PART") return (msg.paramCount() >= 1);
if (cmd == "PRIVMSG") return (msg.paramCount() >= 2);
if (cmd == "NOTICE") return (msg.paramCount() >= 2);
if (cmd == "QUIT") return (true);
if (cmd == "PING") return (msg.paramCount() >= 1);
if (cmd == "PONG") return (msg.paramCount() >= 1);
if (cmd == "KICK") return (msg.paramCount() >= 2);
if (cmd == "INVITE") return (msg.paramCount() >= 2);
if (cmd == "TOPIC") return (msg.paramCount() >= 1);
if (cmd == "MODE") return (msg.paramCount() >= 1);
return (false);
}
/*
Before registration, usually only PASS, NICK, USER, PING/PONG/QUIT are useful.
*/
bool CommandValidator::needsRegistration(const std::string &cmd)
{
return !(cmd == "PASS"
|| cmd == "NICK"
|| cmd == "USER"
|| cmd == "PING"
|| cmd == "PONG"
|| cmd == "QUIT");
}
+62
View File
@@ -0,0 +1,62 @@
#include "IrcMessage.hpp"
IrcMessage::IrcMessage(void) : _valid(false)
{
}
IrcMessage::IrcMessage(const IrcMessage &other)
{
*this = other;
}
IrcMessage &IrcMessage::operator=(const IrcMessage &other)
{
if (this != &other)
{
_prefix = other._prefix;
_command = other._command;
_params = other._params;
_raw = other._raw;
_valid = other._valid;
_error = other._error;
}
return (*this);
}
IrcMessage::~IrcMessage(void)
{
}
void IrcMessage::setPrefix(const std::string &prefix) { _prefix = prefix; }
void IrcMessage::setCommand(const std::string &command) { _command = command; }
void IrcMessage::addParam(const std::string &param) { _params.push_back(param); }
void IrcMessage::setRaw(const std::string &raw) { _raw = raw; }
void IrcMessage::setValid(bool valid) { _valid = valid; }
void IrcMessage::setError(const std::string &error) { _error = error; }
const std::string &IrcMessage::getPrefix(void) const { return (_prefix); }
const std::string &IrcMessage::getCommand(void) const { return (_command); }
const std::vector<std::string> &IrcMessage::getParams(void) const { return (_params); }
const std::string &IrcMessage::getRaw(void) const { return (_raw); }
bool IrcMessage::isValid(void) const { return (_valid); }
const std::string &IrcMessage::getError(void) const { return (_error); }
bool IrcMessage::hasPrefix(void) const
{
return (!_prefix.empty());
}
bool IrcMessage::hasParam(size_t index) const
{
return (index < _params.size());
}
const std::string &IrcMessage::param(size_t index) const
{
return (_params[index]);
}
size_t IrcMessage::paramCount(void) const
{
return (_params.size());
}
+142
View File
@@ -0,0 +1,142 @@
#include "IrcParser.hpp"
#include <cctype>
IrcParser::IrcParser(void) {}
IrcParser::IrcParser(const IrcParser &other) { (void)other; }
IrcParser &IrcParser::operator=(const IrcParser &other) { (void)other; return (*this); }
IrcParser::~IrcParser(void) {}
bool IrcParser::isSpace(char c)
{
return (c == ' ' || c == '\t');
}
std::string IrcParser::trimLeft(const std::string &s)
{
size_t i = 0;
while (i < s.size() && isSpace(s[i]))
i++;
return (s.substr(i));
}
std::string IrcParser::trimRight(const std::string &s)
{
size_t end = s.size();
while (end > 0 && (s[end - 1] == '\r' || s[end - 1] == '\n' || isSpace(s[end - 1])))
end--;
return (s.substr(0, end));
}
std::string IrcParser::toUpper(const std::string &s)
{
std::string out = s;
for (size_t i = 0; i < out.size(); ++i)
out[i] = static_cast<char>(std::toupper(static_cast<unsigned char>(out[i])));
return (out);
}
bool IrcParser::isCommandChar(char c)
{
return (std::isalpha(static_cast<unsigned char>(c)) || std::isdigit(static_cast<unsigned char>(c)));
}
bool IrcParser::isValidCommand(const std::string &command)
{
if (command.empty())
return (false);
for (size_t i = 0; i < command.size(); ++i)
{
if (!isCommandChar(command[i]))
return (false);
}
return (true);
}
/*
IRC params rule:
- parameters are separated by spaces
- if a parameter starts with ':', it is the trailing parameter
- the trailing parameter may contain spaces and is always the last param
Example:
PRIVMSG #chan :hello les gars
params[0] = "#chan"
params[1] = "hello les gars"
*/
void IrcParser::parseParams(const std::string &s, size_t pos, IrcMessage &msg)
{
while (pos < s.size())
{
while (pos < s.size() && isSpace(s[pos]))
pos++;
if (pos >= s.size())
break;
if (s[pos] == ':')
{
msg.addParam(s.substr(pos + 1));
break;
}
size_t start = pos;
while (pos < s.size() && !isSpace(s[pos]))
pos++;
msg.addParam(s.substr(start, pos - start));
}
}
IrcMessage IrcParser::parseLine(const std::string &line)
{
IrcMessage msg;
std::string s;
size_t pos;
size_t start;
msg.setRaw(line);
s = trimRight(trimLeft(line));
if (s.empty())
{
msg.setError("empty line");
return (msg);
}
/*
Optional prefix:
:nick!user@host COMMAND params...
In ft_irc, clients usually do not send prefixes, but accepting it makes the parser robust.
*/
pos = 0;
if (s[pos] == ':')
{
size_t prefixEnd = s.find(' ', pos);
if (prefixEnd == std::string::npos)
{
msg.setError("prefix without command");
return (msg);
}
msg.setPrefix(s.substr(1, prefixEnd - 1));
pos = prefixEnd + 1;
while (pos < s.size() && isSpace(s[pos]))
pos++;
}
start = pos;
while (pos < s.size() && !isSpace(s[pos]))
pos++;
msg.setCommand(toUpper(s.substr(start, pos - start)));
if (!isValidCommand(msg.getCommand()))
{
msg.setError("invalid command");
return (msg);
}
parseParams(s, pos, msg);
msg.setValid(true);
return (msg);
}
+94
View File
@@ -0,0 +1,94 @@
#include "ParseBuffer.hpp"
#include "IrcParser.hpp"
ParseBuffer::ParseBuffer(void) : _maxLineSize(512)
{
}
ParseBuffer::ParseBuffer(size_t maxLineSize) : _maxLineSize(maxLineSize)
{
}
ParseBuffer::ParseBuffer(const ParseBuffer &other)
{
*this = other;
}
ParseBuffer &ParseBuffer::operator=(const ParseBuffer &other)
{
if (this != &other)
{
_buffer = other._buffer;
_maxLineSize = other._maxLineSize;
}
return (*this);
}
ParseBuffer::~ParseBuffer(void)
{
}
void ParseBuffer::append(const char *data, size_t len)
{
if (data && len > 0)
_buffer.append(data, len);
}
/*
Extract lines terminated by:
- "\r\n" standard IRC
- "\n" useful for netcat/manual tests
The returned line does not include CRLF/LF.
*/
bool ParseBuffer::extractOneLine(std::string &line)
{
size_t lf = _buffer.find('\n');
if (lf == std::string::npos)
return (false);
line = _buffer.substr(0, lf);
if (!line.empty() && line[line.size() - 1] == '\r')
line.erase(line.size() - 1);
_buffer.erase(0, lf + 1);
return (true);
}
std::vector<IrcMessage> ParseBuffer::extractMessages(void)
{
std::vector<IrcMessage> messages;
std::string line;
while (extractOneLine(line))
{
IrcMessage msg;
if (line.size() + 2 > _maxLineSize)
{
msg.setRaw(line);
msg.setError("line too long");
msg.setValid(false);
}
else
msg = IrcParser::parseLine(line);
messages.push_back(msg);
}
return (messages);
}
void ParseBuffer::clear(void)
{
_buffer.clear();
}
const std::string &ParseBuffer::raw(void) const
{
return (_buffer);
}
size_t ParseBuffer::size(void) const
{
return (_buffer.size());
}
+39
View File
@@ -0,0 +1,39 @@
#include "Server.hpp"
void Server::start(void)
{
_serverFd = socket(AF_INET, SOCK_STREAM, 0);
if (_serverFd == -1)
throw std::runtime_error(strerror(errno));
int opt = 1;
if (setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1)
{
close(_serverFd);
throw std::runtime_error(strerror(errno));
}
if (fcntl(_serverFd, F_SETFL, O_NONBLOCK) == -1)
{
close(_serverFd);
throw std::runtime_error(strerror(errno));
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(_port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(_serverFd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
close(_serverFd);
throw std::runtime_error(strerror(errno));
}
if (listen(_serverFd, SOMAXCONN) == -1)
{
close(_serverFd);
throw std::runtime_error(strerror(errno));
}
struct pollfd pollfd;
pollfd.fd = _serverFd;
pollfd.events = POLLIN;
pollfd.revents = 0;
_fds.push_back(pollfd);
}