diff --git a/Makefile b/Makefile index 8b01509..2f15769 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,8 @@ CXX = clang++ CXXFLAGS = -Wall -Wextra #-Werror CXXFLAGS += $(HEADERS_D:%=-I%) CXXFLAGS += -std=c++98 -CXXFLAGS += -g3 +CXXFLAGS += -g +CXXFLAGS += -fno-limit-debug-info CXXFLAGS += -MMD -MP #header dependencie #CXXFLAGS += -O3 @@ -29,7 +30,7 @@ SRCS = main.cpp \ postProcessing.cpp \ utils.cpp \ cgi_script.cpp \ - Client.cpp \ + Client.cpp Client_multipart_body.cpp \ OBJS_D = builds OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o) diff --git a/README.md b/README.md index 831a28f..3f0b40e 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,16 @@ ## work together -#### TODO hugo -- `_is_cgi()` and `_fill_cgi_path()` -- two cgi tests : -? - a basic form with "name" and "something", that return a html page with that -? - for GET and POST -? - a script called by a file extension in URI +#### next commit #### questions +- how should we handle a wrong url like `http://localhost/cgi-bin/wrong.phpp/good.php` ? + - do we serve `./srcs/cgi-bin/good.php` ? + - or do we return 404 "not found" ? +-> - for now, execve would crash, but that doesn't produce a 404 error, rather a 500, is it bad ? + - could we use errno after execve to choose an appropriate http error ? subject says : "Checking the value of errno is strictly forbidden after a read or a write operation" +- if a url has a file with extension, but it's not a cgi extension, is it necessary to look further ? + - ex. `http://localhost/file.php/file.py` for `cgi_ext py;` ? - the response page is received long after the cgi-script is done, why ? --- diff --git a/default.config b/default.config index a287ed2..344ea17 100644 --- a/default.config +++ b/default.config @@ -7,7 +7,7 @@ server { listen 0.0.0.0:4040; # client_body_limit asdfa; -# client_body_limit 400; + client_body_limit 3000000; index index.html; # this is another comment @@ -41,6 +41,29 @@ server { autoindex on; } + location /cgi-bin { + root ./srcs/cgi-bin/; + cgi_ext cpp php sh; + } + + location /cgi-bin { + root ./srcs/cgi-bin/; + cgi_ext cpp php sh; + } + + location /upload { + allow_methods POST; + autoindex on; + upload_dir ./www/user_files/; # TODO: append a '/' if there is none ? + # root doesn’t matter if used only with POST and no CGI + } + + location /the_dump { + allow_methods GET; + root ./www/user_files; + autoindex on; + } + location /redirect { redirect 307 https://fr.wikipedia.org/wiki/Ketchup; # redirect 307 https://www.youtube.com/watch?v=rG6b8gjMEkw; @@ -72,13 +95,11 @@ server { location /test/test_deeper/ { # allow_methods - autoindex on; root ./www/test/test_deeper/; } location /test/test_deeper/super_deep { root ./www/test/test_deeper/super_deep/; - autoindex on; } # location /test/test_deeper/something.html { diff --git a/memo.txt b/memo.txt index c0c2abc..1e57ae9 100644 --- a/memo.txt +++ b/memo.txt @@ -1,14 +1,20 @@ IN 42 SUBJECT AND/OR PRIORITY : - CGI (TODO HUGO) -- chunked request (WIP, a bit difficult) +- chunked request (need testing) - Need to test normal body parsing -- basic html upload page for testing request of web browser -- upload files with config "upload_dir" +- upload files testing and adjustements +- https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size +Config en Ko (value * 2^10) serait plus commode que en octet +Et 0 valeur special pour desactiver - Ecrire des tests ! - handle redirection (Work, but weird behavior need deeper test) -- _determine_location() review (New version to complete and test) + +- check return 0 de send() +- curl --resolve, for testing hostname +- test limit de connexions sur listen() + ----------------------------- Si ce n'est pas deja fait : - dans config, check erreur si port > 16bits diff --git a/multipart_request.txt b/multipart_request.txt new file mode 100644 index 0000000..435f47b --- /dev/null +++ b/multipart_request.txt @@ -0,0 +1,53 @@ +POST /upload HTTP/1.1 +Host: localhost:4040 +Connection: keep-alive +Content-Length: 364 +Cache-Control: max-age=0 +sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104" +sec-ch-ua-mobile: ?0 +sec-ch-ua-platform: "Windows" +Upgrade-Insecure-Requests: 1 +Origin: http://localhost:4040 +Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeIV4xrEzThmNUcJf +User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 +Sec-Fetch-Site: same-origin +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Referer: http://localhost:4040/upload_form.html +Accept-Encoding: gzip, deflate, br +Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7 +dnt: 1 +sec-gpc: 1 + +------WebKitFormBoundaryeIV4xrEzThmNUcJf +Content-Disposition: form-data; name="upload_file"; filename=".gitignore" +Content-Type: text/plain + +.DS_Store +Thumbs.db +*.o +*.d +*.swp +*.out +*.exe +*.stackdump +*.a +*.so +*.dSYM +.vscode +*.lnk +*.zip + +builds + +ubuntu_tester +ubuntu_cgi_tester +webserv +!**/webserv/ +*.log + +large.jpg + +------WebKitFormBoundaryeIV4xrEzThmNUcJf-- \ No newline at end of file diff --git a/srcs/Client.cpp b/srcs/Client.cpp index db07f09..f3247c2 100644 --- a/srcs/Client.cpp +++ b/srcs/Client.cpp @@ -92,7 +92,7 @@ void Client::parse_request_headers(std::vector &servers) _parse_request_fields(); // DEBUG -print_client("headers"); +// print_client("headers"); if (status) return; @@ -101,10 +101,10 @@ print_client("headers"); _check_request_errors(); if (status) return; - _parse_port_hostname(this->get_rq_headers("Host")); // use getter for headers because it works case insensitive + _parse_port_hostname(get_rq_headers("Host")); // use getter for headers because it works case insensitive // DEBUG -std::cerr << get_rq_method_str() << " " << get_rq_target() << " " << get_rq_version() << "\n"; +// std::cerr << get_rq_method_str() << " " << get_rq_target() << " " << get_rq_version() << "\n"; // dont clear raw_request, we need it for future reparsing of body // see call of parse_request() in _read_request() @@ -113,6 +113,7 @@ std::cerr << get_rq_method_str() << " " << get_rq_target() << " " << get_rq_vers void Client::parse_request_body() { + std::cerr << "parse_request_body()\n"; size_t pos; pos = raw_request.find(CRLF CRLF); @@ -120,14 +121,38 @@ void Client::parse_request_body() { std::cerr << "parse_request_body() bad call, header incomplete\n"; // QUESTION from hugo : don't we change the status here ? + // RESPONSE from luke : C'est vrai. Peut-être mettre un 500, c'etait plus du debug à la base. + // C'est seulement si on appelle la fonction au mauvais endroit, avant d'avoir un header complet, que ça arrive. return; } - pos += CRLF_SIZE*2; - // Chunked decoding WIP. Dont work. if (!get_rq_headers("Transfer-Encoding").empty() && get_rq_headers("Transfer-Encoding") == "chunked") { + // Chunked decoding WIP. How to test this ? dont know how to send chunks with telnet. + _parse_chunked_body(pos + CRLF_SIZE*2); + } + else if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10)) + { + if (get_rq_headers("Content-Type").find("multipart/form-data") != NPOS) + _parse_multipart_body(pos); + else + _request.body = raw_request.substr(pos + CRLF_SIZE*2); + body_complete = true; + } + // std::cerr << "Content-Length = " << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n"; + // std::cerr << "raw_request.size() - pos = " << raw_request.size() - pos << "\n"; + // _request.body = raw_request.substr(pos); + // std::cerr << "_request.body.size() = " << _request.body.size() << "\n"; + +/////////////// +// Body checks + if (_request.body.size() > assigned_server->client_body_limit) + status = 413; // HTTP Client Errors +} + +void Client::_parse_chunked_body(size_t pos) +{ size_t chunk_size = 1; size_t chunk_field_end = 0; char *endptr = NULL; @@ -142,28 +167,29 @@ void Client::parse_request_body() pos = 0; while (chunk_size != 0) { - if (pos > _request.body.size()) +/* if (pos > _request.body.size()) { std::cerr << "parse_request_body(), pos > size()\n"; // status = 400; return; - } - + } */ +/* if (pos == _request.body.size()) { std::cerr << "parse_request_body(), will reread till last chunk\n"; return; - } + } */ - endptr_copy = endptr; + /* endptr_copy = endptr; */ + (void)endptr_copy; chunk_size = std::strtoul(&_request.body[pos], &endptr, 16); - if (chunk_size == LONG_MAX && errno == ERANGE) - status = 413; - if (endptr == endptr_copy) +/* if (chunk_size == LONG_MAX && errno == ERANGE) + status = 413; */ + /* if (endptr == endptr_copy) { std::cerr << "parse_request_body(), no conversion possible\n"; return; - } + } */ chunk_field_end = _request.body.find(CRLF, pos); @@ -181,48 +207,26 @@ void Client::parse_request_body() _request.headers.erase("Transfer-Encoding"); body_complete = true; - } - else - { - if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10)) - { - _request.body = raw_request.substr(pos); - body_complete = true; - } - - /* Should be equivalent */ - // _request.body = raw_request.substr(pos); - // if (_request.body.size() >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10)) - // body_complete = true; - } - -/////////////// -// Body checks - if (_request.body.size() > assigned_server->client_body_limit) - status = 413; // HTTP Client Errors } -// TODO HUGO : faire la fonction, mdr. -void Client::fill_script_path(const std::string &path, size_t pos) + + +void Client::fill_script_path(std::string &path, size_t pos) { - (void)path; - (void)pos; -/* size_t pos; - size_t len = path.size(); - std::string path = this->get_rq_abs_path(); std::string tmp; - pos = path.find(script); - if (pos == 0) + /*DEBUG*/ std::cout << "\n" << B_PURPLE << "debug path dot" << RESET << "\npath:[" << path << "]\n" << "&path[pos]:[" << &path[pos] << "]\n"; + if (path[0] == '.') { - tmp = path.substr(0, pos + len); - _request.script.path = "./srcs" + tmp; // TODO: root path ? - - _request.script.path = "./srcs" + tmp; // TODO: root path ? - _request.script.info = path.substr(pos + len); - return true; + path.erase(0, 1); + pos--; } - return false; */ + /*DEBUG*/ std::cout << "path:[" << path << "]\n" << "&path[pos]:[" << &path[pos] << "]\n"; + + _request.script.path = path.substr(0, pos); + /*DEBUG*/ std::cout << "script_path:[" << _request.script.path << "]\n"; + _request.script.info = path.substr(pos); + /*DEBUG*/ std::cout << "script_info:[" << _request.script.info << "]\n" << B_PURPLE << "end debug path dot" << RESET << "\n"; } void Client::clear() @@ -247,6 +251,7 @@ void Client::clear_request() _request.version.clear(); _request.headers.clear(); _request.body.clear(); + _request.multi_bodys.clear(); _request.abs_path.clear(); _request.query.clear(); _request.port.clear(); @@ -368,7 +373,7 @@ void Client::_parse_request_fields() // delete first line pos = headers.find(CRLF); if (pos != NPOS) - headers.erase(0, pos + std::string(CRLF).size()); + headers.erase(0, pos + CRLF_SIZE); // delete body part pos = headers.find(CRLF CRLF); if (pos != NPOS) @@ -407,6 +412,9 @@ void Client::_parse_port_hostname(std::string host) void Client::_check_request_errors() { + std::cerr << "Content-Length=" << get_rq_headers("Content-Length") << "\n"; + std::cerr << "strtoul=" << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n"; + std::cerr << "client_body_limit=" << assigned_server->client_body_limit << "\n"; /////////////////////// // Request line checks if (_request.method == UNKNOWN) @@ -431,13 +439,13 @@ void Client::_check_request_errors() ////////////////// // Headers checks - else if (!this->get_rq_headers("Content-Length").empty() - && std::strtoul(this->get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit) + else if (!get_rq_headers("Content-Length").empty() + && std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit) status = 413; - else if (!this->get_rq_headers("Transfer-Encoding").empty() - && this->get_rq_headers("Transfer-Encoding") != "chunked" ) + else if (!get_rq_headers("Transfer-Encoding").empty() + && get_rq_headers("Transfer-Encoding") != "chunked" ) status = 501; - else if (!this->get_rq_headers("Content-Encoding").empty()) + else if (!get_rq_headers("Content-Encoding").empty()) { status = 415; response.append("Accept-Encoding:"); // empty, no encoding accepted diff --git a/srcs/Client.hpp b/srcs/Client.hpp index d7dfc8b..958d482 100644 --- a/srcs/Client.hpp +++ b/srcs/Client.hpp @@ -11,6 +11,7 @@ # include // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa # include "utils.hpp" # include "ServerConfig.hpp" +# include "colors.h" struct Script { @@ -18,6 +19,12 @@ struct Script std::string info; }; +struct MultipartBody +{ + std::map headers; + std::string body; +}; + struct Request { http_method method; @@ -27,6 +34,7 @@ struct Request std::string version; std::map headers; std::string body; + std::vector multi_bodys; std::string port; std::string hostname; struct Script script; @@ -71,12 +79,15 @@ class Client std::string get_rq_script_info() const; std::string get_rq_headers(const std::string & key) const; + const std::vector &get_rq_multi_bodys() const; + const std::string get_rq_multi_bodys_headers(const std::string & key, std::vector::const_iterator body_it) const; + void parse_request_headers(std::vector &servers); void parse_request_body(); void clear(); void clear_request(); void clear_script(); - void fill_script_path(const std::string &path, size_t pos); + void fill_script_path(std::string &path, size_t pos); // DEBUG void print_client(std::string message = ""); @@ -91,7 +102,8 @@ class Client void _parse_request_fields(); void _parse_request_target( std::string target ); void _parse_port_hostname(std::string host); - + void _parse_chunked_body(size_t pos); + void _parse_multipart_body(size_t pos); void _check_request_errors(); }; diff --git a/srcs/Client_multipart_body.cpp b/srcs/Client_multipart_body.cpp new file mode 100644 index 0000000..89cf083 --- /dev/null +++ b/srcs/Client_multipart_body.cpp @@ -0,0 +1,103 @@ + +#include "Client.hpp" + +const std::vector &Client::get_rq_multi_bodys() const { return _request.multi_bodys; } +const std::string Client::get_rq_multi_bodys_headers(const std::string & key, std::vector::const_iterator body_it) const +{ + std::map::const_iterator it; + + it = body_it->headers.find(::str_tolower(key)); + if (it == body_it->headers.end()) + return ""; // IF return reference compiler "warning: returning reference to local temporary" + return it->second; +} + +void Client::_parse_multipart_body(size_t pos) +{ +/* +** Parsing roughly like described in : +** https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1 +*/ + MultipartBody new_body; + std::string boundary; + size_t start_pos; + size_t end_pos; + std::string tmp; + size_t tmp_pos; + size_t ret; + +// Get boundary + boundary = get_rq_headers("Content-Type"); + start_pos = boundary.find("boundary="); + if (start_pos == NPOS) + { + status = 400; std::cerr << "_parse_multipart_body() error 1\n"; + return; + } + start_pos += sizeof("boundary=")-1; + boundary = boundary.substr(start_pos); + std::cerr << "boundary =|" << boundary << "|\n"; + + +// Search boundary + start_pos = raw_request.find("--" + boundary, pos); + if (start_pos == NPOS || start_pos + sizeof("--")-1 + boundary.size() > raw_request.size()) + { + status = 400; std::cerr << "_parse_multipart_body() error 2\n"; + return; + } + start_pos += sizeof("--")-1 + boundary.size() + CRLF_SIZE; + + while (1) // TODO : test loop for multi body + { + end_pos = raw_request.find("--" + boundary, start_pos); + if (end_pos == NPOS) + { + status = 400; std::cerr << "_parse_multipart_body() error 3\n"; + return; + } + /* // Maye useful for multi body (remove "start_pos - CRLF_SIZE" if used) + end_pos = raw_request.rfind(CRLF, end_pos); if (end_pos == NPOS) {status = 400; return; } */ + + new_body.body = raw_request.substr(start_pos, end_pos - start_pos - CRLF_SIZE); + +// Split headers from body + tmp_pos = new_body.body.find(CRLF CRLF); + if (tmp_pos != NPOS) + { + ret = ::parse_http_headers(new_body.body.substr(0, tmp_pos), new_body.headers); + ::str_map_key_tolower(new_body.headers); + if (ret) + { + status = 400; std::cerr << "_parse_multipart_body() error 4\n"; + return; + } + tmp_pos += CRLF_SIZE*2; + new_body.body.erase(0, tmp_pos); + // ::print_map(new_body.headers); + } + else + { // No headers case + tmp_pos = new_body.body.find(CRLF); + if (tmp_pos != 0) + { + status = 400; std::cerr << "_parse_multipart_body() error 5\n"; + return; + } + } + + _request.multi_bodys.push_back(new_body); + +// Move start for next loop + start_pos = end_pos + sizeof("--")-1 + boundary.size(); + if ( start_pos + 2 + CRLF_SIZE == raw_request.size() + && raw_request[start_pos] == '-' + && raw_request[start_pos+1] == '-') + break; + +/* ::print_special(raw_request); + std::cerr << "start_pos = " << start_pos << "\n"; + std::cerr << "raw_request.size() = " << raw_request.size() << "\n"; + std::cerr << raw_request.substr(start_pos); */ + } +} diff --git a/srcs/cgi-bin/cgi.cpp b/srcs/cgi-bin/cgi.cpp old mode 100644 new mode 100755 index 3882239..95013cb Binary files a/srcs/cgi-bin/cgi.cpp and b/srcs/cgi-bin/cgi.cpp differ diff --git a/srcs/cgi-bin/php-cgi b/srcs/cgi-bin/cgi.php similarity index 100% rename from srcs/cgi-bin/php-cgi rename to srcs/cgi-bin/cgi.php diff --git a/srcs/cgi-bin/cgi.sh b/srcs/cgi-bin/cgi.sh new file mode 100755 index 0000000..14e0d29 --- /dev/null +++ b/srcs/cgi-bin/cgi.sh @@ -0,0 +1,4 @@ +#! /bin/bash +echo "status: 100\r\n" +echo "\r\n\r\n" +echo "hiii" diff --git a/srcs/cgi-bin/cgi_cpp.cgi b/srcs/cgi-bin/cgi_cpp.cgi deleted file mode 100755 index 95013cb..0000000 Binary files a/srcs/cgi-bin/cgi_cpp.cgi and /dev/null differ diff --git a/srcs/cgi-bin/cgi_second/cgi.php b/srcs/cgi-bin/cgi_second/cgi.php new file mode 100755 index 0000000..fa20b65 --- /dev/null +++ b/srcs/cgi-bin/cgi_second/cgi.php @@ -0,0 +1,29 @@ +#! /usr/bin/php + diff --git a/srcs/config/parser.cpp b/srcs/config/parser.cpp index dd33ed7..0159dbb 100644 --- a/srcs/config/parser.cpp +++ b/srcs/config/parser.cpp @@ -310,7 +310,7 @@ void ConfigParser::_set_location_values(LocationConfig *location, \ && tmp_val[0] != "303" && tmp_val[0] != "307" && tmp_val[0] != "308") throw std::invalid_argument("bad redirect status"); - std::cout << tmp_val[1] << '\n'; + // std::cout << tmp_val[1] << '\n'; if (tmp_val[1].compare(0, 7, "http://") && tmp_val[1].compare(0, 8, "https://")) throw std::invalid_argument("bad redirect uri"); diff --git a/srcs/main.cpp b/srcs/main.cpp index 94b1fc4..3a44a33 100644 --- a/srcs/main.cpp +++ b/srcs/main.cpp @@ -19,7 +19,7 @@ int main(int ac, char **av) ConfigParser configParser(config.c_str()); - configParser._print_content(); + // configParser._print_content(); // i don't love that servers has to be a pointer... std::vector* servers = configParser.parse(); @@ -27,8 +27,9 @@ int main(int ac, char **av) // use an iterator you moron for (std::vector::iterator it = servers->begin(); it < servers->end(); it++) { + (void)0; // std::cout << it->server_name << " "; - it->print_all(); + // it->print_all(); } diff --git a/srcs/utils.cpp b/srcs/utils.cpp index c4536e0..90562ce 100644 --- a/srcs/utils.cpp +++ b/srcs/utils.cpp @@ -153,7 +153,7 @@ std::string http_methods_to_str(unsigned int methods) file_type eval_file_type(const std::string &path) { - const char *tmp_path = path.c_str(); + const char *tmp_path = path.c_str(); // variable superflu ? struct stat s; if (stat(tmp_path, &s) != -1) @@ -171,6 +171,21 @@ file_type eval_file_type(const std::string &path) return (IS_OTHER); } +size_t eval_file_mode(std::string path, int mode) +{ + if (access(path.c_str(), F_OK) == -1) + { + std::perror("err access()"); + return 404; // NOT_FOUND, file doesn't exist + } + + if (access(path.c_str(), mode) == -1) + { + std::perror("err access()"); + return 403; // FORBIDDEN, file doesn't have execution permission + } + return 0; +} void replace_all_substr( @@ -198,8 +213,8 @@ std::string str_tolower(std::string str) } // identify a line in a string, by delim (ex. '\n') -// delete this line from the string -// and return the deleted line +// delete this line from the string (and the following nl sequence characters) +// and return the deleted line (without the followinf nl sequence characters) std::string extract_line(std::string & str, size_t pos, std::string delim) { @@ -220,7 +235,7 @@ std::string len = end - begin; del_str = str.substr(begin, len); - str.erase(begin, len); + str.erase(begin, len + delim.size()); return del_str; } @@ -242,7 +257,7 @@ size_t std::vector list; std::vector::iterator it; std::vector::iterator it_end; - size_t err = 0; + size_t err_count = 0; size_t pos; std::string key; std::string val; @@ -255,13 +270,13 @@ size_t pos = (*it).find(':'); if (pos == NPOS) { - err++; + err_count++; continue; } key = (*it).substr(0, pos); if ( key.find(' ') != NPOS ) { - err++; + err_count++; continue; } // bad idea, in cgi we need to have the original value @@ -270,7 +285,7 @@ size_t val = ::trim(val, ' '); fields.insert( std::pair(key, val) ); } - return err; + return err_count; } void str_map_key_tolower(std::map & mp) diff --git a/srcs/utils.hpp b/srcs/utils.hpp index 90e734f..ede2808 100644 --- a/srcs/utils.hpp +++ b/srcs/utils.hpp @@ -6,6 +6,7 @@ # include # include # include +# include # include // strtol, strtoul # include // LONG_MAX # include // errno @@ -13,6 +14,7 @@ # include // tolower # include // transform # include // perror, fflush +# include // close, access # include "colors.h" // for debug print_special # define CR "\r" @@ -63,6 +65,7 @@ std::string trim(std::string str, char del); http_method str_to_http_method(std::string &str); std::string http_methods_to_str(unsigned int methods); file_type eval_file_type(const std::string &path); +size_t eval_file_mode(std::string path, int mode); void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr); std::string str_tolower(std::string str); std::string extract_line(std::string & str, size_t pos = 0, std::string delim = "\n"); @@ -73,4 +76,30 @@ void throw_test(); // debug void print_special(std::string str); + +/* Template */ + +template +void print_pair(const std::pair p) +{ + std::cout << p.first << ": "; + std::cout << p.second << "\n"; +} + +template +void print_map(const std::map& c) +{ + typename std::map::const_iterator it = c.begin(); + typename std::map::const_iterator it_end = c.end(); + + std::cout << " --print_map():\n"; + std::cout << "map.size() = " << c.size() << "\n"; + while (it != it_end) + { + print_pair(*it); + ++it; + } + std::cout << " --\n"; +} + #endif diff --git a/srcs/webserv/Webserv.hpp b/srcs/webserv/Webserv.hpp index 12964a3..7fe0b2a 100644 --- a/srcs/webserv/Webserv.hpp +++ b/srcs/webserv/Webserv.hpp @@ -25,12 +25,14 @@ # include // perror, remove # include // strtol, strtoul # include // opendir() +# include // isalpha, local # include "Client.hpp" # include "ServerConfig.hpp" # include "utils.hpp" # include "http_status.hpp" # include "autoindex.hpp" +# include "colors.h" extern bool g_run; extern int g_last_signal; @@ -97,12 +99,13 @@ class Webserv void _autoindex(Client *client, const std::string &path); // method_post.cpp void _post(Client *client, const std::string &path); - void _post_file(Client *client, const std::string &path); + void _upload_files(Client *client); // method_delete.cpp void _delete(Client *client, const std::string &path); void _delete_file(Client *client, const std::string &path); // cgi_script.cpp - size_t _cgi_pos(Client *client, std::string &path); + bool _is_cgi(Client *client, std::string path); + size_t _cgi_pos(Client *client, std::string &path, size_t pos); std::string _exec_cgi(Client *client); char** _set_env(Client *client); char* _dup_env(std::string var, std::string val); diff --git a/srcs/webserv/cgi_script.cpp b/srcs/webserv/cgi_script.cpp index fe81ff4..e1b4d52 100644 --- a/srcs/webserv/cgi_script.cpp +++ b/srcs/webserv/cgi_script.cpp @@ -1,21 +1,72 @@ #include "Webserv.hpp" -// TODO HUGO : go ameliorer la recherche comme on a dit. -size_t Webserv::_cgi_pos(Client *client, std::string &path) +bool Webserv::_is_cgi(Client *client, std::string path) { - size_t pos = NPOS; - std::vector::const_iterator it; - it = client->assigned_location->cgi_ext.begin(); - while (it != client->assigned_location->cgi_ext.end()) + std::string script_path; + size_t file_type; + size_t file_mode; + size_t pos = 0; + + while (pos != NPOS) { - pos = std::min(path.find(*it) + it->size(), pos); - ++it; + pos = _cgi_pos(client, path, pos); + /*DEBUG*/ std::cout << "pos:" << pos << "\n&path[pos]:" << &path[pos] << "\n" << B_YELLOW << "fin debug _cgi_pos()\n\n" << RESET; + if (pos == NPOS) + break; + client->fill_script_path(path, pos); + script_path = "." + client->get_rq_script_path(); + file_type = ::eval_file_type(script_path); + if (file_type == IS_DIR) // but what if it's a symlink ? + continue; + if (file_type == IS_FILE) + { + file_mode = ::eval_file_mode( script_path, X_OK ); + if (!file_mode) + return true; + } } - if (pos == NPOS) - return false; - else - return true; + client->clear_script(); + client->clear_script(); + client->status = file_mode; // 404 not_found OR 403 forbidden + return false; +} + +size_t Webserv::_cgi_pos(Client *client, std::string &path, size_t pos) +{ + std::vector v_ext; + std::vector::const_iterator it; + std::vector::const_iterator it_end; + size_t len; + std::locale loc; // for isalpha() + + /*DEBUG*/ it = client->assigned_location->cgi_ext.begin(); std::cout << B_YELLOW << "\nDEBUG _cgi_pos()\n" << RESET << "vector_ext.size():[" << client->assigned_location->cgi_ext.size() << "]\n\n"; + v_ext = client->assigned_location->cgi_ext; + if (v_ext.empty()) + return NPOS; + /*DEBUG*/ std::cout << "ext:[" << *it << "]\n" << "path:[" << path << "]\n\n"; + it_end = client->assigned_location->cgi_ext.end(); + while (pos < path.size()) + { + /*DEBUG*/ std::cout << "\nwhile\n"; + if (path.compare(pos, 2, "./") == 0) + pos += 2; + /*DEBUG*/ std::cout << "&path[pos]:[" << &path[pos] << "]\n"; + pos = path.find('.', pos); + if (pos == NPOS) + return pos; + it = client->assigned_location->cgi_ext.begin(); + for ( ; it != it_end; ++it) + { + /*DEBUG*/ std::cout << " for\n"; std::cout << " &path[pos]:[" << &path[pos] << "]\n" << " *it:[" << *it << "]\n" << " (*it).size():[" << (*it).size() << "]\n" << " path.substr(pos, (*it).size()):[" << path.substr(pos + 1, (*it).size()) << "]\n\n"; + len = (*it).size(); + if (path.compare(pos + 1, len, *it) == 0) + if ( !std::isalpha(path[pos + 1 + len], loc) ) + return pos + 1 + len; + } + pos++; + } + return NPOS; } std::string Webserv::_exec_cgi(Client *client) @@ -98,6 +149,7 @@ std::string Webserv::_exec_script(Client *client, char **env) int fd_out[2]; int save_in = dup(STDIN_FILENO); int save_out = dup(STDOUT_FILENO); + std::string path; pipe(fd_in); pipe(fd_out); @@ -111,12 +163,13 @@ std::string Webserv::_exec_script(Client *client, char **env) close(FD_RD_FR_CHLD); dup2(FD_RD_FR_PRNT, STDIN_FILENO); dup2(FD_WR_TO_PRNT, STDOUT_FILENO); - // DEBUG - std::cerr << "execve:\n"; - execve(client->get_rq_script_path().c_str(), nll, env); + path = "." + client->get_rq_script_path(); + /*DEBUG*/std::cerr << "execve\n" << "path:[" << path << "]\n"; + execve(path.c_str(), nll, env); // for tests execve crash : //execve("wrong", nll, env); std::cerr << "execve crashed.\n"; + // TODO HUGO : check errno } else { diff --git a/srcs/webserv/init.cpp b/srcs/webserv/init.cpp index d2ab890..0ee6f17 100644 --- a/srcs/webserv/init.cpp +++ b/srcs/webserv/init.cpp @@ -112,9 +112,11 @@ void Webserv::_init_http_status_map() _http_status.insert(status_pair(405, S405)); _http_status.insert(status_pair(408, S408)); _http_status.insert(status_pair(413, S413)); + _http_status.insert(status_pair(415, S415)); _http_status.insert(status_pair(500, S500)); _http_status.insert(status_pair(501, S501)); + _http_status.insert(status_pair(505, S505)); } void Webserv::_init_mime_types_map() diff --git a/srcs/webserv/method_get.cpp b/srcs/webserv/method_get.cpp index 661b287..310e27a 100644 --- a/srcs/webserv/method_get.cpp +++ b/srcs/webserv/method_get.cpp @@ -3,12 +3,13 @@ std::string Webserv::_replace_url_root(Client *client, std::string path) { - std::cerr << "path before = " << path << "\n"; // DEBUG + std::cerr << "assigned_location->path = " << client->assigned_location->path << "\n"; // debug + std::cerr << "path before = " << path << "\n"; // DEBUG if (client->assigned_location->path == "/") path.insert(0, client->assigned_location->root); else path.replace(0, client->assigned_location->path.size(), client->assigned_location->root); - std::cerr << "path after = " << path << "\n"; // DEBUG + std::cerr << "path after = " << path << "\n"; // DEBUG return path; } @@ -20,7 +21,6 @@ void Webserv::_get(Client *client, std::string &path) // Index/Autoindex block if (eval_file_type(path) == IS_DIR) { - std::cout << "made it to Index/Autoindex\n"; if (path[path.size() - 1] != '/') path.push_back('/'); for (size_t i = 0; i < client->assigned_location->index.size(); i++) @@ -50,7 +50,7 @@ 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() ? std::stringstream buf; - std::cout << "made it to get_file\n"; + std::cout << "_get_file()\n"; if (access(path.c_str(), F_OK) == -1) { @@ -103,7 +103,7 @@ void Webserv::_get_file(Client *client, const std::string &path) // const? void Webserv::_autoindex(Client *client, const std::string &path) { - std::cout << "made it to _autoindex\n"; + std::cout << "_autoindex()\n"; // std::cout << "client target: " << client->get_rq_target() << '\n'; @@ -113,10 +113,9 @@ void Webserv::_autoindex(Client *client, const std::string &path) DIR *dir; struct dirent *ent; -// std::cout << "location root: " << client->assigned_location->root << " location path: " -// << client->assigned_location->path << '\n'; + // std::cout << "location root: " << client->assigned_location->root << " location path: " << client->assigned_location->path << '\n'; - std::cout << "Path in auto is: " << path << '\n'; + // std::cout << "Path in auto is: " << path << '\n'; if ( (dir = opendir(path.c_str()) ) != NULL) { dir_list.append(AUTOINDEX_START); diff --git a/srcs/webserv/method_post.cpp b/srcs/webserv/method_post.cpp index 525b144..9ee0aaa 100644 --- a/srcs/webserv/method_post.cpp +++ b/srcs/webserv/method_post.cpp @@ -8,51 +8,119 @@ void Webserv::_post(Client *client, const std::string &path) WIP https://www.rfc-editor.org/rfc/rfc9110.html#name-post */ - _post_file(client, path); + (void)path; + std::cout << "_post()\n"; + std::cerr << "upload_dir = " << client->assigned_location->upload_dir << "\n"; + + + if (client->get_rq_abs_path() != client->assigned_location->path) + client->status = 404; // 404 ? J'ai un doute. + else if (client->assigned_location->upload_dir.empty()) + client->status = 404; // 404 ? J'ai un doute. + else if (client->get_rq_multi_bodys().empty()) + { + client->status = 415; + client->response.append("Accept: multipart/form-data"); // empty, no encoding accepted + client->response.append(CRLF); + } + else + _upload_files(client); } -void Webserv::_post_file(Client *client, const std::string &path) +#define DEFAULT_NAME "unnamed_file" +// TODO : Loop for multi body +void Webserv::_upload_files(Client *client) { std::ofstream ofd; - + std::vector::const_iterator body_it = client->get_rq_multi_bodys().begin(); + std::string path; + std::string filename; + size_t pos; 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) + while (body_it != client->get_rq_multi_bodys().end()) { - std::perror("err access()"); - client->status = 403; - return ; - } + if (body_it->body.empty()) + { + ++body_it; + continue; + } + // Content-Disposition: form-data; name="upload_file"; filename="camion.jpg" + ::print_map(body_it->headers); + filename = client->get_rq_multi_bodys_headers("Content-Disposition", body_it); + std::cerr << "filename ="<< filename << "\n"; + pos = filename.find("filename="); + if (pos != NPOS) + { + filename = filename.substr(pos + sizeof("filename=")-1); + std::cerr << "filename ="<< filename << "\n"; + // A l'arrache pour enlever les " + filename.erase(0, 1); + std::cerr << "filename ="<< filename << "\n"; + filename.erase(filename.size()-1, 1); + std::cerr << "filename ="<< filename << "\n"; + std::cerr << "filename ="<< filename << "\n"; + if (filename.empty()) + filename = DEFAULT_NAME; + } + else + { + filename = DEFAULT_NAME; + } + std::cerr << "filename ="<< filename << "\n"; + path = client->assigned_location->upload_dir; // Assume there a final '/' + path.append(filename); - ofd.open(path.c_str(), std::ios::trunc); - if (!ofd) - { - std::cerr << path << ": ofd.open fail" << '\n'; - client->status = 500; - } - else - { - // Content-Length useless at this point ? - ofd << client->get_rq_body(); + + 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 ? access() on the upload_dir ? + 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::trunc); + if (!ofd) + { + std::cerr << path << ": ofd.open fail" << '\n'; + client->status = 500; + return; + } + ofd << body_it->body; if (!ofd) { std::cerr << path << ": ofd.write fail" << '\n'; client->status = 500; + return; } - 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 - } + ++body_it; + ofd.close(); + } + + if (file_existed) // with multi body it doesn't make much sense + { + // client->status = 200; + client->status = 204; // DEBUG 204 + client->response.append("Location: "); + client->response.append("/index.html"); // WIP + client->response.append(CRLF); + client->response.append(CRLF); + // WIP https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok + } + else + { + // client->status = 201; + client->status = 204; // DEBUG 204 + client->response.append("Location: "); + client->response.append("/index.html"); // WIP + client->response.append(CRLF); + client->response.append(CRLF); + // WIP https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3-4 } } diff --git a/srcs/webserv/request.cpp b/srcs/webserv/request.cpp index 4e2a9ab..713de12 100644 --- a/srcs/webserv/request.cpp +++ b/srcs/webserv/request.cpp @@ -25,7 +25,7 @@ void Webserv::_request(Client *client) } else if (ret == READ_COMPLETE) { - if (client->body_complete) + if (client->body_complete && client->get_rq_multi_bodys().empty()) // DEBUG std::cerr << "______BODY\n" << client->get_rq_body() << "\n______\n"; // DEBUG _epoll_update(client->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD); client->request_complete = true; @@ -37,7 +37,6 @@ int Webserv::_read_request(Client *client) char buf[BUFSIZE]; ssize_t ret; - std::cerr << "call recv()" << "\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) @@ -51,6 +50,8 @@ int Webserv::_read_request(Client *client) return READ_CLOSE; } client->raw_request.append(buf, ret); + // ::print_special(client->raw_request); + // std::cerr << "__raw_request__\n" << client->raw_request << "\n______\n"; // DEBUG // print_special(client->raw_request); @@ -63,8 +64,7 @@ int Webserv::_read_request(Client *client) { if (client->get_rq_headers("Content-Type").empty() && client->get_rq_headers("Content-Length").empty() - // && client->get_rq_headers("Transfer-Encoding").empty() - ) + && client->get_rq_headers("Transfer-Encoding").empty()) return READ_COMPLETE; // No body case } else if (client->raw_request.size() > MAX_HEADER_SIZE) @@ -73,7 +73,7 @@ int Webserv::_read_request(Client *client) return READ_COMPLETE; } } - else if (client->header_complete) + if (client->header_complete) { // client->read_body_size += ret; // Not accurate, part of body could have been read with headers, unused for now client->parse_request_body(); diff --git a/srcs/webserv/response.cpp b/srcs/webserv/response.cpp index ddda809..6cce4c0 100644 --- a/srcs/webserv/response.cpp +++ b/srcs/webserv/response.cpp @@ -21,7 +21,10 @@ void Webserv::_response(Client *client) } else if (ret == SEND_COMPLETE) { - if (client->get_rq_headers("Connection") == "close" || client->status == 408) + if (client->get_rq_headers("Connection") == "close" + || client->status == 400 // TODO: Refactoring + || client->status == 408 + || client->status == 413) _close_client(client->get_cl_fd()); else { @@ -61,32 +64,35 @@ void Webserv::_append_base_headers(Client *client) { client->response.append("Server: Webserv/0.1" CRLF); - if (client->get_rq_headers("Connection") == "close") + if (client->get_rq_headers("Connection") == "close" + || client->status == 400 // TODO: Refactoring + || client->status == 408 + || client->status == 413) client->response.append("Connection: close" CRLF); else client->response.append("Connection: keep-alive" CRLF); } +// TODO HUGO : wip void Webserv::_construct_response(Client *client) { - std::string path = _replace_url_root(client, client->get_rq_abs_path()); + std::string path; + std::string script_output; -/* size_t pos = _cgi_pos(client, path); - if (pos != NPOS) + path = _replace_url_root(client, client->get_rq_abs_path()); + if (_is_cgi(client, path)) { - client->fill_script_path(path, pos); - std::string script_output = _exec_cgi(client); + script_output = _exec_cgi(client); _check_script_output(client, script_output); client->response += script_output; return; - } */ + } _process_method(client, path); } void Webserv::_process_method(Client *client, std::string &path) { - std::cerr << "assigned_location->path = " << client->assigned_location->path << "\n"; // debug - std::cerr << "allow_methods = " << client->assigned_location->allow_methods << "\n"; // debug + std::cerr << "allow_methods = " << http_methods_to_str(client->assigned_location->allow_methods) << "\n"; // debug switch (client->get_rq_method()) { @@ -182,8 +188,6 @@ ServerConfig *_determine_process_server(Client *client, std::vector::const_iterator it = server.locations.begin(); it != server.locations.end(); it++) { // std::cout << it->path << " -- "; - if (it->path.size() > uri.size()) - { -// std::cout << "skipping this one\n"; continue ; - } if (uri.compare(0, it->path.size(), it->path) == 0) { diff --git a/srcs/webserv/timeout.cpp b/srcs/webserv/timeout.cpp index 9bd3e8c..d3f3561 100644 --- a/srcs/webserv/timeout.cpp +++ b/srcs/webserv/timeout.cpp @@ -12,7 +12,12 @@ void Webserv::_timeout() std::cerr << "timeout request fd " << it->get_cl_fd() << "\n"; it->status = 408; _epoll_update(it->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD); + + // DEBUG, close without repsonse 408 + /* _close_client(it->get_cl_fd()); + it = _clients.begin(); */ } + // else // DEBUG ++it; } } diff --git a/test_file_upload.txt b/test_file_upload.txt new file mode 100644 index 0000000..9c6ce79 --- /dev/null +++ b/test_file_upload.txt @@ -0,0 +1,6 @@ +START +http://localhost:4040/test +http://localhost:4040/test/test_deeper/ +http://localhost:4040/test/test_deeper/super_deep/ +http://localhost:4040/test/index1.html +STOP \ No newline at end of file diff --git a/test_template.sh b/test_template.sh index 6a057f4..22388ad 100644 --- a/test_template.sh +++ b/test_template.sh @@ -35,7 +35,7 @@ run_this_test() { echo "----- $test_name -----" echo -e "$_RED$request$_END" - } &>> telnet.out + } >> telnet.out # } &>> test.log # } 2>>&1 # } &>> /dev/stdout diff --git a/www/Cagneyc_intro.gif b/www/Cagneyc_intro.gif new file mode 100644 index 0000000..ed470b9 Binary files /dev/null and b/www/Cagneyc_intro.gif differ diff --git a/www/upload_form.html b/www/upload_form.html new file mode 100644 index 0000000..2d00e92 --- /dev/null +++ b/www/upload_form.html @@ -0,0 +1,12 @@ + +
+ + + + +
diff --git a/www/upload_form_single.html b/www/upload_form_single.html new file mode 100644 index 0000000..4adb11e --- /dev/null +++ b/www/upload_form_single.html @@ -0,0 +1,10 @@ + +
+ + +