diff --git a/42_testers/cgi_tester b/42_testers/cgi_tester new file mode 100755 index 0000000..72def28 Binary files /dev/null and b/42_testers/cgi_tester differ diff --git a/42_testers/tester b/42_testers/tester new file mode 100755 index 0000000..34c9ab1 Binary files /dev/null and b/42_testers/tester differ diff --git a/Makefile b/Makefile index 82cf35a..a775cd8 100644 --- a/Makefile +++ b/Makefile @@ -3,25 +3,15 @@ NAME = webserv CXX = c++ CXXFLAGS = -Wall -Wextra #-Werror -CXXFLAGS += $(HEADERS_I) +CXXFLAGS += $(HEADERS_D:%=-I%) CXXFLAGS += -std=c++98 CXXFLAGS += -g CXXFLAGS += -MMD -MP #header dependencie #CXXFLAGS += -O3 -#SHELL = /bin/zsh VPATH = $(SRCS_D) -HEADERS_I = $(HEADERS_D:%=-I%) -HEADERS_D = srcs \ - headers -HEADERS = Webserv.hpp \ - ConfigParser.hpp \ - ServerConfig.hpp \ - LocationConfig.hpp \ - Client.hpp \ - MethodType.hpp \ - utils.hpp \ +HEADERS_D = srcs SRCS_D = srcs \ srcs/webserv @@ -33,6 +23,8 @@ SRCS = main.cpp \ ConfigParserUtils.cpp \ ConfigParserPost.cpp \ utils.cpp \ + cgi_script.cpp \ + Client.cpp \ OBJS_D = builds OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o) diff --git a/README.md b/README.md new file mode 100644 index 0000000..20c8b93 --- /dev/null +++ b/README.md @@ -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 don’t 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 can’t 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 don’t 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 wouldn’t 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 can’t 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 won’t 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
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) + diff --git a/YoupiBanane/Yeah/not_happy.bad_extension b/YoupiBanane/Yeah/not_happy.bad_extension new file mode 100644 index 0000000..e69de29 diff --git a/YoupiBanane/nop/other.pouic b/YoupiBanane/nop/other.pouic new file mode 100644 index 0000000..e69de29 diff --git a/YoupiBanane/nop/youpi.bad_extension b/YoupiBanane/nop/youpi.bad_extension new file mode 100644 index 0000000..e69de29 diff --git a/YoupiBanane/youpi.bad_extension b/YoupiBanane/youpi.bad_extension new file mode 100644 index 0000000..e69de29 diff --git a/YoupiBanane/youpi.bla b/YoupiBanane/youpi.bla new file mode 100644 index 0000000..e69de29 diff --git a/webserv.en.subject.pdf b/docs/webserv.en.subject.pdf similarity index 100% rename from webserv.en.subject.pdf rename to docs/webserv.en.subject.pdf diff --git a/webserv.fr.subject.pdf b/docs/webserv.fr.subject.pdf similarity index 100% rename from webserv.fr.subject.pdf rename to docs/webserv.fr.subject.pdf diff --git a/docs/webserv_correction.pdf b/docs/webserv_correction.pdf new file mode 100644 index 0000000..d0d6e88 Binary files /dev/null and b/docs/webserv_correction.pdf differ diff --git a/srcs/Client.cpp b/srcs/Client.cpp new file mode 100644 index 0000000..e6c6f39 --- /dev/null +++ b/srcs/Client.cpp @@ -0,0 +1,116 @@ + +#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 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); +} + + + +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_headers(std::string key) { return _request.headers[key]; } + +/********************************************* + * PRIVATE MEMBER FUNCTIONS + *********************************************/ + +void Client::_parse_request_line( std::string rline ) +{ + std::vector 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 = 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 list ) +{ + std::string key; + std::string val; + std::vector::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(key, val) ); + } +} + +void Client::_parse_request_body( size_t pos ) +{ + std::string body = &raw_request[pos]; + + _request.body = body; +} + + diff --git a/srcs/Client.hpp b/srcs/Client.hpp index ede81f3..002f1c5 100644 --- a/srcs/Client.hpp +++ b/srcs/Client.hpp @@ -5,24 +5,45 @@ # include # include # include +# include +# include "utils.hpp" + +struct Request +{ + std::map headers; + std::string method; + std::string path; + std::string version; + std::string body; +}; class Client { public: - // Client(Placeholder); - // Client(); - // Client(Client const &src); - // ~Client(); - // Client &operator=(Client const &rhs); - // Client &operator=(int); + Client(); + ~Client(); + //Client(Client const &src); + //Client &operator=(Client const &rhs); - int fd; - std::string raw_request; - std::map request; + int fd; + std::string raw_request; std::string response; unsigned int status; - // private: + std::string get_method(); + std::string get_path(); + std::string get_version(); + std::string get_body(); + std::string get_headers(std::string key); + + void parse_request(); + + private: + struct Request _request; + + void _parse_request_line( std::string rline ); + void _parse_request_headers( std::vector list ); + void _parse_request_body( size_t pos ); }; diff --git a/srcs/Server.hpp b/srcs/Server.hpp new file mode 100644 index 0000000..7fcb4d9 --- /dev/null +++ b/srcs/Server.hpp @@ -0,0 +1,21 @@ + +#ifndef SERVER_HPP +# define SERVER_HPP + +# include +# include + +class Server +{ + public: + // Server(Placeholder); + // Server(); + // Server(Server const &src); + // ~Server(); + // Server &operator=(Server const &rhs); + + private: + +}; + +#endif diff --git a/srcs/ServerConfig.hpp b/srcs/ServerConfig.hpp index b589d13..ce49255 100644 --- a/srcs/ServerConfig.hpp +++ b/srcs/ServerConfig.hpp @@ -1,14 +1,3 @@ -/* ************************************************************************** */ -/* */ -/* ::: :::::::: */ -/* ServerConfig.hpp :+: :+: :+: */ -/* +:+ +:+ +:+ */ -/* By: me +#+ +:+ +#+ */ -/* +#+#+#+#+#+ +#+ */ -/* Created: 2022/07/23 15:55:16 by me #+# #+# */ -/* Updated: 2022/07/23 16:19:43 by me ### ########.fr */ -/* */ -/* ************************************************************************** */ #ifndef SERVERCONFIG_HPP # define SERVERCONFIG_HPP @@ -68,8 +57,6 @@ public: // int redirect_status; // std::string redirect_uri; - - void print_all() { std::cout << "PRINTING A FULL SERVER CONFIG\n\n"; @@ -100,18 +87,7 @@ public: std::cout << "\n----------\n"; } - - }; - - - #endif - - - - - - diff --git a/srcs/Webserv.hpp b/srcs/Webserv.hpp index 0c60194..6b858a4 100644 --- a/srcs/Webserv.hpp +++ b/srcs/Webserv.hpp @@ -77,9 +77,9 @@ class Webserv private: int _epfd; - std::vector _listen_sockets; + std::vector _listen_sockets; std::vector _servers; - std::vector _clients; + std::vector _clients; // accept.cpp void _accept_connection(int fd); @@ -92,6 +92,13 @@ class Webserv void _construct_response(Client *client); void _insert_status_line(Client *client); void _get_ressource(Client *client); + // 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 int _epoll_update(int fd, uint32_t events, int op); int _epoll_update(int fd, uint32_t events, int op, void *ptr); diff --git a/srcs/Webserv_hugo.cpp b/srcs/Webserv_hugo.cpp new file mode 100644 index 0000000..e373ffa --- /dev/null +++ b/srcs/Webserv_hugo.cpp @@ -0,0 +1,447 @@ + +#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(events[i].data.ptr)); + else if (events[i].events & EPOLLOUT) + _send_response(static_cast(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 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::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(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"); + } +} diff --git a/srcs/cgi-bin/cgi.cpp b/srcs/cgi-bin/cgi.cpp new file mode 100644 index 0000000..3882239 --- /dev/null +++ b/srcs/cgi-bin/cgi.cpp @@ -0,0 +1,41 @@ +# include +# include +# include + +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 = "\n"; + response += "\n"; + response += "\n"; + response += "CGI\n"; + response += "\n"; + response += "\n"; + response += "

CGI request :

\n"; + for (int i = 1; i < ac; i++) + { + response += "

"; + response += av[i]; + response += "

\n"; + } + response += "\n"; + response += "\n"; + + strs << response.size(); + header += strs.str(); + header += end_header; + to_send = header; + to_send += response; + + std::cout << to_send; + + return 0; +} diff --git a/srcs/cgi-bin/cgi_cpp.cgi b/srcs/cgi-bin/cgi_cpp.cgi new file mode 100755 index 0000000..95013cb Binary files /dev/null and b/srcs/cgi-bin/cgi_cpp.cgi differ diff --git a/srcs/cgi-bin/php-cgi b/srcs/cgi-bin/php-cgi new file mode 100755 index 0000000..b51a88e --- /dev/null +++ b/srcs/cgi-bin/php-cgi @@ -0,0 +1,29 @@ +#! /usr/bin/php + + + diff --git a/srcs/ft_itoa.cpp b/srcs/ft_itoa.cpp new file mode 100644 index 0000000..90f6ff7 --- /dev/null +++ b/srcs/ft_itoa.cpp @@ -0,0 +1,13 @@ + +# include +# include + +char* itoa(int n) +{ + std::stringstream strs; + char * str; + + strs << n; + str = (char*)(strs.str().c_str()); + return (str); +} diff --git a/srcs/utils.cpp b/srcs/utils.cpp index 112cedb..48af746 100644 --- a/srcs/utils.cpp +++ b/srcs/utils.cpp @@ -3,9 +3,9 @@ std::vector split(std::string input, char delimiter) { - std::vector answer; - std::stringstream ss(input); - std::string temp; + std::vector answer; + std::stringstream ss(input); + std::string temp; while (getline(ss, temp, delimiter)) answer.push_back(temp); @@ -13,6 +13,22 @@ std::vector split(std::string input, char delimiter) 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) { for (size_t i = 0; i < str.length(); i++) @@ -23,7 +39,6 @@ bool isNumeric(std::string str) return true; } - bool isNumeric_btw(int low, int high, std::string str) { for (size_t i = 0; i < str.length(); i++) @@ -37,11 +52,3 @@ bool isNumeric_btw(int low, int high, std::string str) return true; } -char* itoa(int n) -{ - std::stringstream strs; - - strs << n; - // casts : https://stackoverflow.com/questions/332030/when-should-static-cast-dynamic-cast-const-cast-and-reinterpret-cast-be-used - return ( const_cast( strs.str().c_str() ) ); -} diff --git a/srcs/utils.hpp b/srcs/utils.hpp index 64ca8f3..a2e2409 100644 --- a/srcs/utils.hpp +++ b/srcs/utils.hpp @@ -5,11 +5,12 @@ # include # include # include -# include // atoi (athough it's already cover by ) +# include // atoi std::vector split(std::string input, char delimiter); bool isNumeric(std::string str); bool isNumeric_btw(int low, int high, std::string str); -char* itoa(int n); +std::string itos(int n); +std::string trim(std::string str, char c); #endif diff --git a/srcs/webserv/cgi_script.cpp b/srcs/webserv/cgi_script.cpp new file mode 100644 index 0000000..ee1319e --- /dev/null +++ b/srcs/webserv/cgi_script.cpp @@ -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", 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; +} + diff --git a/srcs/webserv/request.cpp b/srcs/webserv/request.cpp index de761f5..34338fb 100644 --- a/srcs/webserv/request.cpp +++ b/srcs/webserv/request.cpp @@ -37,6 +37,8 @@ void Webserv::_read_request(Client *client) buf[ret] = '\0'; client->raw_request.append(buf); + client->parse_request(); _epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD); } + diff --git a/srcs/webserv/response.cpp b/srcs/webserv/response.cpp index 800903d..da818e7 100644 --- a/srcs/webserv/response.cpp +++ b/srcs/webserv/response.cpp @@ -86,12 +86,22 @@ void Webserv::_insert_status_line(Client *client) #define ROOT "website" #define INDEX "index.html" -#define MAX_FILESIZE 1000000 // (1Mo) +#define MAX_FILESIZE 1000000 // (1Mo) void Webserv::_get_ressource(Client *client) { - std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ? - char buf[MAX_FILESIZE+1]; - char *tmp; + std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ? + char buf[MAX_FILESIZE+1]; + std::string tmp; + + // TMP HUGO + // + if (_is_cgi(client)) + { + _exec_cgi(client); + return; + } + // + // END TMP HUGO // Mini parsing à l'arrache du PATH std::string path; @@ -144,15 +154,16 @@ void Webserv::_get_ressource(Client *client) client->response.append("Content-Type: text/html; charset=UTF-8\r\n"); client->response.append("Content-Length: "); - tmp = ::itoa(ifd.gcount()); - client->response.append(tmp); + + 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->response.append(buf); ifd.close(); } } + diff --git a/website/rfc2119_sigabrt.html b/website/rfc2119_sigabrt.html index 18be9ac..b8b389b 100644 --- a/website/rfc2119_sigabrt.html +++ b/website/rfc2119_sigabrt.html @@ -309,4 +309,4 @@ Full stop should appear outside the parentheses in the last sentence. - \ No newline at end of file +