Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
*.o

# test binaries (built locally from tests/)
test_parser
test_tok

.DS_Store

.vscode
Expand Down
33 changes: 33 additions & 0 deletions include/config/ConfigParser.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#ifndef CONFIG_PARSER_HPP
#define CONFIG_PARSER_HPP

#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include <fstream>
#include <stdexcept>
#include "Token.hpp"
#include "Config.hpp"
#include "ServerConfig.hpp"
#include "LocationConfig.hpp"

class ConfigParser {
private:
std::vector<Token> _tokens;
size_t _pos;
const Token &current();
void advance();
void expect(TokenType type);
ServerConfig parseServer();
LocationConfig parseLocation();
void parseServerDirective(ServerConfig& server);
void parseLocationDirective(LocationConfig &location);
size_t parseSize(const std::string& value);


public:
Config parse(const std::string& path);
};

#endif
21 changes: 21 additions & 0 deletions include/config/Token.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#ifndef TOKEN_HPP
#define TOKEN_HPP

#include <string>

enum TokenType {
WORD,
NUMBER,
LBRACE,
RBRACE,
SEMICOLON,
END_OF_FILE
};

struct Token {
TokenType type;
std::string value;
int line;
};

#endif
18 changes: 18 additions & 0 deletions include/config/Tokenizer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef TOKENIZER_HPP
#define TOKENIZER_HPP

#include <string>
#include <vector>
#include <cctype>
#include "Token.hpp"

class Tokenizer {
private:
Token makeSymbol(TokenType type, const std::string &value, int line);
Token readWordOrNumber(const std::string &content, int line, size_t &i);

public:
std::vector<Token> tokenize(const std::string& content);
};

#endif
212 changes: 212 additions & 0 deletions src/config/ConfigParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#include "../../include/config/ConfigParser.hpp"
#include "../../include/config/Tokenizer.hpp"
#include <cstdlib>
#include <cctype>


Config ConfigParser::parse(const std::string &path)
{
Config config;
Tokenizer tokenizer;

std::ifstream file(path.c_str());
if (!file.is_open())
throw std::runtime_error("Cannot open file: " + path);
std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();

_tokens = tokenizer.tokenize(content);
_pos = 0;

while (current().type != END_OF_FILE)
{
ServerConfig server = parseServer();
config.addServer(server);
}

return config;
}

const Token &ConfigParser::current()
{
return (_tokens[_pos]);
}
void ConfigParser::advance()
{
if (_pos + 1 < _tokens.size())
_pos++;
}

void ConfigParser::expect(TokenType type)
{
if (current().type != type)
{
std::ostringstream oss;
oss << "Unexpected token at line " << current().line;
throw std::runtime_error(oss.str());
}
advance();
}

ServerConfig ConfigParser::parseServer()
{
ServerConfig server;

if (current().type != WORD || current().value != "server")
throw std::runtime_error("Expected 'server' keyword");
advance();
expect(LBRACE);
while (current().type != RBRACE && current().type != END_OF_FILE)
{
if (current().type == WORD && current().value == "location")
server.addLocation(parseLocation());
else
parseServerDirective(server);
}
expect(RBRACE);
return server;
}

void ConfigParser::parseServerDirective(ServerConfig& server)
{
std::string name = current().value;
advance();

if (name == "listen")
{
if (current().type != NUMBER)
throw std::runtime_error("listen expects a number");
server.setPort(std::atoi(current().value.c_str()));
advance();
}
else if (name == "host")
{
server.setHost(current().value);
advance();
}
else if (name == "server_name")
{
server.setServerName(current().value);
advance();
}
else if (name == "client_max_body_size")
{
server.setMaxBodySize(parseSize(current().value));
advance();
}
else if (name == "error_page")
{
int code = std::atoi(current().value.c_str());
advance();
server.addErrorPage(code, current().value);
advance();
}
else
throw std::runtime_error("Unknown server directive: " + name);
expect(SEMICOLON);
}

LocationConfig ConfigParser::parseLocation()
{
LocationConfig location;

advance();
location.setPath(current().value);
advance();
expect(LBRACE);

while (current().type != RBRACE && current().type != END_OF_FILE)
parseLocationDirective(location);
expect(RBRACE);
return location;
}

void ConfigParser::parseLocationDirective(LocationConfig &location)
{
std::string name = current().value;
advance();

if (name == "root")
{
location.setRoot(current().value);
advance();
}
else if (name == "index")
{
location.setIndex(current().value);
advance();
}
else if (name == "autoindex")
{
if (current().value == "off")
location.setAutoindex(false);
else if (current().value == "on")
location.setAutoindex(true);
else
throw std::runtime_error("Unknown value: " + name + current().value);
advance();
}
else if (name == "methods")
{
while (current().type != SEMICOLON && current().type != END_OF_FILE)
{
location.addMethod(current().value);
advance();
}
}
else if (name == "upload_store")
{
location.setUploadPath(current().value);
advance();
}
else if (name == "cgi_extensions")
{
location.setCgiExtension(current().value);
advance();
location.setCgiPath(current().value);
advance();
}
else if (name == "redirect")
{
location.setRedirectUrl(current().value);
advance();
}
else
throw std::runtime_error("Unknown location directive: " + name);
expect(SEMICOLON);
}

size_t ConfigParser::parseSize(const std::string& value)
{
if (value.empty())
throw std::runtime_error("client_max_body_size: empty value");

size_t i = 0;
while (i < value.size() && std::isdigit(static_cast<unsigned char>(value[i])))
++i;
if (i == 0)
throw std::runtime_error("client_max_body_size: expected a number, got '" + value + "'");

size_t bytes = std::strtoul(value.substr(0, i).c_str(), NULL, 10);

if (i == value.size())
return bytes;
if (value.size() - i != 1)
throw std::runtime_error("client_max_body_size: invalid suffix in '" + value + "'");

size_t multiplier;
switch (value[i])
{
case 'k': case 'K': multiplier = 1024UL; break;
case 'm': case 'M': multiplier = 1024UL * 1024UL; break;
case 'g': case 'G': multiplier =
1024UL * 1024UL * 1024UL; break;
default:
throw std::runtime_error("client_max_body_size: unknown suffix in '" + value + "'");
}
if (bytes != 0 && multiplier > (static_cast<size_t>(-1) / bytes))
throw std::runtime_error("client_max_body_size: value too large '" + value + "'");

return bytes * multiplier;
}
70 changes: 70 additions & 0 deletions src/config/Tokenizer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "../../include/config/Tokenizer.hpp"

std::vector<Token> Tokenizer::tokenize(const std::string &content)
{
std::vector<Token> tokenList;
int line = 1;
size_t i = 0;

while (content[i])
{
if (content[i] == '\r' || content[i] == ' ' || content[i] == '\t')
i++;
else if (content[i] == '\n')
{
line++;
i++;
}
else if (content[i] == '{')
{
tokenList.push_back(makeSymbol(LBRACE, "{", line));
i++;
}
else if (content[i] == '}')
{
tokenList.push_back(makeSymbol(RBRACE, "}", line));
i++;
}
else if (content[i] == ';')
{
tokenList.push_back(makeSymbol(SEMICOLON, ";", line));
i++;
}
else if (content[i] == '#')
{
while (content[i] && content[i] != '\n')
i++;
}
else
tokenList.push_back(readWordOrNumber(content, line, i));
}
tokenList.push_back(makeSymbol(END_OF_FILE, "", line));
return tokenList;
}

Token Tokenizer::makeSymbol(TokenType type, const std::string &value, int line)
{
Token t;
t.type = type;
t.value = value;
t.line = line;
return t;
}

Token Tokenizer::readWordOrNumber(const std::string &content, int line, size_t &i)
{
Token t;
t.line = line;
if (isdigit(content[i]))
t.type = NUMBER;
else
t.type = WORD;
while (content[i] && content[i] != ' ' && content[i] != '\t' && content[i] != '\r'
&& content[i] != '\n' && content[i] != '{' && content[i] != '}'
&& content[i] != ';' && content[i] != '#')
{
t.value += content[i];
i++;
}
return t;
}
Loading
Loading