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 @@ + + + + + + + Upload + + + +

Upload File

+
+
+ + +
+ No file selected +
+
+ +
+ +
+
+ + + \ No newline at end of file diff --git a/htdocs/site-1/upload.php b/htdocs/site-1/upload.php new file mode 100644 index 0000000..7255a85 --- /dev/null +++ b/htdocs/site-1/upload.php @@ -0,0 +1,39 @@ +
+
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_map sockets_;
 
     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