39 Commits

Author SHA1 Message Date
hugogogo
76e7960ad7 deleted old request parsing functions 2022-08-01 20:35:42 +02:00
hugogogo
b1158fd33b merged from master and relocate headers inside srcs 2022-08-01 20:33:06 +02:00
hugogogo
979a3d20b8 wip cgi handling, client contains it's own privates functions to handle request headers and body 2022-08-01 19:30:07 +02:00
hugogogo
031932e887 added trim in utils, added parse request header, mv pdf in docs 2022-08-01 00:07:51 +02:00
hugogogo
cbb1d12d54 modified itoa and its headers 2022-07-31 12:59:27 +02:00
hugogogo
2a69e14db2 wip atoi 2022-07-31 12:30:35 +02:00
hugogogo
122032a140 merge master 2022-07-31 12:02:20 +02:00
hugogogo
8381f804cd merge luke works with separate files 2022-07-30 18:35:57 +02:00
hugogogo
e132802a8d wip parsing request 2022-07-29 16:48:37 +02:00
hugogogo
c821d3d92d tests layout readme 2022-07-29 15:29:09 +02:00
hugogogo
7e5492b793 tests layout readme 2022-07-29 14:19:08 +02:00
hugogogo
06ff3a4ea9 added http headers to readme 2022-07-29 14:15:15 +02:00
hugogogo
035dbe804d mv Client and Server in headers 2022-07-29 11:23:40 +02:00
hugogogo
1ec6f0b47e merge master 2022-07-29 11:19:09 +02:00
hugogogo
a2c9f31e58 small changes in cgi tests 2022-07-29 10:43:11 +02:00
hugogogo
e6ec3fe97b more tests on cgi 2022-07-28 17:27:03 +02:00
hugogogo
8477669e6f adding first tests with cgi 2022-07-27 20:47:13 +02:00
hugogogo
d4a14798f4 add correction pdf 2022-07-26 22:46:10 +02:00
hugogogo
9174e0f93b added infos on cgi in readme, and testers of the subject, and test folder 2022-07-26 21:32:57 +02:00
hugogogo
d809ee5de2 sort readme todo 2022-07-25 17:56:31 +02:00
hugogogo
9477d36512 add luke epoll version 2022-07-23 08:53:59 +02:00
hugogogo
ebef346387 clean files 2022-07-23 08:38:08 +02:00
hugogogo
c8c2d94a39 makefile compile both luke and hugo files 2022-07-22 23:37:30 +02:00
hugogogo
46a5e55e6d baked hugo version of webserv 2022-07-22 22:17:12 +02:00
hugogogo
61fb5ea595 renamed main in hugo 2022-07-22 22:03:56 +02:00
hugogogo
15e00d03ce class version of poll ref 2022-07-22 22:00:32 +02:00
hugogogo
63581c8a42 wip vector version 2022-07-22 15:57:55 +02:00
hugogogo
708da8bb61 wip put poll version inside luke version 2022-07-20 17:11:24 +02:00
hugogogo
fecf5411bc separate main tests in differents files and added comparison in readme 2022-07-20 14:34:16 +02:00
hugogogo
726bf36b17 not much more, trying a fork 2022-07-16 18:55:45 +02:00
hugogogo
ca81b6760e pbm with bind and sudo understood, next is port 2022-07-15 22:14:29 +02:00
hugogogo
6b1fd5a15b wip with both my and luke code 2022-07-13 17:31:18 +02:00
hugogogo
461c10b6a2 tmp try with luke code 2022-07-13 14:31:34 +02:00
hugogogo
272404a0c4 tmp try with luke code 2022-07-13 14:30:00 +02:00
hugogogo
75d6c941a6 wip first connexion 2022-07-13 14:22:26 +02:00
hugogogo
340785ef0f mise en page readme 2022-07-12 19:28:13 +02:00
hugogogo
a6ad022b07 mise en page readme 2022-07-12 17:55:20 +02:00
hugogogo
79faba2c17 mise en page readme 2022-07-12 17:48:25 +02:00
hugogogo
79e5b24df6 tmp 2022-07-12 14:46:55 +02:00
149 changed files with 1961 additions and 7192 deletions

4
.gitignore vendored
View File

@@ -20,7 +20,3 @@ ubuntu_cgi_tester
webserv
!**/webserv/
*.log
large.jpg
webserv_tester
webserv_tester2

3
1
View File

@@ -1,3 +0,0 @@
------

View File

@@ -6,73 +6,50 @@ CXXFLAGS = -Wall -Wextra #-Werror
CXXFLAGS += $(HEADERS_D:%=-I%)
CXXFLAGS += -std=c++98
CXXFLAGS += -g
#CXXFLAGS += -fno-limit-debug-info
CXXFLAGS += -MMD -MP #header dependencie
#CXXFLAGS += -O3
VPATH = $(SRCS_D)
HEADERS_D = srcs \
srcs/webserv \
srcs/config
HEADERS_D = srcs
SRCS_D = srcs \
srcs/webserv \
srcs/config \
srcs/webserv
SRCS = main.cpp \
base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \
accept.cpp request.cpp response.cpp \
method_get.cpp method_post.cpp method_delete.cpp \
run_loop.cpp timeout.cpp \
parser.cpp \
extraConfig.cpp \
postProcessing.cpp \
run_loop.cpp \
ConfigParser.cpp \
ConfigParserUtils.cpp \
ConfigParserPost.cpp \
utils.cpp \
cgi.cpp \
Client.cpp Client_multipart_body.cpp \
cgi_script.cpp \
Client.cpp \
OBJS_D = builds
OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o)
DEPS = $(OBJS:.o=.d) #header dependencie
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# . target: prerequisites . $@ : target #
# RULES . recipe . $< : 1st prerequisite #
# . @recipe (silent) . $^ : all prerequisites #
# . target: VAR = assignment . | : order-only prereq. #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# --------------------
# ------ RULES -------
# --------------------
all: $(NAME)
$(OBJS_D)/%.o: %.cpp | $(OBJS_D)
$(CXX) $(CXXFLAGS) -c $< -o $@
printf "$(_CYAN)\r\33[2K\rCompling $@$(_END)"
$(OBJS_D):
mkdir $@
$(NAME): $(OBJS)
$(CXX) $^ -o $(NAME)
echo "$(_GREEN)\r\33[2K\r$(NAME) created 😎$(_END)"
# CGI
cgi:
make -C srcs/cgi-bin
cgiclean:
make clean -C srcs/cgi-bin
cgifclean:
make fclean -C srcs/cgi-bin
cgire:
make re -C srcs/cgi-bin
clean:
rm -rf $(OBJS_D)
echo "$(_RED).o Files Deleted 😱$(_END)"
fclean: clean
rm -f $(NAME)
echo "$(_RED)$(NAME) Deleted 😱$(_END)"
re: fclean all
@@ -80,24 +57,3 @@ re: fclean all
-include $(DEPS) # header dependencie
.SILENT:
# ------------------
# ----- COLORS -----
# ------------------
_GREY=$ \033[30m
_RED=$ \033[31m
_GREEN=$ \033[32m
_YELLOW=$ \033[33m
_BLUE=$ \033[34m
_PURPLE=$ \033[35m
_CYAN=$ \033[36m
_WHITE=$ \033[37m
_END=$ \033[0m

208
README.md
View File

@@ -1,6 +1,18 @@
## work together
---
## questions
- mettre les fonctions specifiques a la requete, dans la class client ?
- où est-ce que j'inclus le cgi ?
- est-ce que le cgi est appellé par `/cgi-bin` ?
- non
- g rajouté `char ** env` dans client.cpp
- non
- ajouter un champ "message body" dans client ?
- non
- comment organiser la creation du message reponse (cgi ou pas) et des headers ?
- comment je gere le path `/cgi-bin/` avec la suite ?
- qu'est-ce que le cgi renvoit comme headers ? comment c'est géré ?
- https://www.rfc-editor.org/rfc/rfc3875
---
## man
@@ -76,142 +88,16 @@
- [ ] Your server must be able to listen to multiple ports (see Configuration file)
- [ ] Your server should never die.
---
## cgi rfc
[rfc 3875](https://www.rfc-editor.org/rfc/rfc3875)
#### output cgi script :
#### summary :
- the cgi-script will send back at least one header field followed by an empty line
- this header field will be one of three :
- "Content-Type"
- "Location"
- "Status"
- the cgi-script may send back more header fields
- the server must check and modify few things :
- there is no field duplicate (resolve conflicts)
- there is no space between filed name and ":"
- change all the '\n' by '\r\n'
- if no Location field && no Status field -> status code = 200
- handle Location field, either :
- local : start with '/' --> rerun the request with new uri
- client : start with '<scheme>:' --> send back status code 302
- there is at least one header field followed by '\r\n\r\n' :
- "Content-Type"
- "Location"
- "Status"
- if status field, change server status for this one
- to pass the body-message to the cgi-script, we write it into the temporary fd on which the script read it's standard input
[3.1: server responsabilities](https://www.rfc-editor.org/rfc/rfc3875#section-3.1)
- The server [...] receives the request from the client
- selects a CGI script to handle the request
- converts the client request to a CGI request
- executes the script and converts the CGI response into a response for the client
[3.3: script uri](https://www.rfc-editor.org/rfc/rfc3875#section-3.3)
- the 'Script-URI' [...] MUST have the property that if the client had accessed this URI instead, then the script would have been executed
[4: how the server prepare the cgi requests](https://www.rfc-editor.org/rfc/rfc3875#section-4)
- the cgi receives 2 differents set of informations :
- the request meta-variables (in UNIX, by env variables)
- and the message-body
[4.1: request meta-variables](https://www.rfc-editor.org/rfc/rfc3875#section-4.1)
- a header field that spans multiple lines MUST be merged onto a single line
[4.2: request message-body](https://www.rfc-editor.org/rfc/rfc3875#section-4.2)
- unless defined otherwise, the script access request data by reading stdin
[6: how the response from the script is returned to the server](https://www.rfc-editor.org/rfc/rfc3875#section-6)
- The response comprises 2 parts, separated by a blank line :
- a message-header
- and a message-body
- The message-header contains one or more header fields
- The body may be NULL
[6.2: responses types](https://www.rfc-editor.org/rfc/rfc3875#section-6.2)
- four types of responses :
- document response
- local redirect response
- client redirect response
- client redirect response with document
- document response :
- it must return a Content-Type header field
- a Status-Header field is optional (200 is assumed if omited)
- the server must check the cgi-script output, and modifie it to comply with the protocol version
- local redirect response :
- it must return only a Location field
- it contains a local path URI and query string ('local-pathquery')
- the local path URI must start with a "/"
- the server must generate the response for this local-pathquery
- client redirect response :
- it must return only a Location field
- it contains an absolute URI path, to indicate the client that it should reprocess the request with this URI
- the absolute URI always start with the name of scheme followed by ":"
- the http server must generate a 302 'Found' message
- client redirect response with document
- it must return a Location field with an absolute URI path
- it must return the Status header field, with a value of 302 'Found'
- the server must check the cgi-script output, and modifie it to comply with the protocol version
[6.3: cgi header fields](https://www.rfc-editor.org/rfc/rfc3875#section-6.3)
- whitespace is permitted between the ":" and the field-value
- but not between the field-name and the ":"
- the CGI script can set three differents fields :
- Content-Type
- Location
- Status
- Content-Type :
- if there is a body in the response, a Content-Type field must be present
- if there is no Content-Type, the server must not attempt to determine one
- Location :
- the local URI path must be an absolut path, not a relative path, nor NULL
- the local URI path must, then, start with "/"
- the absolut URI start with "<name-of-scheme>:"
- Status :
- a 3-digit integer code
- 4 standards :
- 200 'OK' indicates success, it's the default value
- 302 'Found' with Location header and response message-body
- 400 'Bad Request' an unknown request format, like missing CONTENT-TYPE
- 501 'Not Implemented' the script received unsupported REQUEST-METHOD
- construction: `Status:400 "explication of the error"\n`
- the cgi-script can return other header fields, concerning the response message
- the server must translate cgi-headers syntax into http-header syntax
- for exemple, newline can be encoded in different ways
- the cgi-script must not return header fields concerning client-side communication
- the server can remove such fields
- (not sure : https://www.rfc-editor.org/rfc/rfc3875#section-6.3.4)
- the server must resolve conflicts between script-header fields and themselves
[6.3: cgi message body](https://www.rfc-editor.org/rfc/rfc3875#section-6.4)
- the server must read it untill EOF
- the server must not modify it, except to convert charset if needed
[7 and 8: usefull informations about implementation and security](https://www.rfc-editor.org/rfc/rfc3875#section-7)
---
## cgi env variables
[cgi env variables](https://www.rfc-editor.org/rfc/rfc3875#section-4.1)
[cgi env variables](http://www.faqs.org/rfcs/rfc3875.html)
[wikipedia variables environnements cgi](https://fr.wikipedia.org/wiki/Variables_d%27environnement_CGI)
[cgi server variables on adobe](https://helpx.adobe.com/coldfusion/cfml-reference/reserved-words-and-variables/cgi-environment-cgi-scope-variables/cgi-server-variables.html)
```None
```
AUTH_TYPE : if the srcipt is protected, the authentification method used to validate the user
CONTENT_LENGTH : length of the request body-message
CONTENT_TYPE : (Content-Type field) if there is attached information, as with method POST or PUT, this is the content type of the data (e.g. "text/plain", it is set by the attribute "enctype" in html <form> as three values : "application/x-www-form-urlencoded", "multipart/form-data", "text/plain")
CONTENT_LENGTH : length of the request content
CONTENT_TYPE : if there is attached information, as with method POST or PUT, this is the content type of the data (e.g. "text/plain", it is set by the attribute "enctype" in html <form> as three values : "application/x-www-form-urlencoded", "multipart/form-data", "text/plain")
GATEWAY_INTERFACE : CGI version (e.g. CGI/1.1)
PATH_INFO : if any, path of the resquest in addition to the cgi script path (e.g. for cgi script path = "/usr/web/cgi-bin/script.cgi", and the url = "http://server.org/cgi-bin/script.cgi/house", the PATH-INFO would be "house")
PATH_TRANSLATED : full path of the request, like path-to-cgi/PATH_INFO, null if PATH_INFO is null (e.g. for "http://server.org/cgi-bin/prog/the/path", PATH_INFO would be : "/the/path" and PATH_TRANSLATED would be : "/usr/web/cgi-bin/prog/the/path")
@@ -228,66 +114,10 @@ SERVER_PROTOCOL : protocol used for the request (e.g. HTTP/1.1)
SERVER_SOFTWARE : the server software you're using (e.g. Apache 1.3)
```
[redirect status for php-cgi](https://woozle.org/papers/php-cgi.html)
```None
```
REDIRECT_STATUS : for exemple, 200
```
g 50 34 48
p 30 23 32
l 20 14 20
71
---
## http status
[rfc 2616](https://datatracker.ietf.org/doc/html/rfc2616#section-10)
#### Informational
- 100 Continue
- 101 Switching Protocols
#### Successful
- 200 OK
- 201 Created
- 202 Accepted
- 203 Non-Authoritative Information
- 204 No Content
- 205 Reset Content
- 206 Partial Content
#### Redirection
- 300 Multiple Choices
- 301 Moved Permanently
- 302 Found
- 303 See Other
- 304 Not Modified
- 305 Use Proxy
- 306 (Unused)
- 307 Temporary Redirect
#### Client Error
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 413 Request Entity Too Large
- 414 Request-URI Too Long
- 415 Unsupported Media Type
- 416 Requested Range Not Satisfiable
- 417 Expectation Failed
#### Server Error
- 500 Internal Server Error
- 501 Not Implemented
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
- 505 HTTP Version Not Supported
---
## ressources

View File

@@ -1,116 +0,0 @@
telnet> Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HTTP/1.1 200 OK
Server: Webserv/0.1
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Content-Length: 193
<!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>
HTTP/1.1 200 OK
Server: Webserv/0.1
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Content-Length: 290
<!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>
HTTP/1.1 200 OK
Server: Webserv/0.1
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Content-Length: 290
<!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>
HTTP/1.1
Server: Webserv/0.1
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Content-Length: 1277
<!DOCTYPE html><html><head><title>Index of ./Tester/www/</title></head><body><h1>Index of ./Tester/www/</h1><hr><pre><a style="font-size:1.5em" href="/list/..">..</a>
<a style="font-size:1.5em" href="/list/upload_form.html">upload_form.html</a>
<a style="font-size:1.5em" href="/list/form_get.html">form_get.html</a>
<a style="font-size:1.5em" href="/list/kermit.ico">kermit.ico</a>
<a style="font-size:1.5em" href="/list/upload_form_single.html">upload_form_single.html</a>
<a style="font-size:1.5em" href="/list/Cagneyc_intro.gif">Cagneyc_intro.gif</a>
<a style="font-size:1.5em" href="/list/Van_Eyck_Portrait_Arnolfini.jpg">Van_Eyck_Portrait_Arnolfini.jpg</a>
<a style="font-size:1.5em" href="/list/root.png">root.png</a>
<a style="font-size:1.5em" href="/list/test">test</a>
<a style="font-size:1.5em" href="/list/drill.jpg">drill.jpg</a>
<a style="font-size:1.5em" href="/list/punpun.png">punpun.png</a>
<a style="font-size:1.5em" href="/list/favicon.ico">favicon.ico</a>
<a style="font-size:1.5em" href="/list/index.html">index.html</a>
<a style="font-size:1.5em" href="/list/rfc2119.html">rfc2119.html</a>
<a style="font-size:1.5em" href="/list/error_pages">error_pages</a>
<a style="font-size:1.5em" href="/list/rfc2119_files">rfc2119_files</a>
</pre><hr></body></html>HTTP/1.1 404 Not Found
Server: Webserv/0.1
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Content-Length: 210
<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1 style="text-align:center">Check it UP 404 Not Found</h1>
<hr>
<p style=\"text-align:center\">Le Webserv/0.1</p>
</body>
</html>
HTTP/1.1 404 Not Found
Server: Webserv/0.1
Connection: keep-alive
Content-Type: text/html; charset=UTF-8
Content-Length: 210
<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1 style="text-align:center">Check it UP 404 Not Found</h1>
<hr>
<p style=\"text-align:center\">Le Webserv/0.1</p>
</body>
</html>

View File

@@ -1,95 +0,0 @@
#! /bin/bash
# DO I EVEN NEED THIS FILE ? could i just put this stuff in maint_test.sh? or would it not modify the vars...
# you need to put absolutely everything in ""
#test_file=$1
#source $test_file
connect_to_telnet="open $host $port"
start_telnet()
{
echo "open $host $port"
sleep 1
}
run()
{
echo "$connect_to_telnet"
sleep 1
run_this_test
}
#### DEPRICATED ######
run_telnet()
{
echo "$connect_to_telnet"
sleep 1
echo -e "$request"
echo
sleep 1
# ret = $(arg | telnet)
}
run_a_test()
{
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} &> test.log
echo -e "$request" | telnet
}
#######
# This is where stuff is launched
#######
#rm -rf telnet.out
#./webserv $config_file 2>&1 > webserv.log & run_all
#./webserv $config_file 2>&1 > webserv.log &
#./webserv $config_file &> /dev/null &
#./webserv $config_file 1>&1 > webserv.log &
#./webserv $config_file &> webserv.log &
#run_all
#run_this_test
#echo -e "${_GREEN}Running Telnet Test on '$test_name'${_END}"
#sleep 1
#run | telnet >> telnet.out
#run | telnet
#pkill webserv

View File

@@ -1,16 +0,0 @@
#! /bin/bash
echo "open duckduckgo.com 80"
sleep 1
echo "GET / HTTP/1.1"
echo "Host: duckduckgo.com"
echo
echo
sleep 2
#(
#echo open duckduckgo.com 80
#echo "GET / HTTP/1.1"
#echo "Host: duckduckgo.com"
#echo "exit"
#) | telnet

View File

@@ -1,97 +0,0 @@
server {
# this is a comment
server_name server1;
listen 0.0.0.0:4040;
# client_body_limit asdfa;
client_body_limit 5000;
# Max == 18446744073709551615 / 1024 == 18014398509481984
index index.html; # this is another comment
#index mdr.html; # this is another comment
root ./Tester/www/;
error_page 404 ./Tester/www/error_pages/error_404.html;
location / {
allow_methods GET;
root ./Tester/www/;
}
location /srcs/cgi-bin/ {
root ./srcs/cgi-bin/;
allow_methods POST;
cgi_ext php;
}
location /list {
autoindex on;
}
location /cgi-bin {
root ./srcs/cgi-bin/;
cgi_ext out php sh;
}
location /upload {
allow_methods POST;
autoindex on;
upload_dir ./Tester/www/user_files/;
# root doesnt matter if used only with POST and no CGI
}
location /the_dump {
allow_methods GET DELETE;
root ./Tester/www/user_files;
autoindex on;
}
location /redirect {
redirect 307 https://fr.wikipedia.org/wiki/Ketchup;
# redirect 307 https://www.youtube.com/watch?v=rG6b8gjMEkw;
}
location /test {
index index1.html subdex.html;
root ./Tester/www/test/;
}
location /stylesheet {
root ./stylesheet/;
}
location /test/index1.html {
root ./Tester/www/test/index1.html;
index index1.html subdex.html;
}
location /hilarious_404/ {
redirect 301 https://berniesanders.com/404/;
}
location /test/something.html {
# allow_methods DELETE;
root ./Tester/www/test/something.html;
}
location /test/test_deeper/ {
# allow_methods
root ./Tester/www/test/test_deeper/;
}
location /test/test_deeper/super_deep {
root ./Tester/www/test/test_deeper/super_deep/;
}
# location /test/test_deeper/something.html {
# allow_methods DELETE;
# }
}

View File

@@ -1,95 +0,0 @@
server {
# this is a comment
server_name server1;
listen 0.0.0.0:4040;
client_body_limit 1;
# Max == 18446744073709551615 / 1024 == 18014398509481984
index index.html; # this is another comment
root ./Tester/www/;
error_page 404 ./Tester/www/error_pages/error_404.html;
location / {
allow_methods GET;
root ./Tester/www/;
}
location /srcs/cgi-bin/ {
root ./srcs/cgi-bin/;
allow_methods POST;
cgi_ext php;
}
location /list {
autoindex on;
}
location /cgi-bin {
root ./srcs/cgi-bin/;
cgi_ext out php sh;
}
location /upload {
allow_methods POST;
autoindex on;
upload_dir ./Tester/www/user_files/;
# root doesnt matter if used only with POST and no CGI
}
location /the_dump {
allow_methods GET DELETE;
root ./Tester/www/user_files;
autoindex on;
}
location /redirect {
redirect 307 https://fr.wikipedia.org/wiki/Ketchup;
# redirect 307 https://www.youtube.com/watch?v=rG6b8gjMEkw;
}
location /test {
index index1.html subdex.html;
root ./Tester/www/test/;
}
location /stylesheet {
root ./stylesheet/;
}
location /test/index1.html {
root ./Tester/www/test/index1.html;
index index1.html subdex.html;
}
location /hilarious_404/ {
redirect 301 https://berniesanders.com/404/;
}
location /test/something.html {
# allow_methods DELETE;
root ./Tester/www/test/something.html;
}
location /test/test_deeper/ {
# allow_methods
root ./Tester/www/test/test_deeper/;
}
location /test/test_deeper/super_deep {
root ./Tester/www/test/test_deeper/super_deep/;
}
# location /test/test_deeper/something.html {
# allow_methods DELETE;
# }
}

View File

@@ -1,84 +0,0 @@
#! /bin/bash
test_name="Body Test"
config_file="./Tester/test2.config"
port=4040
host="localhost"
#paths=("/" "/test" "/test/" "/list" "list" "/wrong") # you can add many
paths=("/") # you can add many
#methods=("GET" "POST" "DELETE")
methods=("GET")
httpz=("HTTP/1.1")
l1="${methods[0]} ${paths[0]} ${httpz[0]}"
header="Host: $host\n"
body="this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message this is a message \n"
# turns out this does nothing...
#header_cycle=("Content-Length: 17" "Content-Length: 14" "Content-Length: 25")
#header_cycle=("Content-Length: 17" "")
#header_cycle=("Content-Length: 17")
#header_cycle=()
header+="Content-Length: 15"
run_this_test()
{
# for i in "${header_cycle[@]}"
for i in "${paths[@]}"
do
# l1="${methods[0]} ${paths[0]} ${httpz[0]}"
l1="${methods[0]} $i ${httpz[0]}"
# header_send="$header$i"
header_send="$header"
request="$l1\n$header_send\n\n$body\n"
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} >> telnet.log
echo -e "$request"
sleep 1
echo -e "\n" >> telnet.log
done
}
# expected result...
files=()
file="expected_body_test.txt"
files+=("expected_body_test.txt")
#files+=("expected_path_root_test.txt")
#files+=("expected_path_testnoslash_test.txt")
#files+=("expected_path_testslash_test.txt")
#files+=("expected_path_list_test.txt")
#files+=("expected_path_badlist_test.txt")
#files+=("expected_path_wrong_test.txt")
#local_expected_test_file=$file
local_expected_test_files=()
test_path=""
add_path()
{
for i in "${files[@]}"
do
local_expected_test_files+=("$test_path$i")
done
}
#add_path

View File

@@ -1,83 +0,0 @@
#! /bin/bash
test_name="Header Test"
config_file="./Tester/test1.config"
port=4040
host="localhost"
paths=("/") # you can add many
#methods=("GET" "POST" "DELETE")
methods=("GET")
httpz=("HTTP/1.1")
# the parts we will send to webserv
# let main.sh handle the l1
l1="${methods[0]} ${paths[0]} ${httpz[0]}"
header="Host: $host\n"
header+="Nonsense: fu\n"
header_cycle=("Transfer-encoding: fu" "Content-encoding: fu")
#header+="Transfer-encoding: fu\n"
#header+="Content-endcoding: fu\n"
body=
run_this_test()
{
for i in "${header_cycle[@]}"
do
header_send="$header$i"
request="$l1\n$header_send\n$body\n"
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} >> telnet.log
echo -e "$request"
sleep 1
echo -e "\n\n" >> telnet.log
done
}
# expected result...
files=()
file="expected_header_test.txt"
files+=("expected_header_test.txt")
#files+=("expected_path_root_test.txt")
local_expected_test_files=()
test_path=""
add_path()
{
for i in "${files[@]}"
do
local_expected_test_files+=("$test_path$i")
done
}
#add_path

View File

@@ -1,83 +0,0 @@
#! /bin/bash
test_name="Method Test"
config_file="./Tester/test1.config"
port=4040
host="localhost"
paths=("/") # you can add many
methods=("GET" "POST" "DELETE" "GUMBALL")
#methods=("GET")
httpz=("HTTP/1.1")
# the parts we will send to webserv
# let main.sh handle the l1
header="Host: $host\n"
header+="Nonsense: fu\n"
#header_cycle=("Transfer-encoding: fu" "Content-encoding: fu")
#header+="Transfer-encoding: fu\n"
#header+="Content-endcoding: fu\n"
body=
run_this_test()
{
for i in "${methods[@]}"
do
l1="$i ${paths[0]} ${httpz[0]}"
request="$l1\n$header\n$body\n"
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} >> telnet.log
echo -e "$request"
sleep 1
echo -e "\n\n" >> telnet.log
done
}
# expected result...
files=()
file="expected_method_test.txt"
files+=("expected_method_test.txt")
#files+=("expected_path_root_test.txt")
local_expected_test_files=()
test_path=""
add_path()
{
for i in "${files[@]}"
do
local_expected_test_files+=("$test_path$i")
done
}
#add_path

View File

@@ -1,74 +0,0 @@
#! /bin/bash
test_name="Path Test"
config_file="./Tester/test1.config"
port=4040
host="localhost"
paths=("/" "/test" "/test/" "/list" "list" "/wrong") # you can add many
#paths=("/") # you can add many
#methods=("GET" "POST" "DELETE")
methods=("GET")
httpz=("HTTP/1.1")
l1="${methods[0]} ${paths[0]} ${httpz[0]}"
header="Host: $host"
body=
run_this_test()
{
for i in "${paths[@]}"
do
l1="${methods[0]} $i ${httpz[0]}"
request="$l1\n$header\n$body\n"
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} >> telnet.log
echo -e "$request"
sleep 1
echo -e "\n" >> telnet.log
done
}
# expected result...
files=()
file="expected_path_test.txt"
files+=("expected_path_test.txt")
#files+=("expected_path_root_test.txt")
#files+=("expected_path_testnoslash_test.txt")
#files+=("expected_path_testslash_test.txt")
#files+=("expected_path_list_test.txt")
#files+=("expected_path_badlist_test.txt")
#files+=("expected_path_wrong_test.txt")
#local_expected_test_file=$file
local_expected_test_files=()
test_path=""
add_path()
{
for i in "${files[@]}"
do
local_expected_test_files+=("$test_path$i")
done
}
#add_path

View File

@@ -1,62 +0,0 @@
#! /bin/bash
test_name="Template test all good"
config_file="./Tester/test1.config"
port=4040
host="localhost"
paths=("/") # you can add many
methods=("GET" "POST" "DELETE")
#methods=("GET")
httpz=("HTTP/1.1")
# the parts we will send to webserv
# let main.sh handle the l1
l1="${methods[0]} ${paths[0]} ${httpz[0]}"
header="Host: $host"
body=
run_this_test()
{
for i in "${methods[@]}"
do
l1="$i ${paths[0]} ${httpz[0]}"
request="$l1\n$header\n$body\n"
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} >> telnet.log
# } &>> test.log
# } 2>>&1
# } &>> /dev/stdout
echo -e "$request"
# echo
sleep 1
# {
# echo -e "\n------\n"
# } > /dev/stdout
# echo -e "\n\n\n------\n\n\n" >> telnet.out
# echo -e "\n\n\n------\n\n\n" &> /dev/stdout
echo -e "\n\n" >> telnet.log
# echo -e "\n\n------\n\n" >> telnet.out
# echo -e "\n------\n" > /dev/stdout
# run_a_test
# echo
done
}
# expected result...

View File

@@ -1,66 +0,0 @@
#! /bin/bash
test_name="Valid URIs Test"
config_file="./Tester/test1.config"
port=4040
host="localhost"
paths=("/" "/stylesheet/style.css" "/test/something.html" "/test/something.html/" "/test/" "/list" "/hilarious_404" "/redirect") # you can add many
#paths=("/") # you can add many
#methods=("GET" "POST" "DELETE")
methods=("GET")
httpz=("HTTP/1.1")
l1="${methods[0]} ${paths[0]} ${httpz[0]}"
header="Host: $host"
body=
run_this_test()
{
for i in "${paths[@]}"
do
l1="${methods[0]} $i ${httpz[0]}"
request="$l1\n$header\n$body\n"
{
echo "----- $test_name -----"
echo -e "$_RED$request$_END"
} >> telnet.log
echo -e "$request"
sleep 1
echo -e "\n" >> telnet.log
done
}
# expected result...
files=()
file="expected_valid_uri_test.txt"
files+=("expected_valid_files_test.txt")
local_expected_test_files=()
test_path=""
add_path()
{
for i in "${files[@]}"
do
local_expected_test_files+=("$test_path$i")
done
}
#add_path

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1 style="text-align:center">Check it UP 404 Not Found</h1>
<hr>
<p style=\"text-align:center\">Le Webserv/0.1</p>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -1,84 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
body {
display: flex;
flex-direction: row;
margin: auto;
}
form {
display: flex;
flex-direction: column;
border: 1px solid red;
margin: 20px auto;
padding: 5px;
}
form > * {
display: flex;
margin: 5px auto 5px 5px;
}
mark {
margin: 0px 3px;
}
</style>
</head>
<body>
<form method="get" action="/cgi-bin/cgi_cpp.out">
<p><mark>get</mark> form</p>
<p>to <mark>/cgi-bin/cgi_cpp.out</mark></p>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname" value="John"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname" value="Doe"><br><br>
<input type="submit" value="submit">
</form>
<br>
<form method="post" action="/cgi-bin/cgi_cpp.out">
<p><mark>post</mark> form</p>
<p>to <mark>/cgi-bin/cgi_cpp.out</mark></p>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname" value="John"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname" value="Doe"><br><br>
<input type="submit" value="submit">
</form>
<br>
<form method="get" action="/cgi-bin/cgi_cpp_content_length.out">
<p><mark>get</mark> form</p>
<p>to <mark>/cgi-bin/cgi_cpp_content_length.out</mark></p>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname" value="John"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname" value="Doe"><br><br>
<input type="submit" value="submit">
</form>
<br>
<form method="post" action="/cgi-bin/cgi_cpp_content_length.out">
<p><mark>post</mark> form</p>
<p>to <mark>/cgi-bin/cgi_cpp_content_length.out</mark></p>
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname" value="John"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname" value="Doe"><br><br>
<input type="submit" value="submit">
</form>
<br>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

View File

@@ -1,139 +0,0 @@
<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

@@ -1,43 +0,0 @@
<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

@@ -1,34 +0,0 @@
<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

@@ -1,43 +0,0 @@
<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

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 KiB

View File

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

View File

@@ -1,11 +0,0 @@
<!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

@@ -1,17 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Webserv test Something</title>
</head>
<body>
<h1 style="text-align:center">Webserv in Test</h1>
<hr>
<p style="text-align:center">Time to submit something:</p>
<!-- <form action="./srcs/cgi-bin/upload_file.php" method="post" enctype="multipart/form-data"> -->
<form action="/uploaded" method="post" enctype="multipart/form-data">
<!-- <input type="hidden" name="upload_dir" value="./www/uploaded/"> -->
<input type="file" id="fileToUpload" name="myFile">
<input type="submit" value="Upload File">
</form>
</body>
</html>

View File

@@ -1,11 +0,0 @@
<!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

@@ -1,11 +0,0 @@
<!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

@@ -1,11 +0,0 @@
<!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>

View File

@@ -1,12 +0,0 @@
<!--
https://www.w3schools.com/howto/howto_html_file_upload_button.asp
https://www.filestack.com/fileschool/html/html-file-upload-tutorial-example/
https://www.rfc-editor.org/rfc/rfc9110#name-multipart-types
https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
-->
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="upload_file1">
<input type="file" name="upload_file2">
<input type="file" name="upload_file3">
<input type="submit">
</form>

View File

@@ -1,10 +0,0 @@
<!--
https://www.w3schools.com/howto/howto_html_file_upload_button.asp
https://www.filestack.com/fileschool/html/html-file-upload-tutorial-example/
https://www.rfc-editor.org/rfc/rfc9110#name-multipart-types
https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
-->
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="upload_file1">
<input type="submit">
</form>

View File

@@ -1 +0,0 @@
inside YoupiBanane/Yeah/not_happy.bad_extension

View File

@@ -1 +0,0 @@
inside YoupiBanane/nop/other.pouic

View File

@@ -1 +0,0 @@
inside YoupiBanane/nop/youpi.bad_extension

View File

@@ -1 +0,0 @@
inside YoupiBanane/youpi.bad_extension

View File

@@ -1 +0,0 @@
inside YoupiBanane/youpi.bla

View File

@@ -16,7 +16,6 @@ server {
location /board {
allow_methods GET;
root ./www/html;
cgi_ext php cgi
}
location /board/content {

View File

@@ -1,10 +0,0 @@
telnet> Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
HTTP/1.1 413 Content Too Large
Server: Webserv/0.1
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 197
<!DOCTYPE html><html><head><title>413 Content Too Large</title></head><body><h1 style="text-align:center">413 Content Too Large</h1><hr><p style="text-align:center">Le Webserv/0.1</p></body></html>

View File

@@ -1,106 +1,17 @@
server {
# this is a comment
server_name server1;
server_name our_server;
listen 0.0.0.0:4040;
# client_body_limit asdfa;
client_body_limit 5000;
# Max == 18446744073709551615 / 1024 == 18014398509481984
# client_body_limit 400;
index index.html; # this is another comment
#index mdr.html; # this is another comment
root ./www/;
error_page 404 ./www/error_pages/error_404.html;
location / {
allow_methods GET;
root ./www/;
}
location /srcs/cgi-bin/ {
root ./srcs/cgi-bin/;
allow_methods POST;
cgi_ext php;
}
location /list {
autoindex on;
}
location /cgi-bin {
root ./srcs/cgi-bin/;
cgi_ext out php sh;
}
location /upload {
allow_methods POST;
autoindex on;
upload_dir ./www/user_files/;
# root doesnt matter if used only with POST and no CGI
}
location /the_dump {
allow_methods GET DELETE;
root ./www/user_files;
autoindex on;
}
location /redirect {
redirect 307 https://fr.wikipedia.org/wiki/Ketchup;
# redirect 307 https://www.youtube.com/watch?v=rG6b8gjMEkw;
}
location /test {
index index1.html subdex.html;
root ./www/test/;
}
location /stylesheet {
root ./stylesheet/;
}
location /test/index1.html {
root ./www/test/index1.html;
index index1.html subdex.html;
}
location /hilarious_404/ {
redirect 301 https://berniesanders.com/404/;
}
location /test/something.html {
# allow_methods DELETE;
root ./www/test/something.html;
}
location /test/test_deeper/ {
# allow_methods
root ./www/test/test_deeper/;
}
location /test/test_deeper/super_deep {
root ./www/test/test_deeper/super_deep/;
}
# location /test/test_deeper/something.html {
# allow_methods DELETE;
# }
allow_methods GET;
}
server {
server_name server2;
listen 0.0.0.0:4040;
index index.html;
root ./www2/;
}

View File

@@ -0,0 +1,11 @@
<!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>

View File

@@ -1,117 +0,0 @@
#! /bin/bash
# import and run all tests
##########
# Colors
##########
_GREY='\033[30m'
_RED='\033[0;31m'
_GREEN='\033[32m'
_YELLOW='\033[33m'
_BLUE='\033[34m'
_PURPLE='\033[35m'
_CYAN='\033[36m'
_WHITE='\033[37m'
_END='\033[0m'
#test_file_names=("test_template.sh" "test_header.sh" "test_path.sh")
#test_file_names=("test_method.sh" "test_header.sh" "test_path.sh")
test_file_names=("test_body.sh")
#test_file_names=("test_valid_uri.sh")
#test_file_names=("test_path.sh")
test_files=()
expected_result_files=()
fill_test_files()
{
for i in "${test_file_names[@]}"
do
test_files+=("./Tester/$i")
done
}
# needs to be invoked for each test_file!
# acutally kinda useless...
fill_expected_files()
{
for i in "${local_expected_test_files[@]}"
do
expected_result_files+=("./Tester/expected_results/$i")
done
}
test_all()
{
arg=${1}
c=0
make
rm -rf telnet.log
rm -rf webserv.log
rm -rf compare.txt
rm -rf expected.txt
fill_test_files
for i in "${test_files[@]}"
do
source $i
source ./Tester/telnet_test.sh
./webserv $config_file &>> webserv.log &
echo -e "${_GREEN}Running Telnet Test on '$test_name'${_END}"
sleep 1
run | telnet | tee compare.txt >> telnet.log
# run | telnet > compare.txt >> telnet.log
# run | telnet >> telnet.log
pkill webserv
echo -e "\n\n------\n" &>> webserv.log
fill_expected_files
expected_result_file="./Tester/expected_results/$file"
if [ "$arg" = "diff" ];
then
DIFF=$(diff -q compare.txt $expected_result_file)
if [ "$DIFF" == "" ];
then
echo "Good diff"
else
diff compare.txt $expected_result_file
fi
elif [ "$arg" = "create" ];
then
if ! test -f $expected_result_file;
then
cat compare.txt > $expected_result_file
fi
elif [ "$arg" = "replace" ];
then
cat compare.txt > $expected_result_file
fi
c+=1
done
if [ "$arg" = "" ];
then
cat telnet.log
fi
}
test_all $1

View File

@@ -1,78 +1,7 @@
----Priorité élevée------------------------
- CGI (TODO HUGO)
- un thread par serveur présent dans le fichier de config ?
- Need to test normal body parsing (Verif avec HUGO)
----------------
- curl --resolve, for testing hostname
curl -v --resolve server1:4040:127.0.0.1 --resolve server2:4040:127.0.0.1 server1:4040
- test limit de connexions sur listen()
- handle redirection (Work, but weird behavior need deeper test)
- Ecrire des tests !
- cgi_cpp_status.cpp with POST dont show error page. Normal or not ?
For non blocking CGI :
// We could maybe,
// add FD_RD_FR_CHLD to epoll,
// return to the main loop,
// read FD_RD_FR_CHLD each time epoll say its ready,
// then try waitpid() with WNOHANG after each read.
// when waitpid() tell us its finish (or maybe when epoll return EPOLLHUP)
// then actually parse the script_output and send it to the client.
- check status in autoindex
- merge changes from hugo5 to master (attention a pas casse svp :clown:)
----Priorité modérée------------------------
- namespace utils ?
- change "std::string" to reference "std::string &" in most functions
and add "const" if apropriate.
- peut-être check si ip > 32bits
----Priorité faible------------------------
- idealy, we should not clear() raw_request after a response,
but just the part we actually parsed for this response.
I think the client could send multiples request on the same connection one after the other without waiting for response.
So raw_request could contain more than the first request we handle.
- chunked request (need testing)
- client_body_limit 0 valeur special pour desactiver dans config
- 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)
- 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.
- 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.
Valgrind error RESOLVED ! :
==847174== 1,314 bytes in 1 blocks are definitely lost in loss record 1 of 1
==847174== at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==847174== by 0x49AA35D: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==847174== by 0x49ABB52: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==847174== by 0x426BC5: Webserv::_read_cgi_output(cgi_pipe_rfd&) (run_loop.cpp:39) // (LUKE: its an string.append() call)
==847174== by 0x427962: Webserv::run() (run_loop.cpp:153)
==847174== by 0x4052E9: main (main.cpp:38)
RESOLVED, AGAIN :D !
--loop epoll()
EPOLLHUP on client fd 8
47067----loop epoll()
EPOLLHUP on client fd 8
47068----loop epoll()
EPOLLHUP on client fd 8
47069----loop epoll()
EPOLLHUP on client fd 8
47070----loop epoll()
EPOLLHUP on client fd 8
47071----loop epoll()
EPOLLHUP on client fd 8
47072----loop epoll()
EPOLLHUP on client fd 8
47073----loop epoll()
----------------
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,53 +0,0 @@
POST /upload HTTP/1.1
Host: localhost:4040
Connection: keep-alive
Content-Length: 364
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="104", " Not A;Brand";v="99", "Google Chrome";v="104"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
Origin: http://localhost:4040
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeIV4xrEzThmNUcJf
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://localhost:4040/upload_form.html
Accept-Encoding: gzip, deflate, br
Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7
dnt: 1
sec-gpc: 1
------WebKitFormBoundaryeIV4xrEzThmNUcJf
Content-Disposition: form-data; name="upload_file"; filename=".gitignore"
Content-Type: text/plain
.DS_Store
Thumbs.db
*.o
*.d
*.swp
*.out
*.exe
*.stackdump
*.a
*.so
*.dSYM
.vscode
*.lnk
*.zip
builds
ubuntu_tester
ubuntu_cgi_tester
webserv
!**/webserv/
*.log
large.jpg
------WebKitFormBoundaryeIV4xrEzThmNUcJf--

View File

@@ -5,37 +5,7 @@
* COPLIENS
*********************************************/
Client::Client()
: status(0),
header_complete(false),
body_complete(false),
request_complete(false),
assigned_server(NULL),
assigned_location(NULL),
cgi_pipe_rfd(0),
cgi_pid(0),
_fd(0),
_port(""),
_ip(""),
_lsocket(NULL)
{
return;
}
Client::Client(int afd, listen_socket *lsocket, std::string aport, std::string aip)
: status(0),
header_complete(false),
body_complete(false),
request_complete(false),
assigned_server(NULL),
assigned_location(NULL),
cgi_pipe_rfd(0),
cgi_pid(0),
_fd(afd),
_port(aport),
_ip(aip),
_lsocket(lsocket)
{
Client::Client( ) {
return;
}
@@ -43,519 +13,104 @@ Client::~Client() {
return;
}
/* // WIP not sure fo what is more logic here
Client::Client( Client const & src )
: status ( src.status ),
header_complete ( src.header_complete ),
assigned_server ( src.assigned_server ),
assigned_location ( src.assigned_location ),
_fd ( src._fd ),
_port ( src._port ),
_ip ( src._ip ),
_lsocket ( src._lsocket )
{
raw_request = src.raw_request;
response = src.response;
// buf = strdup(src.buf); // TODO: this doesn't work
return;
}
*/
// copy constructor :
// Client::Client( Client const & src ) {}
// assignement operator :
// Client & Client::operator=( Client const & rhs ) {}
/* // WIP placeholder because of const values
Client & Client::operator=( Client const & rhs )
{
if ( this != &rhs )
{
// stuff
}
return *this;
}
*/
/*********************************************
* PUBLIC MEMBER FUNCTIONS
*********************************************/
/* HTTP Headers :
https://www.iana.org/assignments/http-fields/http-fields.xhtml
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests
https://www.tutorialspoint.com/http/http_requests.htm
*/
void Client::parse_request_headers(std::vector<ServerConfig> &servers)
// http headers :
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
// https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests
// https://www.tutorialspoint.com/http/http_requests.htm
void Client::parse_request()
{
if (raw_request.find(CRLF CRLF) == NPOS)
return ;
header_complete = true;
clear_request(); // not mandatory
std::string sub;
std::vector<std::string> list;
size_t pos;
_parse_request_line();
if (status)
return;
_parse_request_fields();
// DEBUG
// print_client("headers");
if (status)
return;
assigned_server = _determine_process_server(this, servers);
assigned_location = _determine_location(*assigned_server, _request.abs_path);
_check_request_errors();
if (status)
return;
_parse_port_hostname(get_rq_headers("Host")); // use getter for headers because it works case insensitive
// DEBUG
// std::cerr << get_rq_method_str() << " " << get_rq_target() << " " << get_rq_version() << "\n";
// dont clear raw_request, we need it for future reparsing of body
// see call of parse_request() in _read_request()
// raw_request.clear();
}
void Client::parse_request_body()
{
std::cerr << "parse_request_body()\n";
size_t pos;
pos = raw_request.find(CRLF CRLF);
if (pos == NPOS)
{
std::cerr << "parse_request_body() bad call, header incomplete\n";
// QUESTION from hugo : don't we change the status here ?
// RESPONSE from luke : C'est vrai. Peut-être mettre un 500, c'etait plus du debug à la base.
// C'est seulement si on appelle la fonction au mauvais endroit, avant d'avoir un header complet, que ça arrive.
return;
}
if (!get_rq_headers("Transfer-Encoding").empty()
&& get_rq_headers("Transfer-Encoding") == "chunked")
{
// Chunked decoding WIP. How to test this ? dont know how to send chunks with telnet.
_parse_chunked_body(pos + CRLF_SIZE*2);
}
else if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10))
{
if (get_rq_headers("Content-Type").find("multipart/form-data") != NPOS)
_parse_multipart_body(pos);
else
_request.body = raw_request.substr(pos + CRLF_SIZE*2);
body_complete = true;
}
// std::cerr << "Content-Length = " << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n";
// std::cerr << "raw_request.size() - pos = " << raw_request.size() - pos << "\n";
// _request.body = raw_request.substr(pos);
// std::cerr << "_request.body.size() = " << _request.body.size() << "\n";
///////////////
// Body checks
if (_request.body.size() > assigned_server->client_body_limit)
status = 413; // HTTP Client Errors
}
void Client::_parse_chunked_body(size_t pos)
{
size_t chunk_size = 1;
size_t chunk_field_end = 0;
char *endptr = NULL;
char *endptr_copy = NULL;
/* TODO: verify if last chunk in raw_request (to avoid multiples complete parsing)
but how ? with "raw_request.rfind("0" CRLF CRLF)", there no confirmation
that we have found the last last-chunk OR just some data */
_request.body = raw_request.substr(pos);
std::cerr << "______Chunked\n" << _request.body << "\n______\n";
pos = 0;
while (chunk_size != 0)
{
/* if (pos > _request.body.size())
{
std::cerr << "parse_request_body(), pos > size()\n";
// status = 400;
return;
} */
/*
if (pos == _request.body.size())
{
std::cerr << "parse_request_body(), will reread till last chunk\n";
return;
} */
/* endptr_copy = endptr; */
(void)endptr_copy;
chunk_size = std::strtoul(&_request.body[pos], &endptr, 16);
if (errno == ERANGE)
{
status = 413;
return ;
}
/* if (endptr == endptr_copy)
{
std::cerr << "parse_request_body(), no conversion possible\n";
return;
} */
chunk_field_end = _request.body.find(CRLF, pos);
if (chunk_field_end == NPOS)
{
std::cerr << "parse_request_body(), chunk_field no CRLF\n";
// status = 400;
return;
}
chunk_field_end += CRLF_SIZE;
_request.body.erase(pos, chunk_field_end);
pos += chunk_size + CRLF_SIZE;
}
_request.headers.erase("Transfer-Encoding");
body_complete = true;
pos = (raw_request).find("\r\n\r\n");
sub = (raw_request).substr(0, pos);
list = split(sub, '\n');
// request_line
_parse_request_line(*list.begin());
list.erase(list.begin());
// headers
_parse_request_headers(list);
//body- message
_parse_request_body(pos + 4);
}
void Client::fill_script_path(std::string &path, size_t pos)
{
std::string tmp;
_request.script.path = path.substr(0, pos);
_request.script.info = path.substr(pos);
}
void Client::clear()
{
clear_request();
raw_request.clear();
response.clear();
status = 0;
header_complete = false;
body_complete = false;
request_complete = false;
assigned_server = NULL;
assigned_location = NULL;
cgi_pipe_rfd = 0;
cgi_pid = 0;
cgi_output.clear();
}
void Client::clear_request()
{
clear_script();
_request.method = UNKNOWN;
_request.target.clear();
_request.version.clear();
_request.headers.clear();
_request.body.clear();
_request.multi_bodys.clear();
_request.abs_path.clear();
_request.query.clear();
_request.port.clear();
_request.hostname.clear();
}
void Client::clear_script()
{
_request.script.path.clear();
_request.script.info.clear();
}
// debug
void Client::print_client(std::string message)
{
std::map<std::string, std::string>::iterator it;
std::cout << "\n=== DEBUG PRINT CLIENT ===\n";
std::cout << message << ":\n----------\n\n" << "raw_request:\n__\n";
::print_special(raw_request);
std::cout << "\n__\n"
<< "get_cl_fd() : [" << get_cl_fd() << "]\n"
<< "get_cl_port() : [" << get_cl_port() << "]\n"
<< "get_cl_ip() : [" << get_cl_ip() << "]\n"
<< "get_rq_method_str() : [" << get_rq_method_str() << "]\n"
<< "get_rq_target() : [" << get_rq_target() << "]\n"
<< "get_rq_abs_path() : [" << get_rq_abs_path() << "]\n"
<< "get_rq_query() : [" << get_rq_query() << "]\n"
<< "get_rq_version() : [" << get_rq_version() << "]\n"
<< "get_rq_body() : [" << get_rq_body() << "]\n"
<< "get_rq_port() : [" << get_rq_port() << "]\n"
<< "get_rq_hostname() : [" << get_rq_hostname() << "]\n"
<< "get_rq_script_path() : [" << get_rq_script_path() << "]\n"
<< "get_rq_script_info() : [" << get_rq_script_info() << "]\n"
<< "headers :\n";
for (it = _request.headers.begin(); it != _request.headers.end(); it++)
std::cout << " " << it->first << ": [" << it->second << "]\n";
std::cout << "\n=== END PRINT CLIENT ===\n\n";
}
/*********************************************
* GETTERS
*********************************************/
// client side
int Client::get_cl_fd() const { return _fd; }
const std::string & Client::get_cl_ip() const { return _ip; }
const std::string & Client::get_cl_port() const { return _port; }
const listen_socket * Client::get_cl_lsocket() const { return _lsocket; }
// requette
http_method Client::get_rq_method() const { return _request.method; }
std::string Client::get_rq_method_str() const
{ return ::http_methods_to_str(_request.method); }
std::string Client::get_rq_target() const { return _request.target; }
std::string Client::get_rq_abs_path() const { return _request.abs_path; }
std::string Client::get_rq_query() const { return _request.query; }
std::string Client::get_rq_version() const { return _request.version; }
std::string Client::get_rq_body() const { return _request.body; }
std::string Client::get_rq_port() const { return _request.port; }
std::string Client::get_rq_hostname() const { return _request.hostname; }
std::string Client::get_rq_script_path()const { return _request.script.path; }
std::string Client::get_rq_script_info()const { return _request.script.info; }
std::string Client::get_rq_headers(const std::string & key) const
{
std::map<std::string, std::string>::const_iterator it;
it = _request.headers.find(::str_tolower(key));
if (it == _request.headers.end())
return "";
return it->second;
}
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]; }
/*********************************************
* PRIVATE MEMBER FUNCTIONS
*********************************************/
void Client::_parse_request_line()
void Client::_parse_request_line( std::string rline )
{
std::vector<std::string> line;
std::string raw_line;
std::vector<std::string> sline;
std::string tmp;
raw_line = ::get_line(raw_request, 0, CRLF);
line = ::split_trim(raw_line, " ");
if (line.size() != 3)
sline = split(rline, ' ');
if (sline.size() != 3)
{
std::cerr << "err _parse_first_line(): wrong number of elements (" << line.size() << " instead of 3)\n";
status = 400; // "bad request"
std::cerr << "err _parse_request_line(): ";
throw std::runtime_error("bad request-line header");
}
else
// method
tmp = ::trim(sline[0], ' ');
tmp = ::trim(tmp, '\r');
_request.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
tmp = ::trim(sline[1], ' ');
tmp = ::trim(tmp, '\r');
_request.path = tmp;
// http version
tmp = ::trim(sline[2], ' ');
tmp = ::trim(tmp, '\r');
_request.version = tmp;
}
void Client::_parse_request_headers( std::vector<std::string> list )
{
std::string key;
std::string val;
std::vector<std::string>::iterator it;
size_t pos;
for (it = list.begin(); it != list.end(); it++)
{
_request.method = str_to_http_method(line[0]);
_request.target = line[1];
_parse_request_target(line[1]);
_request.version = line[2];
pos = (*it).find(':');
key = (*it).substr( 0, pos );
key = ::trim(key, ' ');
key = ::trim(key, '\r');
val = (*it).substr( pos + 1 );
val = ::trim(val, ' ');
val = ::trim(val, '\r');
_request.headers.insert( std::pair<std::string, std::string>(key, val) );
}
}
void Client::_parse_request_target( std::string target )
void Client::_parse_request_body( size_t pos )
{
size_t pos;
std::string body = &raw_request[pos];
pos = target.find("?");
if (pos != NPOS)
_request.query = target.substr(pos + 1);
else
_request.query = "";
_request.abs_path = target.substr(0, pos);
if (_request.abs_path[_request.abs_path.size() - 1] == '/')
_request.abs_path.erase(_request.abs_path.size() - 1);
_request.body = body;
}
void Client::_parse_request_fields()
{
std::string headers;
size_t pos;
int ret;
headers = raw_request;
// delete first line
pos = headers.find(CRLF);
if (pos != NPOS)
headers.erase(0, pos + CRLF_SIZE);
// delete body part
pos = headers.find(CRLF CRLF);
if (pos != NPOS)
headers.erase(pos);
else {
std::cerr << "err _parse_request_fields(): request header doesn't end with empty line\n";
status = 400; // "bad request"
}
// copy result of parser into headers
ret = ::parse_http_headers(headers, _request.headers);
if (ret > 0) {
std::cerr << "err _parse_request_fields(): " << ret << " fields are bad formated\n";
status = 400; // "bad request"
}
::str_map_key_tolower(_request.headers);
}
// TODO : I think its now useless. Probably to delete.
void Client::_parse_port_hostname(std::string host)
{
size_t pos;
if (host == "")
std::cerr << "no host\n";
pos = host.find(':');
// port :
if (pos == NPOS)
_request.port = "4040"; // TODO: make equal to default port in config
else
_request.port = host.substr(pos);
if (_request.port == ":")
_request.port = "";
// hostname :
_request.hostname = host.substr(0, pos);
}
void Client::_check_request_errors()
{
// /* Debug */ std::cerr << "Content-Length=" << get_rq_headers("Content-Length") << "\n";
// /* Debug */ std::cerr << "strtoul=" << std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) << "\n";
// /* Debug */ std::cerr << "client_body_limit=" << assigned_server->client_body_limit << "\n";
///////////////////////
// Request line checks
if (_request.method == UNKNOWN)
status = 501; // HTTP Client Errors
else if (_request.version.compare(0, sizeof("HTTP/1") - 1, "HTTP/1") != 0)
status = 505; // HTTP Client Errors
else if (!(assigned_location->allow_methods & _request.method))
{
status = 405; // HTTP Client Errors
response.append("Allow: ");
response.append(::http_methods_to_str(assigned_location->allow_methods));
response.append(CRLF);
}
else if (assigned_location->redirect_status)
{ // Weird behavior. Sometimes, the web browser seems to wait for a complete response until timeout.
// (for codes 301, 302, 303, 307, and 308)
status = assigned_location->redirect_status;
response.append("Location: ");
response.append(assigned_location->redirect_uri);
response.append(CRLF CRLF);
}
//////////////////
// Headers checks
else if (!get_rq_headers("Content-Length").empty()
&& std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit)
status = 413;
else if (!get_rq_headers("Transfer-Encoding").empty()
&& get_rq_headers("Transfer-Encoding") != "chunked" )
status = 501;
else if (!get_rq_headers("Content-Encoding").empty())
{
status = 415;
response.append("Accept-Encoding:"); // empty, no encoding accepted
response.append(CRLF);
}
return;
}
ServerConfig *Client::_determine_process_server(Client *client, std::vector<ServerConfig> &servers)
{
/*
Behavior like this :
http://nginx.org/en/docs/http/request_processing.html
*/
std::string server_name = client->get_rq_headers("Host");
// /* Debug */ std::cerr << "server_name = " << server_name << "\n";
size_t pos = server_name.rfind(':');
if (pos != NPOS)
server_name.erase(pos);
// /* Debug */ std::cerr << "server_name = " << server_name << "\n";
std::vector<ServerConfig>::iterator it = servers.begin();
std::vector<ServerConfig>::iterator default_server = servers.end();
while (it != servers.end())
{
if (it->host == client->get_cl_lsocket()->host
&& it->port == client->get_cl_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));
}
// const?
const LocationConfig *Client::_determine_location(const ServerConfig &server, const std::string &path)
{
/* 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
New Rule for location paths, they never end in /
Sooo
If we get a url that ends in / ignore the last /
*/
std::string uri = path;
if (uri[uri.size() - 1] == '/' && uri.size() != 1)
uri.erase(uri.size() - 1);
for (std::vector<LocationConfig>::const_iterator it = server.locations.begin(); it != server.locations.end(); it++)
{
// std::cout << it->path << " -- ";
if (it->path.size() > uri.size())
continue ;
if (uri.compare(0, it->path.size(), it->path) == 0)
{
if (it->path.size() == uri.size())
return (&(*it));
else if (uri[it->path.size()] == '/')
return (&(*it));
// this works cuz only ever looking for a / burried in a longer path
}
}
return (&(server.locations.back()));
// /test/mdr
// /test/mdr/
// /test/mdrBST
/////// More stuff to check this still works with
// /test/test_
// /test/test_/
// /test/test_deeper
// /test/test_deeper/
// /test/test_deepei
// /test/test_deepei/
// /test/test_deeperi
// /test/test_deeper/super_deep/
// /test/aaaaaaaaaaa/super_deep/
}
/*********************************************
* OVERLOAD
*********************************************/
bool operator==(const Client& lhs, const Client& rhs)
{ return lhs.get_cl_fd() == rhs.get_cl_fd(); }
bool operator==(const Client& lhs, int fd)
{ return lhs.get_cl_fd() == fd; }
bool operator==(int fd, const Client& rhs)
{ return fd == rhs.get_cl_fd(); }

View File

@@ -6,113 +6,45 @@
# include <string>
# include <map>
# include <vector>
# include <string.h> // strdup
# include <netinet/in.h> // sockaddr_in, struct in_addr
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa
# include "utils.hpp"
# include "ServerConfig.hpp"
# include "colors.h"
struct Script
{
std::string path;
std::string info;
};
struct MultipartBody
{
std::map<std::string, std::string> headers;
std::string body;
};
struct Request
{
http_method method;
std::string target;
std::string abs_path;
std::string query;
std::string version;
std::map<std::string, std::string> headers;
std::string method;
std::string path;
std::string version;
std::string body;
std::vector<MultipartBody> multi_bodys;
std::string port;
std::string hostname;
struct Script script;
};
class Client
{
public:
Client();
Client(int afd, listen_socket *lsocket, std::string aport, std::string aip);
~Client();
// Client(Client const &src);
// Client &operator=(Client const &rhs);
//Client(Client const &src);
//Client &operator=(Client const &rhs);
std::string raw_request;
std::string response;
unsigned int status;
bool header_complete;
bool body_complete;
bool request_complete;
// size_t read_body_size; // unused for now
ServerConfig *assigned_server; // cant be const cause of error_pages.operator[]
const LocationConfig *assigned_location;
int cgi_pipe_rfd;
pid_t cgi_pid;
std::string cgi_output;
int fd;
std::string raw_request;
std::string response;
unsigned int status;
// getters
int get_cl_fd() const;
const std::string & get_cl_port() const;
const std::string & get_cl_ip() const;
const listen_socket * get_cl_lsocket() const;
std::string get_method();
std::string get_path();
std::string get_version();
std::string get_body();
std::string get_headers(std::string key);
// requests getters
http_method get_rq_method() const;
std::string get_rq_method_str() const;
std::string get_rq_target() const;
std::string get_rq_abs_path() const;
std::string get_rq_query() const;
std::string get_rq_version() const;
std::string get_rq_body() const;
std::string get_rq_port() const;
std::string get_rq_hostname() const;
std::string get_rq_script_path() const;
std::string get_rq_script_info() const;
std::string get_rq_headers(const std::string & key) const;
const std::vector<MultipartBody> &get_rq_multi_bodys() const;
const std::string get_rq_multi_bodys_headers(const std::string & key, std::vector<MultipartBody>::const_iterator body_it) const;
void parse_request_headers(std::vector<ServerConfig> &servers);
void parse_request_body();
void clear();
void clear_request();
void clear_script();
void fill_script_path(std::string &path, size_t pos);
// DEBUG
void print_client(std::string message = "");
void parse_request();
private:
int _fd;
std::string _port;
std::string _ip;
listen_socket * _lsocket;
struct Request _request;
void _parse_request_line();
void _parse_request_fields();
void _parse_request_target( std::string target );
void _parse_port_hostname(std::string host);
void _parse_chunked_body(size_t pos);
void _parse_multipart_body(size_t pos);
void _check_request_errors();
void _parse_request_line( std::string rline );
void _parse_request_headers( std::vector<std::string> list );
void _parse_request_body( size_t pos );
ServerConfig*
_determine_process_server(Client *client, std::vector<ServerConfig> &servers);
const LocationConfig*
_determine_location(const ServerConfig &server, const std::string &path);
};
bool operator==(const Client& lhs, const Client& rhs);

View File

@@ -1,103 +0,0 @@
#include "Client.hpp"
const std::vector<MultipartBody> &Client::get_rq_multi_bodys() const { return _request.multi_bodys; }
const std::string Client::get_rq_multi_bodys_headers(const std::string & key, std::vector<MultipartBody>::const_iterator body_it) const
{
std::map<std::string, std::string>::const_iterator it;
it = body_it->headers.find(::str_tolower(key));
if (it == body_it->headers.end())
return ""; // IF return reference compiler "warning: returning reference to local temporary"
return it->second;
}
void Client::_parse_multipart_body(size_t pos)
{
/*
** Parsing roughly like described in :
** https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
*/
MultipartBody new_body;
std::string boundary;
size_t start_pos;
size_t end_pos;
std::string tmp;
size_t tmp_pos;
size_t ret;
// Get boundary
boundary = get_rq_headers("Content-Type");
start_pos = boundary.find("boundary=");
if (start_pos == NPOS)
{
status = 400; std::cerr << "_parse_multipart_body() error 1\n";
return;
}
start_pos += sizeof("boundary=")-1;
boundary = boundary.substr(start_pos);
std::cerr << "boundary =|" << boundary << "|\n";
// Search boundary
start_pos = raw_request.find("--" + boundary, pos);
if (start_pos == NPOS || start_pos + sizeof("--")-1 + boundary.size() > raw_request.size())
{
status = 400; std::cerr << "_parse_multipart_body() error 2\n";
return;
}
start_pos += sizeof("--")-1 + boundary.size() + CRLF_SIZE;
while (1) // TODO : test loop for multi body
{
end_pos = raw_request.find("--" + boundary, start_pos);
if (end_pos == NPOS)
{
status = 400; std::cerr << "_parse_multipart_body() error 3\n";
return;
}
/* // Maye useful for multi body (remove "start_pos - CRLF_SIZE" if used)
end_pos = raw_request.rfind(CRLF, end_pos); if (end_pos == NPOS) {status = 400; return; } */
new_body.body = raw_request.substr(start_pos, end_pos - start_pos - CRLF_SIZE);
// Split headers from body
tmp_pos = new_body.body.find(CRLF CRLF);
if (tmp_pos != NPOS)
{
ret = ::parse_http_headers(new_body.body.substr(0, tmp_pos), new_body.headers);
::str_map_key_tolower(new_body.headers);
if (ret)
{
status = 400; std::cerr << "_parse_multipart_body() error 4\n";
return;
}
tmp_pos += CRLF_SIZE*2;
new_body.body.erase(0, tmp_pos);
// ::print_map(new_body.headers);
}
else
{ // No headers case
tmp_pos = new_body.body.find(CRLF);
if (tmp_pos != 0)
{
status = 400; std::cerr << "_parse_multipart_body() error 5\n";
return;
}
}
_request.multi_bodys.push_back(new_body);
// Move start for next loop
start_pos = end_pos + sizeof("--")-1 + boundary.size();
if ( start_pos + 2 + CRLF_SIZE == raw_request.size()
&& raw_request[start_pos] == '-'
&& raw_request[start_pos+1] == '-')
break;
/* ::print_special(raw_request);
std::cerr << "start_pos = " << start_pos << "\n";
std::cerr << "raw_request.size() = " << raw_request.size() << "\n";
std::cerr << raw_request.substr(start_pos); */
}
}

400
srcs/ConfigParser.cpp Normal file
View File

@@ -0,0 +1,400 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* ConfigParser.cpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* 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 */
/* */
/* ************************************************************************** */
#include "ConfigParser.hpp"
// Default
ConfigParser::ConfigParser()
{
std::cout << "Default Constructor\n";
// don't use yet, you have no idea what the defaults are
}
ConfigParser::ConfigParser(const char* path)
{
std::cout << "Param Constructor\n";
std::ifstream file;
std::string buf;
size_t comment;
_content.clear();
file.open(path);
if (file.is_open())
{
// are there more throws i need to add in case of errors, what would
// those errors be?
while (!file.eof())
{
getline(file, buf);
// remove # comments here.
if ((comment = buf.find_first_of("#")) == std::string::npos)
{
// remove empty lines, i think...
if ((buf.find_first_not_of(" \t")) != std::string::npos)
_content.append(buf + '\n');
}
else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment)
{
// is there a comment at the end of the line
std::string tmp = buf.substr(0, comment - 1);
_content.append(tmp + '\n');
}
}
file.close();
}
else
throw std::invalid_argument("open config");
}
ConfigParser::~ConfigParser()
{
// do i need to destroy anything, won't it handle itself?
}
/*
ConfigParser & ConfigParser::operator=(const ConfigParser& rhs)
{
if (this == rhs) // * & ?
return (*this); // * ?
// make some stuff equal
return (*this);
}
*/
std::vector<ServerConfig> * ConfigParser::parse()
{
std::vector<ServerConfig> * ret = new std::vector<ServerConfig>();
// std::vector<ServerConfig> ret;
size_t start = 0;
size_t curr = _content.find_first_not_of(" \t\n", 0);
if (curr == std::string::npos)
throw std::invalid_argument("empty config file");
while (curr != std::string::npos)
{
// why no checks here
// if not here do i need them elsewhere?
start = _content.find_first_not_of(" \t\n", curr);
curr = _content.find_first_of(" \t\n", start);
std::string key = _content.substr(start, curr - start);
if (key != "server")
throw std::invalid_argument("bad config file arguments 1");
ret->push_back(_parse_server(&curr));
}
_post_processing(ret);
return (ret);
}
ServerConfig ConfigParser::_parse_server(size_t *start)
{
ServerConfig ret;
size_t curr = _content.find_first_not_of(" \t\n", *start);
ret.client_body_limit = 0;
ret.autoindex = false;
if (curr == std::string::npos || _content[curr] != '{')
throw std::invalid_argument("bad config file syntax 1");
curr = _content.find_first_of(" \t\n", curr + 1);
// if (curr == std::string::npos) // are there other things to check for?
// throw std::invalid_argument("bad config file syntax");
while (curr != std::string::npos) // here curr == { + 1
{
// so this moves curr to past the word...
std::string key = _get_first_word(&curr);
// now curr is on space after 1st word.
if (key == "}")
{
// why +1 curr is already after it no?
*start = _content.find_first_not_of(" \t\n", curr + 1);
break ;
}
else if (key == "location")
ret.locations.push_back(_parse_location(&curr));
else
{
std::string values = _get_rest_of_line(&curr);
// curr now should be \n
_set_server_values(&ret, key, values);
}
}
return (ret);
}
LocationConfig ConfigParser::_parse_location(size_t *start)
{
LocationConfig ret;
size_t curr = *start;
// start is after the 1st word aka "location"
ret.client_body_limit = 0;
ret.redirect_status = 0;
ret.path = _get_first_word(&curr);
// in theory now curr should be right after the "path"
curr = _content.find_first_not_of(" \t\n", curr);
if (curr == std::string::npos || _content[curr] != '{')
throw std::invalid_argument("bad config file syntax 2");
curr = _content.find_first_of(" \t\n", curr + 1);
// if (curr == std::string::npos) // are there other things to check for?
// throw std::invalid_argument("bad config file syntax");
while (curr != std::string::npos)
{
// so this moves curr to past the word...
std::string key = _get_first_word(&curr);
// now curr is on space after 1st word.
if (key == "}")
{
*start = curr;
break ;
}
else
{
std::string values = _get_rest_of_line(&curr);
// curr now should be \n
_set_location_values(&ret, key, values);
}
}
return (ret);
}
void ConfigParser::_set_server_values(ServerConfig *server, \
const std::string key, std::string value)
{
value = _pre_set_val_check(key, value);
std::vector<std::string> tmp_val = ::split(value, ' ');
size_t size = tmp_val.size();
if (size < 1)
throw std::invalid_argument("missing value");
else if (key == "server_name" && size == 1)
{
for (size_t i = 0; i < server->server_name.size(); i++)
{
if (server->server_name[i].compare(tmp_val[0]) == 0)
throw std::invalid_argument("server_name already exists");
}
server->server_name.push_back(tmp_val[0]);
}
else if (key == "listen" && size == 1 && server->host == "" \
&& server->port == "")
{
if (tmp_val[0].find_first_of(":") == std::string::npos)
{
// should i limit which ports can be used?
if (!::isNumeric(tmp_val[0]))
throw std::invalid_argument("bad port number");
server->host = "0.0.0.0";
server->port = tmp_val[0];
}
else
{
std::vector<std::string> tmp2 = ::split(tmp_val[0], ':');
std::vector<std::string> ip = ::split(tmp2[0], '.');
if (ip.size() != 4)
throw std::invalid_argument("bad host ip");
for (size_t i = 0; i < ip.size(); i++)
{
if (!::isNumeric_btw(0, 255, ip[i]))
throw std::invalid_argument("bad host ip");
}
if (!::isNumeric(tmp2[1]))
throw std::invalid_argument("bad port number");
server->host = tmp2[0];
server->port = tmp2[1];
}
}
else if (key == "root" && size == 1 && server->root == "")
{
DIR* dir = opendir(tmp_val[0].c_str());
if (dir)
closedir(dir);
else
throw std::invalid_argument("root dir could not be opened");
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 \
&& server->client_body_limit == 0)
{
if (!::isNumeric(tmp_val[0]))
throw std::invalid_argument("client_body_limit not a number");
server->client_body_limit = atoi(tmp_val[0].c_str());
}
else if (key == "index")
{
// 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++)
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++)
{
MethodType m = _str_to_method_type(tmp_val[i]);
if (m == 3)
throw std::invalid_argument("not a valid method");
server->allow_methods.push_back(m);
}
}
else if (key == "error_page")
{
// so it can either be just a /here/is/the/repo
// or it can be http://some_domain.com/here
// wtf... how should we handle...
// you can definitely call Error_pages several times, i think
std::string path = tmp_val[tmp_val.size() - 1];
for (unsigned long i = 0; i != tmp_val.size() - 1; i++)
{
// what are the bounds for Error codes?
if (!(isNumeric_btw(0, 600, tmp_val[i])))
throw std::invalid_argument("value not a valid number");
int status_code = atoi(tmp_val[i].c_str());
// yea cuz here we continue.. why suddenly flexible not throw ?
if (server->error_pages.find(status_code) != server->error_pages.end())
continue ;
server->error_pages[status_code] = path;
}
}
/* else if (key == "recv_timeout" && size == 1 && server->server_name == "")
{
// 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");
}
}
void ConfigParser::_set_location_values(LocationConfig *location, \
const std::string key, std::string value)
{
value = _pre_set_val_check(key, value);
std::vector<std::string> tmp_val = ::split(value, ' ');
size_t size = tmp_val.size();
if (size < 1)
throw std::invalid_argument("missing value");
else if (key == "root" && size == 1 && location->root == "")
{
DIR* dir = opendir(tmp_val[0].c_str());
if (dir)
closedir(dir);
else
throw std::invalid_argument("root dir could not be opened");
location->root = tmp_val[0];
}
else if (key == "client_body_limit" && size == 1 \
&& location->client_body_limit == 0)
{
if (!::isNumeric(tmp_val[0]))
throw std::invalid_argument("client_body_limit not a number");
location->client_body_limit = atoi(tmp_val[0].c_str());
}
else if (key == "index")
{
// you can definitely call Index several times, i think
for (unsigned long i = 0; i != tmp_val.size(); i++)
location->index.push_back(tmp_val[i]);
}
else if (key == "allow_methods" && location->allow_methods.empty())
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
MethodType m = _str_to_method_type(tmp_val[i]);
if (m == 3)
throw std::invalid_argument("not a valid method");
location->allow_methods.push_back(m);
}
}
else if (key == "cgi_info")
{
// you can call cgi_info several times i think.
// ok wtf is all this even doing, figure that out
unsigned long i = value.find_first_of(" ");
if (i == std::string::npos)
throw std::invalid_argument("bad config file arguments 8");
// 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 \
&& location->redirect_uri == "")
{
// actually i think there can only be one per location...
// you can definitely call return several times, i think
if (tmp_val.size() != 2)
throw std::invalid_argument("wrong number of values");
// and tmp_val[0] should be a number and tmp_val[1] a string?
if (!(::isNumeric(tmp_val[0])))
throw std::invalid_argument("value not a number");
// 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_uri = tmp_val[1];
}
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");
}
}

106
srcs/ConfigParser.hpp Normal file
View File

@@ -0,0 +1,106 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* ConfigParser.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/11 23:01:41 by me #+# #+# */
/* Updated: 2022/07/27 19:27:57 by me ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef CONFIGPARSER_HPP
# define CONFIGPARSER_HPP
# include "ServerConfig.hpp"
# include "LocationConfig.hpp"
# include "MethodType.hpp"
# include "utils.hpp"
# include <map>
# include <vector>
# include <exception> // exception, what
# include <stdexcept> // runtime_error, invalid_argument
# include <string> // string
# include <cstdlib> // atoi (athough it's already cover by <string>)
# include <iostream> // cout, cin
# include <fstream> // ifstream
//# include <unistd.h> // access()
# include <dirent.h> // opendir()
class ConfigParser {
public:
// canonical
ConfigParser(const char* path); // a string?
~ConfigParser();
// ideally i wouldn't have one cuz it makes no sense, when would i use it?
// ConfigParser & operator=(const ConfigParser& rhs);
// void parse(); // return void cuz throw exceptions.
std::vector<ServerConfig> * parse(); // const?
// std::vector<ServerConfig> parse(); // const?
// other parses?
// i thought if it were an instance of this class you could call
// private member functions from anywhere...
void _print_content() const;
private:
std::string _content;
// explicit?
// what exaclty does explicit do again?
ConfigParser(); // might need a path as arg?
// should this be in private since it always needs a path?
ServerConfig _parse_server(size_t *start);
LocationConfig _parse_location(size_t *start);
void _set_server_values(ServerConfig *server, const std::string key, std::string value);
void _set_location_values(LocationConfig *location, const std::string key, std::string value);
std::string _pre_set_val_check(const std::string key, \
const std::string value);
std::string _get_first_word(size_t *curr); // const?
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);
// some sort of post processing...
void _post_processing(std::vector<ServerConfig> *servers);
};
// def needs work line a better name an do i even need this?
// should it be in Utils instead?
class MyException : public std::invalid_argument
{
MyException(const std::string str)
: std::invalid_argument(str) {}
};
#endif

85
srcs/ConfigParserPost.cpp Normal file
View File

@@ -0,0 +1,85 @@
#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?
}

104
srcs/ConfigParserUtils.cpp Normal file
View File

@@ -0,0 +1,104 @@
#include "ConfigParser.hpp"
std::string ConfigParser::_pre_set_val_check(const std::string key, \
const std::string value)
{
// check key for ;
// check values for ; at end and right number of words depending on key
// std::cout << "pre check\n";
if (key.find_first_of(";") != std::string::npos)
throw std::invalid_argument("bad config file arguments 2");
// there shouldn't be any tabs, right? not between values...
if (value.find_first_of("\t") != std::string::npos)
{
std::cout << value << "\n";
throw std::invalid_argument("bad config file arguments 3");
}
size_t i = value.find_first_of(";");
// so you can't have no ;
// you can't have just ;
// and you can't have a ; not at the end or several ;
// in theory value_find_last_of should find the only ;
if (i == std::string::npos || (value.find_last_not_of(" \n")) != i \
|| value.compare(";") == 0)
throw std::invalid_argument("bad config file arguments 4");
// we Trim value.
// is this valid?
// would it be better to shove the result directly in tmp_val?
// like call substr in split?
//value = value.substr(0, i - 1);
return (value.substr(0, i));
}
// assumes curr is on a space or \t or \n
// get first word? next word? word?
std::string ConfigParser::_get_first_word(size_t *curr)
{
size_t start;
// are these checks excessive?
if ((start = _content.find_first_not_of(" \t\n", *curr)) == std::string::npos)
throw std::invalid_argument("bad config file arguments");
if ((*curr = _content.find_first_of(" \t\n", start)) == std::string::npos)
throw std::invalid_argument("bad config file arguments");
std::string key = _content.substr(start, *curr - start);
return (key);
}
// also assumes curr is on a space \t or \n
std::string ConfigParser::_get_rest_of_line(size_t *curr)
{
size_t start;
if ((start = _content.find_first_not_of(" \t\n", *curr)) == std::string::npos)
throw std::invalid_argument("bad config file arguments");
// std::cout << "start + 4 = " << _content.substr(start, 4) << "\n";
// std::cout << "curr + 4 = " << _content.substr(*curr, 4) << "\n";
if ((*curr = _content.find_first_of("\n", start)) == std::string::npos)
throw std::invalid_argument("bad config file arguments");
std::string values = _content.substr(start, *curr - start);
// std::cout << "rest of Line values: " << values << "\n";
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;
}
// I might need to make my own Exceptions to throw...

58
srcs/LocationConfig.hpp Normal file
View File

@@ -0,0 +1,58 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* LocationConfig.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: me <erlazo@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/23 16:08:00 by me #+# #+# */
/* Updated: 2022/07/25 20:09:48 by me ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef LOCATIONCONFIG_HPP
# define LOCATIONCONFIG_HPP
# include "MethodType.hpp"
# include <map>
# include <vector>
# include <string>
// again, struct instead?
class LocationConfig
{
public:
// canonic stuff?
std::string path;
int client_body_limit;
std::string root;
std::vector<std::string> index;
std::vector<MethodType> allow_methods;
std::map<std::string, std::string> cgi_info;
// wait if i can call several times, shouldn't it be a map?
// wait no there can only be 1 and i think it might have to be in
// location only...
int redirect_status;
std::string redirect_uri;
// au pire you do location / { return 301 http://location; }
// and that's how you get the redirect from the root.
};
#endif

15
srcs/MethodType.hpp Normal file
View File

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

21
srcs/Server.hpp Normal file
View File

@@ -0,0 +1,21 @@
#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

93
srcs/ServerConfig.hpp Normal file
View File

@@ -0,0 +1,93 @@
#ifndef SERVERCONFIG_HPP
# define SERVERCONFIG_HPP
# include "MethodType.hpp"
# include "LocationConfig.hpp"
# include <map>
# include <vector>
# include <string> // string
# include <iostream> // cout, cin
// a class that's all public? just so we have options?
class ServerConfig
{
public:
// do i need some canonic stuff?
// there can be several
std::vector<std::string> server_name;
// we could shove default in here if we wanted to...
// there can only be 1 per server...
std::string host;
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;
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::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<MethodType> allow_methods;
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()
{
std::cout << "PRINTING A FULL SERVER CONFIG\n\n";
for (size_t i = 0; i < server_name.size(); i++)
std::cout << server_name[i] << " ";
std::cout << "root: " << root << '\n';
std::cout << "index: ";
for (size_t i = 0; i < index.size(); i++)
std::cout << index[i] << " ";
std::cout << "\nerror_pages: ";
for(std::map<int, std::string>::iterator it = error_pages.begin(); \
it != error_pages.end(); it++)
std::cout << it->first << "--" << it->second << " ";
// for (size_t i = 0; i < error_pages.size(); i++)
// std::cout << error_pages->first << "--" << error_pages->second << " ";
std::cout << "\nallow_methods: ";
for (size_t i = 0; i < allow_methods.size(); i++)
std::cout << allow_methods[i] << " ";
std::cout << "\nskiping Locations for now...\n";
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 << "redirect_status: " << redirect_status << '\n';
// std::cout << "redirect_uri: " << redirect_uri << '\n';
std::cout << "host: " << host << '\n';
std::cout << "port: " << port << '\n';
std::cout << "\n----------\n";
}
};
#endif

115
srcs/Webserv.hpp Normal file
View File

@@ -0,0 +1,115 @@
#ifndef WEBSERV_HPP
# define WEBSERV_HPP
# include <map>
# include <vector>
# include <exception> // exception, what
# include <stdexcept> // runtime_error, invalid_argument
# include <sys/epoll.h> // epoll
# include <fcntl.h> // fcntl
# include <sys/wait.h> // waitpid
# include <csignal> // signal
# include <fstream> // ifstream
# include <sstream> // stringstream
# include <cerrno> // errno
# include <unistd.h> // close, access
# include <iostream> // cout, cin
# include <cstring> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr
# include <netinet/in.h> // sockaddr_in
// # 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 <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
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
class Webserv
{
public:
// base.cpp
Webserv();
// Webserv(Webserv const &src);
// what should it take as arg, *, &, ?
// Webserv(std::vector<ServerConfig>& servers);
~Webserv();
// Webserv &operator=(Webserv const &rhs);
// init.cpp
void init_virtual_servers(std::vector<ServerConfig>* servers);
// run_loop.cpp
void run();
private:
int _epfd;
std::vector<int> _listen_sockets;
std::vector<ServerConfig> _servers;
std::vector<Client> _clients;
// accept.cpp
void _accept_connection(int fd);
// 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 _insert_status_line(Client *client);
void _get_ressource(Client *client);
// cgi_script.cpp
bool _is_cgi(Client *client);
void _exec_cgi(Client *client);
void _construct_client(Client *client);
char** _set_env(Client *client);
char* _dup_env(std::string var, std::string val);
void _exec_script(Client *client, char **env);
// epoll_update.cpp
int _epoll_update(int fd, uint32_t events, int op);
int _epoll_update(int fd, uint32_t events, int op, void *ptr);
// signal.cpp
void _handle_last_signal();
// close.cpp
void _close_client(int fd);
void _close_all_clients();
// init.cpp
void _bind(int socket_fd, in_port_t port, std::string host);
void _listen(int socket_fd, unsigned int max_connections);
};
#endif

447
srcs/Webserv_hugo.cpp Normal file
View File

@@ -0,0 +1,447 @@
#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

@@ -1,95 +0,0 @@
# - - - - - - #
# #
# COLORS #
# #
# - - - - - - #
GRAY = "\e[0;30m"
RED = "\e[0;31m"
GREEN = "\e[0;32m"
YELLOW = "\e[0;33m"
BLUE = "\e[0;34m"
PURPLE = "\e[0;35m"
CYAN = "\e[0;36m"
WHITE = "\e[0;37m"
B_GRAY = "\e[1;30m"
B_RED = "\e[1;31m"
B_GREEN = "\e[1;32m"
B_YELLOW = "\e[1;33m"
B_BLUE = "\e[1;34m"
B_PURPLE = "\e[1;35m"
B_CYAN = "\e[1;36m"
B_WHITE = "\e[1;37m"
RESET = "\e[0m"
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# . name = value \ . += append to a variable #
# VARIABLES . value . != set result of command #
# . name is case sensitive . ?= set if not already set #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
NAME = $(SRCS_X:.cpp=.out)
CXX = c++
CXXFLAGS = -Wall -Wextra #-Werror
CXXFLAGS += $(HEADERS_D:%=-I%)
CXXFLAGS += -std=c++98
CXXFLAGS += -g
VPATH = $(SRCS_D)
HEADERS_D = .
SRCS_D = .
SRCS = cgi_utils.cpp
SRCS_X = \
cgi_cpp.cpp \
cgi_cpp_bad_headers.cpp \
cgi_cpp_empty.cpp \
cgi_cpp_empty_lines.cpp \
cgi_cpp_len.cpp \
cgi_cpp_len_big.cpp \
cgi_cpp_len_small.cpp \
cgi_cpp_no_body.cpp \
cgi_cpp_no_headers.cpp \
cgi_cpp_only_crlf.cpp \
cgi_cpp_sleep.cpp \
cgi_cpp_status.cpp \
cgi_cpp_download.cpp \
OBJS_D = builds
OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o)
OBJS_X = $(SRCS_X:%.cpp=$(OBJS_D)/%.o)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
# . target: prerequisites . $@ : target #
# RULES . recipe . $< : 1st prerequisite #
# . recipe . $^ : all prerequisites #
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - #
all: $(NAME)
$(OBJS_D)/%.o: %.cpp | $(OBJS_D)
@echo $(B_GREEN)"compilation :" $@ $(RESET)
$(CXX) $(CXXFLAGS) -c $< -o $@
$(OBJS_D):
mkdir $@
$(NAME): $(OBJS) $(OBJS_X)
$(NAME):
@echo $(B_YELLOW)"linkage :" $@ $(RESET)
$(CXX) $(OBJS) $(@:%.out=$(OBJS_D)/%.o) -o $@
clean:
rm -rf $(OBJS_D)
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY : all clean fclean re

41
srcs/cgi-bin/cgi.cpp Normal file
View File

@@ -0,0 +1,41 @@
# include <iostream>
# include <string>
# include <sstream>
int main (int ac, char **av) {
std::string to_send;
std::string header;
std::string end_header = "\r\n\r\n";
std::string response;
std::stringstream strs;
header = "HTTP/1.1 200 OK\n";
header += "Content-Type: text/html; charset=UTF-8\n";
header += "Content-Length: ";
response = "<!DOCTYPE html>\n";
response += "<html>\n";
response += "<head>\n";
response += "<title>CGI</title>\n";
response += "</head>\n";
response += "<body>\n";
response += "<h2>CGI request :</h2>\n";
for (int i = 1; i < ac; i++)
{
response += "<p>";
response += av[i];
response += "</p>\n";
}
response += "</body>\n";
response += "</html>\n";
strs << response.size();
header += strs.str();
header += end_header;
to_send = header;
to_send += response;
std::cout << to_send;
return 0;
}

View File

@@ -1,4 +0,0 @@
#! /bin/bash
echo "status: 100\r\n"
echo "\r\n\r\n"
echo "hiii"

BIN
srcs/cgi-bin/cgi_cpp.cgi Executable file

Binary file not shown.

View File

@@ -1,25 +0,0 @@
# include "cgi_utils.hpp"
# include <unistd.h>
# include <cstdio>
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
fill_body_basic(env, http_body, rq_body);
std::cout << http_header << CRLF << http_body;
return 0;
}

View File

@@ -1,24 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string http_status;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Bad-Headers: wrong";
fill_body_basic(env, http_body, rq_body);
std::cout << http_header << CRLF << http_body;
return 0;
}

View File

@@ -1,55 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
std::string form_infos;
std::string path;
std::ifstream ifd;
std::stringstream buf;
size_t status;
std::cin >> rq_body;
(void)ac;
(void)av;
(void)env;
http_header = "Content-Type: image/jpeg" CRLF;
form_infos = get_form_infos(rq_body);
path = get_value("file", rq_body);
path = "./www/" + path;
status = ::eval_file_access(path, R_OK);
if (status)
{
std::cout << "Status: " << status << CRLF CRLF;
return 0;
}
ifd.open(path.c_str());
if (!ifd)
{
std::cout << "Status: " << 500 << CRLF CRLF;
return 0;
}
else
{
buf << ifd.rdbuf();
if (!ifd || !buf)
{
std::cout << "Status: " << 500 << CRLF CRLF;
return 0;
}
}
std::cout << http_header << CRLF << buf.str();
return 0;
}

View File

@@ -1,12 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
(void)ac;
(void)av;
(void)env;
return 0;
}

View File

@@ -1,23 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
fill_body_basic(env, http_body, rq_body);
std::cout << http_header << CRLF CRLF CRLF CRLF CRLF << http_body;
return 0;
}

View File

@@ -1,25 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
http_header += "Content-Length: " + itos(http_body.size()) + CRLF;
fill_body_basic(env, http_body, rq_body);
std::cout << http_header << CRLF << http_body;
return 0;
}

View File

@@ -1,25 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
http_header += "Content-Length: " + itos(http_body.size() + 100) + CRLF;
fill_body_basic(env, http_body, rq_body);
std::cout << http_header << CRLF << http_body;
return 0;
}

View File

@@ -1,24 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
http_header += "Content-Length: " + itos(http_body.size() - 100) + CRLF;
fill_body_basic(env, http_body, rq_body);
std::cout << http_header << CRLF << http_body;
return 0;
}

View File

@@ -1,22 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_status;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
(void)env;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
std::cout << http_header << CRLF;
return 0;
}

View File

@@ -1,24 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string http_status;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = CRLF;
fill_body_basic(env, http_body, rq_body);
std::cout << CRLF << http_body;
return 0;
}

View File

@@ -1,15 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
(void)ac;
(void)av;
(void)env;
std::cout << CRLF CRLF;
return 0;
}

View File

@@ -1,29 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string rq_body;
size_t time;
std::stringstream ss;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
fill_body_basic(env, http_body, rq_body);
ss << get_value("sleep", rq_body);
ss >> time;
sleep(time);
std::cout << http_header << CRLF << http_body;
return 0;
}

View File

@@ -1,27 +0,0 @@
# include "cgi_utils.hpp"
int main (int ac, char **av, char ** env)
{
std::string http_header;
std::string http_body;
std::string http_status;
std::string rq_body;
std::cin >> rq_body;
(void)ac;
(void)av;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
fill_body_basic(env, http_body, rq_body);
http_status = get_value("Status", rq_body);
http_header += "Status: " + http_status + CRLF;
std::cout << http_header << CRLF CRLF << http_body;
return 0;
}

View File

@@ -1,29 +0,0 @@
#! /usr/bin/php
<?php
echo "false: 300\r\n";
echo "server: Webserv/0.2\r\n";
echo "Status: 300\r\n";
echo "\r\n";
echo "BEGIN PHP-CGI\n-----------\n\n";
echo "AUTH_TYPE: " . getenv("AUTH_TYPE");
echo "\nCONTENT_LENGTH: " . getenv("CONTENT_LENGTH");
echo "\nCONTENT_TYPE: " . getenv("CONTENT_TYPE");
echo "\nGATEWAY_INTERFACE: " . getenv("GATEWAY_INTERFACE");
echo "\nPATH_INFO: " . getenv("PATH_INFO");
echo "\nPATH_TRANSLATED: " . getenv("PATH_TRANSLATED");
echo "\nQUERY_STRING: " . getenv("QUERY_STRING");
echo "\nREMOTE_ADDR: " . getenv("REMOTE_ADDR");
echo "\nREMOTE_HOST: " . getenv("REMOTE_HOST");
echo "\nREMOTE_IDENT: " . getenv("REMOTE_IDENT");
echo "\nREMOTE_USER: " . getenv("REMOTE_USER");
echo "\nREQUEST_METHOD: " . getenv("REQUEST_METHOD");
echo "\nSCRIPT_NAME: " . getenv("SCRIPT_NAME");
echo "\nSERVER_NAME: " . getenv("SERVER_NAME");
echo "\nSERVER_PORT: " . getenv("SERVER_PORT");
echo "\nSERVER_PROTOCOL: " . getenv("SERVER_PROTOCOL");
echo "\nSERVER_SOFTWARE: " . getenv("SERVER_SOFTWARE");
echo "\nREDIRECT_STATUS: " . getenv("REDIRECT_STATUS");
// echo $_POST['REQUEST_METHOD'];
echo "\n\n-----------\nEND PHP-CGI\n\n";
?>

View File

@@ -1,3 +0,0 @@
h1, h2, h3, p {
display: inline;
}

View File

@@ -1,199 +0,0 @@
#include "cgi_utils.hpp"
std::string str_tolower(std::string str)
{
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
return str;
}
std::string trim(std::string str, char del)
{
size_t pos;
// delete leadings del
pos = str.find_first_not_of(del);
if (pos == NPOS)
pos = str.size();
str = str.substr(pos);
// delete trailing del
pos = str.find_last_not_of(del);
if (pos != NPOS)
str = str.substr(0, pos + 1);
return str;
}
std::vector<std::string>
split(const std::string & input, std::string delim, char ctrim)
{
std::vector<std::string> split_str;
std::string tmp;
size_t start = 0;
size_t end = 0;
size_t len = 0;
while (end != NPOS)
{
end = input.find(delim, start);
len = end - start;
if (end == NPOS)
len = end;
tmp = input.substr(start, len);
if (ctrim != '\0')
tmp = trim(tmp, ctrim);
if (tmp.size() != 0)
split_str.push_back( tmp );
start = end + delim.size();
}
return split_str;
}
std::string itos(int n)
{
std::stringstream strs;
strs << n;
return ( strs.str() );
}
std::string parse_env(const std::string & env)
{
std::string ret = "";
char * ret_env;
ret_env = getenv(env.c_str());
if (ret_env != NULL)
ret = ret_env;
return ret;
}
std::string print_env(char **env, std::string tag)
{
std::string ret = "";
for (int i = 0; env[i] != NULL; ++i)
{
ret += "<" + tag + ">";
ret += env[i];
ret += "</" + tag + "><br>";
}
return ret;
}
std::string
print_form(std::string form, std::string tag_key, std::string tag_val)
{
std::vector<std::string> split_str;
std::vector<std::string> sub_split_str;
std::vector<std::string>::const_iterator it;
std::string ret = "";
std::string key;
split_str = split(form, "&");
for (it = split_str.begin(); it != split_str.end(); ++it)
{
sub_split_str = split(*it, "=");
key = sub_split_str[0];
if (key == "fname")
key = "first name";
else if (key == "lname")
key = "last name";
ret += "<br><" + tag_key + ">" + key + ": </" + tag_key + ">";
ret += "<" + tag_val + ">" + sub_split_str[1] + "</" + tag_val + ">";
}
return ret;
}
std::string get_form_infos(const std::string & rq_body)
{
std::string form_infos;
std::string method;
method = parse_env("REQUEST_METHOD");
if (method == "POST")
form_infos = rq_body;
else if (method == "GET")
form_infos = parse_env("QUERY_STRING");
return form_infos;
}
std::string get_value(const std::string & key, const std::string & rq_body)
{
std::string infos;
std::string ret;
size_t pos;
size_t end;
size_t len;
infos = get_form_infos(rq_body);
pos = str_tolower(infos).find(str_tolower(key));
if (pos == NPOS)
return "";
pos = infos.find_first_of("=", pos);
if (pos == NPOS)
return "";
pos++;
end = infos.find_first_of("&", pos);
if (end == NPOS)
end = infos.size();
len = end - pos;
ret = infos.substr(pos, len);
return ret;
}
void
fill_body_basic(char **env, std::string & http_body, const std::string & rq_body)
{
std::string rq_method = "not found";
std::string rq_query;
std::string form_infos;
rq_method = parse_env("REQUEST_METHOD");
rq_query = parse_env("QUERY_STRING");
if (rq_method == "POST")
form_infos = rq_body;
else if (rq_method == "GET")
form_infos = rq_query;
http_body = HTML_BODY_TOP;
http_body += "<br><h3>method used: </h3>";
http_body += "<p>" + rq_method + "</p>";
http_body += "<br><h3>form body: </h3>";
http_body += "<p>" + rq_body + "</p>";
http_body += "<br><h3>form query: </h3>";
http_body += "<p>" + rq_query + "</p>";
http_body += "<br><br><h3>output:</h3><br>";
http_body += print_form(form_infos, "p", "p");
http_body += "<br><br><h3>cgi_env_variables:</h3><br>";
http_body += print_env(env, "p");
http_body += HTML_BODY_BOTTOM;
}
size_t eval_file_access(const std::string &path, int mode)
{
if (::access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
return 404; // NOT_FOUND, file doesn't exist
}
if (::access(path.c_str(), mode) == -1)
{
std::perror("err access()");
return 403; // FORBIDDEN, file doesn't have access permission
}
return 0;
}

View File

@@ -1,68 +0,0 @@
#ifndef CGI_UTILS_HPP
# define CGI_UTILS_HPP
# include <iostream>
# include <string>
# include <sstream>
# include <fstream>
# include <vector>
# include <stdlib.h> // getenv
# include <algorithm> // transform
# include <unistd.h> // sleep, close, access
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
# define CRLF_SIZE 2
# define NPOS std::string::npos
# define HTML_BODY_TOP "<!DOCTYPE html>"\
"<html>"\
" <head>"\
" <meta charset=\"UTF-8\">"\
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">"\
" <title>CGI</title>"\
" <link href=\"./cgi_style.css\" type=\"text/css\" rel=\"stylesheet\">"\
" </head>"\
" <body>"\
" <h1>CGI</h1><br>"
# define HTML_BODY_BOTTOM " <br><h1>END CGI</h1>"\
" </body>"\
"</html>"
std::string
str_tolower(std::string str);
std::string
trim(std::string str, char del);
std::vector<std::string>
split(const std::string & input, std::string delim, char ctrim = '\0');
std::string
itos(int n);
std::string
parse_env(const std::string & env);
std::string
print_env(char **env, std::string tag = "p");
std::string
get_form_infos(const std::string & rq_body);
std::string
get_value(const std::string & key, const std::string & rq_body);
std::string
print_form(std::string form, std::string key = "p", std::string val = "p");
void
fill_body_basic(char **env, std::string & http_body, const std::string & rq_body);
size_t
eval_file_access(const std::string &path, int mode);
#endif

View File

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

View File

@@ -1,65 +0,0 @@
#! /bin/bash/php
<?php
// $FileName=$_FILES['myFile']['filename'];
# $TmpName=$_FILES['myFile']['tmp_name'];
# move_uploaded_file($TmpName, $FileName);
# echo("File was uploaded successfully!");
// this part needs to be grabed from POST uri
$target_dir = "./www/uploaded/";
//$target_dir = $_POST["upload_dir"];
//$target_dir = $_GET["upload_dir"];
$target_file = $target_dir . basename($_FILES["fileToUpload"]["name"]);
$uploadOk = 1;
$fileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
//$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));
// Check if image file is a actual image or fake image
//if(isset($_POST["submit"])) {
// $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
// if($check !== false) {
// echo "File is an image - " . $check["mime"] . ".";
// $uploadOk = 1;
// } else {
// echo "File is not an image.";
// $uploadOk = 0;
// }
//}
// Check if file already exists
if (file_exists($target_file)) {
echo "Sorry, file already exists.";
$uploadOk = 0;
}
// Check file size
if ($_FILES["fileToUpload"]["size"] > 500000) {
echo "Sorry, your file is too large.";
$uploadOk = 0;
}
// Allow certain file formats
//if($imageFileType != "jpg" && $imageFileType != "png" && $imageFileType != "jpeg"
//&& $imageFileType != "gif" ) {
// echo "Sorry, only JPG, JPEG, PNG & GIF files are allowed.";
// $uploadOk = 0;
//}
// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
echo "Sorry, your file was not uploaded.";
// if everything is ok, try to upload file
} else {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file ". htmlspecialchars( basename( $_FILES["fileToUpload"]["name"])). " has been uploaded.";
} else {
echo "Sorry, there was an error uploading your file.";
}
}
?>

View File

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

View File

@@ -1,61 +0,0 @@
#ifndef CONFIGPARSER_HPP
# define CONFIGPARSER_HPP
# include "ServerConfig.hpp"
# include "LocationConfig.hpp"
# include "utils.hpp"
# include <map>
# include <vector>
# include <exception> // exception, what
# include <stdexcept> // runtime_error, invalid_argument
# include <string> // string
# include <cstdlib> // strtol, stroul
# include <iostream> // cout, cin
# include <fstream> // ifstream
# include <sys/stat.h> // stat(), replaces opendir() don't bother with ERRNO ?
# include <algorithm> // sort() in Post
class ConfigParser {
public:
ConfigParser();
~ConfigParser();
ConfigParser(const std::string &config_file);
void read_config(const std::string &config_file);
std::vector<ServerConfig> * parse(); // const?
// i thought if it were an instance of this class you could call
// private member functions from anywhere... // QUESTION : Wut ?
void print_content() const;
private:
std::string _content;
ServerConfig _parse_server(size_t *start);
LocationConfig _parse_location(size_t *start);
void _set_server_values(ServerConfig *server, const std::string key, std::string value);
void _set_location_values(LocationConfig *location, const std::string key, std::string value);
/* Extra */
std::string _pre_set_val_check(const std::string key,
const std::string value);
std::string _get_first_word(size_t *curr); // const?
std::string _get_rest_of_line(size_t *curr); // const?
/* Post Processing */
void _post_processing(std::vector<ServerConfig> *servers);
bool _find_root_path_location(std::vector<LocationConfig> locations) const;
};
#endif

View File

@@ -1,77 +0,0 @@
#ifndef LOCATIONCONFIG_HPP
# define LOCATIONCONFIG_HPP
# include <vector>
# include <string>
# include "utils.hpp"
struct LocationConfig
{
std::string path; // /path and /path/ are fine
// i remove trailing / systematically
std::string root;
std::vector<std::string> index;
unsigned int allow_methods;
std::vector<std::string> cgi_ext; // php not .php
bool autoindex;
std::string upload_dir;
int redirect_status; // only in location
std::string redirect_uri; // only 1 per location
void print_all() const
{
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";
}
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)) != NPOS)
{
++tmp;
++comp_lhs;
}
if (path[path.find_last_of("/") + 1] != '\0')
++comp_lhs;
tmp = 0;
while ((tmp = rhs.path.find_first_of("/", tmp)) != NPOS)
{
++tmp;
++comp_rhs;
}
if (rhs.path[rhs.path.find_last_of("/") + 1] != '\0')
++comp_rhs;
return (comp_lhs < comp_rhs);
};
bool operator==(const LocationConfig& rhs) const
{
if (path.compare(rhs.path) == 0)
return true;
return false;
}
};
#endif

View File

@@ -1,61 +0,0 @@
#ifndef SERVERCONFIG_HPP
# define SERVERCONFIG_HPP
# include "utils.hpp"
# include "LocationConfig.hpp"
# include <map>
# include <vector>
# include <string> // string
# include <iostream> // cout, cin
struct ServerConfig
{
std::vector<std::string> server_name;
std::string host;
std::string port;
std::string root; // ./www/ or www work www/ and www work
// i do remove trailing / tho
size_t client_body_limit;
std::vector<std::string> index;
std::map<int, std::string> error_pages;
std::vector<LocationConfig> locations;
void print_all() const
{
std::cout << "PRINTING A FULL SERVER CONFIG\n\n";
for (size_t i = 0; i < server_name.size(); i++)
std::cout << server_name[i] << " ";
std::cout << "root: " << root << '\n';
std::cout << "index: ";
for (size_t i = 0; i < index.size(); i++)
std::cout << index[i] << " ";
std::cout << "\nerror_pages: ";
for(std::map<int, std::string>::const_iterator it = error_pages.begin(); \
it != error_pages.end(); it++)
std::cout << it->first << "--" << it->second << " ";
// for (size_t i = 0; i < error_pages.size(); i++)
// std::cout << error_pages->first << "--" << error_pages->second << " ";
// std::cout << "skiping Locations for now...\n";
for (std::vector<LocationConfig>::const_iterator it = locations.begin(); it < locations.end(); it++)
it->print_all();
std::cout << "client_body_limit: " << client_body_limit << '\n';
std::cout << "host: " << host << '\n';
std::cout << "port: " << port << '\n';
std::cout << "\n----------\n";
}
};
#endif

View File

@@ -1,68 +0,0 @@
#include "ConfigParser.hpp"
// should i be sending & references?
// const?
std::string ConfigParser::_pre_set_val_check(const std::string key,
const std::string value)
{
// check key for ;
// check values for ; at end and right number of words depending on key
// std::cout << "pre check\n";
if (key.find_first_of(";") != NPOS)
throw std::invalid_argument("bad config file arguments");
if (value.find_first_of("\t") != NPOS)
throw std::invalid_argument("why would you put tabs between values");
size_t i = value.find_first_of(";");
// so you can't have no ;
// you can't have just ;
// and you can't have a ; not at the end or several ;
// in theory value_find_last_of should find the only ;
if (i == NPOS || (value.find_last_not_of(" \n")) != i \
|| value.compare(";") == 0)
throw std::invalid_argument("bad config file arguments");
return (value.substr(0, i));
}
// const?
// assumes curr is on a space or \t or \n
std::string ConfigParser::_get_first_word(size_t *curr)
{
size_t start;
if ((start = _content.find_first_not_of(" \t\n", *curr)) == NPOS)
throw std::invalid_argument("bad config file arguments");
if ((*curr = _content.find_first_of(" \t\n", start)) == NPOS)
throw std::invalid_argument("bad config file arguments");
std::string key = _content.substr(start, *curr - start);
return (key);
}
// const?
// also assumes curr is on a space \t or \n
std::string ConfigParser::_get_rest_of_line(size_t *curr)
{
size_t start;
if ((start = _content.find_first_not_of(" \t\n", *curr)) == NPOS)
throw std::invalid_argument("bad config file arguments");
if ((*curr = _content.find_first_of("\n", start)) == NPOS)
throw std::invalid_argument("bad config file arguments");
std::string values = _content.substr(start, *curr - start);
return (values);
}
void ConfigParser::print_content() const
{
std::cout << _content;
}

View File

@@ -1,328 +0,0 @@
#include "ConfigParser.hpp"
ConfigParser::ConfigParser() {};
ConfigParser::~ConfigParser() {};
ConfigParser::ConfigParser(const std::string &config_file)
{
read_config(config_file);
}
void ConfigParser::read_config(const std::string &config_file)
{
std::ifstream file;
std::string buf;
size_t comment;
file.open(config_file.c_str());
if (!file)
throw std::invalid_argument("failed to open config");
else
{
_content.clear();
while (!file.eof())
{
getline(file, buf);
// remove # comments here.
if ((comment = buf.find_first_of("#")) == NPOS)
{
// remove empty lines, i think...
if ((buf.find_first_not_of(" \t")) != NPOS)
_content.append(buf + '\n');
}
else if (comment > 0 && (buf.find_first_not_of(" \t")) < comment)
{
// check for comment at the end of the line
std::string tmp = buf.substr(0, comment - 1);
_content.append(tmp + '\n');
}
}
}
}
// const?
std::vector<ServerConfig> * ConfigParser::parse()
{
std::vector<ServerConfig> * ret = new std::vector<ServerConfig>();
size_t start = 0;
size_t curr = _content.find_first_not_of(" \t\n", 0);
if (curr == NPOS)
throw std::invalid_argument("empty config file");
while (curr != NPOS)
{
if ((start = _content.find_first_not_of(" \t\n", curr)) == NPOS)
throw std::invalid_argument("empty config file");
if ((curr = _content.find_first_of(" \t\n", start)) == NPOS)
throw std::invalid_argument("empty config file");
std::string key = _content.substr(start, curr - start);
if (key != "server")
throw std::invalid_argument("bad config file arguments");
ret->push_back(_parse_server(&curr));
}
_post_processing(ret);
return (ret);
}
ServerConfig ConfigParser::_parse_server(size_t *start)
{
ServerConfig ret;
size_t curr = _content.find_first_not_of(" \t\n", *start);
ret.client_body_limit = 0;
if (curr == NPOS || _content[curr] != '{')
throw std::invalid_argument("bad config file syntax");
if ((curr = _content.find_first_of(" \t\n", curr + 1)) == NPOS)
throw std::invalid_argument("bad config file syntax");
// are there other things to check for?
while (curr != NPOS) // here curr == { + 1
{
// so this moves curr to past the word...
std::string key = _get_first_word(&curr);
// now curr is on space after 1st word.
if (key == "}")
{
// why +1 curr is already after it no?
*start = _content.find_first_not_of(" \t\n", curr + 1);
break ;
}
else if (key == "location")
ret.locations.push_back(_parse_location(&curr));
else
{
std::string values = _get_rest_of_line(&curr);
// curr now should be \n
_set_server_values(&ret, key, values);
}
}
return (ret);
}
LocationConfig ConfigParser::_parse_location(size_t *start)
{
LocationConfig ret;
size_t curr = *start;
// start is after the 1st word aka "location"
ret.autoindex = false;
ret.redirect_status = 0;
ret.allow_methods = 0;
ret.path = _get_first_word(&curr);
if (ret.path[0] != '/')
throw std::invalid_argument("Location path require a leading /");
// in theory now curr should be right after the "path"
curr = _content.find_first_not_of(" \t\n", curr);
if (curr == NPOS || _content[curr] != '{')
throw std::invalid_argument("bad config file syntax");
if ((curr = _content.find_first_of(" \t\n", curr + 1)) == NPOS)
throw std::invalid_argument("bad config file syntax");
while (curr != NPOS)
{
// so this moves curr to past the word...
std::string key = _get_first_word(&curr);
// now curr is on space after 1st word.
if (key == "}")
{
*start = curr;
break ;
}
else
{
std::string values = _get_rest_of_line(&curr);
// curr now should be \n
_set_location_values(&ret, key, values);
}
}
return (ret);
}
// should i be sending pointers or references?
void ConfigParser::_set_server_values(ServerConfig *server,
const std::string key, std::string value)
{
// should i be sending pointers or references?
value = _pre_set_val_check(key, value);
std::vector<std::string> tmp_val = ::split(value, ' ');
size_t size = tmp_val.size();
if (size < 1)
throw std::invalid_argument("missing value");
else if (key == "server_name" && server->server_name.empty())
{
for (size_t i = 0; i < size; i++)
{
for (std::vector<std::string>::const_iterator it = server->server_name.begin();
it < server->server_name.end(); it++)
{
if (it->compare(tmp_val[i]) == 0)
throw std::invalid_argument("server_name already exists");
}
server->server_name.push_back(tmp_val[i]);
}
}
else if (key == "listen" && size == 1 && server->host == ""
&& server->port == "") // QUESTION LUKE : C'est quoi cette condition ? Si listen est vide ? Je comprends pas trop.
{
if (tmp_val[0].find_first_of(":") == NPOS)
{
if (!::isNumeric_btw(0, 65535, tmp_val[0]))
throw std::invalid_argument("bad port number");
server->host = "0.0.0.0";
server->port = tmp_val[0];
}
else
{
std::vector<std::string> tmp2 = ::split(tmp_val[0], ':');
std::vector<std::string> ip = ::split(tmp2[0], '.');
if (ip.size() != 4)
throw std::invalid_argument("bad host ip");
for (size_t i = 0; i < ip.size(); i++)
{
if (!::isNumeric_btw(0, 255, ip[i]))
throw std::invalid_argument("bad host ip");
}
if (!::isNumeric_btw(0, 65535, tmp2[1]))
throw std::invalid_argument("bad port number");
server->host = tmp2[0];
server->port = tmp2[1];
}
}
else if (key == "root" && size == 1 && server->root == "")
{
// remove trailing /
if (tmp_val[0][tmp_val[0].size() - 1] == '/')
tmp_val[0].erase(tmp_val[0].size() - 1, 1);
// tmp_val[0].push_back('/');
server->root = tmp_val[0];
}
else if (key == "client_body_limit" && size == 1
&& server->client_body_limit == 0)
{
if (!::isNumeric(tmp_val[0]))
throw std::invalid_argument("client_body_limit not a number");
server->client_body_limit = std::strtoul(tmp_val[0].c_str(), NULL, 10);
if (errno == ERANGE || server->client_body_limit > (ULONG_MAX / KB) )
throw std::invalid_argument("client_body_limit too big");
server->client_body_limit = server->client_body_limit * KB;
}
else if (key == "index")
{
for (size_t i = 0; i != tmp_val.size(); i++)
server->index.push_back(tmp_val[i]);
}
else if (key == "error_page")
{
std::string path = tmp_val[size - 1];
for (size_t i = 0; i < size - 1; i++)
{
if (!(isNumeric_btw(400, 599, tmp_val[i])))
throw std::invalid_argument("invalid error code");
int status_code = std::strtoul(tmp_val[i].c_str(), NULL, 10);
if (server->error_pages.find(status_code) != server->error_pages.end())
throw std::invalid_argument("redeclaring error page");
server->error_pages[status_code] = path;
}
}
else
throw std::invalid_argument("bad key value pair");
}
// should i be sending pointers or references?
void ConfigParser::_set_location_values(LocationConfig *location,
const std::string key, std::string value)
{
// should i be sending pointers or references?
value = _pre_set_val_check(key, value);
std::vector<std::string> tmp_val = ::split(value, ' ');
size_t size = tmp_val.size();
if (size < 1)
throw std::invalid_argument("missing value");
else if (key == "root" && size == 1 && location->root == "")
{
// remove trailing /
if (tmp_val[0][tmp_val[0].size() - 1] == '/')
tmp_val[0].erase(tmp_val[0].size() - 1, 1);
// tmp_val[0].push_back('/');
location->root = tmp_val[0];
}
else if (key == "autoindex" && size == 1)
location->autoindex = (tmp_val[0] == "on" ? true : false);
else if (key == "index")
{
for (size_t i = 0; i < size; i++)
location->index.push_back(tmp_val[i]);
}
else if (key == "allow_methods" && location->allow_methods == 0)
{
for (size_t i = 0; i < size; i++)
{
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
location->allow_methods |= m;
}
}
else if (key == "cgi_ext")
{
for (size_t i = 0; i < size; i++)
{
if (tmp_val[i][0] == '.')
throw std::invalid_argument("cgi_ext should not have a leading '.'");
location->cgi_ext.push_back(tmp_val[i]);
}
}
else if (key == "redirect" && location->redirect_status == 0
&& location->redirect_uri == "")
{
if (size != 2)
throw std::invalid_argument("wrong number of values");
if (tmp_val[0] != "301" && tmp_val[0] != "302"
&& tmp_val[0] != "303" && tmp_val[0] != "307"
&& tmp_val[0] != "308")
throw std::invalid_argument("bad redirect status");
if (tmp_val[1].compare(0, 7, "http://")
&& tmp_val[1].compare(0, 8, "https://"))
throw std::invalid_argument("bad redirect uri");
location->redirect_status = std::strtoul(tmp_val[0].c_str(), NULL, 10);
location->redirect_uri = tmp_val[1];
}
else if (key == "upload_dir" && size == 1 && location->upload_dir == "")
{
// what checks to do?
// add trailing /
if (tmp_val[0][tmp_val[0].size() - 1] != '/')
tmp_val[0].push_back('/');
location->upload_dir = tmp_val[0];
}
else
throw std::invalid_argument("bad key value pair");
}

View File

@@ -1,93 +0,0 @@
#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 = 1 * MB;
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->path[it_l->path.size() - 1] == '/'
&& it_l->path.size() > 1)
it_l->path.erase(it_l->path.size() - 1);
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->autoindex == false)
it_l->index = it->index;
// nothing to be done for cgi_ext, error_pages, redirect
++it_l;
}
it_l = it->locations.begin();
while (it_l != it->locations.end())
{
std::vector<LocationConfig>::const_iterator tmp = it_l + 1;
while (tmp != it->locations.end())
{
if (it_l->path == tmp->path)
throw std::invalid_argument("Duplicate locations in config file");
++tmp;
}
++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) const
{
std::vector<LocationConfig>::const_iterator it = locations.begin();
while (it != locations.end())
{
if (it->path.compare("/") == 0)
return true;
++it;
}
return false;
}

13
srcs/ft_itoa.cpp Normal file
View File

@@ -0,0 +1,13 @@
# 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

@@ -11,9 +11,15 @@ int main(int ac, char **av)
{
std::string config = (ac == 2 ? av[1] : "./default.config");
// like this just looks kinda gross, why bother creating an instance
// and not immediately parsing? like it servers no other purpose...
// what if i call parse directly in the constructor?
// oh because the constructor has no return, but still
// is there a better way?
ConfigParser configParser(config);
// configParser.print_content();
ConfigParser configParser(config.c_str());
configParser._print_content();
// i don't love that servers has to be a pointer...
std::vector<ServerConfig>* servers = configParser.parse();
@@ -21,9 +27,8 @@ int main(int ac, char **av)
// use an iterator you moron
for (std::vector<ServerConfig>::iterator it = servers->begin(); it < servers->end(); it++)
{
(void)0;
// std::cout << it->server_name << " ";
// it->print_all();
// std::cout << it->server_name << " ";
it->print_all();
}
@@ -39,7 +44,7 @@ int main(int ac, char **av)
}
catch (std::exception& e)
{
std::cerr << e.what() << '\n';
std::cout << e.what() << '\n';
}
return (0);

View File

@@ -1,22 +1,6 @@
#include "utils.hpp"
void throw_test()
{
static int i = 0;
++i;
if (i % 8 == 0)
throw std::bad_alloc();
}
// notice : the use of getline make it such as
// it doesn't identify multiple delim as one :
// " something \n else " -> 1 - something
// 2 - else
// is not the same as :
// " something \n\n else " -> 1 - something
// 2 -
// 3 - else
std::vector<std::string> split(std::string input, char delimiter)
{
std::vector<std::string> answer;
@@ -29,62 +13,14 @@ std::vector<std::string> split(std::string input, char delimiter)
return answer;
}
std::vector<std::string>
split_trim(std::string input, std::string delim, char ctrim)
std::string trim(std::string str, char c)
{
std::vector<std::string> split_str;
std::string tmp;
size_t start = 0;
size_t end = 0;
size_t len = 0;
while (end != NPOS)
{
end = input.find(delim, start);
len = end - start;
if (end == NPOS)
len = end;
tmp = input.substr(start, len);
if (ctrim != '\0')
tmp = trim(tmp, ctrim);
if (tmp.size() != 0)
split_str.push_back( tmp );
start = end + delim.size();
}
return split_str;
}
std::string trim(std::string str, char del)
{
size_t pos;
// delete leadings del
pos = str.find_first_not_of(del);
if (pos == NPOS)
pos = str.size();
str = str.substr(pos);
// delete trailing del
pos = str.find_last_not_of(del);
if (pos != NPOS)
str = str.substr(0, pos + 1);
str = str.substr(str.find_first_not_of(c));
str = str.substr(0, str.find_last_not_of(c) + 1);
return str;
}
//// trim a set of char
//std::string trim(std::string str, std::string del)
//{
// std::string new_str;
//
// while (new_str.compare(str) != 0)
// {
// for (size_t i = 0; i < del.size(); i++)
// trim(str, del[i]);
// }
// return str;
//}
std::string itos(int n)
{
std::stringstream strs;
@@ -110,221 +46,9 @@ bool isNumeric_btw(int low, int high, std::string str)
if (std::isdigit(str[i]) == false)
return false;
}
int n = std::strtol(str.c_str(), NULL, 10);
int n = std::atoi(str.c_str());
if (n < low || n > high)
return false;
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);
}
# include <iostream>
file_type eval_file_type(const std::string &path)
{
struct stat s;
if (stat(path.c_str(), &s) != -1)
{
if (S_ISREG(s.st_mode))
return (IS_FILE);
else if (S_ISDIR(s.st_mode))
return (IS_DIR);
}
else
{
std::perror("err stat()");
}
return (IS_OTHER);
}
size_t eval_file_access(const std::string &path, int mode)
{
if (::access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
return 404; // NOT_FOUND, file doesn't exist
}
if (::access(path.c_str(), mode) == -1)
{
std::perror("err access()");
return 403; // FORBIDDEN, file doesn't have access permission
}
return 0;
}
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 == NPOS)
break;
str.replace(pos, ori_substr.size(), new_substr);
pos += new_substr.size();
}
}
std::string str_tolower(std::string str)
{
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
return str;
}
// identify a line in a string, by delim (ex. '\n')
// delete this line from the string (and the following nl sequence characters)
// and return the deleted line (without the followinf nl sequence characters)
std::string
extract_line(std::string & str, size_t pos, std::string delim)
{
std::string del_str;
size_t begin;
size_t end;
size_t len;
begin = str.rfind(delim, pos);
if (begin == NPOS)
begin = 0;
else if (begin < pos)
begin += delim.size();
end = str.find(delim, pos);
len = end;
if (end != NPOS)
len = end - begin;
del_str = str.substr(begin, len);
str.erase(begin, len + delim.size());
return del_str;
}
// get a line in a string, by delim
// same as extract, except it doesn't delete it
std::string get_line(std::string str, size_t pos, std::string delim)
{
std::string ret;
ret = ::extract_line(str, pos, delim);
return ret;
}
size_t
parse_http_headers (
std::string headers,
std::map<std::string, std::string> & fields )
{
std::vector<std::string> list;
std::vector<std::string>::iterator it;
std::vector<std::string>::iterator it_end;
size_t err_count = 0;
size_t pos;
std::string key;
std::string val;
list = ::split_trim(headers, CRLF, ' ');
it_end = list.end();
for (it = list.begin(); it != it_end; it++)
{
pos = (*it).find(':');
if (pos == NPOS)
{
err_count++;
continue;
}
key = (*it).substr(0, pos);
if ( key.find(' ') != NPOS )
{
err_count++;
continue;
}
// bad idea, in cgi we need to have the original value
// key = ::str_tolower(key); // to make "key" case_insensitive
val = (*it).substr(pos + 1);
val = ::trim(val, ' ');
fields.insert( std::pair<std::string, std::string>(key, val) );
}
return err_count;
}
void str_map_key_tolower(std::map<std::string, std::string> & mp)
{
std::map<std::string, std::string> new_mp;
std::map<std::string, std::string>::iterator it;
std::string key;
std::string value;
for (it = mp.begin(); it != mp.end(); it++)
{
key = it->first;
value = it->second;
key = ::str_tolower(key);
new_mp.insert( std::pair<std::string, std::string>(key, value) );
}
mp.swap(new_mp);
}
// DEBUG
void print_special(std::string str)
{
char c;
for (size_t i = 0; i < str.size(); i++)
{
c = str[i];
if (c == '\r')
std::cout << YELLOW << "\\r" << RESET;
else if (c == '\n')
std::cout << YELLOW << "\\n" << RESET << "\n";
else
std::cout << c;
fflush(stdout);
}
}
bool operator==(const listen_socket& lhs, int fd)
{ return lhs.fd == fd; }
bool operator==(int fd, const listen_socket& rhs)
{ return fd == rhs.fd; }

View File

@@ -3,105 +3,14 @@
# define UTILS_HPP
# include <vector>
# include <map>
# include <string>
# include <sstream>
# include <iostream>
# include <cstdlib> // strtol, strtoul
# include <climits> // LONG_MAX
# include <cerrno> // errno
# include <sys/stat.h> // stat()
# include <cctype> // tolower
# include <algorithm> // transform
# include <cstdio> // perror, fflush
# include <unistd.h> // close, access
# include "colors.h" // for debug print_special
# include <cstdlib> // atoi
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
# define CRLF_SIZE 2
# define NPOS std::string::npos
# define KB 1024
# define MB 1048576
/* Equivalent for end of http header size :
** std::string(CRLF CRLF).size();
** sizeof(CRLF CRLF) - 1;
** CRLF_SIZE*2
*/
enum file_type
{
IS_OTHER,
IS_FILE,
IS_DIR
};
enum http_method
{
UNKNOWN = 0b0,
GET = 1 << 0,
POST = 1 << 1,
DELETE = 1 << 2,
ANY_METHODS = 0b11111111,
// ALL_METHODS = 0b11111111,
// i would prefer this...
};
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);
std::vector<std::string> split_trim(std::string input, std::string delim = "\n", char ctrim = '\0');
bool isNumeric(std::string str);
bool isNumeric_btw(int low, int high, std::string str);
std::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 del);
http_method str_to_http_method(std::string &str);
std::string http_methods_to_str(unsigned int methods);
file_type eval_file_type(const std::string &path);
size_t eval_file_access(const std::string &path, int mode);
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr);
std::string str_tolower(std::string str);
std::string extract_line(std::string & str, size_t pos = 0, std::string delim = "\n");
std::string get_line (std::string str, size_t pos = 0, std::string delim = "\n");
size_t parse_http_headers (std::string headers, std::map<std::string, std::string> & fields );
void str_map_key_tolower(std::map<std::string, std::string> & mp);
// debug
void throw_test();
void print_special(std::string str);
/* Template */
template <typename T1, typename T2 >
void print_pair(const std::pair<T1,T2> p)
{
std::cout << p.first << ": ";
std::cout << p.second << "\n";
}
template <typename Key, typename T >
void print_map(const std::map<Key,T>& c)
{
typename std::map<Key,T>::const_iterator it = c.begin();
typename std::map<Key,T>::const_iterator it_end = c.end();
std::cout << " --print_map():\n";
std::cout << "map.size() = " << c.size() << "\n";
while (it != it_end)
{
print_pair(*it);
++it;
}
std::cout << " --\n";
}
std::string trim(std::string str, char c);
#endif

View File

@@ -1,169 +0,0 @@
#ifndef WEBSERV_HPP
# define WEBSERV_HPP
# include <map>
# include <vector>
# include <exception> // exception, what
# include <stdexcept> // runtime_error, invalid_argument
# include <sys/epoll.h> // epoll
# include <fcntl.h> // fcntl
# include <sys/wait.h> // waitpid
# include <csignal> // signal
# include <fstream> // ifstream
# include <sstream> // stringstream
# include <cerrno> // errno
# include <unistd.h> // close, access
# include <iostream> // cout, cin
# include <cstring> // memset
# include <sys/socket.h> // socket, accept, listen, send, recv, bind, connect, setsockopt, getsockname
# include <arpa/inet.h> // htonl, htons, ntohl, ntohs, inet_addr, inet_ntoa
# include <netinet/in.h> // sockaddr_in, struct in_addr
// # 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, remove
# include <cstdlib> // strtol, strtoul
# include <dirent.h> // opendir()
# include <locale> // isalpha, local
# include "Client.hpp"
# include "ServerConfig.hpp"
# include "utils.hpp"
# include "http_status.hpp"
# include "autoindex.hpp"
# include "colors.h"
extern bool g_run;
extern int g_last_signal;
void signal_handler(int signum);
// these might only be TMP
# define FAILURE -1
# define SUCCESS 1
# define MIME_TYPE_DEFAULT "application/octet-stream"
class Webserv
{
public:
// base.cpp
Webserv();
// Webserv(Webserv const &src);
// what should it take as arg, *, &, ?
// Webserv(std::vector<ServerConfig>& servers);
~Webserv();
// Webserv &operator=(Webserv const &rhs);
// init.cpp
void init_virtual_servers(std::vector<ServerConfig>* servers);
// run_loop.cpp
void run();
private:
int _epfd;
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(listen_socket &lsocket);
std::map<std::string, std::string>
_extract_infos(struct sockaddr_in addr);
// request.cpp
void _request(Client *client);
int _read_request(Client *client);
// response.cpp
void _response(Client *client);
int _send_response(Client *client);
void _append_base_headers(Client *client);
void _construct_response(Client *client);
void _process_method(Client *client, std::string &path);
std::string _replace_url_root(Client *client, std::string path);
void _insert_status_line(Client *client);
void _error_html_response(Client *client);
void _append_body(Client *client, const std::string &body, const std::string &file_extension = "");
// method_get.cpp
void _get(Client *client, std::string &path);
void _get_file(Client *client, const std::string &path);
void _autoindex(Client *client, const std::string &path);
std::string _determine_file_extension(const std::string &path) const;
// method_post.cpp
void _post(Client *client, const std::string &path);
void _upload_files(Client *client);
// method_delete.cpp
void _delete(Client *client, const std::string &path);
void _delete_file(Client *client, const std::string &path);
// epoll_update.cpp
int _epoll_update(int fd, uint32_t events, int op);
int _epoll_update(int fd, uint32_t events, int op, void *ptr);
// signal.cpp
void _handle_last_signal();
// close.cpp
void _close_client(int fd);
void _close_all_clients();
void _close_all_clients_fd();
void _close_all_clients_cgi_fd();
void _close_cgi_pipe_rfd(int fd);
void _close_all_cgi_pipe_rfd();
void _close_all_listen_sockets();
void _reopen_lsocket(std::vector<listen_socket>::iterator it);
void _handle_epoll_error_lsocket(uint32_t events, std::vector<listen_socket>::iterator it);
void _handle_epoll_error_client(uint32_t events, int fd);
// 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();
// timeout.cpp
void _timeout();
// cgi.cpp
bool _is_cgi(Client *client, std::string path);
size_t _cgi_pos(Client *client, std::string &path, size_t pos);
void _exec_cgi(Client *client);
void _set_env_vector(Client *client, std::vector<std::string> &env_vector);
void _set_env_cstr(char *env_cstr[], std::vector<std::string> &env_vector);
std::string _dup_env(std::string var, std::string val);
std::string _dup_env(std::string var, int i);
void _exec_script(Client *client, char *env[]);
void _check_script_output(Client *client, std::string & output);
void _check_script_status(Client *client, std::string & output);
size_t _check_script_fields(const std::string & output, size_t status);
void _check_fields_duplicates(Client *client, std::string & output);
void _add_script_body_length_header(std::string & output);
void _remove_body_leading_empty_lines(std::string & output);
Client *_find_cgi_fd(int cgi_fd);
void _read_cgi_output(Client *client);
void _handle_epoll_error_cgi_fd(uint32_t events, Client *client);
void _cgi_epollhup(uint32_t events, Client *client);
///////////////////////
class ExecFail : public std::exception
{
public :
virtual const char* what() const throw() {
return ("Exec CGI fail");
};
};
};
#endif
/*
HTTP Semantics:
https://www.rfc-editor.org/rfc/rfc9110.html
https://www.bortzmeyer.org/9110.html
https://www.bortzmeyer.org/cours-http-cnam.html
HTTP/1.1:
https://www.rfc-editor.org/rfc/rfc9112.html
https://www.bortzmeyer.org/9112.html
CGI:
https://www.rfc-editor.org/rfc/rfc3875.html
*/

View File

@@ -1,16 +1,15 @@
#include "Webserv.hpp"
void Webserv::_accept_connection(listen_socket &lsocket)
void Webserv::_accept_connection(int fd)
{
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
std::map<std::string, std::string> infos;
struct sockaddr_in addr;
socklen_t addr_len;
int accepted_fd;
std::cerr << "accept()\n";
addr_len = sizeof addr;
accepted_fd = ::accept(lsocket.fd, (sockaddr*)&addr, &addr_len);
accepted_fd = ::accept(fd, (sockaddr*)&addr, &addr_len);
if (accepted_fd == -1)
{
std::perror("err accept()");
@@ -20,22 +19,8 @@ void Webserv::_accept_connection(listen_socket &lsocket)
}
::fcntl(accepted_fd, F_SETFL, O_NONBLOCK);
infos = _extract_infos(addr);
Client new_client(accepted_fd, &lsocket, infos["port"], infos["ip"]);
_clients.push_back(new_client);
_clients.push_back(Client());
_clients.back().fd = accepted_fd;
_epoll_update(accepted_fd, EPOLLIN, EPOLL_CTL_ADD);
}
std::map<std::string, std::string>
Webserv::_extract_infos(struct sockaddr_in addr)
{
struct in_addr ip_conversion;
std::map<std::string, std::string> infos;
infos["port"] = ::itos( addr.sin_port );
ip_conversion.s_addr = addr.sin_addr.s_addr;
infos["ip"] = inet_ntoa( ip_conversion );
return infos;
}

View File

@@ -1,28 +0,0 @@
#ifndef AUTOINDEX_HPP
# define AUTOINDEX_HPP
# 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

Some files were not shown because too many files have changed in this diff Show More