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

This commit is contained in:
hugogogo
2022-08-06 11:35:12 +02:00
40 changed files with 1026 additions and 1009 deletions

View File

@@ -11,10 +11,14 @@ CXXFLAGS += -MMD -MP #header dependencie
VPATH = $(SRCS_D)
HEADERS_D = srcs
HEADERS_D = srcs \
srcs/webserv \
srcs/config
SRCS_D = srcs \
srcs/webserv
srcs/webserv \
srcs/config
SRCS = main.cpp \
base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \
accept.cpp request.cpp response.cpp \

View File

@@ -12,6 +12,12 @@ server {
index index.html; # this is another comment
root ./www/;
# If not explicitly set, ConfigParser need to genererate a location block
# like this for path "/" (based on field "root" and "index" of the server)
location / {
root ./www/;
index index.html;
}
allow_methods GET;
}

View File

@@ -1,7 +1,21 @@
- un thread par serveur présent dans le fichier de config ?
----------------
- 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
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)}
- add headers "Date" and "Last-Modified" to response
- change "std::string" to reference "std::string &" in most functions
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.
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.
----------------
__________________________
--------------------------
----Discord 42------------
Un truc cool et surtout bien utile ici c'est d'utiliser un proxy entre ton navigateur et ton serveur pour vérifier ce qui est envoyé en raw. Les navigateurs peuvent avoir des comportements différents.
Vous avez des modules sur vos navigateur ou des logiciels externe. C'est assez rapide et gratuit.

View File

@@ -1,11 +1,13 @@
#include "Client.hpp"
char Client::buf[MAX_FILESIZE+1];
/*********************************************
* COPLIENS
*********************************************/
Client::Client( ) {
Client::Client() : fd(0), body_size(0), status(0) {
return;
}
@@ -35,7 +37,7 @@ void Client::parse_request()
std::vector<std::string> list;
size_t pos;
pos = (raw_request).find("\r\n\r\n");
pos = (raw_request).find(CRLF CRLF);
sub = (raw_request).substr(0, pos);
list = split(sub, '\n');
// request_line
@@ -45,15 +47,33 @@ void Client::parse_request()
_parse_request_headers(list);
//body- message
_parse_request_body(pos + 4);
// add "raw_request.clear()" after parsing ? for little less memory usage ?
}
void Client::clear()
{
clear_request();
raw_request.clear();
response.clear();
body_size = 0;
status = 0;
}
void Client::clear_request()
{
_request.method = UNKNOWN;
_request.path.clear();
_request.version.clear();
_request.headers.clear();
_request.body.clear();
}
std::string Client::get_method() { return _request.method; }
std::string Client::get_path() { return _request.path; }
std::string Client::get_version() { return _request.version; }
std::string Client::get_body() { return _request.body; }
std::string Client::get_headers(std::string key) { return _request.headers[key]; }
http_method Client::get_method() { return _request.method; }
std::string &Client::get_path() { return _request.path; }
std::string &Client::get_version() { return _request.version; }
std::string &Client::get_body() { return _request.body; }
std::string &Client::get_headers(const std::string &key) { return _request.headers[key]; }
/*********************************************
* PRIVATE MEMBER FUNCTIONS
@@ -73,7 +93,7 @@ void Client::_parse_request_line( std::string rline )
// method
tmp = ::trim(sline[0], ' ');
tmp = ::trim(tmp, '\r');
_request.method = tmp;
_request.method = str_to_http_method(tmp);
// TODO uri in request_line
// https://www.rfc-editor.org/rfc/rfc7230#section-5.3
// https://stackoverflow.com/questions/40311306/when-is-absoluteuri-used-from-the-http-request-specs
@@ -113,4 +133,15 @@ void Client::_parse_request_body( size_t pos )
_request.body = body;
}
/*********************************************
* OVERLOAD
*********************************************/
bool operator==(const Client& lhs, const Client& rhs)
{ return lhs.fd == rhs.fd; }
bool operator==(const Client& lhs, int fd)
{ return lhs.fd == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.fd; }

View File

@@ -10,13 +10,14 @@
struct Request
{
std::map<std::string, std::string> headers;
std::string method;
http_method method;
std::string path;
std::string version;
std::map<std::string, std::string> headers;
std::string body;
};
# define MAX_FILESIZE 1000000 // (1Mo)
class Client
{
public:
@@ -28,15 +29,21 @@ class Client
int fd;
std::string raw_request;
std::string response;
static char buf[MAX_FILESIZE+1];
size_t body_size;
unsigned int status;
listen_socket *lsocket;
std::string get_method();
std::string get_path();
std::string get_version();
std::string get_body();
std::string get_headers(std::string key);
// const functions ?
http_method get_method();
std::string &get_path();
std::string &get_version();
std::string &get_body();
std::string &get_headers(const std::string &key);
void parse_request();
void clear();
void clear_request();
private:
struct Request _request;

View File

@@ -1,15 +0,0 @@
#ifndef METHODTYPE_HPP
# define METHODTYPE_HPP
enum MethodType
{
GET,
POST,
DELETE,
INVALID,
};
#endif

View File

@@ -1,21 +0,0 @@
#ifndef SERVER_HPP
# define SERVER_HPP
# include <iostream>
# include <string>
class Server
{
public:
// Server(Placeholder);
// Server();
// Server(Server const &src);
// ~Server();
// Server &operator=(Server const &rhs);
private:
};
#endif

View File

@@ -1,447 +0,0 @@
#include "Webserv.hpp"
int g_last_signal;
bool g_run;
void signal_handler(int signum)
{
g_last_signal = signum;
}
Webserv::Webserv()
{
std::cerr << "Server init\n";
_epfd = ::epoll_create1(0); // (EPOLL_CLOEXEC) for CGI fork ?
if (_epfd == -1)
{
std::perror("err epoll_create1()");
throw std::runtime_error("Epoll init");
}
std::signal(SIGPIPE, signal_handler);
std::signal(SIGINT, signal_handler);
}
/* Webserv::Webserv(Webserv const &src)
{
} */
Webserv::~Webserv()
{
close(_socket_fd);
close(_epfd);
_close_all_clients();
std::cerr << "Server destroyed\n";
}
/* Webserv & Webserv::operator=(Webserv const &rhs)
{
} */
///////////////
// Functions //
void Webserv::init_virtual_servers() // ADD config param
{
_socket_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // (SOCK_CLOEXEC) for CGI fork ?
if (_socket_fd == -1)
{
std::perror("err socket()");
throw std::runtime_error("Socket init");
}
// HUGO ADD
// allow socket descriptor to be reuseable
int on = 1;
if (setsockopt(_socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
{
::perror("setsockopt() failed");
throw std::runtime_error("Socket init");
}
// HUGO ADD END
_bind(_socket_fd, 4040);
_listen(_socket_fd, 512); // 512 arbitrary
if (_epoll_update(_socket_fd, EPOLLIN, EPOLL_CTL_ADD) == -1)
throw std::runtime_error("Socket init");
}
void Webserv::start()
{
std::cerr << "Server started\n";
struct epoll_event events[MAX_EVENTS];
int nfds;
int i;
int count_loop = 0;
g_run = true;
while (g_run)
{
std::cerr << ++count_loop << "----loop epoll()\n";
nfds = ::epoll_wait(_epfd, events, MAX_EVENTS, TIMEOUT);
if (nfds == -1)
{
std::perror("err epoll_wait(): ");
throw std::runtime_error("Epoll wait");
}
else if (nfds == 0)
{
if (!_clients.empty())
{
std::cerr << "Timeout " << TIMEOUT << "ms\n";
_close_all_clients();
}
}
i = 0;
while (i < nfds)
{
// if ((events[i].data.u32 == SERVER_FD) && (events[i].events & EPOLLIN)) // Dont work, see "SERVER_FD" define
if ((events[i].data.fd == _socket_fd) && (events[i].events & EPOLLIN))
_accept_connection(events[i].data.fd);
else if (events[i].events & EPOLLIN)
_read_request(static_cast<Client*>(events[i].data.ptr));
else if (events[i].events & EPOLLOUT)
_send_response(static_cast<Client*>(events[i].data.ptr));
++i;
_actual_client = NULL;
}
}
}
///////////////////////
// Private Functions //
void Webserv::_accept_connection(int fd)
{
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
std::cerr << "accept()\n";
addr_len = sizeof addr;
accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len);
if (accepted_fd == -1)
{
std::perror("err accept(): ");
return ;
}
::fcntl(accepted_fd, F_SETFL, O_NONBLOCK);
_clients.push_back(Client());
_clients.back().fd = accepted_fd;
_epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD, &_clients.back());
}
//////////
// READ //
void Webserv::_read_request(Client *client)
{
char buf[BUFSIZE+1];
ssize_t ret;
_actual_client = client;
std::cerr << "recv()\n";
ret = ::recv(client->fd, buf, BUFSIZE, 0);
if (ret == -1)
{
std::perror("err recv(): ");
// if (g_last_signal)
// _handle_last_signal();
// else
// _close_client(client->fd);
std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
return ;
}
/*
if (ret == BUFSIZE)
// send error like "request too long" to client
*/
buf[ret] = '\0';
client->raw_request.append(buf);
// HUGO TMP
//
_parse_request(client);
//
// HUGO TMP END
_epoll_update(client->fd, EPOLLOUT, EPOLL_CTL_MOD, client);
}
///////////
// WRITE //
void Webserv::_send_response(Client *client)
{
ssize_t ret;
_actual_client = client;
std::cerr << "send()\n";
std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n";
// TMP HUGO test cgi
//
// if "POST" found in _buf, execve a cgi
if (client->raw_request.find("POST") != std::string::npos)
_exec_cgi_script(client);
// if "index.html" found in _buf, send the page
if (client->raw_request.find("index.html") != std::string::npos)
_serve_file(client, "index.html");
//
// TMP HUGO end test cgi
ret = ::send(client->fd, MSG_TEST, sizeof MSG_TEST - 1, 0);
if (ret == -1)
{
std::perror("err send(): ");
if (g_last_signal)
_handle_last_signal();
// else
// _close_client(client->fd);
return ;
}
_close_client(client->fd);
// if (client->raw_request.find("Connection: keep-alive") == std::string::npos)
// _close_client(client->fd);
// else
// _epoll_update(client->fd, EPOLLIN, EPOLL_CTL_MOD, client);
client->raw_request.clear();
}
////////////////////
// Misc functions //
int Webserv::_epoll_update(int fd, uint32_t events, int op)
{
struct epoll_event ev;
std::memset(&ev, 0, sizeof ev);
ev.events = events;
ev.data.fd = fd;
if (::epoll_ctl(_epfd, op, fd, &ev) == -1)
{
std::perror("err _epoll_update(): ");
return (-1);
}
return (0);
}
// TMP HUGO
//
void _parse_request(Client *client)
{
std::string request = client->raw_request;
std::map<std::string, std::string> field = client->request;
// size_t size = request.size();
size_t begin = 0;
size_t end;
size_t len;
// std::string str ("test un: deux\ntest deux: trois quatre\ntest :trois quatre cinq");
// std::string sub;
std::cout << str << "\n\n";
int i = 0;
while (end != std::string::npos)
{
// find first portion, before ':'
end = str.find(':', begin);
len = end - begin;
if (end == std::string::npos)
len = end;
sub = str.substr(begin, len);
std::cout << i << "|" << sub << "\n";
// std::cout << "[begin:" << begin << " - end:" << end << " - len:" << len << "] " << sub << "\n";
begin = end + 1;
// find second portion, until '\n'
end = str.find('\n', begin);
len = end - begin;
if (end == std::string::npos)
len = end;
sub = str.substr(begin, len);
std::cout << i << "|" << sub << "\n";
begin = end + 1;
i++;
}
// for (size_t i = 0; i < size; i++)
// {
// field.insert(request);
// }
// GET /home.html HTTP/1.1
// Host: developer.mozilla.org
// User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0
// Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
// Accept-Language: en-US,en;q=0.5
// Accept-Encoding: gzip, deflate, br
// Referer: https://developer.mozilla.org/testpage.html
// Connection: keep-alive
// Upgrade-Insecure-Requests: 1
// If-Modified-Since: Mon, 18 Jul 2016 02:36:04 GMT
// If-None-Match: "c561c68d0ba92bbeb8b0fff2a9199f722e3a621a"
// Cache-Control: max-age=0
}
void Webserv::_serve_file(Client *client, std::string page)
{
int page_fd;
std::string to_send;
std::string end_header = "\r\n\r\n";
std::string body;
std::stringstream strs;
char buffer[1];
to_send = "HTTP/1.1 200 OK\n";
to_send += "Content-Type: text/html;\n";
to_send += "Content-Length: ";
page_fd = open(page.c_str(), O_RDONLY);
for (int ret = 1; ret > 0;)
{
ret = read(page_fd, buffer, 1);
body += buffer;
}
strs << body.size();
to_send += strs.str();
to_send += end_header;
to_send += body;
if (::send(client->fd, to_send.c_str(), to_send.size(), 0) == -1)
std::perror("err send()");
}
void Webserv::_exec_cgi_script(Client *client)
{
int save_stdout;
char** env = new char*[5];
char * const * nll = NULL;
// set env
env[0] = strdup("PATH_INFO=/no");
env[1] = strdup("REQUEST_METHOD=POST");
env[2] = strdup("SERVER_PROTOCOL=HTTP/1.1");
env[3] = strdup("CONTENT_LENGTH=665");
env[4] = NULL;
// save STDOUT
save_stdout = dup(STDOUT_FILENO);
// inside child process
if (fork() == 0)
{
dup2(client->fd, STDOUT_FILENO);
// execve("./srcs/cgi-bin/cgi_cpp.cgi", nll, env);
execve("./srcs/cgi-bin/php-cgi", nll, env);
}
// inside parent process
else
waitpid(-1, NULL, 0);
// restore stdout
dup2(save_stdout, STDOUT_FILENO);
}
//
// END TMP HUGO
int Webserv::_epoll_update(int fd, uint32_t events, int op, void *ptr)
{
struct epoll_event ev;
std::memset(&ev, 0, sizeof ev);
ev.events = events;
ev.data.ptr = ptr;
if (::epoll_ctl(_epfd, op, fd, &ev) == -1)
{
std::perror("err _epoll_update(): ");
return (-1);
}
return (0);
}
void Webserv::_handle_last_signal()
{
if (g_last_signal == SIGPIPE)
{
std::cerr << "SIGPIPE\n";
if (_actual_client)
{
_close_client(_actual_client->fd);
_actual_client = NULL;
}
}
else if (g_last_signal == SIGINT)
{
g_run = false;
}
g_last_signal = 0;
}
void Webserv::_close_client(int fd)
{
std::vector<Client>::iterator it = _clients.begin();
while (it != _clients.end())
{
if (it->fd == fd)
{
// _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(fd) == -1)
std::perror("err close(): ");
else
std::cerr << "close fd " << fd << "\n";
_clients.erase(it);
break;
}
++it;
}
}
void Webserv::_close_all_clients()
{
while (!_clients.empty())
{
// _epoll_update(_clients.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(_clients.back().fd) == -1)
std::perror("err close(): ");
else
std::cerr << "close fd " << _clients.back().fd << "\n";
_clients.pop_back();
}
}
////////////////////
// Init functions //
void Webserv::_bind(int socket_fd, in_port_t port)
{
// cast invalid ? how to ?
// const struct sockaddr* cast_test = static_cast<const struct sockaddr*>(addr);
struct sockaddr_in addr;
std::memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_port = ::htons(port);
addr.sin_addr.s_addr = ::htonl(INADDR_ANY); // htonl useless with 0 value (INADDR_ANY) ?
if (::bind(socket_fd, (const sockaddr*)&addr, sizeof addr) == -1)
{
std::perror("err bind(): ");
throw std::runtime_error("Socket bind");
}
}
void Webserv::_listen(int socket_fd, unsigned int max_connections)
{
if (::listen(socket_fd, max_connections) == -1)
{
std::perror("err listen(): ");
throw std::runtime_error("Socket listen");
}
}

View File

@@ -6,7 +6,7 @@
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/13 22:11:17 by me #+# #+# */
/* Updated: 2022/07/31 13:18:14 by simplonco ### ########.fr */
/* Updated: 2022/08/03 17:51:35 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
@@ -262,8 +262,8 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
MethodType m = _str_to_method_type(tmp_val[i]);
if (m == 3)
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);
}
@@ -347,8 +347,8 @@ void ConfigParser::_set_location_values(LocationConfig *location, \
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
MethodType m = _str_to_method_type(tmp_val[i]);
if (m == 3)
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
location->allow_methods.push_back(m);
}

View File

@@ -3,10 +3,10 @@
/* ::: :::::::: */
/* ConfigParser.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/11 23:01:41 by me #+# #+# */
/* Updated: 2022/07/27 19:27:57 by me ### ########.fr */
/* Updated: 2022/08/03 17:32:33 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
@@ -15,7 +15,6 @@
# include "ServerConfig.hpp"
# include "LocationConfig.hpp"
# include "MethodType.hpp"
# include "utils.hpp"
# include <map>
@@ -76,8 +75,6 @@ private:
std::string _get_rest_of_line(size_t *curr); // const?
// why static? it's an enum...
static MethodType _str_to_method_type(std::string str);

View File

@@ -82,20 +82,6 @@ std::string ConfigParser::_get_rest_of_line(size_t *curr)
return (values);
}
MethodType ConfigParser::_str_to_method_type(std::string str)
{
if (str == "GET")
return GET;
else if (str == "POST")
return POST;
else if (str == "DELETE")
return DELETE;
return INVALID;
}
void ConfigParser::_print_content() const
{
std::cout << _content;

View File

@@ -3,18 +3,16 @@
/* ::: :::::::: */
/* LocationConfig.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/23 16:08:00 by me #+# #+# */
/* Updated: 2022/07/25 20:09:48 by me ### ########.fr */
/* Updated: 2022/08/02 14:06:07 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef LOCATIONCONFIG_HPP
# define LOCATIONCONFIG_HPP
# include "MethodType.hpp"
# include <map>
# include <vector>
# include <string>
@@ -31,7 +29,7 @@ public:
int client_body_limit;
std::string root;
std::vector<std::string> index;
std::vector<MethodType> allow_methods;
std::vector<http_method> allow_methods;
std::map<std::string, std::string> cgi_info;
// wait if i can call several times, shouldn't it be a map?

View File

@@ -2,7 +2,7 @@
#ifndef SERVERCONFIG_HPP
# define SERVERCONFIG_HPP
# include "MethodType.hpp"
# include "utils.hpp"
# include "LocationConfig.hpp"
# include <map>
@@ -30,7 +30,7 @@ public:
// there can only be one.
std::string root;
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;
@@ -42,7 +42,7 @@ public:
// 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<MethodType> allow_methods;
std::vector<http_method> allow_methods;
std::vector<LocationConfig> locations;

View File

@@ -1,13 +0,0 @@
# include <sstream>
# include <string.h>
char* itoa(int n)
{
std::stringstream strs;
char * str;
strs << n;
str = (char*)(strs.str().c_str());
return (str);
}

View File

@@ -52,3 +52,56 @@ bool isNumeric_btw(int low, int high, std::string str)
return true;
}
http_method str_to_http_method(std::string &str)
{
if (str == "GET")
return GET;
else if (str == "POST")
return POST;
else if (str == "DELETE")
return DELETE;
return UNKNOWN;
}
std::string http_methods_to_str(unsigned int methods)
{
std::string str;
if (methods & GET)
str.append("GET");
if (methods & POST)
{
if (!str.empty())
str.append(", ");
str.append("POST");
}
if (methods & DELETE)
{
if (!str.empty())
str.append(", ");
str.append("DELETE");
}
return (str);
}
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr)
{
if (ori_substr.empty())
return;
size_t pos = 0;
while (1)
{
pos = str.find(ori_substr, pos);
if (pos == std::string::npos)
break;
str.replace(pos, ori_substr.size(), new_substr);
pos += new_substr.size();
}
}
bool operator==(const listen_socket& lhs, int fd)
{ return lhs.fd == fd; }
bool operator==(int fd, const listen_socket& rhs)
{ return fd == rhs.fd; }

View File

@@ -7,10 +7,44 @@
# include <sstream>
# include <cstdlib> // atoi
std::vector<std::string> split(std::string input, char delimiter);
bool isNumeric(std::string str);
bool isNumeric_btw(int low, int high, std::string str);
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
// enum http_method
// {
// UNKNOWN = 0b00000000,
// GET = 0b00000001,
// POST = 0b00000010,
// DELETE = 0b00000100,
// ANY_METHODS = 0b11111111,
// };
enum http_method
{
UNKNOWN = 0b0,
GET = 1 << 0,
POST = 1 << 1,
DELETE = 1 << 2,
ANY_METHODS = 0b11111111,
};
struct listen_socket
{
int fd;
std::string host;
std::string port;
};
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);
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);
http_method str_to_http_method(std::string &str);
std::string http_methods_to_str(unsigned int methods);
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr);
#endif

View File

@@ -22,37 +22,18 @@
// # include <netinet/ip.h> // usefull for what ? -> 'man (7) ip' says it's a superset of 'netinet/in.h'
# include <algorithm> // find
# include <string> // string
# include <cstdio> // perror
# include <cstdio> // perror, remove
# include <cstdlib> // atoi (athough it's already cover by <string>)
# include "Client.hpp"
# include "ServerConfig.hpp"
# include "utils.hpp"
// TODO: A virer
//# include "ConfigParser.hpp"
//# include "LocationConfig.hpp"
//# include "MethodType.hpp"
//# include "utils.hpp"
// TODO: A virer
# include "http_status.hpp"
extern bool g_run;
extern int g_last_signal;
void signal_handler(int signum);
/* enum // WIP test
{
SERVER_FD = 1,
CLIENT_FD
};
struct s // WIP test
{
int fd;
Client *ptr;
};
*/
// these might only be TMP
# define FAILURE -1
# define SUCCESS 1
@@ -77,21 +58,39 @@ class Webserv
private:
int _epfd;
std::vector<int> _listen_sockets;
std::vector<listen_socket> _listen_sockets;
std::vector<ServerConfig> _servers;
std::vector<Client> _clients;
std::map<int, std::string> _http_status;
std::map<std::string, std::string> _mime_types;
// accept.cpp
void _accept_connection(int fd);
void _accept_connection(listen_socket &lsocket);
// request.cpp
void _request(Client *client);
void _read_request(Client *client);
// response.cpp
void _response(Client *client);
void _send_response(Client *client);
void _construct_response(Client *client);
void _send_response(Client *client, ServerConfig &server);
void _append_base_headers(Client *client);
void _construct_response(Client *client, ServerConfig &server);
void _process_method(Client *client, ServerConfig &server, LocationConfig &location);
void _insert_status_line(Client *client);
void _get_ressource(Client *client);
void _error_html_response(Client *client, ServerConfig &server);
void _append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension = "");
void _get(Client *client, ServerConfig &server, LocationConfig &location);
void _get_file(Client *client, const std::string &path);
void _post(Client *client, ServerConfig &server, LocationConfig &location);
void _post_file(Client *client, const std::string &path);
void _delete(Client *client, ServerConfig &server, LocationConfig &location);
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
bool _is_cgi(Client *client);
void _exec_cgi(Client *client);
@@ -107,9 +106,12 @@ class Webserv
// close.cpp
void _close_client(int fd);
void _close_all_clients();
void _close_all_listen_sockets();
// init.cpp
void _bind(int socket_fd, in_port_t port, std::string host);
void _listen(int socket_fd, unsigned int max_connections);
void _init_http_status_map();
void _init_mime_types_map();
};
#endif

View File

@@ -1,7 +1,7 @@
#include "Webserv.hpp"
void Webserv::_accept_connection(int fd)
void Webserv::_accept_connection(listen_socket &lsocket)
{
struct sockaddr_in addr;
socklen_t addr_len;
@@ -9,7 +9,7 @@ void Webserv::_accept_connection(int fd)
std::cerr << "accept()\n";
addr_len = sizeof addr;
accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len);
accepted_fd = ::accept(lsocket.fd, (sockaddr*)&addr, &addr_len);
if (accepted_fd == -1)
{
std::perror("err accept()");
@@ -21,6 +21,7 @@ void Webserv::_accept_connection(int fd)
_clients.push_back(Client());
_clients.back().fd = accepted_fd;
_clients.back().lsocket = &lsocket;
_epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD);
}

View File

@@ -12,6 +12,9 @@ Webserv::Webserv()
throw std::runtime_error("Epoll init");
}
_init_http_status_map();
_init_mime_types_map();
std::signal(SIGPIPE, signal_handler);
std::signal(SIGINT, signal_handler);
}
@@ -42,10 +45,9 @@ Webserv::Webserv(std::vector<ServerConfig>* servers)
Webserv::~Webserv()
{
//close(_socket_fd);
// TODO : CLOSE ALL _listen_sockets
close(_epfd);
_close_all_clients();
_close_all_listen_sockets();
std::cerr << "Server destroyed\n";
}

View File

@@ -40,7 +40,7 @@ char** Webserv::_set_env(Client *client)
env[8] = _dup_env("REMOTE_HOST", client->get_headers("Host")); // just test
env[9] = _dup_env("REMOTE_IDENT");
env[10] = _dup_env("REMOTE_USER");
env[11] = _dup_env("REQUEST_METHOD", client->get_method());
env[11] = _dup_env("REQUEST_METHOD", ::http_methods_to_str(client->get_method()));
env[12] = _dup_env("SCRIPT_NAME");
env[13] = _dup_env("SERVER_NAME");
env[14] = _dup_env("SERVER_PORT");

View File

@@ -6,7 +6,7 @@ void Webserv::_close_client(int fd)
std::vector<Client>::iterator it = _clients.begin();
while (it != _clients.end())
{
if (it->fd == fd)
if (*it == fd)
{
// _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(fd) == -1)
@@ -32,3 +32,16 @@ void Webserv::_close_all_clients()
_clients.pop_back();
}
}
void Webserv::_close_all_listen_sockets()
{
while (!_listen_sockets.empty())
{
// _epoll_update(_listen_sockets.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
if (::close(_listen_sockets.back().fd) == -1)
std::perror("err close()");
else
std::cerr << "close fd " << _listen_sockets.back().fd << "\n";
_listen_sockets.pop_back();
}
}

View File

@@ -0,0 +1,42 @@
#ifndef HTTP_STATUS_HPP
# define HTTP_STATUS_HPP
// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
/*
First version of macro HTML_ERROR(STATUS) dont work with call like this :
client->response.append( HTML_ERROR( _http_status[client->status].c_str() ) );
so I made the other version with dynamic replacement of STATUS .
*/
// # 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 STATUS_PLACEHOLDER "$STATUS"
# define HTML_ERROR \
"<!DOCTYPE html>"\
"<html>"\
"<head>"\
"<title>" STATUS_PLACEHOLDER "</title>"\
"</head>"\
"<body>"\
"<h1 style=\"text-align:center\">" STATUS_PLACEHOLDER "</h1>"\
"<hr>"\
"<p style=\"text-align:center\">Le Webserv/0.1</p>"\
"</body>"\
"</html>"
// When new status added, need to update _init_http_status_map()
# define S200 "200 OK"
# define S201 "201 Created"
# define S204 "204 No Content"
# define S400 "400 Bad Request"
# define S403 "403 Forbidden"
# define S404 "404 Not Found"
# define S405 "405 Method Not Allowed"
# define S413 "413 Content Too Large"
# define S500 "500 Internal Server Error"
# define S501 "501 Not Implemented"
#endif

View File

@@ -4,7 +4,9 @@
void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
{
int ret;
std::vector<int> _open_ports;
std::vector<std::string> open_sockets;
struct listen_socket new_socket;
std::string host_port;
_servers = *servers;
_listen_sockets.clear();
@@ -12,7 +14,11 @@ void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
while (it != _servers.end())
{
if ( std::find(_open_ports.begin(), _open_ports.end(), std::atoi(it->port.data()) ) != _open_ports.end() )
host_port.clear();
host_port.append(it->host);
host_port.append(":");
host_port.append(it->port);
if ( std::find(open_sockets.begin(), open_sockets.end(), host_port) != open_sockets.end() )
{
++it;
continue;
@@ -24,27 +30,31 @@ void Webserv::init_virtual_servers(std::vector<ServerConfig>* servers)
std::perror("err socket()");
throw std::runtime_error("Socket init");
}
new_socket.fd = ret;
new_socket.host = it->host;
new_socket.port = it->port;
_listen_sockets.push_back(new_socket);
// HUGO ADD
//
// allow socket descriptor to be reuseable
// I just copied it from https://www.ibm.com/docs/en/i/7.2?topic=designs-example-nonblocking-io-select
int on = 1;
if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
if (setsockopt(new_socket.fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
::perror("err setsockopt()");
throw std::runtime_error("Socket init");
}
//
// HUGO ADD END
_listen_sockets.push_back(ret);
_bind(_listen_sockets.back(), std::atoi(it->port.data()), it->host);
_listen(_listen_sockets.back(), 512); // 512 arbitrary
_bind(new_socket.fd, std::atoi(it->port.c_str()), it->host);
_listen(new_socket.fd, 512); // 512 arbitrary
if (_epoll_update(_listen_sockets.back(), EPOLLIN, EPOLL_CTL_ADD) == -1)
if (_epoll_update(new_socket.fd, EPOLLIN, EPOLL_CTL_ADD) == -1)
throw std::runtime_error("Socket init");
_open_ports.push_back(std::atoi(it->port.data()));
open_sockets.push_back(host_port);
++it;
}
}
@@ -76,3 +86,135 @@ void Webserv::_listen(int socket_fd, unsigned int max_connections)
throw std::runtime_error("Socket listen");
}
}
void Webserv::_init_http_status_map()
{
_http_status[200] = S200;
_http_status[201] = S201;
_http_status[204] = S204;
_http_status[400] = S400;
_http_status[403] = S403;
_http_status[404] = S404;
_http_status[405] = S405;
_http_status[413] = S413;
_http_status[500] = S500;
_http_status[501] = S501;
}
void Webserv::_init_mime_types_map()
{
_mime_types[""] = "application/octet-stream";
_mime_types["html"] = "text/html";
_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["txt"] = "text/plain";
_mime_types["jad"] = "text/vnd.sun.j2me.app-descriptor";
_mime_types["wml"] = "text/vnd.wap.wml";
_mime_types["htc"] = "text/x-component";
_mime_types["png"] = "image/png";
_mime_types["tif"] = "image/tiff";
_mime_types["tiff"] = "image/tiff";
_mime_types["wbmp"] = "image/vnd.wap.wbmp";
_mime_types["ico"] = "image/x-icon";
_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["jar"] = "application/java-archive";
_mime_types["war"] = "application/java-archive";
_mime_types["ear"] = "application/java-archive";
_mime_types["json"] = "application/json";
_mime_types["hqx"] = "application/mac-binhex40";
_mime_types["doc"] = "application/msword";
_mime_types["pdf"] = "application/pdf";
_mime_types["ps"] = "application/postscript";
_mime_types["eps"] = "application/postscript";
_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["exe"] = "application/octet-stream";
_mime_types["dll"] = "application/octet-stream";
_mime_types["deb"] = "application/octet-stream";
_mime_types["dmg"] = "application/octet-stream";
_mime_types["iso"] = "application/octet-stream";
_mime_types["img"] = "application/octet-stream";
_mime_types["msi"] = "application/octet-stream";
_mime_types["msp"] = "application/octet-stream";
_mime_types["msm"] = "application/octet-stream";
_mime_types["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
_mime_types["xlsx"] = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
_mime_types["pptx"] = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
_mime_types["mid"] = "audio/midi";
_mime_types["midi"] = "audio/midi";
_mime_types["kar"] = "audio/midi";
_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["3gp"] = "video/3gpp";
_mime_types["ts"] = "video/mp2t";
_mime_types["mp4"] = "video/mp4";
_mime_types["mpeg"] = "video/mpeg";
_mime_types["mpg"] = "video/mpeg";
_mime_types["mov"] = "video/quicktime";
_mime_types["webm"] = "video/webm";
_mime_types["flv"] = "video/x-flv";
_mime_types["m4v"] = "video/x-m4v";
_mime_types["mng"] = "video/x-mng";
_mime_types["asx"] = "video/x-ms-asf";
_mime_types["asf"] = "video/x-ms-asf";
_mime_types["wmv"] = "video/x-ms-wmv";
_mime_types["avi"] = "video/x-msvideo";
}

View File

@@ -3,95 +3,145 @@
void Webserv::_response(Client *client)
{
_send_response(client);
client->status = 200; // default value
ServerConfig &server = _determine_process_server(client);
_send_response(client, server);
if (g_last_signal)
_handle_last_signal();
}
void Webserv::_send_response(Client *client)
void Webserv::_send_response(Client *client, ServerConfig &server)
{
ssize_t ret;
std::cerr << "send()\n";
std::cerr << "RAW_REQUEST\n|\n" << client->raw_request << "|\n"; // DEBUG
_construct_response(client);
_append_base_headers(client);
_construct_response(client, server);
_insert_status_line(client);
if (client->status >= 400)
_error_html_response(client, server);
ret = ::send(client->fd, client->response.data(), client->response.size(), 0);
ret = ::send(client->fd, client->response.c_str(), client->response.size(), 0);
if (ret == -1)
{
std::perror("err send()");
std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.fd =" << client->fd << "\n"; // DEBUG
_close_client(client->fd);
return ;
}
if (client->raw_request.find("Connection: close") != std::string::npos)
// Body send (WIP for working binary files)
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->raw_request.clear();
client->response.clear();
client->clear();
}
}
void Webserv::_construct_response(Client *client)
void Webserv::_append_base_headers(Client *client)
{
client->status = 200;
client->response.append("Server: Webserv/0.1\r\n");
client->response.append("Server: Webserv/0.1" CRLF);
if (client->raw_request.find("Connection: close") != std::string::npos)
client->response.append("Connection: close\r\n");
if (client->get_headers("Connection") == "close")
client->response.append("Connection: close" CRLF);
else
client->response.append("Connection: keep-alive\r\n");
_get_ressource(client);
_insert_status_line(client);
client->response.append("Connection: keep-alive" CRLF);
}
void Webserv::_construct_response(Client *client, ServerConfig &server)
{
// TODO : Move this in read(), stop read if content too large
if (client->get_body().size() > server.client_body_limit)
{
client->status = 413;
return;
}
LocationConfig &location = _determine_location(server, client->get_path());
_process_method(client, server, location);
}
void Webserv::_process_method(Client *client, ServerConfig &server, LocationConfig &location)
{
unsigned int allow_methods = ANY_METHODS; // TEMP VARIABLE
// after update in ConfigParser, use the "allow_methods" of location.
// TODO in ConfigParser : by default if no field in config file, "allow_methods" must be set to ANY_METHODS
if (client->get_method() == UNKNOWN)
{
client->status = 501;
}
else if (allow_methods & client->get_method())
{
switch (client->get_method())
{
case (GET):
_get(client, server, location); break;
case (POST):
_post(client, server, location); break;
case (DELETE):
_delete(client, server, location); break;
default:
break;
}
}
else
{
client->status = 405;
client->response.append("Allow: ");
client->response.append(::http_methods_to_str(allow_methods));
client->response.append(CRLF);
}
}
#define E400 "\r\n<!DOCTYPE html><html><head><title>400 Bad Request</title></head><body><h1 style=\"text-align:center\">400 Bad Request</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
#define E404 "\r\n<!DOCTYPE html><html><head><title>404 Not Found</title></head><body><h1 style=\"text-align:center\">404 Not Found</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
#define E500 "\r\n<!DOCTYPE html><html><head><title>500 Internal Server Error</title></head><body><h1 style=\"text-align:center\">500 Internal Server Error</h1><hr><p style=\"text-align:center\">Le Webserv/0.1</p></body></html>"
void Webserv::_insert_status_line(Client *client)
{
std::string status_line;
status_line.append("HTTP/1.1 ");
// WIP, maybe make a map for status response
switch (client->status)
{
case (200):
status_line.append("200 OK");
break;
case (400):
status_line.append("400 Not Found");
client->response.append(E400);
break;
case (404):
status_line.append("404 Not Found");
client->response.append(E404);
break;
case (500):
status_line.append("500 Internal Server Error");
client->response.append(E500);
break;
}
status_line.append("\r\n");
status_line.append(_http_status[client->status]);
status_line.append(CRLF);
client->response.insert(0, status_line);
}
#define ROOT "website"
#define INDEX "index.html"
#define MAX_FILESIZE 1000000 // (1Mo)
void Webserv::_get_ressource(Client *client)
void Webserv::_error_html_response(Client *client, ServerConfig &server)
{
std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ?
char buf[MAX_FILESIZE+1];
std::string tmp;
if (server.error_pages[client->status].empty())
{
std::string html_page = HTML_ERROR;
::replace_all_substr(html_page, STATUS_PLACEHOLDER, _http_status[client->status]);
_append_body(client, html_page.c_str(), html_page.size(), "html");
}
else
_get_file(client, server.error_pages[client->status]);
}
#define INDEX "index.html" // temp wip
void Webserv::_get(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
std::string path = client->get_path();
if (path == "/") // TODO : index and autoindex
path.append(INDEX);
path.insert(0, location.root);
std::cerr << "path = " << path << "\n";
// TMP HUGO
//
@@ -103,41 +153,39 @@ void Webserv::_get_ressource(Client *client)
//
// END TMP HUGO
// Mini parsing à l'arrache du PATH
std::string path;
try
{
path = client->raw_request.substr(0, client->raw_request.find("\r\n"));
path = path.substr(0, path.rfind(" "));
path = path.substr(path.find("/"));
if (path == "/")
path.append(INDEX);
path.insert(0, ROOT);
}
catch (std::out_of_range& e)
{
std::cout << e.what() << '\n';
client->status = 400;
return ;
}
_get_file(client, path);
}
if (access(path.data(), R_OK) == -1)
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 ;
}
ifd.open(path.data(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?)
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 << ": open fail" << '\n';
std::cerr << path << ": ifd.open fail" << '\n';
client->status = 500;
}
else
{
// WIP : Chunk or not chunk (if filesize too big)
std::streampos size = ifd.tellg();
// WIP : Chunk or not chunk (if filesize too big)
if (size > MAX_FILESIZE)
{
// Then chunk
@@ -148,22 +196,211 @@ void Webserv::_get_ressource(Client *client)
}
ifd.seekg(0, std::ios::beg);
ifd.read(buf, size);
buf[ifd.gcount()] = '\0';
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';
client->response.append("Content-Type: text/html; charset=UTF-8\r\n");
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->response.append("Content-Length: ");
tmp = ::itos(ifd.gcount());
client->response.append(tmp.c_str());
client->response.append("\r\n");
// Body
client->response.append("\r\n");
client->response.append(buf);
client->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);
if (mime_type.find("text/") != std::string::npos)
client->response.append("; charset=UTF-8");
client->response.append(CRLF);
client->response.append("Content-Length: ");
std::string tmp = ::itos(body_size);
client->response.append(tmp);
client->response.append(CRLF);
client->response.append(CRLF);
client->response.append(body);
}
void Webserv::_post(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-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
_determine_process_server() should be complete.
TODO : test it
*/
std::string &server_name = client->get_headers("Host");
std::vector<ServerConfig>::iterator it = _servers.begin();
std::vector<ServerConfig>::iterator default_server = _servers.end();
while (it != _servers.end())
{
if (it->host == client->lsocket->host
&& it->port == client->lsocket->port)
{
if ( std::find(it->server_name.begin(), it->server_name.end(), server_name) != it->server_name.end() )
break;
else if (default_server == _servers.end())
default_server = it;
}
++it;
}
if (it != _servers.end())
return (*it);
else
return (*default_server);
}
LocationConfig &Webserv::_determine_location(ServerConfig &server, std::string &path)
{
/*
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
for path "/", and filled with fields "root" and "index" based on parent server block
*/
std::vector<LocationConfig>::iterator it = server.locations.begin();
while (it != server.locations.end())
{
if (it->path.compare(0, path.size(), path))
break;
++it;
}
if (it != server.locations.end())
return (*it);
else
return (server.locations.front());
}

View File

@@ -4,14 +4,6 @@
#define MAX_EVENTS 42 // arbitrary
#define TIMEOUT 3000
// Temp. To move in other file
bool operator==(const Client& lhs, const Client& rhs)
{ return lhs.fd == rhs.fd; }
bool operator==(const Client& lhs, int fd)
{ return lhs.fd == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.fd; }
void Webserv::run()
{
std::cerr << "Server started\n";
@@ -19,6 +11,7 @@ void Webserv::run()
int nfds;
int i;
int count_loop = 0;
std::vector<listen_socket>::iterator it_socket;
g_run = true;
while (g_run)
@@ -42,9 +35,9 @@ void Webserv::run()
while (i < nfds)
{
// TODO : handle EPOLLERR and EPOLLHUP
if ((std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd) != _listen_sockets.end())
&& (events[i].events & EPOLLIN))
_accept_connection(events[i].data.fd);
it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd);
if (it_socket != _listen_sockets.end() && events[i].events & EPOLLIN)
_accept_connection(*it_socket);
else if (events[i].events & EPOLLIN)
_request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
else if (events[i].events & EPOLLOUT)

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Le Webserv</title>
</head>
<body>
<h1 style="text-align:center">Le index (˘ ͜ʖ˘)</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>

View File

@@ -1,300 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!-- saved from url=(0057)https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html -->
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head profile="http://dublincore.org/documents/2008/08/04/dc-html/"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>rfc2119</title>
</head>
<body>
<div class="Verified-headnote-styling">
<span style="font-weight: bold;">This is a purely informative rendering of an RFC that includes verified errata. This rendering may not be used as a reference.</span>
<br>
<br>
The following 'Verified' errata have been incorporated in this document:
<a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#eid493">EID 493</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#btn_494">EID 494</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#btn_495">EID 495</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#btn_494">EID 496</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#eid498">EID 498</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#btn_499">EID 499</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#eid500">EID 500</a>, <a href="https://www.rfc-editor.org/rfc/inline-errata/rfc2119.html#btn_5101">EID 5101</a>
</div>
<pre>Network Working Group S. Bradner
Request for Comments: 2119 Harvard University
BCP: 14 March 1997
Category: Best Current Practice
Key words for use in RFCs to Indicate Requirement Levels
Status of this Memo
This document specifies an Internet Best Current Practices for the
Internet Community, and requests discussion and suggestions for
improvements. Distribution of this memo is unlimited.
Abstract
In many standards track documents several words are used to signify
the requirements in the specification. These words are often
capitalized. This document defines these words as they should be
interpreted in IETF documents. Authors who follow these guidelines
should incorporate this phrase near the beginning of their document:
<span class="Verified-inline-styling" id="inline-499"> The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL <button id="btn_499" target="expand_499" onclick="hideFunction(&quot;expand_499&quot;)">Expand</button>
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT
RECOMMENDED", "MAY", and "OPTIONAL" in this document are to
be interpreted as described in RFC 2119.</span>
<div class="nodeCloseClass" id="expand_499"><div class="Verified-endnote-styling" id="eid499">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid499">EID 499</a> (Verified) is as follows:</i></b>
<b>Section:</b> Abstract
<b>Original Text:</b>
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
RFC 2119.
<b>Corrected Text:</b>
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT
RECOMMENDED", "MAY", and "OPTIONAL" in this document are to
be interpreted as described in RFC 2119.
</pre>
<b>Notes:</b><br>
The phrase "NOT RECOMMENDED" is missing from this sentence.
</div>
</div>
Note that the force of these words is modified by the requirement
level of the document in which they are used.
<span class="Verified-inline-styling" id="inline-495">1. MUST This word, or the terms "REQUIRED" or "SHALL", means that the <button id="btn_495" target="expand_495" onclick="hideFunction(&quot;expand_495&quot;)">Expand</button>
definition is an absolute requirement of the specification.
<div class="Verified-endnote-styling" id="eid493">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid493">EID 493</a> (Verified) is as follows:</i></b>
<b>Section:</b> 1
<b>Original Text:</b>
2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the
definition is an absolute prohibition of the specification.
<b>Corrected Text:</b>
2. MUST NOT This phrase, or the phrase "SHALL NOT", means that the
definition is an absolute prohibition of the specification.
</pre>
<b>Notes:</b><br>
</div>
<div class="Verified-endnote-styling" id="eid498">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid498">EID 498</a> (Verified) is as follows:</i></b>
<b>Section:</b> 1
<b>Original Text:</b>
4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that
there may exist valid reasons in particular circumstances when the
particular behavior is acceptable or even useful, but the full
implications should be understood and the case carefully weighed
before implementing any behavior described with this label.
<b>Corrected Text:</b>
4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED", means that
there may exist valid reasons in particular circumstances when the
particular behavior is acceptable or even useful, but the full
implications should be understood and the case carefully weighed
before implementing any behavior described with this label.
</pre>
<b>Notes:</b><br>
</div>
<div class="Verified-endnote-styling" id="eid500">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid500">EID 500</a> (Verified) is as follows:</i></b>
<b>Section:</b> 1
<b>Original Text:</b>
3. SHOULD This word, or the adjective "RECOMMENDED", mean that there
may exist valid reasons in particular circumstances to ignore a
particular item, but the full implications must be understood and
carefully weighed before choosing a different course.
<b>Corrected Text:</b>
3. SHOULD This word, or the adjective "RECOMMENDED", means that there
may exist valid reasons in particular circumstances to ignore a
particular item, but the full implications must be understood and
carefully weighed before choosing a different course.
</pre>
<b>Notes:</b><br>
</div>
</span>
<div class="nodeCloseClass" id="expand_495"><div class="Verified-endnote-styling" id="eid495">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid495">EID 495</a> (Verified) is as follows:</i></b>
<b>Section:</b> 1
<b>Original Text:</b>
1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that the
definition is an absolute requirement of the specification.
<b>Corrected Text:</b>
1. MUST This word, or the terms "REQUIRED" or "SHALL", means that the
definition is an absolute requirement of the specification.
</pre>
<b>Notes:</b><br>
</div>
</div>
2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the
definition is an absolute prohibition of the specification.
3. SHOULD This word, or the adjective "RECOMMENDED", mean that there
may exist valid reasons in particular circumstances to ignore a
particular item, but the full implications must be understood and
carefully weighed before choosing a different course.
4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that
there may exist valid reasons in particular circumstances when the
particular behavior is acceptable or even useful, but the full
implications should be understood and the case carefully weighed
before implementing any behavior described with this label.
<span class="Verified-inline-styling" id="inline-5101">5. MAY This word, or the adjective "OPTIONAL", mean that an item is <button id="btn_5101" target="expand_5101" onclick="hideFunction(&quot;expand_5101&quot;)">Expand</button>
truly optional. One vendor may choose to include the item because a
particular marketplace requires it or because the vendor feels that
it enhances the product while another vendor may omit the same item.
An implementation which does not include a particular option MUST be
prepared to interoperate with another implementation which does
include the option, though perhaps with reduced functionality. In the
same vein an implementation which does include a particular option
MUST be prepared to interoperate with another implementation which
does not include the option (except, of course, for the feature the
option provides).</span>
<div class="nodeCloseClass" id="expand_5101"><div class="Verified-endnote-styling" id="eid5101">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid5101">EID 5101</a> (Verified) is as follows:</i></b>
<b>Section:</b> 5
<b>Original Text:</b>
5. MAY This word, or the adjective "OPTIONAL", mean that an item is
truly optional. One vendor may choose to include the item because a
particular marketplace requires it or because the vendor feels that
it enhances the product while another vendor may omit the same item.
An implementation which does not include a particular option MUST be
prepared to interoperate with another implementation which does
include the option, though perhaps with reduced functionality. In the
same vein an implementation which does include a particular option
MUST be prepared to interoperate with another implementation which
does not include the option (except, of course, for the feature the
option provides.)
<b>Corrected Text:</b>
5. MAY This word, or the adjective "OPTIONAL", mean that an item is
truly optional. One vendor may choose to include the item because a
particular marketplace requires it or because the vendor feels that
it enhances the product while another vendor may omit the same item.
An implementation which does not include a particular option MUST be
prepared to interoperate with another implementation which does
include the option, though perhaps with reduced functionality. In the
same vein an implementation which does include a particular option
MUST be prepared to interoperate with another implementation which
does not include the option (except, of course, for the feature the
option provides).
</pre>
<b>Notes:</b><br>
Full stop should appear outside the parentheses in the last sentence.
</div>
</div>
6. Guidance in the use of these Imperatives
Imperatives of the type defined in this memo must be used with care
and sparingly. In particular, they MUST only be used where it is
actually required for interoperation or to limit behavior which has
potential for causing harm <span class="Verified-inline-styling" id="inline-494">(e.g., limiting retransmissions)</span> For <button id="btn_494" target="expand_494" onclick="hideFunction(&quot;expand_494&quot;)">Expand Multiple</button>
<div class="nodeCloseClass" id="expand_494"><div class="Verified-endnote-styling" id="eid494">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid494">EID 494</a> (Verified) is as follows:</i></b>
<b>Section:</b> 6
<b>Original Text:</b>
(e.g., limiting retransmisssions)
<b>Corrected Text:</b>
(e.g., limiting retransmissions)
</pre>
<b>Notes:</b><br>
</div>
<div class="Verified-endnote-styling" id="eid496">
<pre><b><i><a href="https://www.rfc-editor.org/errata/eid496">EID 496</a> (Verified) is as follows:</i></b>
<b>Section:</b> 6
<b>Original Text:</b>
In particular, they MUST only be used where it is actually required
for interoperation or to limit behavior which has potential for
causing harm (e.g., limiting retransmisssions) For example, they
must not be used to try to impose a particular method on
implementors where the method is not required for interoperability.
<b>Corrected Text:</b>
In particular, they MUST only be used where it is actually required
for interoperation or to limit behavior which has potential for
causing harm (e.g., limiting retransmissions). For example, they
must not be used to try to impose a particular method on
implementors where the method is not required for interoperability.
</pre>
<b>Notes:</b><br>
</div>
</div> example, they must not be used to try to impose a particular method
on implementors where the method is not required for
interoperability.
7. Security Considerations
These terms are frequently used to specify behavior with security
implications. The effects on security of not implementing a MUST or
SHOULD, or doing something the specification says MUST NOT or SHOULD
NOT be done may be very subtle. Document authors should take the time
to elaborate the security implications of not following
recommendations or requirements as most implementors will not have
had the benefit of the experience and discussion that produced the
specification.
8. Acknowledgments
The definitions of these terms are an amalgam of definitions taken
from a number of RFCs. In addition, suggestions have been
incorporated from a number of people including Robert Ullmann, Thomas
Narten, Neal McBurnett, and Robert Elz.
9. Author's Address
Scott Bradner
Harvard University
1350 Mass. Ave.
Cambridge, MA 02138
phone - +1 617 495 3864
email - sob@harvard.edu
</pre></body></html>

BIN
www/drill.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
www/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,16 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<!-- <link href="styles/style.css" rel="stylesheet"> -->
<title>Le Webserv</title>
</head>
<body>
<h1>My First Heading</h1>
<p>My first paragraph.</p>
<h1 style="text-align:center">Le index (˘ ͜ʖ˘)</h1>
<hr>
<p style="text-align:center">(˚3˚)</p>
</body>
</html>
</html>

BIN
www/kermit.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -13,7 +13,7 @@
<link rel="alternative stylesheet" type="text/css" href="./rfc2119_files/errata-monochrome.css" title="Monochrome">
<link rel="alternative stylesheet" type="text/css" href="./rfc2119_files/errata-printer.css" title="Printer">
<script src="./rfc2119_files/errata.js.téléchargement"></script>
<script src="./rfc2119_files/errata.js"></script>
</head>
<body>
<div class="Verified-headnote-styling">
@@ -309,4 +309,4 @@ Full stop should appear outside the parentheses in the last sentence.
</pre></body></html>
</pre></body></html>

View File

@@ -0,0 +1,139 @@
<style type="text/css">
@media only screen
and (min-width: 992px)
and (max-width: 1199px) {
body { font-size: 14pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (min-width: 768px)
and (max-width: 991px) {
body { font-size: 14pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (min-width: 480px)
and (max-width: 767px) {
body { font-size: 11pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (max-width: 479px) {
body { font-size: 8pt; }
div.content { width: 96ex; margin: 0 auto; }
}
@media only screen
and (min-device-width : 375px)
and (max-device-width : 667px) {
body { font-size: 9.5pt; }
div.content { width: 96ex; margin: 0; }
}
@media only screen
and (min-device-width: 1200px) {
body { font-size: 10pt; margin: 0 4em; }
div.content { width: 96ex; margin: 0; }
}
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 {
font-weight: bold;
line-height: 0pt;
display: inline;
white-space: pre;
font-family: monospace;
font-size: 1em;
font-weight: bold;
}
pre {
font-size: 1em;
margin-top: 0px;
margin-bottom: 0px;
}
.pre {
white-space: pre;
font-family: monospace;
}
.header{
font-weight: bold;
}
.newpage {
page-break-before: always;
}
.invisible {
text-decoration: none;
color: white;
}
a.selflink {
color: black;
text-decoration: none;
}
@media print {
body {
font-family: monospace;
font-size: 10.5pt;
}
h1, h2, h3, h4, h5, h6 {
font-size: 1em;
}
a:link, a:visited {
color: inherit;
text-decoration: none;
}
.noprint {
display: none;
}
}
@media screen {
.grey, .grey a:link, .grey a:visited {
color: #777;
}
.docinfo {
background-color: #EEE;
}
.top {
border-top: 7px solid #EEE;
}
.bgwhite { background-color: white; }
.bgred { background-color: #F44; }
.bggrey { background-color: #666; }
.bgbrown { background-color: #840; }
.bgorange { background-color: #FA0; }
.bgyellow { background-color: #EE0; }
.bgmagenta{ background-color: #F4F; }
.bgblue { background-color: #66F; }
.bgcyan { background-color: #4DD; }
.bggreen { background-color: #4F4; }
.legend { font-size: 90%; }
.cplate { font-size: 70%; border: solid grey 1px; }
}
.Verified-headnote-styling, .Held-headnote-styling, .Reported-headnote-styling, .Rejected-headnote-styling {
border:dashed;
margin:8px;
padding;24px;
overflow-wrap: normal;
padding: 1em;
}
.Verified-endnote-styling, .Held-endnote-styling, .Reported-endnote-styling, .Rejected-endnote-styling {
border:dashed;
margin:8px;
padding:24px;
overflow:auto;
white-space: pre-wrap;
}
.Verified-ineline-styling, .Held-ineline-styling, .Reported-ineline-styling, .Rejected-ineline-styling {
white-space: pre-wrap;
}
.nodeCloseClass {
display:none;
}
.nodeOpenClass {
display:inline;
}
</style>

View File

@@ -0,0 +1,43 @@
<style type="text/css">
.verified {color: green}
.supplementary-styling {
background-color: yellow
}
.old-text {color: red}
.Verified-headnote-styling, .Verified-endnote-styling {
background-color: LightGreen;
color: black;
}
.Verified-inline-styling {
color: Green;
}
.Held-headnote-styling, .Held-endnote-styling {
background-color: LightBlue;
color: black;
}
.Held-inline-styling {
color: Blue;
}
.Reported-headnote-styling, .Reported-endnote-styling {
background-color: Beige;
}
.Reported-inline-styling {
color:GoldenRod;
}
.Rejected-headnote-styling, .Rejected-endnote-styling {
background-color: LightPink;
color: black;
}
.Rejected-inline-styling {
color: red;
}
</style>

View File

@@ -0,0 +1,34 @@
<style type="text/css">
.Verified-headnote-styling, .Verified-endnote-styling {
font-wieght: bold;
}
.Verified-inline-styling {
font-wieght: bold;
background-color: lightGrey;
}
.Held-headnote-styling, .Held-endnote-styling {
font-style: italic;
}
.Held-inline-styling {
font-style: italic;
background-color: lightGrey;
}
.Reported-headnote-styling, .Reported-endnote-styling {
background-color: Beige;
}
.Reported-inline-styling {
color: Yellow;
}
.Rejected-headnote-styling, .Rejected-endnote-styling {
background-color: LightPink;
}
.Rejected-inline-styling {
color: red;
}
</style>

View File

@@ -0,0 +1,43 @@
<style type="text/css">
.Verified-headnote-styling, .Verified-endnote-styling {
font-wieght: bold;
}
.Verified-inline-styling {
font-wieght: bold;
background-color: lightGrey;
}
.Held-headnote-styling, .Held-endnote-styling {
font-style: italic;
}
.Held-inline-styling {
font-style: italic;
background-color: lightGrey;
}
.Reported-headnote-styling, .Reported-endnote-styling {
background-color: Beige;
}
.Reported-inline-styling {
color: Yellow;
}
.Rejected-headnote-styling, .Rejected-endnote-styling {
background-color: LightPink;
}
.Rejected-inline-styling {
color: red;
}
.nodeCloseClass {
display:inline;
}
.nodeOpenClass {
display:inline;
}
</style>

View File

@@ -0,0 +1,4 @@
function hideFunction(nodeId) {
var ul = document.getElementById(nodeId)
ul.className = (ul.className=="nodeOpenClass") ? "nodeCloseClass" : "nodeOpenClass"
}

View File

@@ -0,0 +1,4 @@
function hideFunction(nodeId) {
var ul = document.getElementById(nodeId)
ul.className = (ul.className=="nodeOpenClass") ? "nodeCloseClass" : "nodeOpenClass"
}

BIN
www/root.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 KiB