Commit 66824e77 authored by Sergey Lyubka's avatar Sergey Lyubka

moved CGI and IO into separate files

parent 7fe6b292
......@@ -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
......
#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;
}
#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;
}
}
#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;
......
#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;
}
......@@ -870,6 +870,53 @@ 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;
}
static const char *month_names[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
......@@ -2647,6 +2694,39 @@ 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;
}
static void log_header(const struct mg_connection *conn, const char *header,
FILE *fp) {
......@@ -2691,124 +2771,71 @@ static void log_access(const struct mg_connection *conn) {
fclose(fp);
}
// 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;
}
// 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;
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;
conn->event.type = type;
conn->event.event_param = p;
conn->event.request_info = &conn->request_info;
conn->event.conn = conn;
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;
}
return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
0 : conn->ctx->event_handler(&conn->event);
}
static FILE *mg_fopen(const char *path, const char *mode) {
#ifdef _WIN32
wchar_t wbuf[PATH_MAX], wmode[20];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
return _wfopen(wbuf, wmode);
#else
return fopen(path, mode);
#endif
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");
}
// Print error message to the opened error log stream.
static void cry(struct mg_connection *conn, const char *fmt, ...) {
char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN];
va_list ap;
FILE *fp;
time_t timestamp;
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
// Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the
// same way string option can.
if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
if (fp != NULL) {
flockfile(fp);
timestamp = time(NULL);
// 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;
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
src_addr);
buf[request_length - 1] = '\0';
if (conn->request_info.request_method != NULL) {
fprintf(fp, "%s %s: ", conn->request_info.request_method,
conn->request_info.uri);
// 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");
fprintf(fp, "%s", buf);
fputc('\n', fp);
funlockfile(fp);
fclose(fp);
// 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);
}
}
const char *mg_version(void) {
return MONGOOSE_VERSION;
}
// HTTP 1.1 assumes keep alive if "Connection:" header is not set
// This function must tolerate situations when connection info is not
// set up, for example if request parsing failed.
static int should_keep_alive(const struct mg_connection *conn) {
const char *http_version = conn->request_info.http_version;
const char *header = mg_get_header(conn, "Connection");
if (conn->must_close ||
conn->status_code == 401 ||
mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 ||
(header != NULL && mg_strcasecmp(header, "keep-alive") != 0) ||
(header == NULL && http_version && strcmp(http_version, "1.1"))) {
return 0;
}
return 1;
return request_length;
}
static const char *suggest_connection_header(const struct mg_connection *conn) {
return should_keep_alive(conn) ? "keep-alive" : "close";
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 send_http_error(struct mg_connection *conn, int status,
const char *reason, const char *fmt, ...) {
char buf[MG_BUF_LEN];
va_list ap;
int len = 0;
conn->status_code = status;
buf[0] = '\0';
// Errors 1xx, 204 and 304 MUST NOT send a body
if (status > 199 && status != 204 && status != 304) {
len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason);
buf[len++] = '\n';
va_start(ap, fmt);
len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
va_end(ap);
}
DEBUG_TRACE(("[%s]", buf));
if (call_user(MG_HTTP_ERROR, conn, (void *) (long) status) == 0) {
mg_printf(conn, "HTTP/1.1 %d %s\r\n"
"Content-Length: %d\r\n"
"Connection: %s\r\n\r\n", status, reason, len,
suggest_connection_header(conn));
conn->num_bytes_sent += mg_printf(conn, "%s", buf);
}
// 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
......@@ -2935,126 +2962,26 @@ int mg_write(struct mg_connection *conn, const void *buf, int len) {
(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';
// 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;
// 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) {
struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri,
*root = conn->ctx->config[DOCUMENT_ROOT];
char *p;
int match_len;
char gz_path[PATH_MAX];
char const* accept_encoding;
// No filesystem access
if (root == NULL) {
return 0;
}
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
rewrite = conn->ctx->config[REWRITE];
while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
uri + match_len);
break;
}
}
if (mg_stat(buf, filep)) {
return 1;
}
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
if (strstr(accept_encoding,"gzip") != NULL) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
if (mg_stat(gz_path, filep)) {
filep->gzipped = 1;
return 1;
}
}
}
// Support PATH_INFO for CGI scripts.
for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) {
if (*p == '/') {
*p = '\0';
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
mg_stat(buf, filep)) {
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn->path_info = p + 1;
memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
p[1] = '/';
return 1;
} else {
*p = '/';
}
}
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 0;
return request_len <= 0 && n <= 0 ? -1 : request_len;
}
// Send len bytes from the opened file to the client.
......@@ -3091,315 +3018,64 @@ static void send_file_data(struct mg_connection *conn, FILE *fp,
}
}
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));
}
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;
static void construct_etag(char *buf, size_t buf_len,
const struct file *filep) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
(unsigned long) filep->modification_time, filep->size);
}
expect = mg_get_header(conn, "Expect");
assert(fp != NULL);
static void fclose_on_exec(FILE *fp) {
if (fp != NULL) {
#ifndef _WIN32
fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
#endif
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");
}
}
static void handle_file_request(struct mg_connection *conn, const char *path,
struct file *filep) {
char date[64], lm[64], etag[64], range[64];
const char *msg = "OK", *hdr;
time_t curtime = time(NULL);
int64_t cl, r1, r2;
struct vec mime_vec;
int n;
char gz_path[PATH_MAX];
char const* encoding = "";
FILE *fp;
get_mime_type(conn->ctx, path, &mime_vec);
cl = filep->size;
conn->status_code = 200;
range[0] = '\0';
buffered_len = conn->data_len - conn->request_len;
body = conn->buf + conn->request_len;
assert(buffered_len >= 0);
// if this file is in fact a pre-gzipped file, rewrite its filename
// it's important to rewrite the filename after resolving
// the mime type from it, to preserve the actual file's type
if (filep->gzipped) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", path);
path = gz_path;
encoding = "Content-Encoding: gzip\r\n";
if (buffered_len > 0) {
if ((int64_t) buffered_len > conn->content_len) {
buffered_len = (int) conn->content_len;
}
if ((fp = mg_fopen(path, "rb")) == NULL) {
send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO));
return;
push(fp, sock, ssl, body, (int64_t) buffered_len);
memmove((char *) body, body + buffered_len, buffered_len);
conn->data_len -= buffered_len;
}
fclose_on_exec(fp);
// If Range: header specified, act accordingly
r1 = r2 = 0;
hdr = mg_get_header(conn, "Range");
if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
r1 >= 0 && r2 >= 0) {
// actually, range requests don't play well with a pre-gzipped
// file (since the range is specified in the uncmpressed space)
if (filep->gzipped) {
send_http_error(conn, 501, "Not Implemented",
"range requests in gzipped files are not supported");
return;
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);
}
conn->status_code = 206;
cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1;
mg_snprintf(range, sizeof(range),
"Content-Range: bytes "
"%" INT64_FMT "-%"
INT64_FMT "/%" INT64_FMT "\r\n",
r1, r1 + cl - 1, filep->size);
msg = "Partial Content";
nread = pull(NULL, conn, buf, (int) left);
if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) {
break;
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string(date, sizeof(date), &curtime);
gmt_time_string(lm, sizeof(lm), &filep->modification_time);
construct_etag(etag, sizeof(etag), filep);
(void) mg_printf(conn,
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Etag: %s\r\n"
"Content-Type: %.*s\r\n"
"Content-Length: %" INT64_FMT "\r\n"
"Connection: %s\r\n"
"Accept-Ranges: bytes\r\n"
"%s%s%s\r\n",
conn->status_code, msg, date, lm, etag, (int) mime_vec.len,
mime_vec.ptr, cl, suggest_connection_header(conn), range, encoding,
EXTRA_HTTP_HEADERS);
if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
send_file_data(conn, fp, r1, cl);
}
fclose(fp);
}
void mg_send_file(struct mg_connection *conn, const char *path) {
struct file file = STRUCT_FILE_INITIALIZER;
if (mg_stat(path, &file)) {
handle_file_request(conn, path, &file);
} else {
send_http_error(conn, 404, "Not Found", "%s", "File not found");
if (left_to_read(conn) == 0) {
success = nread >= 0;
}
}
// 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;
// Each error code path in this function must send an error
if (!success) {
send_http_error(conn, 577, http_500_error, "%s", "");
}
}
return success;
}
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.
static int substitute_index_file(struct mg_connection *conn, char *path,
size_t path_len, struct file *filep) {
const char *list = conn->ctx->config[INDEX_FILES];
struct file file = STRUCT_FILE_INITIALIZER;
struct vec filename_vec;
size_t n = strlen(path);
int found = 0;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while (n > 0 && path[n - 1] == '/') {
n--;
}
path[n] = '/';
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
// Ignore too long entries that may overflow path buffer
if (filename_vec.len > path_len - (n + 2))
continue;
// Prepare full path to the index file
mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
// Does it exist?
if (mg_stat(path, &file)) {
// Yes it does, break the loop
*filep = file;
found = 1;
break;
}
}
// If no index file exists, restore directory path
if (!found) {
path[n] = '\0';
}
return found;
}
// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct mg_connection *conn,
const struct file *filep) {
char etag[64];
const char *ims = mg_get_header(conn, "If-Modified-Since");
const char *inm = mg_get_header(conn, "If-None-Match");
construct_etag(etag, sizeof(etag), filep);
return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
(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.
......@@ -3724,38 +3400,362 @@ done:
}
#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;
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;
conn->event.type = type;
conn->event.event_param = p;
conn->event.request_info = &conn->request_info;
conn->event.conn = conn;
}
memcpy(buf, path, len);
buf[len] = '\0';
return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ?
0 : conn->ctx->event_handler(&conn->event);
}
// Try to create intermediate directory
DEBUG_TRACE(("mkdir(%s)", buf));
if (!mg_stat(buf, &file) && mg_mkdir(buf, 0755) != 0) {
res = -1;
break;
static FILE *mg_fopen(const char *path, const char *mode) {
#ifdef _WIN32
wchar_t wbuf[PATH_MAX], wmode[20];
to_unicode(path, wbuf, ARRAY_SIZE(wbuf));
MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, ARRAY_SIZE(wmode));
return _wfopen(wbuf, wmode);
#else
return fopen(path, mode);
#endif
}
// Print error message to the opened error log stream.
static void cry(struct mg_connection *conn, const char *fmt, ...) {
char buf[MG_BUF_LEN], src_addr[IP_ADDR_STR_LEN];
va_list ap;
FILE *fp;
time_t timestamp;
va_start(ap, fmt);
(void) vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
// Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the
// same way string option can.
if (call_user(MG_EVENT_LOG, conn, buf) == 0) {
fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL :
fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
if (fp != NULL) {
flockfile(fp);
timestamp = time(NULL);
sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
fprintf(fp, "[%010lu] [error] [client %s] ", (unsigned long) timestamp,
src_addr);
if (conn->request_info.request_method != NULL) {
fprintf(fp, "%s %s: ", conn->request_info.request_method,
conn->request_info.uri);
}
// Is path itself a directory?
if (p[1] == '\0') {
res = 0;
fprintf(fp, "%s", buf);
fputc('\n', fp);
funlockfile(fp);
fclose(fp);
}
}
}
return res;
const char *mg_version(void) {
return MONGOOSE_VERSION;
}
// HTTP 1.1 assumes keep alive if "Connection:" header is not set
// This function must tolerate situations when connection info is not
// set up, for example if request parsing failed.
static int should_keep_alive(const struct mg_connection *conn) {
const char *http_version = conn->request_info.http_version;
const char *header = mg_get_header(conn, "Connection");
if (conn->must_close ||
conn->status_code == 401 ||
mg_strcasecmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes") != 0 ||
(header != NULL && mg_strcasecmp(header, "keep-alive") != 0) ||
(header == NULL && http_version && strcmp(http_version, "1.1"))) {
return 0;
}
return 1;
}
static const char *suggest_connection_header(const struct mg_connection *conn) {
return should_keep_alive(conn) ? "keep-alive" : "close";
}
static void send_http_error(struct mg_connection *conn, int status,
const char *reason, const char *fmt, ...) {
char buf[MG_BUF_LEN];
va_list ap;
int len = 0;
conn->status_code = status;
buf[0] = '\0';
// Errors 1xx, 204 and 304 MUST NOT send a body
if (status > 199 && status != 204 && status != 304) {
len = mg_snprintf(buf, sizeof(buf), "Error %d: %s", status, reason);
buf[len++] = '\n';
va_start(ap, fmt);
len += mg_vsnprintf(buf + len, sizeof(buf) - len, fmt, ap);
va_end(ap);
}
DEBUG_TRACE(("[%s]", buf));
if (call_user(MG_HTTP_ERROR, conn, (void *) (long) status) == 0) {
mg_printf(conn, "HTTP/1.1 %d %s\r\n"
"Content-Length: %d\r\n"
"Connection: %s\r\n\r\n", status, reason, len,
suggest_connection_header(conn));
conn->num_bytes_sent += mg_printf(conn, "%s", buf);
}
}
// 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) {
struct vec a, b;
const char *rewrite, *uri = conn->request_info.uri,
*root = conn->ctx->config[DOCUMENT_ROOT];
char *p;
int match_len;
char gz_path[PATH_MAX];
char const* accept_encoding;
// No filesystem access
if (root == NULL) {
return 0;
}
// Using buf_len - 1 because memmove() for PATH_INFO may shift part
// of the path one byte on the right.
// If document_root is NULL, leave the file empty.
mg_snprintf(buf, buf_len - 1, "%s%s", root, uri);
rewrite = conn->ctx->config[REWRITE];
while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
mg_snprintf(buf, buf_len - 1, "%.*s%s", (int) b.len, b.ptr,
uri + match_len);
break;
}
}
if (mg_stat(buf, filep)) {
return 1;
}
// if we can't find the actual file, look for the file
// with the same name but a .gz extension. If we find it,
// use that and set the gzipped flag in the file struct
// to indicate that the response need to have the content-
// encoding: gzip header
// we can only do this if the browser declares support
if ((accept_encoding = mg_get_header(conn, "Accept-Encoding")) != NULL) {
if (strstr(accept_encoding,"gzip") != NULL) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", buf);
if (mg_stat(gz_path, filep)) {
filep->gzipped = 1;
return 1;
}
}
}
// Support PATH_INFO for CGI scripts.
for (p = buf + strlen(root == NULL ? "" : root); *p != '\0'; p++) {
if (*p == '/') {
*p = '\0';
if (match_prefix(conn->ctx->config[CGI_EXTENSIONS],
strlen(conn->ctx->config[CGI_EXTENSIONS]), buf) > 0 &&
mg_stat(buf, filep)) {
// Shift PATH_INFO block one character right, e.g.
// "/x.cgi/foo/bar\x00" => "/x.cgi\x00/foo/bar\x00"
// conn->path_info is pointing to the local variable "path" declared
// in handle_request(), so PATH_INFO is not valid after
// handle_request returns.
conn->path_info = p + 1;
memmove(p + 2, p + 1, strlen(p + 1) + 1); // +1 is for trailing \0
p[1] = '/';
return 1;
} else {
*p = '/';
}
}
}
return 0;
}
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));
}
static void construct_etag(char *buf, size_t buf_len,
const struct file *filep) {
snprintf(buf, buf_len, "\"%lx.%" INT64_FMT "\"",
(unsigned long) filep->modification_time, filep->size);
}
static void fclose_on_exec(FILE *fp) {
if (fp != NULL) {
#ifndef _WIN32
fcntl(fileno(fp), F_SETFD, FD_CLOEXEC);
#endif
}
}
static void handle_file_request(struct mg_connection *conn, const char *path,
struct file *filep) {
char date[64], lm[64], etag[64], range[64];
const char *msg = "OK", *hdr;
time_t curtime = time(NULL);
int64_t cl, r1, r2;
struct vec mime_vec;
int n;
char gz_path[PATH_MAX];
char const* encoding = "";
FILE *fp;
get_mime_type(conn->ctx, path, &mime_vec);
cl = filep->size;
conn->status_code = 200;
range[0] = '\0';
// if this file is in fact a pre-gzipped file, rewrite its filename
// it's important to rewrite the filename after resolving
// the mime type from it, to preserve the actual file's type
if (filep->gzipped) {
snprintf(gz_path, sizeof(gz_path), "%s.gz", path);
path = gz_path;
encoding = "Content-Encoding: gzip\r\n";
}
if ((fp = mg_fopen(path, "rb")) == NULL) {
send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO));
return;
}
fclose_on_exec(fp);
// If Range: header specified, act accordingly
r1 = r2 = 0;
hdr = mg_get_header(conn, "Range");
if (hdr != NULL && (n = parse_range_header(hdr, &r1, &r2)) > 0 &&
r1 >= 0 && r2 >= 0) {
// actually, range requests don't play well with a pre-gzipped
// file (since the range is specified in the uncmpressed space)
if (filep->gzipped) {
send_http_error(conn, 501, "Not Implemented",
"range requests in gzipped files are not supported");
return;
}
conn->status_code = 206;
cl = n == 2 ? (r2 > cl ? cl : r2) - r1 + 1: cl - r1;
mg_snprintf(range, sizeof(range),
"Content-Range: bytes "
"%" INT64_FMT "-%"
INT64_FMT "/%" INT64_FMT "\r\n",
r1, r1 + cl - 1, filep->size);
msg = "Partial Content";
}
// Prepare Etag, Date, Last-Modified headers. Must be in UTC, according to
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3
gmt_time_string(date, sizeof(date), &curtime);
gmt_time_string(lm, sizeof(lm), &filep->modification_time);
construct_etag(etag, sizeof(etag), filep);
(void) mg_printf(conn,
"HTTP/1.1 %d %s\r\n"
"Date: %s\r\n"
"Last-Modified: %s\r\n"
"Etag: %s\r\n"
"Content-Type: %.*s\r\n"
"Content-Length: %" INT64_FMT "\r\n"
"Connection: %s\r\n"
"Accept-Ranges: bytes\r\n"
"%s%s%s\r\n",
conn->status_code, msg, date, lm, etag, (int) mime_vec.len,
mime_vec.ptr, cl, suggest_connection_header(conn), range, encoding,
EXTRA_HTTP_HEADERS);
if (strcmp(conn->request_info.request_method, "HEAD") != 0) {
send_file_data(conn, fp, r1, cl);
}
fclose(fp);
}
void mg_send_file(struct mg_connection *conn, const char *path) {
struct file file = STRUCT_FILE_INITIALIZER;
if (mg_stat(path, &file)) {
handle_file_request(conn, path, &file);
} else {
send_http_error(conn, 404, "Not Found", "%s", "File not found");
}
}
// 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.
static int substitute_index_file(struct mg_connection *conn, char *path,
size_t path_len, struct file *filep) {
const char *list = conn->ctx->config[INDEX_FILES];
struct file file = STRUCT_FILE_INITIALIZER;
struct vec filename_vec;
size_t n = strlen(path);
int found = 0;
// The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and
// then append single directory separator character.
while (n > 0 && path[n - 1] == '/') {
n--;
}
path[n] = '/';
// Traverse index files list. For each entry, append it to the given
// path and see if the file exists. If it exists, break the loop
while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
// Ignore too long entries that may overflow path buffer
if (filename_vec.len > path_len - (n + 2))
continue;
// Prepare full path to the index file
mg_strlcpy(path + n + 1, filename_vec.ptr, filename_vec.len + 1);
// Does it exist?
if (mg_stat(path, &file)) {
// Yes it does, break the loop
*filep = file;
found = 1;
break;
}
}
// If no index file exists, restore directory path
if (!found) {
path[n] = '\0';
}
return found;
}
// Return True if we should reply 304 Not Modified.
static int is_not_modified(const struct mg_connection *conn,
const struct file *filep) {
char etag[64];
const char *ims = mg_get_header(conn, "If-Modified-Since");
const char *inm = mg_get_header(conn, "If-None-Match");
construct_etag(etag, sizeof(etag), filep);
return (inm != NULL && !mg_strcasecmp(etag, inm)) ||
(ims != NULL && filep->modification_time <= parse_date_string(ims));
}
static void mkcol(struct mg_connection *conn, const char *path) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment