72 Commits
hugo2 ... hugo4

Author SHA1 Message Date
Hugo LAMY
f6f63931ad litle fix in utils trim
+ added 2 functions to check script output
+ created a html page with 4 forms for tests
+ created 2 cpp forms, with and without creating header content-length
2022-08-15 00:24:08 +02:00
Hugo LAMY
d663a4c7e6 added debug print 2022-08-14 16:17:24 +02:00
Hugo LAMY
be499328f6 fixed error in is_cgi, path was missing dot 2022-08-14 01:17:55 +02:00
Hugo LAMY
41db4fc12b Merge branch 'hugo4' 2022-08-14 01:10:01 +02:00
Hugo LAMY
a20a5eff27 Merge branch 'master' of bitbucket.org:LuckyLaszlo/webserv 2022-08-14 01:09:25 +02:00
Hugo LAMY
db4c7468cc is_cgi function is back and is working
+ in utils added function eval_file_mode(), it returns http errors codes about file
2022-08-14 01:05:20 +02:00
LuckyLaszlo
b0949615c8 fixed mistake in _read_request()
+ changes to some log output
2022-08-13 19:17:32 +02:00
hugogogo
285f2d049f + in fill_script_path() : scipt_path and script_info are filled, removing leadin dot
+ in _cgi_pos() : check validity of extension if it's only alpha characters
+ severall pbm appears
2022-08-13 14:38:50 +02:00
hugogogo
defb2ada61 fixed pbm in extract_line : delete the line and the newline sequence character
+ fixed pbm n _cgi_pos : last return is npos, not pos
2022-08-13 12:39:05 +02:00
LuckyLaszlo
058701f657 upload_form.html
+ need to add multipart/form-data parsing
2022-08-13 01:15:59 +02:00
hugogogo
f17bc9fa58 wip test path cgi :
- _cgi_pos seems to be ok
  - construct url from return is not implemented yet
+ renamed script with extension .php and .cpp
2022-08-12 23:31:59 +02:00
LuckyLaszlo
c7bbf29a1b NPOS macro 2022-08-12 18:16:49 +02:00
LuckyLaszlo
b44acafefe CGI discussion and a little bit of work done 2022-08-12 18:08:39 +02:00
Me
cade79c37f started non hardcoded cgi call 2022-08-12 16:20:50 +02:00
Eric LAZO
52824f30bd merged and added colors to makefile 2022-08-12 15:24:10 +02:00
hugogogo
2a70c6b26d Merge branch 'hugo3'
+ script output verification works (field duplicate and status)
+ parsing_message_http :
  - the file is deleted
  - of the three functions it contained, only one still exist
  - 'parse_http_headers' is now in utils
+ changes in utils :
  - http headers parsing doesn't change key case itself
  - a new function change key case tolower case in map<str, str>
  - added split_trim() that perform a split and a trim at once
  - resolved pbm in trim
  - added print_special to print special char '\r' and '\n'
  - del_line became extract_line, because it returns the deleted line
  - added get line, that does the same without deleting
  - moved http_header in utils, and made it more consistent
+ in Client :
  - parse_request is now named parse_request_headers to work with parse body
  - private function _parse_request_headers is then now _parse_request_fields
    (because it doesn't take care of request first line, but only fields)
  - added a debug function 'print_client'
2022-08-12 14:05:11 +02:00
hugogogo
71c07140e2 added text inside youpibanane files 2022-08-12 12:08:56 +02:00
hugogogo
de09c2357c php newline pbm resolved 2022-08-12 12:00:20 +02:00
hugogogo
c225063361 script output fields is checked for duplicate 2022-08-12 11:45:06 +02:00
LuckyLaszlo
400efbe720 Wip chunked decoding
+ Need to test normal body parsing
+ path_is_valid() renamed eval_file_type()
+ replaced atoi with strtol/strtoul
2022-08-12 05:50:00 +02:00
Me
f7c0ff1a8a clean up 2022-08-12 04:38:07 +02:00
hugogogo
3a58b5d921 wip debug comparison fields output script 2022-08-11 19:42:09 +02:00
hugogogo
a1fff0f8c2 added colors, for print_special
+ renamed client's public function parse_request() into parse_request_headers()
2022-08-11 17:46:27 +02:00
hugogogo
ad2b5a629a correct extract_line to avoid duplicate with erase or substr, and to resolve pbm of returning trailing delim 2022-08-11 17:33:42 +02:00
hugogogo
ff77dfd298 debug new request parsing 2022-08-11 14:50:56 +02:00
LuckyLaszlo
ab0bc2c4c0 added timeout response (status 408)
+ added EPOLLERR and EPOLLHUP handling
+ fix root substitution for default "/" location (temp or permanent ?)
+ tested siege a little, seems good
2022-08-11 07:12:13 +02:00
Me
08f6929db9 fixed autoindex 2022-08-11 02:15:40 +02:00
Me
c32fc2c8a2 I really hope this is the last time we fix Location Picker, i think it's good it's pretty elegant, basically set root and location path to never have / at end and that made things easier 2022-08-11 02:01:24 +02:00
hugogogo
27b4f96618 parsing headers is allright now
+ adding error codes in readme and in comments in client
2022-08-11 00:41:17 +02:00
Me
0b51d13f13 a few more things for location picker 2022-08-11 00:36:24 +02:00
hugogogo
1d67e6988d http message parsin wip but really better 2022-08-11 00:28:01 +02:00
hugogogo
11f71ea74f wip parsing first line http message with new utils functions 2022-08-10 20:27:48 +02:00
hugogogo
9a379c835d correction of trim in case of str only fill with delim char
+ added a new split, that also does trim, to split without counting newlines
2022-08-10 20:01:23 +02:00
LuckyLaszlo
360c27df5f done root substitution
+ autoindex/index adjustement
2022-08-10 19:59:05 +02:00
hugogogo
c7905ebd19 changed del_line_in_str to etract_line 2022-08-10 17:12:28 +02:00
hugogogo
17230ccc42 in cgi exec add save stdin and out 2022-08-10 16:24:24 +02:00
Me
69c1a6f6bf A Much better way of picking locations 2022-08-10 16:14:21 +02:00
LuckyLaszlo
86f7740984 Moved most request errors check in Client
+ bugfix of "Bad file descriptor" due to empty operator=() in Client
+ added a condition in _determine_location_COOP_FIX()
+ udpate memo.txt
+ need fix for index/autoindex (location "/list" dont work)
2022-08-10 07:19:05 +02:00
Me
58f67bf42e fixed a tiny mistake in location picker 2022-08-10 02:07:17 +02:00
Me
f7f3e42b15 Merge remote-tracking branch 'origin/master' into eric_config_parser 2022-08-10 01:59:39 +02:00
Me
8e90221058 location picker works, needs to be tested more tho 2022-08-10 01:59:10 +02:00
LuckyLaszlo
f393aa46bf BUG: "Bad file descriptor" in _close_all_clients() 2022-08-09 22:29:35 +02:00
LuckyLaszlo
1eb989a3fd _determine_location() new version (to complete and test) 2022-08-09 20:47:21 +02:00
Me
56f4cf7e15 Eric notes to self 2022-08-09 20:06:28 +02:00
hugogogo
482a38952e Merge branch 'hugo2'
+ changed the appropriate getters for client
+ moved the functions to parse the http message
  (first line, headers, and body)
  outside client, in parsing_http_message.cpp
+ resolved some of the throw problems
2022-08-09 15:54:40 +02:00
hugogogo
ef034a356b Merge branch 'master' of bitbucket.org:LuckyLaszlo/webserv 2022-08-09 14:59:39 +02:00
Me
1d3cdef7a6 Merge remote-tracking branch 'origin/master' into eric_config_parser 2022-08-09 02:38:09 +02:00
Me
97c90236b9 cgi_ext, redirect and upload_repo now functional in config, fixed location picker, some cleanup 2022-08-09 02:37:28 +02:00
LuckyLaszlo
643b09c4f7 _request() and _response() refactoring
+ Wip redirection (weird behavior)
+ 505 HTTP Version Not Supported
2022-08-09 01:46:59 +02:00
LuckyLaszlo
a44b9b493a split respone.cpp file 2022-08-08 20:36:01 +02:00
Me
7fdc81f5f4 a bit messy but merged index and autoindex work 2022-08-08 17:13:55 +02:00
Me
0a72778c8c successful merge? 2022-08-08 15:25:16 +02:00
LuckyLaszlo
02cfdf43bc bad_alloc handling
+ map.insert() replace map.operator[] for init, more efficient
2022-08-08 04:24:03 +02:00
Me
f87024c32f further tested autoindex, and i got a .css stylesheet to load from a folder outside of /www, pretty neat :) 2022-08-08 04:02:53 +02:00
Me
9ee7205b95 Index and Autoindex work nicely, standarized way paths for root and location path are stored in config, still needs a little polishing prolly 2022-08-08 02:50:37 +02:00
Me
cf69168a84 some cleanup of config stuff 2022-08-07 22:54:58 +02:00
Me
94852babc6 autoindex is working, a few things to iron out be we well on our way 2022-08-07 22:25:24 +02:00
Me
f777441edf autoindex in progress but need to check stuff on master branch 2022-08-07 20:37:53 +02:00
LuckyLaszlo
1ccf61bc68 assign server and location to client in _read_request() 2022-08-07 17:37:24 +02:00
LuckyLaszlo
4ab099ee4d redone _read_request(). Should work with any BUFSIZE.
+ WIP in some aspects. Need to adjust parse_request() among others things.
+ update memo.txt
2022-08-07 05:41:01 +02:00
Me
4870b2c05d Location sorter works, fixed the compilation error i was having on this branch, now working on autoindex 2022-08-07 03:24:49 +02:00
LuckyLaszlo
8ec1353723 fixed response.append(body) problem.
+ File I/O working but maybe not perfect (it seems complicated to do it with std::filebuf)
+ fixed Content-Type for unknown file extension
2022-08-07 01:38:33 +02:00
Me
4b1baca126 merged, seems fine 2022-08-07 01:10:05 +02:00
Me
6f0c3b4d6d kinda successful merge, weird tho, didn't go as smoothly with VScode as usual... 2022-08-05 22:25:11 +02:00
Me
f7dc5ccde4 ok Location sorting and processing is finally working as expected, still a few things to add like error_pages but will get there soon 2022-08-05 21:46:46 +02:00
Me
9ac14aa1aa Well we learned a lot from Nginx, starting to incorporate some, but hoping for feedback before i do most of that, instead worked on improving the location sorter and checking that locations paths are valid, close but not quite done 2022-08-05 03:42:42 +02:00
Eric LAZO
f7e6b61811 LocationConfig sorter works as intended, tho should prolly check things thoroughly later 2022-08-04 19:33:38 +02:00
Me
fce1bcbece well, the location sorter is better, but i don't understand why, kinda by accident, will try to figure it out 2022-08-04 16:45:12 +02:00
Me
ca8427262d wrote a tiny thing to improve the operator< overlaod, will test tomorrow 2022-08-04 01:41:19 +02:00
Me
3d46505411 well, restructured things a bit, but mostly started trying to sort LocationConfigs, it is sort of going well, good ideas, implementations is still a bit lacking... 2022-08-04 01:09:40 +02:00
Me
b0c524a8bd Merge remote-tracking branch 'origin/master' into eric_config_parser
let's get it done
2022-08-03 18:56:54 +02:00
Me
fef26aee5b getting ready to pull 2022-08-03 18:56:34 +02:00
60 changed files with 2422 additions and 1267 deletions

2
.gitignore vendored
View File

@@ -20,3 +20,5 @@ ubuntu_cgi_tester
webserv
!**/webserv/
*.log
large.jpg

View File

@@ -22,14 +22,14 @@ SRCS_D = srcs \
SRCS = main.cpp \
base.cpp init.cpp close.cpp epoll_update.cpp signal.cpp \
accept.cpp request.cpp response.cpp \
run_loop.cpp \
ConfigParser.cpp \
ConfigParserUtils.cpp \
ConfigParserPost.cpp \
method_get.cpp method_post.cpp method_delete.cpp \
run_loop.cpp timeout.cpp \
parser.cpp \
extraConfig.cpp \
postProcessing.cpp \
utils.cpp \
cgi_script.cpp \
Client.cpp \
parsing_message_http.cpp \
OBJS_D = builds
OBJS = $(SRCS:%.cpp=$(OBJS_D)/%.o)
@@ -43,12 +43,14 @@ 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)"
clean:
rm -rf $(OBJS_D)
@@ -62,3 +64,24 @@ 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

145
README.md
View File

@@ -1,86 +1,18 @@
## work together
#### message
- err access line response.cpp:169
- g pas fini la gestion de sortie du cgi (comparaison des headers fields pour supprimer les doublons)
- g rendu independantes les fonctions qui parses un message http (requete ou response) dans `parsing_http_message.cpp`
- il y a une erreur importante dans la fonction qui parse les headers d'un message http : ca ne verifie pas si la premier ligne est un header ou la "first line"
#### TODO
- `_is_cgi()` and `_fill_cgi_path()`
- `_cgi_output()` change status in `client->status`
- two cgi tests :
? - a basic form with "name" and "something", that return a html page with that
? - for GET and POST
? - a script called by a file extension in URI
#### output cgi script :
! TODO : change all the '\n' by '\r\n'
! TODO : there is at least one header field followed by '\r\n\r\n' :
- "Content-Type"
- "Location"
- "Status"
! TODO : there is no space between filed name and ":"
! TODO?: handle Location field, either :
- local : start with '/' --> rerun the request with new uri
- client : start with '<scheme>:' --> send back status code 302
-> TODO : there is no field duplicate (resolve conflicts)
-> TODO : if status field, change server status for this one
-> TODO : if no Location field && no Status field -> status code = 200
#### next commit
#### questions
- in client.cpp i fill the port, is there a default one in case it's not in the request ?
- timeout server but still works ?
- path contains double "//" from `Webserv::_get()` in response.cpp
- cgi path ? defined in config ? and root path ? :
- `Client.cpp : fill_script_path()`
- `cgi.cpp : is_cgi()`
- `cgi.cpp : set_env()`
- what if the uri contains a php file, and the config said php must be handled by cgi, but the path to this php in the uri is wrong ?
- is it ok ? `http://my_site.com/cgi-bin/php-cgi` (real path)
- is it ok ? `http://my_site.com/php-cgi` (reconstruct path ?)
- is it ok ? `http://my_site.com/something/php-cgi` (what about 'something' ?)
- is it ok ? `http://my_site.com/something/cgi-bin/php-cgi` (real path with 'something' before ? )
- I don't save the STDIN and STDOUT before dup2 in child process, is it wrong ?
- how should we handle a wrong url like `http://localhost/cgi-bin/wrong.phpp/good.php` ?
- do we serve `./srcs/cgi-bin/good.php` ?
- or do we return 404 "not found" ?
-> - for now, execve would crash, but that doesn't produce a 404 error, rather a 500, is it bad ?
- could we use errno after execve to choose an appropriate http error ? subject says : "Checking the value of errno is strictly forbidden after a read or a write operation"
- if a url has a file with extension, but it's not a cgi extension, is it necessary to look further ?
- ex. `http://localhost/file.php/file.py` for `cgi_ext py;` ?
- the response page is received long after the cgi-script is done, why ?
#### notifications
- i changed the Client getters in two categories :
- getters for requests infos : `get_rq_<info>`
- getters for client sides infos : `get_cl_<info>` (such as ip of client)
- i changed the variables in request struct in Client :
- `path` become `uri` (ex. `/path/to/file?var=val`)
- add `abs_path` (ex. `/path/to/file` )
- add `query` (ex. `var=val`)
- the header fields names, as key in map, are stored in lowercase, and getters are case-insensitives
respsonse.cpp
```
_response()
{
_determine_process_server()
_send_response()
{
_append_base_headers()
_construct_response()
{
_process_method()
{
_get()
{
_exec_cgi()
}
}
_insert_status_line()
::send(headers)
::send(body)
}
}
}
```
---
## man
@@ -159,6 +91,8 @@ _response()
## 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 :
@@ -167,13 +101,18 @@ _response()
- "Status"
- the cgi-script may send back more header fields
- the server must check and modify few things :
- there is no duplicate in headers fields (if there is, resolve conflict)
- there is no space between the field name and the ":"
- the newlines are of form "\r\n", and not "\n" only
- if the location field is not present, then if the status field is not present either, then the status code is 200
- the cgi-script can return a location field, of two types :
- local redirection : start with a "/", the server must answer as if this was the request uri
- client redirection : start with <name-of-cheme>":", the server must send back a status 302 with this uri to the client
- 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)
@@ -304,6 +243,46 @@ SERVER_SOFTWARE : the server software you're using (e.g. Apache 1.3)
REDIRECT_STATUS : for exemple, 200
```
---
## http errors
#### HTTP Client Errors
- 400 Bad Request This error code indicates that the request cannot be processed because of wrong syntax usage by the client.
- 401 Unauthorized This error code indicates that the client is not authorized to receive the requested data, without authentication. A login name and password based authentication might be required to access the requested data.
- 403 Forbidden There is no way you can access the requested data. A 403 error announces that the data is off limits.
- 404 Not Found This error indicates that the resources requested by the client are currently unavailable.
- 405 Method Not Allowed This error indicates wrong usage of request method. Depending on the kind of data requested, the appropriate request method must be chosen.
- 406 Not Acceptable When the data provided by a web server does not match the specifications made in Accept header of the client HTTP request, this error is the result.
- 407 Proxy Authentication Required This error clearly indicates that an authentication from the proxy server is required to gain access to requested resources.
- 408 Request Timeout This type of error indicates that the client was delayed in making a request, within the specified time allocated to it, by the server.
- 409 Conflict This error code is displayed when the server perceives a conflict between two requests made simultaneously by different clients, for the same resource.
- 410 Gone This error code indicates that the requested data is no longer hosted on the server and therefore further requests made for it, would be futile.
- 411 Length Required If the request made by the client does not include information about the length of the requested data or resource, this error code is displayed.
- 412 Precondition Failed Some requests made by clients come attached with conditions that need to be satisfied by the server, before data transaction may happen. If these conditions are not met, error 412 results.
- 413 Request Entity Too Large When a client makes a request which is too overwhelming for the servers resources to handle, it presents this error code.
- 414 Requested URI Too Long A Uniform Resource Identifier (URI) is a character string used to describe a data stream or resource on a server. Error 414 occurs when the server is unable to process the URI, because of limited resources and long string length.
- 415 Unsupported Media Type A server may be designed to allow only certain formats for media files. When error 415 is displayed, it indicates that the format of file being uploaded through a client request, does not match the requisite format.
- 416 Request Range Not Satisfiable Sometimes, a client may request for only a small part of a file, instead of asking for the entire file. If this request is not specified properly and the part of the file requested does not exist, this error is displayed.
- 417 Expectation Failed This error code is displayed when the server cannot meet the specifications provided in the request.
- 422 Unprocessable Entity This error is displayed when the request made, cannot be processed due to an error in semantic structure.
- 423 Locked This error is displayed when a requested piece of data or resource has been locked, making it inaccessible for a server.
- 424 Failed Dependency A server may process a succession of requests from a client with the fulfillment of each, dependent on the one made before. This error is displayed when a request made before is not fulfilled, due to which the current request cannot be processed.
- 426 Upgrade Required This error signifies that the client may need to switch over to a secure protocol like TLS to get the request processed.
- 444 No Response This error signifies that the server has simply rejected the client request and terminated connection.
- 449 Retry With This is a request made by the server to the client, to make the request again after executing certain actions or making specific changes in request. This is an error code introduced by Microsoft.
- 499 Client Closed Request When client terminates a connection made with the server, while its processing the associated request, this error code is displayed.
- 450 Blocked By Windows Parental Controls Another error code introduced by Microsoft, this one is displayed when a URL is blocked by parental control settings on the web browsers.
#### HTTP Server Errors
- 500 Internal Server Error A generic message displayed by the server, when the problem with the request cannot be specified by any other appropriate code.
- 501 Not Implemented This error indicates the inability of the server to process a request, as it hasnt been configured to respond to the request method used.
- 502 Bad Gateway Sometimes, hosted pages on web servers are transmitted to client via proxy servers. If the proxy server (to which a client has sent a request), fails connecting with the web server (known as the upstream server), error 502 results.
- 503 Service Unavailable When the server is already overloaded with multiple requests, it will temporarily stop entertaining new requests, by displaying a 503 error code.
- 504 Gateway Timeout When the request made by a proxy server to the web server hosting a resource times out, error 504 is reported.
- 505 HTTP Version Not Supported An error code seen rarely, it is displayed when the web server does not support the protocol version of the client request.
---
## ressources

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,14 +10,63 @@ server {
# client_body_limit 400;
index index.html; # this is another comment
root ./www/;
# If not explicitly set, ConfigParser need to genererate a location block
# like this for path "/" (based on field "root" and "index" of the server)
location / {
root ./www/;
index index.html;
error_page 404 ./www/error_pages/error_404.html;
location /list {
autoindex on;
}
allow_methods GET;
location /cgi-bin {
root ./srcs/cgi-bin/;
cgi_ext out php sh;
}
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 /test/index1.html {
root ./www/test/;
index index1.html subdex.html;
}
location /hilarious_404/ {
redirect 301 https://berniesanders.com/404/;
}
location /stylesheet/ {
# root ./www/../;
root ./styelsheet/;
}
location /test/something.html {
# allow_methods DELETE;
root ./www/test/;
}
location /test/test_deeper/ {
# allow_methods
autoindex on;
root ./www/test/test_deeper/;
}
location /test/test_deeper/super_deep {
root ./www/test/test_deeper/super_deep/;
autoindex on;
}
# location /test/test_deeper/something.html {
# allow_methods DELETE;
# }
}

View File

@@ -1,15 +1,28 @@
IN 42 SUBJECT AND/OR PRIORITY :
- CGI (TODO HUGO)
- chunked request (WIP, a bit difficult)
- Need to test normal body parsing
- basic html upload page for testing request of web browser
- upload files with config "upload_dir"
- Ecrire des tests !
- handle redirection (Work, but weird behavior need deeper test)
- _determine_location() review (New version to complete and test)
-----------------------------
Si ce n'est pas deja fait :
- dans config, check erreur si port > 16bits
(peut-être check si ip > 32bits)
-----------------------------
- 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)
- handle redirection
- maybe add a "last_action_time" in Client for timeout handling
little global timeout on epoll, like 100ms, then find client that actualy need to timeout
if (actual_time - client.last_action_time > 10000ms){timeout(client)}
- add headers "Date" and "Last-Modified" to response
- change "std::string" to reference "std::string &" in most functions
and add "const" if apropriate.
- http_method en mode binary flags. "std::vector<http_method> allow_methods" -> "unsigned int allow_methods;"
- Dans le parsing, trier les "locations" par ordre de precision.
Compter les "/" dans le chemin, les locations avec le plus de "/" seront en premier dans le vector.
- Il faut vérifier le path de la requête, voir si le serveur est bien censé délivrer cette ressource et si le client y a accès, avant d'appeler le CGI.

View File

@@ -1,29 +1,38 @@
#include "Client.hpp"
char Client::buf[MAX_FILESIZE+1];
/*********************************************
* COPLIENS
*********************************************/
Client::Client()
: body_size(0)
, status(0)
, _fd(0)
: status(0),
header_complete(false),
body_complete(false),
request_complete(false),
read_body_size(0),
assigned_server(NULL),
assigned_location(NULL),
_fd(0),
_port(""),
_ip(""),
_lsocket(NULL)
{
return;
}
//Client::Client(int afd, listen_socket *lsocket, struct sockaddr_in addr)
Client::Client(int afd, listen_socket *lsocket, std::string aport, std::string aip)
: body_size(0)
, status(0)
, _fd(afd)
, _port(aport)
, _ip(aip)
, _lsocket(lsocket)
: status(0),
header_complete(false),
body_complete(false),
request_complete(false),
read_body_size(0),
assigned_server(NULL),
assigned_location(NULL),
_fd(afd),
_port(aport),
_ip(aip),
_lsocket(lsocket)
{
return;
}
@@ -32,24 +41,26 @@ Client::~Client() {
return;
}
// WIP placeholder because of const values
/* // WIP not sure fo what is more logic here
Client::Client( Client const & src )
: body_size(0)
, status(0)
, _fd ( src._fd )
, _port ( src._port )
, _ip ( src._ip )
, _lsocket ( src._lsocket )
: status ( src.status ),
header_complete ( src.header_complete ),
read_body_size ( src.read_body_size ),
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
body_size = src.body_size;
status = src.status;
return;
}
*/
// WIP placeholder because of const values
/* // WIP placeholder because of const values
Client & Client::operator=( Client const & rhs )
{
if ( this != &rhs )
@@ -58,7 +69,7 @@ Client & Client::operator=( Client const & rhs )
}
return *this;
}
*/
/*********************************************
* PUBLIC MEMBER FUNCTIONS
@@ -68,45 +79,160 @@ Client & Client::operator=( Client const & rhs )
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
// https://www.ibm.com/docs/en/cics-ts/5.3?topic=protocol-http-requests
// https://www.tutorialspoint.com/http/http_requests.htm
void Client::parse_request()
void Client::parse_request_headers(std::vector<ServerConfig> &servers)
{
std::map<std::string, std::string> headers;
std::string body;
// DEBUG
std::cout << "\nREQUEST ____________\n" << raw_request << "\n_____________\n";
if (raw_request.find(CRLF CRLF) == NPOS)
return ;
header_complete = true;
clear_request(); // not mandatory
_parse_request_line();
_parse_request_headers();
_parse_request_body();
_parse_port_hostname(this->get_rq_headers("Host"));
raw_request.clear();
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(this->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();
}
bool Client::fill_script_path(std::string script)
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 ?
return;
}
pos += CRLF_SIZE*2;
// Chunked decoding WIP. Dont work.
if (!get_rq_headers("Transfer-Encoding").empty()
&& get_rq_headers("Transfer-Encoding") == "chunked")
{
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 (chunk_size == LONG_MAX && errno == ERANGE)
status = 413; */
/* 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;
}
else
{
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";
if (raw_request.size() - pos >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10))
{
_request.body = raw_request.substr(pos);
body_complete = true;
}
/* Should be equivalent */
// _request.body = raw_request.substr(pos);
// if (_request.body.size() >= std::strtoul(get_rq_headers("Content-Length").c_str(), NULL, 10))
// body_complete = true;
}
///////////////
// Body checks
if (_request.body.size() > assigned_server->client_body_limit)
status = 413; // HTTP Client Errors
}
void Client::fill_script_path(std::string &path, size_t pos)
{
size_t pos;
int len = script.size();
std::string path = this->get_rq_abs_path();
std::string tmp;
pos = path.find(script);
if (pos == 0)
if (path[0] == '.')
{
tmp = path.substr(0, pos + len);
_request.script.path = "./srcs" + tmp; // TODO: root path ?
_request.script.info = path.substr(pos + len);
return true;
path.erase(0, 1);
pos--;
}
return false;
_request.script.path = path.substr(0, pos);
_request.script.info = path.substr(pos);
}
void Client::clear()
{
clear_request();
header_complete = false;
body_complete = false;
request_complete = false;
read_body_size = 0;
assigned_server = NULL;
assigned_location = NULL;
raw_request.clear();
response.clear();
body_size = 0;
status = 0;
}
@@ -114,7 +240,7 @@ void Client::clear_request()
{
clear_script();
_request.method = UNKNOWN;
_request.uri.clear();
_request.target.clear();
_request.version.clear();
_request.headers.clear();
_request.body.clear();
@@ -130,6 +256,33 @@ void Client::clear_script()
_request.script.info.clear();
}
// debug
void Client::print_client(std::string message)
{
std::map<std::string, std::string>::iterator it;
std::cout << "\n=== DEBUG PRINT CLIENT ===\n";
std::cout << message << ":\n----------\n\n" << "raw_request:\n__\n";
::print_special(raw_request);
std::cout << "\n__\n"
<< "get_cl_fd() : [" << get_cl_fd() << "]\n"
<< "get_cl_port() : [" << get_cl_port() << "]\n"
<< "get_cl_ip() : [" << get_cl_ip() << "]\n"
<< "get_rq_method_str() : [" << get_rq_method_str() << "]\n"
<< "get_rq_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
@@ -145,7 +298,7 @@ const listen_socket * Client::get_cl_lsocket() const { return _lsocket; }
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_uri() const { return _request.uri; }
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; }
@@ -154,6 +307,7 @@ 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;
@@ -172,45 +326,62 @@ std::string Client::get_rq_headers(const std::string & key) const
void Client::_parse_request_line()
{
std::vector<std::string> line;
int ret;
std::string raw_line;
ret = ::parse_http_first_line(raw_request, line);
if (ret != 3)
raw_line = ::get_line(raw_request, 0, CRLF);
line = ::split_trim(raw_line, " ");
if (line.size() != 3)
{
std::cerr << "err _parse_first_line(): wrong number of elements (" << ret << " instead of 3)\n";
std::cerr << "err _parse_first_line(): wrong number of elements (" << line.size() << " instead of 3)\n";
status = 400; // "bad request"
}
else
{
_request.method = str_to_http_method(line[0]);
_request.uri = line[1];
_parse_request_uri(line[1]);
_request.target = line[1];
_parse_request_target(line[1]);
_request.version = line[2];
}
}
void Client::_parse_request_uri( std::string uri )
void Client::_parse_request_target( std::string target )
{
size_t pos;
pos = uri.find("?");
if (pos != std::string::npos)
_request.query = uri.substr(pos + 1);
pos = target.find("?");
if (pos != NPOS)
_request.query = target.substr(pos + 1);
else
_request.query = "";
_request.abs_path = uri.substr(0, pos);
_request.abs_path = target.substr(0, pos);
}
void Client::_parse_request_headers()
void Client::_parse_request_fields()
{
// TODO: check error and adjust status
_request.headers = ::parse_http_headers(raw_request);
}
std::string headers;
size_t pos;
int ret;
void Client::_parse_request_body()
{
// TODO: check error and adjust status
_request.body = ::parse_http_body(raw_request);
headers = raw_request;
// delete first line
pos = headers.find(CRLF);
if (pos != NPOS)
headers.erase(0, pos + std::string(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);
}
void Client::_parse_port_hostname(std::string host)
@@ -222,7 +393,7 @@ void Client::_parse_port_hostname(std::string host)
pos = host.find(':');
// port :
if (pos == std::string::npos)
if (pos == NPOS)
_request.port = "4040"; // TODO: make equal to default port in config
else
_request.port = host.substr(pos);
@@ -232,6 +403,47 @@ void Client::_parse_port_hostname(std::string host)
_request.hostname = host.substr(0, pos);
}
void Client::_check_request_errors()
{
///////////////////////
// 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 (!this->get_rq_headers("Content-Length").empty()
&& std::strtoul(this->get_rq_headers("Content-Length").c_str(), NULL, 10) > assigned_server->client_body_limit)
status = 413;
else if (!this->get_rq_headers("Transfer-Encoding").empty()
&& this->get_rq_headers("Transfer-Encoding") != "chunked" )
status = 501;
else if (!this->get_rq_headers("Content-Encoding").empty())
{
status = 415;
response.append("Accept-Encoding:"); // empty, no encoding accepted
response.append(CRLF);
}
return;
}
/*********************************************
* OVERLOAD

View File

@@ -10,7 +10,8 @@
# 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 "parsing_message_http.hpp"
# include "ServerConfig.hpp"
# include "colors.h"
struct Script
{
@@ -21,7 +22,7 @@ struct Script
struct Request
{
http_method method;
std::string uri;
std::string target;
std::string abs_path;
std::string query;
std::string version;
@@ -32,21 +33,24 @@ struct Request
struct Script script;
};
# define MAX_FILESIZE 1000000 // (1Mo)
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;
static char buf[MAX_FILESIZE+1];
size_t body_size;
unsigned int status;
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;
// getters
int get_cl_fd() const;
@@ -57,7 +61,7 @@ class Client
// requests getters
http_method get_rq_method() const;
std::string get_rq_method_str() const;
std::string get_rq_uri() 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;
@@ -68,11 +72,14 @@ class Client
std::string get_rq_script_info() const;
std::string get_rq_headers(const std::string & key) const;
void parse_request();
void parse_request_headers(std::vector<ServerConfig> &servers);
void parse_request_body();
void clear();
void clear_request();
void clear_script();
bool fill_script_path(std::string script);
void fill_script_path(std::string &path, size_t pos);
// DEBUG
void print_client(std::string message = "");
private:
int _fd;
@@ -82,16 +89,20 @@ class Client
struct Request _request;
void _parse_request_line();
void _parse_request_headers();
void _parse_request_body();
void _parse_request_uri( std::string uri );
void _parse_request_fields();
void _parse_request_target( std::string target );
void _parse_port_hostname(std::string host);
void _check_request_errors();
};
bool operator==(const Client& lhs, const Client& rhs);
bool operator==(const Client& lhs, int fd);
bool operator==(int fd, const Client& rhs);
// Temporary Global Scope. Probably move to Client in the future.
ServerConfig *_determine_process_server(Client *client, std::vector<ServerConfig> &servers);
const LocationConfig *_determine_location(const ServerConfig &server, const std::string &path);
#endif

View File

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

4
srcs/cgi-bin/cgi.sh Executable file
View File

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

Binary file not shown.

114
srcs/cgi-bin/cgi_cpp.cpp Normal file
View File

@@ -0,0 +1,114 @@
# include <iostream>
# include <string>
# include <sstream>
# include <vector>
# include <stdlib.h> // getenv
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
# define NPOS std::string::npos
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 = '\0')
{
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;
}
int main (int ac, char **av, char **en)
{
std::vector<std::string> split_str;
std::vector<std::string> sub_split_str;
std::vector<std::string>::const_iterator it;
char * tmp;
std::string input;
std::string http_header;
std::string http_body;
std::ostringstream strs;
size_t pos;
std::cin >> input;
http_header = "Content-Type: text/html; charset=UTF-8" CRLF;
http_header += "Content-Length: ";
http_body = "\
<!DOCTYPE html>\
<html>\
<head>\
<title>CGI</title>\
</head>\
<body>\
<h2>cgi</h2>\
";
http_body += "<h3>";
tmp = getenv("REQUEST_METHOD");
if (tmp != NULL)
http_body += tmp;
else
http_body = "method not foud";
http_body += "</h3>";
split_str = split(input, "&");
for (it = split_str.begin(); it != split_str.end(); ++it)
{
sub_split_str = split(*it, "=");
http_body += "<h3>";
http_body += sub_split_str[0];
http_body += "</h3>";
http_body += "<p>";
http_body += sub_split_str[1];
http_body += "</p>";
}
http_body += "\
</body>\
</html>\
";
strs << http_body.size();
http_header += strs.str();
http_header += CRLF CRLF;
std::cout << http_header << CRLF CRLF << http_body;
return 0;
}

View File

@@ -0,0 +1,133 @@
# include <iostream>
# include <string>
# include <sstream>
# include <vector>
# include <stdlib.h> // getenv
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
# define NPOS std::string::npos
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 = '\0')
{
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;
}
int main (int ac, char **av, char **en) {
std::vector<std::string> split_str;
std::vector<std::string> sub_split_str;
std::vector<std::string>::const_iterator it;
char * tmp;
std::string output;
std::ostringstream strs;
size_t pos;
std::cout << "Content-Type: text/html; charset=UTF-8" << CRLF CRLF;
std::cout
<< "<!DOCTYPE html>"
<< "<html>"
<< "<head>"
<< " <title>CGI</title>"
<< "</head>"
<< "<body>"
<< " <h2>cgi</h2>"
<< " <h3>";
tmp = getenv("REQUEST_METHOD");
if (tmp != NULL)
output = tmp;
else
output = "method not foud";
std::cout
<< output
<< " </h3>"
<< " <h3>http-request-body-message content :</h3>";
std::cin >> output;
split_str = split(output, "&");
output.clear();
for (it = split_str.begin(); it != split_str.end(); ++it)
{
sub_split_str = split(*it, "=");
std::cout
<< "<p>"
<< sub_split_str[0]
<< " : "
<< sub_split_str[1]
<< "</p>";
}
tmp = getenv("QUERY_STRING");
if (tmp == NULL)
std::cout << "query not foud";
std::cout
<< " <h3>http-uri-query content :</h3>";
output = tmp;
split_str = split(output, "&");
output.clear();
for (it = split_str.begin(); it != split_str.end(); ++it)
{
sub_split_str = split(*it, "=");
std::cout
<< "<h3>"
<< sub_split_str[0]
<< "</h3>"
<< "<p>"
<< sub_split_str[1]
<< "</p>";
}
std::cout
<< "</body>"
<< "</html>";
return 0;
}

29
srcs/cgi-bin/cgi_second/cgi.php Executable file
View File

@@ -0,0 +1,29 @@
#! /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";
?>

25
srcs/colors.h Normal file
View File

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

View File

@@ -1,14 +1,3 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* ConfigParser.hpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/11 23:01:41 by me #+# #+# */
/* Updated: 2022/08/03 17:32:33 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
#ifndef CONFIGPARSER_HPP
# define CONFIGPARSER_HPP
@@ -22,18 +11,19 @@
# include <exception> // exception, what
# include <stdexcept> // runtime_error, invalid_argument
# include <string> // string
# include <cstdlib> // atoi (athough it's already cover by <string>)
# include <cstdlib> // strtol, stroul
# include <iostream> // cout, cin
# include <fstream> // ifstream
//# include <unistd.h> // access()
# include <dirent.h> // opendir()
# include <dirent.h> // opendir(), doesn't work...
# include <sys/stat.h> // stat(), replaces opendir() don't bother with ERRNO ?
# include <algorithm> // sort() in Post
class ConfigParser {
public:
// canonical
// canonical?
ConfigParser(const char* path); // a string?
~ConfigParser();
@@ -41,16 +31,15 @@ public:
// 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;
@@ -75,29 +64,10 @@ private:
std::string _get_rest_of_line(size_t *curr); // const?
// some sort of post processing...
/* Post Processing */
void _post_processing(std::vector<ServerConfig> *servers);
bool _find_root_path_location(std::vector<LocationConfig> locations); // const?
};
// 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
#endif

View File

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

View File

@@ -6,7 +6,7 @@
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/23 16:08:00 by me #+# #+# */
/* Updated: 2022/08/02 14:06:07 by lperrey ### ########.fr */
/* Updated: 2022/08/12 18:12:23 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
@@ -16,7 +16,10 @@
# include <map>
# include <vector>
# include <string>
# include <iostream>
# include <sys/stat.h> // stat()
# include "utils.hpp"
// again, struct instead?
class LocationConfig
@@ -24,22 +27,62 @@ class LocationConfig
public:
// canonic stuff?
std::string path;
int client_body_limit;
std::string root;
std::string path; // /path and /path/ are fine
// i remove trailing / systematically
std::string root;
std::vector<std::string> index;
std::vector<http_method> allow_methods;
std::map<std::string, std::string> cgi_info;
unsigned int allow_methods;
std::vector<std::string> cgi_ext; // php not .php
bool autoindex;
// 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.
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";
}
// works a lot better than using a compare function...
bool operator<(const LocationConfig& rhs) const
{
int comp_lhs = 0;
int comp_rhs = 0;
size_t tmp = 0;
while ((tmp = this->path.find_first_of("/", tmp)) != 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); // right comparison ? not <= ?
};
};

View File

@@ -14,50 +14,28 @@
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?
std::string port;
// there can only be one.
std::string root;
std::string root; // ./www/ or www work www/ and www work
// i do remove trailing / tho
unsigned int client_body_limit; // set to default max if none set
size_t client_body_limit; // set to default max if none set
// 413 (Request Entity Too Large) if exceeded
// default is 1m 1 000 000 ?
// 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<http_method> 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()
void print_all() // const?
{
std::cout << "PRINTING A FULL SERVER CONFIG\n\n";
@@ -73,15 +51,12 @@ public:
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 << "skiping Locations for now...\n";
for (std::vector<LocationConfig>::iterator it = locations.begin(); it < locations.end(); it++)
it->print_all();
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';

View File

@@ -3,8 +3,8 @@
#include "ConfigParser.hpp"
// should i be sending & references?
// const?
std::string ConfigParser::_pre_set_val_check(const std::string key, \
const std::string value)
{
@@ -13,78 +13,58 @@ std::string ConfigParser::_pre_set_val_check(const std::string key, \
// 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)
if (key.find_first_of(";") != 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");
}
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 == std::string::npos || (value.find_last_not_of(" \n")) != i \
if (i == 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));
}
// const?
// 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)
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)) == std::string::npos)
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)) == std::string::npos)
if ((start = _content.find_first_not_of(" \t\n", *curr)) == 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)
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);
// std::cout << "rest of Line values: " << values << "\n";
return (values);
}
void ConfigParser::_print_content() const
{
std::cout << _content;
}
// I might need to make my own Exceptions to throw...

View File

@@ -1,14 +1,3 @@
/* ************************************************************************** */
/* */
/* ::: :::::::: */
/* ConfigParser.cpp :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: lperrey <lperrey@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2022/07/13 22:11:17 by me #+# #+# */
/* Updated: 2022/08/03 17:51:35 by lperrey ### ########.fr */
/* */
/* ************************************************************************** */
#include "ConfigParser.hpp"
@@ -21,7 +10,7 @@ ConfigParser::ConfigParser()
ConfigParser::ConfigParser(const char* path)
{
std::cout << "Param Constructor\n";
// std::cout << "Param Constructor\n";
std::ifstream file;
std::string buf;
@@ -31,21 +20,19 @@ ConfigParser::ConfigParser(const char* path)
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)
if ((comment = buf.find_first_of("#")) == NPOS)
{
// remove empty lines, i think...
if ((buf.find_first_not_of(" \t")) != std::string::npos)
if ((buf.find_first_not_of(" \t")) != 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
// check for comment at the end of the line
std::string tmp = buf.substr(0, comment - 1);
_content.append(tmp + '\n');
}
@@ -53,7 +40,7 @@ ConfigParser::ConfigParser(const char* path)
file.close();
}
else
throw std::invalid_argument("open config");
throw std::invalid_argument("failed to open config");
}
ConfigParser::~ConfigParser()
@@ -72,23 +59,23 @@ ConfigParser & ConfigParser::operator=(const ConfigParser& rhs)
}
*/
// const?
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)
if (curr == NPOS)
throw std::invalid_argument("empty config file");
while (curr != std::string::npos)
while (curr != 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);
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 1");
@@ -104,14 +91,13 @@ ServerConfig ConfigParser::_parse_server(size_t *start)
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] != '{')
if (curr == 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
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);
@@ -142,20 +128,24 @@ LocationConfig ConfigParser::_parse_location(size_t *start)
size_t curr = *start;
// start is after the 1st word aka "location"
ret.client_body_limit = 0;
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 /");
// ret.path.insert(0, "/");
// 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] != '{')
if (curr == 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)
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);
@@ -177,11 +167,11 @@ LocationConfig ConfigParser::_parse_location(size_t *start)
// 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, ' ');
@@ -189,11 +179,12 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
if (size < 1)
throw std::invalid_argument("missing value");
else if (key == "server_name" && size == 1)
else if (key == "server_name" && server->server_name.empty())
{
for (size_t i = 0; i < server->server_name.size(); i++)
for (std::vector<std::string>::iterator it = server->server_name.begin(); \
it < server->server_name.end(); it++)
{
if (server->server_name[i].compare(tmp_val[0]) == 0)
if (it->compare(tmp_val[0]) == 0)
throw std::invalid_argument("server_name already exists");
}
server->server_name.push_back(tmp_val[0]);
@@ -201,9 +192,8 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
else if (key == "listen" && size == 1 && server->host == "" \
&& server->port == "")
{
if (tmp_val[0].find_first_of(":") == std::string::npos)
if (tmp_val[0].find_first_of(":") == 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";
@@ -229,91 +219,47 @@ void ConfigParser::_set_server_values(ServerConfig *server, \
}
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");
// 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 == "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());
server->client_body_limit = std::strtoul(tmp_val[0].c_str(), NULL, 10);
}
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++)
{
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
server->allow_methods.push_back(m);
}
}
else if (key == "error_page")
{
// 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 (!(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())
continue ;
throw std::invalid_argument("redeclaring error page");
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.
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, ' ');
@@ -323,69 +269,65 @@ void ConfigParser::_set_location_values(LocationConfig *location, \
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");
// 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 == "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 == "autoindex" && size == 1)
location->autoindex = (tmp_val[0] == "on" ? true : false);
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())
else if (key == "allow_methods" && location->allow_methods == 0)
{
for (unsigned long i = 0; i != tmp_val.size(); i++)
{
http_method m = ::str_to_http_method(tmp_val[i]);
if (m == UNKNOWN)
throw std::invalid_argument("not a valid method");
location->allow_methods.push_back(m);
location->allow_methods |= m;
}
}
else if (key == "cgi_info")
else if (key == "cgi_ext")
{
// you can call cgi_info several times i think.
// 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());
for (size_t i = 0; i < tmp_val.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 == "return" && location->redirect_status == 0 \
else if (key == "redirect" && 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");
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");
// std::cout << tmp_val[1] << '\n';
if (tmp_val[1].compare(0, 7, "http://")
&& tmp_val[1].compare(0, 8, "https://"))
throw std::invalid_argument("bad redirect uri");
// somehow check that tmp_val[1] is a string? or valid? how?
// something about using access() to see if
location->redirect_status = atoi(tmp_val[0].c_str());
location->redirect_status = std::strtoul(tmp_val[0].c_str(), NULL, 10);
location->redirect_uri = tmp_val[1];
}
else
else if (key == "upload_dir" && size == 1 && location->upload_dir == "")
{
// 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");
// 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

@@ -0,0 +1,89 @@
#include "ConfigParser.hpp"
void ConfigParser::_post_processing(std::vector<ServerConfig> *servers)
{
std::vector<ServerConfig>::iterator it = servers->begin();
while (it != servers->end())
{
// host and port are Mandatory
if (it->host == "")
throw std::invalid_argument("Config file needs a host and port");
// root is mandatory
if (it->root == "")
throw std::invalid_argument("Config file needs a root");
// index is mandatory in Server
if (it->index.empty())
throw std::invalid_argument("Config file needs an Index");
if (it->client_body_limit == 0)
it->client_body_limit = 5000; // what is the recomended size?
// if error_pages is left empty, we'll use the defaults which
// i believe are set elsewhere...
if (!_find_root_path_location(it->locations))
{
LocationConfig tmp;
tmp.path = "/";
tmp.root = it->root;
tmp.index = it->index;
tmp.allow_methods = ANY_METHODS;
tmp.autoindex = false;
tmp.redirect_status = 0;
it->locations.push_back(tmp);
}
std::vector<LocationConfig>::iterator it_l = it->locations.begin();
while (it_l != it->locations.end())
{
if (it_l->root == "")
it_l->root = it->root;
if (it_l->allow_methods == UNKNOWN)
it_l->allow_methods = ANY_METHODS;
if (it_l->index.empty() && it_l->autoindex == false)
it_l->index = it->index;
// nothing to be done for cgi_ext, error_pages, redirect
// if (eval_file_type(it_l->root) == IS_DIR
// && it_l->path[it_l->path.size() - 1] != '/')
// it_l->path.push_back('/');
if (it_l->path[it_l->path.size() - 1] == '/'
&& it_l->path.size() > 1)
it_l->path.erase(it_l->path.size() - 1);
++it_l;
}
std::sort(it->locations.begin(), it->locations.end());
std::reverse(it->locations.begin(), it->locations.end());
++it;
}
}
// const?
bool ConfigParser::_find_root_path_location(std::vector<LocationConfig> locations)
{
std::vector<LocationConfig>::const_iterator it = locations.begin();
while (it != locations.end())
{
if (it->path.compare("/") == 0)
return true;
++it;
}
return false;
}

View File

@@ -19,7 +19,7 @@ int main(int ac, char **av)
ConfigParser configParser(config.c_str());
configParser._print_content();
// configParser._print_content();
// i don't love that servers has to be a pointer...
std::vector<ServerConfig>* servers = configParser.parse();
@@ -27,8 +27,9 @@ 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();
// it->print_all();
}
@@ -44,7 +45,7 @@ int main(int ac, char **av)
}
catch (std::exception& e)
{
std::cout << e.what() << '\n';
std::cerr << e.what() << '\n';
}
return (0);

View File

@@ -1,6 +1,22 @@
#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;
@@ -13,25 +29,62 @@ std::vector<std::string> split(std::string input, char delimiter)
return answer;
}
std::string trim(std::string str, char c)
std::vector<std::string>
split_trim(std::string input, std::string delim, char ctrim)
{
std::vector<std::string> split_str;
std::string tmp;
size_t start = 0;
size_t end = 0;
size_t len = 0;
while (end != 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 c
pos = str.find_first_not_of(c);
if (pos == std::string::npos)
return str;
// delete leadings del
pos = str.find_first_not_of(del);
if (pos == NPOS)
pos = str.size();
str = str.substr(pos);
// delete endings c
pos = str.find_last_not_of(c);
if (pos == std::string::npos)
return str;
str = str.substr(0, pos + 1);
// delete trailing del
pos = str.find_last_not_of(del);
if (pos != NPOS)
str = str.substr(0, pos + 1);
return str;
}
//// trim a set of char
//std::string trim(std::string str, std::string del)
//{
// std::string new_str;
//
// while (new_str.compare(str) != 0)
// {
// for (size_t i = 0; i < del.size(); i++)
// trim(str, del[i]);
// }
// return str;
//}
std::string itos(int n)
{
std::stringstream strs;
@@ -57,7 +110,7 @@ bool isNumeric_btw(int low, int high, std::string str)
if (std::isdigit(str[i]) == false)
return false;
}
int n = std::atoi(str.c_str());
int n = std::strtol(str.c_str(), NULL, 10);
if (n < low || n > high)
return false;
return true;
@@ -96,7 +149,49 @@ std::string http_methods_to_str(unsigned int methods)
return (str);
}
void replace_all_substr(std::string &str, const std::string &ori_substr, const std::string &new_substr)
# include <iostream>
file_type eval_file_type(const std::string &path)
{
const char *tmp_path = path.c_str();
struct stat s;
if (stat(tmp_path, &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_mode(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 execution 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;
@@ -104,7 +199,7 @@ void replace_all_substr(std::string &str, const std::string &ori_substr, const s
while (1)
{
pos = str.find(ori_substr, pos);
if (pos == std::string::npos)
if (pos == NPOS)
break;
str.replace(pos, ori_substr.size(), new_substr);
pos += new_substr.size();
@@ -117,27 +212,116 @@ std::string str_tolower(std::string str)
return str;
}
void del_line_in_str(std::string * str, size_t pos, std::string delim)
// identify a line in a string, by delim (ex. '\n')
// delete this line from the string (and 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)
{
size_t begin;
size_t end;
std::string del_str;
size_t begin;
size_t end;
size_t len;
begin = (*str).rfind(delim, pos);
if (begin == std::string::npos)
begin = str.rfind(delim, pos);
if (begin == NPOS)
begin = 0;
else
else if (begin < pos)
begin += delim.size();
end = (*str).find(delim, pos);
if (end == std::string::npos)
end = 0;
else
end += delim.size();
end = str.find(delim, pos);
len = end;
if (end != NPOS)
len = end - begin;
(*str).erase(begin, 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 = 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++;
continue;
}
key = (*it).substr(0, pos);
if ( key.find(' ') != NPOS )
{
err++;
continue;
}
// bad idea, in cgi we need to have the original value
// key = ::str_tolower(key); // to make "key" case_insensitive
val = (*it).substr(pos + 1);
val = ::trim(val, ' ');
fields.insert( std::pair<std::string, std::string>(key, val) );
}
return err;
}
void str_map_key_tolower(std::map<std::string, std::string> & mp)
{
std::map<std::string, std::string> new_mp;
std::map<std::string, std::string>::iterator it;
std::string key;
std::string value;
for (it = mp.begin(); it != mp.end(); it++)
{
key = it->first;
value = it->second;
key = ::str_tolower(key);
new_mp.insert( std::pair<std::string, std::string>(key, value) );
}
mp.swap(new_mp);
}
// DEBUG
void print_special(std::string str)
{
char c;
for (size_t i = 0; i < str.size(); i++)
{
c = str[i];
if (c == '\r')
std::cout << YELLOW << "\\r" << RESET;
else if (c == '\n')
std::cout << YELLOW << "\\n" << RESET << "\n";
else
std::cout << c;
fflush(stdout);
}
}
bool operator==(const listen_socket& lhs, int fd)
{ return lhs.fd == fd; }

View File

@@ -3,24 +3,37 @@
# define UTILS_HPP
# include <vector>
# include <map>
# include <string>
# include <sstream>
# include <cstdlib> // atoi
# 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
# define CR "\r"
# define LF "\n"
# define CRLF CR LF
# define CRLF_SIZE 2
# define NPOS std::string::npos
// enum http_method
// {
// UNKNOWN = 0b00000000,
// GET = 0b00000001,
// POST = 0b00000010,
// DELETE = 0b00000100,
// ANY_METHODS = 0b11111111,
// };
/* 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
{
@@ -29,6 +42,8 @@ enum http_method
POST = 1 << 1,
DELETE = 1 << 2,
ANY_METHODS = 0b11111111,
// ALL_METHODS = 0b11111111,
// i would prefer this...
};
struct listen_socket
@@ -41,14 +56,23 @@ bool operator==(const listen_socket& lhs, int fd);
bool operator==(int fd, const listen_socket& rhs);
std::vector<std::string> split(std::string input, char delimiter);
std::vector<std::string> split_trim(std::string input, std::string delim = "\n", char ctrim = '\0');
bool isNumeric(std::string str);
bool isNumeric_btw(int low, int high, std::string str);
std::string itos(int n);
std::string trim(std::string str, char c);
std::string trim(std::string str, char del);
http_method str_to_http_method(std::string &str);
std::string http_methods_to_str(unsigned int methods);
file_type eval_file_type(const std::string &path);
size_t eval_file_mode(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);
void del_line_in_str(std::string * str, size_t pos, std::string delim);
std::string extract_line(std::string & str, size_t pos = 0, std::string delim = "\n");
std::string get_line (std::string str, size_t pos = 0, std::string delim = "\n");
size_t parse_http_headers (std::string headers, std::map<std::string, std::string> & fields );
void str_map_key_tolower(std::map<std::string, std::string> & mp);
void throw_test();
// debug
void print_special(std::string str);
#endif

View File

@@ -23,12 +23,16 @@
# include <algorithm> // find
# include <string> // string
# include <cstdio> // perror, remove
# include <cstdlib> // atoi (athough it's already cover by <string>)
# 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;
@@ -38,6 +42,8 @@ void signal_handler(int signum);
# define FAILURE -1
# define SUCCESS 1
# define MIME_TYPE_DEFAULT "application/octet-stream"
class Webserv
{
public:
@@ -70,39 +76,46 @@ class Webserv
_extract_infos(struct sockaddr_in addr);
// request.cpp
void _request(Client *client);
void _read_request(Client *client);
int _read_request(Client *client);
// response.cpp
void _response(Client *client);
void _send_response(Client *client, ServerConfig &server);
int _send_response(Client *client);
void _append_base_headers(Client *client);
void _construct_response(Client *client, ServerConfig &server);
void _process_method(Client *client, ServerConfig &server, LocationConfig &location);
void _construct_response(Client *client);
void _process_method(Client *client, std::string &path);
void _insert_status_line(Client *client);
void _error_html_response(Client *client, ServerConfig &server);
void _append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension = "");
void _get(Client *client, ServerConfig &server, LocationConfig &location);
void _error_html_response(Client *client);
void _append_body(Client *client, const std::string &body, const std::string &file_extension = "");
// ServerConfig *_determine_process_server(Client *client); // cant be const cause of error_pages.operator[]
// const LocationConfig *_determine_location(const ServerConfig &server, const std::string &path) const;
std::string _determine_file_extension(const std::string &path) const;
// method_get.cpp
// move later
std::string _replace_url_root(Client *client, std::string path);
void _get(Client *client, std::string &path);
void _get_file(Client *client, const std::string &path);
void _post(Client *client, ServerConfig &server, LocationConfig &location);
void _autoindex(Client *client, const std::string &path);
// method_post.cpp
void _post(Client *client, const std::string &path);
void _post_file(Client *client, const std::string &path);
void _delete(Client *client, ServerConfig &server, LocationConfig &location);
// method_delete.cpp
void _delete(Client *client, const std::string &path);
void _delete_file(Client *client, const std::string &path);
ServerConfig &_determine_process_server(Client *client);
LocationConfig &_determine_location(ServerConfig &server, std::string const &path);
// cgi_script.cpp
bool _is_cgi(Client *client);
bool _is_cgi(Client *client, std::string path);
size_t _cgi_pos(Client *client, std::string &path, size_t pos);
std::string _exec_cgi(Client *client);
char** _set_env(Client *client);
char* _dup_env(std::string var, std::string val);
char* _dup_env(std::string var, int i);
std::string _exec_script(Client *client, char **env);
void _check_script_output(Client *client, std::string output);
void _check_script_status(Client *client, std::string output);
void _check_script_fields(Client *client, std::string output);
void _check_script_output(Client *client, std::string & output);
void _check_script_status(Client *client, std::string & output);
void _check_script_fields(Client *client, std::string & output);
void _add_script_body_length_header(std::string & output);
void _remove_body_leading_empty_lines(std::string & output);
// epoll_update.cpp
int _epoll_update(int fd, uint32_t events, int op);
int _epoll_update(int fd, uint32_t events, int op, void *ptr);
@@ -112,12 +125,17 @@ class Webserv
void _close_client(int fd);
void _close_all_clients();
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();
};
#endif

View File

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

View File

@@ -1,16 +1,67 @@
#include "Webserv.hpp"
bool Webserv::_is_cgi(Client *client)
bool Webserv::_is_cgi(Client *client, std::string path)
{
// TODO see how it works with config
if (client->fill_script_path("/cgi-bin/php-cgi"))
return true;
if (client->fill_script_path("/cgi-bin/cgi_cpp.cgi"))
return true;
std::string script_path;
size_t file_type;
size_t file_mode;
size_t pos = 0;
while (pos != NPOS)
{
pos = _cgi_pos(client, path, pos);
if (pos == NPOS)
break;
client->fill_script_path(path, pos);
script_path = "." + client->get_rq_script_path();
file_type = ::eval_file_type(script_path);
if (file_type == IS_DIR) // but what if it's a symlink ?
continue;
if (file_type == IS_FILE)
{
file_mode = ::eval_file_mode( script_path, X_OK );
if (!file_mode)
return true;
}
}
client->clear_script();
client->status = file_mode; // 404 not_found OR 403 forbidden
return false;
}
size_t Webserv::_cgi_pos(Client *client, std::string &path, size_t pos)
{
std::vector<std::string> v_ext;
std::vector<std::string>::const_iterator it;
std::vector<std::string>::const_iterator it_end;
size_t len;
std::locale loc; // for isalpha()
v_ext = client->assigned_location->cgi_ext;
if (v_ext.empty())
return NPOS;
it_end = client->assigned_location->cgi_ext.end();
while (pos < path.size())
{
if (path.compare(pos, 2, "./") == 0)
pos += 2;
pos = path.find('.', pos);
if (pos == NPOS)
return pos;
it = client->assigned_location->cgi_ext.begin();
for ( ; it != it_end; ++it)
{
len = (*it).size();
if (path.compare(pos + 1, len, *it) == 0)
if ( !std::isalpha(path[pos + 1 + len], loc) )
return pos + 1 + len;
}
pos++;
}
return NPOS;
}
std::string Webserv::_exec_cgi(Client *client)
{
char** env;
@@ -89,6 +140,7 @@ std::string Webserv::_exec_script(Client *client, char **env)
std::string body = client->get_rq_body();
int fd_in[2];
int fd_out[2];
std::string path;
pipe(fd_in);
pipe(fd_out);
@@ -96,20 +148,22 @@ std::string Webserv::_exec_script(Client *client, char **env)
pid = fork();
if (pid == -1)
std::cerr << "fork crashed" << std::endl;
else if (pid == 0)
else if (pid == 0) // child
{
close(FD_WR_TO_CHLD);
close(FD_RD_FR_CHLD);
dup2(FD_RD_FR_PRNT, STDIN_FILENO);
dup2(FD_WR_TO_PRNT, STDOUT_FILENO);
// DEBUG
std::cerr << "execve:\n";
execve(client->get_rq_script_path().c_str(), nll, env);
path = "." + client->get_rq_script_path();
/*DEBUG*/std::cerr << "execve:[" << path << "]\n";
execve(path.c_str(), nll, env);
// for tests execve crash :
//execve("wrong", nll, env);
std::cerr << "execve crashed.\n";
// TODO HUGO : check errno
}
else
else //parent
{
close(FD_RD_FR_PRNT);
close(FD_WR_TO_PRNT);
@@ -127,52 +181,124 @@ std::string Webserv::_exec_script(Client *client, char **env)
}
if (script_output.empty())
script_output = "Status: 500\r\n\r\n";
return script_output;
}
void Webserv::_check_script_output(Client *client, std::string output)
void Webserv::_check_script_output(Client *client, std::string & output)
{
// TODO: it doesn't work with execve error, i don't know why yet ?
_check_script_status(client, output);
_check_script_fields(client, output);
_add_script_body_length_header(output);
_remove_body_leading_empty_lines(output);
// _check_script_empty_lines(client, output);
// _check_script_space_colons(client, output);
// _check_script_new_lines(client, output);
}
void Webserv::_check_script_status(Client *client, std::string output)
void Webserv::_check_script_status(Client *client, std::string & output)
{
size_t pos;
int status_pos;
pos = output.find("Status:");
if (pos != std::string::npos)
if (pos != NPOS)
{
status_pos = pos + std::string("Status:").size();
client->status = atoi(output.c_str() + status_pos);
::del_line_in_str(&output, pos, CRLF);
client->status = std::strtoul(output.c_str() + status_pos, NULL, 10);
::extract_line(output, pos, CRLF);
}
client->status = 200;
else
client->status = 200;
}
void Webserv::_check_script_fields(Client *client, std::string output)
void Webserv::_check_script_fields(Client *client, std::string & output)
{
std::map<std::string, std::string> srv_fld; // server_field
std::map<std::string, std::string> scr_fld; // script_field
std::map<std::string, std::string>::iterator it_srv;
std::map<std::string, std::string>::iterator it_scr;
std::string tmp;
size_t pos;
srv_fld = parse_http_headers(client->response);
scr_fld = parse_http_headers(output);
// wip: compare both map to supress duplicates
// put server headers in map
tmp = client->response;
pos = tmp.find(CRLF CRLF);
if (pos != NPOS)
tmp.erase(pos);
::parse_http_headers(tmp, srv_fld);
// put script headers in map
tmp = output;
pos = tmp.find(CRLF CRLF);
if (pos != NPOS)
tmp.erase(pos);
::parse_http_headers(tmp, scr_fld);
// compare both map to supress duplicates
for (it_srv = srv_fld.begin(); it_srv != srv_fld.end(); it_srv++)
{
for (it_scr = scr_fld.begin(); it_scr != scr_fld.end(); it_scr++)
{
if (it_srv->first == it_scr->first)
if (str_tolower(it_srv->first) == str_tolower(it_scr->first))
{
pos = client->response.find(it_srv->first);
::del_line_in_str(&client->response, pos, CRLF);
::extract_line(client->response, pos, CRLF);
}
}
}
}
void Webserv::_remove_body_leading_empty_lines(std::string & output)
{
size_t pos;
size_t pos_empty;
pos = output.find(CRLF CRLF);
if (pos == NPOS)
return;
pos += CRLF_SIZE * 2;
pos_empty = pos;
while (pos_empty == pos)
{
pos = output.find(CRLF, pos);
if (pos == pos_empty)
extract_line(output, pos, CRLF);
}
}
void Webserv::_add_script_body_length_header(std::string & output)
{
std::map<std::string, std::string> field;
std::map<std::string, std::string>::iterator it;
std::stringstream str_len;
std::string tmp;
size_t pos;
size_t len;
pos = output.find(CRLF CRLF);
if (pos != NPOS)
tmp = output.substr(pos + CRLF_SIZE);
len = tmp.size();
str_len << len;
// put script headers in map
tmp = output;
pos = tmp.find(CRLF CRLF);
if (pos != NPOS)
tmp.erase(pos);
::parse_http_headers(tmp, field);
// case insensitive search in map for "Content-Length"
tmp = "Content-Length";
for (it = field.begin(); it != field.end(); ++it)
{
if (str_tolower(it->first) == str_tolower(tmp))
{
pos = output.find(it->first);
::extract_line(output, pos, CRLF);
}
}
tmp += ": ";
tmp += str_len.str();
tmp += CRLF;
output.insert(0, tmp);
}

View File

@@ -9,10 +9,9 @@ void Webserv::_close_client(int fd)
if (*it == fd)
{
// _epoll_update(fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
std::cerr << "close fd " << fd << "\n";
if (::close(fd) == -1)
std::perror("err close()");
else
std::cerr << "close fd " << fd << "\n";
_clients.erase(it);
break;
}
@@ -25,10 +24,9 @@ void Webserv::_close_all_clients()
while (!_clients.empty())
{
// _epoll_update(_clients.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
std::cerr << "close fd " << _clients.back().get_cl_fd() << "\n";
if (::close(_clients.back().get_cl_fd()) == -1)
std::perror("err close()");
else
std::cerr << "close fd " << _clients.back().get_cl_fd() << "\n";
_clients.pop_back();
}
}
@@ -38,10 +36,77 @@ void Webserv::_close_all_listen_sockets()
while (!_listen_sockets.empty())
{
// _epoll_update(_listen_sockets.back().fd, 0, EPOLL_CTL_DEL); // normalement superflu, DEBUG
std::cerr << "close fd " << _listen_sockets.back().fd << "\n";
if (::close(_listen_sockets.back().fd) == -1)
std::perror("err close()");
else
std::cerr << "close fd " << _listen_sockets.back().fd << "\n";
_listen_sockets.pop_back();
}
}
void Webserv::_reopen_lsocket(std::vector<listen_socket>::iterator it)
{
/*
** Many common code with init_virtual_servers(). Could refactor it.
*/
int ret;
std::cerr << "close lsocket " << it->fd << "\n";
if (::close(it->fd) == -1)
std::perror("err close()");
std::cerr << "try to reopen lsocket\n";
ret = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); // (SOCK_CLOEXEC) for CGI fork ?
if (ret == -1)
{
std::perror("err socket()");
_listen_sockets.erase(it);
return;
}
it->fd = ret;
// HUGO ADD
// allow socket descriptor to be reuseable
// I just copied it from https://www.ibm.com/docs/en/i/7.2?topic=designs-example-nonblocking-io-select
int on = 1;
if (setsockopt(it->fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
{
::perror("err setsockopt()");
_listen_sockets.erase(it);
return;
}
// HUGO ADD END
try {
_bind(it->fd, std::strtoul(it->port.c_str(), NULL, 10), it->host);
_listen(it->fd, 42); // 42 arbitrary
}
catch (const std::exception& e) {
std::cerr << e.what() << '\n';
_listen_sockets.erase(it);
return;
}
if (_epoll_update(it->fd, EPOLLIN, EPOLL_CTL_ADD) == -1)
{
_listen_sockets.erase(it);
return;
}
std::cerr << "reopen success\n";
}
void Webserv::_handle_epoll_error_lsocket(uint32_t events, std::vector<listen_socket>::iterator it)
{
if (events & EPOLLERR)
std::cerr << "EPOLLERR on lsocket fd " << it->fd << "\n"; // DEBUG
if (events & EPOLLHUP)
std::cerr << "EPOLLHUP on lsocket fd " << it->fd << "\n"; // DEBUG
_reopen_lsocket(it);
}
void Webserv::_handle_epoll_error_client(uint32_t events, int fd)
{
if (events & EPOLLERR)
std::cerr << "EPOLLERR on client fd " << fd << "\n"; // DEBUG
if (events & EPOLLHUP)
std::cerr << "EPOLLHUP on client fd " << fd << "\n"; // DEBUG
_close_client(fd);
}

View File

@@ -30,13 +30,23 @@
# define S201 "201 Created"
# define S204 "204 No Content"
# define S301 "301 Moved Permanently"
# define S302 "302 Found"
# define S303 "303 See Other"
# define S304 "304 Not Modified" // unused
# define S307 "307 Temporary Redirect"
# define S308 "308 Permanent Redirect"
# define S400 "400 Bad Request"
# define S403 "403 Forbidden"
# define S404 "404 Not Found"
# define S405 "405 Method Not Allowed"
# define S408 "408 Request Timeout"
# define S413 "413 Content Too Large"
# define S415 "415 Unsupported Media Type"
# define S500 "500 Internal Server Error"
# define S501 "501 Not Implemented"
# define S505 "505 HTTP Version Not Supported"
#endif

View File

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

View File

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

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

@@ -0,0 +1,155 @@
#include "Webserv.hpp"
std::string Webserv::_replace_url_root(Client *client, std::string path)
{
std::cerr << "assigned_location->path = " << client->assigned_location->path << "\n"; // debug
std::cerr << "path before = " << path << "\n"; // DEBUG
if (client->assigned_location->path == "/")
path.insert(0, client->assigned_location->root);
else
path.replace(0, client->assigned_location->path.size(), client->assigned_location->root);
std::cerr << "path after = " << path << "\n"; // DEBUG
return path;
}
// const?
void Webserv::_get(Client *client, std::string &path)
{
// Index/Autoindex block
if (eval_file_type(path) == IS_DIR)
{
if (path[path.size() - 1] != '/')
path.push_back('/');
for (size_t i = 0; i < client->assigned_location->index.size(); i++)
{
if (eval_file_type(path + client->assigned_location->index[i]) == IS_FILE)
{
path.append(client->assigned_location->index[i]);
_get_file(client, path);
return ;
}
}
if (client->assigned_location->autoindex == true)
_autoindex(client, path);
}
else
_get_file(client, path);
}
# define MAX_FILESIZE 1000000 // (1Mo)
void Webserv::_get_file(Client *client, const std::string &path)
{
/*
std::ios::binary
https://gcc.gnu.org/onlinedocs/libstdc++/manual/fstreams.html#std.io.filestreams.binary
tldr : its seems to not be so simple to do read/write of binary file in a portable way.
*/
std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ?
std::stringstream buf;
std::cout << "_get_file()\n";
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), R_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ifd.open(path.c_str(), std::ios::ate);
if (!ifd)
{
std::cerr << path << ": ifd.open fail" << '\n';
client->status = 500;
}
else
{
/* std::streampos size = ifd.tellg();
// WIP Low priority : Chunk or not chunk (if filesize too big)
if (size > MAX_FILESIZE)
{
// Then chunk
client->status = 500; // WIP temp
std::cerr << "File too large for non chunk body\n";
return ;
} */
ifd.seekg(0, std::ios::beg);
buf << ifd.rdbuf();
if (!ifd || !buf)
{
std::cerr << path << ": ifd.read fail" << '\n';
client->status = 500;
}
else
{
client->status = 200;
std::string file_ext = _determine_file_extension(path);
_append_body(client, buf.str(), file_ext);
}
}
}
// const?
void Webserv::_autoindex(Client *client, const std::string &path)
{
std::cout << "_autoindex()\n";
std::string dir_list;
DIR *dir;
struct dirent *ent;
// std::cout << "location root: " << client->assigned_location->root << " location path: " << client->assigned_location->path << '\n';
// std::cout << "Path in auto is: " << path << '\n';
if ( (dir = opendir(path.c_str()) ) != NULL)
{
dir_list.append(AUTOINDEX_START);
dir_list.append(path);
dir_list.append(AUTOINDEX_MID1);
dir_list.append(path);
dir_list.append(AUTOINDEX_MID2);
/* print all the files and directories within directory */
while ((ent = readdir (dir)) != NULL)
{
// std::cout << "ent: " << ent->d_name << '\n';
if (strcmp(".", ent->d_name) == 0)
continue ;
dir_list.append("<a href=\"");
dir_list.append(client->get_rq_abs_path() + "/");
dir_list.append(ent->d_name);
dir_list.append("\">");
dir_list.append(ent->d_name);
dir_list.append("</a>");
dir_list.append("\r\n"); // is this right?
}
// <a href="http://nginx.org/">nginx.org</a>.<br/>
// <a href="/test/test_deeper/index1.html">index1.html</a>
// apparently this is more than good enough!
// <a href="/test/test_deeper/..">..</a>
dir_list.append(AUTOINDEX_END);
// std::cout << "\n\n" << dir_list << '\n';
closedir (dir);
_append_body(client, dir_list, "html");
}
else
{
// in theory not possible cuz we already checked...
std::cerr << "could not open dir\n";
// throw?
return ;
}
}

View File

@@ -0,0 +1,58 @@
#include "Webserv.hpp"
void Webserv::_post(Client *client, const std::string &path)
{
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-post
*/
_post_file(client, path);
}
void Webserv::_post_file(Client *client, const std::string &path)
{
std::ofstream ofd;
bool file_existed;
if (access(path.c_str(), F_OK) == -1)
file_existed = false;
else
file_existed = true;
// How to determine status 403 for file that dont already exist ?
if (file_existed && access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ofd.open(path.c_str(), std::ios::trunc);
if (!ofd)
{
std::cerr << path << ": ofd.open fail" << '\n';
client->status = 500;
}
else
{
// Content-Length useless at this point ?
ofd << client->get_rq_body();
if (!ofd)
{
std::cerr << path << ": ofd.write fail" << '\n';
client->status = 500;
}
else if (file_existed)
{
client->status = 200;
// WIP https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok
}
else
{
client->status = 201;
// WIP https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3-4
}
}
}

View File

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

View File

@@ -1,36 +0,0 @@
#ifndef PARSING_MESSAGE_HTTP_HPP
# define PARSING_MESSAGE_HTTP_HPP
# include <iostream>
# include <string>
# include <vector>
# include <map>
# include "utils.hpp"
size_t
parse_http_first_line(std::string message, std::vector<std::string> &line);
std::map<std::string, std::string>
parse_http_headers(std::string message);
std::string
parse_http_body(std::string message);
bool
maybe_http_first_line(std::string);
// http message structure :
//
// start-line
// request-line
// method SP target SP version
// response-line
// version SP status SP reason
// header-fields
// name ":" SP value
// CRLF
// body
#endif

View File

@@ -1,18 +1,40 @@
#include "Webserv.hpp"
#define BUFSIZE 8192
// Arbitrary values
#define BUFSIZE 8192 // (8Ko)
#define MAX_HEADER_SIZE 16384 // (16Ko)
enum read_return
{
READ_IN_PROGRESS,
READ_COMPLETE,
READ_CLOSE,
};
void Webserv::_request(Client *client)
{
_read_request(client);
int ret = _read_request(client);
if (g_last_signal)
_handle_last_signal();
_handle_last_signal();
if (ret == READ_CLOSE)
{
_close_client(client->get_cl_fd());
}
else if (ret == READ_COMPLETE)
{
if (client->body_complete)
std::cerr << "______BODY\n" << client->get_rq_body() << "\n______\n"; // DEBUG
_epoll_update(client->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD);
client->request_complete = true;
}
}
void Webserv::_read_request(Client *client)
int Webserv::_read_request(Client *client)
{
char buf[BUFSIZE+1];
char buf[BUFSIZE];
ssize_t ret;
ret = ::recv(client->get_cl_fd(), buf, BUFSIZE, 0);
@@ -20,25 +42,42 @@ void Webserv::_read_request(Client *client)
if (ret == -1)
{
std::perror("err recv()");
std::cerr << "client ptr =" << client << "\n"; // DEBUG
std::cerr << "client.get_cl_fd() =" << client->get_cl_fd() << "\n"; // DEBUG
_close_client(client->get_cl_fd());
return ;
return READ_CLOSE;
}
if (ret == 0) // Not sure what to do in case of 0. Just close ?
if (ret == 0)
{
_close_client(client->get_cl_fd());
return ;
std::cerr << "recv() read 0, then close client" << "\n"; // DEBUG
return READ_CLOSE;
}
/*
if (ret == BUFSIZE)
// send error like "request too long" to client
*/
client->raw_request.append(buf, ret);
// ::print_special(client->raw_request);
// std::cerr << "__raw_request__\n" << client->raw_request << "\n______\n"; // DEBUG
buf[ret] = '\0';
client->raw_request.append(buf);
client->parse_request();
if (!client->header_complete)
{
client->parse_request_headers(_servers);
if (client->status)
return READ_COMPLETE;
if (client->header_complete)
{
if (client->get_rq_headers("Content-Type").empty()
&& client->get_rq_headers("Content-Length").empty()
&& client->get_rq_headers("Transfer-Encoding").empty())
return READ_COMPLETE; // No body case
}
else if (client->raw_request.size() > MAX_HEADER_SIZE)
{ // 413 or 400 ? 413 seems common among http server, but don't fit perfectly.
client->status = 413;
return READ_COMPLETE;
}
}
if (client->header_complete)
{
// client->read_body_size += ret; // Not accurate, part of body could have been read with headers, unused for now
client->parse_request_body();
if (client->status || client->body_complete)
return READ_COMPLETE;
}
_epoll_update(client->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD);
return READ_IN_PROGRESS;
}

View File

@@ -1,57 +1,62 @@
#include "Webserv.hpp"
enum send_return
{
SEND_IN_PROGRESS, // unused
SEND_COMPLETE,
SEND_CLOSE,
};
void Webserv::_response(Client *client)
{
client->status = 200; // default value
int ret = _send_response(client);
ServerConfig &server = _determine_process_server(client);
_send_response(client, server);
if (g_last_signal)
_handle_last_signal();
if (ret == SEND_CLOSE)
{
_close_client(client->get_cl_fd());
}
else if (ret == SEND_COMPLETE)
{
if (client->get_rq_headers("Connection") == "close" || client->status == 408)
_close_client(client->get_cl_fd());
else
{
_epoll_update(client->get_cl_fd(), EPOLLIN, EPOLL_CTL_MOD);
client->clear();
}
}
}
void Webserv::_send_response(Client *client, ServerConfig &server)
int Webserv::_send_response(Client *client)
{
ssize_t ret;
std::cerr << "send()\n";
_append_base_headers(client);
_construct_response(client, server);
if (!client->status)
_construct_response(client);
_insert_status_line(client);
if (client->status >= 400)
_error_html_response(client, server);
_error_html_response(client);
/*DEBUG*/ std::cout << "\n" B_PURPLE "[response + output + headers]:" RESET "\n"; ::print_special(client->response); std::cout << B_PURPLE "-----------" RESET "\n\n";
std::cerr << "client->response.size() = " << client->response.size() << "\n"; // DEBUG
ret = ::send(client->get_cl_fd(), client->response.c_str(), client->response.size(), 0);
if (ret == -1)
{
std::perror("err send()");
std::cerr << "client.get_cl_fd() =" << client->get_cl_fd() << "\n"; // DEBUG
_close_client(client->get_cl_fd());
return ;
std::cerr << "client.fd =" << client->get_cl_fd() << "\n"; // DEBUG
return SEND_CLOSE;
}
std::cerr << "ret send() = " << ret << "\n"; // DEBUG
// Body send (WIP for working binary files)
if (client->body_size)
{
ret = ::send(client->get_cl_fd(), client->buf, client->body_size, 0);
if (ret == -1)
{
std::perror("err send()");
std::cerr << "client.get_cl_fd() =" << client->get_cl_fd() << "\n"; // DEBUG
_close_client(client->get_cl_fd());
return ;
}
}
if (client->get_rq_headers("Connection") == "close")
_close_client(client->get_cl_fd());
else
{
_epoll_update(client->get_cl_fd(), EPOLLIN, EPOLL_CTL_MOD);
client->clear();
}
return SEND_COMPLETE;
}
void Webserv::_append_base_headers(Client *client)
@@ -64,48 +69,44 @@ void Webserv::_append_base_headers(Client *client)
client->response.append("Connection: keep-alive" CRLF);
}
void Webserv::_construct_response(Client *client, ServerConfig &server)
// TODO HUGO : wip
void Webserv::_construct_response(Client *client)
{
// TODO : Move this in read(), stop read if content too large
if (client->get_rq_body().size() > server.client_body_limit)
std::string path;
std::string script_output;
path = _replace_url_root(client, client->get_rq_abs_path());
if (_is_cgi(client, path))
{
client->status = 413;
script_output = _exec_cgi(client);
/*DEBUG*/ std::cout << "\n" B_PURPLE "[script output]:" RESET "\n"; ::print_special(script_output); std::cout << B_PURPLE "-----------" RESET "\n\n";
/*DEBUG*/ std::cout << "\n" B_PURPLE "[response]:" RESET "\n"; ::print_special(client->response); std::cout << B_PURPLE "-----------" RESET "\n\n";
_check_script_output(client, script_output);
client->response += script_output;
/*DEBUG*/ std::cout << "\n" B_PURPLE "[response + output]:" RESET "\n"; ::print_special(client->response); std::cout << B_PURPLE "-----------" RESET "\n\n";
return;
}
LocationConfig &location = _determine_location(server, client->get_rq_uri());
_process_method(client, server, location);
_process_method(client, path);
}
void Webserv::_process_method(Client *client, ServerConfig &server, LocationConfig &location)
void Webserv::_process_method(Client *client, std::string &path)
{
unsigned int allow_methods = ANY_METHODS; // TEMP VARIABLE
// after update in ConfigParser, use the "allow_methods" of location.
// TODO in ConfigParser : by default if no field in config file, "allow_methods" must be set to ANY_METHODS
std::cerr << "allow_methods = " << http_methods_to_str(client->assigned_location->allow_methods) << "\n"; // debug
if (client->get_rq_method() == UNKNOWN)
switch (client->get_rq_method())
{
client->status = 501;
}
else if (allow_methods & client->get_rq_method())
{
switch (client->get_rq_method())
{
case (GET):
_get(client, server, location); break;
case (POST):
_post(client, server, location); break;
case (DELETE):
_delete(client, server, location); break;
default:
break;
}
}
else
{
client->status = 405;
client->response.append("Allow: ");
client->response.append(::http_methods_to_str(allow_methods));
client->response.append(CRLF);
case (GET):
_get(client, path); break;
case (POST):
_post(client, path); break;
case (DELETE):
_delete(client, path); break;
default:
break;
}
}
@@ -119,247 +120,44 @@ void Webserv::_insert_status_line(Client *client)
client->response.insert(0, status_line);
}
void Webserv::_error_html_response(Client *client, ServerConfig &server)
void Webserv::_error_html_response(Client *client)
{
if (server.error_pages[client->status].empty())
if (!client->assigned_server || client->assigned_server->error_pages[client->status].empty())
{
std::string html_page = HTML_ERROR;
::replace_all_substr(html_page, STATUS_PLACEHOLDER, _http_status[client->status]);
_append_body(client, html_page.c_str(), html_page.size(), "html");
_append_body(client, html_page, "html");
}
else
_get_file(client, server.error_pages[client->status]);
_get_file(client, client->assigned_server->error_pages[client->status]);
}
#define INDEX "index.html" // temp wip
void Webserv::_get(Client *client, ServerConfig &server, LocationConfig &location)
void Webserv::_append_body(Client *client, const std::string &body, const std::string &file_extension)
{
(void)server; // To remove from arg if we determine its useless
std::string path = client->get_rq_uri();
const std::string &mime_type = _mime_types[file_extension];
if (path == "/") // TODO : index and autoindex
path.append(INDEX);
path.insert(0, location.root);
std::cerr << "path = " << path << "\n";
// TMP HUGO
//
std::string script_output;
if (_is_cgi(client))
{
script_output = _exec_cgi(client);
// DEBUG
std::cout << "\n____script_output____\n" << script_output << "\n_______________\n";
// wip check output of script
_check_script_output(client, script_output);
client->response += script_output;
return;
}
//
// END TMP HUGO
_get_file(client, path);
}
void Webserv::_get_file(Client *client, const std::string &path)
{
std::ifstream ifd; // For chunk, ifstream directly in struct CLient for multiples read without close() ?
// char buf[MAX_FILESIZE+1];
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), R_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ifd.open(path.c_str(), std::ios::binary | std::ios::ate); // std::ios::binary (binary for files like images ?)
if (!ifd)
{
std::cerr << path << ": ifd.open fail" << '\n';
client->status = 500;
}
client->response.append("Content-Type: ");
if (mime_type.empty())
client->response.append(MIME_TYPE_DEFAULT);
else
{
std::streampos size = ifd.tellg();
// WIP : Chunk or not chunk (if filesize too big)
if (size > MAX_FILESIZE)
{
// Then chunk
client->status = 500; // WIP temp
std::cerr << "File too large for non chunk body\n";
ifd.close();
return ;
}
ifd.seekg(0, std::ios::beg);
ifd.read(client->buf, size);
if (!ifd)
{
std::cerr << path << ": ifd.read fail" << '\n';
client->status = 500;
}
else
{
client->status = 200;
client->buf[ifd.gcount()] = '\0';
std::string file_ext = "";
size_t dot_pos = path.rfind(".");
std::cerr << "dot_pos = " << dot_pos << "\n";
if (dot_pos != std::string::npos && dot_pos + 1 < path.size())
file_ext = path.substr(dot_pos + 1);
std::cerr << "file_ext = " << file_ext << "\n";
client->body_size = ifd.gcount();
// WIP, pass empty body argument because append to string mess up binary file
_append_body(client, "", client->body_size, file_ext);
}
ifd.close();
}
}
void Webserv::_append_body(Client *client, const char *body, size_t body_size, const std::string &file_extension)
{
/*
TODO : determine Content-Type
how ? read the body ?
or before in other way (like based and file extension) and pass here as argument ?
http://nginx.org/en/docs/http/ngx_http_core_module.html#types
Need to look "conf/mime.types" of nginx. Maybe make a map<> based on that.
*/
const std::string &mime_type = _mime_types[file_extension];
client->response.append("Content-Type: ");
client->response.append(mime_type);
if (mime_type.find("text/") != std::string::npos)
if (mime_type.find("text/") != NPOS)
client->response.append("; charset=UTF-8");
client->response.append(CRLF);
}
client->response.append(CRLF);
client->response.append("Content-Length: ");
std::string tmp = ::itos(body_size);
client->response.append(tmp);
client->response.append(CRLF);
client->response.append("Content-Length: ");
std::string tmp = ::itos(body.size());
client->response.append(tmp);
client->response.append(CRLF);
client->response.append(CRLF);
client->response.append(body);
client->response.append(CRLF);
client->response.append(body);
}
void Webserv::_post(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-post
*/
std::string path = client->get_rq_uri();
path.insert(0, location.root);
/* CGI Here ? */
_post_file(client, path);
}
void Webserv::_post_file(Client *client, const std::string &path)
{
std::ofstream ofd;
bool file_existed;
if (access(path.c_str(), F_OK) == -1)
file_existed = false;
else
file_existed = true;
// How to determine status 403 for file that dont already exist ?
if (file_existed && access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
ofd.open(path.c_str(), std::ios::binary | std::ios::trunc);
if (!ofd)
{
std::cerr << path << ": ofd.open fail" << '\n';
client->status = 500;
}
else
{
// Used body.size() so Content-Length useless at this point ?
// Maybe usefull in _read_request() for rejecting too big content.
// Need to _determine_process_server() as soon as possible,
// like in _read_request() for stopping read if body is too big ?
ofd.write(client->get_rq_body().c_str(), client->get_rq_body().size());
if (!ofd)
{
std::cerr << path << ": ofd.write fail" << '\n';
client->status = 500;
}
else if (file_existed)
{
client->status = 200;
// WIP https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok
}
else
{
client->status = 201;
// WIP https://www.rfc-editor.org/rfc/rfc9110.html#section-9.3.3-4
}
ofd.close();
}
}
void Webserv::_delete(Client *client, ServerConfig &server, LocationConfig &location)
{
(void)server; // To remove from arg if we determine its useless
/*
WIP
https://www.rfc-editor.org/rfc/rfc9110.html#name-delete
*/
std::string path = client->get_rq_uri();
path.insert(0, location.root);
/* CGI Here ? */
_delete_file(client, path);
}
void Webserv::_delete_file(Client *client, const std::string &path)
{
if (access(path.c_str(), F_OK) == -1)
{
std::perror("err access()");
client->status = 404;
return ;
}
if (access(path.c_str(), W_OK) == -1)
{
std::perror("err access()");
client->status = 403;
return ;
}
if (remove(path.c_str()) == -1)
{
std::perror("err remove()");
client->status = 500;
return ;
}
}
ServerConfig &Webserv::_determine_process_server(Client *client)
// Temporary Global Scope. Probably move to Client in the future.
ServerConfig *_determine_process_server(Client *client, std::vector<ServerConfig> &servers)
{
/*
http://nginx.org/en/docs/http/request_processing.html
@@ -368,45 +166,94 @@ ServerConfig &Webserv::_determine_process_server(Client *client)
*/
std::string const &server_name = client->get_rq_headers("Host");
std::vector<ServerConfig>::iterator it = _servers.begin();
std::vector<ServerConfig>::iterator default_server = _servers.end();
std::vector<ServerConfig>::iterator it = servers.begin();
std::vector<ServerConfig>::iterator default_server = servers.end();
while (it != _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())
else if (default_server == servers.end())
default_server = it;
}
++it;
}
if (it != _servers.end())
return (*it);
if (it != servers.end())
return (&(*it));
else
return (*default_server);
return (&(*default_server));
}
LocationConfig &Webserv::_determine_location(ServerConfig &server, std::string const &path)
// const?
// Temporary Global Scope. Probably move to Client in the future.
const LocationConfig *_determine_location(const ServerConfig &server, const std::string &path)
{
/*
Assume there is at least one location in vector<LocationConfig> for path "/"
TODO in ConfigParser :
If no location block in config file, one need to be generated
for path "/", and filled with fields "root" and "index" based on parent server block
/* 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::vector<LocationConfig>::iterator it = server.locations.begin();
while (it != server.locations.end())
std::string uri = path;
if (uri[uri.size() - 1] == '/')
uri.erase(uri.size() - 1);
for (std::vector<LocationConfig>::const_iterator it = server.locations.begin(); it != server.locations.end(); it++)
{
if (it->path.compare(0, path.size(), path))
break;
++it;
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
}
}
if (it != server.locations.end())
return (*it);
else
return (server.locations.front());
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/
*/
}
std::string Webserv::_determine_file_extension(const std::string &path) const
{
size_t dot_pos = path.rfind(".");
if (dot_pos != NPOS && dot_pos + 1 < path.size())
return ( path.substr(dot_pos + 1) );
return (std::string(""));
}

View File

@@ -20,31 +20,52 @@ void Webserv::run()
nfds = ::epoll_wait(_epfd, events, MAX_EVENTS, TIMEOUT);
if (nfds == -1)
{
int errno_copy = errno;
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();
}
if (errno_copy == EINTR)
g_run = false;
else
throw std::runtime_error("Epoll wait");
}
else if (nfds == 0 && !_clients.empty())
_timeout();
i = 0;
while (i < nfds)
{
// TODO : handle EPOLLERR and EPOLLHUP
it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd);
if (it_socket != _listen_sockets.end() && events[i].events & EPOLLIN)
_accept_connection(*it_socket);
else if (events[i].events & EPOLLIN)
_request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
else if (events[i].events & EPOLLOUT)
_response( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
++i;
if (!g_run)
try {
it_socket = std::find(_listen_sockets.begin(), _listen_sockets.end(), events[i].data.fd);
if (it_socket != _listen_sockets.end())
{
if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP)
_handle_epoll_error_lsocket(events[i].events, it_socket);
else if (events[i].events & EPOLLIN)
_accept_connection(*it_socket);
}
else
{
if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP)
_handle_epoll_error_client(events[i].events, events[i].data.fd);
else if (events[i].events & EPOLLIN)
_request( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
else if (events[i].events & EPOLLOUT)
_response( &(*std::find(_clients.begin(), _clients.end(), events[i].data.fd)) );
}
++i;
if (!g_run)
break;
}
catch (const std::bad_alloc& e) {
std::cerr << e.what() << '\n';
_close_all_clients();
/* Swap to free the memory
From : http://www.uml.org.cn/c%2B%2B/pdf/EffectiveSTL.pdf#page=66 */
std::vector<Client>().swap(_clients);
break;
}
catch (const std::exception& e) {
std::cerr << e.what() << '\n';
++i;
}
}
}
}

18
srcs/webserv/timeout.cpp Normal file
View File

@@ -0,0 +1,18 @@
#include "Webserv.hpp"
void Webserv::_timeout()
{
std::cerr << "_timeout()\n";
std::vector<Client>::iterator it = _clients.begin();
while (it != _clients.end())
{
if (!it->request_complete)
{
std::cerr << "timeout request fd " << it->get_cl_fd() << "\n";
it->status = 408;
_epoll_update(it->get_cl_fd(), EPOLLOUT, EPOLL_CTL_MOD);
}
++it;
}
}

4
stylesheet/style.css Normal file
View File

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

15
test_chunk.txt Normal file
View File

@@ -0,0 +1,15 @@
https://en.wikipedia.org/wiki/Chunked_transfer_encoding#Example
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding
GET / HTTP/1.1
Host: localhost:4040
Accept: */*
Transfer-Encoding: chunked
7
Mozilla
9
Developer
7
Network
0

4
urls.txt Normal file
View File

@@ -0,0 +1,4 @@
http://localhost:4040/test
http://localhost:4040/test/test_deeper/
http://localhost:4040/test/test_deeper/super_deep/
http://localhost:4040/test/index1.html

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

View File

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

@@ -3,11 +3,82 @@
<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">
<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>

View File

@@ -1,13 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<form method="post">
<input type="submit" value="submit">
</form>
</body>
</html>

View File

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

BIN
www/punpun.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 310 KiB

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

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

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

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

View File

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

View File

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

View File

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

10
www/upload_form.html Normal file
View File

@@ -0,0 +1,10 @@
<!--
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" id="myFile" name="upload_file">
<input type="submit">
</form>