diff --git a/config/default.conf b/config/default.conf index 82d73aa..968b764 100644 --- a/config/default.conf +++ b/config/default.conf @@ -8,6 +8,20 @@ error_page 500 ./htdocs/error_pages/500.html; error_page 502 ./htdocs/error_pages/502.html; error_page 504 ./htdocs/error_pages/504.html; +server { + listen 8085; + host 0.0.0.0; + server_name localhost; + default; + root htdocs/site-4/παράδειγμα; + + location / { + autoindex on; + index index.html index.htm; + allowed_methods GET POST DELETE; + } +} + server { listen 8080; host 0.0.0.0; @@ -26,7 +40,7 @@ server { error_page 502 ./htdocs/error_pages/502.html; error_page 504 ./htdocs/error_pages/504.html; - client_max_body_size 1M; + # client_max_body_size 1M; location / { autoindex off; @@ -51,9 +65,10 @@ server { location /test { root ./htdocs/site-1/tester; autoindex off; - index index.html; + index test.html; allowed_methods GET; } + location /redirect { redirect 301 http://localhost:8081/; allowed_methods GET POST; @@ -66,8 +81,9 @@ server { index index2.html; } - # cgi_enabled yes; - # cgi_handler .php /usr/bin/php-cgi; + cgi_enabled yes; + cgi_handler .php /usr/bin/php-cgi; + client_max_body_size 10M; } server { diff --git a/htdocs/site-1/error_log.txt b/htdocs/site-1/error_log.txt new file mode 100755 index 0000000..deace73 --- /dev/null +++ b/htdocs/site-1/error_log.txt @@ -0,0 +1,38 @@ +[06-Nov-2025 15:22:10 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:22:10 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:22:39 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:22:39 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:24:46 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:24:46 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:25:28 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:25:28 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:28:55 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:28:55 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:30:00 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:30:00 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:32:31 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:32:31 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:32:59 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:32:59 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:33:15 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:33:15 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:33:47 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:33:47 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:33:54 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:33:54 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:34:02 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:34:02 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:34:08 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:34:08 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:36:24 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:36:24 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:36:44 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:36:44 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:37:34 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:37:34 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:37:57 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:37:57 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:38:21 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:38:21 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 +[06-Nov-2025 15:40:56 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 21 +[06-Nov-2025 15:40:56 Europe/Berlin] PHP Warning: Array to string conversion in /home/qmennen/Documents/webserv/htdocs/site-1/upload.php on line 24 diff --git a/htdocs/site-1/upload.html b/htdocs/site-1/upload.html new file mode 100644 index 0000000..ed480e2 --- /dev/null +++ b/htdocs/site-1/upload.html @@ -0,0 +1,27 @@ + + + +
+ + ++ diff --git a/htdocs/site-4/παράδειγμα/index.html b/htdocs/site-4/παράδειγμα/index.html new file mode 100644 index 0000000..ca9740b --- /dev/null +++ b/htdocs/site-4/παράδειγμα/index.html @@ -0,0 +1,11 @@ + + + + + +Document + + + iadijfaf + + \ No newline at end of file diff --git a/test_700kb.bin b/test_700kb.bin new file mode 100644 index 0000000..f285a36 Binary files /dev/null and b/test_700kb.bin differ diff --git a/webserv/client/Client.hpp b/webserv/client/Client.hpp index 5037d24..8f91ef1 100644 --- a/webserv/client/Client.hpp +++ b/webserv/client/Client.hpp @@ -63,6 +63,6 @@ class Client std::unordered_mapsockets_; Server &server_; - void writeToCgi(); - void readFromCgi(); + // void writeToCgi(); + // void readFromCgi(); }; \ No newline at end of file diff --git a/webserv/handler/CgiEnvironment.cpp b/webserv/handler/CgiEnvironment.cpp index 6b05d31..7f4aea6 100644 --- a/webserv/handler/CgiEnvironment.cpp +++ b/webserv/handler/CgiEnvironment.cpp @@ -10,6 +10,7 @@ #include // for strcpy, size_t #include // for optional #include // for pair +#include CgiEnvironment::CgiEnvironment(const URI &uri, const HttpRequest &request) { @@ -55,6 +56,9 @@ CgiEnvironment::CgiEnvironment(const URI &uri, const HttpRequest &request) env_["HTTP_ACCEPT_LANGUAGE"] = headers.get("Accept-Language"); env_["HTTP_ACCEPT_ENCODING"] = headers.get("Accept-Encoding"); + env_["UPLOAD_TMP_DIR"] = "./htdocs/tmp"; // Example upload directory, adjust as needed + env_["TMP_DIR"] = "./htdocs/tmp"; // Example temp directory, adjust as needed + appendCustomHeaders(headers); } diff --git a/webserv/handler/CgiHandler.cpp b/webserv/handler/CgiHandler.cpp index 8b2eecd..c9a9dd6 100644 --- a/webserv/handler/CgiHandler.cpp +++ b/webserv/handler/CgiHandler.cpp @@ -2,19 +2,23 @@ #include #include // for CgiProcess #include // for ErrorHandler +#include // for URI #include // for HttpRequest #include // for HttpResponse #include // for Log, LOCATION #include // for CgiSocket #include // for TimerSocket -#include // for URI #include // for trim #include +#include #include #include +#include #include // for function -#include // for move +#include +#include +#include // for move #include // for ssize_t @@ -28,7 +32,8 @@ void CgiHandler::handle() { Log::info("CgiHandler handling request"); - if (request_.getUri().isCgi() && request_.getUri().getCgiPath().empty() && access(request_.getUri().getFullPath().c_str(), X_OK) != 0) + if (request_.getUri().isCgi() && request_.getUri().getCgiPath().empty() + && access(request_.getUri().getFullPath().c_str(), X_OK) != 0) { ErrorHandler::createErrorResponse(403, response_); return; @@ -41,6 +46,23 @@ void CgiHandler::handle() Log::info("CGI process started and sockets registered"); } +static inline bool findHeaderEnd(const std::string &s, size_t &pos, long &sepSize) +{ + Log::trace(LOCATION); + size_t a = s.find("\r\n\r\n"); + size_t b = s.find("\n\n"); + size_t c = s.find("\r\r"); + size_t end = std::min({a, b, c}); + + if (end == std::string::npos) + { + return false; + } + sepSize = (end == a) ? 4 : 2; + pos = end; + return true; +} + void CgiHandler::write() { Log::trace(LOCATION); @@ -49,25 +71,36 @@ void CgiHandler::write() Log::error("CGI stdin socket is null"); return; } - if (request_.getBody().empty()) + const std::string &body = request_.getBody(); + size_t before = writeOffset_; + while (writeOffset_ < body.size()) { - Log::debug("No body to write to CGI stdin, fd: " + std::to_string(cgiStdIn_->getFd())); - request_.getClient().removeSocket(cgiStdIn_.get()); - cgiStdIn_ = nullptr; - return; + const char *data = body.data() + writeOffset_; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) + size_t remaining = body.size() - writeOffset_; + size_t chunk = remaining > CHUNK_SIZE ? CHUNK_SIZE : remaining; + ssize_t bytesRead = cgiStdIn_->write(data, chunk); + if (bytesRead > 0) + { + writeOffset_ += static_cast (bytesRead); + } + else + { + break; // would block or peer closed; try again on next EPOLLOUT + } } - ssize_t bytesWritten = cgiStdIn_->write(request_.getBody().data(), request_.getBody().size()); - if (bytesWritten < 0) + if (writeOffset_ >= body.size()) { - Log::error("Failed to write to CGI stdin, fd: " + std::to_string(cgiStdIn_->getFd())); + Log::debug("CGI stdin sent " + std::to_string(body.size()) + " bytes, closing write end"); + request_.getClient().removeSocket(cgiStdIn_.get()); + cgiStdIn_.reset(); + bodyWriteCompleted_ = true; } else { - Log::debug("Wrote " + std::to_string(bytesWritten) - + " bytes to CGI stdin, fd: " + std::to_string(cgiStdIn_->getFd())); + Log::debug("Wrote " + std::to_string(writeOffset_ - before) + " bytes, write offset " + + std::to_string(writeOffset_) + "/ " + std::to_string(body.size())); + // Log::debug("CGI stdin progress " + std::to_string(before) + "→" + std::to_string(writeOffset_)); } - request_.getClient().removeSocket(cgiStdIn_.get()); - cgiStdIn_ = nullptr; } void CgiHandler::read() @@ -75,31 +108,108 @@ void CgiHandler::read() Log::trace(LOCATION); if (cgiStdOut_ == nullptr) { - Log::error("CGI stdout socket is null"); + Log::debug("CGI stdout socket is null in read()"); return; } - char buffer[bufferSize_] = {}; // NOLINT(cppcoreguidelines-avoid-c-arrays) - ssize_t bytesRead - = cgiStdOut_->read(buffer, sizeof(buffer) - 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - if (bytesRead < 0) + + char buffer[bufferSize_] = {}; + ssize_t bytesRead = cgiStdOut_->read(buffer, sizeof(buffer)); + + if (bytesRead > 0) { - Log::error("Failed to read from CGI stdout, fd: " + std::to_string(cgiStdOut_->getFd())); - } - else if (bytesRead == 0) - { - Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd())); - request_.getClient().removeSocket(cgiStdOut_.get()); - // request_.getClient().removeSocket(timerSocket_.get()); - cgiStdOut_ = nullptr; - parseCgiOutput(); - return; - } - else - { - buffer[bytesRead] = '\0'; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) appendToBuffer(buffer, static_cast (bytesRead)); Log::debug("Read " + std::to_string(bytesRead) - + " bytes from CGI stdout, fd: " + std::to_string(cgiStdOut_->getFd())); + + " bytes from CGI stdout (buffer size: " + std::to_string(buffer_.size()) + ")"); + + // Parse headers once, as soon as we have them + if (!headersParsed_) + { + size_t headerEnd = 0; + long sepSize = 0; + std::string snapshot(buffer_.begin(), buffer_.end()); + if (findHeaderEnd(snapshot, headerEnd, sepSize)) + { + std::string headers(snapshot.begin(), snapshot.begin() + static_cast (headerEnd)); + parseCgiHeaders(headers); + // After headers parsed, remove them from buffer_ so it contains only body + buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + sepSize); + headersParsed_ = true; + contentLength_ = response_.getHeaders().getContentLength(); + Log::debug("CGI headers parsed, Content-Length: " + + (contentLength_.has_value() ? std::to_string(contentLength_.value()) : "not set")); + } + } + + // Only finalize if we've finished writing the request body AND we have complete response + bool responseComplete = false; + if (headersParsed_ && contentLength_.has_value()) + { + responseComplete = (buffer_.size() >= contentLength_.value()); + } + + if (bodyWriteCompleted_ && responseComplete) + { + Log::debug("Response complete: headers parsed and content received"); + request_.getClient().removeSocket(cgiStdOut_.get()); + cgiStdOut_.reset(); + finalizeCgiResponse(); + return; + } + return; + } + + if (bytesRead == 0) + { + // EOF from CGI process + Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd())); + request_.getClient().removeSocket(cgiStdOut_.get()); + cgiStdOut_.reset(); + + // If headers not parsed yet, try once more + if (!headersParsed_) + { + size_t headerEnd = 0; + long sep = 0; + std::string snap(buffer_.begin(), buffer_.end()); + if (findHeaderEnd(snap, headerEnd, sep)) + { + std::string headers(snap.begin(), snap.begin() + static_cast (headerEnd)); + parseCgiHeaders(headers); + buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + sep); + headersParsed_ = true; + } + } + + // Only finalize if we've finished writing the request body + if (bodyWriteCompleted_) + { + finalizeCgiResponse(); + } + else + { + Log::warning("CGI process closed stdout before request body was completely written"); + // Set error response but don't finalize until write is complete + // ErrorHandler::createErrorResponse(500, response_); + } + return; + } + + if (bytesRead < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + { + Log::debug("CGI stdout would block, will retry on next EPOLLIN"); + return; + } + else + { + Log::error("Error reading from CGI stdout: " + std::string(strerror(errno))); + // Only finalize if write is complete + if (bodyWriteCompleted_) + { + finalizeCgiResponse(); + } + } } } @@ -108,31 +218,27 @@ void CgiHandler::error() Log::trace(LOCATION); if (cgiStdErr_ == nullptr) { - Log::error("CGI stderr socket is null"); return; } - char buffer[bufferSize_] = {}; // NOLINT(cppcoreguidelines-avoid-c-arrays) - ssize_t bytesRead - = cgiStdErr_->read(buffer, sizeof(buffer) - 1); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - if (bytesRead < 0) + while (true) { - Log::error("Failed to read from CGI stderr, fd: " + std::to_string(cgiStdErr_->getFd())); - } - else if (bytesRead == 0) - { - Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd())); - request_.getClient().removeSocket(cgiStdErr_.get()); - // request_.getClient().removeSocket(timerSocket_.get()); // todo maybe this dangerous - cgiStdErr_ = nullptr; - parseCgiOutput(); - return; - } - else - { - buffer[bytesRead] = '\0'; // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index) - appendToBuffer(buffer, static_cast (bytesRead)); - Log::error("CGI stderr output (fd: " + std::to_string(cgiStdErr_->getFd()) - + "): " + std::string(buffer, static_cast (bytesRead))); + char buffer[bufferSize_] = {}; + ssize_t bytesRead = cgiStdErr_->read(buffer, sizeof(buffer)); + if (bytesRead > 0) + { + appendToBuffer(buffer, static_cast (bytesRead)); + Log::error("CGI stderr output (fd: " + std::to_string(cgiStdErr_->getFd()) + + "): " + std::string(buffer, static_cast (bytesRead))); + continue; + } + if (bytesRead == 0) + { + Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd())); + request_.getClient().removeSocket(cgiStdErr_.get()); + cgiStdErr_.reset(); + break; + } + break; } } @@ -150,6 +256,12 @@ void CgiHandler::setCgiSockets(std::unique_ptr cgiStdIn, std::unique_ request_.getClient().addSocket(cgiStdIn_.get()); request_.getClient().addSocket(cgiStdOut_.get()); request_.getClient().addSocket(cgiStdErr_.get()); + + if (request_.getBody().empty()) + { + request_.getClient().removeSocket(cgiStdIn_.get()); + cgiStdIn_.reset(); + } } void CgiHandler::wait() noexcept @@ -168,35 +280,24 @@ void CgiHandler::setPid(int pid) void CgiHandler::parseCgiOutput() { Log::trace(LOCATION); - long headerSeperatorSize = 2; - - // Parse the headers from the buffer - auto header = std::string(buffer_.begin(), buffer_.end()); - size_t headerEnd = std::min({ - header.find("\r\n\r\n"), - header.find("\n\n"), - header.find("\r\r"), - }); - - if (headerEnd == std::string::npos) + if (headersParsed_) + { + return; + } + size_t headerEnd = 0; + long sepSize = 0; + std::string header(buffer_.begin(), buffer_.end()); + if (!findHeaderEnd(header, headerEnd, sepSize)) { Log::debug("CGI output headers not complete yet"); return; } - - if (header.substr(static_cast (headerEnd), 2) == "\r\n") - { - headerSeperatorSize = 4; - } - - Log::debug("headerseperator: " + header.substr(static_cast (headerEnd), 2)); - // Parse the headers - std::string headers(buffer_.begin(), buffer_.begin() + static_cast (headerEnd)); + std::string headers(header.begin(), header.begin() + static_cast (headerEnd)); Log::debug("CGI output headers: " + headers); parseCgiHeaders(headers); - - buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + headerSeperatorSize); - finalizeCgiResponse(); + buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + sepSize); + headersParsed_ = true; + contentLength_ = response_.getHeaders().getContentLength(); } void CgiHandler::parseCgiHeaders(std::string &headers) @@ -247,6 +348,8 @@ void CgiHandler::parseCgiHeaders(std::string &headers) response_.addHeader(name, value); } } + + contentLength_ = response_.getHeaders().getContentLength(); } void CgiHandler::handleTimeout() @@ -275,9 +378,8 @@ void CgiHandler::finalizeCgiResponse() { Log::trace(LOCATION); auto status = response_.getHeaders().get("Status"); - wait(); - if (cgiProcess_->getExitCode() > 0 && status.empty()) + if (cgiProcess_ && cgiProcess_->getExitCode() > 0 && status.empty()) { response_.setStatus(500); } diff --git a/webserv/handler/CgiHandler.hpp b/webserv/handler/CgiHandler.hpp index 996b57d..bf950af 100644 --- a/webserv/handler/CgiHandler.hpp +++ b/webserv/handler/CgiHandler.hpp @@ -4,6 +4,7 @@ #include // for HttpRequest #include // for CgiSocket +#include #include // for unique_ptr #include // for string #include // for vector @@ -36,6 +37,7 @@ class CgiHandler : public AHandler void handleTimeout() override; private: + constexpr static size_t CHUNK_SIZE = 32768; constexpr static size_t bufferSize_ = 8192; // TODO: remove duplicate definition and move to configmanager std::vector buffer_; @@ -43,12 +45,17 @@ class CgiHandler : public AHandler std::unique_ptr cgiStdIn_; std::unique_ptr cgiStdOut_; std::unique_ptr cgiStdErr_; + void parseCgiOutput(); void parseCgiHeaders(std::string &headers); void finalizeCgiResponse(); void appendToBuffer(const char *data, size_t length); int pid_ = -1; + size_t writeOffset_ = 0; + bool headersParsed_ = false; + bool bodyWriteCompleted_ = false; + std::optional contentLength_; void write(); void read(); diff --git a/webserv/handler/CgiHandler2.cpp2 b/webserv/handler/CgiHandler2.cpp2 new file mode 100644 index 0000000..e9b4c1d --- /dev/null +++ b/webserv/handler/CgiHandler2.cpp2 @@ -0,0 +1,254 @@ +CgiHandler::CgiHandler(const HttpRequest &request, HttpResponse &response) + : AHandler(request, response), cgiProcess_(nullptr), cgiStdIn_(nullptr), cgiStdOut_(nullptr) +{ + Log::debug("CgiHandler constructed"); + bodyWriteOffset_ = 0; + headersParsed_ = false; + expectedBody_.reset(); +} + +void CgiHandler::setCgiSockets(std::unique_ptr cgiStdIn, std::unique_ptr cgiStdOut, + std::unique_ptr cgiStdErr) +{ + cgiStdIn->setCallback([this]() { write(); }); + cgiStdOut->setCallback([this]() { read(); }); + cgiStdErr->setCallback([this]() { error(); }); + + cgiStdOut_ = std::move(cgiStdOut); + cgiStdIn_ = std::move(cgiStdIn); + cgiStdErr_ = std::move(cgiStdErr); + + request_.getClient().addSocket(cgiStdIn_.get()); + request_.getClient().addSocket(cgiStdOut_.get()); + request_.getClient().addSocket(cgiStdErr_.get()); + // Ensure stdout/stderr are set to READ interest; stdin to WRITE only if there's a body + cgiStdOut_->setIOState(ASocket::IoState::READ); + cgiStdOut_->markDirty(); + cgiStdErr_->setIOState(ASocket::IoState::READ); + cgiStdErr_->markDirty(); + if (!request_.getBody().empty()) + { + cgiStdIn_->setIOState(ASocket::IoState::WRITE); + cgiStdIn_->markDirty(); + } + else + { + request_.getClient().removeSocket(cgiStdIn_.get()); + cgiStdIn_.reset(); + } +} + +void CgiHandler::write() +{ + Log::trace(LOCATION); + if (!cgiStdIn_) return; + const std::string &body = request_.getBody(); + if (bodyWriteOffset_ >= body.size()) + { + request_.getClient().removeSocket(cgiStdIn_.get()); + cgiStdIn_.reset(); + return; + } + // Stream body until pipe stops accepting data; no errno checks needed (level-triggered epoll) + size_t before = bodyWriteOffset_; + while (bodyWriteOffset_ < body.size()) + { + const char *data = body.data() + bodyWriteOffset_; + size_t remain = body.size() - bodyWriteOffset_; + size_t chunk = remain > 32768 ? 32768 : remain; + ssize_t n = cgiStdIn_->write(data, chunk); + if (n > 0) + { + bodyWriteOffset_ += static_cast (n); + } + else + { + break; // would block or peer closed; try again on next EPOLLOUT + } + } + if (bodyWriteOffset_ >= body.size()) + { + Log::debug("CGI stdin sent " + std::to_string(body.size()) + " bytes, closing write end"); + request_.getClient().removeSocket(cgiStdIn_.get()); + cgiStdIn_.reset(); + } + else + { + Log::debug("CGI stdin progress " + std::to_string(before) + "→" + std::to_string(bodyWriteOffset_)); + cgiStdIn_->setIOState(ASocket::IoState::WRITE); + cgiStdIn_->markDirty(); + } +} + +static inline bool findHeaderEnd(const std::string &s, size_t &pos, long &sepSize) +{ + size_t a = s.find("\r\n\r\n"); + size_t b = s.find("\n\n"); + size_t c = s.find("\r\r"); + size_t end = std::min({a, b, c}); + if (end == std::string::npos) return false; + sepSize = (end == a) ? 4 : 2; + pos = end; + return true; +} + +void CgiHandler::read() +{ + Log::trace(LOCATION); + if (!cgiStdOut_) return; + // Drain as much as available this callback + for (;;) + { + char buffer[bufferSize_] = {}; + ssize_t n = cgiStdOut_->read(buffer, sizeof(buffer)); + if (n > 0) + { + appendToBuffer(buffer, static_cast (n)); + // Parse headers once, as soon as we have them + if (!headersParsed_) + { + size_t headerEnd = 0; + long sepSize = 0; + std::string snapshot(buffer_.begin(), buffer_.end()); + if (findHeaderEnd(snapshot, headerEnd, sepSize)) + { + std::string headers(snapshot.begin(), snapshot.begin() + static_cast (headerEnd)); + parseCgiHeaders(headers); + // After headers parsed, remove them from buffer_ so it contains only body + buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + sepSize); + headersParsed_ = true; + // Capture expected body size if provided + std::string cl = response_.getHeaders().get("Content-Length"); + if (!cl.empty()) + { + expectedBody_ = static_cast (std::strtoul(cl.c_str(), nullptr, 10)); + } + else + { + expectedBody_.reset(); + } + } + } + // If we know Content-Length and already have it, finalize + if (headersParsed_ && expectedBody_.has_value() && buffer_.size() >= expectedBody_.value()) + { + request_.getClient().removeSocket(cgiStdOut_.get()); + cgiStdOut_.reset(); + finalizeCgiResponse(); + return; + } + continue; // try to read more this tick + } + else if (n == 0) + { + // EOF → finalize with whatever body we have + Log::info("CGI process closed stdout, fd: " + std::to_string(cgiStdOut_->getFd())); + request_.getClient().removeSocket(cgiStdOut_.get()); + cgiStdOut_.reset(); + // If headers not parsed yet, try once more (some scripts may end with only headers) + if (!headersParsed_) + { + size_t headerEnd = 0; + long sep = 0; + std::string snap(buffer_.begin(), buffer_.end()); + if (findHeaderEnd(snap, headerEnd, sep)) + { + std::string headers(snap.begin(), snap.begin() + static_cast (headerEnd)); + parseCgiHeaders(headers); + buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + sep); + headersParsed_ = true; + } + } + finalizeCgiResponse(); + return; + } + else + { + // Would block or transient error; wait for next EPOLLIN + break; + } + } +} + +void CgiHandler::error() +{ + Log::trace(LOCATION); + if (!cgiStdErr_) return; + for (;;) + { + char buffer[bufferSize_] = {}; + ssize_t n = cgiStdErr_->read(buffer, sizeof(buffer)); + if (n > 0) + { + appendToBuffer(buffer, static_cast (n)); + Log::error("CGI stderr output (fd: " + std::to_string(cgiStdErr_->getFd()) + + "): " + std::string(buffer, static_cast (n))); + continue; + } + else if (n == 0) + { + Log::info("CGI process closed stderr, fd: " + std::to_string(cgiStdErr_->getFd())); + request_.getClient().removeSocket(cgiStdErr_.get()); + cgiStdErr_.reset(); + break; + } + else + { + break; + } + } +} + +void CgiHandler::parseCgiOutput() +{ + // Changed: only parse headers if present; do NOT finalize here + Log::trace(LOCATION); + if (headersParsed_) return; + size_t headerEnd = 0; + long sepSize = 0; + std::string header(buffer_.begin(), buffer_.end()); + if (!findHeaderEnd(header, headerEnd, sepSize)) + { + Log::debug("CGI output headers not complete yet"); + return; + } + std::string headers(header.begin(), header.begin() + static_cast (headerEnd)); + Log::debug("CGI output headers: " + headers); + parseCgiHeaders(headers); + buffer_.erase(buffer_.begin(), buffer_.begin() + static_cast (headerEnd) + sepSize); + headersParsed_ = true; + std::string cl = response_.getHeaders().get("Content-Length"); + if (!cl.empty()) + expectedBody_ = static_cast (std::strtoul(cl.c_str(), nullptr, 10)); + else + expectedBody_.reset(); +} + +void CgiHandler::parseCgiHeaders(std::string &headers) +{ + Log::trace(LOCATION); + // ...existing code... + // At end, capture Content-Length if present + std::string cl = response_.getHeaders().get("Content-Length"); + if (!cl.empty()) expectedBody_ = static_cast (std::strtoul(cl.c_str(), nullptr, 10)); + // ...existing code... +} + +void CgiHandler::finalizeCgiResponse() +{ + Log::trace(LOCATION); + auto status = response_.getHeaders().get("Status"); + wait(); + if (cgiProcess_ && cgiProcess_->getExitCode() > 0 && status.empty()) + { + response_.setStatus(500); + } + else if (!status.empty()) + { + response_.setStatus(std::atoi(status.c_str())); + } + // Append only the body (headers already stripped) + response_.appendBody(buffer_); + response_.setComplete(); + buffer_.clear(); +} \ No newline at end of file diff --git a/webserv/handler/UploadHandler.cpp b/webserv/handler/UploadHandler.cpp new file mode 100644 index 0000000..7fea4e6 --- /dev/null +++ b/webserv/handler/UploadHandler.cpp @@ -0,0 +1,47 @@ +#include "webserv/handler/UploadHandler.hpp" + +#include "webserv/handler/ErrorHandler.hpp" +#include "webserv/handler/URI.hpp" +#include "webserv/http/HttpConstants.hpp" + +#include // for Log, LOCATION + +#include + +UploadHandler::UploadHandler(const HttpRequest &request, HttpResponse &response) : AHandler(request, response) {} + +void UploadHandler::handle() +{ + Log::trace(LOCATION); + std::string fullPath = request_.getUri().getFullPath(); + if (fullPath.empty()) + { + Log::warning("UploadHandler: Invalid path for UPLOAD"); + ErrorHandler::createErrorResponse(Http::StatusCode::BAD_REQUEST, response_, request_.getUri().getConfig()); + return; + } + uploadFile(fullPath, request_.getBody()); +} + +void UploadHandler::uploadFile(const std::string &path, const std::string &data) +{ + Log::trace(LOCATION); + std::ofstream fileStream{path.c_str(), std::ios::binary}; + if (!fileStream) + { + Log::error("UploadHandler: Failed to open file for upload: " + path); + ErrorHandler::createErrorResponse(Http::StatusCode::FORBIDDEN, response_, request_.getUri().getConfig()); + return; + } + fileStream.write(data.c_str(), data.size()); + fileStream.close(); + + response_.setStatus(Http::StatusCode::CREATED); + response_.setComplete(); +} + +void UploadHandler::handleTimeout() +{ + Log::warning("UploadHandler: Upload operation timed out"); + ErrorHandler::createErrorResponse(504, response_, request_.getUri().getConfig()); +} \ No newline at end of file diff --git a/webserv/handler/UploadHandler.hpp b/webserv/handler/UploadHandler.hpp new file mode 100644 index 0000000..1bb87ae --- /dev/null +++ b/webserv/handler/UploadHandler.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include // for AHandler + +class UploadHandler : public AHandler +{ + public: + UploadHandler(const HttpRequest &request, HttpResponse &response); + + void handle() override; + void handleTimeout() override; + + private: + void uploadFile(const std::string &path, const std::string &data); +}; \ No newline at end of file diff --git a/webserv/socket/ASocket.cpp b/webserv/socket/ASocket.cpp index 98c0704..4c415c8 100644 --- a/webserv/socket/ASocket.cpp +++ b/webserv/socket/ASocket.cpp @@ -36,10 +36,7 @@ ssize_t ASocket::read(void *buf, size_t len) const { Log::trace(LOCATION); ssize_t bytesRead = ::recv(fd_, buf, len, 0); - if (bytesRead == -1) - { - throw std::system_error(errno, std::generic_category(), "Socket: Read error"); - } + return bytesRead; } @@ -47,10 +44,7 @@ ssize_t ASocket::write(const void *buf, size_t len) const { Log::trace(LOCATION); ssize_t bytesSent = ::send(fd_, buf, len, 0); - if (bytesSent == -1) - { - throw std::system_error(errno, std::generic_category(), "Socket: Write error"); - } + return bytesSent; } diff --git a/webserv/socket/CgiSocket.cpp b/webserv/socket/CgiSocket.cpp index 346ff80..3f79565 100644 --- a/webserv/socket/CgiSocket.cpp +++ b/webserv/socket/CgiSocket.cpp @@ -22,10 +22,6 @@ ssize_t CgiSocket::read(void *buf, size_t len) const { Log::trace(LOCATION); ssize_t bytesRead = ::read(getFd(), buf, len); - if (bytesRead == -1) - { - throw std::system_error(errno, std::generic_category(), "Socket: Read error"); - } return bytesRead; } @@ -33,9 +29,5 @@ ssize_t CgiSocket::write(const void *buf, size_t len) const { Log::trace(LOCATION); ssize_t bytesSent = ::write(getFd(), buf, len); - if (bytesSent == -1) - { - throw std::system_error(errno, std::generic_category(), "Socket: Write error"); - } return bytesSent; } diff --git a/webserv/socket/TimerSocket.cpp b/webserv/socket/TimerSocket.cpp index 75c596c..800127e 100644 --- a/webserv/socket/TimerSocket.cpp +++ b/webserv/socket/TimerSocket.cpp @@ -53,10 +53,7 @@ ssize_t TimerSocket::read(void *buf, size_t len) const { Log::trace(LOCATION); ssize_t bytesRead = ::read(getFd(), buf, len); - if (bytesRead == -1) - { - throw std::system_error(errno, std::generic_category(), "Socket: Read error"); - } + return bytesRead; } @@ -64,9 +61,6 @@ ssize_t TimerSocket::write(const void *buf, size_t len) const { Log::trace(LOCATION); ssize_t bytesSent = ::write(getFd(), buf, len); - if (bytesSent == -1) - { - throw std::system_error(errno, std::generic_category(), "Socket: Write error"); - } + return bytesSent; } \ No newline at end of file