diff --git a/playground/tkg/Design1/src/HTML.cpp b/playground/tkg/Design1/src/HTML.cpp index f3a8ce1..eced15a 100644 --- a/playground/tkg/Design1/src/HTML.cpp +++ b/playground/tkg/Design1/src/HTML.cpp @@ -1,5 +1,7 @@ #include "HTML.hpp" +#include + std::string HTML::header() { return "\n\n\n" "\n" @@ -13,6 +15,20 @@ std::string HTML::aTag(const std::string &url, const std::string &text) { return "

" + text + "

\n"; } +std::string HTML::getDefaultErrorPage(const std::string &status, const std::string &reason) { + std::stringstream ss; + ss << "\n\n\n"; + ss << " Error" << status << " " << reason << "\n"; + ss << "\n\n\n
\n"; + ss << "

Error " << status << " " << reason << "

\n"; + ss << "
\n"; + ss << "

Our apologies for the temporary inconvenience. The requested URL was not found on this server.\n

" + "
\n"; + ss << "

Webserv

\n"; + return ss.str(); +} + std::string HTML::sanitize(const std::string &origin) { std::string sanitized; sanitized.reserve(origin.size()); diff --git a/playground/tkg/Design1/src/HTML.hpp b/playground/tkg/Design1/src/HTML.hpp index 21f34ef..c89378d 100644 --- a/playground/tkg/Design1/src/HTML.hpp +++ b/playground/tkg/Design1/src/HTML.hpp @@ -9,4 +9,5 @@ class HTML { static std::string footer(); static std::string aTag(const std::string &url, const std::string &text); static std::string sanitize(const std::string &origin); + static std::string getDefaultErrorPage(const std::string &status, const std::string &reason); }; \ No newline at end of file diff --git a/playground/tkg/Design1/src/HttpResponse.cpp b/playground/tkg/Design1/src/HttpResponse.cpp index 0e50783..ae30a9a 100644 --- a/playground/tkg/Design1/src/HttpResponse.cpp +++ b/playground/tkg/Design1/src/HttpResponse.cpp @@ -29,11 +29,15 @@ void HttpResponse::setStatusAndReason(const int status) { reason_phrase_ = conf_->cache_.statusMsg_[status_]; } -void HttpResponse::setContentType(const std::string &path) { +void HttpResponse::setContentType(const std::string &path, bool forced) { std::string ext = getExtension(path); if (ext == "") return; - if (hasHeader("content-type")) return; - appendHeader("content-type", conf_->cache_.ext_contentType_map_[ext]); + if (conf_->cache_.ext_contentType_map_.find(ext) == conf_->cache_.ext_contentType_map_.end()) return; + if (forced) { + appendHeader("content-type", conf_->cache_.ext_contentType_map_[ext]); + } else if (!hasHeader("content-type")) { + appendHeader("content-type", conf_->cache_.ext_contentType_map_[ext]); + } } void HttpResponse::appendBody(const char *str, size_t size) { body_.insert(body_.end(), str, str + size); } @@ -59,7 +63,7 @@ void HttpResponse::createResponse() { } std::stringstream ss; // status-line - ss << "HTTP/1.1 " << status_ << " " << conf_->cache_.statusMsg_[status_] << CRLF; + ss << "HTTP/1.1 " << status_ << " " << reason_phrase_ << CRLF; // header-fields for (t_headers::const_iterator itr = headers_.cbegin(); itr != headers_.cend(); itr++) { ss << itr->first << ": " << itr->second << CRLF; diff --git a/playground/tkg/Design1/src/HttpResponse.hpp b/playground/tkg/Design1/src/HttpResponse.hpp index 8b11b42..584c87d 100644 --- a/playground/tkg/Design1/src/HttpResponse.hpp +++ b/playground/tkg/Design1/src/HttpResponse.hpp @@ -34,7 +34,7 @@ class HttpResponse { void setStatus(const int status); void setStatusAndReason(const int status, const std::string &reason); void setStatusAndReason(const int status); - void setContentType(const std::string &path); + void setContentType(const std::string &path, bool forced); void appendHeader(const std::string &key, const std::string &value); void appendBody(const char *str, size_t size); void appendBody(const std::string &str); diff --git a/playground/tkg/Design1/src/Observee/ConnectionSocket.cpp b/playground/tkg/Design1/src/Observee/ConnectionSocket.cpp index bc27214..f94c7ee 100644 --- a/playground/tkg/Design1/src/Observee/ConnectionSocket.cpp +++ b/playground/tkg/Design1/src/Observee/ConnectionSocket.cpp @@ -21,6 +21,7 @@ #include #include "../Config/validation.h" +#include "../HTML.hpp" #include "../HttpException.hpp" #include "CGI/CGI.hpp" #include "CGI/CGIInfo.hpp" @@ -37,7 +38,7 @@ void ConnectionSocket::shutdown() { void ConnectionSocket::terminate() { close(id_); } -void ConnectionSocket::initExtension() { extension_ = ""; } +void ConnectionSocket::initExtension() { CGIextension_ = ""; } GET *ConnectionSocket::makeGET(int fd) { GET *obs = new GET(fd, em_, this, &response_); @@ -62,7 +63,7 @@ void ConnectionSocket::execCGI(const std::string &path) { char *argv[2]; argv[1] = NULL; int fd[2]; - CGIInfo info = parseCGIInfo(path, extension_, request_, loc_conf_); + CGIInfo info = parseCGIInfo(path, CGIextension_, request_, loc_conf_); struct stat st; const bool file_exist = stat(info.script_name_.c_str(), &st) != -1; if (!file_exist) throw ResourceNotFoundException("cgi script is not found"); @@ -119,8 +120,8 @@ void ConnectionSocket::processDELETE() { std::string path = loc_conf_->getTargetPath(request_.request_target_->getPath()); // if CGI extension exist, try exec CGI - const bool hasCGI = extension_ != ""; - if (hasCGI && contain(loc_conf_->cgi_exts_, extension_)) { + const bool hasCGI = CGIextension_ != ""; + if (hasCGI && contain(loc_conf_->cgi_exts_, CGIextension_)) { execCGI(path); return; } @@ -133,8 +134,8 @@ void ConnectionSocket::processPOST() { } std::string path = loc_conf_->getTargetPath(request_.request_target_->getPath()); // if CGI extension exist, try exec CGI - const bool hasCGI = extension_ != ""; - if (hasCGI && contain(loc_conf_->cgi_exts_, extension_)) { + const bool hasCGI = CGIextension_ != ""; + if (hasCGI && contain(loc_conf_->cgi_exts_, CGIextension_)) { execCGI(path); return; } @@ -148,8 +149,8 @@ void ConnectionSocket::processGET() { std::string path = loc_conf_->getTargetPath(request_.request_target_->getPath()); // if CGI extension exist, try exec CGI - const bool hasCGI = extension_ != ""; - if (hasCGI && contain(loc_conf_->cgi_exts_, extension_)) { + const bool hasCGI = CGIextension_ != ""; + if (hasCGI && contain(loc_conf_->cgi_exts_, CGIextension_)) { execCGI(path); return; } @@ -173,6 +174,7 @@ void ConnectionSocket::processGET() { DEBUG_PUTS("autoindex"); response_.appendBody(GET::listFilesAndDirectories(path, request_)); response_.setStatusAndReason(200); + response_.setContentType("autoindex.html", false); em_->disableReadEvent(id_); em_->registerWriteEvent(id_); return; // 200 OK @@ -182,6 +184,7 @@ void ConnectionSocket::processGET() { } } // URI file or index file + response_.setContentType(path, false); int fd = open(path.c_str(), O_RDONLY); if (fd < 0) throw InternalServerErrorException("open error"); GET *obs = makeGET(fd); @@ -201,14 +204,27 @@ void ConnectionSocket::processRedirect() { } void ConnectionSocket::processErrorPage(const LocationConf *conf) { + DEBUG_PUTS("process ErrorPage"); std::stringstream ss; ss << response_.getStatus(); - std::map::const_iterator itr = conf->common_.error_pages_.find(ss.str()); - if (itr != conf->common_.error_pages_.end()) { - std::string filename = itr->second; - if (filename[0] != '/') filename = conf->common_.root_ + "/" + filename; - if (conf_.cache_.error_page_paths_.find(filename) != conf_.cache_.error_page_paths_.end()) - response_.appendBody(conf_.cache_.error_page_paths_[filename]); + if (!conf) { + // Bad Request error page + response_.appendBody(HTML::getDefaultErrorPage(ss.str(), conf_.cache_.statusMsg_[response_.getStatus()])); + response_.appendHeader("content-type", "text/html"); + } else { + // Other error page + std::map::const_iterator itr = conf->common_.error_pages_.find(ss.str()); + if (itr != conf->common_.error_pages_.end()) { + std::string filename = itr->second; + if (filename[0] != '/') filename = conf->common_.root_ + "/" + filename; + if (conf_.cache_.error_page_paths_.find(filename) != conf_.cache_.error_page_paths_.end()) { + response_.appendBody(conf_.cache_.error_page_paths_[filename]); + response_.setContentType(conf_.cache_.error_page_paths_[filename], true); + return; + } + } + response_.appendBody(HTML::getDefaultErrorPage(ss.str(), conf_.cache_.statusMsg_[response_.getStatus()])); + response_.appendHeader("content-type", "text/html"); } } @@ -220,7 +236,7 @@ void ConnectionSocket::process() { processRedirect(); return; } - extension_ = getCGIExtension(request_.request_target_->getPath()); + CGIextension_ = getCGIExtension(request_.request_target_->getPath()); if (request_.methodIs(HttpRequest::GET)) { processGET(); } else if (request_.methodIs(HttpRequest::POST)) { @@ -244,12 +260,9 @@ void ConnectionSocket::notify(struct kevent ev) { } } catch (HttpException &e) { // all 4xx 5xx exception(readRequest and process) is caught here - std::cerr << e.what() << std::endl; + DEBUG_PUTS(e.what()); response_.setStatusAndReason(e.statusCode()); - if (loc_conf_) { - // error_page directive is ignored when error ocuured reading Request - processErrorPage(loc_conf_); - } + processErrorPage(loc_conf_); em_->disableReadEvent(id_); em_->registerWriteEvent(id_); } catch (std::runtime_error &e) { @@ -268,7 +281,7 @@ void ConnectionSocket::notify(struct kevent ev) { } if (response_.getState() == HttpResponse::End) { loc_conf_ = NULL; - extension_ = ""; + CGIextension_ = ""; request_ = HttpRequest(); rreader_ = HttpRequestReader(rreader_, request_); response_ = HttpResponse(id_, port_, &conf_); diff --git a/playground/tkg/Design1/src/Observee/ConnectionSocket.hpp b/playground/tkg/Design1/src/Observee/ConnectionSocket.hpp index 5b3d337..fa815fc 100644 --- a/playground/tkg/Design1/src/Observee/ConnectionSocket.hpp +++ b/playground/tkg/Design1/src/Observee/ConnectionSocket.hpp @@ -52,7 +52,7 @@ class ConnectionSocket : public Observee { HttpRequestReader rreader_; HttpResponse response_; Cache cache_; - std::string extension_; + std::string CGIextension_; // std::deque request_; // std::deque response_; diff --git a/playground/tkg/Design1/test/client.py b/playground/tkg/Design1/test/client.py index b49842a..22c198a 100755 --- a/playground/tkg/Design1/test/client.py +++ b/playground/tkg/Design1/test/client.py @@ -19,7 +19,14 @@ def send_http_request(host, port, method, path, body, headers): # レスポンスのボディを受け取る body = response.read() # レスポンスのボディを表示 - print(body.decode()) + content_type = response.getheader("Content-Type") + type = "" + if (content_type): + type, _ = content_type.split("/") + if (type == "text"): + print(body.decode()) + else: + print(body) # 接続を閉じる conn.close() diff --git a/playground/tkg/Design1/test/e2e_test.cpp b/playground/tkg/Design1/test/e2e_test.cpp index 6d650f4..d7ec0ff 100644 --- a/playground/tkg/Design1/test/e2e_test.cpp +++ b/playground/tkg/Design1/test/e2e_test.cpp @@ -5,12 +5,15 @@ #include #include +#include #include #include bool includes(const std::string &str, const std::string &substr); std::string sendRequest(const std::string &host, const std::string &port, const std::string &method, const std::string &path, const std::string &body, const std::string &headers); +std::string sendInvalidRequest(const std::string &host, const std::string &port, const std::string &method, + const std::string &path, const std::string &body, const std::string &headers); TEST(E2E, CGIDoc) { std::string host = "localhost"; @@ -99,7 +102,7 @@ TEST(E2E, CGILocalRedirectToClientRedirect) { ASSERT_TRUE(includes(res, "location: http://example.com")); } -TEST(E2E, Get) { +TEST(E2E, Get_index_html) { std::string host = "localhost"; std::string port = "80"; std::string method = "GET"; @@ -107,11 +110,28 @@ TEST(E2E, Get) { std::string body = "hello"; std::string headers = "Host: localhost;Content-Length:5"; std::string res = sendRequest(host, port, method, path, body, headers); - + std::cerr << res << std::endl; // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); + ASSERT_TRUE(includes(res, "content-type: text/html")); ASSERT_TRUE(includes(res, "")); } +TEST(E2E, Get_42_jpg) { + std::string host = "localhost"; + std::string port = "80"; + std::string method = "GET"; + std::string path = "/html/42.jpg"; + std::string body = "hello"; + std::string headers = "Host: localhost;Content-Length:5"; + std::string res = sendRequest(host, port, method, path, body, headers); + std::cerr << res << std::endl; + // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); + ASSERT_TRUE(includes(res, "content-type: image/jpeg")); + std::ifstream ifs("../html/42.jpg"); + std::string str((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + ASSERT_TRUE(includes(res, str)); +} + TEST(E2E, Autoindex) { std::string host = "localhost"; std::string port = "80"; @@ -120,8 +140,9 @@ TEST(E2E, Autoindex) { std::string body = "hello"; std::string headers = "Host: localhost"; std::string res = sendRequest(host, port, method, path, body, headers); - + std::cerr << res << std::endl; // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); + ASSERT_TRUE(includes(res, "content-type: text/html")); ASSERT_TRUE(includes(res, "")); ASSERT_TRUE(includes(res, "

con.conf

")); @@ -135,11 +156,41 @@ TEST(E2E, Non_Autoindex) { std::string body = "hello"; std::string headers = "Host: localhost"; std::string res = sendRequest(host, port, method, path, body, headers); - + std::cerr << res << std::endl; // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); ASSERT_TRUE(includes(res, "HTTP/1.1 404 Not Found")); } +TEST(E2E, NotImplementedDefaultError) { + std::string host = "localhost"; + std::string port = "80"; + std::string method = "GETS"; + std::string path = "/cgi-bin/"; + std::string body = "hello"; + std::string headers = "Host: localhost"; + std::string res = sendRequest(host, port, method, path, body, headers); + std::cerr << res << std::endl; + // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); + ASSERT_TRUE(includes(res, "HTTP/1.1 501 Not Implemented")); + ASSERT_TRUE(includes( + res, "

Our apologies for the temporary inconvenience. The requested URL was not found on this server.\n

")); +} + +TEST(E2E, duplicate_host_header) { + std::string host = "localhost"; + std::string port = "80"; + std::string method = "GET"; + std::string path = "/cgi-bin/"; + std::string body = "hello"; + std::string headers = "Host: localhost;Host: localhost"; + std::string res = sendInvalidRequest(host, port, method, path, body, headers); + std::cerr << res << std::endl; + // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); + ASSERT_TRUE(includes(res, "HTTP/1.1 400 Bad Request")); + ASSERT_TRUE(includes( + res, "

Our apologies for the temporary inconvenience. The requested URL was not found on this server.\n

")); +} + TEST(E2E, Chunked) { std::string host = "localhost"; std::string port = "80"; @@ -149,7 +200,7 @@ TEST(E2E, Chunked) { std::string headers = "Host: localhost;Transfer-Encoding:chunked, chunked , chunked "; std::string res = sendRequest(host, port, method, path, body, headers); - + std::cerr << res << std::endl; // ASSERT_TRUE(includes(res, "HTTP/1.1 200 OK")); ASSERT_TRUE(includes(res, "")); } @@ -164,7 +215,7 @@ TEST(E2E, ObsFold) { std::string res = sendRequest(host, port, method, path, body, headers); - std::cout << res << std::endl; + std::cerr << res << std::endl; EXPECT_TRUE(includes(res, "HTTP/1.1 400 Bad Request")); } @@ -238,3 +289,67 @@ std::string sendRequest(const std::string &host, const std::string &port, const return response; } + +std::string sendInvalidRequest(const std::string &host, const std::string &port, const std::string &method, + const std::string &path, const std::string &body, const std::string &headers) { + struct addrinfo hints, *res; + int err; + int sock; + + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_STREAM; + // 名前解決の方法を指定 + hints.ai_family = AF_INET; + + if ((err = getaddrinfo(host.c_str(), port.c_str(), &hints, &res)) != 0) { + return ""; + } + void *ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + char addr_buf[64]; + inet_ntop(res->ai_family, ptr, addr_buf, sizeof(addr_buf)); + + sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (sock == -1) { + perror("socket"); + return ""; + } + + if (connect(sock, res->ai_addr, res->ai_addrlen) == -1) { + perror("connect"); + return ""; + } + std::stringstream ss; + ss << method << " " << path << " " + << "HTTP/1.1\r\n"; + std::vector lines; + std::stringstream hs(headers); + std::string str; + if (headers != "") { + while (getline(hs, str, ';')) { + ss << str << "\r\n"; + } + } + ss << "\r\n"; + ss << body; + char response[10000]; + std::string request = ss.str(); + int write_res = sendto(sock, request.c_str(), request.size(), 0, NULL, 0); + if (write_res == -1) { + perror("write"); + } + memset(response, 0, 10000); + ssize_t read_res = -1; + size_t total = 0; + while (read_res != 0) { + read_res = read(sock, response + total, 10000 - total); + if (read_res == -1) { + perror("read"); + exit(1); + } + total += read_res; + response[total] = '\0'; + } + freeaddrinfo(res); + close(sock); + return std::string(response); +}