diff --git a/mongoose.c b/mongoose.c index 42135a5cc6635c130aaf8fed604fb50104141e86..915608b894f68ba0e9ee78d176ba75f3d70cea67 100644 --- a/mongoose.c +++ b/mongoose.c @@ -47,7 +47,6 @@ #define NO_LIBC #define MG_DISABLE_FILESYSTEM #define MG_DISABLE_POPEN -#define MG_DISABLE_CGI #define MG_DISABLE_DIRECTORY_LISTING #define MG_DISABLE_SOCKETPAIR #define MG_DISABLE_PFS @@ -123,6 +122,15 @@ extern void *(*test_calloc)(size_t count, size_t size); #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif +#if !MG_DISABLE_HTTP && MG_ENABLE_CGI +MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog, + const struct mg_str *path_info, + const struct http_message *hm, + const struct mg_serve_http_opts *opts); +struct mg_http_proto_data_cgi; +MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d); +#endif + #endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */ #ifdef MG_MODULE_LINES #line 1 "common/cs_dbg.h" @@ -3806,11 +3814,6 @@ int mg_normalize_uri_path(const struct mg_str *in, struct mg_str *out) { #define MG_WS_NO_HOST_HEADER_MAGIC ((char *) 0x1) #endif -/* CGI requires socketpair. */ -#if MG_DISABLE_SOCKETPAIR && !MG_DISABLE_CGI -#define MG_DISABLE_CGI 1 -#endif - static const char *mg_version_header = "Mongoose/" MG_VERSION; enum mg_http_proto_data_type { DATA_NONE, DATA_FILE, DATA_PUT }; @@ -3823,9 +3826,11 @@ struct mg_http_proto_data_file { enum mg_http_proto_data_type type; }; +#if MG_ENABLE_CGI struct mg_http_proto_data_cgi { struct mg_connection *cgi_nc; }; +#endif struct mg_http_proto_data_chuncked { int64_t body_len; /* How many bytes of chunked body was reassembled. */ @@ -3863,7 +3868,7 @@ struct mg_http_proto_data { #if !MG_DISABLE_FILESYSTEM struct mg_http_proto_data_file file; #endif -#if !MG_DISABLE_CGI +#if MG_ENABLE_CGI struct mg_http_proto_data_cgi cgi; #endif #if MG_ENABLE_HTTP_STREAMING_MULTIPART @@ -3907,15 +3912,6 @@ static void mg_http_free_proto_data_file(struct mg_http_proto_data_file *d) { } #endif -#if !MG_DISABLE_CGI -static void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) { - if (d != NULL) { - if (d->cgi_nc != NULL) d->cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY; - memset(d, 0, sizeof(struct mg_http_proto_data_cgi)); - } -} -#endif - static void mg_http_free_proto_data_endpoints(struct mg_http_endpoint **ep) { struct mg_http_endpoint *current = *ep; @@ -3934,7 +3930,7 @@ static void mg_http_conn_destructor(void *proto_data) { #if !MG_DISABLE_FILESYSTEM mg_http_free_proto_data_file(&pd->file); #endif -#if !MG_DISABLE_CGI +#if MG_ENABLE_CGI mg_http_free_proto_data_cgi(&pd->cgi); #endif #if MG_ENABLE_HTTP_STREAMING_MULTIPART @@ -3944,24 +3940,6 @@ static void mg_http_conn_destructor(void *proto_data) { free(proto_data); } -/* - * 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 mg_cgi_env_block { - struct mg_connection *nc; - char buf[MG_CGI_ENVIRONMENT_SIZE]; /* Environment buffer */ - const char *vars[MG_MAX_CGI_ENVIR_VARS]; /* char *envp[] */ - int len; /* Space taken */ - int nvars; /* Number of variables in envp[] */ -}; - #if !MG_DISABLE_FILESYSTEM #define MIME_ENTRY(_ext, _type) \ @@ -4539,7 +4517,7 @@ static void mg_http_transfer_file_data(struct mg_connection *nc) { mg_http_free_proto_data_file(&pd->file); } } -#if !MG_DISABLE_CGI +#if MG_ENABLE_CGI else if (pd->cgi.cgi_nc != NULL) { /* This is POST data that needs to be forwarded to the CGI process */ if (pd->cgi.cgi_nc != NULL) { @@ -6684,912 +6662,946 @@ out: return ok; } -#if !MG_DISABLE_CGI -#ifdef _WIN32 -struct mg_threadparam { - sock_t s; - HANDLE hPipe; -}; +static int mg_get_month_index(const char *s) { + static const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + size_t i; -static int mg_wait_until_ready(sock_t sock, int for_read) { - fd_set set; - FD_ZERO(&set); - FD_SET(sock, &set); - return select(sock + 1, for_read ? &set : 0, for_read ? 0 : &set, 0, 0) == 1; -} + for (i = 0; i < ARRAY_SIZE(month_names); i++) + if (!strcmp(s, month_names[i])) return (int) i; -static void *mg_push_to_stdin(void *arg) { - struct mg_threadparam *tp = (struct mg_threadparam *) arg; - int n, sent, stop = 0; - DWORD k; - char buf[BUFSIZ]; + return -1; +} - while (!stop && mg_wait_until_ready(tp->s, 1) && - (n = recv(tp->s, buf, sizeof(buf), 0)) > 0) { - if (n == -1 && GetLastError() == WSAEWOULDBLOCK) continue; - for (sent = 0; !stop && sent < n; sent += k) { - if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop = 1; - } - } - DBG(("%s", "FORWARED EVERYTHING TO CGI")); - CloseHandle(tp->hPipe); - MG_FREE(tp); - _endthread(); - return NULL; +static int mg_num_leap_years(int year) { + return year / 4 - year / 100 + year / 400; } -static void *mg_pull_from_stdout(void *arg) { - struct mg_threadparam *tp = (struct mg_threadparam *) arg; - int k = 0, stop = 0; - DWORD n, sent; - char buf[BUFSIZ]; +/* Parse UTC date-time string, and return the corresponding time_t value. */ +MG_INTERNAL time_t mg_parse_date_string(const char *datetime) { + static const unsigned short days_before_month[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; + char month_str[32]; + int second, minute, hour, day, month, year, leap_days, days; + time_t result = (time_t) 0; - while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) { - for (sent = 0; !stop && sent < n; sent += k) { - if (mg_wait_until_ready(tp->s, 0) && - (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) - stop = 1; - } + if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", &day, month_str, &year, &hour, + &minute, &second) == 6) || + (sscanf(datetime, "%d %3s %d %d:%d:%d", &day, month_str, &year, &hour, + &minute, &second) == 6) || + (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", &day, month_str, &year, + &hour, &minute, &second) == 6) || + (sscanf(datetime, "%d-%3s-%d %d:%d:%d", &day, month_str, &year, &hour, + &minute, &second) == 6)) && + year > 1970 && (month = mg_get_month_index(month_str)) != -1) { + leap_days = mg_num_leap_years(year) - mg_num_leap_years(1970); + year -= 1970; + days = year * 365 + days_before_month[month] + (day - 1) + leap_days; + result = days * 24 * 3600 + hour * 3600 + minute * 60 + second; } - DBG(("%s", "EOF FROM CGI")); - CloseHandle(tp->hPipe); - shutdown(tp->s, 2); // Without this, IO thread may get truncated data - closesocket(tp->s); - MG_FREE(tp); - _endthread(); - return NULL; + + return result; } -static void mg_spawn_stdio_thread(sock_t sock, HANDLE hPipe, - void *(*func)(void *)) { - struct mg_threadparam *tp = (struct mg_threadparam *) MG_MALLOC(sizeof(*tp)); - if (tp != NULL) { - tp->s = sock; - tp->hPipe = hPipe; - mg_start_thread(func, tp); +MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st) { + struct mg_str *hdr; + if ((hdr = mg_get_http_header(hm, "If-None-Match")) != NULL) { + char etag[64]; + mg_http_construct_etag(etag, sizeof(etag), st); + return mg_vcasecmp(hdr, etag) == 0; + } else if ((hdr = mg_get_http_header(hm, "If-Modified-Since")) != NULL) { + return st->st_mtime <= mg_parse_date_string(hdr->p); + } else { + return 0; } } -static void mg_abs_path(const char *utf8_path, char *abs_path, size_t len) { - wchar_t buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE]; - to_wchar(utf8_path, buf, ARRAY_SIZE(buf)); - GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL); - WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0); +static void mg_http_send_digest_auth_request(struct mg_connection *c, + const char *domain) { + mg_printf(c, + "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Digest qop=\"auth\", " + "realm=\"%s\", nonce=\"%lu\"\r\n" + "Content-Length: 0\r\n\r\n", + domain, (unsigned long) time(NULL)); } -static int mg_start_process(const char *interp, const char *cmd, - const char *env, const char *envp[], - const char *dir, sock_t sock) { - STARTUPINFOW si; - PROCESS_INFORMATION pi; - HANDLE a[2], b[2], me = GetCurrentProcess(); - wchar_t wcmd[MAX_PATH_SIZE], full_dir[MAX_PATH_SIZE]; - char buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE], buf5[MAX_PATH_SIZE], - buf4[MAX_PATH_SIZE], cmdline[MAX_PATH_SIZE]; - DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS; - FILE *fp; +static void mg_http_send_options(struct mg_connection *nc) { + mg_printf(nc, "%s", + "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS" +#if !MG_DISABLE_DAV + ", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2" +#endif + "\r\n\r\n"); + nc->flags |= MG_F_SEND_AND_CLOSE; +} - memset(&si, 0, sizeof(si)); - memset(&pi, 0, sizeof(pi)); +static int mg_is_creation_request(const struct http_message *hm) { + return mg_vcmp(&hm->method, "MKCOL") == 0 || mg_vcmp(&hm->method, "PUT") == 0; +} - si.cb = sizeof(si); - si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; - si.wShowWindow = SW_HIDE; - si.hStdError = GetStdHandle(STD_ERROR_HANDLE); +MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path, + const struct mg_str *path_info, + struct http_message *hm, + struct mg_serve_http_opts *opts) { + int exists, is_directory, is_dav = mg_is_dav_request(&hm->method); + int is_cgi; + char *index_file = NULL; + cs_stat_t st; - CreatePipe(&a[0], &a[1], NULL, 0); - CreatePipe(&b[0], &b[1], NULL, 0); - DuplicateHandle(me, a[0], me, &si.hStdInput, 0, TRUE, flags); - DuplicateHandle(me, b[1], me, &si.hStdOutput, 0, TRUE, flags); + exists = (mg_stat(path, &st) == 0); + is_directory = exists && S_ISDIR(st.st_mode); - if (interp == NULL && (fp = fopen(cmd, "r")) != NULL) { - buf[0] = buf[1] = '\0'; - fgets(buf, sizeof(buf), fp); - buf[sizeof(buf) - 1] = '\0'; - if (buf[0] == '#' && buf[1] == '!') { - interp = buf + 2; - /* Trim leading spaces: https://github.com/cesanta/mongoose/issues/489 */ - while (*interp != '\0' && isspace(*(unsigned char *) interp)) { - interp++; - } - } - fclose(fp); - } + if (is_directory) + mg_find_index_file(path, opts->index_files, &index_file, &st); - snprintf(buf, sizeof(buf), "%s/%s", dir, cmd); - mg_abs_path(buf, buf2, ARRAY_SIZE(buf2)); + is_cgi = + (mg_match_prefix(opts->cgi_file_pattern, strlen(opts->cgi_file_pattern), + index_file ? index_file : path) > 0); - mg_abs_path(dir, buf5, ARRAY_SIZE(buf5)); - to_wchar(dir, full_dir, ARRAY_SIZE(full_dir)); + DBG(("%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s", nc, + (int) hm->method.len, hm->method.p, path, exists, is_directory, is_dav, + is_cgi, index_file ? index_file : "")); - if (interp != NULL) { - mg_abs_path(interp, buf4, ARRAY_SIZE(buf4)); - snprintf(cmdline, sizeof(cmdline), "%s \"%s\"", buf4, buf2); - } else { - snprintf(cmdline, sizeof(cmdline), "\"%s\"", buf2); + if (is_directory && hm->uri.p[hm->uri.len - 1] != '/' && !is_dav) { + mg_printf(nc, + "HTTP/1.1 301 Moved\r\nLocation: %.*s/\r\n" + "Content-Length: 0\r\n\r\n", + (int) hm->uri.len, hm->uri.p); + MG_FREE(index_file); + return; } - to_wchar(cmdline, wcmd, ARRAY_SIZE(wcmd)); - - if (CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, - (void *) env, full_dir, &si, &pi) != 0) { - mg_spawn_stdio_thread(sock, a[1], mg_push_to_stdin); - mg_spawn_stdio_thread(sock, b[0], mg_pull_from_stdout); - CloseHandle(si.hStdOutput); - CloseHandle(si.hStdInput); - - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - } else { - CloseHandle(a[1]); - CloseHandle(b[0]); - closesocket(sock); + /* If we have path_info, the only way to handle it is CGI. */ + if (path_info->len > 0 && !is_cgi) { + mg_http_send_error(nc, 501, NULL); + MG_FREE(index_file); + return; } - DBG(("CGI command: [%ls] -> %p", wcmd, pi.hProcess)); - /* Not closing a[0] and b[1] because we've used DUPLICATE_CLOSE_SOURCE */ - (void) envp; - return (pi.hProcess != NULL); -} + if (is_dav && opts->dav_document_root == NULL) { + mg_http_send_error(nc, 501, NULL); + } else if (!mg_is_authorized(hm, path, is_directory, opts->auth_domain, + opts->global_auth_file, 1) || + !mg_is_authorized(hm, path, is_directory, opts->auth_domain, + opts->per_directory_auth_file, 0)) { + mg_http_send_digest_auth_request(nc, opts->auth_domain); + } else if (is_cgi) { +#if MG_ENABLE_CGI + mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts); #else -static int mg_start_process(const char *interp, const char *cmd, - const char *env, const char *envp[], - const char *dir, sock_t sock) { - char buf[500]; - pid_t pid = fork(); - (void) env; - - if (pid == 0) { - /* - * In Linux `chdir` declared with `warn_unused_result` attribute - * To shutup compiler we have yo use result in some way - */ - int tmp = chdir(dir); - (void) tmp; - (void) dup2(sock, 0); - (void) 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, (char *) 0, envp); /* (char *) 0 to squash warning */ + mg_http_send_error(nc, 501, NULL); +#endif /* MG_DISABLE_CGI */ + } else if ((!exists || + mg_is_file_hidden(path, opts, 0 /* specials are ok */)) && + !mg_is_creation_request(hm)) { + mg_http_send_error(nc, 404, NULL); +#if !MG_DISABLE_DAV + } else if (!mg_vcmp(&hm->method, "PROPFIND")) { + mg_handle_propfind(nc, path, &st, hm, opts); +#if !MG_DISABLE_DAV_AUTH + } else if (is_dav && + (opts->dav_auth_file == NULL || + (strcmp(opts->dav_auth_file, "-") != 0 && + !mg_is_authorized(hm, path, is_directory, opts->auth_domain, + opts->dav_auth_file, 1)))) { + mg_http_send_digest_auth_request(nc, opts->auth_domain); +#endif + } else if (!mg_vcmp(&hm->method, "MKCOL")) { + mg_handle_mkcol(nc, path, hm); + } else if (!mg_vcmp(&hm->method, "DELETE")) { + mg_handle_delete(nc, opts, path); + } else if (!mg_vcmp(&hm->method, "PUT")) { + mg_handle_put(nc, path, hm); + } else if (!mg_vcmp(&hm->method, "MOVE")) { + mg_handle_move(nc, opts, path, hm); +#if MG_ENABLE_FAKE_DAVLOCK + } else if (!mg_vcmp(&hm->method, "LOCK")) { + mg_handle_lock(nc, path); +#endif +#endif + } else if (!mg_vcmp(&hm->method, "OPTIONS")) { + mg_http_send_options(nc); + } else if (is_directory && index_file == NULL) { +#if !MG_DISABLE_DIRECTORY_LISTING + if (strcmp(opts->enable_directory_listing, "yes") == 0) { + mg_send_directory_listing(nc, path, hm, opts); } else { - execle(interp, interp, cmd, (char *) 0, envp); + mg_http_send_error(nc, 403, NULL); } - snprintf(buf, sizeof(buf), - "Status: 500\r\n\r\n" - "500 Server Error: %s%s%s: %s", - interp == NULL ? "" : interp, interp == NULL ? "" : " ", cmd, - strerror(errno)); - send(1, buf, strlen(buf), 0); - exit(EXIT_FAILURE); /* exec call failed */ +#else + mg_http_send_error(nc, 501, NULL); +#endif + } else if (mg_is_not_modified(hm, &st)) { + mg_http_send_error(nc, 304, "Not Modified"); + } else { + mg_http_serve_file2(nc, index_file ? index_file : path, hm, opts); } - - return (pid != 0); + MG_FREE(index_file); } -#endif /* _WIN32 */ -/* - * Append VARIABLE=VALUE\0 string to the buffer, and add a respective - * pointer into the vars array. - */ -static char *mg_addenv(struct mg_cgi_env_block *block, const char *fmt, ...) { - int n, space; - char *added = block->buf + block->len; - va_list ap; +void mg_serve_http(struct mg_connection *nc, struct http_message *hm, + struct mg_serve_http_opts opts) { + char *path = NULL; + struct mg_str *hdr, path_info; + uint32_t remote_ip = ntohl(*(uint32_t *) &nc->sa.sin.sin_addr); - /* Calculate how much space is left in the buffer */ - space = sizeof(block->buf) - (block->len + 2); - if (space > 0) { - /* Copy VARIABLE=VALUE\0 string into the free space */ - va_start(ap, fmt); - n = vsnprintf(added, (size_t) space, fmt, ap); - va_end(ap); + if (mg_check_ip_acl(opts.ip_acl, remote_ip) != 1) { + /* Not allowed to connect */ + mg_http_send_error(nc, 403, NULL); + nc->flags |= MG_F_SEND_AND_CLOSE; + return; + } - /* 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; - } + if (mg_http_send_port_based_redirect(nc, hm, &opts)) { + return; } - return added; -} + if (opts.document_root == NULL) { + opts.document_root = "."; + } + if (opts.per_directory_auth_file == NULL) { + opts.per_directory_auth_file = ".htpasswd"; + } + if (opts.enable_directory_listing == NULL) { + opts.enable_directory_listing = "yes"; + } + if (opts.cgi_file_pattern == NULL) { + opts.cgi_file_pattern = "**.cgi$|**.php$"; + } + if (opts.ssi_pattern == NULL) { + opts.ssi_pattern = "**.shtml$|**.shtm$"; + } + if (opts.index_files == NULL) { + opts.index_files = "index.html,index.htm,index.shtml,index.cgi,index.php"; + } + /* Normalize path - resolve "." and ".." (in-place). */ + if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) { + mg_http_send_error(nc, 400, NULL); + return; + } + if (mg_uri_to_local_path(hm, &opts, &path, &path_info) == 0) { + mg_http_send_error(nc, 404, NULL); + return; + } + mg_send_http_file(nc, path, &path_info, hm, &opts); -static void mg_addenv2(struct mg_cgi_env_block *blk, const char *name) { - const char *s; - if ((s = getenv(name)) != NULL) mg_addenv(blk, "%s=%s", name, s); + MG_FREE(path); + path = NULL; + + /* Close connection for non-keep-alive requests */ + if (mg_vcmp(&hm->proto, "HTTP/1.1") != 0 || + ((hdr = mg_get_http_header(hm, "Connection")) != NULL && + mg_vcmp(hdr, "keep-alive") != 0)) { +#if 0 + nc->flags |= MG_F_SEND_AND_CLOSE; +#endif + } } -static void mg_prepare_cgi_environment(struct mg_connection *nc, - const char *prog, - const struct mg_str *path_info, - const struct http_message *hm, - const struct mg_serve_http_opts *opts, - struct mg_cgi_env_block *blk) { - const char *s; - struct mg_str *h; - char *p; - size_t i; - char buf[100]; +#endif /* MG_DISABLE_FILESYSTEM */ - blk->len = blk->nvars = 0; - blk->nc = nc; +/* returns 0 on success, -1 on error */ +static int mg_http_common_url_parse(const char *url, const char *schema, + const char *schema_tls, int *use_ssl, + char **addr, int *port_i, + const char **path) { + int addr_len = 0; - if ((s = getenv("SERVER_NAME")) != NULL) { - mg_addenv(blk, "SERVER_NAME=%s", s); + if (memcmp(url, schema, strlen(schema)) == 0) { + url += strlen(schema); + } else if (memcmp(url, schema_tls, strlen(schema_tls)) == 0) { + url += strlen(schema_tls); + *use_ssl = 1; +#if !MG_ENABLE_SSL + return -1; /* SSL is not enabled, cannot do HTTPS URLs */ +#endif + } + + while (*url != '\0') { + *addr = (char *) MG_REALLOC(*addr, addr_len + 6 /* space for port too. */); + if (*addr == NULL) { + DBG(("OOM")); + return -1; + } + if (*url == '/') { + break; + } + if (*url == ':') *port_i = addr_len; + (*addr)[addr_len++] = *url; + (*addr)[addr_len] = '\0'; + url++; + } + if (addr_len == 0) goto cleanup; + if (*port_i < 0) { + *port_i = addr_len; + strcpy(*addr + *port_i, *use_ssl ? ":443" : ":80"); } else { - mg_sock_to_str(nc->sock, buf, sizeof(buf), 3); - mg_addenv(blk, "SERVER_NAME=%s", buf); + *port_i = -1; } - mg_addenv(blk, "SERVER_ROOT=%s", opts->document_root); - mg_addenv(blk, "DOCUMENT_ROOT=%s", opts->document_root); - mg_addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MG_VERSION); - /* Prepare the environment block */ - mg_addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); - mg_addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); - mg_addenv(blk, "%s", "REDIRECT_STATUS=200"); /* For PHP */ + if (*path == NULL) *path = url; - mg_addenv(blk, "REQUEST_METHOD=%.*s", (int) hm->method.len, hm->method.p); + if (**path == '\0') *path = "/"; - mg_addenv(blk, "REQUEST_URI=%.*s%s%.*s", (int) hm->uri.len, hm->uri.p, - hm->query_string.len == 0 ? "" : "?", (int) hm->query_string.len, - hm->query_string.p); + DBG(("%s %s", *addr, *path)); - mg_conn_addr_to_str(nc, buf, sizeof(buf), - MG_SOCK_STRINGIFY_REMOTE | MG_SOCK_STRINGIFY_IP); - mg_addenv(blk, "REMOTE_ADDR=%s", buf); - mg_conn_addr_to_str(nc, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT); - mg_addenv(blk, "SERVER_PORT=%s", buf); + return 0; - s = hm->uri.p + hm->uri.len - path_info->len - 1; - if (*s == '/') { - const char *base_name = strrchr(prog, DIRSEP); - mg_addenv(blk, "SCRIPT_NAME=%.*s/%s", (int) (s - hm->uri.p), hm->uri.p, - (base_name != NULL ? base_name + 1 : prog)); - } else { - mg_addenv(blk, "SCRIPT_NAME=%.*s", (int) (s - hm->uri.p + 1), hm->uri.p); - } - mg_addenv(blk, "SCRIPT_FILENAME=%s", prog); +cleanup: + MG_FREE(*addr); + return -1; +} - if (path_info != NULL && path_info->len > 0) { - mg_addenv(blk, "PATH_INFO=%.*s", (int) path_info->len, path_info->p); - /* Not really translated... */ - mg_addenv(blk, "PATH_TRANSLATED=%.*s", (int) path_info->len, path_info->p); +struct mg_connection *mg_connect_http_base( + struct mg_mgr *mgr, mg_event_handler_t ev_handler, + struct mg_connect_opts opts, const char *schema, const char *schema_ssl, + const char *url, const char **path, char **addr) { + struct mg_connection *nc = NULL; + int port_i = -1; + int use_ssl = 0; + + if (mg_http_common_url_parse(url, schema, schema_ssl, &use_ssl, addr, &port_i, + path) < 0) { + return NULL; } + LOG(LL_DEBUG, ("%s use_ssl? %d", url, use_ssl)); + if (use_ssl) { #if MG_ENABLE_SSL - mg_addenv(blk, "HTTPS=%s", nc->ssl != NULL ? "on" : "off"); + /* + * Schema requires SSL, but no SSL parameters were provided in opts. + * In order to maintain backward compatibility, use a faux-SSL with no + * verification. + */ + if (opts.ssl_ca_cert == NULL) { + opts.ssl_ca_cert = "*"; + } #else - mg_addenv(blk, "HTTPS=off"); + MG_SET_PTRPTR(opts.error_string, "ssl is disabled"); + MG_FREE(addr); + return NULL; #endif - - if ((h = mg_get_http_header((struct http_message *) hm, "Content-Type")) != - NULL) { - mg_addenv(blk, "CONTENT_TYPE=%.*s", (int) h->len, h->p); } - if (hm->query_string.len > 0) { - mg_addenv(blk, "QUERY_STRING=%.*s", (int) hm->query_string.len, - hm->query_string.p); + if ((nc = mg_connect_opt(mgr, *addr, ev_handler, opts)) != NULL) { + mg_set_protocol_http_websocket(nc); + /* If the port was addred by us, restore the original host. */ + if (port_i >= 0) (*addr)[port_i] = '\0'; } - if ((h = mg_get_http_header((struct http_message *) hm, "Content-Length")) != - NULL) { - mg_addenv(blk, "CONTENT_LENGTH=%.*s", (int) h->len, h->p); - } + return nc; +} - mg_addenv2(blk, "PATH"); - mg_addenv2(blk, "TMP"); - mg_addenv2(blk, "TEMP"); - mg_addenv2(blk, "TMPDIR"); - mg_addenv2(blk, "PERLLIB"); - mg_addenv2(blk, MG_ENV_EXPORT_TO_CGI); +struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + struct mg_connect_opts opts, + const char *url, + const char *extra_headers, + const char *post_data) { + char *addr = NULL; + const char *path = NULL; + struct mg_connection *nc = mg_connect_http_base( + mgr, ev_handler, opts, "http://", "https://", url, &path, &addr); -#if defined(_WIN32) - mg_addenv2(blk, "COMSPEC"); - mg_addenv2(blk, "SYSTEMROOT"); - mg_addenv2(blk, "SystemDrive"); - mg_addenv2(blk, "ProgramFiles"); - mg_addenv2(blk, "ProgramFiles(x86)"); - mg_addenv2(blk, "CommonProgramFiles(x86)"); -#else - mg_addenv2(blk, "LD_LIBRARY_PATH"); -#endif /* _WIN32 */ + if (nc == NULL) { + return NULL; + } - /* Add all headers as HTTP_* variables */ - for (i = 0; hm->header_names[i].len > 0; i++) { - p = mg_addenv(blk, "HTTP_%.*s=%.*s", (int) hm->header_names[i].len, - hm->header_names[i].p, (int) hm->header_values[i].len, - hm->header_values[i].p); + mg_printf(nc, "%s %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %" SIZE_T_FMT + "\r\n%s\r\n%s", + post_data == NULL ? "GET" : "POST", path, addr, + post_data == NULL ? 0 : strlen(post_data), + extra_headers == NULL ? "" : extra_headers, + post_data == NULL ? "" : post_data); - /* Convert variable name into uppercase, and change - to _ */ - for (; *p != '=' && *p != '\0'; p++) { - if (*p == '-') *p = '_'; - *p = (char) toupper(*(unsigned char *) p); - } - } + MG_FREE(addr); + return nc; +} - blk->vars[blk->nvars++] = NULL; - blk->buf[blk->len++] = '\0'; +struct mg_connection *mg_connect_http(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + const char *url, + const char *extra_headers, + const char *post_data) { + struct mg_connect_opts opts; + memset(&opts, 0, sizeof(opts)); + return mg_connect_http_opt(mgr, ev_handler, opts, url, extra_headers, + post_data); } -static void mg_cgi_ev_handler(struct mg_connection *cgi_nc, int ev, - void *ev_data) { - struct mg_connection *nc = (struct mg_connection *) cgi_nc->user_data; - (void) ev_data; +#if !MG_DISABLE_HTTP_WEBSOCKET +struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + struct mg_connect_opts opts, + const char *url, const char *protocol, + const char *extra_headers) { + char *addr = NULL; + const char *path = NULL; + struct mg_connection *nc = mg_connect_http_base( + mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr); - if (nc == NULL) return; + if (nc != NULL) { + mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers); + } - switch (ev) { - case MG_EV_RECV: - /* - * CGI script does not output reply line, like "HTTP/1.1 CODE XXXXX\n" - * It outputs headers, then body. Headers might include "Status" - * header, which changes CODE, and it might include "Location" header - * which changes CODE to 302. - * - * Therefore we do not send the output from the CGI script to the user - * until all CGI headers are received. - * - * Here we parse the output from the CGI script, and if all headers has - * been received, send appropriate reply line, and forward all - * received headers to the client. - */ - if (nc->flags & MG_F_USER_1) { - struct mbuf *io = &cgi_nc->recv_mbuf; - int len = mg_http_get_request_len(io->buf, io->len); + MG_FREE(addr); + return nc; +} - if (len == 0) break; - if (len < 0 || io->len > MG_MAX_HTTP_REQUEST_SIZE) { - cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY; - mg_http_send_error(nc, 500, "Bad headers"); - } else { - struct http_message hm; - struct mg_str *h; - mg_http_parse_headers(io->buf, io->buf + io->len, io->len, &hm); - if (mg_get_http_header(&hm, "Location") != NULL) { - mg_printf(nc, "%s", "HTTP/1.1 302 Moved\r\n"); - } else if ((h = mg_get_http_header(&hm, "Status")) != NULL) { - mg_printf(nc, "HTTP/1.1 %.*s\r\n", (int) h->len, h->p); - } else { - mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\n"); - } - } - nc->flags &= ~MG_F_USER_1; - } - if (!(nc->flags & MG_F_USER_1)) { - mg_forward(cgi_nc, nc); - } - break; - case MG_EV_CLOSE: - mg_http_free_proto_data_cgi(&mg_http_get_proto_data(cgi_nc)->cgi); - nc->flags |= MG_F_SEND_AND_CLOSE; - break; - } +struct mg_connection *mg_connect_ws(struct mg_mgr *mgr, + mg_event_handler_t ev_handler, + const char *url, const char *protocol, + const char *extra_headers) { + struct mg_connect_opts opts; + memset(&opts, 0, sizeof(opts)); + return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers); } +#endif /* MG_DISABLE_HTTP_WEBSOCKET */ -static void mg_handle_cgi(struct mg_connection *nc, const char *prog, - const struct mg_str *path_info, - const struct http_message *hm, - const struct mg_serve_http_opts *opts) { - struct mg_cgi_env_block blk; - char dir[MAX_PATH_SIZE]; - const char *p; - sock_t fds[2]; +size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name, + size_t var_name_len, char *file_name, + size_t file_name_len, const char **data, + size_t *data_len) { + static const char cd[] = "Content-Disposition: "; + size_t hl, bl, n, ll, pos, cdl = sizeof(cd) - 1; - DBG(("%p [%s]", nc, prog)); - mg_prepare_cgi_environment(nc, prog, path_info, hm, opts, &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'. - */ - if ((p = strrchr(prog, DIRSEP)) == NULL) { - snprintf(dir, sizeof(dir), "%s", "."); - } else { - snprintf(dir, sizeof(dir), "%.*s", (int) (p - prog), prog); - prog = p + 1; - } + if (buf == NULL || buf_len <= 0) return 0; + if ((hl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0; + if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0; - /* - * Try to create socketpair in a loop until success. mg_socketpair() - * can be interrupted by a signal and fail. - * TODO(lsm): use sigaction to restart interrupted syscall - */ - do { - mg_socketpair(fds, SOCK_STREAM); - } while (fds[0] == INVALID_SOCKET); + /* Get boundary length */ + bl = mg_get_line_len(buf, buf_len); - if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir, - fds[1]) != 0) { - size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len); - struct mg_connection *cgi_nc = - mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler); - struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(cgi_nc); - cgi_pd->cgi.cgi_nc = cgi_nc; - cgi_pd->cgi.cgi_nc->user_data = nc; - nc->flags |= MG_F_USER_1; - /* Push POST data to the CGI */ - if (n > 0 && n < nc->recv_mbuf.len) { - mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n); + /* Loop through headers, fetch variable name and file name */ + var_name[0] = file_name[0] = '\0'; + for (n = bl; (ll = mg_get_line_len(buf + n, hl - n)) > 0; n += ll) { + if (mg_ncasecmp(cd, buf + n, cdl) == 0) { + struct mg_str header; + header.p = buf + n + cdl; + header.len = ll - (cdl + 2); + mg_http_parse_header(&header, "name", var_name, var_name_len); + mg_http_parse_header(&header, "filename", file_name, file_name_len); } - mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len); - } else { - closesocket(fds[0]); - mg_http_send_error(nc, 500, "CGI failure"); } -#ifndef _WIN32 - closesocket(fds[1]); /* On Windows, CGI stdio thread closes that socket */ -#endif + /* Scan through the body, search for terminating boundary */ + for (pos = hl; pos + (bl - 2) < buf_len; pos++) { + if (buf[pos] == '-' && !memcmp(buf, &buf[pos], bl - 2)) { + if (data_len != NULL) *data_len = (pos - 2) - hl; + if (data != NULL) *data = buf + hl; + return pos; + } + } + + return 0; +} + +void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path, + mg_event_handler_t handler) { + struct mg_http_proto_data *pd = NULL; + struct mg_http_endpoint *new_ep = NULL; + + if (nc == NULL) return; + new_ep = (struct mg_http_endpoint *) calloc(1, sizeof(*new_ep)); + if (new_ep == NULL) return; + + pd = mg_http_get_proto_data(nc); + new_ep->name = strdup(uri_path); + new_ep->name_len = strlen(new_ep->name); + new_ep->handler = handler; + new_ep->next = pd->endpoints; + pd->endpoints = new_ep; } + +#endif /* MG_DISABLE_HTTP */ +#ifdef MG_MODULE_LINES +#line 1 "mongoose/src/http_cgi.c" #endif +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ -static int mg_get_month_index(const char *s) { - static const char *month_names[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; - size_t i; +#if !MG_DISABLE_HTTP && MG_ENABLE_CGI - for (i = 0; i < ARRAY_SIZE(month_names); i++) - if (!strcmp(s, month_names[i])) return (int) i; +/* + * 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 mg_cgi_env_block { + struct mg_connection *nc; + char buf[MG_CGI_ENVIRONMENT_SIZE]; /* Environment buffer */ + const char *vars[MG_MAX_CGI_ENVIR_VARS]; /* char *envp[] */ + int len; /* Space taken */ + int nvars; /* Number of variables in envp[] */ +}; - return -1; -} +#ifdef _WIN32 +struct mg_threadparam { + sock_t s; + HANDLE hPipe; +}; -static int mg_num_leap_years(int year) { - return year / 4 - year / 100 + year / 400; +static int mg_wait_until_ready(sock_t sock, int for_read) { + fd_set set; + FD_ZERO(&set); + FD_SET(sock, &set); + return select(sock + 1, for_read ? &set : 0, for_read ? 0 : &set, 0, 0) == 1; } -/* Parse UTC date-time string, and return the corresponding time_t value. */ -MG_INTERNAL time_t mg_parse_date_string(const char *datetime) { - static const unsigned short days_before_month[] = { - 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; - char month_str[32]; - int second, minute, hour, day, month, year, leap_days, days; - time_t result = (time_t) 0; +static void *mg_push_to_stdin(void *arg) { + struct mg_threadparam *tp = (struct mg_threadparam *) arg; + int n, sent, stop = 0; + DWORD k; + char buf[BUFSIZ]; - if (((sscanf(datetime, "%d/%3s/%d %d:%d:%d", &day, month_str, &year, &hour, - &minute, &second) == 6) || - (sscanf(datetime, "%d %3s %d %d:%d:%d", &day, month_str, &year, &hour, - &minute, &second) == 6) || - (sscanf(datetime, "%*3s, %d %3s %d %d:%d:%d", &day, month_str, &year, - &hour, &minute, &second) == 6) || - (sscanf(datetime, "%d-%3s-%d %d:%d:%d", &day, month_str, &year, &hour, - &minute, &second) == 6)) && - year > 1970 && (month = mg_get_month_index(month_str)) != -1) { - leap_days = mg_num_leap_years(year) - mg_num_leap_years(1970); - year -= 1970; - days = year * 365 + days_before_month[month] + (day - 1) + leap_days; - result = days * 24 * 3600 + hour * 3600 + minute * 60 + second; + while (!stop && mg_wait_until_ready(tp->s, 1) && + (n = recv(tp->s, buf, sizeof(buf), 0)) > 0) { + if (n == -1 && GetLastError() == WSAEWOULDBLOCK) continue; + for (sent = 0; !stop && sent < n; sent += k) { + if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0)) stop = 1; + } } - - return result; + DBG(("%s", "FORWARED EVERYTHING TO CGI")); + CloseHandle(tp->hPipe); + MG_FREE(tp); + _endthread(); + return NULL; } -MG_INTERNAL int mg_is_not_modified(struct http_message *hm, cs_stat_t *st) { - struct mg_str *hdr; - if ((hdr = mg_get_http_header(hm, "If-None-Match")) != NULL) { - char etag[64]; - mg_http_construct_etag(etag, sizeof(etag), st); - return mg_vcasecmp(hdr, etag) == 0; - } else if ((hdr = mg_get_http_header(hm, "If-Modified-Since")) != NULL) { - return st->st_mtime <= mg_parse_date_string(hdr->p); - } else { - return 0; - } -} +static void *mg_pull_from_stdout(void *arg) { + struct mg_threadparam *tp = (struct mg_threadparam *) arg; + int k = 0, stop = 0; + DWORD n, sent; + char buf[BUFSIZ]; -static void mg_http_send_digest_auth_request(struct mg_connection *c, - const char *domain) { - mg_printf(c, - "HTTP/1.1 401 Unauthorized\r\n" - "WWW-Authenticate: Digest qop=\"auth\", " - "realm=\"%s\", nonce=\"%lu\"\r\n" - "Content-Length: 0\r\n\r\n", - domain, (unsigned long) time(NULL)); + while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) { + for (sent = 0; !stop && sent < n; sent += k) { + if (mg_wait_until_ready(tp->s, 0) && + (k = send(tp->s, buf + sent, n - sent, 0)) <= 0) + stop = 1; + } + } + DBG(("%s", "EOF FROM CGI")); + CloseHandle(tp->hPipe); + shutdown(tp->s, 2); // Without this, IO thread may get truncated data + closesocket(tp->s); + MG_FREE(tp); + _endthread(); + return NULL; } -static void mg_http_send_options(struct mg_connection *nc) { - mg_printf(nc, "%s", - "HTTP/1.1 200 OK\r\nAllow: GET, POST, HEAD, CONNECT, OPTIONS" -#if !MG_DISABLE_DAV - ", MKCOL, PUT, DELETE, PROPFIND, MOVE\r\nDAV: 1,2" -#endif - "\r\n\r\n"); - nc->flags |= MG_F_SEND_AND_CLOSE; +static void mg_spawn_stdio_thread(sock_t sock, HANDLE hPipe, + void *(*func)(void *)) { + struct mg_threadparam *tp = (struct mg_threadparam *) MG_MALLOC(sizeof(*tp)); + if (tp != NULL) { + tp->s = sock; + tp->hPipe = hPipe; + mg_start_thread(func, tp); + } } -static int mg_is_creation_request(const struct http_message *hm) { - return mg_vcmp(&hm->method, "MKCOL") == 0 || mg_vcmp(&hm->method, "PUT") == 0; +static void mg_abs_path(const char *utf8_path, char *abs_path, size_t len) { + wchar_t buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE]; + to_wchar(utf8_path, buf, ARRAY_SIZE(buf)); + GetFullPathNameW(buf, ARRAY_SIZE(buf2), buf2, NULL); + WideCharToMultiByte(CP_UTF8, 0, buf2, wcslen(buf2) + 1, abs_path, len, 0, 0); } -MG_INTERNAL void mg_send_http_file(struct mg_connection *nc, char *path, - const struct mg_str *path_info, - struct http_message *hm, - struct mg_serve_http_opts *opts) { - int exists, is_directory, is_dav = mg_is_dav_request(&hm->method); - int is_cgi; - char *index_file = NULL; - cs_stat_t st; - - exists = (mg_stat(path, &st) == 0); - is_directory = exists && S_ISDIR(st.st_mode); +static int mg_start_process(const char *interp, const char *cmd, + const char *env, const char *envp[], + const char *dir, sock_t sock) { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + HANDLE a[2], b[2], me = GetCurrentProcess(); + wchar_t wcmd[MAX_PATH_SIZE], full_dir[MAX_PATH_SIZE]; + char buf[MAX_PATH_SIZE], buf2[MAX_PATH_SIZE], buf5[MAX_PATH_SIZE], + buf4[MAX_PATH_SIZE], cmdline[MAX_PATH_SIZE]; + DWORD flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS; + FILE *fp; - if (is_directory) - mg_find_index_file(path, opts->index_files, &index_file, &st); + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); - is_cgi = - (mg_match_prefix(opts->cgi_file_pattern, strlen(opts->cgi_file_pattern), - index_file ? index_file : path) > 0); + si.cb = sizeof(si); + si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; + si.wShowWindow = SW_HIDE; + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); - DBG(("%p %.*s [%s] exists=%d is_dir=%d is_dav=%d is_cgi=%d index=%s", nc, - (int) hm->method.len, hm->method.p, path, exists, is_directory, is_dav, - is_cgi, index_file ? index_file : "")); + CreatePipe(&a[0], &a[1], NULL, 0); + CreatePipe(&b[0], &b[1], NULL, 0); + DuplicateHandle(me, a[0], me, &si.hStdInput, 0, TRUE, flags); + DuplicateHandle(me, b[1], me, &si.hStdOutput, 0, TRUE, flags); - if (is_directory && hm->uri.p[hm->uri.len - 1] != '/' && !is_dav) { - mg_printf(nc, - "HTTP/1.1 301 Moved\r\nLocation: %.*s/\r\n" - "Content-Length: 0\r\n\r\n", - (int) hm->uri.len, hm->uri.p); - MG_FREE(index_file); - return; + if (interp == NULL && (fp = fopen(cmd, "r")) != NULL) { + buf[0] = buf[1] = '\0'; + fgets(buf, sizeof(buf), fp); + buf[sizeof(buf) - 1] = '\0'; + if (buf[0] == '#' && buf[1] == '!') { + interp = buf + 2; + /* Trim leading spaces: https://github.com/cesanta/mongoose/issues/489 */ + while (*interp != '\0' && isspace(*(unsigned char *) interp)) { + interp++; + } + } + fclose(fp); } - /* If we have path_info, the only way to handle it is CGI. */ - if (path_info->len > 0 && !is_cgi) { - mg_http_send_error(nc, 501, NULL); - MG_FREE(index_file); - return; - } + snprintf(buf, sizeof(buf), "%s/%s", dir, cmd); + mg_abs_path(buf, buf2, ARRAY_SIZE(buf2)); - if (is_dav && opts->dav_document_root == NULL) { - mg_http_send_error(nc, 501, NULL); - } else if (!mg_is_authorized(hm, path, is_directory, opts->auth_domain, - opts->global_auth_file, 1) || - !mg_is_authorized(hm, path, is_directory, opts->auth_domain, - opts->per_directory_auth_file, 0)) { - mg_http_send_digest_auth_request(nc, opts->auth_domain); - } else if (is_cgi) { -#if !MG_DISABLE_CGI - mg_handle_cgi(nc, index_file ? index_file : path, path_info, hm, opts); -#else - mg_http_send_error(nc, 501, NULL); -#endif /* MG_DISABLE_CGI */ - } else if ((!exists || - mg_is_file_hidden(path, opts, 0 /* specials are ok */)) && - !mg_is_creation_request(hm)) { - mg_http_send_error(nc, 404, NULL); -#if !MG_DISABLE_DAV - } else if (!mg_vcmp(&hm->method, "PROPFIND")) { - mg_handle_propfind(nc, path, &st, hm, opts); -#if !MG_DISABLE_DAV_AUTH - } else if (is_dav && - (opts->dav_auth_file == NULL || - (strcmp(opts->dav_auth_file, "-") != 0 && - !mg_is_authorized(hm, path, is_directory, opts->auth_domain, - opts->dav_auth_file, 1)))) { - mg_http_send_digest_auth_request(nc, opts->auth_domain); -#endif - } else if (!mg_vcmp(&hm->method, "MKCOL")) { - mg_handle_mkcol(nc, path, hm); - } else if (!mg_vcmp(&hm->method, "DELETE")) { - mg_handle_delete(nc, opts, path); - } else if (!mg_vcmp(&hm->method, "PUT")) { - mg_handle_put(nc, path, hm); - } else if (!mg_vcmp(&hm->method, "MOVE")) { - mg_handle_move(nc, opts, path, hm); -#if MG_ENABLE_FAKE_DAVLOCK - } else if (!mg_vcmp(&hm->method, "LOCK")) { - mg_handle_lock(nc, path); -#endif -#endif - } else if (!mg_vcmp(&hm->method, "OPTIONS")) { - mg_http_send_options(nc); - } else if (is_directory && index_file == NULL) { -#if !MG_DISABLE_DIRECTORY_LISTING - if (strcmp(opts->enable_directory_listing, "yes") == 0) { - mg_send_directory_listing(nc, path, hm, opts); - } else { - mg_http_send_error(nc, 403, NULL); - } -#else - mg_http_send_error(nc, 501, NULL); -#endif - } else if (mg_is_not_modified(hm, &st)) { - mg_http_send_error(nc, 304, "Not Modified"); + mg_abs_path(dir, buf5, ARRAY_SIZE(buf5)); + to_wchar(dir, full_dir, ARRAY_SIZE(full_dir)); + + if (interp != NULL) { + mg_abs_path(interp, buf4, ARRAY_SIZE(buf4)); + snprintf(cmdline, sizeof(cmdline), "%s \"%s\"", buf4, buf2); } else { - mg_http_serve_file2(nc, index_file ? index_file : path, hm, opts); + snprintf(cmdline, sizeof(cmdline), "\"%s\"", buf2); } - MG_FREE(index_file); -} + to_wchar(cmdline, wcmd, ARRAY_SIZE(wcmd)); -void mg_serve_http(struct mg_connection *nc, struct http_message *hm, - struct mg_serve_http_opts opts) { - char *path = NULL; - struct mg_str *hdr, path_info; - uint32_t remote_ip = ntohl(*(uint32_t *) &nc->sa.sin.sin_addr); + if (CreateProcessW(NULL, wcmd, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, + (void *) env, full_dir, &si, &pi) != 0) { + mg_spawn_stdio_thread(sock, a[1], mg_push_to_stdin); + mg_spawn_stdio_thread(sock, b[0], mg_pull_from_stdout); - if (mg_check_ip_acl(opts.ip_acl, remote_ip) != 1) { - /* Not allowed to connect */ - mg_http_send_error(nc, 403, NULL); - nc->flags |= MG_F_SEND_AND_CLOSE; - return; - } + CloseHandle(si.hStdOutput); + CloseHandle(si.hStdInput); - if (mg_http_send_port_based_redirect(nc, hm, &opts)) { - return; + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } else { + CloseHandle(a[1]); + CloseHandle(b[0]); + closesocket(sock); } + DBG(("CGI command: [%ls] -> %p", wcmd, pi.hProcess)); - if (opts.document_root == NULL) { - opts.document_root = "."; - } - if (opts.per_directory_auth_file == NULL) { - opts.per_directory_auth_file = ".htpasswd"; - } - if (opts.enable_directory_listing == NULL) { - opts.enable_directory_listing = "yes"; - } - if (opts.cgi_file_pattern == NULL) { - opts.cgi_file_pattern = "**.cgi$|**.php$"; - } - if (opts.ssi_pattern == NULL) { - opts.ssi_pattern = "**.shtml$|**.shtm$"; - } - if (opts.index_files == NULL) { - opts.index_files = "index.html,index.htm,index.shtml,index.cgi,index.php"; - } - /* Normalize path - resolve "." and ".." (in-place). */ - if (!mg_normalize_uri_path(&hm->uri, &hm->uri)) { - mg_http_send_error(nc, 400, NULL); - return; - } - if (mg_uri_to_local_path(hm, &opts, &path, &path_info) == 0) { - mg_http_send_error(nc, 404, NULL); - return; - } - mg_send_http_file(nc, path, &path_info, hm, &opts); + /* Not closing a[0] and b[1] because we've used DUPLICATE_CLOSE_SOURCE */ + (void) envp; + return (pi.hProcess != NULL); +} +#else +static int mg_start_process(const char *interp, const char *cmd, + const char *env, const char *envp[], + const char *dir, sock_t sock) { + char buf[500]; + pid_t pid = fork(); + (void) env; - MG_FREE(path); - path = NULL; + if (pid == 0) { + /* + * In Linux `chdir` declared with `warn_unused_result` attribute + * To shutup compiler we have yo use result in some way + */ + int tmp = chdir(dir); + (void) tmp; + (void) dup2(sock, 0); + (void) dup2(sock, 1); + closesocket(sock); - /* Close connection for non-keep-alive requests */ - if (mg_vcmp(&hm->proto, "HTTP/1.1") != 0 || - ((hdr = mg_get_http_header(hm, "Connection")) != NULL && - mg_vcmp(hdr, "keep-alive") != 0)) { -#if 0 - nc->flags |= MG_F_SEND_AND_CLOSE; -#endif + /* + * 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, (char *) 0, envp); /* (char *) 0 to squash warning */ + } else { + execle(interp, interp, cmd, (char *) 0, envp); + } + snprintf(buf, sizeof(buf), + "Status: 500\r\n\r\n" + "500 Server Error: %s%s%s: %s", + interp == NULL ? "" : interp, interp == NULL ? "" : " ", cmd, + strerror(errno)); + send(1, buf, strlen(buf), 0); + exit(EXIT_FAILURE); /* exec call failed */ } -} -#endif /* MG_DISABLE_FILESYSTEM */ + return (pid != 0); +} +#endif /* _WIN32 */ -/* returns 0 on success, -1 on error */ -static int mg_http_common_url_parse(const char *url, const char *schema, - const char *schema_tls, int *use_ssl, - char **addr, int *port_i, - const char **path) { - int addr_len = 0; +/* + * Append VARIABLE=VALUE\0 string to the buffer, and add a respective + * pointer into the vars array. + */ +static char *mg_addenv(struct mg_cgi_env_block *block, const char *fmt, ...) { + int n, space; + char *added = block->buf + block->len; + va_list ap; - if (memcmp(url, schema, strlen(schema)) == 0) { - url += strlen(schema); - } else if (memcmp(url, schema_tls, strlen(schema_tls)) == 0) { - url += strlen(schema_tls); - *use_ssl = 1; -#if !MG_ENABLE_SSL - return -1; /* SSL is not enabled, cannot do HTTPS URLs */ -#endif - } + /* Calculate how much space is left in the buffer */ + space = sizeof(block->buf) - (block->len + 2); + if (space > 0) { + /* Copy VARIABLE=VALUE\0 string into the free space */ + va_start(ap, fmt); + n = vsnprintf(added, (size_t) space, fmt, ap); + va_end(ap); - while (*url != '\0') { - *addr = (char *) MG_REALLOC(*addr, addr_len + 6 /* space for port too. */); - if (*addr == NULL) { - DBG(("OOM")); - return -1; - } - if (*url == '/') { - break; + /* 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; } - if (*url == ':') *port_i = addr_len; - (*addr)[addr_len++] = *url; - (*addr)[addr_len] = '\0'; - url++; - } - if (addr_len == 0) goto cleanup; - if (*port_i < 0) { - *port_i = addr_len; - strcpy(*addr + *port_i, *use_ssl ? ":443" : ":80"); - } else { - *port_i = -1; } - if (*path == NULL) *path = url; + return added; +} - if (**path == '\0') *path = "/"; +static void mg_addenv2(struct mg_cgi_env_block *blk, const char *name) { + const char *s; + if ((s = getenv(name)) != NULL) mg_addenv(blk, "%s=%s", name, s); +} - DBG(("%s %s", *addr, *path)); +static void mg_prepare_cgi_environment(struct mg_connection *nc, + const char *prog, + const struct mg_str *path_info, + const struct http_message *hm, + const struct mg_serve_http_opts *opts, + struct mg_cgi_env_block *blk) { + const char *s; + struct mg_str *h; + char *p; + size_t i; + char buf[100]; - return 0; + blk->len = blk->nvars = 0; + blk->nc = nc; -cleanup: - MG_FREE(*addr); - return -1; -} + if ((s = getenv("SERVER_NAME")) != NULL) { + mg_addenv(blk, "SERVER_NAME=%s", s); + } else { + mg_sock_to_str(nc->sock, buf, sizeof(buf), 3); + mg_addenv(blk, "SERVER_NAME=%s", buf); + } + mg_addenv(blk, "SERVER_ROOT=%s", opts->document_root); + mg_addenv(blk, "DOCUMENT_ROOT=%s", opts->document_root); + mg_addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", MG_VERSION); -struct mg_connection *mg_connect_http_base( - struct mg_mgr *mgr, mg_event_handler_t ev_handler, - struct mg_connect_opts opts, const char *schema, const char *schema_ssl, - const char *url, const char **path, char **addr) { - struct mg_connection *nc = NULL; - int port_i = -1; - int use_ssl = 0; + /* Prepare the environment block */ + mg_addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1"); + mg_addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1"); + mg_addenv(blk, "%s", "REDIRECT_STATUS=200"); /* For PHP */ - if (mg_http_common_url_parse(url, schema, schema_ssl, &use_ssl, addr, &port_i, - path) < 0) { - return NULL; - } + mg_addenv(blk, "REQUEST_METHOD=%.*s", (int) hm->method.len, hm->method.p); - LOG(LL_DEBUG, ("%s use_ssl? %d", url, use_ssl)); - if (use_ssl) { -#if MG_ENABLE_SSL - /* - * Schema requires SSL, but no SSL parameters were provided in opts. - * In order to maintain backward compatibility, use a faux-SSL with no - * verification. - */ - if (opts.ssl_ca_cert == NULL) { - opts.ssl_ca_cert = "*"; - } -#else - MG_SET_PTRPTR(opts.error_string, "ssl is disabled"); - MG_FREE(addr); - return NULL; -#endif + mg_addenv(blk, "REQUEST_URI=%.*s%s%.*s", (int) hm->uri.len, hm->uri.p, + hm->query_string.len == 0 ? "" : "?", (int) hm->query_string.len, + hm->query_string.p); + + mg_conn_addr_to_str(nc, buf, sizeof(buf), + MG_SOCK_STRINGIFY_REMOTE | MG_SOCK_STRINGIFY_IP); + mg_addenv(blk, "REMOTE_ADDR=%s", buf); + mg_conn_addr_to_str(nc, buf, sizeof(buf), MG_SOCK_STRINGIFY_PORT); + mg_addenv(blk, "SERVER_PORT=%s", buf); + + s = hm->uri.p + hm->uri.len - path_info->len - 1; + if (*s == '/') { + const char *base_name = strrchr(prog, DIRSEP); + mg_addenv(blk, "SCRIPT_NAME=%.*s/%s", (int) (s - hm->uri.p), hm->uri.p, + (base_name != NULL ? base_name + 1 : prog)); + } else { + mg_addenv(blk, "SCRIPT_NAME=%.*s", (int) (s - hm->uri.p + 1), hm->uri.p); } + mg_addenv(blk, "SCRIPT_FILENAME=%s", prog); - if ((nc = mg_connect_opt(mgr, *addr, ev_handler, opts)) != NULL) { - mg_set_protocol_http_websocket(nc); - /* If the port was addred by us, restore the original host. */ - if (port_i >= 0) (*addr)[port_i] = '\0'; + if (path_info != NULL && path_info->len > 0) { + mg_addenv(blk, "PATH_INFO=%.*s", (int) path_info->len, path_info->p); + /* Not really translated... */ + mg_addenv(blk, "PATH_TRANSLATED=%.*s", (int) path_info->len, path_info->p); } - return nc; -} +#if MG_ENABLE_SSL + mg_addenv(blk, "HTTPS=%s", nc->ssl != NULL ? "on" : "off"); +#else + mg_addenv(blk, "HTTPS=off"); +#endif -struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr, - mg_event_handler_t ev_handler, - struct mg_connect_opts opts, - const char *url, - const char *extra_headers, - const char *post_data) { - char *addr = NULL; - const char *path = NULL; - struct mg_connection *nc = mg_connect_http_base( - mgr, ev_handler, opts, "http://", "https://", url, &path, &addr); + if ((h = mg_get_http_header((struct http_message *) hm, "Content-Type")) != + NULL) { + mg_addenv(blk, "CONTENT_TYPE=%.*s", (int) h->len, h->p); + } - if (nc == NULL) { - return NULL; + if (hm->query_string.len > 0) { + mg_addenv(blk, "QUERY_STRING=%.*s", (int) hm->query_string.len, + hm->query_string.p); } - mg_printf(nc, "%s %s HTTP/1.1\r\nHost: %s\r\nContent-Length: %" SIZE_T_FMT - "\r\n%s\r\n%s", - post_data == NULL ? "GET" : "POST", path, addr, - post_data == NULL ? 0 : strlen(post_data), - extra_headers == NULL ? "" : extra_headers, - post_data == NULL ? "" : post_data); + if ((h = mg_get_http_header((struct http_message *) hm, "Content-Length")) != + NULL) { + mg_addenv(blk, "CONTENT_LENGTH=%.*s", (int) h->len, h->p); + } - MG_FREE(addr); - return nc; -} + mg_addenv2(blk, "PATH"); + mg_addenv2(blk, "TMP"); + mg_addenv2(blk, "TEMP"); + mg_addenv2(blk, "TMPDIR"); + mg_addenv2(blk, "PERLLIB"); + mg_addenv2(blk, MG_ENV_EXPORT_TO_CGI); -struct mg_connection *mg_connect_http(struct mg_mgr *mgr, - mg_event_handler_t ev_handler, - const char *url, - const char *extra_headers, - const char *post_data) { - struct mg_connect_opts opts; - memset(&opts, 0, sizeof(opts)); - return mg_connect_http_opt(mgr, ev_handler, opts, url, extra_headers, - post_data); -} +#ifdef _WIN32 + mg_addenv2(blk, "COMSPEC"); + mg_addenv2(blk, "SYSTEMROOT"); + mg_addenv2(blk, "SystemDrive"); + mg_addenv2(blk, "ProgramFiles"); + mg_addenv2(blk, "ProgramFiles(x86)"); + mg_addenv2(blk, "CommonProgramFiles(x86)"); +#else + mg_addenv2(blk, "LD_LIBRARY_PATH"); +#endif /* _WIN32 */ -#if !MG_DISABLE_HTTP_WEBSOCKET -struct mg_connection *mg_connect_ws_opt(struct mg_mgr *mgr, - mg_event_handler_t ev_handler, - struct mg_connect_opts opts, - const char *url, const char *protocol, - const char *extra_headers) { - char *addr = NULL; - const char *path = NULL; - struct mg_connection *nc = mg_connect_http_base( - mgr, ev_handler, opts, "ws://", "wss://", url, &path, &addr); + /* Add all headers as HTTP_* variables */ + for (i = 0; hm->header_names[i].len > 0; i++) { + p = mg_addenv(blk, "HTTP_%.*s=%.*s", (int) hm->header_names[i].len, + hm->header_names[i].p, (int) hm->header_values[i].len, + hm->header_values[i].p); - if (nc != NULL) { - mg_send_websocket_handshake2(nc, path, addr, protocol, extra_headers); + /* Convert variable name into uppercase, and change - to _ */ + for (; *p != '=' && *p != '\0'; p++) { + if (*p == '-') *p = '_'; + *p = (char) toupper(*(unsigned char *) p); + } } - MG_FREE(addr); - return nc; + blk->vars[blk->nvars++] = NULL; + blk->buf[blk->len++] = '\0'; } -struct mg_connection *mg_connect_ws(struct mg_mgr *mgr, - mg_event_handler_t ev_handler, - const char *url, const char *protocol, - const char *extra_headers) { - struct mg_connect_opts opts; - memset(&opts, 0, sizeof(opts)); - return mg_connect_ws_opt(mgr, ev_handler, opts, url, protocol, extra_headers); -} -#endif /* MG_DISABLE_HTTP_WEBSOCKET */ +static void mg_cgi_ev_handler(struct mg_connection *cgi_nc, int ev, + void *ev_data) { + struct mg_connection *nc = (struct mg_connection *) cgi_nc->user_data; + (void) ev_data; -size_t mg_parse_multipart(const char *buf, size_t buf_len, char *var_name, - size_t var_name_len, char *file_name, - size_t file_name_len, const char **data, - size_t *data_len) { - static const char cd[] = "Content-Disposition: "; - size_t hl, bl, n, ll, pos, cdl = sizeof(cd) - 1; + if (nc == NULL) return; - if (buf == NULL || buf_len <= 0) return 0; - if ((hl = mg_http_get_request_len(buf, buf_len)) <= 0) return 0; - if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0; + switch (ev) { + case MG_EV_RECV: + /* + * CGI script does not output reply line, like "HTTP/1.1 CODE XXXXX\n" + * It outputs headers, then body. Headers might include "Status" + * header, which changes CODE, and it might include "Location" header + * which changes CODE to 302. + * + * Therefore we do not send the output from the CGI script to the user + * until all CGI headers are received. + * + * Here we parse the output from the CGI script, and if all headers has + * been received, send appropriate reply line, and forward all + * received headers to the client. + */ + if (nc->flags & MG_F_USER_1) { + struct mbuf *io = &cgi_nc->recv_mbuf; + int len = mg_http_get_request_len(io->buf, io->len); - /* Get boundary length */ - bl = mg_get_line_len(buf, buf_len); + if (len == 0) break; + if (len < 0 || io->len > MG_MAX_HTTP_REQUEST_SIZE) { + cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY; + mg_http_send_error(nc, 500, "Bad headers"); + } else { + struct http_message hm; + struct mg_str *h; + mg_http_parse_headers(io->buf, io->buf + io->len, io->len, &hm); + if (mg_get_http_header(&hm, "Location") != NULL) { + mg_printf(nc, "%s", "HTTP/1.1 302 Moved\r\n"); + } else if ((h = mg_get_http_header(&hm, "Status")) != NULL) { + mg_printf(nc, "HTTP/1.1 %.*s\r\n", (int) h->len, h->p); + } else { + mg_printf(nc, "%s", "HTTP/1.1 200 OK\r\n"); + } + } + nc->flags &= ~MG_F_USER_1; + } + if (!(nc->flags & MG_F_USER_1)) { + mg_forward(cgi_nc, nc); + } + break; + case MG_EV_CLOSE: + mg_http_free_proto_data_cgi(&mg_http_get_proto_data(cgi_nc)->cgi); + nc->flags |= MG_F_SEND_AND_CLOSE; + break; + } +} - /* Loop through headers, fetch variable name and file name */ - var_name[0] = file_name[0] = '\0'; - for (n = bl; (ll = mg_get_line_len(buf + n, hl - n)) > 0; n += ll) { - if (mg_ncasecmp(cd, buf + n, cdl) == 0) { - struct mg_str header; - header.p = buf + n + cdl; - header.len = ll - (cdl + 2); - mg_http_parse_header(&header, "name", var_name, var_name_len); - mg_http_parse_header(&header, "filename", file_name, file_name_len); - } +MG_INTERNAL void mg_handle_cgi(struct mg_connection *nc, const char *prog, + const struct mg_str *path_info, + const struct http_message *hm, + const struct mg_serve_http_opts *opts) { + struct mg_cgi_env_block blk; + char dir[MAX_PATH_SIZE]; + const char *p; + sock_t fds[2]; + + DBG(("%p [%s]", nc, prog)); + mg_prepare_cgi_environment(nc, prog, path_info, hm, opts, &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'. + */ + if ((p = strrchr(prog, DIRSEP)) == NULL) { + snprintf(dir, sizeof(dir), "%s", "."); + } else { + snprintf(dir, sizeof(dir), "%.*s", (int) (p - prog), prog); + prog = p + 1; } - /* Scan through the body, search for terminating boundary */ - for (pos = hl; pos + (bl - 2) < buf_len; pos++) { - if (buf[pos] == '-' && !memcmp(buf, &buf[pos], bl - 2)) { - if (data_len != NULL) *data_len = (pos - 2) - hl; - if (data != NULL) *data = buf + hl; - return pos; + /* + * Try to create socketpair in a loop until success. mg_socketpair() + * can be interrupted by a signal and fail. + * TODO(lsm): use sigaction to restart interrupted syscall + */ + do { + mg_socketpair(fds, SOCK_STREAM); + } while (fds[0] == INVALID_SOCKET); + + if (mg_start_process(opts->cgi_interpreter, prog, blk.buf, blk.vars, dir, + fds[1]) != 0) { + size_t n = nc->recv_mbuf.len - (hm->message.len - hm->body.len); + struct mg_connection *cgi_nc = + mg_add_sock(nc->mgr, fds[0], mg_cgi_ev_handler); + struct mg_http_proto_data *cgi_pd = mg_http_get_proto_data(cgi_nc); + cgi_pd->cgi.cgi_nc = cgi_nc; + cgi_pd->cgi.cgi_nc->user_data = nc; + nc->flags |= MG_F_USER_1; + /* Push POST data to the CGI */ + if (n > 0 && n < nc->recv_mbuf.len) { + mg_send(cgi_pd->cgi.cgi_nc, hm->body.p, n); } + mbuf_remove(&nc->recv_mbuf, nc->recv_mbuf.len); + } else { + closesocket(fds[0]); + mg_http_send_error(nc, 500, "CGI failure"); } - return 0; +#ifndef _WIN32 + closesocket(fds[1]); /* On Windows, CGI stdio thread closes that socket */ +#endif } -void mg_register_http_endpoint(struct mg_connection *nc, const char *uri_path, - mg_event_handler_t handler) { - struct mg_http_proto_data *pd = NULL; - struct mg_http_endpoint *new_ep = NULL; - - if (nc == NULL) return; - new_ep = (struct mg_http_endpoint *) calloc(1, sizeof(*new_ep)); - if (new_ep == NULL) return; - - pd = mg_http_get_proto_data(nc); - new_ep->name = strdup(uri_path); - new_ep->name_len = strlen(new_ep->name); - new_ep->handler = handler; - new_ep->next = pd->endpoints; - pd->endpoints = new_ep; +MG_INTERNAL void mg_http_free_proto_data_cgi(struct mg_http_proto_data_cgi *d) { + if (d != NULL) { + if (d->cgi_nc != NULL) d->cgi_nc->flags |= MG_F_CLOSE_IMMEDIATELY; + memset(d, 0, sizeof(struct mg_http_proto_data_cgi)); + } } -#endif /* MG_DISABLE_HTTP */ +#endif /* MG_ENABLE_HTTP && MG_ENABLE_CGI */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/util.c" #endif diff --git a/mongoose.h b/mongoose.h index 4a1300f130f29a9c1a116fdf8b867ba5ed1a5682..e929f0eb20844a1e602d63a7c94dabbc6066e6f2 100644 --- a/mongoose.h +++ b/mongoose.h @@ -454,7 +454,6 @@ void mg_lwip_set_keepalive_params(struct mg_connection *nc, int idle, #define MG_DISABLE_SOCKETPAIR 1 #define MG_DISABLE_SYNC_RESOLVER 1 #define MG_DISABLE_POPEN 1 -#define MG_DISABLE_CGI 1 #define MG_DISABLE_DAV 1 #define MG_DISABLE_DIRECTORY_LISTING 1 #define MG_DISABLE_FILESYSTEM 1 @@ -514,7 +513,6 @@ int inet_pton(int af, const char *src, void *dst); #define MG_DISABLE_SOCKETPAIR 1 #define MG_DISABLE_SYNC_RESOLVER 1 #define MG_DISABLE_POPEN 1 -#define MG_DISABLE_CGI 1 /* Only SPIFFS supports directories, SLFS does not. */ #ifndef CC3200_FS_SPIFFS #define MG_DISABLE_DAV 1 @@ -644,7 +642,6 @@ struct dirent *readdir(DIR *dir); #define MG_DISABLE_SOCKETPAIR 1 #define MG_DISABLE_SYNC_RESOLVER 1 #define MG_DISABLE_POPEN 1 -#define MG_DISABLE_CGI 1 #define MG_DISABLE_DAV 1 #define MG_DISABLE_DIRECTORY_LISTING 1 @@ -1186,10 +1183,6 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #ifndef CS_MONGOOSE_SRC_FEATURES_H_ #define CS_MONGOOSE_SRC_FEATURES_H_ -#ifndef MG_DISABLE_CGI -#define MG_DISABLE_CGI 0 -#endif - #ifndef MG_DISABLE_DIRECTORY_LISTING #define MG_DISABLE_DIRECTORY_LISTING 0 #endif @@ -1262,6 +1255,10 @@ const char *c_strnstr(const char *s, const char *find, size_t slen); #define MG_DISABLE_WS_RANDOM_MASK 0 #endif +#ifndef MG_ENABLE_CGI +#define MG_ENABLE_CGI (CS_PLATFORM == CS_P_UNIX || CS_PLATFORM == CS_P_WINDOWS) +#endif + #ifndef MG_ENABLE_COAP #define MG_ENABLE_COAP 0 #endif