Browse Source

save to modify cgi

master
nicolas-arnaud 2 years ago
parent
commit
948e087227
  1. 7
      Makefile
  2. 5
      default.json
  3. 3
      includes/Client.hpp
  4. 1
      includes/Master.hpp
  5. 3
      includes/Server.hpp
  6. 4
      includes/webserv.hpp
  7. 25
      public/html/index.py
  8. 13
      srcs/json/Nodes.cpp
  9. 11
      srcs/json/Parser.cpp
  10. 61
      srcs/json/Token.cpp
  11. 21
      srcs/load/Env.cpp
  12. 38
      srcs/load/Server.cpp
  13. 30
      srcs/sock/Client.cpp
  14. 57
      srcs/sock/Master.cpp
  15. 2
      srcs/webserv.cpp

7
Makefile

@ -5,13 +5,16 @@ SRCS= srcs/webserv.cpp srcs/tools.cpp srcs/debug.cpp \
srcs/json/Nodes.cpp srcs/json/Token.cpp srcs/json/Parser.cpp
OBJS= $(SRCS:.cpp=.o)
CXX=c++
CXXFLAGS= -g -I includes -Werror -Wextra -Wall -std=c++98
CXXFLAGS= -I includes -Werror -Wextra -Wall -std=c++98
all : $(NAME)
$(NAME): $(OBJS)
$(CXX) -g -fsanitize=address $(OBJS) -o $(NAME)
$(CXX) $(OBJS) -o $(NAME)
debug:
$(CXX) -I includes -Werror -Wextra -Wall -std=c++98 -g -fsanitize=address $(SRCS) -o $(NAME) -D DEBUG=1
clean:
rm -rf $(OBJS)

5
default.json

@ -13,6 +13,11 @@
"listens": ["localhost"],
"root": "html/",
"indexs": ["index.html"]
},
{
"server_name": "localhost",
"root": "html/",
"indexs": ["index.html"]
},
{
"server_name": "narnaud.42.fr",

3
includes/Client.hpp

@ -20,8 +20,7 @@ class Client {
bool check_method(void);
void create_file(string path);
void cgi(string cgi_path, string path);
void send_redir(int redir_code, string opt);
void send_error(int error_code);
void send_error(int error_code, string opt = "");
void send_answer(string msg);
#ifdef DEBUG

1
includes/Master.hpp

@ -8,7 +8,6 @@ class Master {
public:
Master(ip_port_t listen);
Master(int fd, Master *parent);
~Master(void);
void pre_select(void);

3
includes/Server.hpp

@ -9,8 +9,7 @@ public:
std::vector<ip_port_t> _listens; ///< The list of listens the server which are linked to the server.
Server(JSONNode *server);
~Server(void);
Master *create_master(string str);
std::vector<Master *> get_sockets(JSONNode *server);
std::vector<Master *> create_masters(JSONNode *server);
Route *choose_route(string uri);
string getName(void);
};

4
includes/webserv.hpp

@ -1,5 +1,7 @@
#pragma once
#define DEBUG 1
#ifndef DEBUG
#define DEBUG 0
#endif
#include <arpa/inet.h>
#include <dirent.h>

25
public/html/index.py

@ -1,21 +1,18 @@
#!/usr/bin/python
# Import modules for CGI handling
import cgi, cgitb
import cgi
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name')
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Hello - Second CGI Program</title>"
print "</head>"
print "<body>"
print "<h2>Hello %s %s</h2>" % (first_name, last_name)
print "</body>"
print "</html>"
print("Content-Type: text/html")
print()
print("<html>")
print("<head>")
print("<title>Hello - Second CGI Program</title>")
print("</head>")
print("<body>")
print("<h2>Hello %s %s</h2>" % (first_name, last_name))
print("</body>")
print("</html>")

13
srcs/json/Nodes.cpp

@ -1,5 +1,18 @@
/**
* @file Nodes.cpp
* @brief A node object is a container for each json values or blocks. It can contain either a std::map<String,
* std::Node*> a std::vector<Node*> a String* a number or a bool.
*
* @author Narnaud
* @version 0.1
* @date 2023-01-17
*/
#include "webserv.hpp"
/**
* @brief Destructor
* Destroy Node and all sub Nodes he contain.
*/
JSONNode::~JSONNode(void) {
switch (type) {
case OBJECT:

11
srcs/json/Parser.cpp

@ -1,5 +1,12 @@
#include "webserv.hpp"
/**
* @file Parser.cpp
* @brief The parser lead the Tokenizer to travel along configuration file and create nodes with tokens.
* @author Narnaud
* @version 0.1
* @date 2023-01-17
*/
JSONParser::JSONParser(const string filename) : tokenizer(filename) {}
JSONNode *JSONParser::parse() {
@ -8,7 +15,7 @@ JSONNode *JSONParser::parse() {
Token token;
token = tokenizer.getToken();
if (token.type == CURLY_OPEN) parsed = parseObject();
if (!parsed) std::cout << "Configuration file isn't valid\n";
else throw std::logic_error("Invalid json syntax: json file must be an unique object block");
return parsed;
}
@ -23,7 +30,7 @@ JSONNode *JSONParser::parseObject() {
throw std::logic_error("No more tokens");
}
Token token = tokenizer.getToken();
if (token.type != STRING) throw std::logic_error("Invalid json syntax: object key isn't a string");
if (token.type != STRING) throw std::logic_error("Invalid json syntax: Unclosed string");
string key = token.value;
token = tokenizer.getToken();
if (token.type != COLON) throw std::logic_error("Invalid json syntax: missing colon");

61
srcs/json/Token.cpp

@ -1,19 +1,30 @@
/**
* @file Token.cpp
* @brief The tokenizer read json file and when asked for give us the next token encounter and his type.
* @author Narnaud
* @version 0.1
* @date 2023-01-17
*/
#include "webserv.hpp"
/**
* @brief Constructor
* Open conf file if he is readeable.
*
* @param fileName conf file path.
*/
Tokenizer::Tokenizer(string fileName) {
file.open(fileName.c_str(), std::ios::in);
if (!file.good())
cout << "File open error"
<< "\n";
if (!file.good()) std::cerr << "File open error.\n";
}
bool Tokenizer::hasMoreTokens() { return !file.eof(); }
char Tokenizer::getWithoutWhiteSpace() {
char c = ' ';
while ((c == ' ' || c == '\n') || c == '\t') {
file.get(c); // check
file.get(c);
if ((c == ' ' || c == '\n') && !file.good()) {
// cout << file.eof() << " " << file.fail() << "\n";
throw std::logic_error("Ran out of tokens");
} else if (!file.good()) {
return c;
@ -22,20 +33,17 @@ char Tokenizer::getWithoutWhiteSpace() {
return c;
}
void Tokenizer::rollBackToken() {
if (file.eof()) file.clear();
file.seekg(prevPos);
}
Token Tokenizer::getToken() {
char c;
if (file.eof()) {
cout << "Exhaused tokens"
<< "\n";
// throw std::exception("Exhausted tokens");
}
if (file.eof()) { cout << "Exhaused tokens\n"; }
prevPos = file.tellg();
c = getWithoutWhiteSpace();
Token token;
token.type = NULL_TYPE;
if (c == '"') {
@ -43,16 +51,11 @@ Token Tokenizer::getToken() {
token.value = "";
file.get(c);
while (c != '"') {
if (c == '}' || c == ']' || c == ',') throw std::logic_error("Invalid json syntax: a string is not close");
if (c == '}' || c == ']' || c == ',') throw std::logic_error("Invalid json syntax: a string is not close");
token.value += c;
file.get(c);
}
} else if (c == '{') {
token.type = CURLY_OPEN;
} else if (c == '}') {
token.type = CURLY_CLOSE;
} else if (c == '-' || (c >= '0' && c <= '9')) {
// Check if string is numeric
token.type = NUMBER;
token.value = "";
token.value += c;
@ -60,15 +63,11 @@ Token Tokenizer::getToken() {
while ((c == '-') || (c >= '0' && c <= '9') || c == '.') {
prevCharPos = file.tellg();
file.get(c);
if (file.eof()) {
break;
} else {
if ((c == '-') || (c >= '0' && c <= '9') || (c == '.')) {
token.value += c;
} else {
file.seekg(prevCharPos);
}
if ((c == '-') || (c >= '0' && c <= '9') || (c == '.')) token.value += c;
else file.seekg(prevCharPos);
}
}
} else if (c == 'f') {
@ -82,15 +81,11 @@ Token Tokenizer::getToken() {
} else if (c == 'n') {
token.type = NULL_TYPE;
file.seekg(3, std::ios_base::cur);
} else if (c == '[') {
token.type = ARRAY_OPEN;
} else if (c == ']') {
token.type = ARRAY_CLOSE;
} else if (c == ':') {
token.type = COLON;
} else if (c == ',') {
token.type = COMMA;
}
// cout << token.type << " : " << token.value << "\n";
} else if (c == '{') token.type = CURLY_OPEN;
else if (c == '}') token.type = CURLY_CLOSE;
else if (c == '[') token.type = ARRAY_OPEN;
else if (c == ']') token.type = ARRAY_CLOSE;
else if (c == ':') token.type = COLON;
else if (c == ',') token.type = COMMA;
return token;
}

21
srcs/load/Env.cpp

@ -1,11 +1,18 @@
/**
* @file Env.cpp
* @brief The main server object. Contain all servers and sockets.
* @author Narnaud
* @version 0.1
* @date 2023-01-17
*/
#include "webserv.hpp"
/*|==========|
* Environment constructor:
/**
* @brief Constructor
*
* Input: The JSONParser output
* Output: The env object containing servers and sockets vectors defined inside
* conf file by servers blocks and listens.
* The instance contain servers defined in configuration file by servers list and sockets by listens ones.
*
* @param conf The JsonParser output
*/
Env::Env(JSONNode *conf) {
try {
@ -16,7 +23,7 @@ Env::Env(JSONNode *conf) {
for (std::vector<JSONNode *>::iterator it = lst.begin(); it < lst.end(); it++) {
Server *server = new Server(*it);
this->_servers.push_back(server);
std::vector<Master *> tmp_s = server->get_sockets(*it);
std::vector<Master *> tmp_s = server->create_masters(*it);
this->_masters.insert(this->_masters.end(), tmp_s.begin(), tmp_s.end());
}
}
@ -62,11 +69,11 @@ void Env::pre_select(void) {
for (std::vector<Master *>::iterator it = this->_masters.begin(); it < this->_masters.end(); it++)
(*it)->pre_select();
}
/**
* @brief Refresh all master_sockets and their clients datas (disconnect, new
* connection, etc..) and parse requests recieved.
*/
void Env::post_select(void) {
cout << "==> Handle requests and answers:\n";
for (std::vector<Master *>::iterator it = this->_masters.begin(); it < this->_masters.end(); it++) try {

38
srcs/load/Server.cpp

@ -44,25 +44,6 @@ Server::~Server(void) {
*/
string Server::getName(void) { return _name; }
/**
* @brief Master socket safe creation.
*
* @param str a "ip:port" string
*
* @return a Master socket object or NULL if the creation failed.
*/
Master *Server::create_master(string str) {
ip_port_t listen = get_ip_port_t(str);
if (listen.ip.at(0) != '[') {
try {
_listens.push_back(listen);
Master *sock = new Master(listen);
return (sock);
} catch (std::exception &e) { std::cerr << e.what() << '\n'; }
} else cout << "Listen: IPv6 isn't supported\n";
return NULL;
}
/**
* @brief Create server's defined sockets:
*
@ -71,18 +52,23 @@ Master *Server::create_master(string str) {
* @retrn A vector containing all the succesfull created sockets using
* listens from the server block.
*/
std::vector<Master *> Server::get_sockets(JSONNode *server) {
std::vector<Master *> Server::create_masters(JSONNode *server) {
JSONObject datas = server->obj();
std::vector<Master *> ret;
Master *tmp;
ip_port_t listen;
if (datas["listens"]) {
JSONList listens = datas["listens"]->lst();
for (JSONList::iterator it = listens.begin(); it != listens.end(); it++) {
if ((tmp = create_master((*it)->str()))) ret.push_back(tmp);
}
} else if ((tmp = create_master("0.0.0.0"))) ret.push_back(tmp);
for (JSONList::iterator it = listens.begin(); it != listens.end(); it++)
_listens.push_back(get_ip_port_t((*it)->str()));
} else _listens.push_back(get_ip_port_t("0.0.0.0"));
for (std::vector<ip_port_t>::iterator listen = _listens.begin(); listen < _listens.end(); listen++) {
if (listen->ip.at(0) != '[') try {
ret.push_back(new Master(*listen));
} catch (std::exception &e) { std::cerr << e.what() << '\n'; }
else cout << "Listen: IPv6 isn't supported\n";
}
return ret;
// else if ((tmp = create_master("0.0.0.0"))) ret.push_back(tmp); return ret;
}
/**

30
srcs/sock/Client.cpp

@ -34,8 +34,7 @@ void Client::init(void) {
bool Client::getRequest(Env *env, string paquet) {
if (paquet.length() < 1) send_error(403);
// if (DEBUG)
debug_block("Paquet: ", paquet);
if (DEBUG) debug_block("Paquet: ", paquet);
if (header_pick("Method:", 0) != "") return getBody(paquet);
vec_string lines = split(paquet, "\r\n");
for (vec_string::iterator it = lines.begin(); it < lines.end(); it++) {
@ -152,6 +151,12 @@ void Client::create_file(string path) {
}
}
/**
* @brief Launch cgi binary to parse the file requested by the client.
*
* @param cgi_path The cgi binary location specified in configuration file according to the file requested.
* @param path The path to the file requested.
*/
void Client::cgi(string cgi_path, string path) {
int status;
int fd[2];
@ -185,15 +190,15 @@ void Client::cgi(string cgi_path, string path) {
send_answer(ss.str());
}
void Client::send_redir(int redir_code, string opt) {
switch (redir_code) {
case 301:
return send_answer("HTTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + "\r\n\r\n");
}
}
void Client::send_error(int error_code) {
/**
* @brief Send an error answer to the client.
*
* @param error_code The HTTP response code to send.
*/
void Client::send_error(int error_code, string opt = "") {
switch (error_code) {
case 301:
return send_answer("HTTP/1.1 301 Moved Permanently\r\nLocation: " + opt + "\r\n\r\n");
case 400:
return send_answer("HTTP/1.1 400 Bad Request\r\nContent-Length: 0\r\n\r\n");
case 403:
@ -209,6 +214,11 @@ void Client::send_error(int error_code) {
}
}
/**
* @brief Send an answer to the client.
*
* @param msg The HTTP message to send.
*/
void Client::send_answer(string msg) {
if (DEBUG) debug_block("ANSWER: ", msg);
#ifdef __linux__

57
srcs/sock/Master.cpp

@ -7,20 +7,21 @@
*/
#include "webserv.hpp"
/* Master destructor */
/**
* @brief Destructor
* Close master socket descriptor.
*/
Master::~Master(void) {
close(_fd);
cout << "Destroyed master socket\n";
}
/* |==========|
* Master constructor
/**
* @brief Constructor
* Try to create a socket listening to ip and port defined by input.
* If the creation success, the socket is then ready to select for new clients.
* If no exception if thrown, the creation success and the socket is then ready to select for new clients.
*
* Input: A ip_port_t structure which contain the ip and the port the master
* care about.
* Output: A Master object.
* @param list An ip_port_t struct which contain the ip and the port the master listen.
*/
Master::Master(ip_port_t list) : _listen(list) {
int x = 1, port = _listen.port;
@ -43,7 +44,10 @@ Master::Master(ip_port_t list) : _listen(list) {
if (_fd < _min_fd) _min_fd = _fd;
}
/* Set into static Master::readfds the active fds which will be select.*/
/**
* @brief The pre select operations:
* Add master's socket descriptor and each one of his childs to the select list of descriptor.
*/
void Master::pre_select(void) {
FD_SET(_fd, &_readfds);
if (_fd > _max_fd) _max_fd = _fd;
@ -59,6 +63,15 @@ void Master::pre_select(void) {
* - look then if known clients sent requests or disconnected
* - if client sent request, handle it to generate answer adapted
*/
/**
* @brief Checkk master and his clients sockets after select performed.
* - First look for new clients
* - Then handle clients awaiting action:
* - Disconnect them if nothing's new on the socket (request altready handled)
* - Read and parse request else until it's ready to answer and does it.
*
* @param env The environment object which contain the liste of servers to know which one the client is trying to reach.
*/
void Master::post_select(Env *env) {
int valread;
int addrlen = sizeof(_address);
@ -77,7 +90,7 @@ void Master::post_select(Env *env) {
for (std::vector<Client *>::iterator it = _childs.begin(); it < _childs.end(); it++) {
child_fd = (*it)->_fd;
if (FD_ISSET(child_fd, &_readfds)) {
valread = read(child_fd, buffer, 128);
valread = read(child_fd, buffer, 127);
buffer[valread] = '\0';
if (valread == 0) {
getpeername(child_fd, (struct sockaddr *)&_address, (socklen_t *)&addrlen);
@ -90,20 +103,20 @@ void Master::post_select(Env *env) {
}
}
/* |==========|
* Choose the server which must handle a request
* Each server can listen multiple range_ip:port and each range_ip:port can be
* listen by multiple servers. So for each request, we must look at the socket
* which given us the client to know how the client came. If multiple servers
* listen the range from where the client came, ones with exact correspondance
* are prefered.
/**
*
* @brief Choose the server which must handle a request
* Each server can lsiten multiple range_ip:port and each range_it:port can be listen by multiple servers.
* So for each request, we must look at the socket which given us the client to know how the client came. If multiple
* servers listen the range from where the client came, ones with exact correspondance are prefered. If there are
* multiples servers listening exactly the ip the client try to reach or which listen a range which contain it, the
* first one which have the same server_name as the host the client used to reach server is used, else it's the first
* one of exact correspondance or first one which have the ip requested in his listen range.
*
* If there are multiples servers listening exactly the ip the client try to
* reach or whic listen a range which contain it, the first one which have the
* same server_name as the host the client used to reach server is used, else
* it's the first one of exact correspondance or first one which have the ip
* requested in his listen range.
* @param env The environment object.
* @param host The host the client used to reached the server.
*
* @return The server object choosen to handle the request.
*/
Server *Master::choose_server(Env *env, string host) {
std::vector<Server *> exact, inrange;
@ -129,13 +142,11 @@ Server *Master::choose_server(Env *env, string host) {
}
if (DEBUG) std::cout << "req: " << _listen.ip << ":" << _listen.port << "\n";
if (exact.size() == 0) {
std::cout << "in range server check\n";
for (std::vector<Server *>::iterator server = inrange.begin(); server < inrange.end(); server++) {
if (host == (*server)->getName()) return *server;
}
return inrange.front();
} else {
std::cout << "exact server check\n";
for (std::vector<Server *>::iterator server = exact.begin(); server < exact.end(); server++) {
if (host == (*server)->getName()) return *server;
}

2
srcs/webserv.cpp

@ -40,7 +40,7 @@ int main(int ac, char **av) {
Env env(conf);
while (1) env.cycle();
} catch (const std::exception &e) {
std::cerr << e.what();
std::cerr << e.what() << "\n";
return EXIT_FAILURE;
}
return EXIT_SUCCESS;

Loading…
Cancel
Save