#include "Webserv.hpp" /* CGI RFC: https://www.rfc-editor.org/rfc/rfc3875.html */ bool Webserv::_is_cgi(Client *client, std::string path) { std::string script_path; size_t file_type; size_t file_mode = client->status; size_t pos = 0; while (pos != NPOS) { pos = _cgi_pos(client, path, pos); 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_access( script_path, X_OK ); if (!file_mode) return true; } } 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() v_ext = client->assigned_location->cgi_ext; if (v_ext.empty()) return NPOS; it_end = client->assigned_location->cgi_ext.end(); while (pos < path.size()) { if (path.compare(pos, 2, "./") == 0) pos += 2; pos = path.find('.', pos); if (pos == NPOS) return pos; it = client->assigned_location->cgi_ext.begin(); for ( ; it != it_end; ++it) { 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; } void Webserv::_cgi_open_pipes(Client *client) { #define R 0 #define W 1 int fd_in[2]; int fd_out[2]; ::pipe(fd_in); ::pipe(fd_out); client->cgi_pipe_r_from_parent = fd_in[R]; client->cgi_pipe_w_to_child = fd_in[W]; client->cgi_pipe_r_from_child = fd_out[R]; client->cgi_pipe_w_to_parent = fd_out[W]; // epoll add for writing body to child _epoll_update(client->cgi_pipe_w_to_child, EPOLLOUT, EPOLL_CTL_ADD); // stop monitoring client->fd until we can write body _epoll_update(client->get_cl_fd(), 0, EPOLL_CTL_DEL); client->cgi_state = CGI_WAIT_TO_EXEC; } void Webserv::_exec_cgi(Client *client) { char* env_cstr[19] = {NULL}; std::vector env_vector; env_vector.reserve(18); int i = 0; _set_env_vector(client, env_vector); try { _set_env_cstr(env_cstr, env_vector); _exec_script(client, env_cstr); while (env_cstr[i] != NULL) delete[] env_cstr[i++]; return; } catch (const Webserv::ExecFail& e) { while (env_cstr[i] != NULL) delete[] env_cstr[i++]; throw; } } std::string Webserv::_dup_env(std::string var, std::string val = "") { std::string str; str = var + "=" + val; return (str); } std::string Webserv::_dup_env(std::string var, int i) { std::string str; std::string val; val = ::itos(i); str = var + "=" + val; return (str); } // TODO : verifier que les variables sont corrects /* https://www.rfc-editor.org/rfc/rfc3875#section-4.1 */ void Webserv::_set_env_vector(Client *client, std::vector &env_vector) { env_vector.push_back(_dup_env("AUTH_TYPE")); // authentification not supporte env_vector.push_back(_dup_env("CONTENT_LENGTH" , client->get_rq_body().size())); env_vector.push_back(_dup_env("CONTENT_TYPE" , client->get_rq_headers("Content-Type"))); env_vector.push_back(_dup_env("GATEWAY_INTERFACE" , "CGI/1.1")); // https://www.rfc-editor.org/rfc/rfc3875#section-4.1.4 env_vector.push_back(_dup_env("PATH_INFO" , client->get_rq_script_info())); // LUKE: To Check env_vector.push_back(_dup_env("PATH_TRANSLATED")); // not supported // LUKE: Why not supported ? env_vector.push_back(_dup_env("QUERY_STRING" , client->get_rq_query())); env_vector.push_back(_dup_env("REMOTE_ADDR" , client->get_cl_ip())); env_vector.push_back(_dup_env("REMOTE_HOST" , client->get_cl_ip())); // equal to REMOTE_ADDR or empty env_vector.push_back(_dup_env("REMOTE_IDENT")); // authentification not supported env_vector.push_back(_dup_env("REMOTE_USER")); // authentification not supported env_vector.push_back(_dup_env("REQUEST_METHOD" , client->get_rq_method_str())); env_vector.push_back(_dup_env("SCRIPT_NAME" , "/" + client->get_rq_script_path())); // LUKE: To Check env_vector.push_back(_dup_env("SERVER_NAME" , client->get_cl_lsocket()->host)); env_vector.push_back(_dup_env("SERVER_PORT" , client->get_cl_lsocket()->port)); env_vector.push_back(_dup_env("SERVER_PROTOCOL" , "HTTP/1.1")); env_vector.push_back(_dup_env("SERVER_SOFTWARE" , "Webserv/0.1")); env_vector.push_back(_dup_env("REDIRECT_STATUS" , "200")); } void Webserv::_set_env_cstr(char *env_cstr[], std::vector &env_vector) { std::vector::const_iterator it = env_vector.begin(); std::vector::const_iterator it_end = env_vector.end(); int i = 0; while (it != it_end) { env_cstr[i] = new char[it->size()+1]; std::strcpy(env_cstr[i], it->c_str()); ++it; ++i; } env_cstr[i] = NULL; } void Webserv::_exec_script(Client *client, char *env[]) { pid_t pid; char * const nll[1] = {NULL}; std::string script_output; std::string path; pid = fork(); if (pid == -1) std::perror("err fork()"); else if (pid == 0) // child { std::signal(SIGPIPE, SIG_DFL); std::signal(SIGINT, SIG_DFL); if (dup2(client->cgi_pipe_r_from_parent, STDIN_FILENO) == -1) { std::perror("err dup2()"); if (::close(client->cgi_pipe_r_from_parent) == -1) // Valgind debug, not essential std::perror("err close"); if (::close(client->cgi_pipe_w_to_parent) == -1) // Valgind debug, not essential std::perror("err close"); throw ExecFail(); } if (dup2(client->cgi_pipe_w_to_parent, STDOUT_FILENO) == -1) { std::perror("err dup2()"); if (::close(client->cgi_pipe_r_from_parent) == -1) // Valgind debug, not essential std::perror("err close"); if (::close(client->cgi_pipe_w_to_parent) == -1) // Valgind debug, not essential std::perror("err close"); throw ExecFail(); } _close_all_clients_fd(); if (::close(_epfd) == -1) std::perror("err close"); path = client->get_rq_script_path(); // Wut ? Only relative path ? /*DEBUG*/std::cerr << "execve:[" << path << "]\n"; if (::execve(path.c_str(), nll, env) == -1) // replace path for debug error forcing { std::perror("err execve()"); if (::close(STDIN_FILENO) == -1) // Valgind debug, not essential std::perror("err close"); if (::close(STDOUT_FILENO) == -1) // Valgind debug, not essential std::perror("err close"); throw ExecFail(); } } else //parent { if (::close(client->cgi_pipe_r_from_parent) == -1) std::perror("err close"); if (::close(client->cgi_pipe_w_to_parent) == -1) std::perror("err close"); if (::close(client->cgi_pipe_w_to_child) == -1) std::perror("err close"); // add client->cgi_pipe_r_from_child to epoll, _epoll_update(client->cgi_pipe_r_from_child, EPOLLIN, EPOLL_CTL_ADD); // stop monitoring client->fd until the cgi-script as done is job _epoll_update(client->get_cl_fd(), 0, EPOLL_CTL_DEL); client->cgi_pid = pid; client->cgi_state = CGI_WAIT_FOR_OUTPUT; } } #define STATUS_500 std::string("Status: 500" CRLF CRLF); void Webserv::_check_script_output(Client *client, std::string & output) { size_t pos; pos = client->cgi_output.find(CRLF CRLF); if (pos == 0 || pos == NPOS) { client->status = 500;; return; } _check_script_status(client, output); if (client->status >= 400 && client->status < 600) return; // /*DEBUG*/ std::cout << "\n" B_PURPLE "[script status]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n"; client->status = _check_script_fields(output, client->status); // /*DEBUG*/ std::cout << "\n" B_PURPLE "[script fields]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n"; _check_fields_duplicates(client, output); // /*DEBUG*/ std::cout << "\n" B_PURPLE "[fields duplicates]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n"; _remove_body_leading_empty_lines(output); // /*DEBUG*/ std::cout << "\n" B_PURPLE "[script empty lines]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n"; _add_script_body_length_header(output); // /*DEBUG*/ std::cout << "\n" B_PURPLE "[script content length]:\n" << "client->status:[" << client->status << "]" RESET "\n"; ::print_special(output); std::cout << B_PURPLE "-----------" RESET "\n\n"; } void Webserv::_check_script_status(Client *client, std::string & output) { size_t pos; int status_pos; pos = output.find("Status:"); if (pos != NPOS) { status_pos = pos + std::string("Status:").size(); client->status = std::strtoul(output.c_str() + status_pos, NULL, 10); ::extract_line(output, pos, CRLF); } else client->status = 200; } size_t Webserv::_check_script_fields(const std::string & output, size_t status) { std::string headers; std::string body; size_t pos; std::cerr << "0\n"; pos = output.find(CRLF CRLF); if (pos == NPOS) // there is not empty line return 500; std::cerr << "1\n"; headers = output.substr(0, pos); body = output.substr(pos + CRLF_SIZE * 2); headers = str_tolower(headers); pos = headers.find("content-type"); if (pos == NPOS) // there is no content-type field { if (!body.empty()) // there is body return 500; std::cerr << "2\n"; if (headers.find("location") == NPOS) // there is no location field return 500; std::cerr << "3\n"; } else if (headers.find("location") != NPOS) // there is a location field { if (body.empty()) // there is no body return 500; std::cerr << "4\n"; } std::cerr << "5\n"; return status; } void Webserv::_check_fields_duplicates(Client *client, std::string & output) { std::map srv_fld; // server_field std::map scr_fld; // script_field std::map::iterator it_srv; std::map::iterator it_scr; std::string tmp; size_t pos; // put server headers in map tmp = client->response; pos = tmp.find(CRLF CRLF); if (pos != NPOS) tmp.erase(pos); ::parse_http_headers(tmp, srv_fld); // put script headers in map tmp = output; pos = tmp.find(CRLF CRLF); if (pos != NPOS) tmp.erase(pos); ::parse_http_headers(tmp, scr_fld); // compare both map to supress duplicates for (it_srv = srv_fld.begin(); it_srv != srv_fld.end(); it_srv++) { for (it_scr = scr_fld.begin(); it_scr != scr_fld.end(); it_scr++) { if (str_tolower(it_srv->first) == str_tolower(it_scr->first)) { pos = client->response.find(it_srv->first); ::extract_line(client->response, pos, CRLF); } } } } void Webserv::_remove_body_leading_empty_lines(std::string & output) { size_t pos; size_t pos_empty; pos = output.find(CRLF CRLF); if (pos == NPOS) return; pos += CRLF_SIZE * 2; pos_empty = pos; while (pos_empty == pos) { pos = output.find(CRLF, pos); if (pos == pos_empty) extract_line(output, pos, CRLF); } } void Webserv::_add_script_body_length_header(std::string & output) { std::map field; std::map::iterator it; std::stringstream str_len; std::string tmp; size_t pos; size_t len; pos = output.find(CRLF CRLF); if (pos != NPOS) tmp = output.substr(pos + (CRLF_SIZE * 2)); len = tmp.size(); str_len << len; // put script headers in map tmp = output; pos = tmp.find(CRLF CRLF); if (pos != NPOS) tmp.erase(pos); ::parse_http_headers(tmp, field); // case insensitive search in map for "Content-Length" tmp = "Content-Length"; for (it = field.begin(); it != field.end(); ++it) { if (str_tolower(it->first) == str_tolower(tmp)) { pos = output.find(it->first); ::extract_line(output, pos, CRLF); } } tmp += ": "; tmp += str_len.str(); tmp += CRLF; output.insert(0, tmp); }