client variables are made const

This commit is contained in:
hugogogo
2022-08-06 19:58:09 +02:00
43 changed files with 205852 additions and 1086 deletions

View File

@@ -1,11 +1,30 @@
#include "Client.hpp"
char Client::buf[MAX_FILESIZE+1];
/*********************************************
* COPLIENS
*********************************************/
Client::Client( ) {
Client::Client()
: body_size(0)
, status(0)
, _fd(0)
{
return;
}
//Client::Client(int afd, listen_socket *lsocket, struct sockaddr_in addr)
Client::Client(int afd, listen_socket *lsocket, std::string aport, std::string aip)
: body_size(0)
, status(0)
, _fd(afd)
, _port(aport)
, _ip(aip)
, _lsocket(lsocket)
{
return;
}
@@ -13,12 +32,32 @@ Client::~Client() {
return;
}
// copy constructor :
// Client::Client( Client const & src ) {}
// assignement operator :
// Client & Client::operator=( Client const & rhs ) {}
// WIP placeholder because of const values
Client::Client( Client const & src )
: body_size(0)
, status(0)
, _fd ( src.get_cl_fd() )
, _port ( src.get_cl_port() )
, _ip ( src.get_cl_ip() )
, _lsocket ( src.get_cl_lsocket() )
{
raw_request = src.raw_request;
response = src.response;
// buf = strdup(src.buf);
body_size = src.body_size;
status = src.status;
return;
}
// WIP placeholder because of const values
Client & Client::operator=( Client const & rhs )
{
if ( this != &rhs )
{
// stuff
}
return *this;
}
/*********************************************
@@ -35,29 +74,67 @@ void Client::parse_request()
std::vector<std::string> list;
size_t pos;
pos = (raw_request).find("\r\n\r\n");
std::cout << "\n"
<< "request:\n" << raw_request
<< "START _______________________\n\n"
<< raw_request
<< "\n END _______________________\n";
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);
// port and hostname
_parse_port_hostname(_request.headers["Host"]);
// add "raw_request.clear()" after parsing ? for little less memory usage ?
}
void Client::clear()
{
clear_request();
raw_request.clear();
response.clear();
body_size = 0;
status = 0;
}
void Client::clear_request()
{
_request.method = UNKNOWN;
_request.path.clear();
_request.version.clear();
_request.headers.clear();
_request.body.clear();
}
/*********************************************
* GETTERS
*********************************************/
// client side
int Client::get_cl_fd() const { return _fd; }
std::string Client::get_cl_ip() const { return _ip; }
listen_socket * Client::get_cl_lsocket() const { return _lsocket; }
std::string Client::get_cl_port() const { return _port; }
// requette
http_method Client::get_rq_method()const { return _request.method; }
std::string Client::get_rq_method_str() const
{
std::string method;
method = ::http_methods_to_str(_request.method);
return method;
}
std::string Client::get_rq_path() const { return _request.path; }
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_headers(const std::string & key)
{ return _request.headers[key]; }
std::string 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_port() { return _request.port; }
std::string Client::get_hostname() { return _request.hostname; }
std::string Client::get_headers(std::string key) { return _request.headers[key]; }
/*********************************************
* PRIVATE MEMBER FUNCTIONS
@@ -77,7 +154,7 @@ void Client::_parse_request_line( std::string rline )
// method
tmp = ::trim(sline[0], ' ');
tmp = ::trim(tmp, '\r');
_request.method = tmp;
_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
@@ -139,3 +216,15 @@ void Client::_parse_port_hostname(std::string host)
_request.hostname = host.substr(0, pos);
}
/*********************************************
* OVERLOAD
*********************************************/
bool operator==(const Client& lhs, const Client& rhs)
{ return lhs.get_cl_fd() == rhs.get_cl_fd(); }
bool operator==(const Client& lhs, int fd)
{ return lhs.get_cl_fd() == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.get_cl_fd(); }

View File

@@ -6,46 +6,68 @@
# include <string>
# include <map>
# include <vector>
# include <string.h> // strdup
# include "utils.hpp"
# include <netinet/in.h> // sockaddr_in, struct in_addr
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa
struct Request
{
std::map<std::string, std::string> headers;
std::string method;
http_method method;
std::string path;
std::string version;
std::map<std::string, std::string> headers;
std::string body;
std::string port;
std::string hostname;
};
# define MAX_FILESIZE 1000000 // (1Mo)
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;
std::string port;
std::string ip;
std::string raw_request;
std::string response;
static char buf[MAX_FILESIZE+1];
size_t body_size;
unsigned int status;
std::string get_method();
std::string get_path();
std::string get_version();
std::string get_body();
std::string get_port();
std::string get_hostname();
std::string get_headers(std::string key);
// std::string port;
// std::string ip;
// listen_socket * lsocket;
// getters
int get_cl_fd() const;
std::string get_cl_port() const;
std::string get_cl_ip() 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_path() 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_headers(const std::string & key);
void parse_request();
void clear();
void clear_request();
private:
struct Request _request;
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 );

View File

@@ -1,15 +0,0 @@
#ifndef METHODTYPE_HPP
# define METHODTYPE_HPP
enum MethodType
{
GET,
POST,
DELETE,
INVALID,
};
#endif

View File

@@ -1,21 +0,0 @@
#ifndef SERVER_HPP
# define SERVER_HPP
# include <iostream>
# include <string>
class Server
{
public:
// Server(Placeholder);
// Server();
// Server(Server const &src);
// ~Server();
// Server &operator=(Server const &rhs);
private:
};
#endif

View File

@@ -1,447 +0,0 @@
#include "Webserv.hpp"
int g_last_signal;
bool g_run;
void signal_handler(int signum)
{
g_last_signal = signum;
}
Webserv::Webserv()
{
std::cerr << "Server init\n";
_epfd = ::epoll_create1(0); // (EPOLL_CLOEXEC) for CGI fork ?
if (_epfd == -1)
{
std::perror("err epoll_create1()");
throw std::runtime_error("Epoll init");
}
std::signal(SIGPIPE, signal_handler);
std::signal(SIGINT, signal_handler);
}
/* Webserv::Webserv(Webserv const &src)
{
} */
Webserv::~Webserv()
{
close(_socket_fd);
close(_epfd);
_close_all_clients();
std::cerr << "Server destroyed\n";
}
/* Webserv & Webserv::operator=(Webserv const &rhs)
{
} */
///////////////
// Functions //
void Webserv::init_virtual_servers() // ADD config param
{
_socket_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // (SOCK_CLOEXEC) for CGI fork ?
if (_socket_fd == -1)
{
std::perror("err socket()");
throw std::runtime_error("Socket init");
}
// HUGO ADD
// allow socket descriptor to be reuseable
int on = 1;
if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
{
::perror("setsockopt() failed");
throw std::runtime_error("Socket init");
}
// HUGO ADD END
_bind(_socket_fd, 4040);
_listen(_socket_fd, 512); // 512 arbitrary
if (_epoll_update(_socket_fd, EPOLLIN, EPOLL_CTL_ADD) == -1)
throw std::runtime_error("Socket init");
}
void Webserv::start()
{
std::cerr << "Server started\n";
struct epoll_event events[MAX_EVENTS];
int nfds;
int i;
int count_loop = 0;
g_run = true;
while (g_run)
{
std::cerr << ++count_loop << "----loop epoll()\n";
nfds = ::epoll_wait(_epfd, events, MAX_EVENTS, TIMEOUT);
if (nfds == -1)
{
std::perror("err epoll_wait(): ");
throw std::runtime_error("Epoll wait");
}
else if (nfds == 0)
{
if (!_clients.empty())
{
std::cerr << "Timeout " << TIMEOUT << "ms\n";
_close_all_clients();
}
}
i = 0;
while (i < nfds)
{
// if ((events[i].data.u32 == SERVER_FD) && (events[i].events & EPOLLIN)) // Dont work, see "SERVER_FD" define
if ((events[i].data.fd == _socket_fd) && (events[i].events & EPOLLIN))
_accept_connection(events[i].data.fd);
else if (events[i].events & EPOLLIN)
_read_request(static_cast<Client*>(events[i].data.ptr));
else if (events[i].events & EPOLLOUT)
_send_response(static_cast<Client*>(events[i].data.ptr));
++i;
_actual_client = NULL;
}
}
}
///////////////////////
// Private Functions //
void Webserv::_accept_connection(int fd)
{
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
std::cerr << "accept()\n";
addr_len = sizeof addr;
accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len);
if (accepted_fd == -1)
{
std::perror("err accept(): ");
return ;
}
::fcntl(accepted_fd, F_SETFL, O_NONBLOCK);
_clients.push_back(Client());
_clients.back().fd = accepted_fd;
_epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD, &_clients.back());
}
//////////
// READ //
void Webserv::_read_request(Client *client)
{
char buf[BUFSIZE+1];
ssize_t ret;
_actual_client = client;
std::cerr << "recv()\n";
ret = ::recv(client->fd, buf, BUFSIZE, 0);
if (ret == -1)
{
std::perror("err recv(): ");
// if (g_last_signal)
// _handle_last_signal();
// else
// _close_client(client->fd);
std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
return ;
}
/*
if (ret == BUFSIZE)
// send error like "request too long" to client
*/
buf[ret] = '\0';
client->raw_request.append(buf);
// HUGO TMP
//
_parse_request(client);
//
// HUGO TMP END
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD, client);
}
///////////
// WRITE //
void Webserv::_send_response(Client *client)
{
ssize_t ret;
_actual_client = client;
std::cerr << "send()\n";
std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n";
// TMP HUGO test cgi
//
// if "POST" found in _buf, execve a cgi
if (client->raw_request.find("POST") != std::string::npos)
_exec_cgi_script(client);
// if "index.html" found in _buf, send the page
if (client->raw_request.find("index.html") != std::string::npos)
_serve_file(client, "index.html");
//
// TMP HUGO end test cgi
ret = ::send(client->fd, MSG_TEST, sizeof MSG_TEST - 1, 0);
if (ret == -1)
{
std::perror("err send(): ");
if (g_last_signal)
_handle_last_signal();
// else
// _close_client(client->fd);
return ;
}
_close_client(client->fd);
// if (client->raw_request.find("Connection: keep-alive") == std::string::npos)
// _close_client(client->fd);
// else
// _epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD, client);
client->raw_request.clear();
}
////////////////////
// Misc functions //
int Webserv::_epoll_update(int fd, uint32_t events, int op)
{
struct epoll_event ev;
std::memset(&ev, 0, sizeof ev);
ev.events = events;
ev.data.fd = fd;
if (::epoll_ctl(_epfd, op, fd, &ev) == -1)
{
std::perror("err _epoll_update(): ");
return (-1);
}
return (0);
}
// TMP HUGO
//
void _parse_request(Client *client)
{
std::string request = client->raw_request;
std::map<std::string, std::string> field = client->request;
// size_t size = request.size();
size_t begin = 0;
size_t end;
size_t len;
// std::string str ("test un: deux\ntest deux: trois quatre\ntest :trois quatre cinq");
// std::string sub;
std::cout << str << "\n\n";
int i = 0;
while (end != std::string::npos)
{
// find first portion, before ':'
end = str.find(':', begin);
len = end - begin;
if (end == std::string::npos)
len = end;
sub = str.substr(begin, len);
std::cout << i << "|" << sub << "\n";
// std::cout << "[begin:" << begin << " - end:" << end << " - len:" << len << "] " << sub << "\n";
begin = end + 1;
// find second portion, until '\n'
end = str.find('\n', begin);
len = end - begin;
if (end == std::string::npos)
len = end;
sub = str.substr(begin, len);
std::cout << i << "|" << sub << "\n";
begin = end + 1;
i++;
}
// for (size_t i = 0; i < size; i++)
// {
// field.insert(request);
// }
// GET /home.html HTTP/1.1
// Host: developer.mozilla.org
// User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
// Accept-Language: en-US,en;q=0.5
// Accept-Encoding: gzip, deflate, br
// Referer: https://developer.mozilla.org/testpage.html
// Connection: keep-alive
// Upgrade-Insecure-Requests: 1
// If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
// If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
// Cache-Control: max-age=0
}
void Webserv::_serve_file(Client *client, std::string page)
{
int page_fd;
std::string to_send;
std::string end_header = "\r\n\r\n";
std::string body;
std::stringstream strs;
char buffer[1];
to_send = "HTTP/1.1 200 OK\n";
to_send += "Content-Type: text/html;\n";
to_send += "Content-Length: ";
page_fd = open(page.c_str(), O_RDONLY);
for (int ret = 1; ret > 0;)
{
ret = read(page_fd, buffer, 1);
body += buffer;
}
strs << body.size();
to_send += strs.str();
to_send += end_header;
to_send += body;
if (::send(client->fd, to_send.c_str(), to_send.size(), 0) == -1)
std::perror("err send()");
}
void Webserv::_exec_cgi_script(Client *client)
{
int save_stdout;
char** env = new char*[5];
char * const * nll = NULL;
// set env
env[0] = strdup("PATH_INFO=/no");
env[1] = strdup("REQUEST_METHOD=POST");
env[2] = strdup("SERVER_PROTOCOL=HTTP/1.1");
env[3] = strdup("CONTENT_LENGTH=665");
env[4] = NULL;
// 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, env);
execve("./srcs/cgi-bin/php-cgi", nll, env);
}
// inside parent process
else
waitpid(-1, NULL, 0);
// restore stdout
dup2(save_stdout, STDOUT_FILENO);
}
//
// END TMP HUGO
int Webserv::_epoll_update(int fd, uint32_t events, int op, void *ptr)
{
struct epoll_event ev;
std::memset(&ev, 0, sizeof ev);
ev.events = events;
ev.data.ptr = ptr;
if (::epoll_ctl(_epfd, op, fd, &ev) == -1)
{
std::perror("err _epoll_update(): ");
return (-1);
}
return (0);
}
void Webserv::_handle_last_signal()
{
if (g_last_signal == SIGPIPE)
{
std::cerr << "SIGPIPE\n";
if (_actual_client)
{
_close_client(_actual_client->fd);
_actual_client = NULL;
}
}
else if (g_last_signal == SIGINT)
{
g_run = false;
}
g_last_signal = 0;
}
void Webserv::_close_client(int fd)
{
std::vector<Client>::iterator it = _clients.begin();
while (it != _clients.end())
{
if (it->fd == fd)
{
// _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(fd) == -1)
std::perror("err close(): ");
else
std::cerr << "close fd " << fd << "\n";
_clients.erase(it);
break;
}
++it;
}
}
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)
std::perror("err close(): ");
else
std::cerr << "close fd " << _clients.back().fd << "\n";
_clients.pop_back();
}
}
////////////////////
// Init functions //
void Webserv::_bind(int socket_fd, in_port_t port)
{
// cast invalid ? how to ?
// const struct sockaddr* cast_test = static_cast<const struct sockaddr*>(addr);
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = ::htons(port);
addr.sin_addr.s_addr = ::htonl(INADDR_ANY); // htonl useless with 0 value (INADDR_ANY) ?
if (::bind(socket_fd, (const sockaddr*)&addr, sizeof addr) == -1)
{
std::perror("err bind(): ");
throw std::runtime_error("Socket bind");
}
}
void Webserv::_listen(int socket_fd, unsigned int max_connections)
{
if (::listen(socket_fd, max_connections) == -1)
{
std::perror("err listen(): ");
throw std::runtime_error("Socket listen");
}
}

View File

@@ -6,7 +6,7 @@
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/13 22:11:17 by me #+# #+# */
/* Updated: 2022/07/31 13:18:14 by simplonco ### ########.fr */
/* Updated: 2022/08/03 17:51:35 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
@@ -262,8 +262,8 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
MethodType m = _str_to_method_type(tmp_val[i]);
if (m == 3)
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
server->allow_methods.push_back(m);
}
@@ -347,8 +347,8 @@ void ConfigParser::_set_location_values(LocationConfig *location, \
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
MethodType m = _str_to_method_type(tmp_val[i]);
if (m == 3)
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
location->allow_methods.push_back(m);
}

View File

@@ -3,10 +3,10 @@
/* ::: :::::::: */
/* ConfigParser.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/11 23:01:41 by me #+# #+# */
/* Updated: 2022/07/27 19:27:57 by me ### ########.fr */
/* Updated: 2022/08/03 17:32:33 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
@@ -15,7 +15,6 @@
# include "ServerConfig.hpp"
# include "LocationConfig.hpp"
# include "MethodType.hpp"
# include "utils.hpp"
# include <map>
@@ -76,8 +75,6 @@ private:
std::string _get_rest_of_line(size_t *curr); // const?
// why static? it's an enum...
static MethodType _str_to_method_type(std::string str);

View File

@@ -82,20 +82,6 @@ std::string ConfigParser::_get_rest_of_line(size_t *curr)
return (values);
}
MethodType ConfigParser::_str_to_method_type(std::string str)
{
if (str == "GET")
return GET;
else if (str == "POST")
return POST;
else if (str == "DELETE")
return DELETE;
return INVALID;
}
void ConfigParser::_print_content() const
{
std::cout << _content;

View File

@@ -3,18 +3,16 @@
/* ::: :::::::: */
/* LocationConfig.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/23 16:08:00 by me #+# #+# */
/* Updated: 2022/07/25 20:09:48 by me ### ########.fr */
/* Updated: 2022/08/02 14:06:07 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef LOCATIONCONFIG_HPP
# define LOCATIONCONFIG_HPP
# include "MethodType.hpp"
# include <map>
# include <vector>
# include <string>
@@ -31,7 +29,7 @@ public:
int client_body_limit;
std::string root;
std::vector<std::string> index;
std::vector<MethodType> allow_methods;
std::vector<http_method> allow_methods;
std::map<std::string, std::string> cgi_info;
// wait if i can call several times, shouldn't it be a map?

View File

@@ -2,7 +2,7 @@
#ifndef SERVERCONFIG_HPP
# define SERVERCONFIG_HPP
# include "MethodType.hpp"
# include "utils.hpp"
# include "LocationConfig.hpp"
# include <map>
@@ -30,7 +30,7 @@ public:
// there can only be one.
std::string root;
int client_body_limit; // set to default max if none set
unsigned int client_body_limit; // set to default max if none set
// might be the only one we let slide if bad input...
bool autoindex;
@@ -42,7 +42,7 @@ public:
// i'm tempted to do something diff for storing method types...
// fuck it, you can only call allow_methods once in Server
// once more in each location.
std::vector<MethodType> allow_methods;
std::vector<http_method> allow_methods;
std::vector<LocationConfig> locations;

View File

@@ -1,13 +0,0 @@
# include <sstream>
# include <string.h>
char* itoa(int n)
{
std::stringstream strs;
char * str;
strs << n;
str = (char*)(strs.str().c_str());
return (str);
}

View File

@@ -52,3 +52,56 @@ bool isNumeric_btw(int low, int high, std::string str)
return true;
}
http_method str_to_http_method(std::string &str)
{
if (str == "GET")
return GET;
else if (str == "POST")
return POST;
else if (str == "DELETE")
return DELETE;
return UNKNOWN;
}
std::string http_methods_to_str(unsigned int methods)
{
std::string str;
if (methods & GET)
str.append("GET");
if (methods & POST)
{
if (!str.empty())
str.append(", ");
str.append("POST");
}
if (methods & DELETE)
{
if (!str.empty())
str.append(", ");
str.append("DELETE");
}
return (str);
}
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr)
{
if (ori_substr.empty())
return;
size_t pos = 0;
while (1)
{
pos = str.find(ori_substr, pos);
if (pos == std::string::npos)
break;
str.replace(pos, ori_substr.size(), new_substr);
pos += new_substr.size();
}
}
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,10 +7,44 @@
# include <sstream>
# include <cstdlib> // atoi
std::vector<std::string> split(std::string input, char delimiter);
bool isNumeric(std::string str);
bool isNumeric_btw(int low, int high, std::string str);
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
// enum http_method
// {
// UNKNOWN = 0b00000000,
// GET = 0b00000001,
// POST = 0b00000010,
// DELETE = 0b00000100,
// ANY_METHODS = 0b11111111,
// };
enum http_method
{
UNKNOWN = 0b0,
GET = 1 << 0,
POST = 1 << 1,
DELETE = 1 << 2,
ANY_METHODS = 0b11111111,
};
struct listen_socket
{
int fd;
std::string host;
std::string port;
};
bool operator==(const listen_socket& lhs, int fd);
bool operator==(int fd, const listen_socket& rhs);
std::vector<std::string> split(std::string input, char delimiter);
bool isNumeric(std::string str);
bool isNumeric_btw(int low, int high, std::string str);
std::string itos(int n);
std::string trim(std::string str, char c);
http_method str_to_http_method(std::string &str);
std::string http_methods_to_str(unsigned int methods);
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr);
#endif

View File

@@ -17,42 +17,23 @@
# 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
# include <cstdio> // perror
# include <cstdio> // perror, remove
# include <cstdlib> // atoi (athough it's already cover by <string>)
# include "Client.hpp"
# include "ServerConfig.hpp"
# include "utils.hpp"
// TODO: A virer
//# include "ConfigParser.hpp"
//# include "LocationConfig.hpp"
//# include "MethodType.hpp"
//# include "utils.hpp"
// TODO: A virer
# include "http_status.hpp"
extern bool g_run;
extern int g_last_signal;
void signal_handler(int signum);
/* enum // WIP test
{
SERVER_FD = 1,
CLIENT_FD
};
struct s // WIP test
{
int fd;
Client *ptr;
};
*/
// these might only be TMP
# define FAILURE -1
# define SUCCESS 1
@@ -77,21 +58,41 @@ class Webserv
private:
int _epfd;
std::vector<int> _listen_sockets;
std::vector<listen_socket> _listen_sockets;
std::vector<ServerConfig> _servers;
std::vector<Client> _clients;
std::map<int, std::string> _http_status;
std::map<std::string, std::string> _mime_types;
// accept.cpp
void _accept_connection(int fd);
void _accept_connection(listen_socket &lsocket);
std::map<std::string, std::string>
_extract_infos(struct sockaddr_in addr);
// request.cpp
void _request(Client *client);
void _read_request(Client *client);
// response.cpp
void _response(Client *client);
void _send_response(Client *client);
void _construct_response(Client *client);
void _send_response(Client *client, ServerConfig &server);
void _append_base_headers(Client *client);
void _construct_response(Client *client, ServerConfig &server);
void _process_method(Client *client, ServerConfig &server, LocationConfig &location);
void _insert_status_line(Client *client);
void _get_ressource(Client *client);
void _error_html_response(Client *client, ServerConfig &server);
void _append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension = "");
void _get(Client *client, ServerConfig &server, LocationConfig &location);
void _get_file(Client *client, const std::string &path);
void _post(Client *client, ServerConfig &server, LocationConfig &location);
void _post_file(Client *client, const std::string &path);
void _delete(Client *client, ServerConfig &server, LocationConfig &location);
void _delete_file(Client *client, const std::string &path);
ServerConfig &_determine_process_server(Client *client);
LocationConfig &_determine_location(ServerConfig &server, std::string const &path);
// cgi_script.cpp
bool _is_cgi(Client *client);
void _exec_cgi(Client *client);
@@ -107,9 +108,12 @@ class Webserv
// close.cpp
void _close_client(int fd);
void _close_all_clients();
void _close_all_listen_sockets();
// init.cpp
void _bind(int socket_fd, in_port_t port, std::string host);
void _listen(int socket_fd, unsigned int max_connections);
void _init_http_status_map();
void _init_mime_types_map();
};
#endif

View File

@@ -1,15 +1,16 @@
#include "Webserv.hpp"
void Webserv::_accept_connection(int fd)
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;
accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len);
accepted_fd = ::accept(lsocket.fd, (sockaddr*)&addr, &addr_len);
if (accepted_fd == -1)
{
std::perror("err accept()");
@@ -19,22 +20,22 @@ void Webserv::_accept_connection(int fd)
}
::fcntl(accepted_fd, F_SETFL, O_NONBLOCK);
_clients.push_back(Client());
_clients.back().fd = accepted_fd;
// HUGO WIP
//
_clients.back().port = ::itos(addr.sin_port);
struct in_addr tmp = { addr.sin_addr.s_addr };
_clients.back().ip = inet_ntoa( tmp );
std::cout
<< "port:" << _clients.back().port
<< " ip:" << _clients.back().ip
<< "\n";
//
// HUGO END
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

@@ -12,6 +12,9 @@ Webserv::Webserv()
throw std::runtime_error("Epoll init");
}
_init_http_status_map();
_init_mime_types_map();
std::signal(SIGPIPE, signal_handler);
std::signal(SIGINT, signal_handler);
}
@@ -42,10 +45,9 @@ Webserv::Webserv(std::vector<ServerConfig>* servers)
Webserv::~Webserv()
{
//close(_socket_fd);
// TODO : CLOSE ALL _listen_sockets
close(_epfd);
_close_all_clients();
_close_all_listen_sockets();
std::cerr << "Server destroyed\n";
}

View File

@@ -3,7 +3,7 @@
bool Webserv::_is_cgi(Client *client)
{
if (client->get_path().find("/cgi-bin/") != std::string::npos)
if (client->get_rq_path().find("/cgi-bin/") != std::string::npos)
return true;
return false;
}
@@ -30,22 +30,22 @@ 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[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", client->ip);
env[8] = _dup_env("REMOTE_HOST", client->get_headers("Host")); // just test
env[9] = _dup_env("REMOTE_IDENT"); // TODO: implement if authentification
env[10] = _dup_env("REMOTE_USER"); // TODO: implement if authentification
env[11] = _dup_env("REQUEST_METHOD", client->get_method());
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"); // TODO: define (https://www.rfc-editor.org/rfc/rfc3875#section-4.1.13
// ,https://www.rfc-editor.org/rfc/rfc3875#section-8.2)
env[13] = _dup_env("SERVER_NAME", client->get_hostname());
env[14] = _dup_env("SERVER_PORT", client->get_port());
env[15] = _dup_env("SERVER_PROTOCOL", client->get_version());
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;
@@ -63,7 +63,7 @@ void Webserv::_exec_script(Client *client, char **env)
// inside child process
if (fork() == 0)
{
dup2(client->fd, STDOUT_FILENO);
dup2(client->get_cl_fd(), STDOUT_FILENO);
// execve("./srcs/cgi-bin/cgi_cpp.cgi", nll, client->env);
execve("./srcs/cgi-bin/php-cgi", nll, env);
}

View File

@@ -6,7 +6,7 @@ void Webserv::_close_client(int fd)
std::vector<Client>::iterator it = _clients.begin();
while (it != _clients.end())
{
if (it->fd == fd)
if (*it == fd)
{
// _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(fd) == -1)
@@ -25,10 +25,23 @@ 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();
}
}
void Webserv::_close_all_listen_sockets()
{
while (!_listen_sockets.empty())
{
// _epoll_update(_listen_sockets.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(_listen_sockets.back().fd) == -1)
std::perror("err close()");
else
std::cerr << "close fd " << _listen_sockets.back().fd << "\n";
_listen_sockets.pop_back();
}
}

View File

@@ -0,0 +1,42 @@
#ifndef HTTP_STATUS_HPP
# define HTTP_STATUS_HPP
// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
/*
First version of macro HTML_ERROR(STATUS) dont work with call like this :
client->response.append( HTML_ERROR( _http_status[client->status].c_str() ) );
so I made the other version with dynamic replacement of STATUS .
*/
// # define HTML_ERROR(STATUS) "\r\n<!DOCTYPE html><html><head><title>"STATUS"</title></head><body><h1 style=\"text-align:center\">"STATUS"</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
# define STATUS_PLACEHOLDER "$STATUS"
# define HTML_ERROR \
"<!DOCTYPE html>"\
"<html>"\
"<head>"\
"<title>" STATUS_PLACEHOLDER "</title>"\
"</head>"\
"<body>"\
"<h1 style=\"text-align:center\">" STATUS_PLACEHOLDER "</h1>"\
"<hr>"\
"<p style=\"text-align:center\">Le Webserv/0.1</p>"\
"</body>"\
"</html>"
// When new status added, need to update _init_http_status_map()
# define S200 "200 OK"
# define S201 "201 Created"
# define S204 "204 No Content"
# define S400 "400 Bad Request"
# define S403 "403 Forbidden"
# define S404 "404 Not Found"
# define S405 "405 Method Not Allowed"
# define S413 "413 Content Too Large"
# define S500 "500 Internal Server Error"
# define S501 "501 Not Implemented"
#endif

View File

@@ -4,7 +4,9 @@
void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
{
int ret;
std::vector<int> _open_ports;
std::vector<std::string> open_sockets;
struct listen_socket new_socket;
std::string host_port;
_servers = *servers;
_listen_sockets.clear();
@@ -12,7 +14,11 @@ void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
while (it != _servers.end())
{
if ( std::find(_open_ports.begin(), _open_ports.end(), std::atoi(it->port.data()) ) != _open_ports.end() )
host_port.clear();
host_port.append(it->host);
host_port.append(":");
host_port.append(it->port);
if ( std::find(open_sockets.begin(), open_sockets.end(), host_port) != open_sockets.end() )
{
++it;
continue;
@@ -24,13 +30,17 @@ void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
std::perror("err socket()");
throw std::runtime_error("Socket init");
}
new_socket.fd = ret;
new_socket.host = it->host;
new_socket.port = it->port;
_listen_sockets.push_back(new_socket);
// HUGO ADD
//
// allow socket descriptor to be reuseable
// I just copied it from https://www.ibm.com/docs/en/i/7.2?topic=designs-example-nonblocking-io-select
int on = 1;
if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
if (setsockopt(new_socket.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
::perror("err setsockopt()");
throw std::runtime_error("Socket init");
@@ -38,15 +48,13 @@ void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
//
// HUGO ADD END
_listen_sockets.push_back(ret);
_bind(new_socket.fd, std::atoi(it->port.c_str()), it->host);
_listen(new_socket.fd, 512); // 512 arbitrary
_bind(_listen_sockets.back(), std::atoi(it->port.data()), it->host);
_listen(_listen_sockets.back(), 512); // 512 arbitrary
if (_epoll_update(_listen_sockets.back(), EPOLLIN, EPOLL_CTL_ADD) == -1)
if (_epoll_update(new_socket.fd, EPOLLIN, EPOLL_CTL_ADD) == -1)
throw std::runtime_error("Socket init");
_open_ports.push_back(std::atoi(it->port.data()));
open_sockets.push_back(host_port);
++it;
}
}
@@ -78,3 +86,136 @@ void Webserv::_listen(int socket_fd, unsigned int max_connections)
throw std::runtime_error("Socket listen");
}
}
void Webserv::_init_http_status_map()
{
_http_status[200] = S200;
_http_status[201] = S201;
_http_status[204] = S204;
_http_status[400] = S400;
_http_status[403] = S403;
_http_status[404] = S404;
_http_status[405] = S405;
_http_status[413] = S413;
_http_status[500] = S500;
_http_status[501] = S501;
}
void Webserv::_init_mime_types_map()
{
_mime_types[""] = "application/octet-stream";
_mime_types["html"] = "text/html";
_mime_types["htm"] = "text/html";
_mime_types["shtml"] = "text/html";
_mime_types["css"] = "text/css";
_mime_types["xml"] = "text/xml";
_mime_types["gif"] = "image/gif";
_mime_types["jpeg"] = "image/jpeg";
_mime_types["jpg"] = "image/jpeg";
_mime_types["js"] = "application/javascript";
_mime_types["atom"] = "application/atom+xml";
_mime_types["rss"] = "application/rss+xml";
_mime_types["mml"] = "text/mathml";
_mime_types["txt"] = "text/plain";
_mime_types["jad"] = "text/vnd.sun.j2me.app-descriptor";
_mime_types["wml"] = "text/vnd.wap.wml";
_mime_types["htc"] = "text/x-component";
_mime_types["png"] = "image/png";
_mime_types["tif"] = "image/tiff";
_mime_types["tiff"] = "image/tiff";
_mime_types["wbmp"] = "image/vnd.wap.wbmp";
_mime_types["ico"] = "image/x-icon";
_mime_types["jng"] = "image/x-jng";
_mime_types["bmp"] = "image/x-ms-bmp";
_mime_types["svg"] = "image/svg+xml";
_mime_types["svgz"] = "image/svg+xml";
_mime_types["webp"] = "image/webp";
_mime_types["woff"] = "application/font-woff";
_mime_types["jar"] = "application/java-archive";
_mime_types["war"] = "application/java-archive";
_mime_types["ear"] = "application/java-archive";
_mime_types["json"] = "application/json";
_mime_types["hqx"] = "application/mac-binhex40";
_mime_types["doc"] = "application/msword";
_mime_types["pdf"] = "application/pdf";
_mime_types["ps"] = "application/postscript";
_mime_types["eps"] = "application/postscript";
_mime_types["ai"] = "application/postscript";
_mime_types["rtf"] = "application/rtf";
_mime_types["m3u8"] = "application/vnd.apple.mpegurl";
_mime_types["xls"] = "application/vnd.ms-excel";
_mime_types["eot"] = "application/vnd.ms-fontobject";
_mime_types["ppt"] = "application/vnd.ms-powerpoint";
_mime_types["wmlc"] = "application/vnd.wap.wmlc";
_mime_types["kml"] = "application/vnd.google-earth.kml+xml";
_mime_types["kmz"] = "application/vnd.google-earth.kmz";
_mime_types["7z"] = "application/x-7z-compressed";
_mime_types["cco"] = "application/x-cocoa";
_mime_types["jardiff"] = "application/x-java-archive-diff";
_mime_types["jnlp"] = "application/x-java-jnlp-file";
_mime_types["run"] = "application/x-makeself";
_mime_types["pl"] = "application/x-perl";
_mime_types["pm"] = "application/x-perl";
_mime_types["prc"] = "application/x-pilot";
_mime_types["pdb"] = "application/x-pilot";
_mime_types["rar"] = "application/x-rar-compressed";
_mime_types["rpm"] = "application/x-redhat-package-manager";
_mime_types["sea"] = "application/x-sea";
_mime_types["swf"] = "application/x-shockwave-flash";
_mime_types["sit"] = "application/x-stuffit";
_mime_types["tcl"] = "application/x-tcl";
_mime_types["tk"] = "application/x-tcl";
_mime_types["der"] = "application/x-x509-ca-cert";
_mime_types["pem"] = "application/x-x509-ca-cert";
_mime_types["crt"] = "application/x-x509-ca-cert";
_mime_types["xpi"] = "application/x-xpinstall";
_mime_types["xhtml"] = "application/xhtml+xml";
_mime_types["xspf"] = "application/xspf+xml";
_mime_types["zip"] = "application/zip";
_mime_types["bin"] = "application/octet-stream";
_mime_types["exe"] = "application/octet-stream";
_mime_types["dll"] = "application/octet-stream";
_mime_types["deb"] = "application/octet-stream";
_mime_types["dmg"] = "application/octet-stream";
_mime_types["iso"] = "application/octet-stream";
_mime_types["img"] = "application/octet-stream";
_mime_types["msi"] = "application/octet-stream";
_mime_types["msp"] = "application/octet-stream";
_mime_types["msm"] = "application/octet-stream";
_mime_types["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
_mime_types["xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
_mime_types["pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
_mime_types["mid"] = "audio/midi";
_mime_types["midi"] = "audio/midi";
_mime_types["kar"] = "audio/midi";
_mime_types["mp3"] = "audio/mpeg";
_mime_types["ogg"] = "audio/ogg";
_mime_types["m4a"] = "audio/x-m4a";
_mime_types["ra"] = "audio/x-realaudio";
_mime_types["3gpp"] = "video/3gpp";
_mime_types["3gp"] = "video/3gpp";
_mime_types["ts"] = "video/mp2t";
_mime_types["mp4"] = "video/mp4";
_mime_types["mpeg"] = "video/mpeg";
_mime_types["mpg"] = "video/mpeg";
_mime_types["mov"] = "video/quicktime";
_mime_types["webm"] = "video/webm";
_mime_types["flv"] = "video/x-flv";
_mime_types["m4v"] = "video/x-m4v";
_mime_types["mng"] = "video/x-mng";
_mime_types["asx"] = "video/x-ms-asf";
_mime_types["asf"] = "video/x-ms-asf";
_mime_types["wmv"] = "video/x-ms-wmv";
_mime_types["avi"] = "video/x-msvideo";
}

View File

@@ -15,19 +15,19 @@ void Webserv::_read_request(Client *client)
char buf[BUFSIZE+1];
ssize_t ret;
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
_close_client(client->fd);
std::cerr << "client.get_cl_fd() =" << client->get_cl_fd() << "\n"; // DEBUG
_close_client(client->get_cl_fd());
return ;
}
if (ret == 0) // Not sure what to do in case of 0. Just close ?
{
_close_client(client->fd);
_close_client(client->get_cl_fd());
return ;
}
/*
@@ -39,6 +39,6 @@ void Webserv::_read_request(Client *client)
client->raw_request.append(buf);
client->parse_request();
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD);
_epoll_update(client->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD);
}

View File

@@ -3,95 +3,145 @@
void Webserv::_response(Client *client)
{
_send_response(client);
client->status = 200; // default value
ServerConfig &server = _determine_process_server(client);
_send_response(client, server);
if (g_last_signal)
_handle_last_signal();
}
void Webserv::_send_response(Client *client)
void Webserv::_send_response(Client *client, ServerConfig &server)
{
ssize_t ret;
std::cerr << "send()\n";
std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n"; // DEBUG
_construct_response(client);
_append_base_headers(client);
_construct_response(client, server);
_insert_status_line(client);
if (client->status >= 400)
_error_html_response(client, server);
ret = ::send(client->fd, client->response.data(), 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 ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
_close_client(client->fd);
std::cerr << "client.get_cl_fd() =" << client->get_cl_fd() << "\n"; // DEBUG
_close_client(client->get_cl_fd());
return ;
}
if (client->raw_request.find("Connection: close") != std::string::npos)
_close_client(client->fd);
// Body send (WIP for working binary files)
if (client->body_size)
{
ret = ::send(client->get_cl_fd(), client->buf, client->body_size, 0);
if (ret == -1)
{
std::perror("err send()");
std::cerr << "client.get_cl_fd() =" << client->get_cl_fd() << "\n"; // DEBUG
_close_client(client->get_cl_fd());
return ;
}
}
if (client->get_rq_headers("Connection") == "close")
_close_client(client->get_cl_fd());
else
{
_epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD);
client->raw_request.clear();
client->response.clear();
_epoll_update(client->get_cl_fd(), EPOLLIN, EPOLL_CTL_MOD);
client->clear();
}
}
void Webserv::_construct_response(Client *client)
void Webserv::_append_base_headers(Client *client)
{
client->status = 200;
client->response.append("Server: Webserv/0.1\r\n");
client->response.append("Server: Webserv/0.1" CRLF);
if (client->raw_request.find("Connection: close") != std::string::npos)
client->response.append("Connection: close\r\n");
if (client->get_rq_headers("Connection") == "close")
client->response.append("Connection: close" CRLF);
else
client->response.append("Connection: keep-alive\r\n");
_get_ressource(client);
_insert_status_line(client);
client->response.append("Connection: keep-alive" CRLF);
}
void Webserv::_construct_response(Client *client, ServerConfig &server)
{
// TODO : Move this in read(), stop read if content too large
if (client->get_rq_body().size() > server.client_body_limit)
{
client->status = 413;
return;
}
LocationConfig &location = _determine_location(server, client->get_rq_path());
_process_method(client, server, location);
}
void Webserv::_process_method(Client *client, ServerConfig &server, LocationConfig &location)
{
unsigned int allow_methods = ANY_METHODS; // TEMP VARIABLE
// after update in ConfigParser, use the "allow_methods" of location.
// TODO in ConfigParser : by default if no field in config file, "allow_methods" must be set to ANY_METHODS
if (client->get_rq_method() == UNKNOWN)
{
client->status = 501;
}
else if (allow_methods & client->get_rq_method())
{
switch (client->get_rq_method())
{
case (GET):
_get(client, server, location); break;
case (POST):
_post(client, server, location); break;
case (DELETE):
_delete(client, server, location); break;
default:
break;
}
}
else
{
client->status = 405;
client->response.append("Allow: ");
client->response.append(::http_methods_to_str(allow_methods));
client->response.append(CRLF);
}
}
#define E400 "\r\n<!DOCTYPE html><html><head><title>400 Bad Request</title></head><body><h1 style=\"text-align:center\">400 Bad Request</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
#define E404 "\r\n<!DOCTYPE html><html><head><title>404 Not Found</title></head><body><h1 style=\"text-align:center\">404 Not Found</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
#define E500 "\r\n<!DOCTYPE html><html><head><title>500 Internal Server Error</title></head><body><h1 style=\"text-align:center\">500 Internal Server Error</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
void Webserv::_insert_status_line(Client *client)
{
std::string status_line;
status_line.append("HTTP/1.1 ");
// WIP, maybe make a map for status response
switch (client->status)
{
case (200):
status_line.append("200 OK");
break;
case (400):
status_line.append("400 Not Found");
client->response.append(E400);
break;
case (404):
status_line.append("404 Not Found");
client->response.append(E404);
break;
case (500):
status_line.append("500 Internal Server Error");
client->response.append(E500);
break;
}
status_line.append("\r\n");
status_line.append(_http_status[client->status]);
status_line.append(CRLF);
client->response.insert(0, status_line);
}
#define ROOT "website"
#define INDEX "index.html"
#define MAX_FILESIZE 1000000 // (1Mo)
void Webserv::_get_ressource(Client *client)
void Webserv::_error_html_response(Client *client, ServerConfig &server)
{
std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ?
char buf[MAX_FILESIZE+1];
std::string tmp;
if (server.error_pages[client->status].empty())
{
std::string html_page = HTML_ERROR;
::replace_all_substr(html_page, STATUS_PLACEHOLDER, _http_status[client->status]);
_append_body(client, html_page.c_str(), html_page.size(), "html");
}
else
_get_file(client, server.error_pages[client->status]);
}
#define INDEX "index.html" // temp wip
void Webserv::_get(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
std::string path = client->get_rq_path();
if (path == "/") // TODO : index and autoindex
path.append(INDEX);
path.insert(0, location.root);
std::cerr << "path = " << path << "\n";
// TMP HUGO
//
@@ -103,41 +153,39 @@ void Webserv::_get_ressource(Client *client)
//
// END TMP HUGO
// Mini parsing à l'arrache du PATH
std::string path;
try
{
path = client->raw_request.substr(0, client->raw_request.find("\r\n"));
path = path.substr(0, path.rfind(" "));
path = path.substr(path.find("/"));
if (path == "/")
path.append(INDEX);
path.insert(0, ROOT);
}
catch (std::out_of_range& e)
{
std::cout << e.what() << '\n';
client->status = 400;
return ;
}
_get_file(client, path);
}
if (access(path.data(), R_OK) == -1)
void Webserv::_get_file(Client *client, const std::string &path)
{
std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ?
// char buf[MAX_FILESIZE+1];
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
ifd.open(path.data(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?)
if (access(path.c_str(), R_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ifd.open(path.c_str(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?)
if (!ifd)
{
std::cerr << path << ": open fail" << '\n';
std::cerr << path << ": ifd.open fail" << '\n';
client->status = 500;
}
else
{
// WIP : Chunk or not chunk (if filesize too big)
std::streampos size = ifd.tellg();
// WIP : Chunk or not chunk (if filesize too big)
if (size > MAX_FILESIZE)
{
// Then chunk
@@ -148,22 +196,211 @@ void Webserv::_get_ressource(Client *client)
}
ifd.seekg(0, std::ios::beg);
ifd.read(buf, size);
buf[ifd.gcount()] = '\0';
ifd.read(client->buf, size);
if (!ifd)
{
std::cerr << path << ": ifd.read fail" << '\n';
client->status = 500;
}
else
{
client->status = 200;
client->buf[ifd.gcount()] = '\0';
client->response.append("Content-Type: text/html; charset=UTF-8\r\n");
std::string file_ext = "";
size_t dot_pos = path.rfind(".");
std::cerr << "dot_pos = " << dot_pos << "\n";
if (dot_pos != std::string::npos && dot_pos + 1 < path.size())
file_ext = path.substr(dot_pos + 1);
std::cerr << "file_ext = " << file_ext << "\n";
client->response.append("Content-Length: ");
tmp = ::itos(ifd.gcount());
client->response.append(tmp.c_str());
client->response.append("\r\n");
// Body
client->response.append("\r\n");
client->response.append(buf);
client->body_size = ifd.gcount();
// WIP, pass empty body argument because append to string mess up binary file
_append_body(client, "", client->body_size, file_ext);
}
ifd.close();
}
}
void Webserv::_append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension)
{
/*
TODO : determine Content-Type
how ? read the body ?
or before in other way (like based and file extension) and pass here as argument ?
http://nginx.org/en/docs/http/ngx_http_core_module.html#types
Need to look "conf/mime.types" of nginx. Maybe make a map<> based on that.
*/
const std::string &mime_type = _mime_types[file_extension];
client->response.append("Content-Type: ");
client->response.append(mime_type);
if (mime_type.find("text/") != std::string::npos)
client->response.append("; charset=UTF-8");
client->response.append(CRLF);
client->response.append("Content-Length: ");
std::string tmp = ::itos(body_size);
client->response.append(tmp);
client->response.append(CRLF);
client->response.append(CRLF);
client->response.append(body);
}
void Webserv::_post(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-post
*/
std::string path = client->get_rq_path();
path.insert(0, location.root);
/* CGI Here ? */
_post_file(client, path);
}
void Webserv::_post_file(Client *client, const std::string &path)
{
std::ofstream ofd;
bool file_existed;
if (access(path.c_str(), F_OK) == -1)
file_existed = false;
else
file_existed = true;
// How to determine status 403 for file that dont already exist ?
if (file_existed && access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ofd.open(path.c_str(), std::ios::binary | std::ios::trunc);
if (!ofd)
{
std::cerr << path << ": ofd.open fail" << '\n';
client->status = 500;
}
else
{
// Used body.size() so Content-Length useless at this point ?
// 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.write(client->get_rq_body().c_str(), client->get_rq_body().size());
if (!ofd)
{
std::cerr << path << ": ofd.write fail" << '\n';
client->status = 500;
}
else if (file_existed)
{
client->status = 200;
// WIP https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok
}
else
{
client->status = 201;
// WIP https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3-4
}
ofd.close();
}
}
void Webserv::_delete(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-delete
*/
std::string path = client->get_rq_path();
path.insert(0, location.root);
/* CGI Here ? */
_delete_file(client, path);
}
void Webserv::_delete_file(Client *client, const std::string &path)
{
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
if (remove(path.c_str()) == -1)
{
std::perror("err remove()");
client->status = 500;
return ;
}
}
ServerConfig &Webserv::_determine_process_server(Client *client)
{
/*
http://nginx.org/en/docs/http/request_processing.html
_determine_process_server() should be complete.
TODO : test it
*/
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->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;
else if (default_server == _servers.end())
default_server = it;
}
++it;
}
if (it != _servers.end())
return (*it);
else
return (*default_server);
}
LocationConfig &Webserv::_determine_location(ServerConfig &server, std::string const &path)
{
/*
Assume there is at least one location in vector<LocationConfig> for path "/"
TODO in ConfigParser :
If no location block in config file, one need to be generated
for path "/", and filled with fields "root" and "index" based on parent server block
*/
std::vector<LocationConfig>::iterator it = server.locations.begin();
while (it != server.locations.end())
{
if (it->path.compare(0, path.size(), path))
break;
++it;
}
if (it != server.locations.end())
return (*it);
else
return (server.locations.front());
}

View File

@@ -4,14 +4,6 @@
#define MAX_EVENTS 42 // arbitrary
#define TIMEOUT 3000
// Temp. To move in other file
bool operator==(const Client& lhs, const Client& rhs)
{ return lhs.fd == rhs.fd; }
bool operator==(const Client& lhs, int fd)
{ return lhs.fd == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.fd; }
void Webserv::run()
{
std::cerr << "Server started\n";
@@ -19,6 +11,7 @@ void Webserv::run()
int nfds;
int i;
int count_loop = 0;
std::vector<listen_socket>::iterator it_socket;
g_run = true;
while (g_run)
@@ -42,9 +35,9 @@ void Webserv::run()
while (i < nfds)
{
// TODO : handle EPOLLERR and EPOLLHUP
if ((std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd) != _listen_sockets.end())
&& (events[i].events & EPOLLIN))
_accept_connection(events[i].data.fd);
it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd);
if (it_socket != _listen_sockets.end() && events[i].events & EPOLLIN)
_accept_connection(*it_socket);
else if (events[i].events & EPOLLIN)
_request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
else if (events[i].events & EPOLLOUT)