Merge branch 'hugo3'

+ script output verification works (field duplicate and status)
+ parsing_message_http :
  - the file is deleted
  - of the three functions it contained, only one still exist
  - 'parse_http_headers' is now in utils
+ changes in utils :
  - http headers parsing doesn't change key case itself
  - a new function change key case tolower case in map<str, str>
  - added split_trim() that perform a split and a trim at once
  - resolved pbm in trim
  - added print_special to print special char '\r' and '\n'
  - del_line became extract_line, because it returns the deleted line
  - added get line, that does the same without deleting
  - moved http_header in utils, and made it more consistent
+ in Client :
  - parse_request is now named parse_request_headers to work with parse body
  - private function _parse_request_headers is then now _parse_request_fields
    (because it doesn't take care of request first line, but only fields)
  - added a debug function 'print_client'
This commit is contained in:
hugogogo
2022-08-12 14:05:11 +02:00
19 changed files with 374 additions and 184 deletions

View File

@@ -79,11 +79,8 @@ Client & Client::operator=( Client const & rhs )
// 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::vector<ServerConfig> &servers)
void Client::parse_request_headers(std::vector<ServerConfig> &servers)
{
std::map<std::string, std::string> headers;
std::string body;
if (raw_request.find(CRLF CRLF) == NPOS)
return ;
header_complete = true;
@@ -92,27 +89,37 @@ void Client::parse_request(std::vector<ServerConfig> &servers)
_parse_request_line();
if (status)
return;
_parse_request_headers();
_parse_request_fields();
// DEBUG
print_client("headers");
if (status)
return;
assigned_server = ::_determine_process_server(this, servers);
assigned_location = ::_determine_location(*assigned_server, _request.abs_path);
_check_request_errors();
if (status)
return;
_parse_port_hostname(this->get_rq_headers("Host"));
_parse_port_hostname(this->get_rq_headers("Host")); // use getter for headers because it works case insensitive
std::cerr << get_rq_method_str() << " " << get_rq_uri() << " " << get_rq_version() << "\n"; // DEBUG
/* dont clear raw_request, we need it for future reparsing of body
see call of parse_request() in _read_request() */
// DEBUG
std::cerr << get_rq_method_str() << " " << get_rq_uri() << " " << get_rq_version() << "\n";
// dont clear raw_request, we need it for future reparsing of body
// see call of parse_request() in _read_request()
// raw_request.clear();
}
void Client::parse_request_body()
{
size_t pos;
pos = raw_request.find(CRLF CRLF);
if (pos == NPOS)
{
std::cerr << "parse_request_body() bad call, header incomplete\n";
// QUESTION from hugo : don't we change the status here ?
return;
}
pos += CRLF_SIZE*2;
@@ -192,7 +199,7 @@ void Client::parse_request_body()
///////////////
// Body checks
if (_request.body.size() > assigned_server->client_body_limit)
status = 413;
status = 413; // HTTP Client Errors
}
bool Client::fill_script_path(std::string script)
@@ -247,6 +254,33 @@ void Client::clear_script()
_request.script.info.clear();
}
// debug
void Client::print_client(std::string message)
{
std::map<std::string, std::string>::iterator it;
std::cout << "\n=== DEBUG PRINT CLIENT ===\n";
std::cout << message << ":\n----------\n\n" << "raw_request:\n__\n";
::print_special(raw_request);
std::cout << "\n__\n"
<< "get_cl_fd() : [" << get_cl_fd() << "]\n"
<< "get_cl_port() : [" << get_cl_port() << "]\n"
<< "get_cl_ip() : [" << get_cl_ip() << "]\n"
<< "get_rq_method_str() : [" << get_rq_method_str() << "]\n"
<< "get_rq_uri() : [" << get_rq_uri() << "]\n"
<< "get_rq_abs_path() : [" << get_rq_abs_path() << "]\n"
<< "get_rq_query() : [" << get_rq_query() << "]\n"
<< "get_rq_version() : [" << get_rq_version() << "]\n"
<< "get_rq_body() : [" << get_rq_body() << "]\n"
<< "get_rq_port() : [" << get_rq_port() << "]\n"
<< "get_rq_hostname() : [" << get_rq_hostname() << "]\n"
<< "get_rq_script_path() : [" << get_rq_script_path() << "]\n"
<< "get_rq_script_info() : [" << get_rq_script_info() << "]\n"
<< "headers :\n";
for (it = _request.headers.begin(); it != _request.headers.end(); it++)
std::cout << " " << it->first << ": [" << it->second << "]\n";
std::cout << "\n=== END PRINT CLIENT ===\n\n";
}
/*********************************************
* GETTERS
@@ -290,12 +324,13 @@ std::string Client::get_rq_headers(const std::string & key) const
void Client::_parse_request_line()
{
std::vector<std::string> line;
int ret;
std::string raw_line;
ret = ::parse_http_first_line(raw_request, line);
if (ret != 3)
raw_line = ::get_line(raw_request, 0, CRLF);
line = ::split_trim(raw_line, " ");
if (line.size() != 3)
{
std::cerr << "err _parse_first_line(): wrong number of elements (" << ret << " instead of 3)\n";
std::cerr << "err _parse_first_line(): wrong number of elements (" << line.size() << " instead of 3)\n";
status = 400; // "bad request"
}
else
@@ -319,10 +354,32 @@ void Client::_parse_request_uri( std::string uri )
_request.abs_path = uri.substr(0, pos);
}
void Client::_parse_request_headers()
void Client::_parse_request_fields()
{
// TODO: check error and adjust status
_request.headers = ::parse_http_headers(raw_request);
std::string headers;
size_t pos;
int ret;
headers = raw_request;
// delete first line
pos = headers.find(CRLF);
if (pos != std::string::npos)
headers.erase(0, pos + std::string(CRLF).size());
// delete body part
pos = headers.find(CRLF CRLF);
if (pos != std::string::npos)
headers.erase(pos);
else {
std::cerr << "err _parse_request_fields(): request header doesn't end with empty line\n";
status = 400; // "bad request"
}
// copy result of parser into headers
ret = ::parse_http_headers(headers, _request.headers);
if (ret > 0) {
std::cerr << "err _parse_request_fields(): " << ret << " fields are bad formated\n";
status = 400; // "bad request"
}
::str_map_key_tolower(_request.headers);
}
void Client::_parse_port_hostname(std::string host)
@@ -349,12 +406,12 @@ void Client::_check_request_errors()
///////////////////////
// Request line checks
if (_request.method == UNKNOWN)
status = 501;
status = 501; // HTTP Client Errors
else if (_request.version.compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0)
status = 505;
status = 505; // HTTP Client Errors
else if (!(assigned_location->allow_methods & _request.method))
{
status = 405;
status = 405; // HTTP Client Errors
response.append("Allow: ");
response.append(::http_methods_to_str(assigned_location->allow_methods));
response.append(CRLF);
@@ -396,3 +453,4 @@ bool operator==(const Client& lhs, int fd)
{ return lhs.get_cl_fd() == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.get_cl_fd(); }

View File

@@ -11,7 +11,6 @@
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa
# include "utils.hpp"
# include "ServerConfig.hpp"
# include "parsing_message_http.hpp"
struct Script
{
@@ -72,12 +71,14 @@ class Client
std::string get_rq_script_info() const;
std::string get_rq_headers(const std::string & key) const;
void parse_request(std::vector<ServerConfig> &servers);
void parse_request_headers(std::vector<ServerConfig> &servers);
void parse_request_body();
void clear();
void clear_request();
void clear_script();
bool fill_script_path(std::string script);
// DEBUG
void print_client(std::string message = "");
private:
int _fd;
@@ -87,7 +88,7 @@ class Client
struct Request _request;
void _parse_request_line();
void _parse_request_headers();
void _parse_request_fields();
void _parse_request_uri( std::string uri );
void _parse_port_hostname(std::string host);

View File

@@ -1,7 +1,8 @@
#! /usr/bin/php
<?php
echo "Status: 200\r\n";
echo "false: 300\r\n";
echo "server: Webserv/0.2\r\n";
echo "Status: 300\r\n";
echo "\r\n";
echo "BEGIN PHP-CGI\n-----------\n\n";
echo "AUTH_TYPE: " . getenv("AUTH_TYPE");
@@ -26,4 +27,3 @@
// echo $_POST['REQUEST_METHOD'];
echo "\n\n-----------\nEND PHP-CGI\n\n";
?>

25
srcs/colors.h Normal file
View File

@@ -0,0 +1,25 @@
#ifndef COLORS_H
# define COLORS_H
# define GRAY "\e[0;30m"
# define RED "\e[0;31m"
# define GREEN "\e[0;32m"
# define YELLOW "\e[0;33m"
# define BLUE "\e[0;34m"
# define PURPLE "\e[0;35m"
# define CYAN "\e[0;36m"
# define WHITE "\e[0;37m"
# define B_GRAY "\e[1;30m"
# define B_RED "\e[1;31m"
# define B_GREEN "\e[1;32m"
# define B_YELLOW "\e[1;33m"
# define B_BLUE "\e[1;34m"
# define B_PURPLE "\e[1;35m"
# define B_CYAN "\e[1;36m"
# define B_WHITE "\e[1;37m"
# define RESET "\e[0m"
#endif

View File

@@ -9,6 +9,14 @@ void throw_test()
throw std::bad_alloc();
}
// notice : the use of getline make it such as
// it doesn't identify multiple delim as one :
// " something \n else " -> 1 - something
// 2 - else
// is not the same as :
// " something \n\n else " -> 1 - something
// 2 -
// 3 - else
std::vector<std::string> split(std::string input, char delimiter)
{
std::vector<std::string> answer;
@@ -21,25 +29,62 @@ std::vector<std::string> split(std::string input, char delimiter)
return answer;
}
std::string trim(std::string str, char c)
std::vector<std::string>
split_trim(std::string input, std::string delim, char ctrim)
{
std::vector<std::string> split_str;
std::string tmp;
size_t start = 0;
size_t end = 0;
size_t len = 0;
while (end != std::string::npos)
{
end = input.find(delim, start);
len = end - start;
if (end == std::string::npos)
len = end;
tmp = input.substr(start, len);
if (ctrim != '\0')
tmp = trim(tmp, ctrim);
if (tmp.size() != 0)
split_str.push_back( tmp );
start = end + delim.size();
}
return split_str;
}
std::string trim(std::string str, char del)
{
size_t pos;
// delete leadings c
pos = str.find_first_not_of(c);
// delete leadings del
pos = str.find_first_not_of(del);
if (pos == std::string::npos)
return str;
pos = str.size();
str = str.substr(pos);
// delete endings c
pos = str.find_last_not_of(c);
if (pos == std::string::npos)
return str;
str = str.substr(0, pos + 1);
// delete trailing del
pos = str.find_last_not_of(del);
if (pos != std::string::npos)
str = str.substr(0, pos + 1);
return str;
}
//// trim a set of char
//std::string trim(std::string str, std::string del)
//{
// std::string new_str;
//
// while (new_str.compare(str) != 0)
// {
// for (size_t i = 0; i < del.size(); i++)
// trim(str, del[i]);
// }
// return str;
//}
std::string itos(int n)
{
std::stringstream strs;
@@ -127,7 +172,11 @@ file_type eval_file_type(const std::string &path)
}
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr)
void
replace_all_substr(
std::string &str,
const std::string &ori_substr,
const std::string &new_substr)
{
if (ori_substr.empty())
return;
@@ -148,27 +197,116 @@ std::string str_tolower(std::string str)
return str;
}
void del_line_in_str(std::string * str, size_t pos, std::string delim)
// identify a line in a string, by delim (ex. '\n')
// delete this line from the string
// and return the deleted line
std::string
extract_line(std::string & str, size_t pos, std::string delim)
{
size_t begin;
size_t end;
std::string del_str;
size_t begin;
size_t end;
size_t len;
begin = (*str).rfind(delim, pos);
begin = str.rfind(delim, pos);
if (begin == std::string::npos)
begin = 0;
else
begin += delim.size();
end = (*str).find(delim, pos);
if (end == std::string::npos)
end = 0;
else
end += delim.size();
end = str.find(delim, pos);
len = end;
if (end != std::string::npos)
len = end - begin;
(*str).erase(begin, end - begin);
del_str = str.substr(begin, len);
str.erase(begin, len);
return del_str;
}
// get a line in a string, by delim
// same as extract, except it doesn't delete it
std::string get_line(std::string str, size_t pos, std::string delim)
{
std::string ret;
ret = ::extract_line(str, pos, delim);
return ret;
}
size_t
parse_http_headers (
std::string headers,
std::map<std::string, std::string> & fields )
{
std::vector<std::string> list;
std::vector<std::string>::iterator it;
std::vector<std::string>::iterator it_end;
size_t err = 0;
size_t pos;
std::string key;
std::string val;
list = ::split_trim(headers, CRLF, ' ');
it_end = list.end();
for (it = list.begin(); it != it_end; it++)
{
pos = (*it).find(':');
if (pos == std::string::npos)
{
err++;
continue;
}
key = (*it).substr(0, pos);
if ( key.find(' ') != std::string::npos )
{
err++;
continue;
}
// bad idea, in cgi we need to have the original value
// key = ::str_tolower(key); // to make "key" case_insensitive
val = (*it).substr(pos + 1);
val = ::trim(val, ' ');
fields.insert( std::pair<std::string, std::string>(key, val) );
}
return err;
}
void str_map_key_tolower(std::map<std::string, std::string> & mp)
{
std::map<std::string, std::string> new_mp;
std::map<std::string, std::string>::iterator it;
std::string key;
std::string value;
for (it = mp.begin(); it != mp.end(); it++)
{
key = it->first;
value = it->second;
key = ::str_tolower(key);
new_mp.insert( std::pair<std::string, std::string>(key, value) );
}
mp.swap(new_mp);
}
// DEBUG
void print_special(std::string str)
{
char c;
for (size_t i = 0; i < str.size(); i++)
{
c = str[i];
if (c == '\r')
std::cout << YELLOW << "\\r" << RESET;
else if (c == '\n')
std::cout << YELLOW << "\\n" << RESET << "\n";
else
std::cout << c;
fflush(stdout);
}
}
bool operator==(const listen_socket& lhs, int fd)
{ return lhs.fd == fd; }

View File

@@ -3,6 +3,7 @@
# define UTILS_HPP
# include <vector>
# include <map>
# include <string>
# include <sstream>
# include <cstdlib> // strtol, strtoul
@@ -11,7 +12,8 @@
# include <sys/stat.h> // stat()
# include <cctype> // tolower
# include <algorithm> // transform
# include <cstdio> // perror
# include <cstdio> // perror, fflush
# include "colors.h" // for debug print_special
# define CR "\r"
# define LF "\n"
@@ -53,16 +55,22 @@ bool operator==(const listen_socket& lhs, int fd);
bool operator==(int fd, const listen_socket& rhs);
std::vector<std::string> split(std::string input, char delimiter);
std::vector<std::string> split_trim(std::string input, std::string delim = "\n", char ctrim = '\0');
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);
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);
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr);
std::string str_tolower(std::string str);
void del_line_in_str(std::string * str, size_t pos, std::string delim);
std::string extract_line(std::string & str, size_t pos = 0, std::string delim = "\n");
std::string get_line (std::string str, size_t pos = 0, std::string delim = "\n");
size_t parse_http_headers (std::string headers, std::map<std::string, std::string> & fields );
void str_map_key_tolower(std::map<std::string, std::string> & mp);
void throw_test();
// debug
void print_special(std::string str);
#endif

View File

@@ -104,9 +104,9 @@ class Webserv
char* _dup_env(std::string var, std::string val);
char* _dup_env(std::string var, int i);
std::string _exec_script(Client *client, char **env);
void _check_script_output(Client *client, std::string output);
void _check_script_status(Client *client, std::string output);
void _check_script_fields(Client *client, std::string output);
void _check_script_output(Client *client, std::string & output);
void _check_script_status(Client *client, std::string & output);
void _check_script_fields(Client *client, std::string & output);
// 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);

View File

@@ -89,6 +89,8 @@ std::string Webserv::_exec_script(Client *client, char **env)
std::string body = client->get_rq_body();
int fd_in[2];
int fd_out[2];
int save_in = dup(STDIN_FILENO);
int save_out = dup(STDOUT_FILENO);
pipe(fd_in);
pipe(fd_out);
@@ -127,17 +129,22 @@ std::string Webserv::_exec_script(Client *client, char **env)
}
if (script_output.empty())
script_output = "Status: 500\r\n\r\n";
dup2(save_in, STDIN_FILENO);
dup2(save_out, STDOUT_FILENO);
return script_output;
}
void Webserv::_check_script_output(Client *client, std::string output)
void Webserv::_check_script_output(Client *client, std::string & output)
{
// TODO: it doesn't work with execve error, i don't know why yet ?
_check_script_status(client, output);
_check_script_fields(client, output);
// _check_script_empty_lines(client, output);
// _check_script_space_colons(client, output);
// _check_script_new_lines(client, output);
}
void Webserv::_check_script_status(Client *client, std::string output)
void Webserv::_check_script_status(Client *client, std::string & output)
{
size_t pos;
int status_pos;
@@ -147,30 +154,42 @@ void Webserv::_check_script_status(Client *client, std::string output)
{
status_pos = pos + std::string("Status:").size();
client->status = std::strtoul(output.c_str() + status_pos, NULL, 10);
::del_line_in_str(&output, pos, CRLF);
::extract_line(output, pos, CRLF);
}
client->status = 200;
else
client->status = 200;
}
void Webserv::_check_script_fields(Client *client, std::string output)
void Webserv::_check_script_fields(Client *client, std::string & output)
{
std::map<std::string, std::string> srv_fld; // server_field
std::map<std::string, std::string> scr_fld; // script_field
std::map<std::string, std::string>::iterator it_srv;
std::map<std::string, std::string>::iterator it_scr;
std::string tmp;
size_t pos;
srv_fld = parse_http_headers(client->response);
scr_fld = parse_http_headers(output);
// wip: compare both map to supress duplicates
// put server headers in map
tmp = client->response;
pos = tmp.find(CRLF CRLF);
if (pos != std::string::npos)
tmp.erase(pos);
::parse_http_headers(tmp, srv_fld);
// put script headers in map
tmp = output;
pos = tmp.find(CRLF CRLF);
if (pos != std::string::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 (it_srv->first == it_scr->first)
if (str_tolower(it_srv->first) == str_tolower(it_scr->first))
{
pos = client->response.find(it_srv->first);
::del_line_in_str(&client->response, pos, CRLF);
::extract_line(client->response, pos, CRLF);
}
}
}

View File

@@ -18,9 +18,6 @@ void Webserv::_get(Client *client)
if (_is_cgi(client))
{
script_output = _exec_cgi(client);
// DEBUG
std::cout << "\n____script_output____\n" << script_output << "\n_______________\n";
// wip check output of script
_check_script_output(client, script_output);
client->response += script_output;
return;
@@ -48,8 +45,6 @@ void Webserv::_get(Client *client)
}
else
_get_file(client, path);
}
# define MAX_FILESIZE 1000000 // (1Mo)

View File

@@ -1,64 +1,6 @@
#include "parsing_message_http.hpp"
size_t
parse_http_first_line(std::string message, std::vector<std::string> &line)
{
std::vector<std::string> sline;
std::string sub;
std::string tmp;
size_t pos;
size_t ret;
// TODO: check for err in substr
pos = message.find(CRLF);
sub = message.substr(0, pos);
sline = ::split(sub, ' ');
ret = sline.size();
if (ret != 3)
return ret;
for (int i = 0; i < 3; i++)
{
tmp = ::trim(sline[i], ' ');
tmp = ::trim(tmp, '\r');
line.push_back(tmp);
}
return ret;
}
std::map<std::string, std::string>
parse_http_headers(std::string message)
{
std::map<std::string, std::string> headers;
std::vector<std::string> list;
std::vector<std::string>::iterator it;
std::string sub;
std::string key;
std::string val;
size_t pos;
pos = (message).find(CRLF CRLF);
sub = (message).substr(0, pos);
list = ::split(sub, '\n');
if ( maybe_http_first_line( *list.begin() ) )
list.erase(list.begin());
for (it = list.begin(); it != list.end(); it++)
{
// TODO: if pattern is not "NAME: value" return error
pos = (*it).find(':');
key = (*it).substr( 0, pos );
key = ::trim(key, ' ');
key = ::trim(key, '\r');
key = ::str_tolower(key);
val = (*it).substr( pos + 1 );
val = ::trim(val, ' ');
val = ::trim(val, '\r');
headers.insert( std::pair<std::string, std::string>(key, val) );
}
return headers;
}
std::string
parse_http_body(std::string message)
{
@@ -73,18 +15,3 @@ std::string
return body;
}
bool maybe_http_first_line(std::string str)
{
// method SP target SP version https://www.rfc-editor.org/rfc/rfc7230.html#section-3.1.1
// version SP status SP reason https://www.rfc-editor.org/rfc/rfc7230.html#section-3.1.2
std::vector<std::string> sline;
sline = ::split(str, ' ');
if (sline.size() != 3)
return false;
if (sline[0].find(':') != std::string::npos)
return false;
return true;
}

View File

@@ -8,18 +8,14 @@
# include <map>
# include "utils.hpp"
size_t
parse_http_first_line(std::string message, std::vector<std::string> &line);
std::map<std::string, std::string>
parse_http_headers(std::string message);
parse_http_headers (
std::string headers,
std::map<std::string, std::string> fields )
std::string
parse_http_body(std::string message);
bool
maybe_http_first_line(std::string);
// http message structure :
//
// start-line

View File

@@ -54,7 +54,7 @@ int Webserv::_read_request(Client *client)
if (!client->header_complete)
{
client->parse_request(_servers);
client->parse_request_headers(_servers);
if (client->status)
return READ_COMPLETE;
if (client->header_complete)