Merge branch 'master' of bitbucket.org:LuckyLaszlo/webserv

This commit is contained in:
hugogogo
2022-08-09 14:59:39 +02:00
33 changed files with 1186 additions and 685 deletions

View File

@@ -22,10 +22,11 @@ SRCS_D = srcs \
SRCS = main.cpp \ SRCS = main.cpp \
base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \ base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \
accept.cpp request.cpp response.cpp \ accept.cpp request.cpp response.cpp \
method_get.cpp method_post.cpp method_delete.cpp \
run_loop.cpp \ run_loop.cpp \
ConfigParser.cpp \ parser.cpp \
ConfigParserUtils.cpp \ extraConfig.cpp \
ConfigParserPost.cpp \ postProcessing.cpp \
utils.cpp \ utils.cpp \
cgi_script.cpp \ cgi_script.cpp \
Client.cpp \ Client.cpp \

View File

@@ -10,14 +10,44 @@ server {
# client_body_limit 400; # client_body_limit 400;
index index.html; # this is another comment index index.html; # this is another comment
root ./www/; root ./www/;
# If not explicitly set, ConfigParser need to genererate a location block location /test {
# like this for path "/" (based on field "root" and "index" of the server) index index1.html;
location / {
root ./www/;
index index.html;
} }
allow_methods GET; # /stylesheet/ alone doesn't work, i mean we don't have wildcards...
location /stylesheet/style.css {
# root ./www/../;
root ./;
}
location /test/something.html {
# allow_methods DELETE;
}
# location /something/long/here {
# }
location /test/test_deeper/ {
# allow_methods
autoindex on;
}
location /test/test_deeper/super_deep {
autoindex on;
}
# location /test/test_deeper/something.html {
# allow_methods DELETE;
# }
# ok in theory if one were to go to /test they would get the index in www
# as opposed to the one in /website...
# location /test {
# root /www;
# }
} }

View File

@@ -1,13 +1,25 @@
IN 42 SUBJECT, PRIORITY :
- chunked request (response not mandatory it seems)
- CGI
- index (default file directory)
- Ecrire des tests !
- handle redirection (weird behavior, to fix)
- upload files with config "upload_repo"
-----------------------------
- autoindex (done, need test)
--------------
- replace atoi() with a better function
- 408 Request Timeout
- gerer le champ "Accept" du client
- gerer les ".." dans un URL (verifier que l'on ne sort pas du dossier "root")
- do correct handling of special character in url (/rfc2119_files/errata.js.t%C3%A9l%C3%A9chargement -> /rfc2119_files/errata.js.téléchargement) - do correct handling of special character in url (/rfc2119_files/errata.js.t%C3%A9l%C3%A9chargement -> /rfc2119_files/errata.js.téléchargement)
- handle redirection
- maybe add a "last_action_time" in Client for timeout handling - maybe add a "last_action_time" in Client for timeout handling
little global timeout on epoll, like 100ms, then find client that actualy need to timeout little global timeout on epoll, like 100ms, then find client that actualy need to timeout
if (actual_time - client.last_action_time > 10000ms){timeout(client)} if (actual_time - client.last_action_time > 10000ms){timeout(client)}
- add headers "Date" and "Last-Modified" to response - add headers "Date" and "Last-Modified" to response
- change "std::string" to reference "std::string &" in most functions - change "std::string" to reference "std::string &" in most functions
and add "const" if apropriate. and add "const" if apropriate.
- http_method en mode binary flags. "std::vector<http_method> allow_methods" -> "unsigned int allow_methods;"
- Dans le parsing, trier les "locations" par ordre de precision. - Dans le parsing, trier les "locations" par ordre de precision.
Compter les "/" dans le chemin, les locations avec le plus de "/" seront en premier dans le vector. Compter les "/" dans le chemin, les locations avec le plus de "/" seront en premier dans le vector.
- Il faut vérifier le path de la requête, voir si le serveur est bien censé délivrer cette ressource et si le client y a accès, avant d'appeler le CGI. - Il faut vérifier le path de la requête, voir si le serveur est bien censé délivrer cette ressource et si le client y a accès, avant d'appeler le CGI.

View File

@@ -1,13 +1,19 @@
#include "Client.hpp" #include "Client.hpp"
char Client::buf[MAX_FILESIZE+1];
/********************************************* /*********************************************
* COPLIENS * COPLIENS
*********************************************/ *********************************************/
Client::Client() : fd(0), body_size(0), status(0) { Client::Client()
: fd(0),
lsocket(NULL),
status(0),
header_complete(false),
read_body_size(0),
assigned_server(NULL),
assigned_location(NULL)
{
return; return;
} }
@@ -54,9 +60,12 @@ void Client::parse_request()
void Client::clear() void Client::clear()
{ {
clear_request(); clear_request();
header_complete = false;
read_body_size = 0;
assigned_server = NULL;
assigned_location = NULL;
raw_request.clear(); raw_request.clear();
response.clear(); response.clear();
body_size = 0;
status = 0; status = 0;
} }
@@ -128,6 +137,8 @@ void Client::_parse_request_headers( std::vector<std::string> list )
void Client::_parse_request_body( size_t pos ) void Client::_parse_request_body( size_t pos )
{ {
// TODO : a revoir avec une std::string,
// pour ne pas avoir le probleme d'un '0' qui marque la fin des données
std::string body = &raw_request[pos]; std::string body = &raw_request[pos];
_request.body = body; _request.body = body;

View File

@@ -7,6 +7,7 @@
# include <map> # include <map>
# include <vector> # include <vector>
# include "utils.hpp" # include "utils.hpp"
# include "ServerConfig.hpp"
struct Request struct Request
{ {
@@ -17,7 +18,6 @@ struct Request
std::string body; std::string body;
}; };
# define MAX_FILESIZE 1000000 // (1Mo)
class Client class Client
{ {
public: public:
@@ -27,12 +27,16 @@ class Client
//Client &operator=(Client const &rhs); //Client &operator=(Client const &rhs);
int fd; int fd;
const listen_socket *lsocket;
std::string raw_request; std::string raw_request;
std::string response; std::string response;
static char buf[MAX_FILESIZE+1];
size_t body_size;
unsigned int status; unsigned int status;
listen_socket *lsocket;
bool header_complete;
size_t read_body_size;
ServerConfig *assigned_server; // cant be const cause of error_pages.operator[]
const LocationConfig *assigned_location;
// const functions ? // const functions ?
http_method get_method(); http_method get_method();

View File

@@ -26,8 +26,9 @@
# include <iostream> // cout, cin # include <iostream> // cout, cin
# include <fstream> // ifstream # include <fstream> // ifstream
//# include <unistd.h> // access() //# include <unistd.h> // access()
# include <dirent.h> // opendir() # include <dirent.h> // opendir(), doesn't work...
# include <sys/stat.h> // stat(), replaces opendir() don't bother with ERRNO ?
# include <algorithm> // sort() in Post
class ConfigParser { class ConfigParser {
@@ -51,6 +52,13 @@ public:
// private member functions from anywhere... // private member functions from anywhere...
void _print_content() const; void _print_content() const;
// I don't love that this is here but...
// doesn't work use the operator overload
// bool compareLocationConfigs(const LocationConfig &a, const LocationConfig &b);
private: private:
std::string _content; std::string _content;
@@ -82,11 +90,20 @@ private:
void _post_processing(std::vector<ServerConfig> *servers); void _post_processing(std::vector<ServerConfig> *servers);
bool _find_root_path_location(std::vector<LocationConfig> locations); // const?
}; };
// no idea if it should go here...
//bool compareLocationConfigs(const LocationConfig &a,
// const LocationConfig &b);
// def needs work line a better name an do i even need this? // def needs work line a better name an do i even need this?
// should it be in Utils instead? // should it be in Utils instead?
class MyException : public std::invalid_argument class MyException : public std::invalid_argument

View File

@@ -1,85 +0,0 @@
#include "ConfigParser.hpp"
void ConfigParser::_post_processing(std::vector<ServerConfig> *servers)
{
// make certain servers default
// fill out empty settings
// if special settings are empty throw
std::vector<ServerConfig>::iterator it = servers->begin();
while (it != servers->end())
{
// host and port should already be set
if (it->host == "")
throw std::invalid_argument("Config file needs a host and port");
// is that a good default?
if (it->root == "")
it->root = "/";
if (it->client_body_limit == 0)
it->client_body_limit = 5000; // what is the recomended size?
// autoindex should already be false by default right?
// what do we do if Allow methods is left empty?
// all ?
if (it->allow_methods.empty())
throw std::invalid_argument("No methods specified");
// what to do if index is left empty? index.html?
// ok but i still need to check index, no idea how...
// if error_pages is left empty, we'll use the defaults which
// i believe are set elsewhere...
std::vector<LocationConfig>::iterator it_l = it->locations.begin();
while (it_l != it->locations.end())
{
// check that path is feasible...
// opendir?
DIR* dir = opendir(it_l->path.c_str());
if (dir)
closedir(dir);
else
throw std::invalid_argument("location dir could not be opened");
if (it_l->client_body_limit == 0)
it_l->client_body_limit = 5000; // what is the recomended size?
if (it_l->root == "")
it_l->root = it->root;
// fill out allow methods from server?
if (it_l->allow_methods.empty())
it_l->allow_methods = it->allow_methods;
// fill out index from Server?
// or do a bunch of checks on what is in there...
// same for redirect status i think
// maybe do something for Cgi_info?
++it_l;
}
++it;
}
// do the defaults at the end?
}

View File

@@ -6,7 +6,7 @@
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */ /* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */ /* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/23 16:08:00 by me #+# #+# */ /* Created: 2022/07/23 16:08:00 by me #+# #+# */
/* Updated: 2022/08/02 14:06:07 by lperrey ### ########.fr */ /* Updated: 2022/08/04 19:32:40 by erlazo ### ########.fr */
/* */ /* */
/* ************************************************************************** */ /* ************************************************************************** */
@@ -16,7 +16,13 @@
# include <map> # include <map>
# include <vector> # include <vector>
# include <string> # include <string>
# include <iostream>
# include <sys/stat.h> // stat()
# include <stdio.h> // printf(), gotta go
# include "utils.hpp"
// again, struct instead? // again, struct instead?
class LocationConfig class LocationConfig
@@ -24,22 +30,64 @@ class LocationConfig
public: public:
// canonic stuff? // canonic stuff?
std::string path; std::string path; // /path and /path/ are fine
// i add trailing / if a dir
int client_body_limit; std::string root;
std::string root;
std::vector<std::string> index; std::vector<std::string> index;
std::vector<http_method> allow_methods; unsigned int allow_methods;
std::map<std::string, std::string> cgi_info; std::vector<std::string> cgi_ext; // php not .php
bool autoindex;
// wait if i can call several times, shouldn't it be a map? std::string upload_repo; // might change name...
// wait no there can only be 1 and i think it might have to be in
// location only... int redirect_status;
int redirect_status; std::string redirect_uri;
std::string redirect_uri;
// au pire you do location / { return 301 http://location; } // au pire you do location / { return 301 http://location; }
// and that's how you get the redirect from the root. // and that's how you get the redirect from the root.
void print_all()
{
std::cout << "\nPRINTING A LOCATION\n";
std::cout << "Path: " << path << '\n';
std::cout << "root: " << root << '\n';
std::cout << "autoindex: " << autoindex << '\n';
std::cout << "Skipping index...\n";
std::cout << "Location allow_methods: ";
std::cout << ::http_methods_to_str(allow_methods) << "\n";
std::cout << "Skipping redirect status etc...\n";
std::cout << "------\n";
}
// works a lot better than using a compare function...
bool operator<(const LocationConfig& rhs) const
{
int comp_lhs = 0;
int comp_rhs = 0;
size_t tmp = 0;
while ((tmp = this->path.find_first_of("/", tmp)) != std::string::npos)
{
++tmp;
++comp_lhs;
}
if (path[path.find_last_of("/") + 1] != '\0')
++comp_lhs;
tmp = 0;
while ((tmp = rhs.path.find_first_of("/", tmp)) != std::string::npos)
{
++tmp;
++comp_rhs;
}
if (rhs.path[rhs.path.find_last_of("/") + 1] != '\0')
++comp_rhs;
return (comp_lhs < comp_rhs); // right comparison ? not <= ?
};
}; };

View File

@@ -14,48 +14,24 @@
class ServerConfig class ServerConfig
{ {
public: public:
// do i need some canonic stuff? // do i need some canonic stuff?
// there can be several
std::vector<std::string> server_name; std::vector<std::string> server_name;
// we could shove default in here if we wanted to... // we could shove default in here if we wanted to...
// there can only be 1 per server...
std::string host; std::string host;
std::string port; // port needs to be something else... not quite an int std::string port; // port needs to be something else... not quite an int
// should a Server be able to handle several?
// there can only be one. std::string root; // ./www/ or www work www/ and www work
std::string root; // i do remove trailing / tho
unsigned int client_body_limit; // set to default max if none set unsigned int client_body_limit; // set to default max if none set
// might be the only one we let slide if bad input...
bool autoindex;
// we will check the index in the post processing with access() ?
std::vector<std::string> index; std::vector<std::string> index;
std::map<int, std::string> error_pages; std::map<int, std::string> error_pages;
// i'm tempted to do something diff for storing method types...
// fuck it, you can only call allow_methods once in Server
// once more in each location.
std::vector<http_method> allow_methods;
std::vector<LocationConfig> locations; std::vector<LocationConfig> locations;
// not convinced we need these...
// struct timeval send_timeout;
// struct timeval recv_timeout;
// fuck maybe i do need return here...
// wait if i can call several times, shouldn't it be a map?
// i think actually there can only be 1 and it can only be in a location?
// int redirect_status;
// std::string redirect_uri;
void print_all() void print_all()
{ {
@@ -73,15 +49,12 @@ public:
std::cout << it->first << "--" << it->second << " "; std::cout << it->first << "--" << it->second << " ";
// for (size_t i = 0; i < error_pages.size(); i++) // for (size_t i = 0; i < error_pages.size(); i++)
// std::cout << error_pages->first << "--" << error_pages->second << " "; // std::cout << error_pages->first << "--" << error_pages->second << " ";
std::cout << "\nallow_methods: ";
for (size_t i = 0; i < allow_methods.size(); i++) // std::cout << "skiping Locations for now...\n";
std::cout << allow_methods[i] << " "; for (std::vector<LocationConfig>::iterator it = locations.begin(); it < locations.end(); it++)
std::cout << "\nskiping Locations for now...\n"; it->print_all();
std::cout << "also skiping send_timeout and recv\n";
std::cout << "autoindex: " << autoindex << '\n';
std::cout << "client_body_limit: " << client_body_limit << '\n'; std::cout << "client_body_limit: " << client_body_limit << '\n';
// std::cout << "redirect_status: " << redirect_status << '\n';
// std::cout << "redirect_uri: " << redirect_uri << '\n';
std::cout << "host: " << host << '\n'; std::cout << "host: " << host << '\n';
std::cout << "port: " << port << '\n'; std::cout << "port: " << port << '\n';

View File

@@ -0,0 +1,27 @@
// prolly get rid of this file...
#include "LocationConfig.hpp"
#include <string>
#include <algorithm>
// Ok so maybe it can't be a member functions?
bool compareLocationConfigs(const LocationConfig &a, const LocationConfig &b)
{
int len_a;
int len_b;
size_t tmp = 0;
// consider adding 1 to path that ends in a file not folder.
while ((tmp = a.path.find_first_of("/", tmp)) != std::string::npos)
++len_a;
tmp = 0;
while ((tmp = b.path.find_first_of("/", tmp)) != std::string::npos)
++len_b;
return (len_a < len_b); // right comparison ? not <= ?
}

View File

@@ -82,6 +82,9 @@ std::string ConfigParser::_get_rest_of_line(size_t *curr)
return (values); return (values);
} }
void ConfigParser::_print_content() const void ConfigParser::_print_content() const
{ {
std::cout << _content; std::cout << _content;

View File

@@ -31,8 +31,6 @@ ConfigParser::ConfigParser(const char* path)
file.open(path); file.open(path);
if (file.is_open()) if (file.is_open())
{ {
// are there more throws i need to add in case of errors, what would
// those errors be?
while (!file.eof()) while (!file.eof())
{ {
getline(file, buf); getline(file, buf);
@@ -45,7 +43,7 @@ ConfigParser::ConfigParser(const char* path)
} }
else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment) else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment)
{ {
// is there a comment at the end of the line // check for comment at the end of the line
std::string tmp = buf.substr(0, comment - 1); std::string tmp = buf.substr(0, comment - 1);
_content.append(tmp + '\n'); _content.append(tmp + '\n');
} }
@@ -53,7 +51,7 @@ ConfigParser::ConfigParser(const char* path)
file.close(); file.close();
} }
else else
throw std::invalid_argument("open config"); throw std::invalid_argument("failed to open config");
} }
ConfigParser::~ConfigParser() ConfigParser::~ConfigParser()
@@ -85,10 +83,11 @@ std::vector<ServerConfig> * ConfigParser::parse()
throw std::invalid_argument("empty config file"); throw std::invalid_argument("empty config file");
while (curr != std::string::npos) while (curr != std::string::npos)
{ {
// why no checks here if ((start = _content.find_first_not_of(" \t\n", curr)) == std::string::npos)
// if not here do i need them elsewhere? throw std::invalid_argument("empty config file");
start = _content.find_first_not_of(" \t\n", curr);
curr = _content.find_first_of(" \t\n", start); if ((curr = _content.find_first_of(" \t\n", start)) == std::string::npos)
throw std::invalid_argument("empty config file");
std::string key = _content.substr(start, curr - start); std::string key = _content.substr(start, curr - start);
if (key != "server") if (key != "server")
throw std::invalid_argument("bad config file arguments 1"); throw std::invalid_argument("bad config file arguments 1");
@@ -104,13 +103,12 @@ ServerConfig ConfigParser::_parse_server(size_t *start)
size_t curr = _content.find_first_not_of(" \t\n", *start); size_t curr = _content.find_first_not_of(" \t\n", *start);
ret.client_body_limit = 0; ret.client_body_limit = 0;
ret.autoindex = false;
if (curr == std::string::npos || _content[curr] != '{') if (curr == std::string::npos || _content[curr] != '{')
throw std::invalid_argument("bad config file syntax 1"); throw std::invalid_argument("bad config file syntax 1");
curr = _content.find_first_of(" \t\n", curr + 1); if ((curr = _content.find_first_of(" \t\n", curr + 1)) == std::string::npos)
// if (curr == std::string::npos) // are there other things to check for? throw std::invalid_argument("bad config file syntax");
// throw std::invalid_argument("bad config file syntax"); // are there other things to check for?
while (curr != std::string::npos) // here curr == { + 1 while (curr != std::string::npos) // here curr == { + 1
{ {
// so this moves curr to past the word... // so this moves curr to past the word...
@@ -142,9 +140,15 @@ LocationConfig ConfigParser::_parse_location(size_t *start)
size_t curr = *start; size_t curr = *start;
// start is after the 1st word aka "location" // start is after the 1st word aka "location"
ret.client_body_limit = 0; ret.autoindex = false;
ret.redirect_status = 0; ret.redirect_status = 0;
ret.allow_methods = 0;
ret.path = _get_first_word(&curr); ret.path = _get_first_word(&curr);
// are you sure about this?
if (ret.path[0] != '/')
ret.path.insert(0, "/");
// throw std::invalid_argument("Location path require a leading /");
// in theory now curr should be right after the "path" // in theory now curr should be right after the "path"
curr = _content.find_first_not_of(" \t\n", curr); curr = _content.find_first_not_of(" \t\n", curr);
@@ -152,9 +156,9 @@ LocationConfig ConfigParser::_parse_location(size_t *start)
if (curr == std::string::npos || _content[curr] != '{') if (curr == std::string::npos || _content[curr] != '{')
throw std::invalid_argument("bad config file syntax 2"); throw std::invalid_argument("bad config file syntax 2");
curr = _content.find_first_of(" \t\n", curr + 1); if ((curr = _content.find_first_of(" \t\n", curr + 1)) == std::string::npos)
// if (curr == std::string::npos) // are there other things to check for? throw std::invalid_argument("bad config file syntax");
// throw std::invalid_argument("bad config file syntax"); // are there other things to check for?
while (curr != std::string::npos) while (curr != std::string::npos)
{ {
// so this moves curr to past the word... // so this moves curr to past the word...
@@ -177,8 +181,6 @@ LocationConfig ConfigParser::_parse_location(size_t *start)
void ConfigParser::_set_server_values(ServerConfig *server, \ void ConfigParser::_set_server_values(ServerConfig *server, \
const std::string key, std::string value) const std::string key, std::string value)
{ {
@@ -189,11 +191,12 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
if (size < 1) if (size < 1)
throw std::invalid_argument("missing value"); throw std::invalid_argument("missing value");
else if (key == "server_name" && size == 1) else if (key == "server_name" && server->server_name.empty())
{ {
for (size_t i = 0; i < server->server_name.size(); i++) for (std::vector<std::string>::iterator it = server->server_name.begin(); \
it < server->server_name.end(); it++)
{ {
if (server->server_name[i].compare(tmp_val[0]) == 0) if (it->compare(tmp_val[0]) == 0)
throw std::invalid_argument("server_name already exists"); throw std::invalid_argument("server_name already exists");
} }
server->server_name.push_back(tmp_val[0]); server->server_name.push_back(tmp_val[0]);
@@ -229,19 +232,11 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
} }
else if (key == "root" && size == 1 && server->root == "") else if (key == "root" && size == 1 && server->root == "")
{ {
DIR* dir = opendir(tmp_val[0].c_str()); // remove trailing /
if (dir) if (tmp_val[0][tmp_val[0].size() - 1] == '/')
closedir(dir); tmp_val[0].erase(tmp_val[0].size() - 1, 1);
else
throw std::invalid_argument("root dir could not be opened");
server->root = tmp_val[0]; server->root = tmp_val[0];
} }
else if (key == "autoindex" && size == 1)
{
// autoindex is a bool, there's no good way for me to see if it has
// bet set already
server->autoindex = (tmp_val[0] == "on" ? true : false);
}
else if (key == "client_body_limit" && size == 1 \ else if (key == "client_body_limit" && size == 1 \
&& server->client_body_limit == 0) && server->client_body_limit == 0)
{ {
@@ -252,22 +247,9 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
else if (key == "index") else if (key == "index")
{ {
// i think you can call index several times... // i think you can call index several times...
// should i be doing an access?
// since index is at the root, but root might not yet be defined
// will check index later in post
for (unsigned long i = 0; i != tmp_val.size(); i++) for (unsigned long i = 0; i != tmp_val.size(); i++)
server->index.push_back(tmp_val[i]); server->index.push_back(tmp_val[i]);
} }
else if (key == "allow_methods" && server->allow_methods.empty())
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
server->allow_methods.push_back(m);
}
}
else if (key == "error_page") else if (key == "error_page")
{ {
@@ -292,22 +274,8 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
server->error_pages[status_code] = path; server->error_pages[status_code] = path;
} }
} }
/* else if (key == "recv_timeout" && size == 1 && server->server_name == "") else
{
// what is tv_sec and do i need it?
// ok so i don't fully understand this part but ok, keep for now...
server->recv_timeout.tv_sec = atoi(tmp_val[0].c_str());
}
else if (key == "send_timeout" && size == 1 && server->server_name == "")
{
server->send_timeout.tv_sec = atoi(tmp_val[0].c_str());
}
*/ else
{
// means either you didn't write the right key, or the value is
// missing, or the value has already been filled.
throw std::invalid_argument("bad key value pair"); throw std::invalid_argument("bad key value pair");
}
} }
@@ -323,48 +291,43 @@ void ConfigParser::_set_location_values(LocationConfig *location, \
throw std::invalid_argument("missing value"); throw std::invalid_argument("missing value");
else if (key == "root" && size == 1 && location->root == "") else if (key == "root" && size == 1 && location->root == "")
{ {
DIR* dir = opendir(tmp_val[0].c_str()); // remove trailing /
if (dir) if (tmp_val[0][tmp_val[0].size() - 1] == '/')
closedir(dir); tmp_val[0].erase(tmp_val[0].size() - 1, 1);
else
throw std::invalid_argument("root dir could not be opened");
location->root = tmp_val[0]; location->root = tmp_val[0];
} }
else if (key == "client_body_limit" && size == 1 \ else if (key == "autoindex" && size == 1)
&& location->client_body_limit == 0)
{ {
if (!::isNumeric(tmp_val[0])) location->autoindex = (tmp_val[0] == "on" ? true : false);
throw std::invalid_argument("client_body_limit not a number"); std::cout << "in parser " << location->path << " autoindex: " << location->autoindex << '\n';
location->client_body_limit = atoi(tmp_val[0].c_str());
} }
else if (key == "index") else if (key == "index")
{ {
// what about index /index.html; aka at the root? not handle?
// you can definitely call Index several times, i think // you can definitely call Index several times, i think
for (unsigned long i = 0; i != tmp_val.size(); i++) for (unsigned long i = 0; i != tmp_val.size(); i++)
location->index.push_back(tmp_val[i]); location->index.push_back(tmp_val[i]);
} }
else if (key == "allow_methods" && location->allow_methods.empty()) else if (key == "allow_methods" && location->allow_methods == 0)
{ {
for (unsigned long i = 0; i != tmp_val.size(); i++) for (unsigned long i = 0; i != tmp_val.size(); i++)
{ {
http_method m = ::str_to_http_method(tmp_val[i]); http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN) if (m == UNKNOWN)
throw std::invalid_argument("not a valid method"); throw std::invalid_argument("not a valid method");
location->allow_methods.push_back(m); location->allow_methods |= m;
} }
} }
else if (key == "cgi_info") else if (key == "cgi_ext")
{ {
// you can call cgi_info several times i think. for (size_t i = 0; i < tmp_val.size(); i++)
// ok wtf is all this even doing, figure that out {
unsigned long i = value.find_first_of(" "); if (tmp_val[i][0] == '.')
if (i == std::string::npos) throw std::invalid_argument("cgi_ext should not have a leading '.'");
throw std::invalid_argument("bad config file arguments 8"); location->cgi_ext.push_back(tmp_val[i]);
// ok why an int now, we gotta be more consistent! }
int j = value.find_first_not_of(" ", i);
location->cgi_info[value.substr(0, i)] = value.substr(j, value.length());
} }
else if (key == "return" && location->redirect_status == 0 \ else if (key == "redirect" && location->redirect_status == 0 \
&& location->redirect_uri == "") && location->redirect_uri == "")
{ {
// actually i think there can only be one per location... // actually i think there can only be one per location...
@@ -372,20 +335,26 @@ void ConfigParser::_set_location_values(LocationConfig *location, \
if (tmp_val.size() != 2) if (tmp_val.size() != 2)
throw std::invalid_argument("wrong number of values"); throw std::invalid_argument("wrong number of values");
// and tmp_val[0] should be a number and tmp_val[1] a string? // and tmp_val[0] should be a number and tmp_val[1] a string?
if (!(::isNumeric(tmp_val[0]))) if (tmp_val[0] != "301" && tmp_val[0] != "302"
throw std::invalid_argument("value not a number"); && tmp_val[0] != "303" && tmp_val[0] != "307"
&& tmp_val[0] != "308")
throw std::invalid_argument("bad redirect status");
// double check this
// it means we aren't allowing internal redirects.
if (tmp_val[1].compare(0, 7, "http://")
|| tmp_val[1].compare(0, 8, "https://"))
throw std::invalid_argument("bad redirect uri");
// somehow check that tmp_val[1] is a string? or valid? how?
// something about using access() to see if
location->redirect_status = atoi(tmp_val[0].c_str()); location->redirect_status = atoi(tmp_val[0].c_str());
location->redirect_uri = tmp_val[1]; location->redirect_uri = tmp_val[1];
} }
else else if (key == "upload_repo" && size == 1 && location->upload_repo == "")
{ {
// means either you didn't write the right key, or the value is // what checks to do?
// missing, or the value has already been filled. location->upload_repo = tmp_val[0];
throw std::invalid_argument("bad key value pair");
} }
else
throw std::invalid_argument("bad key value pair");
} }

View File

@@ -0,0 +1,98 @@
#include "ConfigParser.hpp"
void ConfigParser::_post_processing(std::vector<ServerConfig> *servers)
{
std::vector<ServerConfig>::iterator it = servers->begin();
while (it != servers->end())
{
// host and port are Mandatory
if (it->host == "")
throw std::invalid_argument("Config file needs a host and port");
// root is mandatory
if (it->root == "")
throw std::invalid_argument("Config file needs a root");
// index is mandatory in Server
if (it->index.empty())
throw std::invalid_argument("Config file needs an Index");
if (it->client_body_limit == 0)
it->client_body_limit = 5000; // what is the recomended size?
// if error_pages is left empty, we'll use the defaults which
// i believe are set elsewhere...
if (!_find_root_path_location(it->locations))
{
LocationConfig tmp;
tmp.path = "/";
tmp.root = it->root;
tmp.index = it->index;
tmp.allow_methods = ANY_METHODS;
tmp.autoindex = false;
tmp.redirect_status = 0;
it->locations.push_back(tmp);
}
std::vector<LocationConfig>::iterator it_l = it->locations.begin();
while (it_l != it->locations.end())
{
if (it_l->root == "")
it_l->root = it->root;
if (it_l->allow_methods == UNKNOWN)
it_l->allow_methods = ANY_METHODS;
if (it_l->index.empty())
it_l->index = it->index;
// same for redirect status i think
// maybe do something for Cgi_ext?
// std::cout << "In Post, Root + Path: " << it_l->root + it_l->path << '\n';
/* if (path_is_valid(it_l->root + it_l->path) == 0)
{
//either we throw or we erase
throw std::invalid_argument("location path is invalid");
}
*/
if (path_is_valid(it_l->root + it_l->path) == 1 \
&& it_l->path[it_l->path.size() - 1] != '/')
it_l->path.push_back('/');
++it_l;
}
std::sort(it->locations.begin(), it->locations.end());
// std::reverse(it->locations.begin(), it->locations.end());
++it;
}
}
bool ConfigParser::_find_root_path_location(std::vector<LocationConfig> locations)
{
std::vector<LocationConfig>::iterator it = locations.begin();
while (it != locations.end())
{
if (it->path.compare("/") == 0)
{
// std::cout << "in compare: " << it->path << " -- ";
return true;
}
++it;
}
return false;
}

View File

@@ -44,7 +44,7 @@ int main(int ac, char **av)
} }
catch (std::exception& e) catch (std::exception& e)
{ {
std::cout << e.what() << '\n'; std::cerr << e.what() << '\n';
} }
return (0); return (0);

View File

@@ -1,6 +1,14 @@
#include "utils.hpp" #include "utils.hpp"
void throw_test()
{
static int i = 0;
++i;
if (i % 8 == 0)
throw std::bad_alloc();
}
std::vector<std::string> split(std::string input, char delimiter) std::vector<std::string> split(std::string input, char delimiter)
{ {
std::vector<std::string> answer; std::vector<std::string> answer;
@@ -60,6 +68,9 @@ http_method str_to_http_method(std::string &str)
return POST; return POST;
else if (str == "DELETE") else if (str == "DELETE")
return DELETE; return DELETE;
else if (str == "ALL_METHODS") // for Eric: why this test ? can we delete it?
return ANY_METHODS;
// would prefere ALL_METHODS
return UNKNOWN; return UNKNOWN;
} }
@@ -85,6 +96,32 @@ std::string http_methods_to_str(unsigned int methods)
return (str); return (str);
} }
# include <iostream>
// you could make this &path...
int path_is_valid(std::string path)
{
const char *tmp_path = path.c_str();
struct stat s;
if (stat(tmp_path, &s) == 0)
{
if (S_ISREG(s.st_mode))
{
// std::cout << "is a file\n";
return (2);
}
else if (S_ISDIR(s.st_mode))
{
// std::cout << "is a Dir\n";
return (1);
}
}
// std::cout << "path is neither dir nor file\n";
return (0);
}
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()) if (ori_substr.empty())

View File

@@ -6,20 +6,12 @@
# include <string> # include <string>
# include <sstream> # include <sstream>
# include <cstdlib> // atoi # include <cstdlib> // atoi
# include <sys/stat.h> // stat()
# define CR "\r" # define CR "\r"
# define LF "\n" # define LF "\n"
# define CRLF CR LF # define CRLF CR LF
// enum http_method
// {
// UNKNOWN = 0b00000000,
// GET = 0b00000001,
// POST = 0b00000010,
// DELETE = 0b00000100,
// ANY_METHODS = 0b11111111,
// };
enum http_method enum http_method
{ {
UNKNOWN = 0b0, UNKNOWN = 0b0,
@@ -27,6 +19,8 @@ enum http_method
POST = 1 << 1, POST = 1 << 1,
DELETE = 1 << 2, DELETE = 1 << 2,
ANY_METHODS = 0b11111111, ANY_METHODS = 0b11111111,
// ALL_METHODS = 0b11111111,
// i would prefer this...
}; };
struct listen_socket struct listen_socket
@@ -45,6 +39,8 @@ std::string itos(int n);
std::string trim(std::string str, char c); std::string trim(std::string str, char c);
http_method str_to_http_method(std::string &str); http_method str_to_http_method(std::string &str);
std::string http_methods_to_str(unsigned int methods); std::string http_methods_to_str(unsigned int methods);
int path_is_valid(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);
void throw_test();
#endif #endif

View File

@@ -24,11 +24,13 @@
# include <string> // string # include <string> // string
# include <cstdio> // perror, remove # include <cstdio> // perror, remove
# include <cstdlib> // atoi (athough it's already cover by <string>) # include <cstdlib> // atoi (athough it's already cover by <string>)
# include <dirent.h> // opendir()
# include "Client.hpp" # include "Client.hpp"
# include "ServerConfig.hpp" # include "ServerConfig.hpp"
# include "utils.hpp" # include "utils.hpp"
# include "http_status.hpp" # include "http_status.hpp"
# include "autoindex.hpp"
extern bool g_run; extern bool g_run;
extern int g_last_signal; extern int g_last_signal;
@@ -38,6 +40,8 @@ void signal_handler(int signum);
# define FAILURE -1 # define FAILURE -1
# define SUCCESS 1 # define SUCCESS 1
# define MIME_TYPE_DEFAULT "application/octet-stream"
class Webserv class Webserv
{ {
public: public:
@@ -68,29 +72,29 @@ class Webserv
void _accept_connection(listen_socket &lsocket); void _accept_connection(listen_socket &lsocket);
// request.cpp // request.cpp
void _request(Client *client); void _request(Client *client);
void _read_request(Client *client); int _read_request(Client *client);
// response.cpp // response.cpp
void _response(Client *client); void _response(Client *client);
void _send_response(Client *client, ServerConfig &server); int _send_response(Client *client);
void _append_base_headers(Client *client); void _append_base_headers(Client *client);
void _construct_response(Client *client, ServerConfig &server); void _construct_response(Client *client);
void _process_method(Client *client, ServerConfig &server, LocationConfig &location); void _process_method(Client *client);
void _insert_status_line(Client *client); void _insert_status_line(Client *client);
void _error_html_response(Client *client, ServerConfig &server); void _error_html_response(Client *client);
void _append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension = ""); void _append_body(Client *client, const std::string &body, const std::string &file_extension = "");
ServerConfig *_determine_process_server(Client *client); // cant be const cause of error_pages.operator[]
void _get(Client *client, ServerConfig &server, LocationConfig &location); const LocationConfig *_determine_location(const ServerConfig &server, const std::string &path) const;
std::string _determine_file_extension(const std::string &path) const;
// method_get.cpp
void _get(Client *client);
void _get_file(Client *client, const std::string &path); void _get_file(Client *client, const std::string &path);
void _autoindex(Client *client, std::string &path);
void _post(Client *client, ServerConfig &server, LocationConfig &location); // method_post.cpp
void _post(Client *client);
void _post_file(Client *client, const std::string &path); void _post_file(Client *client, const std::string &path);
// method_delete.cpp
void _delete(Client *client, ServerConfig &server, LocationConfig &location); void _delete(Client *client);
void _delete_file(Client *client, const std::string &path); void _delete_file(Client *client, const std::string &path);
ServerConfig &_determine_process_server(Client *client);
LocationConfig &_determine_location(ServerConfig &server, std::string &path);
// cgi_script.cpp // cgi_script.cpp
bool _is_cgi(Client *client); bool _is_cgi(Client *client);
void _exec_cgi(Client *client); void _exec_cgi(Client *client);

View File

@@ -0,0 +1,32 @@
#ifndef AUTOINDEX_HPP
# define AUTOINDEX_HPP
// # define HTML_ERROR(STATUS) "\r\n<!DOCTYPE html><html><head><title>"STATUS"</title></head><body><h1 style=\"text-align:center\">"STATUS"</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
# define AUTOINDEX_START \
"<!DOCTYPE html>"\
"<html>"\
"<head>"\
"<title>Index of "
# define AUTOINDEX_MID1 \
"</title>"\
"</head>"\
"<body>" \
"<h1>Index of "
# define AUTOINDEX_MID2 \
"</h1>"\
"<hr>"\
"<pre>"
# define AUTOINDEX_END \
"</pre>"\
"<hr>"\
"</body>"\
"</html>"
#endif

View File

@@ -30,6 +30,13 @@
# define S201 "201 Created" # define S201 "201 Created"
# define S204 "204 No Content" # define S204 "204 No Content"
# define S301 "301 Moved Permanently"
# define S302 "302 Found"
# define S303 "303 See Other"
# define S304 "304 Not Modified" // unused
# define S307 "307 Temporary Redirect"
# define S308 "308 Permanent Redirect"
# define S400 "400 Bad Request" # define S400 "400 Bad Request"
# define S403 "403 Forbidden" # define S403 "403 Forbidden"
# define S404 "404 Not Found" # define S404 "404 Not Found"
@@ -38,5 +45,6 @@
# define S500 "500 Internal Server Error" # define S500 "500 Internal Server Error"
# define S501 "501 Not Implemented" # define S501 "501 Not Implemented"
# define S505 "505 HTTP Version Not Supported"
#endif #endif

View File

@@ -89,132 +89,151 @@ void Webserv::_listen(int socket_fd, unsigned int max_connections)
void Webserv::_init_http_status_map() void Webserv::_init_http_status_map()
{ {
_http_status[200] = S200; /* "map.insert()" over "map.operator[]" :
_http_status[201] = S201; ** http://www.uml.org.cn/c%2B%2B/pdf/EffectiveSTL.pdf#page=93
_http_status[204] = S204; */
typedef std::map<int, std::string>::value_type status_pair;
_http_status[400] = S400; // _http_status.insert(std::make_pair(200, S200)); // equivalent
_http_status[403] = S403; _http_status.insert(status_pair(200, S200));
_http_status[404] = S404; _http_status.insert(status_pair(201, S201));
_http_status[405] = S405; _http_status.insert(status_pair(204, S204));
_http_status[413] = S413;
_http_status[500] = S500; _http_status.insert(status_pair(301, S301));
_http_status[501] = S501; _http_status.insert(status_pair(302, S302));
_http_status.insert(status_pair(303, S303));
_http_status.insert(status_pair(304, S304));
_http_status.insert(status_pair(307, S307));
_http_status.insert(status_pair(308, S308));
_http_status.insert(status_pair(400, S400));
_http_status.insert(status_pair(403, S403));
_http_status.insert(status_pair(404, S404));
_http_status.insert(status_pair(405, S405));
_http_status.insert(status_pair(413, S413));
_http_status.insert(status_pair(500, S500));
_http_status.insert(status_pair(501, S501));
} }
void Webserv::_init_mime_types_map() void Webserv::_init_mime_types_map()
{ {
_mime_types[""] = "application/octet-stream"; /* From :
** http://nginx.org/en/docs/http/ngx_http_core_module.html#types
*/
typedef std::map<std::string, std::string>::value_type mime_pair;
_mime_types["html"] = "text/html"; _mime_types.insert(mime_pair("", MIME_TYPE_DEFAULT));
_mime_types["htm"] = "text/html";
_mime_types["shtml"] = "text/html";
_mime_types["css"] = "text/css";
_mime_types["xml"] = "text/xml";
_mime_types["gif"] = "image/gif";
_mime_types["jpeg"] = "image/jpeg";
_mime_types["jpg"] = "image/jpeg";
_mime_types["js"] = "application/javascript";
_mime_types["atom"] = "application/atom+xml";
_mime_types["rss"] = "application/rss+xml";
_mime_types["mml"] = "text/mathml"; _mime_types.insert(mime_pair("html", "text/html"));
_mime_types["txt"] = "text/plain"; _mime_types.insert(mime_pair("html", "text/html"));
_mime_types["jad"] = "text/vnd.sun.j2me.app-descriptor"; _mime_types.insert(mime_pair("htm", "text/html"));
_mime_types["wml"] = "text/vnd.wap.wml"; _mime_types.insert(mime_pair("shtml", "text/html"));
_mime_types["htc"] = "text/x-component"; _mime_types.insert(mime_pair("css", "text/css"));
_mime_types.insert(mime_pair("xml", "text/xml"));
_mime_types.insert(mime_pair("gif", "image/gif"));
_mime_types.insert(mime_pair("jpeg", "image/jpeg"));
_mime_types.insert(mime_pair("jpg", "image/jpeg"));
_mime_types.insert(mime_pair("js", "application/javascript"));
_mime_types.insert(mime_pair("atom", "application/atom+xml"));
_mime_types.insert(mime_pair("rss", "application/rss+xml"));
_mime_types["png"] = "image/png"; _mime_types.insert(mime_pair("mml", "text/mathml"));
_mime_types["tif"] = "image/tiff"; _mime_types.insert(mime_pair("txt", "text/plain"));
_mime_types["tiff"] = "image/tiff"; _mime_types.insert(mime_pair("jad", "text/vnd.sun.j2me.app-descriptor"));
_mime_types["wbmp"] = "image/vnd.wap.wbmp"; _mime_types.insert(mime_pair("wml", "text/vnd.wap.wml"));
_mime_types["ico"] = "image/x-icon"; _mime_types.insert(mime_pair("htc", "text/x-component"));
_mime_types["jng"] = "image/x-jng";
_mime_types["bmp"] = "image/x-ms-bmp";
_mime_types["svg"] = "image/svg+xml";
_mime_types["svgz"] = "image/svg+xml";
_mime_types["webp"] = "image/webp";
_mime_types["woff"] = "application/font-woff"; _mime_types.insert(mime_pair("png", "image/png"));
_mime_types["jar"] = "application/java-archive"; _mime_types.insert(mime_pair("tif", "image/tiff"));
_mime_types["war"] = "application/java-archive"; _mime_types.insert(mime_pair("tiff", "image/tiff"));
_mime_types["ear"] = "application/java-archive"; _mime_types.insert(mime_pair("wbmp", "image/vnd.wap.wbmp"));
_mime_types["json"] = "application/json"; _mime_types.insert(mime_pair("ico", "image/x-icon"));
_mime_types["hqx"] = "application/mac-binhex40"; _mime_types.insert(mime_pair("jng", "image/x-jng"));
_mime_types["doc"] = "application/msword"; _mime_types.insert(mime_pair("bmp", "image/x-ms-bmp"));
_mime_types["pdf"] = "application/pdf"; _mime_types.insert(mime_pair("svg", "image/svg+xml"));
_mime_types["ps"] = "application/postscript"; _mime_types.insert(mime_pair("svgz", "image/svg+xml"));
_mime_types["eps"] = "application/postscript"; _mime_types.insert(mime_pair("webp", "image/webp"));
_mime_types["ai"] = "application/postscript";
_mime_types["rtf"] = "application/rtf";
_mime_types["m3u8"] = "application/vnd.apple.mpegurl";
_mime_types["xls"] = "application/vnd.ms-excel";
_mime_types["eot"] = "application/vnd.ms-fontobject";
_mime_types["ppt"] = "application/vnd.ms-powerpoint";
_mime_types["wmlc"] = "application/vnd.wap.wmlc";
_mime_types["kml"] = "application/vnd.google-earth.kml+xml";
_mime_types["kmz"] = "application/vnd.google-earth.kmz";
_mime_types["7z"] = "application/x-7z-compressed";
_mime_types["cco"] = "application/x-cocoa";
_mime_types["jardiff"] = "application/x-java-archive-diff";
_mime_types["jnlp"] = "application/x-java-jnlp-file";
_mime_types["run"] = "application/x-makeself";
_mime_types["pl"] = "application/x-perl";
_mime_types["pm"] = "application/x-perl";
_mime_types["prc"] = "application/x-pilot";
_mime_types["pdb"] = "application/x-pilot";
_mime_types["rar"] = "application/x-rar-compressed";
_mime_types["rpm"] = "application/x-redhat-package-manager";
_mime_types["sea"] = "application/x-sea";
_mime_types["swf"] = "application/x-shockwave-flash";
_mime_types["sit"] = "application/x-stuffit";
_mime_types["tcl"] = "application/x-tcl";
_mime_types["tk"] = "application/x-tcl";
_mime_types["der"] = "application/x-x509-ca-cert";
_mime_types["pem"] = "application/x-x509-ca-cert";
_mime_types["crt"] = "application/x-x509-ca-cert";
_mime_types["xpi"] = "application/x-xpinstall";
_mime_types["xhtml"] = "application/xhtml+xml";
_mime_types["xspf"] = "application/xspf+xml";
_mime_types["zip"] = "application/zip";
_mime_types["bin"] = "application/octet-stream"; _mime_types.insert(mime_pair("woff", "application/font-woff"));
_mime_types["exe"] = "application/octet-stream"; _mime_types.insert(mime_pair("jar", "application/java-archive"));
_mime_types["dll"] = "application/octet-stream"; _mime_types.insert(mime_pair("war", "application/java-archive"));
_mime_types["deb"] = "application/octet-stream"; _mime_types.insert(mime_pair("ear", "application/java-archive"));
_mime_types["dmg"] = "application/octet-stream"; _mime_types.insert(mime_pair("json", "application/json"));
_mime_types["iso"] = "application/octet-stream"; _mime_types.insert(mime_pair("hqx", "application/mac-binhex40"));
_mime_types["img"] = "application/octet-stream"; _mime_types.insert(mime_pair("doc", "application/msword"));
_mime_types["msi"] = "application/octet-stream"; _mime_types.insert(mime_pair("pdf", "application/pdf"));
_mime_types["msp"] = "application/octet-stream"; _mime_types.insert(mime_pair("ps", "application/postscript"));
_mime_types["msm"] = "application/octet-stream"; _mime_types.insert(mime_pair("eps", "application/postscript"));
_mime_types.insert(mime_pair("ai", "application/postscript"));
_mime_types.insert(mime_pair("rtf", "application/rtf"));
_mime_types.insert(mime_pair("m3u8", "application/vnd.apple.mpegurl"));
_mime_types.insert(mime_pair("xls", "application/vnd.ms-excel"));
_mime_types.insert(mime_pair("eot", "application/vnd.ms-fontobject"));
_mime_types.insert(mime_pair("ppt", "application/vnd.ms-powerpoint"));
_mime_types.insert(mime_pair("wmlc", "application/vnd.wap.wmlc"));
_mime_types.insert(mime_pair("kml", "application/vnd.google-earth.kml+xml"));
_mime_types.insert(mime_pair("kmz", "application/vnd.google-earth.kmz"));
_mime_types.insert(mime_pair("7z", "application/x-7z-compressed"));
_mime_types.insert(mime_pair("cco", "application/x-cocoa"));
_mime_types.insert(mime_pair("jardiff", "application/x-java-archive-diff"));
_mime_types.insert(mime_pair("jnlp", "application/x-java-jnlp-file"));
_mime_types.insert(mime_pair("run", "application/x-makeself"));
_mime_types.insert(mime_pair("pl", "application/x-perl"));
_mime_types.insert(mime_pair("pm", "application/x-perl"));
_mime_types.insert(mime_pair("prc", "application/x-pilot"));
_mime_types.insert(mime_pair("pdb", "application/x-pilot"));
_mime_types.insert(mime_pair("rar", "application/x-rar-compressed"));
_mime_types.insert(mime_pair("rpm", "application/x-redhat-package-manager"));
_mime_types.insert(mime_pair("sea", "application/x-sea"));
_mime_types.insert(mime_pair("swf", "application/x-shockwave-flash"));
_mime_types.insert(mime_pair("sit", "application/x-stuffit"));
_mime_types.insert(mime_pair("tcl", "application/x-tcl"));
_mime_types.insert(mime_pair("tk", "application/x-tcl"));
_mime_types.insert(mime_pair("der", "application/x-x509-ca-cert"));
_mime_types.insert(mime_pair("pem", "application/x-x509-ca-cert"));
_mime_types.insert(mime_pair("crt", "application/x-x509-ca-cert"));
_mime_types.insert(mime_pair("xpi", "application/x-xpinstall"));
_mime_types.insert(mime_pair("xhtml", "application/xhtml+xml"));
_mime_types.insert(mime_pair("xspf", "application/xspf+xml"));
_mime_types.insert(mime_pair("zip", "application/zip"));
_mime_types["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; _mime_types.insert(mime_pair("bin", "application/octet-stream"));
_mime_types["xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; _mime_types.insert(mime_pair("exe", "application/octet-stream"));
_mime_types["pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; _mime_types.insert(mime_pair("dll", "application/octet-stream"));
_mime_types.insert(mime_pair("deb", "application/octet-stream"));
_mime_types.insert(mime_pair("dmg", "application/octet-stream"));
_mime_types.insert(mime_pair("iso", "application/octet-stream"));
_mime_types.insert(mime_pair("img", "application/octet-stream"));
_mime_types.insert(mime_pair("msi", "application/octet-stream"));
_mime_types.insert(mime_pair("msp", "application/octet-stream"));
_mime_types.insert(mime_pair("msm", "application/octet-stream"));
_mime_types["mid"] = "audio/midi"; _mime_types.insert(mime_pair("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"));
_mime_types["midi"] = "audio/midi"; _mime_types.insert(mime_pair("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
_mime_types["kar"] = "audio/midi"; _mime_types.insert(mime_pair("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"));
_mime_types["mp3"] = "audio/mpeg";
_mime_types["ogg"] = "audio/ogg";
_mime_types["m4a"] = "audio/x-m4a";
_mime_types["ra"] = "audio/x-realaudio";
_mime_types["3gpp"] = "video/3gpp"; _mime_types.insert(mime_pair("mid", "audio/midi"));
_mime_types["3gp"] = "video/3gpp"; _mime_types.insert(mime_pair("midi", "audio/midi"));
_mime_types["ts"] = "video/mp2t"; _mime_types.insert(mime_pair("kar", "audio/midi"));
_mime_types["mp4"] = "video/mp4"; _mime_types.insert(mime_pair("mp3", "audio/mpeg"));
_mime_types["mpeg"] = "video/mpeg"; _mime_types.insert(mime_pair("ogg", "audio/ogg"));
_mime_types["mpg"] = "video/mpeg"; _mime_types.insert(mime_pair("m4a", "audio/x-m4a"));
_mime_types["mov"] = "video/quicktime"; _mime_types.insert(mime_pair("ra", "audio/x-realaudio"));
_mime_types["webm"] = "video/webm";
_mime_types["flv"] = "video/x-flv"; _mime_types.insert(mime_pair("3gpp", "video/3gpp"));
_mime_types["m4v"] = "video/x-m4v"; _mime_types.insert(mime_pair("3gp", "video/3gpp"));
_mime_types["mng"] = "video/x-mng"; _mime_types.insert(mime_pair("ts", "video/mp2t"));
_mime_types["asx"] = "video/x-ms-asf"; _mime_types.insert(mime_pair("mp4", "video/mp4"));
_mime_types["asf"] = "video/x-ms-asf"; _mime_types.insert(mime_pair("mpeg", "video/mpeg"));
_mime_types["wmv"] = "video/x-ms-wmv"; _mime_types.insert(mime_pair("mpg", "video/mpeg"));
_mime_types["avi"] = "video/x-msvideo"; _mime_types.insert(mime_pair("mov", "video/quicktime"));
_mime_types.insert(mime_pair("webm", "video/webm"));
_mime_types.insert(mime_pair("flv", "video/x-flv"));
_mime_types.insert(mime_pair("m4v", "video/x-m4v"));
_mime_types.insert(mime_pair("mng", "video/x-mng"));
_mime_types.insert(mime_pair("asx", "video/x-ms-asf"));
_mime_types.insert(mime_pair("asf", "video/x-ms-asf"));
_mime_types.insert(mime_pair("wmv", "video/x-ms-wmv"));
_mime_types.insert(mime_pair("avi", "video/x-msvideo"));
} }

View File

@@ -0,0 +1,40 @@
#include "Webserv.hpp"
void Webserv::_delete(Client *client)
{
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-delete
*/
std::string path = client->get_path();
path.insert(0, client->assigned_location->root);
/* CGI Here ? */
_delete_file(client, path);
}
void Webserv::_delete_file(Client *client, const std::string &path)
{
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
if (remove(path.c_str()) == -1)
{
std::perror("err remove()");
client->status = 500;
return ;
}
}

189
srcs/webserv/method_get.cpp Normal file
View File

@@ -0,0 +1,189 @@
#include "Webserv.hpp"
void Webserv::_get(Client *client)
{
/* RULES **
if path is a valid dir check if index is specified and serve that
if no index and autoindex, server that
if file, server that!
Where does cgi fit in in all this ???
*/
std::string path = client->get_path();
// this might not be the best thing, a voir
path.insert(0, client->assigned_location->root);
std::cerr << "path = " << path << "\n";
// path = root + location.path
// we will tack on an index if there is a valid one
// or autoindex if allowed
// or let _get_file sort out the error otherwise.
if (path_is_valid(path) == 1)
{
// std::cout << "path is valid\n";
if (path[path.size() - 1] != '/')
path.push_back('/');
for (size_t i = 0; i < client->assigned_location->index.size(); i++)
{
// std::cout << "location path: " << client->assigned_location->path << '\n';
// std::cout << "location index: " << client->assigned_location->index[i] << '\n';
// std::cout << "path with index: " << path + assigned_location->index[i] << '\n';
if (path_is_valid(path + client->assigned_location->index[i]) == 2)
{
// std::cout << "found a valid index\n";
path.append(client->assigned_location->index[i]);
_get_file(client, path);
return ;
}
}
if (client->assigned_location->autoindex == true)
{
_autoindex(client, path);
return ;
}
}
// else
// _get_file(client, path);
// what about cgi ???
// TMP HUGO
//
if (_is_cgi(client))
{
_exec_cgi(client);
return;
}
//
// END TMP HUGO
_get_file(client, path);
}
# define MAX_FILESIZE 1000000 // (1Mo)
void Webserv::_get_file(Client *client, const std::string &path)
{
/*
std::ios::binary
https://gcc.gnu.org/onlinedocs/libstdc++/manual/fstreams.html#std.io.filestreams.binary
tldr : its seems to not be so simple to do read/write of binary file in a portable way.
*/
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";
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), R_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ifd.open(path.c_str(), std::ios::ate);
if (!ifd)
{
std::cerr << path << ": ifd.open fail" << '\n';
client->status = 500;
}
else
{
std::streampos size = ifd.tellg();
// WIP : Chunk or not chunk (if filesize too big)
if (size > MAX_FILESIZE)
{
// Then chunk
client->status = 500; // WIP temp
std::cerr << "File too large for non chunk body\n";
return ;
}
ifd.seekg(0, std::ios::beg);
buf << ifd.rdbuf();
if (!ifd || !buf)
{
std::cerr << path << ": ifd.read fail" << '\n';
client->status = 500;
}
else
{
client->status = 200;
std::string file_ext = _determine_file_extension(path);
_append_body(client, buf.str(), file_ext);
}
}
}
// i only sort of need &path...
// def can improve but works for now...
//void Webserv::_autoindex(Client *client, LocationConfig &location, std::string &path)
void Webserv::_autoindex(Client *client, std::string &path)
{
// std::cout << "made it to _autoindex\n";
(void)path;
std::string dir_list;
DIR *dir;
struct dirent *ent;
// std::cout << "location root: " << client->assigned_location->root << " location path: "
// << client->assigned_location->path << '\n';
// if ((dir = opendir (path.c_str())) != NULL)
if ((dir = opendir ((client->assigned_location->root + client->assigned_location->path).c_str())) != NULL)
{
/* print all the files and directories within directory */
dir_list.append(AUTOINDEX_START);
dir_list.append(client->assigned_location->path);
dir_list.append(AUTOINDEX_MID1);
dir_list.append(client->assigned_location->path);
dir_list.append(AUTOINDEX_MID2);
while ((ent = readdir (dir)) != NULL)
{
if (strcmp(".", ent->d_name) == 0)
continue ;
dir_list.append("<a href=\"");
dir_list.append(client->assigned_location->path.c_str());
dir_list.append(ent->d_name);
dir_list.append("\">");
dir_list.append(ent->d_name);
dir_list.append("</a>");
dir_list.append("\r\n"); // is this right?
}
// <a href="http://nginx.org/">nginx.org</a>.<br/>
// <a href="/test/test_deeper/index1.html">index1.html</a>
// apparently this is more than good enough!
// <a href="/test/test_deeper/..">..</a>
dir_list.append(AUTOINDEX_END);
// std::cout << "\n\n" << dir_list << '\n';
closedir (dir);
_append_body(client, dir_list, "html");
}
else
{
// in theory not possible cuz we already checked...
/* could not open directory */
// perror ("");
std::cout << "could not open dir\n";
return ;
}
}

View File

@@ -0,0 +1,66 @@
#include "Webserv.hpp"
void Webserv::_post(Client *client)
{
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-post
*/
std::string path = client->get_path();
path.insert(0, client->assigned_location->root);
/* CGI Here ? */
_post_file(client, path);
}
void Webserv::_post_file(Client *client, const std::string &path)
{
std::ofstream ofd;
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)
{
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;
}
else
{
// Content-Length useless at this point ?
// Maybe usefull in _read_request() for rejecting too big content.
// Need to _determine_process_server() as soon as possible,
// like in _read_request() for stopping read if body is too big ?
ofd << client->get_body();
if (!ofd)
{
std::cerr << path << ": ofd.write fail" << '\n';
client->status = 500;
}
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
}
}
}

View File

@@ -2,19 +2,38 @@
#include "Webserv.hpp" #include "Webserv.hpp"
#define BUFSIZE 8192 #define BUFSIZE 8192
#define MAX_HEADER_SIZE 42000 // arbitrary
enum read_return
{
READ_IN_PROGRESS,
READ_COMPLETE,
READ_CLOSE,
};
void Webserv::_request(Client *client) void Webserv::_request(Client *client)
{ {
_read_request(client); int ret = _read_request(client);
if (g_last_signal) if (g_last_signal)
_handle_last_signal(); _handle_last_signal();
if (ret == READ_CLOSE)
{
_close_client(client->fd);
}
else if (ret == READ_COMPLETE)
{
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD);
}
} }
void Webserv::_read_request(Client *client) int Webserv::_read_request(Client *client) // Messy, Need refactoring
{ {
char buf[BUFSIZE+1]; char buf[BUFSIZE];
ssize_t ret; ssize_t ret;
std::cerr << "call recv()" << "\n" ;
ret = ::recv(client->fd, buf, BUFSIZE, 0); ret = ::recv(client->fd, buf, BUFSIZE, 0);
std::cerr << "recv() on fd(" << client->fd << ") returned = " << ret << "\n" ; std::cerr << "recv() on fd(" << client->fd << ") returned = " << ret << "\n" ;
if (ret == -1) if (ret == -1)
@@ -22,23 +41,65 @@ void Webserv::_read_request(Client *client)
std::perror("err recv()"); std::perror("err recv()");
std::cerr << "client ptr =" << client << "\n"; // DEBUG std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
_close_client(client->fd); return READ_CLOSE;
return ;
} }
if (ret == 0) // Not sure what to do in case of 0. Just close ? if (ret == 0)
{ {
_close_client(client->fd); std::cerr << "recv() read 0, then close client" << "\n"; // DEBUG
return ; return READ_CLOSE;
} }
/*
if (ret == BUFSIZE)
// send error like "request too long" to client
*/
buf[ret] = '\0'; client->raw_request.append(buf, ret);
client->raw_request.append(buf); if (!client->header_complete)
client->parse_request(); {
if (client->raw_request.find(CRLF CRLF) != std::string::npos)
{
// std::cerr << "Raw_request :\n|||||||||||||||||||||||||||||\n " << client->raw_request << "\n|||||||||||||||||||||||||||||\n"; // DEBUG
client->header_complete = true;
client->parse_request(); // TODO : split function to avoid useless parsing ?
if (client->status) // WIP, need to change client->parse_request() for status update
return READ_COMPLETE;
client->assigned_server = _determine_process_server(client);
client->assigned_location = _determine_location(*client->assigned_server, client->get_path());
if (client->get_version().compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0)
{ // TODO : move in Client parsing ?
client->status = 505;
return READ_COMPLETE;
}
if (!client->get_headers("Content-Length").empty()
&& ::atoi(client->get_headers("Content-Length").c_str()) > (int)client->assigned_server->client_body_limit)
{
client->status = 413;
return READ_COMPLETE;
}
}
else if (client->raw_request.size() > MAX_HEADER_SIZE)
{
client->status = 400;
return READ_COMPLETE;
}
}
else if (client->header_complete)
{
client->read_body_size += ret;
if (client->read_body_size > client->assigned_server->client_body_limit)
{
client->status = 413;
return READ_COMPLETE;
}
if ((int)client->read_body_size >= ::atoi(client->get_headers("Content-Length").c_str()))
{
client->parse_request(); // reparse for the body
return READ_COMPLETE;
}
}
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD);
if (client->header_complete && client->get_headers("Content-Type").empty() && client->get_headers("Content-Length").empty() )
{
return READ_COMPLETE;
}
return READ_IN_PROGRESS;
} }

View File

@@ -1,57 +1,60 @@
#include "Webserv.hpp" #include "Webserv.hpp"
enum send_return
{
SEND_IN_PROGRESS, // unused
SEND_COMPLETE,
SEND_CLOSE,
};
void Webserv::_response(Client *client) void Webserv::_response(Client *client)
{ {
client->status = 200; // default value int ret = _send_response(client);
ServerConfig &server = _determine_process_server(client);
_send_response(client, server);
if (g_last_signal) if (g_last_signal)
_handle_last_signal(); _handle_last_signal();
if (ret == SEND_CLOSE)
{
_close_client(client->fd);
}
else if (ret == SEND_COMPLETE)
{
if (client->get_headers("Connection") == "close")
_close_client(client->fd);
else
{
_epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD);
client->clear();
}
}
} }
void Webserv::_send_response(Client *client, ServerConfig &server) int Webserv::_send_response(Client *client)
{ {
ssize_t ret; ssize_t ret;
std::cerr << "send()\n"; std::cerr << "send()\n";
_append_base_headers(client); _append_base_headers(client);
_construct_response(client, server); if (!client->status)
_construct_response(client);
_insert_status_line(client); _insert_status_line(client);
if (client->status >= 400) if (client->status >= 400)
_error_html_response(client, server); _error_html_response(client);
std::cerr << "client->response.size() = " << client->response.size() << "\n"; // DEBUG
ret = ::send(client->fd, client->response.c_str(), client->response.size(), 0); ret = ::send(client->fd, client->response.c_str(), client->response.size(), 0);
if (ret == -1) if (ret == -1)
{ {
std::perror("err send()"); std::perror("err send()");
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
_close_client(client->fd); return SEND_CLOSE;
return ;
} }
std::cerr << "ret send() = " << ret << "\n"; // DEBUG
// Body send (WIP for working binary files) return SEND_COMPLETE;
if (client->body_size)
{
ret = ::send(client->fd, client->buf, client->body_size, 0);
if (ret == -1)
{
std::perror("err send()");
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
_close_client(client->fd);
return ;
}
}
if (client->get_headers("Connection") == "close")
_close_client(client->fd);
else
{
_epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD);
client->clear();
}
} }
void Webserv::_append_base_headers(Client *client) void Webserv::_append_base_headers(Client *client)
@@ -64,38 +67,53 @@ void Webserv::_append_base_headers(Client *client)
client->response.append("Connection: keep-alive" CRLF); client->response.append("Connection: keep-alive" CRLF);
} }
void Webserv::_construct_response(Client *client, ServerConfig &server) void Webserv::_construct_response(Client *client)
{ {
// TODO : Move this in read(), stop read if content too large // TODO : Move this in read(), stop read if content too large
if (client->get_body().size() > server.client_body_limit) if (client->get_body().size() > client->assigned_server->client_body_limit)
{ {
client->status = 413; client->status = 413;
return; return;
} }
LocationConfig &location = _determine_location(server, client->get_path()); /* if (client->assigned_location->redirect_status)
_process_method(client, server, location); {
// (for codes 301, 302, 303, 307, and 308)
client->status = client->assigned_location->redirect_status;
client->response.append("Location: ");
client->response.append(client->assigned_location->redirect_uri);
client->response.append(CRLF);
} */
if (client->get_path().find("redirect_test") != std::string::npos) // Test block
{ // Weird behavior. The web browser seems to wait for a complete response until timeout.
// (for codes 301, 302, 303, 307, and 308)
client->status = 307;
client->response.append("Location: ");
client->response.append("https://www.rfc-editor.org/rfc/rfc3875#section-3.3");
client->response.append(CRLF);
client->response.append(CRLF);
return ;
}
_process_method(client);
} }
void Webserv::_process_method(Client *client, ServerConfig &server, LocationConfig &location) void Webserv::_process_method(Client *client)
{ {
unsigned int allow_methods = ANY_METHODS; // TEMP VARIABLE std::cerr << "assigned_location->path = " << client->assigned_location->path << "\n"; // debug
// after update in ConfigParser, use the "allow_methods" of location. std::cerr << "allow_methods = " << client->assigned_location->allow_methods << "\n"; // debug
// TODO in ConfigParser : by default if no field in config file, "allow_methods" must be set to ANY_METHODS
if (client->get_method() == UNKNOWN) if (client->get_method() == UNKNOWN)
{ {
client->status = 501; client->status = 501;
} }
else if (allow_methods & client->get_method()) else if (client->assigned_location->allow_methods & client->get_method())
{ {
switch (client->get_method()) switch (client->get_method())
{ {
case (GET): case (GET):
_get(client, server, location); break; _get(client); break;
case (POST): case (POST):
_post(client, server, location); break; _post(client); break;
case (DELETE): case (DELETE):
_delete(client, server, location); break; _delete(client); break;
default: default:
break; break;
} }
@@ -104,7 +122,7 @@ void Webserv::_process_method(Client *client, ServerConfig &server, LocationConf
{ {
client->status = 405; client->status = 405;
client->response.append("Allow: "); client->response.append("Allow: ");
client->response.append(::http_methods_to_str(allow_methods)); client->response.append(::http_methods_to_str(client->assigned_location->allow_methods));
client->response.append(CRLF); client->response.append(CRLF);
} }
} }
@@ -119,241 +137,43 @@ void Webserv::_insert_status_line(Client *client)
client->response.insert(0, status_line); client->response.insert(0, status_line);
} }
void Webserv::_error_html_response(Client *client, ServerConfig &server) void Webserv::_error_html_response(Client *client)
{ {
if (server.error_pages[client->status].empty()) if (!client->assigned_server || client->assigned_server->error_pages[client->status].empty())
{ {
std::string html_page = HTML_ERROR; std::string html_page = HTML_ERROR;
::replace_all_substr(html_page, STATUS_PLACEHOLDER, _http_status[client->status]); ::replace_all_substr(html_page, STATUS_PLACEHOLDER, _http_status[client->status]);
_append_body(client, html_page.c_str(), html_page.size(), "html"); _append_body(client, html_page, "html");
} }
else else
_get_file(client, server.error_pages[client->status]); _get_file(client, client->assigned_server->error_pages[client->status]);
} }
#define INDEX "index.html" // temp wip void Webserv::_append_body(Client *client, const std::string &body, const std::string &file_extension)
void Webserv::_get(Client *client, ServerConfig &server, LocationConfig &location)
{ {
(void)server; // To remove from arg if we determine its useless const std::string &mime_type = _mime_types[file_extension];
std::string path = client->get_path();
if (path == "/") // TODO : index and autoindex client->response.append("Content-Type: ");
path.append(INDEX); if (mime_type.empty())
path.insert(0, location.root); client->response.append(MIME_TYPE_DEFAULT);
std::cerr << "path = " << path << "\n";
// TMP HUGO
//
if (_is_cgi(client))
{
_exec_cgi(client);
return;
}
//
// END TMP HUGO
_get_file(client, path);
}
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() ?
// char buf[MAX_FILESIZE+1];
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), R_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ifd.open(path.c_str(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?)
if (!ifd)
{
std::cerr << path << ": ifd.open fail" << '\n';
client->status = 500;
}
else else
{ {
std::streampos size = ifd.tellg();
// WIP : Chunk or not chunk (if filesize too big)
if (size > MAX_FILESIZE)
{
// Then chunk
client->status = 500; // WIP temp
std::cerr << "File too large for non chunk body\n";
ifd.close();
return ;
}
ifd.seekg(0, std::ios::beg);
ifd.read(client->buf, size);
if (!ifd)
{
std::cerr << path << ": ifd.read fail" << '\n';
client->status = 500;
}
else
{
client->status = 200;
client->buf[ifd.gcount()] = '\0';
std::string file_ext = "";
size_t dot_pos = path.rfind(".");
std::cerr << "dot_pos = " << dot_pos << "\n";
if (dot_pos != std::string::npos && dot_pos + 1 < path.size())
file_ext = path.substr(dot_pos + 1);
std::cerr << "file_ext = " << file_ext << "\n";
client->body_size = ifd.gcount();
// WIP, pass empty body argument because append to string mess up binary file
_append_body(client, "", client->body_size, file_ext);
}
ifd.close();
}
}
void Webserv::_append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension)
{
/*
TODO : determine Content-Type
how ? read the body ?
or before in other way (like based and file extension) and pass here as argument ?
http://nginx.org/en/docs/http/ngx_http_core_module.html#types
Need to look "conf/mime.types" of nginx. Maybe make a map<> based on that.
*/
const std::string &mime_type = _mime_types[file_extension];
client->response.append("Content-Type: ");
client->response.append(mime_type); client->response.append(mime_type);
if (mime_type.find("text/") != std::string::npos) if (mime_type.find("text/") != std::string::npos)
client->response.append("; charset=UTF-8"); client->response.append("; charset=UTF-8");
client->response.append(CRLF); }
client->response.append(CRLF);
client->response.append("Content-Length: "); client->response.append("Content-Length: ");
std::string tmp = ::itos(body_size); std::string tmp = ::itos(body.size());
client->response.append(tmp); client->response.append(tmp);
client->response.append(CRLF); client->response.append(CRLF);
client->response.append(CRLF); client->response.append(CRLF);
client->response.append(body); client->response.append(body);
} }
void Webserv::_post(Client *client, ServerConfig &server, LocationConfig &location) ServerConfig *Webserv::_determine_process_server(Client *client)
{
(void)server; // To remove from arg if we determine its useless
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-post
*/
std::string path = client->get_path();
path.insert(0, location.root);
/* CGI Here ? */
_post_file(client, path);
}
void Webserv::_post_file(Client *client, const std::string &path)
{
std::ofstream ofd;
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)
{
std::perror("err access()");
client->status = 403;
return ;
}
ofd.open(path.c_str(), std::ios::binary | std::ios::trunc);
if (!ofd)
{
std::cerr << path << ": ofd.open fail" << '\n';
client->status = 500;
}
else
{
// Used body.size() so Content-Length useless at this point ?
// Maybe usefull in _read_request() for rejecting too big content.
// Need to _determine_process_server() as soon as possible,
// like in _read_request() for stopping read if body is too big ?
ofd.write(client->get_body().c_str(), client->get_body().size());
if (!ofd)
{
std::cerr << path << ": ofd.write fail" << '\n';
client->status = 500;
}
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
}
ofd.close();
}
}
void Webserv::_delete(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-delete
*/
std::string path = client->get_path();
path.insert(0, location.root);
/* CGI Here ? */
_delete_file(client, path);
}
void Webserv::_delete_file(Client *client, const std::string &path)
{
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
if (remove(path.c_str()) == -1)
{
std::perror("err remove()");
client->status = 500;
return ;
}
}
ServerConfig &Webserv::_determine_process_server(Client *client)
{ {
/* /*
http://nginx.org/en/docs/http/request_processing.html http://nginx.org/en/docs/http/request_processing.html
@@ -378,29 +198,71 @@ ServerConfig &Webserv::_determine_process_server(Client *client)
++it; ++it;
} }
if (it != _servers.end()) if (it != _servers.end())
return (*it); return (&(*it));
else else
return (*default_server); return (&(*default_server));
} }
LocationConfig &Webserv::_determine_location(ServerConfig &server, std::string &path) const LocationConfig *Webserv::_determine_location(const ServerConfig &server, const std::string &path) const
{ {
/* std::cout << "determin location path sent: " << path << '\n';
Assume there is at least one location in vector<LocationConfig> for path "/"
TODO in ConfigParser :
If no location block in config file, one need to be generated /// NO FUCKING IDEA WHY BUT...
for path "/", and filled with fields "root" and "index" based on parent server block // basically if 2 strings are identical to a point, compare from
// longer one or it'll freak out cuz of \0 or something idk
//// Basically: str.compare() from the larger string...
/* RULES ***
If a path coresponds exactly to a location, use that one
if no path coresponds then use the most correct one
most correct means the most precise branch that is still above
the point we are aiming for
*/ */
std::vector<LocationConfig>::iterator it = server.locations.begin(); std::vector<LocationConfig>::const_iterator best;
while (it != server.locations.end()) std::cout << "\nMade it to weird location picker case.\n";
for (std::vector<LocationConfig>::const_iterator it = server.locations.begin(); it != server.locations.end(); it++)
{ {
if (it->path.compare(0, path.size(), path)) std::cout << it->path << " -- ";
break; // if (rit->path.size() > path.size())
++it; /* if ((rit->path[rit->path.size() - 1] == '/' ? rit->path.size() : rit->path.size() - 1) > path.size())
{
std::cout << "skipping this one\n";
continue ;
}
*/
// OK I REALLY DON"T LOVE THIS PART, BUT IT DOES WORK FOR NOW...
// if (it->path[it->path.size() - 1] == '/'
// && it->path.compare(0, it->path.size(), path + "/") == 0)
// HOLD ON THIS MIGHT BE GOOD, BUT I COULD USE SOME HELP...
if (it->path[it->path.size() - 1] == '/'
&& it->path.compare(0, it->path.size() - 1, path) == 0)
{
best = it;
std::cout << "Picked a best! 1\n";
}
// int comp = path.compare(0, rit->path.size(), rit->path);
//int comp = rit->path.compare(0, rit->path.size() - 1, path);
// std::cout << "rit path size: " << rit->path.size() << " comp: " << comp << '\n';
// if (rit->path.compare(0, rit->path.size(), path) == 0)
// if (comp == 0)
if (path.compare(0, it->path.size(), it->path) == 0)
{
best = it;
std::cout << "Picked a best! 2\n";
}
} }
if (it != server.locations.end()) return (&(*best));
return (*it); }
else
return (server.locations.front()); std::string Webserv::_determine_file_extension(const std::string &path) const
{
size_t dot_pos = path.rfind(".");
if (dot_pos != std::string::npos && dot_pos + 1 < path.size())
return ( path.substr(dot_pos + 1) );
return (std::string(""));
} }

View File

@@ -34,17 +34,34 @@ void Webserv::run()
i = 0; i = 0;
while (i < nfds) while (i < nfds)
{ {
// TODO : handle EPOLLERR and EPOLLHUP try
it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd); {
if (it_socket != _listen_sockets.end() && events[i].events & EPOLLIN) // TODO : handle EPOLLERR and EPOLLHUP
_accept_connection(*it_socket); it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd);
else if (events[i].events & EPOLLIN) if (it_socket != _listen_sockets.end() && events[i].events & EPOLLIN)
_request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) ); _accept_connection(*it_socket);
else if (events[i].events & EPOLLOUT) else if (events[i].events & EPOLLIN)
_response( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) ); _request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
++i; else if (events[i].events & EPOLLOUT)
if (!g_run) _response( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
++i;
if (!g_run)
break;
}
catch (const std::bad_alloc& e)
{
std::cerr << e.what() << '\n';
_close_all_clients();
/* Swap to free the memory
From : http://www.uml.org.cn/c%2B%2B/pdf/EffectiveSTL.pdf#page=66 */
std::vector<Client>().swap(_clients);
break; break;
}
catch (const std::exception& e)
{
std::cerr << e.what() << '\n';
++i;
}
} }
} }
} }

4
stylesheet/style.css Normal file
View File

@@ -0,0 +1,4 @@
h1 {
color: red;
text-align: center;
}

View File

@@ -8,4 +8,4 @@
<hr> <hr>
<p style="text-align:center">(˚3˚)</p> <p style="text-align:center">(˚3˚)</p>
</body> </body>
</html> </html>

14
www/test/index1.html Normal file
View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>Webserv test index</title>
<!-- <link rel="stylesheet" href="stylesheet/style.css"> -->
<!-- <link rel="stylesheet" href="/stylesheet/style.css"> -->
</head>
<body>
<h1>Webserv Test Index</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>

11
www/test/something.html Normal file
View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Webserv test Something</title>
</head>
<body>
<h1 style="text-align:center">Webserv Test Something</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Webserv Test Deeper Index</title>
</head>
<body>
<h1 style="text-align:center">Webserv Test Deeper Index</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Webserv test deeper Something</title>
</head>
<body>
<h1 style="text-align:center">Webserv Test Deeper Something</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>

View File

@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Webserv test deeper Something</title>
</head>
<body>
<h1 style="text-align:center">Webserv Test Super Deep Something</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>