mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-14 10:20:01 +00:00
385 lines
12 KiB
C
385 lines
12 KiB
C
#include "internal.h"
|
|
|
|
static int forward_body_data(struct mg_connection *conn, FILE *fp,
|
|
SOCKET sock, SSL *ssl) {
|
|
const char *expect, *body;
|
|
char buf[MG_BUF_LEN];
|
|
int nread, buffered_len, success = 0;
|
|
int64_t left;
|
|
|
|
expect = mg_get_header(conn, "Expect");
|
|
assert(fp != NULL);
|
|
|
|
if (conn->content_len == INT64_MAX) {
|
|
send_http_error(conn, 411, "Length Required", "%s", "");
|
|
} else if (expect != NULL && mg_strcasecmp(expect, "100-continue")) {
|
|
send_http_error(conn, 417, "Expectation Failed", "%s", "");
|
|
} else {
|
|
if (expect != NULL) {
|
|
(void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n");
|
|
}
|
|
|
|
buffered_len = conn->data_len - conn->request_len;
|
|
body = conn->buf + conn->request_len;
|
|
assert(buffered_len >= 0);
|
|
|
|
if (buffered_len > 0) {
|
|
if ((int64_t) buffered_len > conn->content_len) {
|
|
buffered_len = (int) conn->content_len;
|
|
}
|
|
push(fp, sock, ssl, body, (int64_t) buffered_len);
|
|
memmove((char *) body, body + buffered_len, buffered_len);
|
|
conn->data_len -= buffered_len;
|
|
}
|
|
|
|
nread = 0;
|
|
while (conn->num_bytes_read < conn->content_len + conn->request_len) {
|
|
left = left_to_read(conn);
|
|
if (left > (int64_t) sizeof(buf)) {
|
|
left = sizeof(buf);
|
|
}
|
|
nread = pull(NULL, conn, buf, (int) left);
|
|
if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (left_to_read(conn) == 0) {
|
|
success = nread >= 0;
|
|
}
|
|
|
|
// Each error code path in this function must send an error
|
|
if (!success) {
|
|
send_http_error(conn, 577, http_500_error, "%s", "");
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
#if !defined(NO_CGI)
|
|
// 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
|
|
int len; // Space taken
|
|
char *vars[MAX_CGI_ENVIR_VARS]; // char **envp
|
|
int nvars; // Number of variables
|
|
};
|
|
|
|
static char *addenv(struct cgi_env_block *block,
|
|
PRINTF_FORMAT_STRING(const char *fmt), ...)
|
|
PRINTF_ARGS(2, 3);
|
|
|
|
// 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 = mg_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;
|
|
} else {
|
|
cry(block->conn, "%s: CGI env buffer truncated for [%s]", __func__, fmt);
|
|
}
|
|
|
|
return added;
|
|
}
|
|
|
|
static void prepare_cgi_environment(struct mg_connection *conn,
|
|
const char *prog,
|
|
struct cgi_env_block *blk) {
|
|
const struct mg_request_info *ri = &conn->request_info;
|
|
const char *s, *slash;
|
|
struct vec var_vec;
|
|
char *p, src_addr[IP_ADDR_STR_LEN];
|
|
int i;
|
|
|
|
blk->len = blk->nvars = 0;
|
|
blk->conn = conn;
|
|
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
|
|
|
|
addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
|
|
addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
|
|
addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
|
|
addenv(blk, "SERVER_SOFTWARE=%s/%s", "Mongoose", mg_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", ntohs(conn->client.lsa.sin.sin_port));
|
|
|
|
addenv(blk, "REQUEST_METHOD=%s", ri->request_method);
|
|
addenv(blk, "REMOTE_ADDR=%s", src_addr);
|
|
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->ssl == NULL ? "off" : "on");
|
|
|
|
if ((s = mg_get_header(conn, "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(conn, "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 (ri->remote_user != NULL) {
|
|
addenv(blk, "REMOTE_USER=%s", ri->remote_user);
|
|
addenv(blk, "%s", "AUTH_TYPE=Digest");
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
}
|
|
|
|
// Add user-specified variables
|
|
s = conn->ctx->config[CGI_ENVIRONMENT];
|
|
while ((s = next_option(s, &var_vec, NULL)) != NULL) {
|
|
addenv(blk, "%.*s", (int) var_vec.len, var_vec.ptr);
|
|
}
|
|
|
|
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 handle_cgi_request(struct mg_connection *conn, const char *prog) {
|
|
int headers_len, data_len, i, fdin[2] = {-1, -1}, fdout[2] = {-1, -1};
|
|
const char *status, *status_text;
|
|
char buf[16384], *pbuf, dir[PATH_MAX], *p;
|
|
struct mg_request_info ri;
|
|
struct cgi_env_block blk;
|
|
FILE *in = NULL, *out = NULL;
|
|
pid_t pid = (pid_t) -1;
|
|
|
|
memset(&ri, 0, sizeof(ri));
|
|
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'.
|
|
(void) mg_snprintf(dir, sizeof(dir), "%s", prog);
|
|
if ((p = strrchr(dir, '/')) != NULL) {
|
|
*p++ = '\0';
|
|
} else {
|
|
dir[0] = '.', dir[1] = '\0';
|
|
p = (char *) prog;
|
|
}
|
|
|
|
if (pipe(fdin) != 0 || pipe(fdout) != 0) {
|
|
send_http_error(conn, 500, http_500_error,
|
|
"Cannot create CGI pipe: %s", strerror(ERRNO));
|
|
goto done;
|
|
}
|
|
|
|
pid = spawn_process(conn, p, blk.buf, blk.vars, fdin[0], fdout[1], dir);
|
|
if (pid == (pid_t) -1) {
|
|
send_http_error(conn, 500, http_500_error,
|
|
"Cannot spawn CGI process [%s]: %s", prog, strerror(ERRNO));
|
|
goto done;
|
|
}
|
|
|
|
// Make sure child closes all pipe descriptors. It must dup them to 0,1
|
|
set_close_on_exec(fdin[0]);
|
|
set_close_on_exec(fdin[1]);
|
|
set_close_on_exec(fdout[0]);
|
|
set_close_on_exec(fdout[1]);
|
|
|
|
// Parent closes only one side of the pipes.
|
|
// If we don't mark them as closed, close() attempt before
|
|
// return from this function throws an exception on Windows.
|
|
// Windows does not like when closed descriptor is closed again.
|
|
(void) close(fdin[0]);
|
|
(void) close(fdout[1]);
|
|
fdin[0] = fdout[1] = -1;
|
|
|
|
|
|
if ((in = fdopen(fdin[1], "wb")) == NULL ||
|
|
(out = fdopen(fdout[0], "rb")) == NULL) {
|
|
send_http_error(conn, 500, http_500_error,
|
|
"fopen: %s", strerror(ERRNO));
|
|
goto done;
|
|
}
|
|
|
|
setbuf(in, NULL);
|
|
setbuf(out, NULL);
|
|
|
|
// Send POST data to the CGI process if needed
|
|
if (!strcmp(conn->request_info.request_method, "POST") &&
|
|
!forward_body_data(conn, in, INVALID_SOCKET, NULL)) {
|
|
goto done;
|
|
}
|
|
|
|
// Close so child gets an EOF.
|
|
fclose(in);
|
|
in = NULL;
|
|
fdin[1] = -1;
|
|
|
|
// Now read CGI reply into a buffer. We need to set correct
|
|
// status code, thus we need to see all HTTP headers first.
|
|
// Do not send anything back to client, until we buffer in all
|
|
// HTTP headers.
|
|
data_len = 0;
|
|
headers_len = read_request(out, conn, buf, sizeof(buf), &data_len);
|
|
if (headers_len <= 0) {
|
|
send_http_error(conn, 500, http_500_error,
|
|
"CGI program sent malformed or too big (>%u bytes) "
|
|
"HTTP headers: [%.*s]",
|
|
(unsigned) sizeof(buf), data_len, buf);
|
|
goto done;
|
|
}
|
|
pbuf = buf;
|
|
buf[headers_len - 1] = '\0';
|
|
parse_http_headers(&pbuf, &ri);
|
|
|
|
// Make up and send the status line
|
|
status_text = "OK";
|
|
if ((status = get_header(&ri, "Status")) != NULL) {
|
|
conn->status_code = atoi(status);
|
|
status_text = status;
|
|
while (isdigit(* (unsigned char *) status_text) || *status_text == ' ') {
|
|
status_text++;
|
|
}
|
|
} else if (get_header(&ri, "Location") != NULL) {
|
|
conn->status_code = 302;
|
|
} else {
|
|
conn->status_code = 200;
|
|
}
|
|
if (get_header(&ri, "Connection") != NULL &&
|
|
!mg_strcasecmp(get_header(&ri, "Connection"), "keep-alive")) {
|
|
conn->must_close = 1;
|
|
}
|
|
(void) mg_printf(conn, "HTTP/1.1 %d %s\r\n", conn->status_code,
|
|
status_text);
|
|
|
|
// Send headers
|
|
for (i = 0; i < ri.num_headers; i++) {
|
|
mg_printf(conn, "%s: %s\r\n",
|
|
ri.http_headers[i].name, ri.http_headers[i].value);
|
|
}
|
|
mg_write(conn, "\r\n", 2);
|
|
|
|
// Send chunk of data that may have been read after the headers
|
|
conn->num_bytes_sent += mg_write(conn, buf + headers_len,
|
|
(size_t)(data_len - headers_len));
|
|
|
|
// Read the rest of CGI output and send to the client
|
|
send_file_data(conn, out, 0, INT64_MAX);
|
|
|
|
done:
|
|
if (pid != (pid_t) -1) {
|
|
kill(pid, SIGKILL);
|
|
}
|
|
if (fdin[0] != -1) {
|
|
close(fdin[0]);
|
|
}
|
|
if (fdout[1] != -1) {
|
|
close(fdout[1]);
|
|
}
|
|
|
|
if (in != NULL) {
|
|
fclose(in);
|
|
} else if (fdin[1] != -1) {
|
|
close(fdin[1]);
|
|
}
|
|
|
|
if (out != NULL) {
|
|
fclose(out);
|
|
} else if (fdout[0] != -1) {
|
|
close(fdout[0]);
|
|
}
|
|
}
|
|
#endif // !NO_CGI
|
|
|