Merge remote-tracking branch 'origin/master' into eric_config_parser

let's get it done
This commit is contained in:
Me
2022-08-03 18:56:54 +02:00
37 changed files with 725 additions and 220 deletions

BIN
42_testers/cgi_tester Executable file

Binary file not shown.

BIN
42_testers/tester Executable file

Binary file not shown.

View File

@@ -3,28 +3,22 @@ NAME = webserv
CXX = c++ CXX = c++
CXXFLAGS = -Wall -Wextra #-Werror CXXFLAGS = -Wall -Wextra #-Werror
CXXFLAGS += $(HEADERS_I) CXXFLAGS += $(HEADERS_D:%=-I%)
CXXFLAGS += -std=c++98 CXXFLAGS += -std=c++98
CXXFLAGS += -g CXXFLAGS += -g
CXXFLAGS += -MMD -MP #header dependencie CXXFLAGS += -MMD -MP #header dependencie
#CXXFLAGS += -O3 #CXXFLAGS += -O3
#SHELL = /bin/zsh
VPATH = $(SRCS_D) VPATH = $(SRCS_D)
HEADERS_I = $(HEADERS_D:%=-I%)
HEADERS_D = srcs \ HEADERS_D = srcs \
headers srcs/webserv \
HEADERS = Webserv.hpp \ srcs/config
ConfigParser.hpp \
ServerConfig.hpp \
LocationConfig.hpp \
Client.hpp \
MethodType.hpp \
utils.hpp \
SRCS_D = srcs \ SRCS_D = srcs \
srcs/webserv srcs/webserv \
srcs/config
SRCS = main.cpp \ SRCS = main.cpp \
base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \ base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \
accept.cpp request.cpp response.cpp \ accept.cpp request.cpp response.cpp \
@@ -33,6 +27,8 @@ SRCS = main.cpp \
ConfigParserUtils.cpp \ ConfigParserUtils.cpp \
ConfigParserPost.cpp \ ConfigParserPost.cpp \
utils.cpp \ utils.cpp \
cgi_script.cpp \
Client.cpp \
OBJS_D = builds OBJS_D = builds
OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o) OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o)

136
README.md Normal file
View File

@@ -0,0 +1,136 @@
---
## questions
- mettre les fonctions specifiques a la requete, dans la class client ?
- où est-ce que j'inclus le cgi ?
- est-ce que le cgi est appellé par `/cgi-bin` ?
- non
- g rajouté `char ** env` dans client.cpp
- non
- ajouter un champ "message body" dans client ?
- non
- comment organiser la creation du message reponse (cgi ou pas) et des headers ?
- comment je gere le path `/cgi-bin/` avec la suite ?
- qu'est-ce que le cgi renvoit comme headers ? comment c'est géré ?
- https://www.rfc-editor.org/rfc/rfc3875
---
## man
- **htons, htonl, ntohs, ntohl :** converts the unsigned short or integer argument between host byte order and network byte order
- **poll :** waits for one of a set of file descriptors to become ready to perform I/O
- alternatives : select, epoll (epoll_create, epoll_ctl, epoll_wait), kqueue (kqueue, kevent)
- **socket :** creates an endpoint for communication and returns a file descriptor that refers to that endpoint
- **listen :** marks a socket as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept()
- **accept :** used with connection-based socket types. It extracts the first connection request on the queue of pending connections for the listening socket, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket is unaffected by this call
- **send :** (~write) used to transmit a message to another socket. May be used only when the socket is in a connected state (so that the intended recipient is known). The only difference between send() and write() is the presence of flags. With a zero flags argument, send() is equivalent to write()
- **recv :** (~read) used to receive messages from a socket. May be used to receive data on both connectionless and connection-oriented sockets. The only difference between recv() and read() is the presence of flags. With a zero flags argument, recv() is generally equivalent to read()
- **bind :** associate a socket fd to a local address. When a socket is created with socket(), it exists in a name space (address family) but has no address assigned to it. It is normally necessary to assign a local address using bind() before a socket may receive connections (see accept())
- **connect :** connects a socket fd to a remote address
- **inet_addr :** converts the Internet host address cp from IPv4 numbers-and-dots notation into binary data in network byte order. Use of this function is problematic because in case of error it returns -1, wich is a valid address (255.255.255.255). Avoid its use in favor of inet_aton(), inet_pton(), or getaddrinfo()
- **setsockopt :** manipulate options for a socket fd. Options may exist at multiple protocol levels; they are always present at the uppermost socket level
- **getsockname :** returns the current address to which a socket fd is bound
- **fcntl :** manipulate an open fd, by performing some actions, like duplicate it or changing its flags
---
## todo
- [ ] read the RFC and do some tests with telnet and NGINX
#### parsing config
- [ ] Your program has to take a configuration file as argument, or use a default path.
- [ ] Choose the port and host of each server.
- [ ] Setup the server_names or not.
- [ ] The first server for a host:port will be the default for this host:port (that means it will answer to all the requests that dont belong to an other server).
- [ ] Setup default error pages.
- [ ] Limit client body size.
- [ ] Setup routes with one or multiple of the following rules/configuration (routes wont be using regexp):
- [ ] Define a list of accepted HTTP methods for the route.
- [ ] Define a HTTP redirection.
- [ ] Define a directory or a file from where the file should be searched (for example, if url /kapouet is rooted to /tmp/www, url /kapouet/pouic/toto/pouet is /tmp/www/pouic/toto/pouet).
- [ ] Turn on or off directory listing.
- [ ] Set a default file to answer if the request is a directory.
- [ ] Execute CGI based on certain file extension (for example .php).
- [ ] Make the route able to accept uploaded files and configure where they should be saved.
#### connection basic
- [ ] You cant execve another web server.
- [ ] Your server must never block and the client can be bounced properly if necessary.
- [ ] It must be non-blocking and use only 1 poll() (or equivalent) for all the I/O operations between the client and the server (listen included).
- [ ] poll() (or equivalent) must check read and write at the same time.
- [ ] You must never do a read or a write operation without going through poll() (or equivalent).
- [ ] Checking the value of errno is strictly forbidden after a read or a write operation.
- [ ] You dont need to use poll() (or equivalent) before reading your configuration file. Because you have to use non-blocking file descriptors, it is possible to use read/recv or write/send functions with no poll() (or equivalent), and your server wouldnt be blocking. But it would consume more system resources. Thus, if you try to read/recv or write/send in any file descriptor without using poll() (or equivalent), your grade will be 0.
- [ ] You can use every macro and define like FD_SET, FD_CLR, FD_ISSET, FD_ZERO (understanding what and how they do it is very useful).
- [ ] A request to your server should never hang forever.
- [ ] Your server must be compatible with the web browser of your choice.
#### parsing request HTTP (fields, ...)
- [ ] We will consider that NGINX is HTTP 1.1 compliant and may be used to compare headers and answer behaviors.
#### response HTTP (fields, ...)
- [ ] Your HTTP response status codes must be accurate.
- [ ] You server must have default error pages if none are provided.
- [ ] You cant use fork for something else than CGI (like PHP, or Python, and so forth).
- [ ] You must be able to serve a fully static website.
#### upload files
- [ ] Clients must be able to upload files.
#### CGI
- [ ] You need at least GET, POST, and DELETE methods.
- [ ] Do you wonder what a CGI is?
- [ ] Because you wont call the CGI directly, use the full path as PATH_INFO.
- [ ] Just remember that, for chunked request, your server needs to unchunked it and the CGI will expect EOF as end of the body.
- [ ] Same things for the output of the CGI. If no content_length is returned from the CGI, EOF will mark the end of the returned data.
- [ ] Your program should call the CGI with the file requested as first argument.
- [ ] The CGI should be run in the correct directory for relative path file access.
- [ ] Your server should work with one CGI (php-CGI, Python, and so forth).
#### write tests
- [ ] Stress tests your server. It must stay available at all cost.
- [ ] Do not test with only one program.
- [ ] Write your tests with a more convenient language such as Python or Golang, and so forth. Even in C or C++ if you want to
#### persistent connexion
- [ ] Your server must be able to listen to multiple ports (see Configuration file)
- [ ] Your server should never die.
---
## cgi env variables
[cgi env variables](http://www.faqs.org/rfcs/rfc3875.html)
[wikipedia variables environnements cgi](https://fr.wikipedia.org/wiki/Variables_d%27environnement_CGI)
[cgi server variables on adobe](https://helpx.adobe.com/coldfusion/cfml-reference/reserved-words-and-variables/cgi-environment-cgi-scope-variables/cgi-server-variables.html)
```
AUTH_TYPE : if the srcipt is protected, the authentification method used to validate the user
CONTENT_LENGTH : length of the request content
CONTENT_TYPE : if there is attached information, as with method POST or PUT, this is the content type of the data (e.g. "text/plain", it is set by the attribute "enctype" in html <form> as three values : "application/x-www-form-urlencoded", "multipart/form-data", "text/plain")
GATEWAY_INTERFACE : CGI version (e.g. CGI/1.1)
PATH_INFO : if any, path of the resquest in addition to the cgi script path (e.g. for cgi script path = "/usr/web/cgi-bin/script.cgi", and the url = "http://server.org/cgi-bin/script.cgi/house", the PATH-INFO would be "house")
PATH_TRANSLATED : full path of the request, like path-to-cgi/PATH_INFO, null if PATH_INFO is null (e.g. for "http://server.org/cgi-bin/prog/the/path", PATH_INFO would be : "/the/path" and PATH_TRANSLATED would be : "/usr/web/cgi-bin/prog/the/path")
QUERY_STRING : everything following the ? in the url sent by client (e.g. for url "http://server.org/query?var1=val2&var2=val2", it would be : "var1=val2&var2=val2")
REMOTE_ADDR : ip address of the client
REMOTE_HOST : host name of the client, empty if not known, or equal to REMOTE_ADDR
REMOTE_IDENT : if known, username of the client, otherwise empty, use for logging only
REMOTE_USER : username of client, if script is protected and the server support user authentification
REQUEST_METHOD : method used for the request (for http, usually POST or GET)
SCRIPT_NAME : path to the cgi, relative to the root, used for self-referencing URLs (e.g. "/cgi-bin/script.cgi")
SERVER_NAME : name of the server, as hostname, IP address, or DNS (e.g. dns : "www.server.org")
SERVER_PORT : the port number your server is listening on (e.g. 80)
SERVER_PROTOCOL : protocol used for the request (e.g. HTTP/1.1)
SERVER_SOFTWARE : the server software you're using (e.g. Apache 1.3)
```
[redirect status for php-cgi](https://woozle.org/papers/php-cgi.html)
```
REDIRECT_STATUS : for exemple, 200
```
---
## ressources
- [correction](https://github.com/AliMaskar96/42-Correction-Sheets/blob/master/ng_5_webserv.pdf)
- [create an http server](https://medium.com/from-the-scratch/http-server-what-do-you-need-to-know-to-build-a-simple-http-server-from-scratch-d1ef8945e4fa)
- [guide to network programming](https://beej.us/guide/bgnet/)
- [same, translated in french](http://vidalc.chez.com/lf/socket.html)
- [bind() vs connect()](https://stackoverflow.com/questions/27014955/socket-connect-vs-bind)
- [INADDR_ANY for bind](https://stackoverflow.com/questions/16508685/understanding-inaddr-any-for-socket-programming)
- [hack with CGI](https://www.youtube.com/watch?v=ph6-AKByBU4)
- [http headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers)
- [list of http headers fields](https://en.wikipedia.org/wiki/List_of_HTTP_header_fields)
- [http request ibm](https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests)
- [http request other](https://www.tutorialspoint.com/http/http_requests.htm)
- [request line uri](https://stackoverflow.com/questions/40311306/when-is-absoluteuri-used-from-the-http-request-specs)

View File

View File

View File

View File

0
YoupiBanane/youpi.bla Normal file
View File

View File

@@ -12,6 +12,13 @@ server {
index index.html; # this is another comment index index.html; # this is another comment
root /website; root /website;
# If not explicitly set, ConfigParser need to genererate a location block
# like this for path "/" (based on field "root" and "index" of the server)
location / {
root ./www/;
index index.html;
}
allow_methods GET; allow_methods GET;
@@ -22,4 +29,3 @@ server {
} }
} }

BIN
docs/webserv_correction.pdf Normal file

Binary file not shown.

View File

@@ -1,7 +1,14 @@
- un thread par serveur présent dans le fichier de config ?
----------------
---------------- - http_method en mode binary flags. "std::vector<http_method> allow_methods" -> "unsigned int allow_methods;"
- Dans le parsing, trier les "locations" par ordre de precision.
Compter les "/" dans le chemin, les locations avec le plus de "/" seront en premier dans le vector.
- Il faut vérifier le path de la requête, voir si le serveur est bien censé délivrer cette ressource et si le client y a accès, avant d'appeler le CGI.
__________________________
--------------------------
----Discord 42------------
Un truc cool et surtout bien utile ici c'est d'utiliser un proxy entre ton navigateur et ton serveur pour vérifier ce qui est envoyé en raw. Les navigateurs peuvent avoir des comportements différents. Un truc cool et surtout bien utile ici c'est d'utiliser un proxy entre ton navigateur et ton serveur pour vérifier ce qui est envoyé en raw. Les navigateurs peuvent avoir des comportements différents.
Vous avez des modules sur vos navigateur ou des logiciels externe. C'est assez rapide et gratuit. Vous avez des modules sur vos navigateur ou des logiciels externe. C'est assez rapide et gratuit.

127
srcs/Client.cpp Normal file
View File

@@ -0,0 +1,127 @@
#include "Client.hpp"
/*********************************************
* COPLIENS
*********************************************/
Client::Client( ) {
return;
}
Client::~Client() {
return;
}
// copy constructor :
// Client::Client( Client const & src ) {}
// assignement operator :
// Client & Client::operator=( Client const & rhs ) {}
/*********************************************
* PUBLIC MEMBER FUNCTIONS
*********************************************/
// http headers :
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
// https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests
// https://www.tutorialspoint.com/http/http_requests.htm
void Client::parse_request()
{
std::string sub;
std::vector<std::string> list;
size_t pos;
pos = (raw_request).find("\r\n\r\n");
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);
}
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 )
{
std::vector<std::string> sline;
std::string tmp;
sline = split(rline, ' ');
if (sline.size() != 3)
{
std::cerr << "err _parse_request_line(): ";
throw std::runtime_error("bad request-line header");
}
// 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++)
{
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) );
}
}
void Client::_parse_request_body( size_t pos )
{
std::string body = &raw_request[pos];
_request.body = body;
}
/*********************************************
* OVERLOAD
*********************************************/
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; }

View File

@@ -5,24 +5,46 @@
# include <iostream> # include <iostream>
# include <string> # include <string>
# include <map> # include <map>
# include <vector>
# include "utils.hpp"
struct Request
{
std::map<std::string, std::string> headers;
http_method method;
std::string path;
std::string version;
std::string body;
};
class Client class Client
{ {
public: public:
// Client(Placeholder); Client();
// Client(); ~Client();
// Client(Client const &src); //Client(Client const &src);
// ~Client(); //Client &operator=(Client const &rhs);
// Client &operator=(Client const &rhs);
// Client &operator=(int);
int fd; int fd;
std::string raw_request; std::string raw_request;
std::map<std::string, std::string> request;
std::string response; std::string response;
unsigned int status; unsigned int status;
// private: // 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);
void parse_request();
private:
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 );
}; };

View File

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

41
srcs/cgi-bin/cgi.cpp Normal file
View File

@@ -0,0 +1,41 @@
# include <iostream>
# include <string>
# include <sstream>
int main (int ac, char **av) {
std::string to_send;
std::string header;
std::string end_header = "\r\n\r\n";
std::string response;
std::stringstream strs;
header = "HTTP/1.1 200 OK\n";
header += "Content-Type: text/html; charset=UTF-8\n";
header += "Content-Length: ";
response = "<!DOCTYPE html>\n";
response += "<html>\n";
response += "<head>\n";
response += "<title>CGI</title>\n";
response += "</head>\n";
response += "<body>\n";
response += "<h2>CGI request :</h2>\n";
for (int i = 1; i < ac; i++)
{
response += "<p>";
response += av[i];
response += "</p>\n";
}
response += "</body>\n";
response += "</html>\n";
strs << response.size();
header += strs.str();
header += end_header;
to_send = header;
to_send += response;
std::cout << to_send;
return 0;
}

BIN
srcs/cgi-bin/cgi_cpp.cgi Executable file

Binary file not shown.

29
srcs/cgi-bin/php-cgi Executable file
View File

@@ -0,0 +1,29 @@
#! /usr/bin/php
<?php
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");
echo "\nGATEWAY_INTERFACE: " . getenv("GATEWAY_INTERFACE");
echo "\nPATH_INFO: " . getenv("PATH_INFO");
echo "\nPATH_TRANSLATED: " . getenv("PATH_TRANSLATED");
echo "\nQUERY_STRING: " . getenv("QUERY_STRING");
echo "\nREMOTE_ADDR: " . getenv("REMOTE_ADDR");
echo "\nREMOTE_HOST: " . getenv("REMOTE_HOST");
echo "\nREMOTE_IDENT: " . getenv("REMOTE_IDENT");
echo "\nREMOTE_USER: " . getenv("REMOTE_USER");
echo "\nREQUEST_METHOD: " . getenv("REQUEST_METHOD");
echo "\nSCRIPT_NAME: " . getenv("SCRIPT_NAME");
echo "\nSERVER_NAME: " . getenv("SERVER_NAME");
echo "\nSERVER_PORT: " . getenv("SERVER_PORT");
echo "\nSERVER_PROTOCOL: " . getenv("SERVER_PROTOCOL");
echo "\nSERVER_SOFTWARE: " . getenv("SERVER_SOFTWARE");
echo "\nREDIRECT_STATUS: " . getenv("REDIRECT_STATUS");
// echo $_POST['REQUEST_METHOD'];
echo "\n\n-----------\nEND PHP-CGI\n\n";
?>

View File

@@ -6,7 +6,7 @@
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */ /* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/13 22:11:17 by me #+# #+# */ /* 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++) for (unsigned long i = 0; i != tmp_val.size(); i++)
{ {
MethodType m = _str_to_method_type(tmp_val[i]); http_method m = ::str_to_http_method(tmp_val[i]);
if (m == 3) if (m == UNKNOWN)
throw std::invalid_argument("not a valid method"); throw std::invalid_argument("not a valid method");
server->allow_methods.push_back(m); 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++) for (unsigned long i = 0; i != tmp_val.size(); i++)
{ {
MethodType m = _str_to_method_type(tmp_val[i]); http_method m = ::str_to_http_method(tmp_val[i]);
if (m == 3) if (m == UNKNOWN)
throw std::invalid_argument("not a valid method"); throw std::invalid_argument("not a valid method");
location->allow_methods.push_back(m); location->allow_methods.push_back(m);
} }

View File

@@ -3,10 +3,10 @@
/* ::: :::::::: */ /* ::: :::::::: */
/* ConfigParser.hpp :+: :+: :+: */ /* ConfigParser.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */ /* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */ /* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/11 23:01:41 by me #+# #+# */ /* 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 "ServerConfig.hpp"
# include "LocationConfig.hpp" # include "LocationConfig.hpp"
# include "MethodType.hpp"
# include "utils.hpp" # include "utils.hpp"
# include <map> # include <map>
@@ -76,8 +75,6 @@ private:
std::string _get_rest_of_line(size_t *curr); // const? 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); 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 void ConfigParser::_print_content() const
{ {
std::cout << _content; std::cout << _content;

View File

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

View File

@@ -1,19 +1,8 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* ServerConfig.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/23 15:55:16 by me #+# #+# */
/* Updated: 2022/07/23 16:19:43 by me ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef SERVERCONFIG_HPP #ifndef SERVERCONFIG_HPP
# define SERVERCONFIG_HPP # define SERVERCONFIG_HPP
# include "MethodType.hpp" # include "utils.hpp"
# include "LocationConfig.hpp" # include "LocationConfig.hpp"
# include <map> # include <map>
@@ -53,7 +42,7 @@ public:
// i'm tempted to do something diff for storing method types... // i'm tempted to do something diff for storing method types...
// fuck it, you can only call allow_methods once in Server // fuck it, you can only call allow_methods once in Server
// once more in each location. // once more in each location.
std::vector<MethodType> allow_methods; std::vector<http_method> allow_methods;
std::vector<LocationConfig> locations; std::vector<LocationConfig> locations;
@@ -68,8 +57,6 @@ public:
// int redirect_status; // int redirect_status;
// std::string redirect_uri; // std::string redirect_uri;
void print_all() void print_all()
{ {
std::cout << "PRINTING A FULL SERVER CONFIG\n\n"; std::cout << "PRINTING A FULL SERVER CONFIG\n\n";
@@ -100,18 +87,7 @@ public:
std::cout << "\n----------\n"; std::cout << "\n----------\n";
} }
}; };
#endif #endif

View File

@@ -3,9 +3,9 @@
std::vector<std::string> split(std::string input, char delimiter) std::vector<std::string> split(std::string input, char delimiter)
{ {
std::vector<std::string> answer; std::vector<std::string> answer;
std::stringstream ss(input); std::stringstream ss(input);
std::string temp; std::string temp;
while (getline(ss, temp, delimiter)) while (getline(ss, temp, delimiter))
answer.push_back(temp); answer.push_back(temp);
@@ -13,6 +13,22 @@ std::vector<std::string> split(std::string input, char delimiter)
return answer; return answer;
} }
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);
return str;
}
std::string itos(int n)
{
std::stringstream strs;
strs << n;
return ( strs.str() );
}
bool isNumeric(std::string str) bool isNumeric(std::string str)
{ {
for (size_t i = 0; i < str.length(); i++) for (size_t i = 0; i < str.length(); i++)
@@ -23,7 +39,6 @@ bool isNumeric(std::string str)
return true; return true;
} }
bool isNumeric_btw(int low, int high, std::string str) bool isNumeric_btw(int low, int high, std::string str)
{ {
for (size_t i = 0; i < str.length(); i++) for (size_t i = 0; i < str.length(); i++)
@@ -37,11 +52,35 @@ bool isNumeric_btw(int low, int high, std::string str)
return true; return true;
} }
char* itoa(int n) http_method str_to_http_method(std::string &str)
{ {
std::stringstream strs; if (str == "GET")
return GET;
strs << n; else if (str == "POST")
// casts : https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used return POST;
return ( const_cast<char*>( strs.str().c_str() ) ); 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);
} }

View File

@@ -5,11 +5,32 @@
# include <vector> # include <vector>
# include <string> # include <string>
# include <sstream> # include <sstream>
# include <cstdlib> // atoi (athough it's already cover by <string>) # include <cstdlib> // atoi
std::vector<std::string> split(std::string input, char delimiter); // enum http_method
bool isNumeric(std::string str); // {
bool isNumeric_btw(int low, int high, std::string str); // UNKNOWN = 0b00000000,
char* itoa(int n); // GET = 0b00000001,
// POST = 0b00000010,
// DELETE = 0b00000100,
// ALL_METHODS = 0b11111111,
// };
enum http_method
{
UNKNOWN = 0b0,
GET = 1 << 0,
POST = 1 << 1,
DELETE = 1 << 2,
ALL_METHODS = 0b11111111,
};
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);
#endif #endif

View File

@@ -22,37 +22,17 @@
// # include <netinet/ip.h> // usefull for what ? -> 'man (7) ip' says it's a superset of 'netinet/in.h' // # include <netinet/ip.h> // usefull for what ? -> 'man (7) ip' says it's a superset of 'netinet/in.h'
# include <algorithm> // find # include <algorithm> // find
# include <string> // string # include <string> // string
# include <cstdio> // perror # include <cstdio> // perror, remove
# include <cstdlib> // atoi (athough it's already cover by <string>) # include <cstdlib> // atoi (athough it's already cover by <string>)
# include "Client.hpp" # include "Client.hpp"
# include "ServerConfig.hpp" # include "ServerConfig.hpp"
# include "utils.hpp" # include "utils.hpp"
// TODO: A virer
//# include "ConfigParser.hpp"
//# include "LocationConfig.hpp"
//# include "MethodType.hpp"
//# include "utils.hpp"
// TODO: A virer
extern bool g_run; extern bool g_run;
extern int g_last_signal; extern int g_last_signal;
void signal_handler(int signum); 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 // these might only be TMP
# define FAILURE -1 # define FAILURE -1
# define SUCCESS 1 # define SUCCESS 1
@@ -77,9 +57,9 @@ class Webserv
private: private:
int _epfd; int _epfd;
std::vector<int> _listen_sockets; std::vector<int> _listen_sockets;
std::vector<ServerConfig> _servers; std::vector<ServerConfig> _servers;
std::vector<Client> _clients; std::vector<Client> _clients;
// accept.cpp // accept.cpp
void _accept_connection(int fd); void _accept_connection(int fd);
@@ -88,10 +68,21 @@ class Webserv
void _read_request(Client *client); void _read_request(Client *client);
// response.cpp // response.cpp
void _response(Client *client); void _response(Client *client);
void _send_response(Client *client); void _send_response(Client *client, ServerConfig &server);
void _construct_response(Client *client); void _construct_response(Client *client, ServerConfig &server);
void _insert_status_line(Client *client); void _insert_status_line(Client *client, ServerConfig &server);
void _get_ressource(Client *client); void _get_ressource(Client *client, ServerConfig &server, LocationConfig &location);
void _post(Client *client, ServerConfig &server, LocationConfig &location);
void _delete(Client *client, ServerConfig &server, LocationConfig &location);
ServerConfig &_determine_process_server(Client *client);
LocationConfig &_determine_location(ServerConfig &server, 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);
// epoll_update.cpp // epoll_update.cpp
int _epoll_update(int fd, uint32_t events, int op); int _epoll_update(int fd, uint32_t events, int op);
int _epoll_update(int fd, uint32_t events, int op, void *ptr); int _epoll_update(int fd, uint32_t events, int op, void *ptr);

View File

@@ -0,0 +1,80 @@
#include "Webserv.hpp"
bool Webserv::_is_cgi(Client *client)
{
if (client->get_path().find("/cgi-bin/") != std::string::npos)
return true;
return false;
}
void Webserv::_exec_cgi(Client *client)
{
char** env;
env = _set_env(client);
_exec_script(client, env);
// _construct_response(client);
}
char* Webserv::_dup_env(std::string var, std::string val = "")
{
std::string str;
str = var + "=" + val;
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[18] = NULL;
return env;
}
void Webserv::_exec_script(Client *client, char **env)
{
int save_stdout;
char * const * nll = 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, client->env);
execve("./srcs/cgi-bin/php-cgi", nll, env);
}
// inside parent process
else
waitpid(-1, NULL, 0);
// restore stdout
dup2(save_stdout, STDOUT_FILENO);
}
void Webserv::_construct_client(Client *client)
{
(void)client;
}

View File

@@ -6,7 +6,7 @@ void Webserv::_close_client(int fd)
std::vector<Client>::iterator it = _clients.begin(); std::vector<Client>::iterator it = _clients.begin();
while (it != _clients.end()) while (it != _clients.end())
{ {
if (it->fd == fd) if (*it == fd)
{ {
// _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG // _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(fd) == -1) if (::close(fd) == -1)

View File

@@ -37,6 +37,8 @@ void Webserv::_read_request(Client *client)
buf[ret] = '\0'; buf[ret] = '\0';
client->raw_request.append(buf); client->raw_request.append(buf);
client->parse_request();
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD); _epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD);
} }

View File

@@ -3,25 +3,25 @@
void Webserv::_response(Client *client) void Webserv::_response(Client *client)
{ {
_send_response(client); ServerConfig &server = _determine_process_server(client);
_send_response(client, server);
if (g_last_signal) if (g_last_signal)
_handle_last_signal(); _handle_last_signal();
} }
void Webserv::_send_response(Client *client) void Webserv::_send_response(Client *client, ServerConfig &server)
{ {
ssize_t ret; ssize_t ret;
std::cerr << "send()\n"; std::cerr << "send()\n";
std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n"; // DEBUG // std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n"; // DEBUG
_construct_response(client); _construct_response(client, server);
ret = ::send(client->fd, client->response.data(), client->response.size(), 0); ret = ::send(client->fd, client->response.c_str(), client->response.size(), 0);
if (ret == -1) if (ret == -1)
{ {
std::perror("err send()"); std::perror("err send()");
std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
_close_client(client->fd); _close_client(client->fd);
return ; return ;
@@ -37,25 +37,55 @@ void Webserv::_send_response(Client *client)
} }
} }
void Webserv::_construct_response(Client *client) void Webserv::_construct_response(Client *client, ServerConfig &server)
{ {
client->status = 200; LocationConfig &location = _determine_location(server, client->get_path());
client->status = 200; // default value
client->response.append("Server: Webserv/0.1\r\n"); client->response.append("Server: Webserv/0.1\r\n");
if (client->raw_request.find("Connection: close") != std::string::npos) if (client->get_headers("Connection") == "close")
client->response.append("Connection: close\r\n"); client->response.append("Connection: close\r\n");
else else
client->response.append("Connection: keep-alive\r\n"); client->response.append("Connection: keep-alive\r\n");
_get_ressource(client);
_insert_status_line(client); unsigned int allow_methods = ALL_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 ALL_METHODS
if (client->get_method() == UNKNOWN)
{
client->status = 400;
}
else if (allow_methods & client->get_method())
{
if (client->get_method() & GET)
_get_ressource(client, server, location);
else if (client->get_method() & POST)
_post(client, server, location);
else if (client->get_method() & DELETE)
_delete(client, server, location);
}
else
{
client->status = 405;
client->response.append("Allow: ");
client->response.append(::http_methods_to_str(allow_methods));
client->response.append("\r\n");
}
_insert_status_line(client, server);
} }
#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>" // https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
#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 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 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>" #define S200 "200 OK"
void Webserv::_insert_status_line(Client *client) #define S400 "400 Bad Request"
#define S404 "404 Not Found"
#define S500 "500 Internal Server Error"
void Webserv::_insert_status_line(Client *client, ServerConfig &server)
{ {
std::string status_line; std::string status_line;
@@ -64,19 +94,19 @@ void Webserv::_insert_status_line(Client *client)
switch (client->status) switch (client->status)
{ {
case (200): case (200):
status_line.append("200 OK"); status_line.append(S200);
break; break;
case (400): case (400):
status_line.append("400 Not Found"); status_line.append(S400);
client->response.append(E400); client->response.append(HTML_ERROR(S400));
break; break;
case (404): case (404):
status_line.append("404 Not Found"); status_line.append(S404);
client->response.append(E404); client->response.append(HTML_ERROR(S404));
break; break;
case (500): case (500):
status_line.append("500 Internal Server Error"); status_line.append(S500);
client->response.append(E500); client->response.append(HTML_ERROR(S500));
break; break;
} }
status_line.append("\r\n"); status_line.append("\r\n");
@@ -84,41 +114,41 @@ void Webserv::_insert_status_line(Client *client)
client->response.insert(0, status_line); client->response.insert(0, status_line);
} }
#define ROOT "website" #define ROOT "www"
#define INDEX "index.html" #define INDEX "index.html"
#define MAX_FILESIZE 1000000 // (1Mo) #define MAX_FILESIZE 1000000 // (1Mo)
void Webserv::_get_ressource(Client *client) void Webserv::_get_ressource(Client *client, ServerConfig &server, LocationConfig &location)
{ {
std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ? std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ?
char buf[MAX_FILESIZE+1]; char buf[MAX_FILESIZE+1];
char *tmp; std::string tmp;
std::string path = client->get_path();
// Mini parsing à l'arrache du PATH if (path == "/") // TODO ; With server config
std::string path; path.append(INDEX);
try path.insert(0, ROOT);
{
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 ;
}
if (access(path.data(), R_OK) == -1) std::cerr << "path = " << path << "\n";
// TMP HUGO
//
if (_is_cgi(client))
{
_exec_cgi(client);
return;
}
//
// END TMP HUGO
if (access(path.c_str(), R_OK) == -1)
{ {
std::perror("err access()"); std::perror("err access()");
client->status = 404; client->status = 404;
return ; return ;
} }
ifd.open(path.data(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?) ifd.open(path.c_str(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?)
if (!ifd) if (!ifd)
{ {
std::cerr << path << ": open fail" << '\n'; std::cerr << path << ": open fail" << '\n';
@@ -141,18 +171,78 @@ void Webserv::_get_ressource(Client *client)
ifd.read(buf, size); ifd.read(buf, size);
buf[ifd.gcount()] = '\0'; buf[ifd.gcount()] = '\0';
client->response.append("Content-Type: text/html; charset=UTF-8\r\n"); client->response.append("Content-Type: text/html; charset=UTF-8\r\n"); // TODO : determine Content-Type
client->response.append("Content-Length: "); client->response.append("Content-Length: ");
tmp = ::itoa(ifd.gcount()); tmp = ::itos(ifd.gcount());
client->response.append(tmp); client->response.append(tmp);
client->response.append("\r\n"); client->response.append("\r\n");
// Body // Body
client->response.append("\r\n"); client->response.append("\r\n");
client->response.append(buf); client->response.append(buf);
ifd.close(); ifd.close();
} }
} }
void Webserv::_post(Client *client, ServerConfig &server, LocationConfig &location)
{
/*
TODO
*/
(void)0;
}
void Webserv::_delete(Client *client, ServerConfig &server, LocationConfig &location)
{
/*
TODO
*/
(void)0;
}
ServerConfig &Webserv::_determine_process_server(Client *client)
{
/*
TODO : determine virtual server based on ip_address::port and server_name
Ior now its just based on server_name.
(maybe with a map< string, std::vector<ServerConfig> > where the string is "ip_adress::port")
http://nginx.org/en/docs/http/request_processing.html
*/
std::string &server_name = client->get_headers("Host");
std::vector<ServerConfig>::iterator it = _servers.begin();
while (it != _servers.end())
{
if ( std::find(it->server_name.begin(), it->server_name.end(), server_name) != it->server_name.end() )
break;
++it;
}
if (it != _servers.end())
return (*it);
else
return (_servers.front());
}
LocationConfig &Webserv::_determine_location(ServerConfig &server, std::string &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 MAX_EVENTS 42 // arbitrary
#define TIMEOUT 3000 #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() void Webserv::run()
{ {
std::cerr << "Server started\n"; std::cerr << "Server started\n";

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Le Webserv</title>
</head>
<body>
<h1 style="text-align:center">Le index (˘ ͜ʖ˘)</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>

View File

@@ -1,16 +1,11 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<!-- <link href="styles/style.css" rel="stylesheet"> --> <title>Le Webserv</title>
</head> </head>
<body> <body>
<h1 style="text-align:center">Le index (˘ ͜ʖ˘)</h1>
<h1>My First Heading</h1> <hr>
<p>My first paragraph.</p> <p style="text-align:center">(˚3˚)</p>
</body> </body>
</html> </html>

View File

@@ -309,4 +309,4 @@ Full stop should appear outside the parentheses in the last sentence.
</pre></body></html> </pre></body></html>