src/server/safs/cgi.c

changeset 433
39fe86ae4db0
parent 431
032b0ad35ee3
child 450
d7b276de183b
--- a/src/server/safs/cgi.c	Sun Nov 13 10:57:38 2022 +0100
+++ b/src/server/safs/cgi.c	Sun Nov 13 12:58:25 2022 +0100
@@ -179,6 +179,8 @@
     handler->writeev = writeev;
     handler->stderrev = stderr_readev;
     
+    net_setnonblock(sn->csd, 1);
+    
     // add poll events for cgi stdout/stderr
     int error = 0;
     if(ev_pollin(sn->ev, handler->process.err[0], stderr_readev)) {
@@ -200,15 +202,96 @@
     return REQ_PROCESSING;
 }
 
+static int cgi_try_write_flush(CGIHandler *handler, Session *sn) {
+    ssize_t wr = 0;
+    while(
+            handler->writebuf_size - handler->writebuf_pos > 0 &&
+            (wr = net_write(
+                            sn->csd,
+                            handler->writebuf + handler->writebuf_pos,
+                            handler->writebuf_size - handler->writebuf_pos))
+             > 0)
+    {
+        handler->writebuf_pos += wr;
+    }
+    if(wr < 0) {
+        if(errno != EWOULDBLOCK) {
+            handler->result = REQ_ABORTED;
+        }
+        return 1;
+    }
+    return 0;
+}
+
+static int cgi_try_write(CGIHandler *handler, EventHandler *ev, Session *sn, char *buf, size_t size) {
+    size_t pos = 0;
+    ssize_t wr = 0;
+    while(size - pos > 0 && (wr = net_write(sn->csd, buf + pos, size - pos)) > 0) {
+        pos += wr;
+    }
+    if(wr < 0) {
+        if(errno == EWOULDBLOCK) {
+            // copy remaining bytes to the write buffer
+            // we assume there are no remaining bytes in writebuf
+            size_t remaining = size-pos;
+            if(remaining <= handler->writebuf_alloc) {
+                memcpy(handler->writebuf, buf+pos, remaining);
+            } else {
+                handler->writebuf_alloc = size > 4096 ? size : 4096;
+                handler->writebuf = pool_realloc(sn->pool, handler->writebuf, handler->writebuf_alloc);
+                if(!handler->writebuf) {
+                    handler->result = REQ_ABORTED;
+                    return 1;
+                }
+            }
+            handler->writebuf_size = remaining;
+            handler->writebuf_pos = 0;
+            
+            // initialize poll, if it isn't already active
+            if(!handler->poll_out) {
+                if(event_pollout(ev, sn->csd, handler->writeev)) {
+                    handler->result = REQ_ABORTED;
+                    return 1;
+                }
+                handler->poll_out = TRUE;
+            }
+            return 1;
+        }
+        handler->result = REQ_ABORTED;
+        return 1;
+    }
+    
+    return 0;
+}
+
 int cgi_stdout_readevent(EventHandler *ev, Event *event) {
     CGIHandler *handler = event->cookie;
+    
+    return cgi_read_output(handler, ev);
+}
+
+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 cgi_read_output(CGIHandler *handler, EventHandler *ev) {
     CGIResponseParser *parser = handler->parser;
     Session *sn = parser->sn;
     Request *rq = parser->rq;
     
+    // try to flush handler->writebuf
+    // if writebuf is empty, this does nothing and returns 0
+    if(cgi_try_write_flush(handler, sn)) {
+        return handler->result == REQ_ABORTED ? 0 : 1;
+    }
+    
     char buf[4096]; // I/O buffer
     ssize_t r;
-    ssize_t wr = 0;
     
     handler->result = REQ_PROCEED;
     while((r = read(handler->process.out[0], buf, 4096)) > 0) {
@@ -221,28 +304,51 @@
                         "broken cgi script response: path: %s", handler->path);
                 protocol_status(sn, rq, 500, NULL);
                 handler->result = REQ_ABORTED;
-                break;
+                return 0;
             } else if(ret == 1) {
+                WS_ASSERT(pos <= r);
+                
+                parser->response_length += r-pos;
+                
                 parser->cgiheader = FALSE;
                 if(parser->status > 0) {
                     protocol_status(sn, rq, parser->status, parser->msg);
                 }
-                http_start_response(sn, rq);
+                
+                handler->response = http_create_response(sn, rq);
+                if(!handler->response) {
+                    handler->result = REQ_ABORTED;
+                    return 0;
+                }
+                
+                int send_response = http_send_response(handler->response);
+                if(send_response < 0) {
+                    handler->result = REQ_ABORTED;
+                    break;
+                } else if(send_response == 1) {
+                    // EWOULDBLOCK
+                    if(!handler->poll_out) {
+                        if(event_pollout(ev, sn->csd, handler->writeev)) {
+                            handler->result = REQ_ABORTED;
+                            return 0;
+                        }
+                        handler->poll_out = TRUE;
+                        return 1;
+                    }
+                } else {
+                    handler->response = NULL;
+                }
+                
                 if(pos < r) {
-                    parser->response_length += r-pos;
-                    wr = net_write(sn->csd, &buf[pos], r-pos);
-                    if(wr <= 0) {
-                        handler->result = REQ_ABORTED;
-                        break;
+                    if(cgi_try_write(handler, ev, sn, &buf[pos], r-pos)) {
+                        return handler->result == REQ_ABORTED ? 0 : 1;
                     }
                 }
             }
         } else {
             parser->response_length += r;
-            wr = net_write(sn->csd, buf, r);
-            if(wr <= 0) {
-                handler->result = REQ_ABORTED;
-                break;
+            if(cgi_try_write(handler, ev, sn, buf, r)) {
+                return handler->result == REQ_ABORTED ? 0 : 1;
             }
         }
     }
@@ -250,21 +356,7 @@
         return 1;
     }
     
-    char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs);
-    if(ctlen_header) {
-        int64_t ctlenhdr;
-        if(util_strtoint(ctlen_header, &ctlenhdr)) {
-            if(ctlenhdr != parser->response_length) {
-                log_ereport(
-                        LOG_FAILURE,
-                        "cgi-send: script: %s: content length mismatch",
-                        handler->path);
-                rq->rq_attr.keep_alive = 0;
-                handler->result = REQ_ABORTED;
-            }
-        }
-    }
-    
+    handler->read_output_finished = TRUE;
     return 0;
 }
 
@@ -337,7 +429,7 @@
     CGIResponseParser *parser = handler->parser;
     Session *sn = parser->sn;
     Request *rq = parser->rq;
-     
+      
     if(handler->result == REQ_ABORTED) {
         log_ereport(LOG_FAILURE, "cgi-send: kill script: %s", handler->path);
         kill(handler->process.pid, SIGKILL);
@@ -349,6 +441,11 @@
         handler->stderrev->finish = cgi_event_finish;
         return 0;
     }
+    if(handler->poll_out && !handler->send_response_finished) {
+        // send response is still active
+        handler->writeev->finish = cgi_event_finish;
+        return 0;
+    }
     
     int exit_code = cgi_close(&handler->process);
     if(exit_code != 0) {
@@ -357,6 +454,27 @@
     }
       
     cgi_parser_free(parser);
+    
+    // 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
+    char *ctlen_header = pblock_findkeyval(pb_key_content_length, rq->srvhdrs);
+    if(ctlen_header) {
+        int64_t ctlenhdr;
+        if(util_strtoint(ctlen_header, &ctlenhdr)) {
+            if(ctlenhdr != parser->response_length) {
+                log_ereport(
+                        LOG_FAILURE,
+                        "cgi-send: script: %s: content length mismatch",
+                        handler->path);
+                rq->rq_attr.keep_alive = 0;
+                handler->result = REQ_ABORTED;
+            }
+        }
+    }
+    
+    net_setnonblock(sn->csd, 0);
+    
     // return to nsapi loop
     nsapi_function_return(sn, rq, handler->result);
     return 0;

mercurial