| /* |
| * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com> |
| * All rights reserved |
| * |
| * "THE BEER-WARE LICENSE" (Revision 42): |
| * Sergey Lyubka wrote this file. As long as you retain this notice you |
| * can do whatever you want with this stuff. If we meet some day, and you think |
| * this stuff is worth it, you can buy me a beer in return. |
| */ |
| |
| #include "defs.h" |
| |
| #if !defined(NO_CGI) |
| struct env_block { |
| char buf[ENV_MAX]; /* Environment buffer */ |
| int len; /* Space taken */ |
| char *vars[CGI_ENV_VARS]; /* Point into the buffer */ |
| int nvars; /* Number of variables */ |
| }; |
| |
| static void |
| addenv(struct env_block *block, const char *fmt, ...) |
| { |
| int n, space; |
| va_list ap; |
| |
| space = sizeof(block->buf) - block->len - 2; |
| assert(space >= 0); |
| |
| va_start(ap, fmt); |
| n = vsnprintf(block->buf + block->len, space, fmt, ap); |
| va_end(ap); |
| |
| if (n > 0 && n < space && block->nvars < CGI_ENV_VARS - 2) { |
| block->vars[block->nvars++] = block->buf + block->len; |
| block->len += n + 1; /* Include \0 terminator */ |
| } |
| } |
| |
| static void |
| add_http_headers_to_env(struct env_block *b, const char *s, int len) |
| { |
| const char *p, *v, *e = s + len; |
| int space, n, i, ch; |
| |
| /* Loop through all headers in the request */ |
| while (s < e) { |
| |
| /* Find where this header ends. Remember where value starts */ |
| for (p = s, v = NULL; p < e && *p != '\n'; p++) |
| if (v == NULL && *p == ':') |
| v = p; |
| |
| /* 2 null terminators and "HTTP_" */ |
| space = (sizeof(b->buf) - b->len) - (2 + 5); |
| assert(space >= 0); |
| |
| /* Copy header if enough space in the environment block */ |
| if (v > s && p > v + 2 && space > p - s) { |
| |
| /* Store var */ |
| if (b->nvars < (int) NELEMS(b->vars) - 1) |
| b->vars[b->nvars++] = b->buf + b->len; |
| |
| (void) memcpy(b->buf + b->len, "HTTP_", 5); |
| b->len += 5; |
| |
| /* Copy header name. Substitute '-' to '_' */ |
| n = v - s; |
| for (i = 0; i < n; i++) { |
| ch = s[i] == '-' ? '_' : s[i]; |
| b->buf[b->len++] = toupper(ch); |
| } |
| |
| b->buf[b->len++] = '='; |
| |
| /* Copy header value */ |
| v += 2; |
| n = p[-1] == '\r' ? (p - v) - 1 : p - v; |
| for (i = 0; i < n; i++) |
| b->buf[b->len++] = v[i]; |
| |
| /* Null-terminate */ |
| b->buf[b->len++] = '\0'; |
| } |
| |
| s = p + 1; /* Shift to the next header */ |
| } |
| } |
| |
| static void |
| prepare_environment(const struct conn *c, const char *prog, |
| struct env_block *blk) |
| { |
| const struct headers *h = &c->ch; |
| const char *s, *fname, *root = c->ctx->options[OPT_ROOT]; |
| size_t len; |
| |
| blk->len = blk->nvars = 0; |
| |
| /* SCRIPT_FILENAME */ |
| fname = prog; |
| if ((s = strrchr(prog, '/'))) |
| fname = s + 1; |
| |
| /* 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"); /* PHP */ |
| addenv(blk, "SERVER_PORT=%d", c->loc_port); |
| addenv(blk, "SERVER_NAME=%s", c->ctx->options[OPT_AUTH_REALM]); |
| addenv(blk, "SERVER_ROOT=%s", root); |
| addenv(blk, "DOCUMENT_ROOT=%s", root); |
| addenv(blk, "REQUEST_METHOD=%s", |
| _shttpd_known_http_methods[c->method].ptr); |
| addenv(blk, "REMOTE_ADDR=%s", inet_ntoa(c->sa.u.sin.sin_addr)); |
| addenv(blk, "REMOTE_PORT=%hu", ntohs(c->sa.u.sin.sin_port)); |
| addenv(blk, "REQUEST_URI=%s", c->uri); |
| addenv(blk, "SCRIPT_NAME=%s", prog + strlen(root)); |
| addenv(blk, "SCRIPT_FILENAME=%s", fname); /* PHP */ |
| addenv(blk, "PATH_TRANSLATED=%s", prog); |
| |
| if (h->ct.v_vec.len > 0) |
| addenv(blk, "CONTENT_TYPE=%.*s", |
| h->ct.v_vec.len, h->ct.v_vec.ptr); |
| |
| if (c->query != NULL) |
| addenv(blk, "QUERY_STRING=%s", c->query); |
| |
| if (c->path_info != NULL) |
| addenv(blk, "PATH_INFO=/%s", c->path_info); |
| |
| if (h->cl.v_big_int > 0) |
| addenv(blk, "CONTENT_LENGTH=%lu", h->cl.v_big_int); |
| |
| if ((s = getenv("PATH")) != NULL) |
| addenv(blk, "PATH=%s", s); |
| |
| #ifdef _WIN32 |
| if ((s = getenv("COMSPEC")) != NULL) |
| addenv(blk, "COMSPEC=%s", s); |
| if ((s = getenv("SYSTEMROOT")) != NULL) |
| addenv(blk, "SYSTEMROOT=%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 (h->user.v_vec.len > 0) { |
| addenv(blk, "REMOTE_USER=%.*s", |
| h->user.v_vec.len, h->user.v_vec.ptr); |
| addenv(blk, "%s", "AUTH_TYPE=Digest"); |
| } |
| |
| /* Add user-specified variables */ |
| s = c->ctx->options[OPT_CGI_ENVIRONMENT]; |
| FOR_EACH_WORD_IN_LIST(s, len) |
| addenv(blk, "%.*s", len, s); |
| |
| /* Add all headers as HTTP_* variables */ |
| add_http_headers_to_env(blk, c->headers, |
| c->rem.headers_len - (c->headers - c->request)); |
| |
| blk->vars[blk->nvars++] = NULL; |
| blk->buf[blk->len++] = '\0'; |
| |
| assert(blk->nvars < CGI_ENV_VARS); |
| assert(blk->len > 0); |
| assert(blk->len < (int) sizeof(blk->buf)); |
| |
| /* Debug stuff to view passed environment */ |
| DBG(("%s: %d vars, %d env size", prog, blk->nvars, blk->len)); |
| { |
| int i; |
| for (i = 0 ; i < blk->nvars; i++) |
| DBG(("[%s]", blk->vars[i] ? blk->vars[i] : "null")); |
| } |
| } |
| |
| int |
| _shttpd_run_cgi(struct conn *c, const char *prog) |
| { |
| struct env_block blk; |
| char dir[FILENAME_MAX], *p; |
| int ret, pair[2]; |
| |
| prepare_environment(c, prog, &blk); |
| pair[0] = pair[1] = -1; |
| |
| /* CGI must be executed in its own directory */ |
| (void) _shttpd_snprintf(dir, sizeof(dir), "%s", prog); |
| for (p = dir + strlen(dir) - 1; p > dir; p--) |
| if (*p == '/') { |
| *p++ = '\0'; |
| break; |
| } |
| |
| if (shttpd_socketpair(pair) != 0) { |
| ret = -1; |
| } else if (_shttpd_spawn_process(c, |
| prog, blk.buf, blk.vars, pair[1], dir)) { |
| ret = -1; |
| (void) closesocket(pair[0]); |
| (void) closesocket(pair[1]); |
| } else { |
| ret = 0; |
| c->loc.chan.sock = pair[0]; |
| } |
| |
| return (ret); |
| } |
| |
| void |
| _shttpd_do_cgi(struct conn *c) |
| { |
| DBG(("running CGI: [%s]", c->uri)); |
| assert(c->loc.io.size > CGI_REPLY_LEN); |
| memcpy(c->loc.io.buf, CGI_REPLY, CGI_REPLY_LEN); |
| c->loc.io.head = c->loc.io.tail = c->loc.io.total = CGI_REPLY_LEN; |
| c->loc.io_class = &_shttpd_io_cgi; |
| c->loc.flags = FLAG_R; |
| if (c->method == METHOD_POST) |
| c->loc.flags |= FLAG_W; |
| } |
| |
| #endif /* !NO_CGI */ |