Merge branch 'hugo2'

+ changed the appropriate getters for client
+ moved the functions to parse the http message
  (first line, headers, and body)
  outside client, in parsing_http_message.cpp
+ resolved some of the throw problems
This commit is contained in:
hugogogo
2022-08-09 15:54:40 +02:00
24 changed files with 205391 additions and 214 deletions

View File

@@ -5,7 +5,7 @@ CXX = c++
CXXFLAGS = -Wall -Wextra #-Werror
CXXFLAGS += $(HEADERS_D:%=-I%)
CXXFLAGS += -std=c++98
CXXFLAGS += -g
CXXFLAGS += -g3
CXXFLAGS += -MMD -MP #header dependencie
#CXXFLAGS += -O3
@@ -30,6 +30,7 @@ SRCS = main.cpp \
utils.cpp \
cgi_script.cpp \
Client.cpp \
parsing_message_http.cpp \
OBJS_D = builds
OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o)

View File

@@ -1,4 +1,30 @@
## work together
#### TODO hugo
- `_is_cgi()` and `_fill_cgi_path()`
- two cgi tests :
? - a basic form with "name" and "something", that return a html page with that
? - for GET and POST
? - a script called by a file extension in URI
#### questions
- in client, fd is in privat, so accesible by getter, is it ok ?
- in client.cpp i fill the port, is there a default one in case it's not in the request ?
- timeout server but still works ?
- path contains double "//" from `Webserv::_get()` in response.cpp
- cgi path ? defined in config ? and root path ? :
- `Client.cpp : fill_script_path()`
- `cgi.cpp : is_cgi()`
- `cgi.cpp : set_env()`
- what if the uri contains a php file, and the config said php must be handled by cgi, but the path to this php in the uri is wrong ?
- is it ok ? `http://my_site.com/cgi-bin/php-cgi` (real path)
- is it ok ? `http://my_site.com/php-cgi` (reconstruct path ?)
- is it ok ? `http://my_site.com/something/php-cgi` (what about 'something' ?)
- is it ok ? `http://my_site.com/something/cgi-bin/php-cgi` (real path with 'something' before ? )
- I don't save the STDIN and STDOUT before dup2 in child process, is it wrong ?
- the response page is received long after the cgi-script is done, why ?
---
## man
@@ -77,6 +103,37 @@
## cgi rfc
[rfc 3875](https://www.rfc-editor.org/rfc/rfc3875)
#### output cgi script :
! TODO : change all the '\n' by '\r\n'
! TODO : there is at least one header field followed by '\r\n\r\n' :
- "Content-Type"
- "Location"
- "Status"
! TODO : there is no space between filed name and ":"
! TODO?: handle Location field, either :
- local : start with '/' --> rerun the request with new uri
- client : start with '<scheme>:' --> send back status code 302
-> TODO : there is no field duplicate (resolve conflicts)
-> TODO : if status field, change server status for this one
-> TODO : if no Location field && no Status field -> status code = 200
#### summary :
- the cgi-script will send back at least one header field followed by an empty line
- this header field will be one of three :
- "Content-Type"
- "Location"
- "Status"
- the cgi-script may send back more header fields
- the server must check and modify few things :
- there is no duplicate in headers fields (if there is, resolve conflict)
- there is no space between the field name and the ":"
- the newlines are of form "\r\n", and not "\n" only
- if the location field is not present, then if the status field is not present either, then the status code is 200
- the cgi-script can return a location field, of two types :
- local redirection : start with a "/", the server must answer as if this was the request uri
- client redirection : start with <name-of-cheme>":", the server must send back a status 302 with this uri to the client
- to pass the body-message to the cgi-script, we write it into the temporary fd on which the script read it's standard input
[3.1: server responsabilities](https://www.rfc-editor.org/rfc/rfc3875#section-3.1)
- The server [...] receives the request from the client
@@ -124,10 +181,12 @@
- local redirect response :
- it must return only a Location field
- it contains a local path URI and query string ('local-pathquery')
- the local path URI must start with a "/"
- the server must generate the response for this local-pathquery
- client redirect response :
- it must return only a Location field
- it contains an absolute URI path, to indicate the client that it should reprocess the request with this URI
- the absolute URI always start with the name of scheme followed by ":"
- the http server must generate a 302 'Found' message
- client redirect response with document
- it must return a Location field with an absolute URI path
@@ -172,16 +231,6 @@
[7 and 8: usefull informations about implementation and security](https://www.rfc-editor.org/rfc/rfc3875#section-7)
#### questions :
- le cgi-script doit renvoyer au moins un header suivit d'une ligne vide
- il peut dans certains cas envoyer d'autres headers
- le serveur doit verifier qu'il n'y a pas de doublons dans les headers
- le serveur doit verifier le formatage des headers (typiquement l'encodage, par exemple pour les newlines)
- ? comment on passe le body-message au script ? section 4.2
- ? on doit gerer l'authentification ?
- ? pourquoi on doit construire un script-cgi ? section 3.3
- ? si l'uri correspond au script-cgi, ca appel le script donc ? section 3.3
---
## cgi env variables

View File

@@ -16,6 +16,7 @@ server {
location /board {
allow_methods GET;
root ./www/html;
cgi_ext php cgi
}
location /board/content {

View File

@@ -6,13 +6,29 @@
*********************************************/
Client::Client()
: fd(0),
lsocket(NULL),
status(0),
: status(0),
header_complete(false),
read_body_size(0),
assigned_server(NULL),
assigned_location(NULL)
assigned_location(NULL),
_fd(0),
_port(""),
_ip(""),
_lsocket(NULL)
{
return;
}
Client::Client(int afd, listen_socket *lsocket, std::string aport, std::string aip)
: status(0),
header_complete(false),
read_body_size(0),
assigned_server(NULL),
assigned_location(NULL),
_fd(afd),
_port(aport),
_ip(aip),
_lsocket(lsocket)
{
return;
}
@@ -21,12 +37,33 @@ Client::~Client() {
return;
}
// copy constructor :
// Client::Client( Client const & src ) {}
// assignement operator :
// Client & Client::operator=( Client const & rhs ) {}
// WIP not sure fo what is more logic here
Client::Client( Client const & src )
: status ( src.status ),
header_complete ( src.header_complete ),
read_body_size ( src.read_body_size ),
assigned_server ( src.assigned_server ),
assigned_location ( src.assigned_location ),
_fd ( src._fd ),
_port ( src._port ),
_ip ( src._ip ),
_lsocket ( src._lsocket )
{
raw_request = src.raw_request;
response = src.response;
// buf = strdup(src.buf); // TODO: this doesn't work
return;
}
// WIP placeholder because of const values
Client & Client::operator=( Client const & rhs )
{
if ( this != &rhs )
{
// stuff
}
return *this;
}
/*********************************************
@@ -39,22 +76,35 @@ Client::~Client() {
// https://www.tutorialspoint.com/http/http_requests.htm
void Client::parse_request()
{
std::string sub;
std::vector<std::string> list;
size_t pos;
std::map<std::string, std::string> headers;
std::string body;
pos = (raw_request).find(CRLF CRLF);
sub = (raw_request).substr(0, pos);
list = split(sub, '\n');
// request_line
_parse_request_line(*list.begin());
list.erase(list.begin());
// headers
_parse_request_headers(list);
//body- message
_parse_request_body(pos + 4);
// DEBUG
std::cout << "\nREQUEST ____________\n" << raw_request << "\n_____________\n";
// add "raw_request.clear()" after parsing ? for little less memory usage ?
_parse_request_line();
_parse_request_headers();
_parse_request_body();
_parse_port_hostname(this->get_rq_headers("Host"));
raw_request.clear();
}
bool Client::fill_script_path(std::string script)
{
size_t pos;
int len = script.size();
std::string path = this->get_rq_abs_path();
std::string tmp;
pos = path.find(script);
if (pos == 0)
{
tmp = path.substr(0, pos + len);
_request.script.path = "./srcs" + tmp; // TODO: root path ?
_request.script.info = path.substr(pos + len);
return true;
}
return false;
}
void Client::clear()
@@ -71,88 +121,135 @@ void Client::clear()
void Client::clear_request()
{
clear_script();
_request.method = UNKNOWN;
_request.path.clear();
_request.uri.clear();
_request.version.clear();
_request.headers.clear();
_request.body.clear();
_request.abs_path.clear();
_request.query.clear();
_request.port.clear();
_request.hostname.clear();
}
void Client::clear_script()
{
_request.script.path.clear();
_request.script.info.clear();
}
/*********************************************
* GETTERS
*********************************************/
// client side
int Client::get_cl_fd() const { return _fd; }
const std::string & Client::get_cl_ip() const { return _ip; }
const std::string & Client::get_cl_port() const { return _port; }
const listen_socket * Client::get_cl_lsocket() const { return _lsocket; }
// requette
http_method Client::get_rq_method() const { return _request.method; }
std::string Client::get_rq_method_str() const
{ return ::http_methods_to_str(_request.method); }
std::string Client::get_rq_uri() const { return _request.uri; }
std::string Client::get_rq_abs_path() const { return _request.abs_path; }
std::string Client::get_rq_query() const { return _request.query; }
std::string Client::get_rq_version() const { return _request.version; }
std::string Client::get_rq_body() const { return _request.body; }
std::string Client::get_rq_port() const { return _request.port; }
std::string Client::get_rq_hostname() const { return _request.hostname; }
std::string Client::get_rq_script_path()const { return _request.script.path; }
std::string Client::get_rq_script_info()const { return _request.script.info; }
std::string Client::get_rq_headers(const std::string & key) const
{
std::map<std::string, std::string>::const_iterator it;
it = _request.headers.find(::str_tolower(key));
if (it == _request.headers.end())
return "";
return it->second;
}
http_method Client::get_method() { return _request.method; }
std::string &Client::get_path() { return _request.path; }
std::string &Client::get_version() { return _request.version; }
std::string &Client::get_body() { return _request.body; }
std::string &Client::get_headers(const std::string &key) { return _request.headers[key]; }
/*********************************************
* PRIVATE MEMBER FUNCTIONS
*********************************************/
void Client::_parse_request_line( std::string rline )
void Client::_parse_request_line()
{
std::vector<std::string> sline;
std::string tmp;
std::vector<std::string> line;
int ret;
sline = split(rline, ' ');
if (sline.size() != 3)
ret = ::parse_http_first_line(raw_request, line);
if (ret != 3)
{
std::cerr << "err _parse_request_line(): ";
throw std::runtime_error("bad request-line header");
std::cerr << "err _parse_first_line(): wrong number of elements (" << ret << " instead of 3)\n";
status = 400; // "bad request"
}
// method
tmp = ::trim(sline[0], ' ');
tmp = ::trim(tmp, '\r');
_request.method = str_to_http_method(tmp);
// TODO uri in request_line
// https://www.rfc-editor.org/rfc/rfc7230#section-5.3
// https://stackoverflow.com/questions/40311306/when-is-absoluteuri-used-from-the-http-request-specs
tmp = ::trim(sline[1], ' ');
tmp = ::trim(tmp, '\r');
_request.path = tmp;
// http version
tmp = ::trim(sline[2], ' ');
tmp = ::trim(tmp, '\r');
_request.version = tmp;
}
void Client::_parse_request_headers( std::vector<std::string> list )
{
std::string key;
std::string val;
std::vector<std::string>::iterator it;
size_t pos;
for (it = list.begin(); it != list.end(); it++)
else
{
pos = (*it).find(':');
key = (*it).substr( 0, pos );
key = ::trim(key, ' ');
key = ::trim(key, '\r');
val = (*it).substr( pos + 1 );
val = ::trim(val, ' ');
val = ::trim(val, '\r');
_request.headers.insert( std::pair<std::string, std::string>(key, val) );
_request.method = str_to_http_method(line[0]);
_request.uri = line[1];
_parse_request_uri(line[1]);
_request.version = line[2];
}
}
void Client::_parse_request_body( size_t pos )
void Client::_parse_request_uri( std::string uri )
{
// TODO : a revoir avec une std::string,
// pour ne pas avoir le probleme d'un '0' qui marque la fin des données
std::string body = &raw_request[pos];
size_t pos;
_request.body = body;
pos = uri.find("?");
if (pos != std::string::npos)
_request.query = uri.substr(pos + 1);
else
_request.query = "";
_request.abs_path = uri.substr(0, pos);
}
void Client::_parse_request_headers()
{
// TODO: check error and adjust status
_request.headers = ::parse_http_headers(raw_request);
}
void Client::_parse_request_body()
{
// TODO: check error and adjust status
_request.body = ::parse_http_body(raw_request);
}
void Client::_parse_port_hostname(std::string host)
{
size_t pos;
if (host == "")
std::cerr << "no host\n";
pos = host.find(':');
// port :
if (pos == std::string::npos)
_request.port = "4040"; // TODO: make equal to default port in config
else
_request.port = host.substr(pos);
if (_request.port == ":")
_request.port = "";
// hostname :
_request.hostname = host.substr(0, pos);
}
/*********************************************
* OVERLOAD
*********************************************/
bool operator==(const Client& lhs, const Client& rhs)
{ return lhs.fd == rhs.fd; }
{ return lhs.get_cl_fd() == rhs.get_cl_fd(); }
bool operator==(const Client& lhs, int fd)
{ return lhs.fd == fd; }
{ return lhs.get_cl_fd() == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.fd; }
{ return fd == rhs.get_cl_fd(); }

View File

@@ -6,55 +6,88 @@
# include <string>
# include <map>
# include <vector>
# include <string.h> // strdup
# include <netinet/in.h> // sockaddr_in, struct in_addr
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa
# include "utils.hpp"
# include "ServerConfig.hpp"
# include "parsing_message_http.hpp"
struct Script
{
std::string path;
std::string info;
};
struct Request
{
http_method method;
std::string path;
std::string uri;
std::string abs_path;
std::string query;
std::string version;
std::map<std::string, std::string> headers;
std::string body;
std::string port;
std::string hostname;
struct Script script;
};
class Client
{
public:
Client();
Client(int afd, listen_socket *lsocket, std::string aport, std::string aip);
~Client();
//Client(Client const &src);
//Client &operator=(Client const &rhs);
Client(Client const &src);
Client &operator=(Client const &rhs);
int fd;
const listen_socket *lsocket;
std::string raw_request;
std::string response;
unsigned int status;
bool header_complete;
size_t read_body_size;
ServerConfig *assigned_server; // cant be const cause of error_pages.operator[]
std::string raw_request;
std::string response;
unsigned int status;
bool header_complete;
size_t read_body_size;
ServerConfig *assigned_server; // cant be const cause of error_pages.operator[]
const LocationConfig *assigned_location;
// const functions ?
http_method get_method();
std::string &get_path();
std::string &get_version();
std::string &get_body();
std::string &get_headers(const std::string &key);
// getters
int get_cl_fd() const;
const std::string & get_cl_port() const;
const std::string & get_cl_ip() const;
const listen_socket * get_cl_lsocket() const;
// requests getters
http_method get_rq_method() const;
std::string get_rq_method_str() const;
std::string get_rq_uri() const;
std::string get_rq_abs_path() const;
std::string get_rq_query() const;
std::string get_rq_version() const;
std::string get_rq_body() const;
std::string get_rq_port() const;
std::string get_rq_hostname() const;
std::string get_rq_script_path() const;
std::string get_rq_script_info() const;
std::string get_rq_headers(const std::string & key) const;
void parse_request();
void clear();
void clear_request();
void clear_script();
bool fill_script_path(std::string script);
private:
int _fd;
std::string _port;
std::string _ip;
listen_socket * _lsocket;
struct Request _request;
void _parse_request_line( std::string rline );
void _parse_request_headers( std::vector<std::string> list );
void _parse_request_body( size_t pos );
void _parse_request_line();
void _parse_request_headers();
void _parse_request_body();
void _parse_request_uri( std::string uri );
void _parse_port_hostname(std::string host);
};
@@ -63,3 +96,4 @@ bool operator==(const Client& lhs, int fd);
bool operator==(int fd, const Client& rhs);
#endif

View File

@@ -1,9 +1,9 @@
#! /usr/bin/php
<?php
echo "Status: 200\r\n";
echo "\r\n";
echo "BEGIN PHP-CGI\n-----------\n\n";
//phpinfo();
echo "AUTH_TYPE: " . getenv("AUTH_TYPE");
echo "\nCONTENT_LENGTH: " . getenv("CONTENT_LENGTH");
echo "\nCONTENT_TYPE: " . getenv("CONTENT_TYPE");

View File

@@ -23,8 +23,19 @@ std::vector<std::string> split(std::string input, char delimiter)
std::string trim(std::string str, char c)
{
str = str.substr(str.find_first_not_of(c));
str = str.substr(0, str.find_last_not_of(c) + 1);
size_t pos;
// delete leadings c
pos = str.find_first_not_of(c);
if (pos == std::string::npos)
return str;
str = str.substr(pos);
// delete endings c
pos = str.find_last_not_of(c);
if (pos == std::string::npos)
return str;
str = str.substr(0, pos + 1);
return str;
}
@@ -137,8 +148,37 @@ void replace_all_substr(std::string &str, const std::string &ori_substr, const s
}
}
std::string str_tolower(std::string str)
{
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
return str;
}
void del_line_in_str(std::string * str, size_t pos, std::string delim)
{
size_t begin;
size_t end;
begin = (*str).rfind(delim, pos);
if (begin == std::string::npos)
begin = 0;
else
begin += delim.size();
end = (*str).find(delim, pos);
if (end == std::string::npos)
end = 0;
else
end += delim.size();
(*str).erase(begin, end - begin);
}
bool operator==(const listen_socket& lhs, int fd)
{ return lhs.fd == fd; }
bool operator==(int fd, const listen_socket& rhs)
{ return fd == rhs.fd; }

View File

@@ -7,6 +7,8 @@
# include <sstream>
# include <cstdlib> // atoi
# include <sys/stat.h> // stat()
# include <cctype> // tolower
# include <algorithm> // transform
# define CR "\r"
# define LF "\n"
@@ -41,6 +43,8 @@ http_method str_to_http_method(std::string &str);
std::string http_methods_to_str(unsigned int methods);
int path_is_valid(std::string path);
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr);
void throw_test();
std::string str_tolower(std::string str);
void del_line_in_str(std::string * str, size_t pos, std::string delim);
void throw_test();
#endif

View File

@@ -17,8 +17,8 @@
# include <iostream> // cout, cin
# include <cstring> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr
# include <netinet/in.h> // sockaddr_in
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa
# include <netinet/in.h> // sockaddr_in, struct in_addr
// # include <netinet/ip.h> // usefull for what ? -> 'man (7) ip' says it's a superset of 'netinet/in.h'
# include <algorithm> // find
# include <string> // string
@@ -34,7 +34,7 @@
extern bool g_run;
extern int g_last_signal;
void signal_handler(int signum);
void signal_handler(int signum);
// these might only be TMP
# define FAILURE -1
@@ -70,21 +70,23 @@ class Webserv
// accept.cpp
void _accept_connection(listen_socket &lsocket);
std::map<std::string, std::string>
_extract_infos(struct sockaddr_in addr);
// request.cpp
void _request(Client *client);
int _read_request(Client *client);
// response.cpp
void _response(Client *client);
int _send_response(Client *client);
void _append_base_headers(Client *client);
void _construct_response(Client *client);
void _process_method(Client *client);
void _insert_status_line(Client *client);
void _error_html_response(Client *client);
void _append_body(Client *client, const std::string &body, const std::string &file_extension = "");
ServerConfig *_determine_process_server(Client *client); // cant be const cause of error_pages.operator[]
void _response(Client *client);
int _send_response(Client *client);
void _append_base_headers(Client *client);
void _construct_response(Client *client);
void _process_method(Client *client);
void _insert_status_line(Client *client);
void _error_html_response(Client *client);
void _append_body(Client *client, const std::string &body, const std::string &file_extension = "");
ServerConfig *_determine_process_server(Client *client); // cant be const cause of error_pages.operator[]
const LocationConfig *_determine_location(const ServerConfig &server, const std::string &path) const;
std::string _determine_file_extension(const std::string &path) const;
std::string _determine_file_extension(const std::string &path) const;
// method_get.cpp
void _get(Client *client);
void _get_file(Client *client, const std::string &path);
@@ -96,12 +98,15 @@ class Webserv
void _delete(Client *client);
void _delete_file(Client *client, const std::string &path);
// cgi_script.cpp
bool _is_cgi(Client *client);
void _exec_cgi(Client *client);
void _construct_client(Client *client);
char** _set_env(Client *client);
char* _dup_env(std::string var, std::string val);
void _exec_script(Client *client, char **env);
bool _is_cgi(Client *client);
std::string _exec_cgi(Client *client);
char** _set_env(Client *client);
char* _dup_env(std::string var, std::string val);
char* _dup_env(std::string var, int i);
std::string _exec_script(Client *client, char **env);
void _check_script_output(Client *client, std::string output);
void _check_script_status(Client *client, std::string output);
void _check_script_fields(Client *client, std::string output);
// epoll_update.cpp
int _epoll_update(int fd, uint32_t events, int op);
int _epoll_update(int fd, uint32_t events, int op, void *ptr);
@@ -116,6 +121,8 @@ class Webserv
void _listen(int socket_fd, unsigned int max_connections);
void _init_http_status_map();
void _init_mime_types_map();
};
#endif

View File

@@ -3,9 +3,10 @@
void Webserv::_accept_connection(listen_socket &lsocket)
{
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
std::map<std::string, std::string> infos;
std::cerr << "accept()\n";
addr_len = sizeof addr;
@@ -19,9 +20,22 @@ void Webserv::_accept_connection(listen_socket &lsocket)
}
::fcntl(accepted_fd, F_SETFL, O_NONBLOCK);
_clients.push_back(Client());
_clients.back().fd = accepted_fd;
_clients.back().lsocket = &lsocket;
infos = _extract_infos(addr);
Client client(accepted_fd, &lsocket, infos["port"], infos["ip"]);
_clients.push_back(client);
_epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD);
}
std::map<std::string, std::string>
Webserv::_extract_infos(struct sockaddr_in addr)
{
struct in_addr ip_conversion;
std::map<std::string, std::string> infos;
infos["port"] = ::itos( addr.sin_port );
ip_conversion.s_addr = addr.sin_addr.s_addr;
infos["ip"] = inet_ntoa( ip_conversion );
return infos;
}

View File

@@ -3,18 +3,27 @@
bool Webserv::_is_cgi(Client *client)
{
if (client->get_path().find("/cgi-bin/") != std::string::npos)
// TODO see how it works with config
if (client->fill_script_path("/cgi-bin/php-cgi"))
return true;
if (client->fill_script_path("/cgi-bin/cgi_cpp.cgi"))
return true;
return false;
}
void Webserv::_exec_cgi(Client *client)
std::string Webserv::_exec_cgi(Client *client)
{
char** env;
char** env;
std::string script_output;
env = _set_env(client);
_exec_script(client, env);
// _construct_response(client);
script_output = _exec_script(client, env);
for (int i = 0; env[i]; i++)
delete[] env[i];
delete[] env;
return script_output;
}
char* Webserv::_dup_env(std::string var, std::string val = "")
@@ -25,56 +34,145 @@ char* Webserv::_dup_env(std::string var, std::string val = "")
return ( strdup(str.c_str()) );
}
char* Webserv::_dup_env(std::string var, int i)
{
std::string str;
std::string val;
val = ::itos(i);
str = var + "=" + val;
// TODO change strdup for something with new
return ( strdup(str.c_str()) );
}
char** Webserv::_set_env(Client *client)
{
char** env = new char*[19];
env[0] = _dup_env("AUTH_TYPE");
env[1] = _dup_env("CONTENT_LENGTH", "665");
env[2] = _dup_env("CONTENT_TYPE");
env[3] = _dup_env("GATEWAY_INTERFACE");
env[4] = _dup_env("PATH_INFO");
env[5] = _dup_env("PATH_TRANSLATED");
env[6] = _dup_env("QUERY_STRING");
env[7] = _dup_env("REMOTE_ADDR");
env[8] = _dup_env("REMOTE_HOST", client->get_headers("Host")); // just test
env[9] = _dup_env("REMOTE_IDENT");
env[10] = _dup_env("REMOTE_USER");
env[11] = _dup_env("REQUEST_METHOD", ::http_methods_to_str(client->get_method()));
env[12] = _dup_env("SCRIPT_NAME");
env[13] = _dup_env("SERVER_NAME");
env[14] = _dup_env("SERVER_PORT");
env[15] = _dup_env("SERVER_PROTOCOL", client->get_version());
env[16] = _dup_env("SERVER_SOFTWARE");
env[17] = _dup_env("REDIRECT_STATUS");
env[0] = _dup_env("AUTH_TYPE"); // authentification not supported
env[1] = _dup_env("CONTENT_LENGTH" , client->get_rq_body().size());
env[2] = _dup_env("CONTENT_TYPE" , client->get_rq_headers("Content-Type"));
env[3] = _dup_env("GATEWAY_INTERFACE" , "CGI/1.1"); // https://www.rfc-editor.org/rfc/rfc3875
env[4] = _dup_env("PATH_INFO" , client->get_rq_script_info());
env[5] = _dup_env("PATH_TRANSLATED"); // not supported
env[6] = _dup_env("QUERY_STRING" , client->get_rq_query());
env[7] = _dup_env("REMOTE_ADDR" , client->get_cl_ip());
env[8] = _dup_env("REMOTE_HOST" , client->get_rq_headers("Host")); // just test
env[9] = _dup_env("REMOTE_IDENT"); // authentification not supported
env[10] = _dup_env("REMOTE_USER"); // authentification not supported
env[11] = _dup_env("REQUEST_METHOD" , client->get_rq_method_str());
env[12] = _dup_env("SCRIPT_NAME" , client->get_rq_script_path());
env[13] = _dup_env("SERVER_NAME" , client->get_rq_hostname());
env[14] = _dup_env("SERVER_PORT" , client->get_rq_port());
env[15] = _dup_env("SERVER_PROTOCOL" , client->get_rq_version());
env[16] = _dup_env("SERVER_SOFTWARE" , "webser/1.0");
env[17] = _dup_env("REDIRECT_STATUS" , "200");
env[18] = NULL;
return env;
}
void Webserv::_exec_script(Client *client, char **env)
std::string Webserv::_exec_script(Client *client, char **env)
{
int save_stdout;
char * const * nll = NULL;
#define RD 0
#define WR 1
#define CGI_BUF_SIZE 10
#define FD_WR_TO_CHLD fd_in[WR]
#define FD_WR_TO_PRNT fd_out[WR]
#define FD_RD_FR_CHLD fd_out[RD]
#define FD_RD_FR_PRNT fd_in[RD]
// save STDOUT
save_stdout = dup(STDOUT_FILENO);
// inside child process
if (fork() == 0)
{
dup2(client->fd, STDOUT_FILENO);
// execve("./srcs/cgi-bin/cgi_cpp.cgi", nll, client->env);
execve("./srcs/cgi-bin/php-cgi", nll, env);
pid_t pid;
char buf[CGI_BUF_SIZE]; // WIP define buffer
char * const * nll = NULL;
std::string script_output;
std::string body = client->get_rq_body();
int fd_in[2];
int fd_out[2];
pipe(fd_in);
pipe(fd_out);
pid = fork();
if (pid == -1)
std::cerr << "fork crashed" << std::endl;
else if (pid == 0)
{
close(FD_WR_TO_CHLD);
close(FD_RD_FR_CHLD);
dup2(FD_RD_FR_PRNT, STDIN_FILENO);
dup2(FD_WR_TO_PRNT, STDOUT_FILENO);
// DEBUG
std::cerr << "execve:\n";
execve(client->get_rq_script_path().c_str(), nll, env);
// for tests execve crash :
//execve("wrong", nll, env);
std::cerr << "execve crashed.\n";
}
// inside parent process
else
{
close(FD_RD_FR_PRNT);
close(FD_WR_TO_PRNT);
write(FD_WR_TO_CHLD, body.c_str(), body.size());
close(FD_WR_TO_CHLD);
waitpid(-1, NULL, 0);
// restore stdout
dup2(save_stdout, STDOUT_FILENO);
memset(buf, '\0', CGI_BUF_SIZE);
while (read(FD_RD_FR_CHLD, buf, CGI_BUF_SIZE - 1) > 0)
{
script_output += buf;
memset(buf, '\0', CGI_BUF_SIZE);
}
close(FD_RD_FR_CHLD);
}
if (script_output.empty())
script_output = "Status: 500\r\n\r\n";
return script_output;
}
void Webserv::_construct_client(Client *client)
void Webserv::_check_script_output(Client *client, std::string output)
{
(void)client;
// TODO: it doesn't work with execve error, i don't know why yet ?
_check_script_status(client, output);
_check_script_fields(client, output);
}
void Webserv::_check_script_status(Client *client, std::string output)
{
size_t pos;
int status_pos;
pos = output.find("Status:");
if (pos != std::string::npos)
{
status_pos = pos + std::string("Status:").size();
client->status = atoi(output.c_str() + status_pos);
::del_line_in_str(&output, pos, CRLF);
}
client->status = 200;
}
void Webserv::_check_script_fields(Client *client, std::string output)
{
std::map<std::string, std::string> srv_fld; // server_field
std::map<std::string, std::string> scr_fld; // script_field
std::map<std::string, std::string>::iterator it_srv;
std::map<std::string, std::string>::iterator it_scr;
size_t pos;
srv_fld = parse_http_headers(client->response);
scr_fld = parse_http_headers(output);
// wip: compare both map to supress duplicates
for (it_srv = srv_fld.begin(); it_srv != srv_fld.end(); it_srv++)
{
for (it_scr = scr_fld.begin(); it_scr != scr_fld.end(); it_scr++)
{
if (it_srv->first == it_scr->first)
{
pos = client->response.find(it_srv->first);
::del_line_in_str(&client->response, pos, CRLF);
}
}
}
}

View File

@@ -25,10 +25,10 @@ void Webserv::_close_all_clients()
while (!_clients.empty())
{
// _epoll_update(_clients.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(_clients.back().fd) == -1)
if (::close(_clients.back().get_cl_fd()) == -1)
std::perror("err close()");
else
std::cerr << "close fd " << _clients.back().fd << "\n";
std::cerr << "close fd " << _clients.back().get_cl_fd() << "\n";
_clients.pop_back();
}
}

View File

@@ -237,3 +237,4 @@ void Webserv::_init_mime_types_map()
_mime_types.insert(mime_pair("wmv", "video/x-ms-wmv"));
_mime_types.insert(mime_pair("avi", "video/x-msvideo"));
}

View File

@@ -7,7 +7,7 @@ void Webserv::_delete(Client *client)
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-delete
*/
std::string path = client->get_path();
std::string path = client->get_rq_abs_path();
path.insert(0, client->assigned_location->root);
/* CGI Here ? */

View File

@@ -13,7 +13,7 @@ Where does cgi fit in in all this ???
*/
std::string path = client->get_path();
std::string path = client->get_rq_abs_path();
// this might not be the best thing, a voir
path.insert(0, client->assigned_location->root);
@@ -57,9 +57,15 @@ Where does cgi fit in in all this ???
// TMP HUGO
//
std::string script_output;
if (_is_cgi(client))
{
_exec_cgi(client);
script_output = _exec_cgi(client);
// DEBUG
std::cout << "\n____script_output____\n" << script_output << "\n_______________\n";
// wip check output of script
_check_script_output(client, script_output);
client->response += script_output;
return;
}
//

View File

@@ -8,7 +8,7 @@ void Webserv::_post(Client *client)
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-post
*/
std::string path = client->get_path();
std::string path = client->get_rq_abs_path();
path.insert(0, client->assigned_location->root);
/* CGI Here ? */
@@ -46,7 +46,7 @@ void Webserv::_post_file(Client *client, const std::string &path)
// Maybe usefull in _read_request() for rejecting too big content.
// Need to _determine_process_server() as soon as possible,
// like in _read_request() for stopping read if body is too big ?
ofd << client->get_body();
ofd << client->get_rq_body();
if (!ofd)
{
std::cerr << path << ": ofd.write fail" << '\n';

View File

@@ -0,0 +1,90 @@
#include "parsing_message_http.hpp"
size_t
parse_http_first_line(std::string message, std::vector<std::string> &line)
{
std::vector<std::string> sline;
std::string sub;
std::string tmp;
size_t pos;
size_t ret;
// TODO: check for err in substr
pos = message.find(CRLF);
sub = message.substr(0, pos);
sline = ::split(sub, ' ');
ret = sline.size();
if (ret != 3)
return ret;
for (int i = 0; i < 3; i++)
{
tmp = ::trim(sline[i], ' ');
tmp = ::trim(tmp, '\r');
line.push_back(tmp);
}
return ret;
}
std::map<std::string, std::string>
parse_http_headers(std::string message)
{
std::map<std::string, std::string> headers;
std::vector<std::string> list;
std::vector<std::string>::iterator it;
std::string sub;
std::string key;
std::string val;
size_t pos;
pos = (message).find(CRLF CRLF);
sub = (message).substr(0, pos);
list = ::split(sub, '\n');
if ( maybe_http_first_line( *list.begin() ) )
list.erase(list.begin());
for (it = list.begin(); it != list.end(); it++)
{
// TODO: if pattern is not "NAME: value" return error
pos = (*it).find(':');
key = (*it).substr( 0, pos );
key = ::trim(key, ' ');
key = ::trim(key, '\r');
key = ::str_tolower(key);
val = (*it).substr( pos + 1 );
val = ::trim(val, ' ');
val = ::trim(val, '\r');
headers.insert( std::pair<std::string, std::string>(key, val) );
}
return headers;
}
std::string
parse_http_body(std::string message)
{
std::string body;
size_t pos;
pos = message.find(CRLF CRLF);
pos += std::string(CRLF CRLF).size();
// TODO: copying just like that might fail in case of binary or images
body = message.substr(pos);
return body;
}
bool maybe_http_first_line(std::string str)
{
// method SP target SP version https://www.rfc-editor.org/rfc/rfc7230.html#section-3.1.1
// version SP status SP reason https://www.rfc-editor.org/rfc/rfc7230.html#section-3.1.2
std::vector<std::string> sline;
sline = ::split(str, ' ');
if (sline.size() != 3)
return false;
if (sline[0].find(':') != std::string::npos)
return false;
return true;
}

View File

@@ -0,0 +1,36 @@
#ifndef PARSING_MESSAGE_HTTP_HPP
# define PARSING_MESSAGE_HTTP_HPP
# include <iostream>
# include <string>
# include <vector>
# include <map>
# include "utils.hpp"
size_t
parse_http_first_line(std::string message, std::vector<std::string> &line);
std::map<std::string, std::string>
parse_http_headers(std::string message);
std::string
parse_http_body(std::string message);
bool
maybe_http_first_line(std::string);
// http message structure :
//
// start-line
// request-line
// method SP target SP version
// response-line
// version SP status SP reason
// header-fields
// name ":" SP value
// CRLF
// body
#endif

View File

@@ -0,0 +1,2 @@
#include "parsing_request.hpp"

View File

@@ -20,11 +20,11 @@ void Webserv::_request(Client *client)
if (ret == READ_CLOSE)
{
_close_client(client->fd);
_close_client(client->get_cl_fd());
}
else if (ret == READ_COMPLETE)
{
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD);
_epoll_update(client->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD);
}
}
@@ -34,13 +34,13 @@ int Webserv::_read_request(Client *client) // Messy, Need refactoring
ssize_t ret;
std::cerr << "call recv()" << "\n" ;
ret = ::recv(client->fd, buf, BUFSIZE, 0);
std::cerr << "recv() on fd(" << client->fd << ") returned = " << ret << "\n" ;
ret = ::recv(client->get_cl_fd(), buf, BUFSIZE, 0);
std::cerr << "recv() on fd(" << client->get_cl_fd() << ") returned = " << ret << "\n" ;
if (ret == -1)
{
std::perror("err recv()");
std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
std::cerr << "client.fd =" << client->get_cl_fd() << "\n"; // DEBUG
return READ_CLOSE;
}
if (ret == 0)
@@ -60,14 +60,14 @@ int Webserv::_read_request(Client *client) // Messy, Need refactoring
if (client->status) // WIP, need to change client->parse_request() for status update
return READ_COMPLETE;
client->assigned_server = _determine_process_server(client);
client->assigned_location = _determine_location(*client->assigned_server, client->get_path());
if (client->get_version().compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0)
client->assigned_location = _determine_location(*client->assigned_server, client->get_rq_abs_path());
if (client->get_rq_version().compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0)
{ // TODO : move in Client parsing ?
client->status = 505;
return READ_COMPLETE;
}
if (!client->get_headers("Content-Length").empty()
&& ::atoi(client->get_headers("Content-Length").c_str()) > (int)client->assigned_server->client_body_limit)
if (!client->get_rq_headers("Content-Length").empty()
&& ::atoi(client->get_rq_headers("Content-Length").c_str()) > (int)client->assigned_server->client_body_limit)
{
client->status = 413;
return READ_COMPLETE;
@@ -87,7 +87,7 @@ int Webserv::_read_request(Client *client) // Messy, Need refactoring
client->status = 413;
return READ_COMPLETE;
}
if ((int)client->read_body_size >= ::atoi(client->get_headers("Content-Length").c_str()))
if ((int)client->read_body_size >= ::atoi(client->get_rq_headers("Content-Length").c_str()))
{
client->parse_request(); // reparse for the body
return READ_COMPLETE;
@@ -95,7 +95,7 @@ int Webserv::_read_request(Client *client) // Messy, Need refactoring
}
if (client->header_complete && client->get_headers("Content-Type").empty() && client->get_headers("Content-Length").empty() )
if (client->header_complete && client->get_rq_headers("Content-Type").empty() && client->get_rq_headers("Content-Length").empty() )
{
return READ_COMPLETE;
}

View File

@@ -17,15 +17,15 @@ void Webserv::_response(Client *client)
if (ret == SEND_CLOSE)
{
_close_client(client->fd);
_close_client(client->get_cl_fd());
}
else if (ret == SEND_COMPLETE)
{
if (client->get_headers("Connection") == "close")
_close_client(client->fd);
if (client->get_rq_headers("Connection") == "close")
_close_client(client->get_cl_fd());
else
{
_epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD);
_epoll_update(client->get_cl_fd(), EPOLLIN, EPOLL_CTL_MOD);
client->clear();
}
}
@@ -45,11 +45,11 @@ int Webserv::_send_response(Client *client)
_error_html_response(client);
std::cerr << "client->response.size() = " << client->response.size() << "\n"; // DEBUG
ret = ::send(client->fd, client->response.c_str(), client->response.size(), 0);
ret = ::send(client->get_cl_fd(), client->response.c_str(), client->response.size(), 0);
if (ret == -1)
{
std::perror("err send()");
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
std::cerr << "client.fd =" << client->get_cl_fd() << "\n"; // DEBUG
return SEND_CLOSE;
}
std::cerr << "ret send() = " << ret << "\n"; // DEBUG
@@ -61,7 +61,7 @@ void Webserv::_append_base_headers(Client *client)
{
client->response.append("Server: Webserv/0.1" CRLF);
if (client->get_headers("Connection") == "close")
if (client->get_rq_headers("Connection") == "close")
client->response.append("Connection: close" CRLF);
else
client->response.append("Connection: keep-alive" CRLF);
@@ -70,7 +70,7 @@ void Webserv::_append_base_headers(Client *client)
void Webserv::_construct_response(Client *client)
{
// TODO : Move this in read(), stop read if content too large
if (client->get_body().size() > client->assigned_server->client_body_limit)
if (client->get_rq_body().size() > client->assigned_server->client_body_limit)
{
client->status = 413;
return;
@@ -83,7 +83,7 @@ void Webserv::_construct_response(Client *client)
client->response.append(client->assigned_location->redirect_uri);
client->response.append(CRLF);
} */
if (client->get_path().find("redirect_test") != std::string::npos) // Test block
if (client->get_rq_abs_path().find("redirect_test") != std::string::npos) // Test block
{ // Weird behavior. The web browser seems to wait for a complete response until timeout.
// (for codes 301, 302, 303, 307, and 308)
client->status = 307;
@@ -100,13 +100,13 @@ void Webserv::_process_method(Client *client)
{
std::cerr << "assigned_location->path = " << client->assigned_location->path << "\n"; // debug
std::cerr << "allow_methods = " << client->assigned_location->allow_methods << "\n"; // debug
if (client->get_method() == UNKNOWN)
if (client->get_rq_method() == UNKNOWN)
{
client->status = 501;
}
else if (client->assigned_location->allow_methods & client->get_method())
else if (client->assigned_location->allow_methods & client->get_rq_method())
{
switch (client->get_method())
switch (client->get_rq_method())
{
case (GET):
_get(client); break;
@@ -181,14 +181,14 @@ ServerConfig *Webserv::_determine_process_server(Client *client)
TODO : test it
*/
std::string &server_name = client->get_headers("Host");
std::string const &server_name = client->get_rq_headers("Host");
std::vector<ServerConfig>::iterator it = _servers.begin();
std::vector<ServerConfig>::iterator default_server = _servers.end();
while (it != _servers.end())
{
if (it->host == client->lsocket->host
&& it->port == client->lsocket->port)
if (it->host == client->get_cl_lsocket()->host
&& it->port == client->get_cl_lsocket()->port)
{
if ( std::find(it->server_name.begin(), it->server_name.end(), server_name) != it->server_name.end() )
break;

204671
tmp.txt Normal file

File diff suppressed because it is too large Load Diff

13
www/form_get.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form method="get">
<input type="submit" value="submit">
</form>
</body>
</html>

13
www/form_post.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form method="post">
<input type="submit" value="submit">
</form>
</body>
</html>