mirror of
https://github.com/cesanta/mongoose.git
synced 2024-11-24 19:19:00 +08:00
moved CGI and IO into separate files
This commit is contained in:
parent
7fe6b29218
commit
66824e7757
@ -29,7 +29,8 @@ VERSION = $(shell perl -lne \
|
||||
SOURCES = src/internal.h src/util.c src/string.c src/parse_date.c \
|
||||
src/options.c src/crypto.c src/auth.c src/win32.c src/unix.c \
|
||||
src/mg_printf.c src/ssl.c src/http_client.c src/mime.c \
|
||||
src/directory.c src/log.c src/mongoose.c src/lua.c
|
||||
src/directory.c src/log.c src/parse_http.c src/io.c src/cgi.c \
|
||||
src/mongoose.c src/lua.c
|
||||
|
||||
TINY_SOURCES = ../mongoose.c main.c
|
||||
LUA_SOURCES = $(TINY_SOURCES) sqlite3.c lsqlite3.c lua_5.2.1.c
|
||||
|
383
build/src/cgi.c
Normal file
383
build/src/cgi.c
Normal file
@ -0,0 +1,383 @@
|
||||
#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], fdout[2];
|
||||
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;
|
||||
|
||||
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
|
||||
|
@ -236,3 +236,36 @@ static void handle_directory_request(struct mg_connection *conn,
|
||||
conn->status_code = 200;
|
||||
}
|
||||
|
||||
// For a given PUT path, create all intermediate subdirectories
|
||||
// for given path. Return 0 if the path itself is a directory,
|
||||
// or -1 on error, 1 if OK.
|
||||
static int put_dir(const char *path) {
|
||||
char buf[PATH_MAX];
|
||||
const char *s, *p;
|
||||
struct file file = STRUCT_FILE_INITIALIZER;
|
||||
int len, res = 1;
|
||||
|
||||
for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
|
||||
len = p - path;
|
||||
if (len >= (int) sizeof(buf)) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
memcpy(buf, path, len);
|
||||
buf[len] = '\0';
|
||||
|
||||
// Try to create intermediate directory
|
||||
DEBUG_TRACE(("mkdir(%s)", buf));
|
||||
if (!mg_stat(buf, &file) && mg_mkdir(buf, 0755) != 0) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Is path itself a directory?
|
||||
if (p[1] == '\0') {
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
187
build/src/io.c
Normal file
187
build/src/io.c
Normal file
@ -0,0 +1,187 @@
|
||||
#include "internal.h"
|
||||
|
||||
// Return number of bytes left to read for this connection
|
||||
static int64_t left_to_read(const struct mg_connection *conn) {
|
||||
return conn->content_len + conn->request_len - conn->num_bytes_read;
|
||||
}
|
||||
|
||||
// Write data to the IO channel - opened file descriptor, socket or SSL
|
||||
// descriptor. Return number of bytes written.
|
||||
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
|
||||
int64_t len) {
|
||||
int64_t sent;
|
||||
int n, k;
|
||||
|
||||
(void) ssl; // Get rid of warning
|
||||
sent = 0;
|
||||
while (sent < len) {
|
||||
|
||||
// How many bytes we send in this iteration
|
||||
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
|
||||
|
||||
#if !defined(NO_SSL)
|
||||
if (ssl != NULL) {
|
||||
n = SSL_write(ssl, buf + sent, k);
|
||||
} else
|
||||
#endif
|
||||
if (fp != NULL) {
|
||||
n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
|
||||
if (ferror(fp))
|
||||
n = -1;
|
||||
} else {
|
||||
n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
if (n <= 0)
|
||||
break;
|
||||
|
||||
sent += n;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
|
||||
// Return negative value on error, or number of bytes read on success.
|
||||
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
int nread;
|
||||
|
||||
if (len <= 0) return 0;
|
||||
if (fp != NULL) {
|
||||
// Use read() instead of fread(), because if we're reading from the CGI
|
||||
// pipe, fread() may block until IO buffer is filled up. We cannot afford
|
||||
// to block and must pass all read bytes immediately to the client.
|
||||
nread = read(fileno(fp), buf, (size_t) len);
|
||||
#ifndef NO_SSL
|
||||
} else if (conn->ssl != NULL) {
|
||||
nread = SSL_read(conn->ssl, buf, len);
|
||||
#endif
|
||||
} else {
|
||||
nread = recv(conn->client.sock, buf, (size_t) len, 0);
|
||||
}
|
||||
if (nread > 0) {
|
||||
conn->num_bytes_read += nread;
|
||||
}
|
||||
|
||||
return conn->ctx->stop_flag ? -1 : nread;
|
||||
}
|
||||
|
||||
static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
int n, nread = 0;
|
||||
|
||||
while (len > 0 && conn->ctx->stop_flag == 0) {
|
||||
n = pull(fp, conn, buf + nread, len);
|
||||
if (n < 0) {
|
||||
nread = n; // Propagate the error
|
||||
break;
|
||||
} else if (n == 0) {
|
||||
break; // No more data to read
|
||||
} else {
|
||||
nread += n;
|
||||
len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_read(struct mg_connection *conn, void *buf, int len) {
|
||||
int n, buffered_len, nread = 0;
|
||||
int64_t left;
|
||||
|
||||
if (conn->content_len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// conn->buf body
|
||||
// |=================|==========|===============|
|
||||
// |<--request_len-->| |
|
||||
// |<-----------data_len------->| conn->buf + conn->buf_size
|
||||
|
||||
// First, check for data buffered in conn->buf by read_request().
|
||||
if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
|
||||
char *body = conn->buf + conn->request_len;
|
||||
if (buffered_len > len) buffered_len = len;
|
||||
if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
|
||||
|
||||
memcpy(buf, body, (size_t) buffered_len);
|
||||
memmove(body, body + buffered_len,
|
||||
&conn->buf[conn->data_len] - &body[buffered_len]);
|
||||
len -= buffered_len;
|
||||
conn->data_len -= buffered_len;
|
||||
nread += buffered_len;
|
||||
}
|
||||
|
||||
// Read data from the socket.
|
||||
if (len > 0 && (left = left_to_read(conn)) > 0) {
|
||||
if (left < len) {
|
||||
len = (int) left;
|
||||
}
|
||||
n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
|
||||
nread = n >= 0 ? nread + n : n;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_write(struct mg_connection *conn, const void *buf, int len) {
|
||||
return push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
|
||||
(int64_t) len);
|
||||
}
|
||||
|
||||
// Keep reading the input (either opened file descriptor fd, or socket sock,
|
||||
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
|
||||
// buffer (which marks the end of HTTP request). Buffer buf may already
|
||||
// have some data. The length of the data is stored in nread.
|
||||
// Upon every read operation, increase nread by the number of bytes read.
|
||||
static int read_request(FILE *fp, struct mg_connection *conn,
|
||||
char *buf, int bufsiz, int *nread) {
|
||||
int request_len, n = 0;
|
||||
|
||||
request_len = get_request_len(buf, *nread);
|
||||
while (conn->ctx->stop_flag == 0 &&
|
||||
*nread < bufsiz &&
|
||||
request_len == 0 &&
|
||||
(n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
|
||||
*nread += n;
|
||||
assert(*nread <= bufsiz);
|
||||
request_len = get_request_len(buf, *nread);
|
||||
}
|
||||
|
||||
return request_len <= 0 && n <= 0 ? -1 : request_len;
|
||||
}
|
||||
|
||||
// Send len bytes from the opened file to the client.
|
||||
static void send_file_data(struct mg_connection *conn, FILE *fp,
|
||||
int64_t offset, int64_t len) {
|
||||
char buf[MG_BUF_LEN];
|
||||
int num_read, num_written, to_read;
|
||||
|
||||
// If offset is beyond file boundaries, don't send anything
|
||||
if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (len > 0) {
|
||||
// Calculate how much to read from the file in the buffer
|
||||
to_read = sizeof(buf);
|
||||
if ((int64_t) to_read > len) {
|
||||
to_read = (int) len;
|
||||
}
|
||||
|
||||
// Read from file, exit the loop on error
|
||||
if ((num_read = fread(buf, 1, (size_t) to_read, fp)) <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Send read bytes to the client, exit the loop on error
|
||||
if ((num_written = mg_write(conn, buf, (size_t) num_read)) != num_read) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Both read and were successful, adjust counters
|
||||
conn->num_bytes_sent += num_written;
|
||||
len -= num_written;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,5 @@
|
||||
#include "internal.h"
|
||||
|
||||
// Return number of bytes left to read for this connection
|
||||
static int64_t left_to_read(const struct mg_connection *conn) {
|
||||
return conn->content_len + conn->request_len - conn->num_bytes_read;
|
||||
}
|
||||
|
||||
static int call_user(int type, struct mg_connection *conn, void *p) {
|
||||
if (conn != NULL && conn->ctx != NULL) {
|
||||
conn->event.user_data = conn->ctx->user_data;
|
||||
@ -120,177 +115,6 @@ static void send_http_error(struct mg_connection *conn, int status,
|
||||
}
|
||||
}
|
||||
|
||||
// Write data to the IO channel - opened file descriptor, socket or SSL
|
||||
// descriptor. Return number of bytes written.
|
||||
static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
|
||||
int64_t len) {
|
||||
int64_t sent;
|
||||
int n, k;
|
||||
|
||||
(void) ssl; // Get rid of warning
|
||||
sent = 0;
|
||||
while (sent < len) {
|
||||
|
||||
// How many bytes we send in this iteration
|
||||
k = len - sent > INT_MAX ? INT_MAX : (int) (len - sent);
|
||||
|
||||
#if !defined(NO_SSL)
|
||||
if (ssl != NULL) {
|
||||
n = SSL_write(ssl, buf + sent, k);
|
||||
} else
|
||||
#endif
|
||||
if (fp != NULL) {
|
||||
n = (int) fwrite(buf + sent, 1, (size_t) k, fp);
|
||||
if (ferror(fp))
|
||||
n = -1;
|
||||
} else {
|
||||
n = send(sock, buf + sent, (size_t) k, MSG_NOSIGNAL);
|
||||
}
|
||||
|
||||
if (n <= 0)
|
||||
break;
|
||||
|
||||
sent += n;
|
||||
}
|
||||
|
||||
return sent;
|
||||
}
|
||||
|
||||
// Read from IO channel - opened file descriptor, socket, or SSL descriptor.
|
||||
// Return negative value on error, or number of bytes read on success.
|
||||
static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
int nread;
|
||||
|
||||
if (len <= 0) return 0;
|
||||
if (fp != NULL) {
|
||||
// Use read() instead of fread(), because if we're reading from the CGI
|
||||
// pipe, fread() may block until IO buffer is filled up. We cannot afford
|
||||
// to block and must pass all read bytes immediately to the client.
|
||||
nread = read(fileno(fp), buf, (size_t) len);
|
||||
#ifndef NO_SSL
|
||||
} else if (conn->ssl != NULL) {
|
||||
nread = SSL_read(conn->ssl, buf, len);
|
||||
#endif
|
||||
} else {
|
||||
nread = recv(conn->client.sock, buf, (size_t) len, 0);
|
||||
}
|
||||
if (nread > 0) {
|
||||
conn->num_bytes_read += nread;
|
||||
}
|
||||
|
||||
return conn->ctx->stop_flag ? -1 : nread;
|
||||
}
|
||||
|
||||
static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) {
|
||||
int n, nread = 0;
|
||||
|
||||
while (len > 0 && conn->ctx->stop_flag == 0) {
|
||||
n = pull(fp, conn, buf + nread, len);
|
||||
if (n < 0) {
|
||||
nread = n; // Propagate the error
|
||||
break;
|
||||
} else if (n == 0) {
|
||||
break; // No more data to read
|
||||
} else {
|
||||
nread += n;
|
||||
len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_read(struct mg_connection *conn, void *buf, int len) {
|
||||
int n, buffered_len, nread = 0;
|
||||
int64_t left;
|
||||
|
||||
if (conn->content_len <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// conn->buf body
|
||||
// |=================|==========|===============|
|
||||
// |<--request_len-->| |
|
||||
// |<-----------data_len------->| conn->buf + conn->buf_size
|
||||
|
||||
// First, check for data buffered in conn->buf by read_request().
|
||||
if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) {
|
||||
char *body = conn->buf + conn->request_len;
|
||||
if (buffered_len > len) buffered_len = len;
|
||||
if (buffered_len > conn->content_len) buffered_len = (int)conn->content_len;
|
||||
|
||||
memcpy(buf, body, (size_t) buffered_len);
|
||||
memmove(body, body + buffered_len,
|
||||
&conn->buf[conn->data_len] - &body[buffered_len]);
|
||||
len -= buffered_len;
|
||||
conn->data_len -= buffered_len;
|
||||
nread += buffered_len;
|
||||
}
|
||||
|
||||
// Read data from the socket.
|
||||
if (len > 0 && (left = left_to_read(conn)) > 0) {
|
||||
if (left < len) {
|
||||
len = (int) left;
|
||||
}
|
||||
n = pull_all(NULL, conn, (char *) buf + nread, (int) len);
|
||||
nread = n >= 0 ? nread + n : n;
|
||||
}
|
||||
|
||||
return nread;
|
||||
}
|
||||
|
||||
int mg_write(struct mg_connection *conn, const void *buf, int len) {
|
||||
return push(NULL, conn->client.sock, conn->ssl, (const char *) buf,
|
||||
(int64_t) len);
|
||||
}
|
||||
|
||||
int mg_get_var(const char *data, size_t data_len, const char *name,
|
||||
char *dst, size_t dst_len) {
|
||||
const char *p, *e, *s;
|
||||
size_t name_len;
|
||||
int len;
|
||||
|
||||
if (dst == NULL || dst_len == 0) {
|
||||
len = -2;
|
||||
} else if (data == NULL || name == NULL || data_len == 0) {
|
||||
len = -1;
|
||||
dst[0] = '\0';
|
||||
} else {
|
||||
name_len = strlen(name);
|
||||
e = data + data_len;
|
||||
len = -1;
|
||||
dst[0] = '\0';
|
||||
|
||||
// data is "var1=val1&var2=val2...". Find variable first
|
||||
for (p = data; p + name_len < e; p++) {
|
||||
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
|
||||
!mg_strncasecmp(name, p, name_len)) {
|
||||
|
||||
// Point p to variable value
|
||||
p += name_len + 1;
|
||||
|
||||
// Point s to the end of the value
|
||||
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
||||
if (s == NULL) {
|
||||
s = e;
|
||||
}
|
||||
assert(s >= p);
|
||||
|
||||
// Decode variable into destination buffer
|
||||
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
||||
|
||||
// Redirect error code from -1 to -2 (destination buffer too small).
|
||||
if (len == -1) {
|
||||
len = -2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
// Return 1 if real file has been found, 0 otherwise
|
||||
static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
|
||||
size_t buf_len, struct file *filep) {
|
||||
@ -366,44 +190,6 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf,
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Send len bytes from the opened file to the client.
|
||||
static void send_file_data(struct mg_connection *conn, FILE *fp,
|
||||
int64_t offset, int64_t len) {
|
||||
char buf[MG_BUF_LEN];
|
||||
int num_read, num_written, to_read;
|
||||
|
||||
// If offset is beyond file boundaries, don't send anything
|
||||
if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (len > 0) {
|
||||
// Calculate how much to read from the file in the buffer
|
||||
to_read = sizeof(buf);
|
||||
if ((int64_t) to_read > len) {
|
||||
to_read = (int) len;
|
||||
}
|
||||
|
||||
// Read from file, exit the loop on error
|
||||
if ((num_read = fread(buf, 1, (size_t) to_read, fp)) <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Send read bytes to the client, exit the loop on error
|
||||
if ((num_written = mg_write(conn, buf, (size_t) num_read)) != num_read) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Both read and were successful, adjust counters
|
||||
conn->num_bytes_sent += num_written;
|
||||
len -= num_written;
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
|
||||
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
|
||||
}
|
||||
|
||||
static void gmt_time_string(char *buf, size_t buf_len, time_t *t) {
|
||||
strftime(buf, buf_len, "%a, %d %b %Y %H:%M:%S GMT", gmtime(t));
|
||||
}
|
||||
@ -513,88 +299,6 @@ void mg_send_file(struct mg_connection *conn, const char *path) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Parse HTTP headers from the given buffer, advance buffer to the point
|
||||
// where parsing stopped.
|
||||
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
|
||||
ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
|
||||
ri->http_headers[i].value = skip(buf, "\r\n");
|
||||
if (ri->http_headers[i].name[0] == '\0')
|
||||
break;
|
||||
ri->num_headers = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_valid_http_method(const char *method) {
|
||||
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
|
||||
!strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
|
||||
!strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
|
||||
!strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
|
||||
|| !strcmp(method, "MKCOL")
|
||||
;
|
||||
}
|
||||
|
||||
// Parse HTTP request, fill in mg_request_info structure.
|
||||
// This function modifies the buffer by NUL-terminating
|
||||
// HTTP request components, header names and header values.
|
||||
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
|
||||
int is_request, request_length = get_request_len(buf, len);
|
||||
if (request_length > 0) {
|
||||
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
|
||||
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
|
||||
ri->num_headers = 0;
|
||||
|
||||
buf[request_length - 1] = '\0';
|
||||
|
||||
// RFC says that all initial whitespaces should be ingored
|
||||
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
|
||||
buf++;
|
||||
}
|
||||
ri->request_method = skip(&buf, " ");
|
||||
ri->uri = skip(&buf, " ");
|
||||
ri->http_version = skip(&buf, "\r\n");
|
||||
|
||||
// HTTP message could be either HTTP request or HTTP response, e.g.
|
||||
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
|
||||
is_request = is_valid_http_method(ri->request_method);
|
||||
if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
|
||||
(!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
|
||||
request_length = -1;
|
||||
} else {
|
||||
if (is_request) {
|
||||
ri->http_version += 5;
|
||||
}
|
||||
parse_http_headers(&buf, ri);
|
||||
}
|
||||
}
|
||||
return request_length;
|
||||
}
|
||||
|
||||
// Keep reading the input (either opened file descriptor fd, or socket sock,
|
||||
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
|
||||
// buffer (which marks the end of HTTP request). Buffer buf may already
|
||||
// have some data. The length of the data is stored in nread.
|
||||
// Upon every read operation, increase nread by the number of bytes read.
|
||||
static int read_request(FILE *fp, struct mg_connection *conn,
|
||||
char *buf, int bufsiz, int *nread) {
|
||||
int request_len, n = 0;
|
||||
|
||||
request_len = get_request_len(buf, *nread);
|
||||
while (conn->ctx->stop_flag == 0 &&
|
||||
*nread < bufsiz &&
|
||||
request_len == 0 &&
|
||||
(n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) {
|
||||
*nread += n;
|
||||
assert(*nread <= bufsiz);
|
||||
request_len = get_request_len(buf, *nread);
|
||||
}
|
||||
|
||||
return request_len <= 0 && n <= 0 ? -1 : request_len;
|
||||
}
|
||||
|
||||
// For given directory path, substitute it to valid index file.
|
||||
// Return 0 if index file has been found, -1 if not found.
|
||||
// If the file is found, it's stats is returned in stp.
|
||||
@ -653,420 +357,6 @@ static int is_not_modified(const struct mg_connection *conn,
|
||||
(ims != NULL && filep->modification_time <= parse_date_string(ims));
|
||||
}
|
||||
|
||||
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], fdout[2];
|
||||
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;
|
||||
|
||||
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
|
||||
|
||||
// For a given PUT path, create all intermediate subdirectories
|
||||
// for given path. Return 0 if the path itself is a directory,
|
||||
// or -1 on error, 1 if OK.
|
||||
static int put_dir(const char *path) {
|
||||
char buf[PATH_MAX];
|
||||
const char *s, *p;
|
||||
struct file file = STRUCT_FILE_INITIALIZER;
|
||||
int len, res = 1;
|
||||
|
||||
for (s = p = path + 2; (p = strchr(s, '/')) != NULL; s = ++p) {
|
||||
len = p - path;
|
||||
if (len >= (int) sizeof(buf)) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
memcpy(buf, path, len);
|
||||
buf[len] = '\0';
|
||||
|
||||
// Try to create intermediate directory
|
||||
DEBUG_TRACE(("mkdir(%s)", buf));
|
||||
if (!mg_stat(buf, &file) && mg_mkdir(buf, 0755) != 0) {
|
||||
res = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Is path itself a directory?
|
||||
if (p[1] == '\0') {
|
||||
res = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void mkcol(struct mg_connection *conn, const char *path) {
|
||||
int rc, body_len;
|
||||
struct de de;
|
||||
|
63
build/src/parse_http.c
Normal file
63
build/src/parse_http.c
Normal file
@ -0,0 +1,63 @@
|
||||
#include "internal.h"
|
||||
|
||||
// Parse HTTP headers from the given buffer, advance buffer to the point
|
||||
// where parsing stopped.
|
||||
static void parse_http_headers(char **buf, struct mg_request_info *ri) {
|
||||
int i;
|
||||
|
||||
for (i = 0; i < (int) ARRAY_SIZE(ri->http_headers); i++) {
|
||||
ri->http_headers[i].name = skip_quoted(buf, ":", " ", 0);
|
||||
ri->http_headers[i].value = skip(buf, "\r\n");
|
||||
if (ri->http_headers[i].name[0] == '\0')
|
||||
break;
|
||||
ri->num_headers = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
static int is_valid_http_method(const char *method) {
|
||||
return !strcmp(method, "GET") || !strcmp(method, "POST") ||
|
||||
!strcmp(method, "HEAD") || !strcmp(method, "CONNECT") ||
|
||||
!strcmp(method, "PUT") || !strcmp(method, "DELETE") ||
|
||||
!strcmp(method, "OPTIONS") || !strcmp(method, "PROPFIND")
|
||||
|| !strcmp(method, "MKCOL");
|
||||
}
|
||||
|
||||
// Parse HTTP request, fill in mg_request_info structure.
|
||||
// This function modifies the buffer by NUL-terminating
|
||||
// HTTP request components, header names and header values.
|
||||
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
|
||||
int is_request, request_length = get_request_len(buf, len);
|
||||
if (request_length > 0) {
|
||||
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
|
||||
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
|
||||
ri->num_headers = 0;
|
||||
|
||||
buf[request_length - 1] = '\0';
|
||||
|
||||
// RFC says that all initial whitespaces should be ingored
|
||||
while (*buf != '\0' && isspace(* (unsigned char *) buf)) {
|
||||
buf++;
|
||||
}
|
||||
ri->request_method = skip(&buf, " ");
|
||||
ri->uri = skip(&buf, " ");
|
||||
ri->http_version = skip(&buf, "\r\n");
|
||||
|
||||
// HTTP message could be either HTTP request or HTTP response, e.g.
|
||||
// "GET / HTTP/1.0 ...." or "HTTP/1.0 200 OK ..."
|
||||
is_request = is_valid_http_method(ri->request_method);
|
||||
if ((is_request && memcmp(ri->http_version, "HTTP/", 5) != 0) ||
|
||||
(!is_request && memcmp(ri->request_method, "HTTP/", 5) != 0)) {
|
||||
request_length = -1;
|
||||
} else {
|
||||
if (is_request) {
|
||||
ri->http_version += 5;
|
||||
}
|
||||
parse_http_headers(&buf, ri);
|
||||
}
|
||||
}
|
||||
return request_length;
|
||||
}
|
||||
|
||||
static int parse_range_header(const char *header, int64_t *a, int64_t *b) {
|
||||
return sscanf(header, "bytes=%" INT64_FMT "-%" INT64_FMT, a, b);
|
||||
}
|
@ -374,3 +374,50 @@ int mg_get_cookie(const char *cookie_header, const char *var_name,
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
int mg_get_var(const char *data, size_t data_len, const char *name,
|
||||
char *dst, size_t dst_len) {
|
||||
const char *p, *e, *s;
|
||||
size_t name_len;
|
||||
int len;
|
||||
|
||||
if (dst == NULL || dst_len == 0) {
|
||||
len = -2;
|
||||
} else if (data == NULL || name == NULL || data_len == 0) {
|
||||
len = -1;
|
||||
dst[0] = '\0';
|
||||
} else {
|
||||
name_len = strlen(name);
|
||||
e = data + data_len;
|
||||
len = -1;
|
||||
dst[0] = '\0';
|
||||
|
||||
// data is "var1=val1&var2=val2...". Find variable first
|
||||
for (p = data; p + name_len < e; p++) {
|
||||
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
|
||||
!mg_strncasecmp(name, p, name_len)) {
|
||||
|
||||
// Point p to variable value
|
||||
p += name_len + 1;
|
||||
|
||||
// Point s to the end of the value
|
||||
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
||||
if (s == NULL) {
|
||||
s = e;
|
||||
}
|
||||
assert(s >= p);
|
||||
|
||||
// Decode variable into destination buffer
|
||||
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
||||
|
||||
// Redirect error code from -1 to -2 (destination buffer too small).
|
||||
if (len == -1) {
|
||||
len = -2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
1018
mongoose.c
1018
mongoose.c
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user