cgi: allow time travelling headers to decide response code
authorAndy Green <andy@warmcat.com>
Thu, 18 May 2017 13:19:57 +0000 (21:19 +0800)
committerAndy Green <andy@warmcat.com>
Thu, 18 May 2017 13:19:57 +0000 (21:19 +0800)
https://github.com/warmcat/libwebsockets/issues/899

lib/libwebsockets.c
lib/libwebsockets.h
lib/private-libwebsockets.h
lib/server.c

index b8e8974..0e914f3 100755 (executable)
@@ -302,6 +302,8 @@ lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason)
                        }
                        pcgi = &(*pcgi)->cgi_list;
                }
+               if (wsi->cgi->headers_buf)
+                       lws_free_set_NULL(wsi->cgi->headers_buf);
                /* we have a cgi going, we must kill it */
                wsi->cgi->being_closed = 1;
                lws_cgi_kill(wsi);
@@ -2272,6 +2274,8 @@ lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len
                return -1;
        }
 
+       wsi->cgi->response_code = HTTP_STATUS_OK;
+
        cgi = wsi->cgi;
        cgi->wsi = wsi; /* set cgi's owning wsi */
 
@@ -2545,13 +2549,21 @@ bail1:
        return -1;
 }
 
+/* we have to parse out these headers in the CGI output */
+
+static const char * const significant_hdr[SIGNIFICANT_HDR_COUNT] = {
+       "content-length: ",
+       "location: ",
+       "status: ",
+};
+
 LWS_VISIBLE LWS_EXTERN int
 lws_cgi_write_split_stdout_headers(struct lws *wsi)
 {
-       int n, m, match = 0, lp = 0;
-       static const char * const content_length = "content-length: ";
-       char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start,
-            *end = &buf[sizeof(buf) - 1 - LWS_PRE], c, l[12];
+       int n, m;
+       unsigned char buf[LWS_PRE + 1024], *start = &buf[LWS_PRE], *p = start,
+            *end = &buf[sizeof(buf) - 1 - LWS_PRE];
+       char c;
 
        if (!wsi->cgi)
                return -1;
@@ -2561,6 +2573,72 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi)
                 * payload chunks, since they need to be
                 * handled separately
                 */
+
+               switch (wsi->hdr_state) {
+
+               case LHCS_RESPONSE:
+                       lwsl_info("LHCS_RESPONSE: issuing response %d\n",
+                                       wsi->cgi->response_code);
+                       if (lws_add_http_header_status(wsi, wsi->cgi->response_code, &p, end))
+                               return 1;
+                       if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION,
+                                       (unsigned char *)"close", 5, &p, end))
+                               return 1;
+                       n = lws_write(wsi, start, p - start,
+                                     LWS_WRITE_HTTP_HEADERS);
+
+                       /* finalize cached headers before dumping them */
+                       if (lws_finalize_http_header(wsi,
+                                       (unsigned char **)&wsi->cgi->headers_pos,
+                                       (unsigned char *)wsi->cgi->headers_end))
+                               return -1;
+
+                       wsi->hdr_state = LHCS_DUMP_HEADERS;
+                       /* back to the loop for writeability again */
+                       return 0;
+
+               case LHCS_DUMP_HEADERS:
+
+                       n = wsi->cgi->headers_pos - wsi->cgi->headers_dumped;
+                       if (n > 512)
+                               n = 512;
+
+                       m = lws_write(wsi, (unsigned char *)wsi->cgi->headers_dumped,
+                                     n, LWS_WRITE_HTTP_HEADERS);
+                       if (m < 0) {
+                               lwsl_debug("%s: write says %d\n", __func__, m);
+                               return -1;
+                       }
+                       wsi->cgi->headers_dumped += n;
+                       if (wsi->cgi->headers_dumped == wsi->cgi->headers_pos) {
+                               wsi->hdr_state = LHCS_PAYLOAD;
+                               lws_free_set_NULL(wsi->cgi->headers_buf);
+                       }
+
+                       /* writeability becomes uncertain now we wrote
+                        * something, we must return to the event loop
+                        */
+                       return 0;
+               }
+
+               if (!wsi->cgi->headers_buf) {
+                       /* if we don't already have a headers buf, cook one up */
+                       n = 2048;
+                       wsi->cgi->headers_buf = malloc(n);
+                       if (!wsi->cgi->headers_buf) {
+                               lwsl_err("OOM\n");
+                               return -1;
+                       }
+                       wsi->cgi->headers_pos = wsi->cgi->headers_buf;
+                       wsi->cgi->headers_dumped = wsi->cgi->headers_pos;
+                       wsi->cgi->headers_end = wsi->cgi->headers_buf + n - 1;
+
+                       for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
+                               wsi->cgi->match[n] = 0;
+                               wsi->cgi->lp = 0;
+                       }
+               }
+
                n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]), &c, 1);
                if (n < 0) {
                        if (errno != EAGAIN) {
@@ -2569,37 +2647,67 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi)
                        }
                        else
                                n = 0;
+
+                       if (wsi->cgi->headers_pos >= wsi->cgi->headers_end - 4) {
+                               lwsl_notice("CGI headers larger than buffer size\n");
+
+                               return -1;
+                       }
                }
                if (n) {
-                       lwsl_debug("-- 0x%02X %c\n", (unsigned char)c, c);
+                       lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, wsi->cgi->match[1], wsi->hdr_state);
+                       if (!c)
+                               return -1;
                        switch (wsi->hdr_state) {
                        case LCHS_HEADER:
-                               if (!content_length[match] &&
-                                   (c >= '0' && c <= '9') &&
-                                   lp < sizeof(l) - 1) {
-                                       l[lp++] = c;
-                                       l[lp] = '\0';
-                                       wsi->cgi->content_length = atol(l);
+                               hdr:
+                               for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
+                                       /* significant headers with numeric decimal payloads */
+                                       if (!significant_hdr[n][wsi->cgi->match[n]] &&
+                                           (c >= '0' && c <= '9') &&
+                                           wsi->cgi->lp < sizeof(wsi->cgi->l) - 1) {
+                                               wsi->cgi->l[wsi->cgi->lp++] = c;
+                                               wsi->cgi->l[wsi->cgi->lp] = '\0';
+                                               switch (n) {
+                                               case SIGNIFICANT_HDR_CONTENT_LENGTH:
+                                                       wsi->cgi->content_length = atol(wsi->cgi->l);
+                                                       break;
+                                               case SIGNIFICANT_HDR_STATUS:
+                                                       wsi->cgi->response_code = atol(wsi->cgi->l);
+                                                       lwsl_debug("Status set to %d\n", wsi->cgi->response_code);
+                                                       break;
+                                               default:
+                                                       break;
+                                               }
+                                       }
+                                       /* hits up to the NUL are sticky until next hdr */
+                                       if (significant_hdr[n][wsi->cgi->match[n]]) {
+                                               if (tolower(c) == significant_hdr[n][wsi->cgi->match[n]])
+                                                       wsi->cgi->match[n]++;
+                                               else
+                                                       wsi->cgi->match[n] = 0;
+                                       }
                                }
-                               if (tolower(c) == content_length[match])
-                                       match++;
-                               else
-                                       match = 0;
 
                                /* some cgi only send us \x0a for EOL */
                                if (c == '\x0a') {
                                        wsi->hdr_state = LCHS_SINGLE_0A;
-                                       *p++ = '\x0d';
+                                       *wsi->cgi->headers_pos++ = '\x0d';
                                }
-                               *p++ = c;
-                               if (c == '\x0d') {
+                               *wsi->cgi->headers_pos++ = c;
+                               if (c == '\x0d')
                                        wsi->hdr_state = LCHS_LF1;
-                                       break;
+
+                               /* presence of Location: mandates 302 retcode */
+                               if (wsi->hdr_state != LCHS_HEADER &&
+                                   !significant_hdr[SIGNIFICANT_HDR_LOCATION][wsi->cgi->match[SIGNIFICANT_HDR_LOCATION]]) {
+                                       lwsl_debug("CGI: Location hdr seen\n");
+                                       wsi->cgi->response_code = 302;
                                }
 
                                break;
                        case LCHS_LF1:
-                               *p++ = c;
+                               *wsi->cgi->headers_pos++ = c;
                                if (c == '\x0a') {
                                        wsi->hdr_state = LCHS_CR2;
                                        break;
@@ -2615,28 +2723,29 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi)
                                        break;
                                }
                                wsi->hdr_state = LCHS_HEADER;
-                               match = 0;
-                               *p++ = c;
-                               break;
+                               for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
+                                       wsi->cgi->match[n] = 0;
+                               wsi->cgi->lp = 0;
+                               goto hdr;
+
                        case LCHS_LF2:
                        case LCHS_SINGLE_0A:
                                m = wsi->hdr_state;
                                if (c == '\x0a') {
                                        lwsl_debug("Content-Length: %ld\n", wsi->cgi->content_length);
-                                       wsi->hdr_state = LHCS_PAYLOAD;
+                                       wsi->hdr_state = LHCS_RESPONSE;
                                        /* drop the \0xa ... finalize will add it if needed */
-                                       if (lws_finalize_http_header(wsi,
-                                                       (unsigned char **)&p,
-                                                       (unsigned char *)end))
-                                               return -1;
                                        break;
                                }
                                if (m == LCHS_LF2)
                                        /* we got \r\n\r[^\n]... it's unreasonable */
                                        return -1;
                                /* we got \x0anext header, it's reasonable */
-                               *p++ = c;
+                               *wsi->cgi->headers_pos++ = c;
                                wsi->hdr_state = LCHS_HEADER;
+                               for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
+                                       wsi->cgi->match[n] = 0;
+                               wsi->cgi->lp = 0;
                                break;
                        case LHCS_PAYLOAD:
                                break;
@@ -2644,22 +2753,12 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi)
                }
 
                /* ran out of input, ended the headers, or filled up the headers buf */
-               if (!n || wsi->hdr_state == LHCS_PAYLOAD || (p + 4) == end) {
-
-                       m = lws_write(wsi, (unsigned char *)start,
-                                     p - start, LWS_WRITE_HTTP_HEADERS);
-                       if (m < 0) {
-                               lwsl_debug("%s: write says %d\n", __func__, m);
-                               return -1;
-                       }
-                       /* writeability becomes uncertain now we wrote
-                        * something, we must return to the event loop
-                        */
-
+               if (!n || wsi->hdr_state == LHCS_PAYLOAD)
                        return 0;
-               }
        }
 
+       /* payload processing */
+
        n = read(lws_get_socket_fd(wsi->cgi->stdwsi[LWS_STDOUT]),
                 start, sizeof(buf) - LWS_PRE);
 
index 185a569..860eeb1 100644 (file)
@@ -4399,6 +4399,8 @@ enum lws_cgi_hdr_state {
        LCHS_LF1,
        LCHS_CR2,
        LCHS_LF2,
+       LHCS_RESPONSE,
+       LHCS_DUMP_HEADERS,
        LHCS_PAYLOAD,
        LCHS_SINGLE_0A,
 };
index ad61015..78d1ceb 100644 (file)
@@ -1442,16 +1442,32 @@ struct _lws_websocket_related {
 
 #ifdef LWS_WITH_CGI
 
+enum {
+       SIGNIFICANT_HDR_CONTENT_LENGTH,
+       SIGNIFICANT_HDR_LOCATION,
+       SIGNIFICANT_HDR_STATUS,
+
+       SIGNIFICANT_HDR_COUNT
+};
+
 /* wsi who is master of the cgi points to an lws_cgi */
 
 struct lws_cgi {
        struct lws_cgi *cgi_list;
        struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */
        struct lws *wsi; /* owner */
+       unsigned char *headers_buf;
+       unsigned char *headers_pos;
+       unsigned char *headers_dumped;
+       unsigned char *headers_end;
        unsigned long content_length;
        unsigned long content_length_seen;
        int pipe_fds[3][2];
+       int match[SIGNIFICANT_HDR_COUNT];
        int pid;
+       int response_code;
+       int lp;
+       char l[12];
 
        unsigned int being_closed:1;
 
index acadcd6..db214bd 100644 (file)
@@ -1058,7 +1058,6 @@ lws_http_action(struct lws *wsi)
                        NULL, /* replace with cgi path */
                        NULL
                };
-               unsigned char *p, *end, buffer[1024];
 
                lwsl_debug("%s: cgi\n", __func__);
                cmd[0] = hit->origin;
@@ -1073,17 +1072,6 @@ lws_http_action(struct lws *wsi)
                        lwsl_err("%s: cgi failed\n", __func__);
                        return -1;
                }
-               p = buffer + LWS_PRE;
-               end = p + sizeof(buffer) - LWS_PRE;
-
-               if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
-                       return 1;
-               if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION,
-                               (unsigned char *)"close", 5, &p, end))
-                       return 1;
-               n = lws_write(wsi, buffer + LWS_PRE,
-                             p - (buffer + LWS_PRE),
-                             LWS_WRITE_HTTP_HEADERS);
 
                goto deal_body;
        }