From 31f0409bf93a82ee6e59d79ff19e2bf5b623d474 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka <valenok@gmail.com> Date: Thu, 5 Dec 2013 15:03:38 +0000 Subject: [PATCH] Added CGI endpoint --- build/src/core.c | 405 ++++++++++++++++++++++++++++++++--------------- build/src/core.h | 17 +- 2 files changed, 286 insertions(+), 136 deletions(-) diff --git a/build/src/core.c b/build/src/core.c index 6a6fb0708..41a42ac7b 100644 --- a/build/src/core.c +++ b/build/src/core.c @@ -115,6 +115,10 @@ struct linked_list_link { struct linked_list_link *prev, *next; }; #define IOBUF_SIZE 8192 #define MAX_PATH_SIZE 8192 #define LUA_SCRIPT_PATTERN "mg_*.lua$" +#define CGI_ENVIRONMENT_SIZE 4096 +#define MAX_CGI_ENVIR_VARS 64 +#define ENV_EXPORT_TO_CGI "MONGOOSE_CGI" +#define MONGOOSE_VERSION "5.0" // Extra HTTP headers to send in every static file reply #if !defined(EXTRA_HTTP_HEADERS) @@ -198,12 +202,13 @@ struct iobuf { union endpoint { int fd; // Opened regular local file + sock_t cgi_sock; // CGI socket void *ssl; // SSL descriptor struct uri_handler *uh; // URI handler user function }; -enum endpoint_type { EP_NONE, EP_FILE, EP_USER }; -enum connection_flags { CONN_CLOSE = 1, CONN_SPOOL_DONE = 2 }; +enum endpoint_type { EP_NONE, EP_FILE, EP_CGI, EP_USER }; +enum connection_flags { CONN_CLOSE = 1, CONN_SPOOL_DONE = 2, CONN_SSL = 4 }; struct connection { struct mg_connection mg_conn; // XXX: Must be first @@ -417,8 +422,10 @@ static int mg_socketpair(sock_t sp[2]) { return ret; } +#ifndef NO_CGI #ifdef _WIN32 -static pid_t start_process(char *cmd, char *env[], char *dir, sock_t sock) { +static pid_t start_process(const char *interp, const char *cmd, const char *env, + const char *envp[], const char *dir, sock_t sock) { STARTUPINFOA si = {0}; PROCESS_INFORMATION pi = {0}; HANDLE hs = (HANDLE) sock, me = GetCurrentProcess(); @@ -433,7 +440,7 @@ static pid_t start_process(char *cmd, char *env[], char *dir, sock_t sock) { closesocket(sock); DBG(("Starting commad: [%s]", cmd)); - CreateProcess(NULL, cmd, NULL, NULL, TRUE, + CreateProcess(NULL, (char *) cmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, (void *) env, dir, &si, &pi); CloseHandle(si.hStdOutput); @@ -443,8 +450,222 @@ static pid_t start_process(char *cmd, char *env[], char *dir, sock_t sock) { return (pid_t) pi.hProcess; } #else +static pid_t start_process(const char *interp, const char *cmd, const char *env, + const char *envp[], const char *dir, sock_t sock) { + pid_t pid = fork(); + (void) env; + + if (pid == 0) { + chdir(dir); + dup2(sock, 0); + dup2(sock, 1); + closesocket(sock); + + // After exec, all signal handlers are restored to their default values, + // with one exception of SIGCHLD. According to POSIX.1-2001 and Linux's + // implementation, SIGCHLD's handler will leave unchanged after exec + // if it was set to be ignored. Restore it to default action. + signal(SIGCHLD, SIG_DFL); + + if (interp == NULL) { + execle(cmd, cmd, NULL, envp); + } else { + execle(interp, interp, cmd, NULL, envp); + } + exit(EXIT_FAILURE); // exec call failed + } + + return pid; +} #endif +// This structure helps to create an environment for the spawned CGI program. +// Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings, +// last element must be NULL. +// However, on Windows there is a requirement that all these VARIABLE=VALUE\0 +// strings must reside in a contiguous buffer. The end of the buffer is +// marked by two '\0' characters. +// We satisfy both worlds: we create an envp array (which is vars), all +// entries are actually pointers inside buf. +struct cgi_env_block { + struct mg_connection *conn; + char buf[CGI_ENVIRONMENT_SIZE]; // Environment buffer + const char *vars[MAX_CGI_ENVIR_VARS]; // char *envp[] + int len; // Space taken + int nvars; // Number of variables in envp[] +}; + +// Append VARIABLE=VALUE\0 string to the buffer, and add a respective +// pointer into the vars array. +static char *addenv(struct cgi_env_block *block, const char *fmt, ...) { + int n, space; + char *added; + va_list ap; + + // Calculate how much space is left in the buffer + space = sizeof(block->buf) - block->len - 2; + assert(space >= 0); + + // Make a pointer to the free space int the buffer + added = block->buf + block->len; + + // Copy VARIABLE=VALUE\0 string into the free space + va_start(ap, fmt); + n = vsnprintf(added, (size_t) space, fmt, ap); + va_end(ap); + + // Make sure we do not overflow buffer and the envp array + if (n > 0 && n + 1 < space && + block->nvars < (int) ARRAY_SIZE(block->vars) - 2) { + // Append a pointer to the added string into the envp array + block->vars[block->nvars++] = added; + // Bump up used length counter. Include \0 terminator + block->len += n + 1; + } + + return added; +} + +static void prepare_cgi_environment(struct connection *conn, + const char *prog, + struct cgi_env_block *blk) { + struct mg_connection *ri = &conn->mg_conn; + const char *s, *slash; + char *p, **opts = conn->server->config_options; + int i; + + blk->len = blk->nvars = 0; + blk->conn = ri; + + addenv(blk, "SERVER_NAME=%s", opts[AUTH_DOMAIN]); + addenv(blk, "SERVER_ROOT=%s", opts[DOCUMENT_ROOT]); + addenv(blk, "DOCUMENT_ROOT=%s", opts[DOCUMENT_ROOT]); + addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MONGOOSE_VERSION); + + // Prepare the environment block + addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); + addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); + addenv(blk, "%s", "REDIRECT_STATUS=200"); // For PHP + + // TODO(lsm): fix this for IPv6 case + addenv(blk, "SERVER_PORT=%d", ri->remote_port); + + addenv(blk, "REQUEST_METHOD=%s", ri->request_method); + addenv(blk, "REMOTE_ADDR=%s", ri->remote_ip); + addenv(blk, "REMOTE_PORT=%d", ri->remote_port); + addenv(blk, "REQUEST_URI=%s%s%s", ri->uri, + ri->query_string == NULL ? "" : "?", + ri->query_string == NULL ? "" : ri->query_string); + + // SCRIPT_NAME + if (conn->path_info != NULL) { + addenv(blk, "SCRIPT_NAME=%.*s", + (int) (strlen(ri->uri) - strlen(conn->path_info)), ri->uri); + addenv(blk, "PATH_INFO=%s", conn->path_info); + } else { + s = strrchr(prog, '/'); + slash = strrchr(ri->uri, '/'); + addenv(blk, "SCRIPT_NAME=%.*s%s", + slash == NULL ? 0 : (int) (slash - ri->uri), ri->uri, + s == NULL ? prog : s); + } + + addenv(blk, "SCRIPT_FILENAME=%s", prog); + addenv(blk, "PATH_TRANSLATED=%s", prog); + addenv(blk, "HTTPS=%s", conn->flags & CONN_SSL ? "on" : "off"); + + if ((s = mg_get_header(ri, "Content-Type")) != NULL) + addenv(blk, "CONTENT_TYPE=%s", s); + + if (ri->query_string != NULL) { + addenv(blk, "QUERY_STRING=%s", ri->query_string); + } + + if ((s = mg_get_header(ri, "Content-Length")) != NULL) + addenv(blk, "CONTENT_LENGTH=%s", s); + + if ((s = getenv("PATH")) != NULL) + addenv(blk, "PATH=%s", s); + +#if defined(_WIN32) + if ((s = getenv("COMSPEC")) != NULL) { + addenv(blk, "COMSPEC=%s", s); + } + if ((s = getenv("SYSTEMROOT")) != NULL) { + addenv(blk, "SYSTEMROOT=%s", s); + } + if ((s = getenv("SystemDrive")) != NULL) { + addenv(blk, "SystemDrive=%s", s); + } + if ((s = getenv("ProgramFiles")) != NULL) { + addenv(blk, "ProgramFiles=%s", s); + } + if ((s = getenv("ProgramFiles(x86)")) != NULL) { + addenv(blk, "ProgramFiles(x86)=%s", s); + } + if ((s = getenv("CommonProgramFiles(x86)")) != NULL) { + addenv(blk, "CommonProgramFiles(x86)=%s", s); + } +#else + if ((s = getenv("LD_LIBRARY_PATH")) != NULL) + addenv(blk, "LD_LIBRARY_PATH=%s", s); +#endif // _WIN32 + + if ((s = getenv("PERLLIB")) != NULL) + addenv(blk, "PERLLIB=%s", s); + + if ((s = getenv(ENV_EXPORT_TO_CGI)) != NULL) + addenv(blk, "%s=%s", ENV_EXPORT_TO_CGI, s); + + // Add all headers as HTTP_* variables + for (i = 0; i < ri->num_headers; i++) { + p = addenv(blk, "HTTP_%s=%s", + ri->http_headers[i].name, ri->http_headers[i].value); + + // Convert variable name into uppercase, and change - to _ + for (; *p != '=' && *p != '\0'; p++) { + if (*p == '-') + *p = '_'; + *p = (char) toupper(* (unsigned char *) p); + } + } + + blk->vars[blk->nvars++] = NULL; + blk->buf[blk->len++] = '\0'; + + assert(blk->nvars < (int) ARRAY_SIZE(blk->vars)); + assert(blk->len > 0); + assert(blk->len < (int) sizeof(blk->buf)); +} + +static void open_cgi_endpoint(struct connection *conn, const char *prog) { + struct cgi_env_block blk; + char dir[MAX_PATH_SIZE], *p = NULL; + sock_t fds[2]; + + prepare_cgi_environment(conn, prog, &blk); + // CGI must be executed in its own directory. 'dir' must point to the + // directory containing executable program, 'p' must point to the + // executable program name relative to 'dir'. + snprintf(dir, sizeof(dir), "%s", prog); + if ((p = strrchr(dir, '/')) != NULL) { + *p++ = '\0'; + } else { + dir[0] = '.', dir[1] = '\0'; + p = (char *) prog; + } + + mg_socketpair(fds); + start_process(conn->server->config_options[CGI_INTERPRETER], + prog, blk.buf, blk.vars, dir, fds[1]); + + conn->endpoint_type = EP_CGI; + conn->endpoint.cgi_sock = fds[0]; + closesocket(fds[1]); +} + +#endif // !NO_CGI + // 'sa' must be an initialized address to bind to static sock_t open_listening_socket(union socket_address *sa) { sock_t on = 1, sock = INVALID_SOCKET; @@ -1095,6 +1316,7 @@ static int deliver_websocket_frame(struct connection *conn) { if (buffered) { conn->mg_conn.content_len = data_len; conn->mg_conn.content = buf + header_len; + conn->mg_conn.wsbits = buf[0]; // Apply mask if necessary if (mask_len > 0) { @@ -1114,100 +1336,6 @@ static int deliver_websocket_frame(struct connection *conn) { return buffered; } -#if 0 -int mg_websocket_read(struct mg_connection *conn, int *bits, char **data) { - // Pointer to the beginning of the portion of the incoming websocket message - // queue. The original websocket upgrade request is never removed, - // so the queue begins after it. - unsigned char *buf = (unsigned char *) conn->buf + conn->request_len; - int n, stop = 0; - size_t i, len, mask_len, data_len, header_len, body_len; - char mask[4]; - - assert(conn->content_len == 0); - - // Loop continuously, reading messages from the socket, invoking the callback, - // and waiting repeatedly until an error occurs. - while (!stop) { - header_len = 0; - // body_len is the length of the entire queue in bytes - // len is the length of the current message - // data_len is the length of the current message's data payload - // header_len is the length of the current message's header - if ((body_len = conn->data_len - conn->request_len) >= 2) { - len = buf[1] & 127; - mask_len = buf[1] & 128 ? 4 : 0; - if (len < 126 && body_len >= mask_len) { - data_len = len; - header_len = 2 + mask_len; - } else if (len == 126 && body_len >= 4 + mask_len) { - header_len = 4 + mask_len; - data_len = ((((int) buf[2]) << 8) + buf[3]); - } else if (body_len >= 10 + mask_len) { - header_len = 10 + mask_len; - data_len = (((uint64_t) htonl(* (uint32_t *) &buf[2])) << 32) + - htonl(* (uint32_t *) &buf[6]); - } - } - - // Data layout is as follows: - // conn->buf buf - // v v frame1 | frame2 - // |---------------------|----------------|--------------|------- - // | |<--header_len-->|<--data_len-->| - // |<-conn->request_len->|<-----body_len----------->| - // |<-------------------conn->data_len------------->| - - if (header_len > 0) { - // Allocate space to hold websocket payload - if ((*data = malloc(data_len)) == NULL) { - // Allocation failed, exit the loop and then close the connection - // TODO: notify user about the failure - data_len = 0; - break; - } - - // Save mask and bits, otherwise it may be clobbered by memmove below - *bits = buf[0]; - memcpy(mask, buf + header_len - mask_len, mask_len); - - // Read frame payload into the allocated buffer. - assert(body_len >= header_len); - if (data_len + header_len > body_len) { - len = body_len - header_len; - memcpy(*data, buf + header_len, len); - // TODO: handle pull error - pull_all(NULL, conn, *data + len, data_len - len); - conn->data_len = conn->request_len; - } else { - len = data_len + header_len; - memcpy(*data, buf + header_len, data_len); - memmove(buf, buf + len, body_len - len); - conn->data_len -= len; - } - - // Apply mask if necessary - if (mask_len > 0) { - for (i = 0; i < data_len; i++) { - (*data)[i] ^= mask[i % 4]; - } - } - - return data_len; - } else { - // Buffering websocket request - if ((n = pull(NULL, conn, conn->buf + conn->data_len, - conn->buf_size - conn->data_len)) <= 0) { - break; - } - conn->data_len += n; - } - } - - return 0; -} -#endif - int mg_websocket_write(struct mg_connection* conn, int opcode, const char *data, size_t data_len) { unsigned char *copy; @@ -1229,14 +1357,14 @@ int mg_websocket_write(struct mg_connection* conn, int opcode, } else if (data_len <= 0xFFFF) { // 16-bit length field copy[1] = 126; - * (uint16_t *) (copy + 2) = htons(data_len); + * (uint16_t *) (copy + 2) = (uint16_t) htons((uint16_t) data_len); memcpy(copy + 4, data, data_len); copy_len = 4 + data_len; } else { // 64-bit length field copy[1] = 127; - * (uint32_t *) (copy + 2) = htonl((uint64_t) data_len >> 32); - * (uint32_t *) (copy + 6) = htonl(data_len & 0xffffffff); + * (uint32_t *) (copy + 2) = (uint32_t) htonl(data_len >> 32); + * (uint32_t *) (copy + 6) = (uint32_t) htonl(data_len & 0xffffffff); memcpy(copy + 10, data, data_len); copy_len = 10 + data_len; } @@ -1248,11 +1376,14 @@ int mg_websocket_write(struct mg_connection* conn, int opcode, return retval; } -#else -static void send_websocket_handshake(struct mg_connection *conn, - const char *key) { - (void) key; - send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n"); + +static void send_websocket_handshake_if_requested(struct mg_connection *conn) { + const char *ver = mg_get_header(conn, "Sec-WebSocket-Version"), + *key = mg_get_header(conn, "Sec-WebSocket-Key"); + if (ver != NULL && key != NULL) { + conn->is_websocket = 1; + send_websocket_handshake(conn, key); + } } #endif // !USE_WEBSOCKET @@ -1521,9 +1652,12 @@ static void call_uri_handler_if_data_is_buffered(struct connection *conn) { struct mg_connection *c = &conn->mg_conn; c->content = loc->buf; +#ifdef USE_WEBSOCKET if (conn->mg_conn.is_websocket) { do { } while (deliver_websocket_frame(conn)); - } else if (loc->len >= c->content_len) { + } else +#endif + if (loc->len >= c->content_len) { conn->endpoint.uh->handler(c); close_local_endpoint(conn); } @@ -1534,6 +1668,7 @@ static void open_local_endpoint(struct connection *conn) { file_stat_t st; int exists = 0, is_directory = 0; const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length"); + const char *cgi_pat = conn->server->config_options[CGI_PATTERN]; conn->mg_conn.content_len = cl_hdr == NULL ? 0 : to64(cl_hdr); @@ -1556,6 +1691,18 @@ static void open_local_endpoint(struct connection *conn) { } else if (match_prefix(LUA_SCRIPT_PATTERN, 6, path) > 0) { send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n"); conn->flags |= CONN_SPOOL_DONE; + } else if (match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) { + if (strcmp(conn->mg_conn.request_method, "POST") && + strcmp(conn->mg_conn.request_method, "HEAD") && + strcmp(conn->mg_conn.request_method, "GET")) { + send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n"); + } else { +#if !defined(NO_CGI) + open_cgi_endpoint(conn, path); +#else + send_http_error(conn, "%s", "HTTP/1.1 501 Not Implemented\r\n\r\n"); +#endif // !NO_CGI + } } else if (is_not_modified(conn, &st)) { send_http_error(conn, "%s", "HTTP/1.1 304 Not Modified\r\n\r\n"); } else if ((conn->endpoint.fd = open(path, O_RDONLY)) != -1) { @@ -1574,15 +1721,6 @@ static void send_continue_if_expected(struct connection *conn) { } } -static void send_websocket_handshake_if_requested(struct mg_connection *conn) { - const char *ver = mg_get_header(conn, "Sec-WebSocket-Version"), - *key = mg_get_header(conn, "Sec-WebSocket-Key"); - if (ver != NULL && key != NULL) { - conn->is_websocket = 1; - send_websocket_handshake(conn, key); - } -} - static void process_request(struct connection *conn) { struct iobuf *io = &conn->local_iobuf; @@ -1606,7 +1744,9 @@ static void process_request(struct connection *conn) { // Invalid request, or request is too big: close the connection conn->flags |= CONN_CLOSE; } else if (conn->request_len > 0 && conn->endpoint_type == EP_NONE) { +#ifdef USE_WEBSOCKET send_websocket_handshake_if_requested(&conn->mg_conn); +#endif send_continue_if_expected(conn); open_local_endpoint(conn); } else if (conn->endpoint_type == EP_USER) { @@ -1626,6 +1766,18 @@ static void read_from_client(struct connection *conn) { } } +static void read_from_cgi(struct connection *conn) { + char buf[IOBUF_SIZE]; + int n = recv(conn->endpoint.cgi_sock, buf, sizeof(buf), 0); + + DBG(("-> %d", n)); + if (is_error(n)) { + close_local_endpoint(conn); + } else if (n > 0) { + spool(&conn->remote_iobuf, buf, n); + } +} + static int should_keep_alive(const struct connection *conn) { const char *method = conn->mg_conn.request_method; const char *http_version = conn->mg_conn.http_version; @@ -1636,16 +1788,13 @@ static int should_keep_alive(const struct connection *conn) { } static void close_local_endpoint(struct connection *conn) { - int keep_alive = should_keep_alive(conn); // Must be done before free() - - //DBG(("Closing for conn %p", conn)); + // Must be done before free() + int keep_alive = should_keep_alive(conn) && conn->endpoint_type == EP_FILE; - // Close file descriptor switch (conn->endpoint_type) { case EP_FILE: close(conn->endpoint.fd); break; - case EP_USER: break; - case EP_NONE: break; - default: assert(0); break; + case EP_CGI: closesocket(conn->endpoint.cgi_sock); break; + default: break; } conn->endpoint_type = EP_NONE; @@ -1699,6 +1848,8 @@ void mg_poll_server(struct mg_server *server, int milliseconds) { add_to_set(conn->client_sock, &read_set, &max_fd); if (conn->endpoint_type == EP_FILE) { transfer_file_data(conn); + } else if (conn->endpoint_type == EP_CGI) { + add_to_set(conn->endpoint.cgi_sock, &read_set, &max_fd); } if (conn->remote_iobuf.len > 0) { add_to_set(conn->client_sock, &write_set, &max_fd); @@ -1732,6 +1883,10 @@ void mg_poll_server(struct mg_server *server, int milliseconds) { conn->expire_time = expire_time; read_from_client(conn); } + if (conn->endpoint_type == EP_CGI && + FD_ISSET(conn->endpoint.cgi_sock, &read_set)) { + read_from_cgi(conn); + } if (FD_ISSET(conn->client_sock, &write_set)) { conn->expire_time = expire_time; write_to_client(conn); @@ -1893,11 +2048,11 @@ static int handler(struct mg_connection *conn) { return conn->content_len == 4 && !memcmp(conn->content, "exit", 4); } -int main(void) { +int main(int argc, char *argv[]) { struct mg_server *server = mg_create_server(NULL); mg_set_option(server, "listening_port", "8080"); - mg_set_option(server, "document_root", "."); + mg_set_option(server, "document_root", argc > 1 ? argv[1] : "."); mg_add_uri_handler(server, "/ws", handler); mg_start_thread(timer_thread, server); diff --git a/build/src/core.h b/build/src/core.h index aa24701f5..677b9defc 100644 --- a/build/src/core.h +++ b/build/src/core.h @@ -34,19 +34,20 @@ struct mg_connection { const char *http_version; // E.g. "1.0", "1.1" const char *query_string; // URL part after '?', not including '?', or NULL - long remote_ip; // Client's IP address + char remote_ip[48]; // Max IPv6 string length is 45 characters int remote_port; // Client's port int num_headers; // Number of HTTP headers struct mg_header { const char *name; // HTTP header name const char *value; // HTTP header value - } http_headers[64]; // Maximum 64 headers + } http_headers[30]; char *content; // POST (or websocket message) data, or NULL int content_len; // content length int is_websocket; // Connection is a websocket connection + unsigned char wsbits; // First byte of the websocket frame void *server_param; // Parameter passed to mg_add_uri_handler() void *connection_param; // Placeholder for connection-specific data }; @@ -68,29 +69,23 @@ void mg_set_log_handler(struct mg_server*, int (*)(struct mg_connection*, int)); const char **mg_get_valid_option_names(void); const char *mg_get_option(const struct mg_server *server, const char *name); -// Websocket functions -//void mg_websocket_handshake(struct mg_connection *); -//int mg_websocket_read(struct mg_connection *, int *bits, char **data); -//int mg_websocket_write(struct mg_connection* conn, int opcode, -// const char *data, size_t data_len); - // Connection management functions int mg_write(struct mg_connection *, const void *buf, int len); +const char *mg_get_header(const struct mg_connection *, const char *name); +const char *mg_get_mime_type(const char *file_name); #if 0 int mg_printf(struct mg_connection *, const char *fmt, ...); void mg_send_file(struct mg_connection *, const char *path); int mg_read(struct mg_connection *, void *buf, int len); -const char *mg_get_header(const struct mg_connection *, const char *name); int mg_get_var(const char *data, size_t data_len, const char *var_name, char *dst, size_t dst_len); int mg_get_cookie(const char *cookie, const char *var_name, char *buf, size_t buf_len); -const char *mg_get_mime_type(const char *file_name); +#endif // Utility functions int mg_start_thread(void *(*func)(void *), void *param); -#endif #ifdef __cplusplus } -- GitLab