src/server/safs/cgi.c

changeset 502
11ac3761c0e3
parent 501
2aa6bd9f166f
child 503
aeaf7db26fac
--- a/src/server/safs/cgi.c	Sat Jun 10 18:12:04 2023 +0200
+++ b/src/server/safs/cgi.c	Sun Jun 11 15:53:55 2023 +0200
@@ -198,6 +198,7 @@
         error = 1;
     }
     
+    handler->wait_read = TRUE;
     handler->events = 2; // 2 events (stdout, stderr)
     
     if(error) {
@@ -222,28 +223,29 @@
              > 0)
     {
         handler->writebuf_pos += wr;
+        handler->count_write += wr;
     }
-    if(wr < 0) {
-        if(errno != EWOULDBLOCK) {
+    if(handler->writebuf_size - handler->writebuf_pos > 0) {
+        if(net_errno(sn->csd) != EWOULDBLOCK) {
             handler->result = REQ_ABORTED;
         }
-        handler->wait_write = TRUE;
+        
         return 1;
-    } else {
-        handler->wait_write = FALSE;
     }
     return 0;
 }
 
 static int cgi_try_write(CGIHandler *handler, EventHandler *ev, Session *sn, char *buf, size_t size) {
-    handler->wait_write = FALSE;
+
     size_t pos = 0;
     ssize_t wr = 0;
     while(size - pos > 0 && (wr = net_write(sn->csd, buf + pos, size - pos)) > 0) {
         pos += wr;
+        handler->count_write += wr;
     }
-    if(wr < 0) {
-        if(errno == EWOULDBLOCK) {
+    
+    if(pos < size) {
+        if(net_errno(sn->csd) == EWOULDBLOCK) {
             // copy remaining bytes to the write buffer
             // we assume there are no remaining bytes in writebuf
             size_t remaining = size-pos;
@@ -268,10 +270,9 @@
                 handler->events++;
                 handler->poll_out = TRUE;
             }
-            handler->wait_write = TRUE;
         } else {
             handler->result = REQ_ABORTED;
-            log_ereport(LOG_FAILURE, "cgi_try_write: %s", strerror(errno));
+            log_ereport(LOG_FAILURE, "cgi_try_write: %s", strerror(net_errno(sn->csd)));
         }
         return 1;
     }
@@ -282,14 +283,22 @@
 int cgi_stdout_readevent(EventHandler *ev, Event *event) {
     CGIHandler *handler = event->cookie;
     
-    return cgi_read_output(handler, ev);
+    int ret = cgi_read_output(handler, ev);
+    if(ret == 0) {
+        handler->wait_read = FALSE;
+    }
+    return ret;
 }
 
 int cgi_writeevent(EventHandler *ev, Event *event) {
     CGIHandler *handler = event->cookie;
     
     // cgi_read_output will try to flush the buffer
-    return cgi_read_output(handler, ev);
+    int ret = cgi_read_output(handler, ev);
+    if(ret == 0) {
+        handler->poll_out = FALSE;
+    }
+    return ret;
 }
 
 
@@ -303,7 +312,7 @@
     // if writebuf is empty, this does nothing and returns 0
     if(cgi_try_write_flush(handler, sn)) {
         if(handler->result == REQ_ABORTED) {
-            log_ereport(LOG_DEBUG, "cgi-send: req: %p write failed: %s: abort", strerror(errno), rq);
+            log_ereport(LOG_DEBUG, "cgi-send: req: %p write failed: %s: abort", handler->parser->rq, strerror(net_errno(sn->csd)), rq);
             return 0;
         } else {
             return 1;
@@ -375,7 +384,7 @@
     if(r < 0 && errno == EWOULDBLOCK) {
         return 1;
     }
-      
+    handler->cgi_eof = TRUE; 
     return 0;
 }
 
@@ -473,12 +482,32 @@
     Session *sn = parser->sn;
     Request *rq = parser->rq;
     
+    char *event_fn = "";
+    if(event->fn == cgi_stdout_readevent) {
+        event_fn = "stdout";
+    } else if(event->fn == cgi_stderr_readevent) {
+        event_fn = "stderr";
+    } else if(event->fn == cgi_writeevent) {
+        event_fn = "httpout";
+    }
+    log_ereport(LOG_DEBUG, "cgi-send: req: %p finish: event: %d pollout: %d cgi_eof: %d fn: %s", rq, handler->events, handler->poll_out, handler->cgi_eof, event_fn);
+    
     if(--handler->events > 0) {
-        if(handler->events == 1 && handler->poll_out && !handler->wait_write) {
-            // write event registered, however it will not be activated anymore
-            // we can safely remove the event
-            if(event_removepoll(ev, sn->csd)) {
-                log_ereport(LOG_FAILURE, "cgi_event_finish: event_removepoll: %s", strerror(errno));
+        if(handler->events == 1) {
+            if(handler->poll_out) {
+                // write event registered, however it will not be activated anymore
+                // we can safely remove the event
+                log_ereport(LOG_DEBUG, "cgi-send: req: %p finish: event: 1 remove-poll write", rq);
+                if(event_removepoll(ev, sn->csd)) {
+                    log_ereport(LOG_FAILURE, "cgi_event_finish: event_removepoll: %s", strerror(errno));
+                }
+            } else if(handler->cgi_eof && handler->wait_read) {
+                log_ereport(LOG_DEBUG, "cgi-send: req: %p finish: event: 1 remove-poll read", rq);
+                if(ev_remove_poll(ev, handler->process.out[0])) {
+                    log_ereport(LOG_FAILURE, "cgi_event_finish: ev_remove_poll: %s", strerror(errno));
+                }
+            } else {
+                return 0;
             }
         } else {
             return 0;
@@ -490,7 +519,7 @@
         killpg(handler->process.pid, SIGTERM);
     }
     
-     log_ereport(LOG_DEBUG, "cgi-send: req: %p cgi_close", rq);
+    log_ereport(LOG_DEBUG, "cgi-send: req: %p cgi_close", rq);
     
     int exit_code = cgi_close(&handler->process);
     if(exit_code != 0) {
@@ -500,6 +529,7 @@
       
     cgi_parser_free(parser);
     
+    WSBool response_length_error = FALSE;
     // check if content-length set by the cgi script matches the number
     // of writes, that were written to the stream
     // this ensures, that broken cgi scripts don't break the connection
@@ -512,11 +542,25 @@
                         LOG_FAILURE,
                         "cgi-send: script: %s: content length mismatch",
                         handler->path);
-                rq->rq_attr.keep_alive = 0;
-                handler->result = REQ_ABORTED;
+                response_length_error = TRUE;
             }
         }
     }
+    // make sure we haven't lost any bytes
+    // should not happen unless the non-blocking IO code is buggy
+    if(handler->result != REQ_ABORTED && handler->parser->response_length != handler->count_write) {
+        log_ereport(
+                LOG_FAILURE,
+                "cgi-send: script: %s: IO error: cgi response length != http response length",
+                handler->path);
+        response_length_error = TRUE;
+    }
+    
+    // if the response length is broken, we must close the connection
+    if(response_length_error) {
+        rq->rq_attr.keep_alive = 0;
+        handler->result = REQ_ABORTED;
+    }
     
     net_setnonblock(sn->csd, 0);
     

mercurial