src/server/safs/cgi.c

changeset 118
38bf6dd8f4e7
child 119
155cbab9eefd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/server/safs/cgi.c	Wed Oct 26 15:53:56 2016 +0200
@@ -0,0 +1,270 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2016 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "cgi.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "../util/util.h"
+#include "../../ucx/string.h"
+
+#include "cgiutils.h"
+
+#define CGI_VARS 32
+
+#define CGI_RESPONSE_PARSER_BUFLEN      2048
+#define CGI_RESPONSE_MAX_LINE_LENGTH    512
+
+int send_cgi(pblock *pb, Session *sn, Request *rq) {  
+    char *path = pblock_findval("path", rq->vars);
+    
+    struct stat s;
+    if(stat(path, &s)) {
+        int statuscode = util_errno2status(errno);
+        protocol_status(sn, rq, statuscode, NULL);
+        return REQ_ABORTED;
+    }
+    if(S_ISDIR(s.st_mode)) {
+        protocol_status(sn, rq, 403, NULL);
+        return REQ_ABORTED;
+    }
+    
+    param_free(pblock_remove("content-type", rq->srvhdrs));
+    
+    const char *args = pblock_findval("query", rq->reqpb);
+    char **argv = cgi_create_argv(path, NULL, args);
+    
+    char **env = http_hdrs2env(rq->headers);
+    env = cgi_common_vars(sn, rq, env);
+    env = cgi_specific_vars(sn, rq, args, env, 1);
+    
+    CGIProcess cgip;
+    int ret = cgi_start(&cgip, path, argv, env);
+    if(ret != REQ_PROCEED) {
+        return ret;
+    }
+    
+    // TODO: send http body
+    close(cgip.in[1]);
+    
+    // read from child
+    CGIResponseParser *parser = cgi_parser_new(sn, rq);
+    WSBool cgiheader = TRUE;
+    
+    char buf[4096];
+    ssize_t r;
+    while((r = read(cgip.out[0], buf, 4096)) > 0) {
+        if(cgiheader) {
+            size_t pos;
+            int ret = cgi_parse_response(parser, buf, r, &pos);
+            if(ret == -1) {
+                protocol_status(sn, rq, 500, NULL);
+                return REQ_ABORTED;
+            } else if(ret == 0) {
+                
+            } else if(ret == 1) {
+                cgiheader = FALSE;
+                http_start_response(sn, rq);
+                if(pos < r) {
+                    net_write(sn->csd, buf+pos, r-pos);
+                }
+            }
+        } else {
+            net_write(sn->csd, buf, r);
+        }
+    }
+    
+    return REQ_PROCEED;
+}
+
+int cgi_start(CGIProcess *p, char *path, char *const argv[], char *const envp[]) {
+    if(pipe(p->in) || pipe(p->out)) {
+        log_ereport(
+                LOG_FAILURE,
+                "send-cgi: cannot create pipe: %s",
+                strerror(errno));
+        return REQ_ABORTED;
+    }
+    
+    p->pid = fork();
+    if(p->pid == 0) {
+        // child
+        if(dup2(p->in[0], STDIN_FILENO) == -1) {
+            perror("cgi_start: dup2");
+            exit(EXIT_FAILURE);
+        }
+        if(dup2(p->out[1], STDOUT_FILENO) == -1) {
+            perror("cgi_start: dup2");
+            exit(EXIT_FAILURE);
+        }
+        
+        // we need to close this unused pipe
+        // otherwise stdin cannot reach EOF
+        close(p->in[1]);
+        
+        // execute program
+        exit(execve(path, argv, envp));
+    } else {
+        // parent  
+        close(p->out[1]);
+    }
+    
+    return REQ_PROCEED;
+}
+
+CGIResponseParser* cgi_parser_new(Session *sn, Request *rq) {
+    CGIResponseParser* parser = pool_malloc(sn->pool, sizeof(CGIResponseParser));
+    parser->sn = sn;
+    parser->rq = rq;
+    parser->tmp = ucx_buffer_new(NULL, 64, UCX_BUFFER_AUTOEXTEND);
+    return parser;
+}
+
+/*
+ * parses a cgi response line and adds the response header to rq->srvhdrs
+ * returns 0: incomplete line
+ *         1: successfully parsed lines
+ *         2: cgi response header complete (empty line)
+ *        -1: error
+ */
+static int parse_lines(CGIResponseParser *parser, char *buf, size_t len, int *pos) {
+    sstr_t name;
+    sstr_t value;
+    WSBool space = TRUE;
+    int i;
+    
+    int line_begin = 0;
+    int value_begin = 0;
+    for(i=0;i<len;i++) {
+        char c = buf[i];
+        if(value_begin == line_begin && c == ':') {
+            name = sstrn(buf + line_begin, i - line_begin);
+            value_begin = i + 1;
+        } else if(c == '\n') {
+            if(value_begin == line_begin) {
+                if(space) {
+                    *pos = i + 1;
+                    return 2;
+                } else {
+                    // line ends with content but without ':' -> error
+                    return -1;
+                }
+            }
+            value = sstrn(buf + value_begin, i - value_begin);
+            
+            name = sstrtrim(name);
+            value = sstrtrim(value);
+            
+            if(name.length == 0 || value.length == 0) {
+                return -1;
+            }
+            pblock_nvlinsert(
+                    name.ptr,
+                    name.length,
+                    value.ptr,
+                    value.length,
+                    parser->rq->srvhdrs);
+            
+            line_begin = i+1;
+            value_begin = line_begin;
+            space = TRUE;
+        } else if(c != ' ') {
+            space = FALSE;
+        }
+    }
+    
+    if(i < len) {
+        *pos = i;
+        return 0;
+    }
+    return 1;
+}
+
+/*
+ * returns -1: error
+ *          0: response header incomplete
+ *          1: complete
+ */
+int cgi_parse_response(CGIResponseParser *parser, char *buf, size_t len, size_t *bpos) {
+    *bpos = 0;
+    int pos = 0;
+    if(parser->tmp->pos > 0) {
+        // the tmp buffer contains an unfinished line
+        // fill up the buffer until the line is complete
+        WSBool nb = FALSE;
+        for(pos=0;pos<len;pos++) {
+            if(buf[pos] == '\n') {
+                nb = TRUE;
+                break;
+            }
+        }
+        ucx_buffer_write(buf, 1, pos, parser->tmp);
+        
+        if(nb) {
+            // line complete
+            int npos;
+            int r = parse_lines(parser, parser->tmp->space, parser->tmp->pos, &npos);
+            switch(r) {
+                case -1: return -1;
+                case 0: return -1;
+                case 1: break;
+                case 2: {
+                    *bpos = pos + 1;
+                    return 1;
+                }
+            }
+            // reset tmp buffer
+            parser->tmp->pos = 0;
+        } else {
+            if(parser->tmp->pos > CGI_RESPONSE_MAX_LINE_LENGTH) {
+                return -1;
+            }
+        }
+    }
+    
+    int npos = 0;
+    int r = parse_lines(parser, buf + pos, len - pos, &npos);
+    switch(r) {
+        default: return -1;
+        case 0:
+        case 1: {
+            int newlen = len - npos;
+            if(npos > 0) {
+                ucx_buffer_write(buf + npos, 1, newlen, parser->tmp);
+            }
+            return 0;
+        }
+        case 2: {
+            *bpos = pos + npos;
+            return 1;
+        }
+    }
+}
+

mercurial