Commit 546bec33 authored by valenok's avatar valenok

API change for mg_start: most binary compatible across releases.

parent 0b1e621c
...@@ -57,9 +57,9 @@ static int exit_flag; /* Program termination flag */ ...@@ -57,9 +57,9 @@ static int exit_flag; /* Program termination flag */
#define CONFIG_FILE "mongoose.conf" #define CONFIG_FILE "mongoose.conf"
#endif /* !CONFIG_FILE */ #endif /* !CONFIG_FILE */
static void #define MAX_OPTIONS 40
signal_handler(int sig_num)
{ static void signal_handler(int sig_num) {
#if !defined(_WIN32) #if !defined(_WIN32)
if (sig_num == SIGCHLD) { if (sig_num == SIGCHLD) {
do { do {
...@@ -74,116 +74,96 @@ signal_handler(int sig_num) ...@@ -74,116 +74,96 @@ signal_handler(int sig_num)
/* /*
* Edit the passwords file. * Edit the passwords file.
*/ */
static int static int mg_edit_passwords(const char *fname, const char *domain,
mg_edit_passwords(const char *fname, const char *domain, const char *user, const char *pass) {
const char *user, const char *pass)
{
struct mg_context *ctx; struct mg_context *ctx;
struct mg_config config; const char *options[] = {"authentication_domain", NULL, NULL};
int retval; int success;
memset(&config, 0, sizeof(config)); options[1] = domain;
config.auth_domain = (char *) domain; ctx = mg_start(NULL, options);
config.num_threads = "0"; success = mg_modify_passwords_file(ctx, fname, user, pass);
config.listening_ports = "";
ctx = mg_start(&config);
retval = mg_modify_passwords_file(ctx, fname, user, pass);
mg_stop(ctx); mg_stop(ctx);
return (retval); return success;
} }
#define OFFSET(x) offsetof(struct mg_config, x) static void show_usage_and_exit(void) {
const char **names;
static struct option_descriptor { int i, len;
const char *name;
const char *description; fprintf(stderr, "Mongoose version %s (c) Sergey Lyubka\n", mg_version());
size_t offset; fprintf(stderr, "Usage:\n");
} known_options[] = { fprintf(stderr, " mongoose -A <htpasswd_file> <realm> <user> <passwd>\n");
{"root", "\tWeb root directory", OFFSET(document_root)}, fprintf(stderr, " mongoose <config_file>\n");
{"index_files", "Index files", OFFSET(index_files)}, fprintf(stderr, " mongoose [-option value ...]\n");
{"ssl_cert", "SSL certificate file", OFFSET(ssl_certificate)}, fprintf(stderr, "OPTIONS:\n ");
{"ports", "Listening ports", OFFSET(listening_ports)},
{"dir_list", "Directory listing", OFFSET(enable_directory_listing)}, names = mg_get_valid_option_names();
{"protect", "URI to htpasswd mapping", OFFSET(protect)}, len = 2;
{"cgi_ext", "CGI extensions", OFFSET(cgi_extensions)}, for (i = 0; names[i] != NULL; i++) {
{"cgi_interp", "CGI interpreter to use", OFFSET(cgi_interpreter)}, len += strlen(names[i]) + 1;
{"cgi_env", "Custom CGI enviroment variables", OFFSET(cgi_environment)}, if (len >= 80) {
{"ssi_ext", "SSI extensions", OFFSET(ssi_extensions)}, len = strlen(names[i]) + 3;
{"auth_realm", "Authentication domain name", OFFSET(auth_domain)}, fprintf(stderr, "%s", "\n ");
{"auth_gpass", "Global passwords file", OFFSET(global_passwords_file)},
{"auth_PUT", "PUT,DELETE auth file", OFFSET(put_delete_passwords_file)},
{"uid", "\tRun as user", OFFSET(uid)},
{"access_log", "Access log file", OFFSET(access_log_file)},
{"error_log", "Error log file", OFFSET(error_log_file)},
{"acl", "\tAllow/deny IP addresses/subnets", OFFSET(acl)},
{"num_threads", "Threads to spawn", OFFSET(num_threads)},
{"mime_types", "Extra mime types to use", OFFSET(mime_types)},
{NULL, NULL, 0}
};
static void
show_usage_and_exit(const struct mg_config *config)
{
const struct option_descriptor *o;
const char *value;
(void) fprintf(stderr,
"Mongoose version %s (c) Sergey Lyubka\n"
"usage: mongoose [options] [config_file]\n", mg_version());
fprintf(stderr, " -A <htpasswd_file> <realm> <user> <passwd>\n");
for (o = known_options; o->name != NULL; o++) {
(void) fprintf(stderr, " -%s\t%s", o->name, o->description);
value = * (char **) ((char *) config + o->offset);
if (value != NULL)
fprintf(stderr, " (default: \"%s\")", value);
fputc('\n', stderr);
} }
fprintf(stderr, "%s%c", names[i], names[i + 1] == NULL ? '\n' : ',');
}
fprintf(stderr, "See http://code.google.com/p/mongoose/wiki/MongooseManual"
" for more details.\n");
fprintf(stderr, "Example:\n mongoose -listening_ports 80,443s "
"-enable_directory_listing no\n");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
static void static char *sdup(const char *str) {
set_option(struct mg_config *config, const char *name, char *value) char *p;
{ if ((p = malloc(strlen(str) + 1)) != NULL) {
const struct option_descriptor *o; strcpy(p, str);
}
return p;
}
static void set_option(char **options, const char *name, const char *value) {
int i;
for (o = known_options; o->name != NULL; o++) for (i = 0; i < MAX_OPTIONS - 3; i++) {
if (strcmp(name, o->name) == 0) { if (options[i] == NULL) {
* (char **) ((char *) config + o->offset) = value; options[i] = sdup(name);
options[i + 1] = sdup(value);
options[i + 2] = NULL;
break; break;
} }
}
if (o->name == NULL) if (i == MAX_OPTIONS - 3) {
show_usage_and_exit(config); fprintf(stderr, "%s\n", "Too many options specified");
exit(EXIT_FAILURE);
}
} }
static void static void process_command_line_arguments(char *argv[], char **options) {
process_command_line_arguments(struct mg_config *config, char *argv[])
{
const char *config_file = CONFIG_FILE; const char *config_file = CONFIG_FILE;
char line[512], opt[512], *vals[100], char line[512], opt[512], *vals[100], val[512], path[FILENAME_MAX], *p;
val[512], path[FILENAME_MAX], *p;
FILE *fp; FILE *fp;
size_t i, line_no = 0; size_t i, line_no = 0;
/* First find out, which config file to open */ /* First find out, which config file to open */
for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2)
if (argv[i + 1] == NULL) if (argv[i + 1] == NULL)
show_usage_and_exit(config); show_usage_and_exit();
if (argv[i] != NULL && argv[i + 1] != NULL) { if (argv[i] != NULL && argv[i + 1] != NULL) {
/* More than one non-option arguments are given */ /* More than one non-option arguments are given */
show_usage_and_exit(config); show_usage_and_exit();
} else if (argv[i] != NULL) { } else if (argv[i] != NULL) {
/* Just one non-option argument is given, this is config file */ /* Just one non-option argument is given, this is config file */
config_file = argv[i]; config_file = argv[i];
} else { } else {
/* No config file specified. Look for one where binary lives */ /* No config file specified. Look for one where binary lives */
if ((p = strrchr(argv[0], DIRSEP)) != 0) { if ((p = strrchr(argv[0], DIRSEP)) != 0) {
(void) snprintf(path, sizeof(path), "%.*s%s", snprintf(path, sizeof(path), "%.*s%s",
(int) (p - argv[0]) + 1, argv[0], config_file); (int) (p - argv[0]) + 1, argv[0], config_file);
config_file = path; config_file = path;
} }
...@@ -193,7 +173,7 @@ process_command_line_arguments(struct mg_config *config, char *argv[]) ...@@ -193,7 +173,7 @@ process_command_line_arguments(struct mg_config *config, char *argv[])
/* If config file was set in command line and open failed, exit */ /* If config file was set in command line and open failed, exit */
if (fp == NULL && argv[i] != NULL) { if (fp == NULL && argv[i] != NULL) {
(void) fprintf(stderr, "cannot open config file %s: %s\n", fprintf(stderr, "cannot open config file %s: %s\n",
config_file, strerror(errno)); config_file, strerror(errno));
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
...@@ -202,8 +182,8 @@ process_command_line_arguments(struct mg_config *config, char *argv[]) ...@@ -202,8 +182,8 @@ process_command_line_arguments(struct mg_config *config, char *argv[])
(void) memset(vals, 0, sizeof(vals)); (void) memset(vals, 0, sizeof(vals));
if (fp != NULL) { if (fp != NULL) {
(void) printf("Loading config file %s, " printf("Loading config file %s, ignoring command line arguments\n",
"ignoring command line arguments\n", config_file); config_file);
/* Loop over the lines in config file */ /* Loop over the lines in config file */
while (fgets(line, sizeof(line), fp) != NULL) { while (fgets(line, sizeof(line), fp) != NULL) {
...@@ -219,77 +199,71 @@ process_command_line_arguments(struct mg_config *config, char *argv[]) ...@@ -219,77 +199,71 @@ process_command_line_arguments(struct mg_config *config, char *argv[])
config_file, (int) line_no); config_file, (int) line_no);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* TODO(lsm): free this at some point */ set_option(options, opt, val);
p = malloc(strlen(val) + 1);
(void) strcpy(p, val);
set_option(config, opt, p);
} }
(void) fclose(fp); (void) fclose(fp);
} else { } else {
for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2) for (i = 1; argv[i] != NULL && argv[i][0] == '-'; i += 2)
set_option(config, &argv[i][1], argv[i + 1]); set_option(options, &argv[i][1], argv[i + 1]);
} }
} }
int int main(int argc, char *argv[]) {
main(int argc, char *argv[])
{
struct mg_config config;
struct mg_context *ctx; struct mg_context *ctx;
char *options[MAX_OPTIONS];
/* Initialize configuration with default values */ int i;
(void) memset(&config, 0, sizeof(config));
config.document_root = ".";
config.enable_directory_listing = "yes";
config.auth_domain = "mydomain.com";
config.num_threads = "20";
config.index_files = "index.html,index.htm,index.cgi";
config.cgi_extensions = ".cgi,.pl,.php";
config.ssi_extensions = ".shtml,.shtm";
config.listening_ports = "8080";
/* Edit passwords file if -A option is specified */ /* Edit passwords file if -A option is specified */
if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'A') { if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 'A') {
if (argc != 6) if (argc != 6) {
show_usage_and_exit(&config); show_usage_and_exit();
exit(mg_edit_passwords(argv[2], argv[3], argv[4], argv[5]) == }
MG_SUCCESS ? EXIT_SUCCESS : EXIT_FAILURE); exit(mg_edit_passwords(argv[2], argv[3], argv[4], argv[5]) ?
EXIT_SUCCESS : EXIT_FAILURE);
} }
/* Show usage if -h or --help options are specified */ /* Show usage if -h or --help options are specified */
if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) if (argc == 2 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) {
show_usage_and_exit(&config); show_usage_and_exit();
}
/* Update config based on command line arguments */ /* Update config based on command line arguments */
process_command_line_arguments(&config, argv); options[0] = NULL;
process_command_line_arguments(argv, options);
/* Setup signal handler: quit on Ctrl-C */ /* Setup signal handler: quit on Ctrl-C */
#ifndef _WIN32 #ifndef _WIN32
(void) signal(SIGCHLD, signal_handler); signal(SIGCHLD, signal_handler);
#endif /* _WIN32 */ #endif /* _WIN32 */
(void) signal(SIGTERM, signal_handler); signal(SIGTERM, signal_handler);
(void) signal(SIGINT, signal_handler); signal(SIGINT, signal_handler);
/* Start Mongoose */ /* Start Mongoose */
if ((ctx = mg_start(&config)) == NULL) { ctx = mg_start(NULL, (const char **) options);
for (i = 0; options[i] != NULL; i++) {
free(options[i]);
}
if (ctx == NULL) {
(void) printf("%s\n", "Cannot initialize Mongoose context"); (void) printf("%s\n", "Cannot initialize Mongoose context");
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
(void) printf("Mongoose %s started on port(s) %s " printf("Mongoose %s started on port(s) %s with web root [%s]\n",
"with web root [%s]\n", mg_version(), mg_get_option(ctx, "listening_ports"),
mg_version(), config.listening_ports, config.document_root); mg_get_option(ctx, "document_root"));
fflush(stdout); fflush(stdout);
while (exit_flag == 0) while (exit_flag == 0) {
sleep(1); sleep(1);
}
(void) printf("Exiting on signal %d, " printf("Exiting on signal %d, waiting for all threads to finish...",
"waiting for all threads to finish...", exit_flag); exit_flag);
fflush(stdout); fflush(stdout);
mg_stop(ctx); mg_stop(ctx);
(void) printf("%s", " done.\n"); printf("%s", " done.\n");
return (EXIT_SUCCESS); return EXIT_SUCCESS;
} }
...@@ -222,7 +222,6 @@ typedef int SOCKET; ...@@ -222,7 +222,6 @@ typedef int SOCKET;
typedef int socklen_t; typedef int socklen_t;
#endif // NO_SOCKLEN_T #endif // NO_SOCKLEN_T
typedef enum {MG_FALSE, MG_TRUE} bool_t;
typedef void * (*mg_thread_func_t)(void *); typedef void * (*mg_thread_func_t)(void *);
static const char *http_500_error = "Internal Server Error"; static const char *http_500_error = "Internal Server Error";
...@@ -333,7 +332,7 @@ struct vec { ...@@ -333,7 +332,7 @@ struct vec {
// Structure used by mg_stat() function. Uses 64 bit file length. // Structure used by mg_stat() function. Uses 64 bit file length.
struct mgstat { struct mgstat {
bool_t is_directory; // Directory marker int is_directory; // Directory marker
int64_t size; // File size int64_t size; // File size
time_t mtime; // Modification time time_t mtime; // Modification time
}; };
...@@ -345,13 +344,33 @@ struct socket { ...@@ -345,13 +344,33 @@ struct socket {
SOCKET sock; // Listening socket SOCKET sock; // Listening socket
struct usa lsa; // Local socket address struct usa lsa; // Local socket address
struct usa rsa; // Remote socket address struct usa rsa; // Remote socket address
bool_t is_ssl; // Is socket SSL-ed int is_ssl; // Is socket SSL-ed
};
enum {
DOCUMENT_ROOT, LISTENING_PORTS, INDEX_FILES, SSL_CERTIFICATE, CGI_EXTENSIONS,
CGI_INTERPRETER, CGI_ENVIRONMENT, SSI_EXTENSIONS, AUTHENTICATION_DOMAIN,
URI_PROTECTION, GLOBAL_PASSWORDS_FILE, PUT_DELETE_PASSWORDS_FILE,
ACCESS_LOG_FILE, ERROR_LOG_FILE, ACCESS_CONTROL_LIST, RUN_AS_USER,
EXTRA_MIME_TYPES, ENABLE_DIRECTORY_LISTING, ENABLE_KEEP_ALIVE, NUM_THREADS,
NUM_OPTIONS
};
static const char *option_names[] = {
"document_root", "listening_ports", "index_files", "ssl_certificate",
"cgi_extensions", "cgi_interpreter", "cgi_environment", "ssi_extensions",
"authentication_domain", "protect_uri", "global_passwords_file",
"put_delete_passwords_file", "access_log_file", "error_log_file",
"access_control_list", "run_as_user", "extra_mime_types",
"enable_directory_listing", "enable_keep_alive", "num_threads",
NULL
}; };
struct mg_context { struct mg_context {
int stop_flag; // Should we stop event loop int stop_flag; // Should we stop event loop
SSL_CTX *ssl_ctx; // SSL context SSL_CTX *ssl_ctx; // SSL context
const struct mg_config *config; // Mongoose configuration char *config[NUM_OPTIONS]; // Mongoose configuration parameters
mg_callback_t user_callback; // User-defined callback function
struct socket *listening_sockets; struct socket *listening_sockets;
...@@ -380,11 +399,39 @@ struct mg_connection { ...@@ -380,11 +399,39 @@ struct mg_connection {
int data_len; // Total size of data in a buffer int data_len; // Total size of data in a buffer
}; };
const char **mg_get_valid_option_names(void) {
return option_names;
}
static void *call_user(struct mg_connection *conn, enum mg_event event) {
return conn->ctx->user_callback == NULL ? NULL :
conn->ctx->user_callback(event, conn, &conn->request_info);
}
static int get_option_index(const char *name) {
int i;
for (i = 0; option_names[i] != NULL; i++) {
if (strcmp(option_names[i], name) == 0) {
return i;
}
}
return -1;
}
const char *mg_get_option(const struct mg_context *ctx, const char *name) {
int i;
if ((i = get_option_index(name)) == -1) {
return NULL;
} else if (ctx->config[i] == NULL) {
return "";
} else {
return ctx->config[i];
}
}
// Print error message to the opened error log stream. // Print error message to the opened error log stream.
static void cry(struct mg_connection *conn, const char *fmt, ...) { static void cry(struct mg_connection *conn, const char *fmt, ...) {
char buf[BUFSIZ]; char buf[BUFSIZ];
mg_callback_t log_callback;
enum mg_error_t processed = MG_ERROR;
va_list ap; va_list ap;
FILE *fp; FILE *fp;
time_t timestamp; time_t timestamp;
...@@ -396,14 +443,10 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { ...@@ -396,14 +443,10 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) {
// Do not lock when getting the callback value, here and below. // Do not lock when getting the callback value, here and below.
// I suppose this is fine, since function cannot disappear in the // I suppose this is fine, since function cannot disappear in the
// same way string option can. // same way string option can.
log_callback = conn->ctx->config->event_log_handler;
conn->request_info.log_message = buf; conn->request_info.log_message = buf;
if (log_callback != NULL) { if (call_user(conn, MG_EVENT_LOG) == NULL) {
processed = log_callback(conn, &conn->request_info); fp = conn->ctx->config[ERROR_LOG_FILE] == NULL ? stderr :
} mg_fopen(conn->ctx->config[ERROR_LOG_FILE], "a+");
if (processed == MG_ERROR) {
fp = conn->ctx->config->error_log_file == NULL ? stderr :
mg_fopen(conn->ctx->config->error_log_file, "a+");
if (fp != NULL) { if (fp != NULL) {
flockfile(fp); flockfile(fp);
...@@ -611,7 +654,7 @@ static const char *next_option(const char *list, struct vec *val, ...@@ -611,7 +654,7 @@ static const char *next_option(const char *list, struct vec *val,
} }
#if !defined(NO_CGI) #if !defined(NO_CGI)
static bool_t match_extension(const char *path, const char *ext_list) { static int match_extension(const char *path, const char *ext_list) {
struct vec ext_vec; struct vec ext_vec;
size_t path_len; size_t path_len;
...@@ -621,9 +664,9 @@ static bool_t match_extension(const char *path, const char *ext_list) { ...@@ -621,9 +664,9 @@ static bool_t match_extension(const char *path, const char *ext_list) {
if (ext_vec.len < path_len && if (ext_vec.len < path_len &&
mg_strncasecmp(path + path_len - ext_vec.len, mg_strncasecmp(path + path_len - ext_vec.len,
ext_vec.ptr, ext_vec.len) == 0) ext_vec.ptr, ext_vec.len) == 0)
return MG_TRUE; return 1;
return MG_FALSE; return 0;
} }
#endif // !NO_CGI #endif // !NO_CGI
...@@ -632,17 +675,11 @@ static void send_http_error(struct mg_connection *conn, int status, ...@@ -632,17 +675,11 @@ static void send_http_error(struct mg_connection *conn, int status,
char buf[BUFSIZ]; char buf[BUFSIZ];
va_list ap; va_list ap;
int len; int len;
mg_callback_t error_handler;
bool_t handled;
DEBUG_TRACE(("%d %s", status, reason)); DEBUG_TRACE(("%d %s", status, reason));
conn->request_info.status_code = status; conn->request_info.status_code = status;
error_handler = conn->ctx->config->http_error_handler; if (call_user(conn, MG_HTTP_ERROR) == NULL) {
handled = error_handler ?
error_handler(conn, &conn->request_info) : MG_ERROR;
if (handled == MG_ERROR) {
buf[0] = '\0'; buf[0] = '\0';
len = 0; len = 0;
...@@ -668,7 +705,7 @@ static void send_http_error(struct mg_connection *conn, int status, ...@@ -668,7 +705,7 @@ static void send_http_error(struct mg_connection *conn, int status,
#ifdef _WIN32 #ifdef _WIN32
static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) { static int pthread_mutex_init(pthread_mutex_t *mutex, void *unused) {
unused = NULL; unused = NULL;
*mutex = CreateMutex(NULL, MG_FALSE, NULL); *mutex = CreateMutex(NULL, FALSE, NULL);
return *mutex == NULL ? -1 : 0; return *mutex == NULL ? -1 : 0;
} }
...@@ -686,7 +723,7 @@ static int pthread_mutex_unlock(pthread_mutex_t *mutex) { ...@@ -686,7 +723,7 @@ static int pthread_mutex_unlock(pthread_mutex_t *mutex) {
static int pthread_cond_init(pthread_cond_t *cv, const void *unused) { static int pthread_cond_init(pthread_cond_t *cv, const void *unused) {
unused = NULL; unused = NULL;
*cv = CreateEvent(NULL, MG_FALSE, MG_FALSE, NULL); *cv = CreateEvent(NULL, FALSE, FALSE, NULL);
return *cv == NULL ? -1 : 0; return *cv == NULL ? -1 : 0;
} }
...@@ -1021,9 +1058,9 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, ...@@ -1021,9 +1058,9 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog,
me = GetCurrentProcess(); me = GetCurrentProcess();
(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me, (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdin), me,
&si.hStdInput, 0, MG_TRUE, DUPLICATE_SAME_ACCESS); &si.hStdInput, 0, TRUE, DUPLICATE_SAME_ACCESS);
(void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me, (void) DuplicateHandle(me, (HANDLE) _get_osfhandle(fd_stdout), me,
&si.hStdOutput, 0, MG_TRUE, DUPLICATE_SAME_ACCESS); &si.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS);
// If CGI file is a script, try to read the interpreter line // If CGI file is a script, try to read the interpreter line
interp = conn->ctx->config->cgi_interpreter; interp = conn->ctx->config->cgi_interpreter;
...@@ -1056,7 +1093,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, ...@@ -1056,7 +1093,7 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog,
change_slashes_to_backslashes(line); change_slashes_to_backslashes(line);
DEBUG_TRACE(("Running [%s]", cmdline)); DEBUG_TRACE(("Running [%s]", cmdline));
if (CreateProcessA(NULL, cmdline, NULL, NULL, MG_TRUE, if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) { CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) {
cry(conn, "%s: CreateProcess(%s): %d", cry(conn, "%s: CreateProcess(%s): %d",
__func__, cmdline, ERRNO); __func__, cmdline, ERRNO);
...@@ -1139,12 +1176,12 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog, ...@@ -1139,12 +1176,12 @@ static pid_t spawn_process(struct mg_connection *conn, const char *prog,
} else if (dup2(fd_stdout, 1) == -1) { } else if (dup2(fd_stdout, 1) == -1) {
cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO)); cry(conn, "%s: dup2(%d, 1): %s", __func__, fd_stdout, strerror(ERRNO));
} else { } else {
(void) dup2(fd_stdout, 2); //(void) dup2(fd_stdout, 2);
(void) close(fd_stdin); (void) close(fd_stdin);
(void) close(fd_stdout); (void) close(fd_stdout);
// Execute CGI program. No need to lock: new process // Execute CGI program. No need to lock: new process
interp = conn->ctx->config->cgi_interpreter; interp = conn->ctx->config[CGI_INTERPRETER];
if (interp == NULL) { if (interp == NULL) {
(void) execle(prog, prog, NULL, envp); (void) execle(prog, prog, NULL, envp);
cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO)); cry(conn, "%s: execle(%s): %s", __func__, prog, strerror(ERRNO));
...@@ -1266,6 +1303,7 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len) { ...@@ -1266,6 +1303,7 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len) {
if (n <= 0) { if (n <= 0) {
break; break;
} }
buf = (char *) buf + n;
conn->consumed_content += n; conn->consumed_content += n;
nread += n; nread += n;
len -= n; len -= n;
...@@ -1297,7 +1335,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) { ...@@ -1297,7 +1335,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
// uses '+' as character for space, see RFC 1866 section 8.2.1 // uses '+' as character for space, see RFC 1866 section 8.2.1
// http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt // http://ftp.ics.uci.edu/pub/ietf/html/rfc1866.txt
static size_t url_decode(const char *src, size_t src_len, char *dst, static size_t url_decode(const char *src, size_t src_len, char *dst,
size_t dst_len, bool_t is_form_url_encoded) { size_t dst_len, int is_form_url_encoded) {
size_t i, j; size_t i, j;
int a, b; int a, b;
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') #define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
...@@ -1326,72 +1364,58 @@ static size_t url_decode(const char *src, size_t src_len, char *dst, ...@@ -1326,72 +1364,58 @@ static size_t url_decode(const char *src, size_t src_len, char *dst,
// It can be specified in query string, or in the POST data. // It can be specified in query string, or in the POST data.
// Return NULL if the variable not found, or allocated 0-terminated value. // Return NULL if the variable not found, or allocated 0-terminated value.
// It is caller's responsibility to free the returned value. // It is caller's responsibility to free the returned value.
enum mg_error_t mg_get_var(const char *buf, size_t buf_len, int mg_get_var(const char *buf, size_t buf_len, const char *name,
const char *var_name, char *var_value, char *dst, size_t dst_len) {
size_t var_value_len) {
const char *p, *e, *s; const char *p, *e, *s;
char *val; size_t name_len, len;
size_t var_len, len;
enum mg_error_t ret_val;
var_len = strlen(var_name); name_len = strlen(name);
e = buf + buf_len; e = buf + buf_len;
val = NULL; len = -1;
ret_val = MG_NOT_FOUND; dst[0] = '\0';
var_value[0] = '\0';
// buf is "var1=val1&var2=val2...". Find variable first // buf is "var1=val1&var2=val2...". Find variable first
for (p = buf; p + var_len < e; p++) for (p = buf; p != NULL && p + name_len < e; p++) {
if ((p == buf || p[-1] == '&') && p[var_len] == '=' && if ((p == buf || p[-1] == '&') && p[name_len] == '=' &&
!mg_strncasecmp(var_name, p, var_len)) { !mg_strncasecmp(name, p, name_len)) {
// Point p to variable value // Point p to variable value
p += var_len + 1; p += name_len + 1;
// Point s to the end of the value // Point s to the end of the value
s = (const char *) memchr(p, '&', e - p); s = (const char *) memchr(p, '&', e - p);
if (s == NULL) { if (s == NULL) {
s = e; s = e;
} }
assert(s >= p);
/* Try to allocate the buffer */ // Decode variable into destination buffer
len = s - p; if ((size_t) (s - p) < dst_len) {
if (len >= var_value_len) { len = url_decode(p, s - p, dst, dst_len, 1);
ret_val = MG_BUFFER_TOO_SMALL;
} else {
url_decode(p, len, var_value, len + 1, MG_TRUE);
ret_val = MG_SUCCESS;
} }
break; break;
} }
}
return ret_val; return len;
}
enum mg_error_t mg_get_qsvar(const struct mg_request_info *ri,
const char *var_name, char *var_value,
size_t var_value_len) {
return ri->query_string == NULL ? MG_NOT_FOUND : mg_get_var(ri->query_string,
strlen(ri->query_string), var_name, var_value, var_value_len);
} }
enum mg_error_t mg_get_cookie(const struct mg_connection *conn, int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
const char *cookie_name, char *dst, char *dst, size_t dst_size) {
size_t dst_size) {
const char *s, *p, *end; const char *s, *p, *end;
int len; int name_len, len = -1;
dst[0] = '\0'; dst[0] = '\0';
if ((s = mg_get_header(conn, "Cookie")) == NULL) { if ((s = mg_get_header(conn, "Cookie")) == NULL) {
return MG_NOT_FOUND; return 0;
} }
len = strlen(cookie_name); name_len = strlen(cookie_name);
end = s + strlen(s); end = s + strlen(s);
for (; (s = strstr(s, cookie_name)) != NULL; s += len) for (; (s = strstr(s, cookie_name)) != NULL; s += name_len)
if (s[len] == '=') { if (s[name_len] == '=') {
s += len + 1; s += name_len + 1;
if ((p = strchr(s, ' ')) == NULL) if ((p = strchr(s, ' ')) == NULL)
p = end; p = end;
if (p[-1] == ';') if (p[-1] == ';')
...@@ -1400,15 +1424,14 @@ enum mg_error_t mg_get_cookie(const struct mg_connection *conn, ...@@ -1400,15 +1424,14 @@ enum mg_error_t mg_get_cookie(const struct mg_connection *conn,
s++; s++;
p--; p--;
} }
if ((size_t) (p - s) >= dst_size) { if ((size_t) (p - s) < dst_size) {
return MG_BUFFER_TOO_SMALL; len = (p - s) + 1;
} else { mg_strlcpy(dst, s, len);
mg_strlcpy(dst, s, (p - s) + 1);
return MG_SUCCESS;
} }
break;
} }
return MG_NOT_FOUND; return len;
} }
// Mongoose allows to specify multiple directories to serve, // Mongoose allows to specify multiple directories to serve,
...@@ -1422,7 +1445,7 @@ static int get_document_root(const struct mg_connection *conn, ...@@ -1422,7 +1445,7 @@ static int get_document_root(const struct mg_connection *conn,
uri = conn->request_info.uri; uri = conn->request_info.uri;
len_of_matched_uri = 0; len_of_matched_uri = 0;
root = next_option(conn->ctx->config->document_root, document_root, NULL); root = next_option(conn->ctx->config[DOCUMENT_ROOT], document_root, NULL);
while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) { while ((root = next_option(root, &uri_vec, &path_vec)) != NULL) {
if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) { if (memcmp(uri, uri_vec.ptr, uri_vec.len) == 0) {
...@@ -1650,7 +1673,7 @@ static void get_mime_type(struct mg_context *ctx, const char *path, ...@@ -1650,7 +1673,7 @@ static void get_mime_type(struct mg_context *ctx, const char *path,
// Scan user-defined mime types first, in case user wants to // Scan user-defined mime types first, in case user wants to
// override default mime types. // override default mime types.
list = ctx->config->mime_types; list = ctx->config[EXTRA_MIME_TYPES];
while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) { while ((list = next_option(list, &ext_vec, &mime_vec)) != NULL) {
// ext now points to the path suffix // ext now points to the path suffix
ext = path + path_len - ext_vec.len; ext = path + path_len - ext_vec.len;
...@@ -1896,10 +1919,9 @@ void mg_md5(char *buf, ...) { ...@@ -1896,10 +1919,9 @@ void mg_md5(char *buf, ...) {
} }
// Check the user's password, return 1 if OK // Check the user's password, return 1 if OK
static bool_t check_password(const char *method, const char *ha1, static int check_password(const char *method, const char *ha1, const char *uri,
const char *uri, const char *nonce, const char *nc, const char *nonce, const char *nc, const char *cnonce,
const char *cnonce, const char *qop, const char *qop, const char *response) {
const char *response) {
char ha2[32 + 1], expected_response[32 + 1]; char ha2[32 + 1], expected_response[32 + 1];
// NOTE(lsm): due to a bug in MSIE, we do not compare the URI // NOTE(lsm): due to a bug in MSIE, we do not compare the URI
...@@ -1908,7 +1930,7 @@ static bool_t check_password(const char *method, const char *ha1, ...@@ -1908,7 +1930,7 @@ static bool_t check_password(const char *method, const char *ha1,
strlen(response) != 32 strlen(response) != 32
// || now - strtoul(dig->nonce, NULL, 10) > 3600 // || now - strtoul(dig->nonce, NULL, 10) > 3600
) { ) {
return MG_FALSE; return 0;
} }
mg_md5(ha2, method, ":", uri, NULL); mg_md5(ha2, method, ":", uri, NULL);
...@@ -1927,12 +1949,12 @@ static FILE *open_auth_file(struct mg_connection *conn, const char *path) { ...@@ -1927,12 +1949,12 @@ static FILE *open_auth_file(struct mg_connection *conn, const char *path) {
struct mgstat st; struct mgstat st;
FILE *fp; FILE *fp;
if (ctx->config->global_passwords_file != NULL) { if (ctx->config[GLOBAL_PASSWORDS_FILE] != NULL) {
// Use global passwords file // Use global passwords file
fp = mg_fopen(ctx->config->global_passwords_file, "r"); fp = mg_fopen(ctx->config[GLOBAL_PASSWORDS_FILE], "r");
if (fp == NULL) if (fp == NULL)
cry(fc(ctx), "fopen(%s): %s", cry(fc(ctx), "fopen(%s): %s",
ctx->config->global_passwords_file, strerror(ERRNO)); ctx->config[GLOBAL_PASSWORDS_FILE], strerror(ERRNO));
} else if (!mg_stat(path, &st) && st.is_directory) { } else if (!mg_stat(path, &st) && st.is_directory) {
(void) mg_snprintf(conn, name, sizeof(name), "%s%c%s", (void) mg_snprintf(conn, name, sizeof(name), "%s%c%s",
path, DIRSEP, PASSWORDS_FILE_NAME); path, DIRSEP, PASSWORDS_FILE_NAME);
...@@ -1955,14 +1977,14 @@ struct ah { ...@@ -1955,14 +1977,14 @@ struct ah {
char *user, *uri, *cnonce, *response, *qop, *nc, *nonce; char *user, *uri, *cnonce, *response, *qop, *nc, *nonce;
}; };
static bool_t parse_auth_header(struct mg_connection *conn, char *buf, static int parse_auth_header(struct mg_connection *conn, char *buf,
size_t buf_size, struct ah *ah) { size_t buf_size, struct ah *ah) {
char *name, *value, *s; char *name, *value, *s;
const char *auth_header; const char *auth_header;
if ((auth_header = mg_get_header(conn, "Authorization")) == NULL || if ((auth_header = mg_get_header(conn, "Authorization")) == NULL ||
mg_strncasecmp(auth_header, "Digest ", 7) != 0) { mg_strncasecmp(auth_header, "Digest ", 7) != 0) {
return MG_FALSE; return 0;
} }
// Make modifiable copy of the auth header // Make modifiable copy of the auth header
...@@ -2016,16 +2038,16 @@ static bool_t parse_auth_header(struct mg_connection *conn, char *buf, ...@@ -2016,16 +2038,16 @@ static bool_t parse_auth_header(struct mg_connection *conn, char *buf,
conn->request_info.remote_user = mg_strdup(ah->user); conn->request_info.remote_user = mg_strdup(ah->user);
} }
return MG_TRUE; return 1;
} }
// Authorize against the opened passwords file. Return 1 if authorized. // Authorize against the opened passwords file. Return 1 if authorized.
static bool_t authorize(struct mg_connection *conn, FILE *fp) { static int authorize(struct mg_connection *conn, FILE *fp) {
struct ah ah; struct ah ah;
char line[256], f_user[256], ha1[256], f_domain[256], buf[MAX_REQUEST_SIZE]; char line[256], f_user[256], ha1[256], f_domain[256], buf[MAX_REQUEST_SIZE];
if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) { if (!parse_auth_header(conn, buf, sizeof(buf), &ah)) {
return MG_FALSE; return 0;
} }
// Loop over passwords file // Loop over passwords file
...@@ -2035,29 +2057,28 @@ static bool_t authorize(struct mg_connection *conn, FILE *fp) { ...@@ -2035,29 +2057,28 @@ static bool_t authorize(struct mg_connection *conn, FILE *fp) {
} }
if (!strcmp(ah.user, f_user) && if (!strcmp(ah.user, f_user) &&
!strcmp(conn->ctx->config->auth_domain, f_domain)) !strcmp(conn->ctx->config[AUTHENTICATION_DOMAIN], f_domain))
return check_password( return check_password(
conn->request_info.request_method, conn->request_info.request_method,
ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop, ha1, ah.uri, ah.nonce, ah.nc, ah.cnonce, ah.qop,
ah.response); ah.response);
} }
return MG_FALSE; return 0;
} }
// Return MG_TRUE if request is authorised, MG_FALSE otherwise. // Return 1 if request is authorised, 0 otherwise.
static bool_t check_authorization(struct mg_connection *conn, static int check_authorization(struct mg_connection *conn, const char *path) {
const char *path) {
FILE *fp; FILE *fp;
char fname[PATH_MAX]; char fname[PATH_MAX];
struct vec uri_vec, filename_vec; struct vec uri_vec, filename_vec;
const char *list; const char *list;
bool_t authorized; int authorized;
fp = NULL; fp = NULL;
authorized = MG_TRUE; authorized = 1;
list = conn->ctx->config->protect; list = conn->ctx->config[URI_PROTECTION];
while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) { while ((list = next_option(list, &uri_vec, &filename_vec)) != NULL) {
if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) { if (!memcmp(conn->request_info.uri, uri_vec.ptr, uri_vec.len)) {
(void) mg_snprintf(conn, fname, sizeof(fname), "%.*s", (void) mg_snprintf(conn, fname, sizeof(fname), "%.*s",
...@@ -2087,16 +2108,16 @@ static void send_authorization_request(struct mg_connection *conn) { ...@@ -2087,16 +2108,16 @@ static void send_authorization_request(struct mg_connection *conn) {
"HTTP/1.1 401 Unauthorized\r\n" "HTTP/1.1 401 Unauthorized\r\n"
"WWW-Authenticate: Digest qop=\"auth\", " "WWW-Authenticate: Digest qop=\"auth\", "
"realm=\"%s\", nonce=\"%lu\"\r\n\r\n", "realm=\"%s\", nonce=\"%lu\"\r\n\r\n",
conn->ctx->config->auth_domain, conn->ctx->config[AUTHENTICATION_DOMAIN],
(unsigned long) time(NULL)); (unsigned long) time(NULL));
} }
static bool_t is_authorized_for_put(struct mg_connection *conn) { static int is_authorized_for_put(struct mg_connection *conn) {
FILE *fp; FILE *fp;
int ret = MG_FALSE; int ret = 0;
fp = conn->ctx->config->put_delete_passwords_file == NULL ? NULL : fp = conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ? NULL :
mg_fopen(conn->ctx->config->put_delete_passwords_file, "r"); mg_fopen(conn->ctx->config[PUT_DELETE_PASSWORDS_FILE], "r");
if (fp != NULL) { if (fp != NULL) {
...@@ -2107,9 +2128,8 @@ static bool_t is_authorized_for_put(struct mg_connection *conn) { ...@@ -2107,9 +2128,8 @@ static bool_t is_authorized_for_put(struct mg_connection *conn) {
return ret; return ret;
} }
enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, int mg_modify_passwords_file(struct mg_context *ctx, const char *fname,
const char *fname, const char *user, const char *user, const char *pass) {
const char *pass) {
int found; int found;
char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX]; char line[512], u[512], d[512], ha1[33], tmp[PATH_MAX];
const char *domain; const char *domain;
...@@ -2117,7 +2137,7 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, ...@@ -2117,7 +2137,7 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx,
found = 0; found = 0;
fp = fp2 = NULL; fp = fp2 = NULL;
domain = ctx->config->auth_domain; domain = ctx->config[AUTHENTICATION_DOMAIN];
// Regard empty password as no password - remove user record. // Regard empty password as no password - remove user record.
if (pass[0] == '\0') { if (pass[0] == '\0') {
...@@ -2134,10 +2154,10 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, ...@@ -2134,10 +2154,10 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx,
// Open the given file and temporary file // Open the given file and temporary file
if ((fp = mg_fopen(fname, "r")) == NULL) { if ((fp = mg_fopen(fname, "r")) == NULL) {
cry(fc(ctx), "Cannot open %s: %s", fname, strerror(errno)); cry(fc(ctx), "Cannot open %s: %s", fname, strerror(errno));
return MG_ERROR; return 0;
} else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) { } else if ((fp2 = mg_fopen(tmp, "w+")) == NULL) {
cry(fc(ctx), "Cannot open %s: %s", tmp, strerror(errno)); cry(fc(ctx), "Cannot open %s: %s", tmp, strerror(errno));
return MG_ERROR; return 0;
} }
// Copy the stuff to temporary file // Copy the stuff to temporary file
...@@ -2171,7 +2191,7 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, ...@@ -2171,7 +2191,7 @@ enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx,
(void) mg_remove(fname); (void) mg_remove(fname);
(void) mg_rename(tmp, fname); (void) mg_rename(tmp, fname);
return MG_SUCCESS; return 1;
} }
struct de { struct de {
...@@ -2452,7 +2472,7 @@ static void parse_http_headers(char **buf, struct mg_request_info *ri) { ...@@ -2452,7 +2472,7 @@ static void parse_http_headers(char **buf, struct mg_request_info *ri) {
} }
} }
static bool_t is_valid_http_method(const char *method) { static int is_valid_http_method(const char *method) {
return !strcmp(method, "GET") || return !strcmp(method, "GET") ||
!strcmp(method, "POST") || !strcmp(method, "POST") ||
!strcmp(method, "HEAD") || !strcmp(method, "HEAD") ||
...@@ -2461,8 +2481,8 @@ static bool_t is_valid_http_method(const char *method) { ...@@ -2461,8 +2481,8 @@ static bool_t is_valid_http_method(const char *method) {
} }
// Parse HTTP request, fill in mg_request_info structure. // Parse HTTP request, fill in mg_request_info structure.
static bool_t parse_http_request(char *buf, struct mg_request_info *ri) { static int parse_http_request(char *buf, struct mg_request_info *ri) {
int status = MG_FALSE; int status = 0;
ri->request_method = skip(&buf, " "); ri->request_method = skip(&buf, " ");
ri->uri = skip(&buf, " "); ri->uri = skip(&buf, " ");
...@@ -2473,7 +2493,7 @@ static bool_t parse_http_request(char *buf, struct mg_request_info *ri) { ...@@ -2473,7 +2493,7 @@ static bool_t parse_http_request(char *buf, struct mg_request_info *ri) {
strncmp(ri->http_version, "HTTP/", 5) == 0) { strncmp(ri->http_version, "HTTP/", 5) == 0) {
ri->http_version += 5; /* Skip "HTTP/" */ ri->http_version += 5; /* Skip "HTTP/" */
parse_http_headers(&buf, ri); parse_http_headers(&buf, ri);
status = MG_TRUE; status = 1;
} }
return status; return status;
...@@ -2505,15 +2525,13 @@ static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz, ...@@ -2505,15 +2525,13 @@ static int read_request(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int bufsiz,
// For given directory path, substitute it to valid index file. // For given directory path, substitute it to valid index file.
// Return 0 if index file has been found, -1 if not found. // Return 0 if index file has been found, -1 if not found.
// If the file is found, it's stats is returned in stp. // If the file is found, it's stats is returned in stp.
static bool_t substitute_index_file(struct mg_connection *conn, char *path, static int substitute_index_file(struct mg_connection *conn, char *path,
size_t path_len, struct mgstat *stp) { size_t path_len, struct mgstat *stp) {
const char *list; const char *list = conn->ctx->config[INDEX_FILES];
struct mgstat st; struct mgstat st;
struct vec filename_vec; struct vec filename_vec;
size_t n; size_t n = strlen(path);
bool_t found; int found = 0;
n = strlen(path);
// The 'path' given to us points to the directory. Remove all trailing // The 'path' given to us points to the directory. Remove all trailing
// directory separator characters from the end of the path, and // directory separator characters from the end of the path, and
...@@ -2525,9 +2543,6 @@ static bool_t substitute_index_file(struct mg_connection *conn, char *path, ...@@ -2525,9 +2543,6 @@ static bool_t substitute_index_file(struct mg_connection *conn, char *path,
// Traverse index files list. For each entry, append it to the given // 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 // path and see if the file exists. If it exists, break the loop
list = conn->ctx->config->index_files;
found = MG_FALSE;
while ((list = next_option(list, &filename_vec, NULL)) != NULL) { while ((list = next_option(list, &filename_vec, NULL)) != NULL) {
// Ignore too long entries that may overflow path buffer // Ignore too long entries that may overflow path buffer
...@@ -2541,13 +2556,13 @@ static bool_t substitute_index_file(struct mg_connection *conn, char *path, ...@@ -2541,13 +2556,13 @@ static bool_t substitute_index_file(struct mg_connection *conn, char *path,
if (mg_stat(path, &st) == 0) { if (mg_stat(path, &st) == 0) {
// Yes it does, break the loop // Yes it does, break the loop
*stp = st; *stp = st;
found = MG_TRUE; found = 1;
break; break;
} }
} }
// If no index file exists, restore directory path // If no index file exists, restore directory path
if (found == MG_FALSE) { if (!found) {
path[n] = '\0'; path[n] = '\0';
} }
...@@ -2555,17 +2570,17 @@ static bool_t substitute_index_file(struct mg_connection *conn, char *path, ...@@ -2555,17 +2570,17 @@ static bool_t substitute_index_file(struct mg_connection *conn, char *path,
} }
// Return True if we should reply 304 Not Modified. // Return True if we should reply 304 Not Modified.
static bool_t is_not_modified(const struct mg_connection *conn, static int is_not_modified(const struct mg_connection *conn,
const struct mgstat *stp) { const struct mgstat *stp) {
const char *ims = mg_get_header(conn, "If-Modified-Since"); const char *ims = mg_get_header(conn, "If-Modified-Since");
return ims != NULL && stp->mtime <= parse_date_string(ims); return ims != NULL && stp->mtime <= parse_date_string(ims);
} }
static bool_t handle_request_body(struct mg_connection *conn, FILE *fp) { static int handle_request_body(struct mg_connection *conn, FILE *fp) {
const char *expect, *data; const char *expect, *data;
char buf[BUFSIZ]; char buf[BUFSIZ];
int to_read, nread, data_len; int to_read, nread, data_len;
bool_t status = MG_FALSE; int success = 0;
expect = mg_get_header(conn, "Expect"); expect = mg_get_header(conn, "Expect");
assert(fp != NULL); assert(fp != NULL);
...@@ -2583,8 +2598,8 @@ static bool_t handle_request_body(struct mg_connection *conn, FILE *fp) { ...@@ -2583,8 +2598,8 @@ static bool_t handle_request_body(struct mg_connection *conn, FILE *fp) {
assert(data_len >= 0); assert(data_len >= 0);
if (conn->content_len <= (int64_t) data_len) { if (conn->content_len <= (int64_t) data_len) {
status = fp == NULL || (push(fp, INVALID_SOCKET, NULL, data, success = push(fp, INVALID_SOCKET, NULL, data,
conn->content_len) == conn->content_len) ? MG_TRUE : MG_FALSE; conn->content_len) == conn->content_len;
} else { } else {
push(fp, INVALID_SOCKET, NULL, data, (int64_t) data_len); push(fp, INVALID_SOCKET, NULL, data, (int64_t) data_len);
conn->consumed_content += data_len; conn->consumed_content += data_len;
...@@ -2601,18 +2616,17 @@ static bool_t handle_request_body(struct mg_connection *conn, FILE *fp) { ...@@ -2601,18 +2616,17 @@ static bool_t handle_request_body(struct mg_connection *conn, FILE *fp) {
conn->consumed_content += nread; conn->consumed_content += nread;
} }
if (conn->consumed_content == conn->content_len) { if (conn->consumed_content == conn->content_len) {
status = MG_TRUE; success = 1;
} }
} }
// Each error code path in this function must send an error // Each error code path in this function must send an error
if (status != MG_TRUE) { if (!success) {
send_http_error(conn, 577, http_500_error, send_http_error(conn, 577, http_500_error, "");
"%s", "Error handling body data");
} }
} }
return status; return success;
} }
#if !defined(NO_CGI) #if !defined(NO_CGI)
...@@ -2681,7 +2695,7 @@ static void prepare_cgi_environment(struct mg_connection *conn, ...@@ -2681,7 +2695,7 @@ static void prepare_cgi_environment(struct mg_connection *conn,
get_document_root(conn, &root); get_document_root(conn, &root);
addenv(blk, "SERVER_NAME=%s", conn->ctx->config->auth_domain); addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr); addenv(blk, "SERVER_ROOT=%.*s", root.len, root.ptr);
addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr); addenv(blk, "DOCUMENT_ROOT=%.*s", root.len, root.ptr);
...@@ -2750,7 +2764,7 @@ static void prepare_cgi_environment(struct mg_connection *conn, ...@@ -2750,7 +2764,7 @@ static void prepare_cgi_environment(struct mg_connection *conn,
} }
// Add user-specified variables // Add user-specified variables
s = conn->ctx->config->cgi_environment; s = conn->ctx->config[CGI_ENVIRONMENT];
while ((s = next_option(s, &var_vec, NULL)) != NULL) { while ((s = next_option(s, &var_vec, NULL)) != NULL) {
addenv(blk, "%.*s", var_vec.len, var_vec.ptr); addenv(blk, "%.*s", var_vec.len, var_vec.ptr);
} }
...@@ -2942,7 +2956,7 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, ...@@ -2942,7 +2956,7 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi,
char *tag, int include_level) { char *tag, int include_level) {
char file_name[BUFSIZ], path[PATH_MAX], *p; char file_name[BUFSIZ], path[PATH_MAX], *p;
struct vec root; struct vec root;
bool_t is_ssi; int is_ssi;
FILE *fp; FILE *fp;
get_document_root(conn, &root); get_document_root(conn, &root);
...@@ -2975,7 +2989,7 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi, ...@@ -2975,7 +2989,7 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi,
tag, path, strerror(ERRNO)); tag, path, strerror(ERRNO));
} else { } else {
set_close_on_exec(fileno(fp)); set_close_on_exec(fileno(fp));
is_ssi = match_extension(path, conn->ctx->config->ssi_extensions); is_ssi = match_extension(path, conn->ctx->config[SSI_EXTENSIONS]);
if (is_ssi) { if (is_ssi) {
send_ssi_file(conn, path, fp, include_level + 1); send_ssi_file(conn, path, fp, include_level + 1);
} else { } else {
...@@ -3009,12 +3023,12 @@ static void send_ssi_file(struct mg_connection *conn, const char *path, ...@@ -3009,12 +3023,12 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
return; return;
} }
in_ssi_tag = MG_FALSE; in_ssi_tag = 0;
len = 0; len = 0;
while ((ch = fgetc(fp)) != EOF) { while ((ch = fgetc(fp)) != EOF) {
if (in_ssi_tag && ch == '>') { if (in_ssi_tag && ch == '>') {
in_ssi_tag = MG_FALSE; in_ssi_tag = 0;
buf[len++] = (char) ch; buf[len++] = (char) ch;
buf[len] = '\0'; buf[len] = '\0';
assert(len <= (int) sizeof(buf)); assert(len <= (int) sizeof(buf));
...@@ -3034,14 +3048,14 @@ static void send_ssi_file(struct mg_connection *conn, const char *path, ...@@ -3034,14 +3048,14 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
} else if (in_ssi_tag) { } else if (in_ssi_tag) {
if (len == 5 && memcmp(buf, "<!--#", 5) != 0) { if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
// Not an SSI tag // Not an SSI tag
in_ssi_tag = MG_FALSE; in_ssi_tag = 0;
} else if (len == (int) sizeof(buf) - 2) { } else if (len == (int) sizeof(buf) - 2) {
cry(conn, "%s: SSI tag is too large", path); cry(conn, "%s: SSI tag is too large", path);
len = 0; len = 0;
} }
buf[len++] = ch & 0xff; buf[len++] = ch & 0xff;
} else if (ch == '<') { } else if (ch == '<') {
in_ssi_tag = MG_TRUE; in_ssi_tag = 1;
if (len > 0) { if (len > 0) {
(void) mg_write(conn, buf, len); (void) mg_write(conn, buf, len);
} }
...@@ -3083,35 +3097,32 @@ static void handle_ssi_file_request(struct mg_connection *conn, ...@@ -3083,35 +3097,32 @@ static void handle_ssi_file_request(struct mg_connection *conn,
// and Mongoose must decide what action to take: serve a file, or // and Mongoose must decide what action to take: serve a file, or
// a directory, or call embedded function, etcetera. // a directory, or call embedded function, etcetera.
static void handle_request(struct mg_connection *conn) { static void handle_request(struct mg_connection *conn) {
struct mg_request_info *ri; struct mg_request_info *ri = &conn->request_info;
char path[PATH_MAX]; char path[PATH_MAX];
int uri_len; int uri_len;
struct mgstat st; struct mgstat st;
mg_callback_t new_request_callback;
ri = &conn->request_info;
if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) { if ((conn->request_info.query_string = strchr(ri->uri, '?')) != NULL) {
* conn->request_info.query_string++ = '\0'; * conn->request_info.query_string++ = '\0';
} }
uri_len = strlen(ri->uri); uri_len = strlen(ri->uri);
new_request_callback = conn->ctx->config->new_request_handler; (void) url_decode(ri->uri, uri_len, ri->uri, uri_len + 1, 0);
(void) url_decode(ri->uri, uri_len, ri->uri, uri_len + 1, MG_FALSE);
remove_double_dots_and_double_slashes(ri->uri); remove_double_dots_and_double_slashes(ri->uri);
convert_uri_to_file_name(conn, ri->uri, path, sizeof(path)); convert_uri_to_file_name(conn, ri->uri, path, sizeof(path));
DEBUG_TRACE(("%s", ri->uri)); DEBUG_TRACE(("%s", ri->uri));
if (new_request_callback && new_request_callback(conn, ri) == MG_TRUE) { if (call_user(conn, MG_NEW_REQUEST) != NULL) {
// Do nothing, callback has served the request // Do nothing, callback has served the request
} else if (!check_authorization(conn, path)) { } else if (!check_authorization(conn, path)) {
send_authorization_request(conn); send_authorization_request(conn);
} else if (strstr(path, PASSWORDS_FILE_NAME)) { } else if (strstr(path, PASSWORDS_FILE_NAME)) {
// Do not allow to view passwords files // Do not allow to view passwords files
send_http_error(conn, 403, "Forbidden", "Access Forbidden"); send_http_error(conn, 403, "Forbidden", "Access Forbidden");
} else if (conn->ctx->config->document_root == NULL) { } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, 404, "Not Found", "Not Found"); send_http_error(conn, 404, "Not Found", "Not Found");
} else if ((!strcmp(ri->request_method, "PUT") || } else if ((!strcmp(ri->request_method, "PUT") ||
!strcmp(ri->request_method, "DELETE")) && !strcmp(ri->request_method, "DELETE")) &&
(conn->ctx->config->put_delete_passwords_file == NULL || (conn->ctx->config[PUT_DELETE_PASSWORDS_FILE] == NULL ||
!is_authorized_for_put(conn))) { !is_authorized_for_put(conn))) {
send_authorization_request(conn); send_authorization_request(conn);
} else if (!strcmp(ri->request_method, "PUT")) { } else if (!strcmp(ri->request_method, "PUT")) {
...@@ -3130,14 +3141,14 @@ static void handle_request(struct mg_connection *conn) { ...@@ -3130,14 +3141,14 @@ static void handle_request(struct mg_connection *conn) {
"HTTP/1.1 301 Moved Permanently\r\n" "HTTP/1.1 301 Moved Permanently\r\n"
"Location: %s/\r\n\r\n", ri->uri); "Location: %s/\r\n\r\n", ri->uri);
} else if (st.is_directory && } else if (st.is_directory &&
substitute_index_file(conn, path, sizeof(path), &st) == MG_FALSE) { !substitute_index_file(conn, path, sizeof(path), &st)) {
if (conn->ctx->config->enable_directory_listing) { if (!mg_strcasecmp(conn->ctx->config[ENABLE_DIRECTORY_LISTING], "yes")) {
handle_directory_request(conn, path); handle_directory_request(conn, path);
} else { } else {
send_http_error(conn, 403, "Directory Listing Denied", send_http_error(conn, 403, "Directory Listing Denied",
"Directory listing denied"); "Directory listing denied");
} }
} else if (match_extension(path, conn->ctx->config->cgi_extensions)) { } else if (match_extension(path, conn->ctx->config[CGI_EXTENSIONS])) {
if (strcmp(ri->request_method, "POST") && if (strcmp(ri->request_method, "POST") &&
strcmp(ri->request_method, "GET")) { strcmp(ri->request_method, "GET")) {
send_http_error(conn, 501, "Not Implemented", send_http_error(conn, 501, "Not Implemented",
...@@ -3145,7 +3156,7 @@ static void handle_request(struct mg_connection *conn) { ...@@ -3145,7 +3156,7 @@ static void handle_request(struct mg_connection *conn) {
} else { } else {
handle_cgi_request(conn, path); handle_cgi_request(conn, path);
} }
} else if (match_extension(path, conn->ctx->config->ssi_extensions)) { } else if (match_extension(path, conn->ctx->config[SSI_EXTENSIONS])) {
handle_ssi_file_request(conn, path); handle_ssi_file_request(conn, path);
} else if (is_not_modified(conn, &st)) { } else if (is_not_modified(conn, &st)) {
send_http_error(conn, 304, "Not Modified", ""); send_http_error(conn, 304, "Not Modified", "");
...@@ -3163,28 +3174,28 @@ static void close_all_listening_sockets(struct mg_context *ctx) { ...@@ -3163,28 +3174,28 @@ static void close_all_listening_sockets(struct mg_context *ctx) {
} }
} }
static enum mg_error_t set_ports_option(struct mg_context *ctx) { static int set_ports_option(struct mg_context *ctx) {
SOCKET sock; SOCKET sock;
int is_ssl; const char *list = ctx->config[LISTENING_PORTS];
int is_ssl, success = 1;
struct vec vec; struct vec vec;
struct socket *listener; struct socket *listener;
const char *list = ctx->config->listening_ports;
while ((list = next_option(list, &vec, NULL)) != NULL) { while (success && (list = next_option(list, &vec, NULL)) != NULL) {
is_ssl = vec.ptr[vec.len - 1] == 's' ? MG_TRUE : MG_FALSE; is_ssl = vec.ptr[vec.len - 1] == 's';
if ((listener = calloc(1, sizeof(*listener))) == NULL) { if ((listener = calloc(1, sizeof(*listener))) == NULL) {
cry(fc(ctx), "%s", "Too many listeninig sockets"); cry(fc(ctx), "%s", "Too many listeninig sockets");
return MG_ERROR; success = 0;
} else if ((sock = mg_open_listening_port(ctx, } else if ((sock = mg_open_listening_port(ctx,
vec.ptr, &listener->lsa)) == INVALID_SOCKET) { vec.ptr, &listener->lsa)) == INVALID_SOCKET) {
cry(fc(ctx), "cannot bind to %.*s", vec.len, vec.ptr); cry(fc(ctx), "cannot bind to %.*s", vec.len, vec.ptr);
return MG_ERROR; success = 0;
} else if (is_ssl == MG_TRUE && ctx->ssl_ctx == NULL) { } else if (is_ssl && ctx->ssl_ctx == NULL) {
(void) closesocket(sock); (void) closesocket(sock);
cry(fc(ctx), "cannot add SSL socket, please specify " cry(fc(ctx), "cannot add SSL socket, please specify "
"-ssl_cert option BEFORE -ports option"); "-ssl_cert option BEFORE -ports option");
return MG_ERROR; success = 0;
} else { } else {
listener->sock = sock; listener->sock = sock;
listener->is_ssl = is_ssl; listener->is_ssl = is_ssl;
...@@ -3193,7 +3204,11 @@ static enum mg_error_t set_ports_option(struct mg_context *ctx) { ...@@ -3193,7 +3204,11 @@ static enum mg_error_t set_ports_option(struct mg_context *ctx) {
} }
} }
return MG_SUCCESS; if (!success) {
close_all_listening_sockets(ctx);
}
return success;
} }
static void log_header(const struct mg_connection *conn, const char *header, static void log_header(const struct mg_connection *conn, const char *header,
...@@ -3212,8 +3227,8 @@ static void log_access(const struct mg_connection *conn) { ...@@ -3212,8 +3227,8 @@ static void log_access(const struct mg_connection *conn) {
FILE *fp; FILE *fp;
char date[64]; char date[64];
fp = conn->ctx->config->access_log_file == NULL ? NULL : fp = conn->ctx->config[ACCESS_LOG_FILE] == NULL ? NULL :
mg_fopen(conn->ctx->config->access_log_file, "a+"); mg_fopen(conn->ctx->config[ACCESS_LOG_FILE], "a+");
if (fp == NULL) if (fp == NULL)
return; return;
...@@ -3243,22 +3258,21 @@ static void log_access(const struct mg_connection *conn) { ...@@ -3243,22 +3258,21 @@ static void log_access(const struct mg_connection *conn) {
(void) fclose(fp); (void) fclose(fp);
} }
static bool_t isbyte(int n) { static int isbyte(int n) {
return n >= 0 && n <= 255 ? MG_TRUE : MG_FALSE; return n >= 0 && n <= 255;
} }
// Verify given socket address against the ACL. // Verify given socket address against the ACL.
// Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed. // Return -1 if ACL is malformed, 0 if address is disallowed, 1 if allowed.
static enum mg_error_t check_acl(struct mg_context *ctx, static int check_acl(struct mg_context *ctx, const struct usa *usa) {
const struct usa *usa) {
int a, b, c, d, n, mask, allowed; int a, b, c, d, n, mask, allowed;
char flag; char flag;
uint32_t acl_subnet, acl_mask, remote_ip; uint32_t acl_subnet, acl_mask, remote_ip;
struct vec vec; struct vec vec;
const char *list = ctx->config->acl; const char *list = ctx->config[ACCESS_CONTROL_LIST];
if (list == NULL) { if (list == NULL) {
return MG_SUCCESS; return 1;
} }
(void) memcpy(&remote_ip, &usa->u.sin.sin_addr, sizeof(remote_ip)); (void) memcpy(&remote_ip, &usa->u.sin.sin_addr, sizeof(remote_ip));
...@@ -3271,18 +3285,18 @@ static enum mg_error_t check_acl(struct mg_context *ctx, ...@@ -3271,18 +3285,18 @@ static enum mg_error_t check_acl(struct mg_context *ctx,
if (sscanf(vec.ptr, "%c%d.%d.%d.%d%n", &flag, &a, &b, &c, &d, &n) != 5) { if (sscanf(vec.ptr, "%c%d.%d.%d.%d%n", &flag, &a, &b, &c, &d, &n) != 5) {
cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__); cry(fc(ctx), "%s: subnet must be [+|-]x.x.x.x[/x]", __func__);
return MG_ERROR; return 0;
} else if (flag != '+' && flag != '-') { } else if (flag != '+' && flag != '-') {
cry(fc(ctx), "%s: flag must be + or -: [%s]", __func__, vec.ptr); cry(fc(ctx), "%s: flag must be + or -: [%s]", __func__, vec.ptr);
return MG_ERROR; return 0;
} else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) { } else if (!isbyte(a)||!isbyte(b)||!isbyte(c)||!isbyte(d)) {
cry(fc(ctx), "%s: bad ip address: [%s]", __func__, vec.ptr); cry(fc(ctx), "%s: bad ip address: [%s]", __func__, vec.ptr);
return MG_ERROR; return 0;
} else if (sscanf(vec.ptr + n, "/%d", &mask) == 0) { } else if (sscanf(vec.ptr + n, "/%d", &mask) == 0) {
// Do nothing, no mask specified // Do nothing, no mask specified
} else if (mask < 0 || mask > 32) { } else if (mask < 0 || mask > 32) {
cry(fc(ctx), "%s: bad subnet mask: %d [%s]", __func__, n, vec.ptr); cry(fc(ctx), "%s: bad subnet mask: %d [%s]", __func__, n, vec.ptr);
return MG_ERROR; return 0;
} }
acl_subnet = (a << 24) | (b << 16) | (c << 8) | d; acl_subnet = (a << 24) | (b << 16) | (c << 8) | d;
...@@ -3293,7 +3307,7 @@ static enum mg_error_t check_acl(struct mg_context *ctx, ...@@ -3293,7 +3307,7 @@ static enum mg_error_t check_acl(struct mg_context *ctx,
} }
} }
return allowed == '+' ? MG_SUCCESS : MG_ERROR; return allowed == '+';
} }
static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) { static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {
...@@ -3304,13 +3318,13 @@ static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) { ...@@ -3304,13 +3318,13 @@ static void add_to_set(SOCKET fd, fd_set *set, int *max_fd) {
} }
#if !defined(_WIN32) #if !defined(_WIN32)
static enum mg_error_t set_uid_option(struct mg_context *ctx) { static int set_uid_option(struct mg_context *ctx) {
struct passwd *pw; struct passwd *pw;
const char *uid = ctx->config->uid; const char *uid = ctx->config[RUN_AS_USER];
enum mg_error_t error; int success = 0;
if (uid == NULL) { if (uid == NULL) {
error = MG_SUCCESS; success = 1;
} else { } else {
if ((pw = getpwnam(uid)) == NULL) { if ((pw = getpwnam(uid)) == NULL) {
cry(fc(ctx), "%s: unknown user [%s]", __func__, uid); cry(fc(ctx), "%s: unknown user [%s]", __func__, uid);
...@@ -3319,11 +3333,11 @@ static enum mg_error_t set_uid_option(struct mg_context *ctx) { ...@@ -3319,11 +3333,11 @@ static enum mg_error_t set_uid_option(struct mg_context *ctx) {
} else if (setuid(pw->pw_uid) == -1) { } else if (setuid(pw->pw_uid) == -1) {
cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno)); cry(fc(ctx), "%s: setuid(%s): %s", __func__, uid, strerror(errno));
} else { } else {
error = MG_SUCCESS; success = 1;
} }
} }
return error; return success;
} }
#endif // !_WIN32 #endif // !_WIN32
...@@ -3346,7 +3360,7 @@ static unsigned long ssl_id_callback(void) { ...@@ -3346,7 +3360,7 @@ static unsigned long ssl_id_callback(void) {
return (unsigned long) pthread_self(); return (unsigned long) pthread_self();
} }
static bool_t load_dll(struct mg_context *ctx, const char *dll_name, static int load_dll(struct mg_context *ctx, const char *dll_name,
struct ssl_func *sw) { struct ssl_func *sw) {
union {void *p; void (*fp)(void);} u; union {void *p; void (*fp)(void);} u;
void *dll_handle; void *dll_handle;
...@@ -3354,7 +3368,7 @@ static bool_t load_dll(struct mg_context *ctx, const char *dll_name, ...@@ -3354,7 +3368,7 @@ static bool_t load_dll(struct mg_context *ctx, const char *dll_name,
if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) { if ((dll_handle = dlopen(dll_name, RTLD_LAZY)) == NULL) {
cry(fc(ctx), "%s: cannot load %s", __func__, dll_name); cry(fc(ctx), "%s: cannot load %s", __func__, dll_name);
return MG_FALSE; return 0;
} }
for (fp = sw; fp->name != NULL; fp++) { for (fp = sw; fp->name != NULL; fp++) {
...@@ -3368,28 +3382,28 @@ static bool_t load_dll(struct mg_context *ctx, const char *dll_name, ...@@ -3368,28 +3382,28 @@ static bool_t load_dll(struct mg_context *ctx, const char *dll_name,
#endif /* _WIN32 */ #endif /* _WIN32 */
if (u.fp == NULL) { if (u.fp == NULL) {
cry(fc(ctx), "%s: cannot find %s", __func__, fp->name); cry(fc(ctx), "%s: cannot find %s", __func__, fp->name);
return MG_FALSE; return 0;
} else { } else {
fp->ptr = u.fp; fp->ptr = u.fp;
} }
} }
return MG_TRUE; return 1;
} }
// Dynamically load SSL library. Set up ctx->ssl_ctx pointer. // Dynamically load SSL library. Set up ctx->ssl_ctx pointer.
static enum mg_error_t set_ssl_option(struct mg_context *ctx) { static int set_ssl_option(struct mg_context *ctx) {
SSL_CTX *CTX; SSL_CTX *CTX;
int i, size; int i, size;
const char *pem = ctx->config->ssl_certificate; const char *pem = ctx->config[SSL_CERTIFICATE];
if (pem == NULL) { if (pem == NULL) {
return MG_SUCCESS; return 1;
} }
if (load_dll(ctx, SSL_LIB, ssl_sw) == MG_FALSE || if (!load_dll(ctx, SSL_LIB, ssl_sw) ||
load_dll(ctx, CRYPTO_LIB, crypto_sw) == MG_FALSE) { !load_dll(ctx, CRYPTO_LIB, crypto_sw)) {
return MG_ERROR; return 0;
} }
// Initialize SSL crap // Initialize SSL crap
...@@ -3398,18 +3412,18 @@ static enum mg_error_t set_ssl_option(struct mg_context *ctx) { ...@@ -3398,18 +3412,18 @@ static enum mg_error_t set_ssl_option(struct mg_context *ctx) {
if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) { if ((CTX = SSL_CTX_new(SSLv23_server_method())) == NULL) {
cry(fc(ctx), "SSL_CTX_new error: %s", ssl_error()); cry(fc(ctx), "SSL_CTX_new error: %s", ssl_error());
} else if (ctx->config->ssl_password_handler != NULL) { } else if (ctx->user_callback != NULL) {
SSL_CTX_set_default_passwd_cb(CTX, ctx->config->ssl_password_handler); ctx->user_callback(MG_INIT_SSL, (struct mg_connection *) CTX, NULL);
} }
if (CTX != NULL && SSL_CTX_use_certificate_file(CTX, pem, if (CTX != NULL && SSL_CTX_use_certificate_file(CTX, pem,
SSL_FILETYPE_PEM) == 0) { SSL_FILETYPE_PEM) == 0) {
cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error()); cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error());
return MG_ERROR; return 0;
} else if (CTX != NULL && SSL_CTX_use_PrivateKey_file(CTX, pem, } else if (CTX != NULL && SSL_CTX_use_PrivateKey_file(CTX, pem,
SSL_FILETYPE_PEM) == 0) { SSL_FILETYPE_PEM) == 0) {
cry(fc(ctx), "%s: cannot open %s: %s", NULL, pem, ssl_error()); cry(fc(ctx), "%s: cannot open %s: %s", NULL, pem, ssl_error());
return MG_ERROR; return 0;
} }
// Initialize locking callbacks, needed for thread safety. // Initialize locking callbacks, needed for thread safety.
...@@ -3417,7 +3431,7 @@ static enum mg_error_t set_ssl_option(struct mg_context *ctx) { ...@@ -3417,7 +3431,7 @@ static enum mg_error_t set_ssl_option(struct mg_context *ctx) {
size = sizeof(pthread_mutex_t) * CRYPTO_num_locks(); size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
if ((ssl_mutexes = (pthread_mutex_t *) malloc(size)) == NULL) { if ((ssl_mutexes = (pthread_mutex_t *) malloc(size)) == NULL) {
cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error()); cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
return MG_ERROR; return 0;
} }
for (i = 0; i < CRYPTO_num_locks(); i++) { for (i = 0; i < CRYPTO_num_locks(); i++) {
...@@ -3430,24 +3444,25 @@ static enum mg_error_t set_ssl_option(struct mg_context *ctx) { ...@@ -3430,24 +3444,25 @@ static enum mg_error_t set_ssl_option(struct mg_context *ctx) {
// Done with everything. Save the context. // Done with everything. Save the context.
ctx->ssl_ctx = CTX; ctx->ssl_ctx = CTX;
return MG_SUCCESS; return 1;
} }
#endif // !NO_SSL #endif // !NO_SSL
static enum mg_error_t set_gpass_option(struct mg_context *ctx) { static int set_gpass_option(struct mg_context *ctx) {
struct mgstat mgstat; struct mgstat mgstat;
const char *path = ctx->config->global_passwords_file; const char *path = ctx->config[GLOBAL_PASSWORDS_FILE];
return path == NULL || mg_stat(path, &mgstat) == 0 ? MG_SUCCESS : MG_ERROR; return path == NULL || mg_stat(path, &mgstat) == 0;
} }
static enum mg_error_t set_acl_option(struct mg_context *ctx) { static int set_acl_option(struct mg_context *ctx) {
struct usa fake; struct usa fake;
return check_acl(ctx, &fake); return check_acl(ctx, &fake);
} }
static bool_t verify_document_root(struct mg_context *ctx, const char *root) { static int verify_document_root(struct mg_context *ctx) {
char path[PATH_MAX], *p; char path[PATH_MAX], *p;
struct mgstat buf; struct mgstat buf;
const char *root = ctx->config[DOCUMENT_ROOT];
if ((p = strchr(root, ',')) == NULL) { if ((p = strchr(root, ',')) == NULL) {
mg_strlcpy(path, root, sizeof(path)); mg_strlcpy(path, root, sizeof(path));
...@@ -3457,9 +3472,9 @@ static bool_t verify_document_root(struct mg_context *ctx, const char *root) { ...@@ -3457,9 +3472,9 @@ static bool_t verify_document_root(struct mg_context *ctx, const char *root) {
if (mg_stat(path, &buf) != 0) { if (mg_stat(path, &buf) != 0) {
cry(fc(ctx), "Invalid root directory: \"%s\"", root); cry(fc(ctx), "Invalid root directory: \"%s\"", root);
return MG_FALSE; return 0;
} }
return MG_TRUE; return 1;
} }
static void reset_per_request_attributes(struct mg_connection *conn) { static void reset_per_request_attributes(struct mg_connection *conn) {
...@@ -3562,7 +3577,7 @@ static void process_new_connection(struct mg_connection *conn) { ...@@ -3562,7 +3577,7 @@ static void process_new_connection(struct mg_connection *conn) {
} }
// Worker threads take accepted socket from the queue // Worker threads take accepted socket from the queue
static bool_t consume_socket(struct mg_context *ctx, struct socket *sp) { static int consume_socket(struct mg_context *ctx, struct socket *sp) {
(void) pthread_mutex_lock(&ctx->mutex); (void) pthread_mutex_lock(&ctx->mutex);
DEBUG_TRACE(("going idle")); DEBUG_TRACE(("going idle"));
...@@ -3574,7 +3589,7 @@ static bool_t consume_socket(struct mg_context *ctx, struct socket *sp) { ...@@ -3574,7 +3589,7 @@ static bool_t consume_socket(struct mg_context *ctx, struct socket *sp) {
// If this happens, it is time to exit. // If this happens, it is time to exit.
if (ctx->stop_flag) { if (ctx->stop_flag) {
(void) pthread_mutex_unlock(&ctx->mutex); (void) pthread_mutex_unlock(&ctx->mutex);
return MG_FALSE; return 0;
} }
assert(ctx->sq_head > ctx->sq_tail); assert(ctx->sq_head > ctx->sq_tail);
...@@ -3592,7 +3607,7 @@ static bool_t consume_socket(struct mg_context *ctx, struct socket *sp) { ...@@ -3592,7 +3607,7 @@ static bool_t consume_socket(struct mg_context *ctx, struct socket *sp) {
(void) pthread_cond_signal(&ctx->sq_empty); (void) pthread_cond_signal(&ctx->sq_empty);
(void) pthread_mutex_unlock(&ctx->mutex); (void) pthread_mutex_unlock(&ctx->mutex);
return MG_TRUE; return 1;
} }
static void worker_thread(struct mg_context *ctx) { static void worker_thread(struct mg_context *ctx) {
...@@ -3661,14 +3676,14 @@ static void produce_socket(struct mg_context *ctx, const struct socket *sp) { ...@@ -3661,14 +3676,14 @@ static void produce_socket(struct mg_context *ctx, const struct socket *sp) {
static void accept_new_connection(const struct socket *listener, static void accept_new_connection(const struct socket *listener,
struct mg_context *ctx) { struct mg_context *ctx) {
struct socket accepted; struct socket accepted;
bool_t allowed; int allowed;
accepted.rsa.len = sizeof(accepted.rsa.u.sin); accepted.rsa.len = sizeof(accepted.rsa.u.sin);
accepted.lsa = listener->lsa; accepted.lsa = listener->lsa;
accepted.sock = accept(listener->sock, &accepted.rsa.u.sa, &accepted.rsa.len); accepted.sock = accept(listener->sock, &accepted.rsa.u.sa, &accepted.rsa.len);
if (accepted.sock != INVALID_SOCKET) { if (accepted.sock != INVALID_SOCKET) {
allowed = check_acl(ctx, &accepted.rsa) == MG_SUCCESS; allowed = check_acl(ctx, &accepted.rsa);
if (allowed == MG_SUCCESS) { if (allowed) {
// Put accepted socket structure into the queue // Put accepted socket structure into the queue
DEBUG_TRACE(("accepted socket %d", accepted.sock)); DEBUG_TRACE(("accepted socket %d", accepted.sock));
accepted.is_ssl = listener->is_ssl; accepted.is_ssl = listener->is_ssl;
...@@ -3729,11 +3744,6 @@ static void master_thread(struct mg_context *ctx) { ...@@ -3729,11 +3744,6 @@ static void master_thread(struct mg_context *ctx) {
} }
(void) pthread_mutex_unlock(&ctx->mutex); (void) pthread_mutex_unlock(&ctx->mutex);
// Deallocate SSL context
if (ctx->ssl_ctx != NULL) {
SSL_CTX_free(ctx->ssl_ctx);
}
// All threads exited, no sync is needed. Destroy mutex and condvars // All threads exited, no sync is needed. Destroy mutex and condvars
(void) pthread_mutex_destroy(&ctx->mutex); (void) pthread_mutex_destroy(&ctx->mutex);
(void) pthread_cond_destroy(&ctx->cond); (void) pthread_cond_destroy(&ctx->cond);
...@@ -3746,6 +3756,24 @@ static void master_thread(struct mg_context *ctx) { ...@@ -3746,6 +3756,24 @@ static void master_thread(struct mg_context *ctx) {
DEBUG_TRACE(("exiting")); DEBUG_TRACE(("exiting"));
} }
static void free_context(struct mg_context *ctx) {
int i;
// Deallocate config parameters
for (i = 0; i < NUM_OPTIONS; i++) {
if (ctx->config[i] != NULL)
free(ctx->config[i]);
}
// Deallocate SSL context
if (ctx->ssl_ctx != NULL) {
SSL_CTX_free(ctx->ssl_ctx);
}
// Deallocate context itself
free(ctx);
}
void mg_stop(struct mg_context *ctx) { void mg_stop(struct mg_context *ctx) {
ctx->stop_flag = 1; ctx->stop_flag = 1;
...@@ -3753,15 +3781,16 @@ void mg_stop(struct mg_context *ctx) { ...@@ -3753,15 +3781,16 @@ void mg_stop(struct mg_context *ctx) {
while (ctx->stop_flag != 2) { while (ctx->stop_flag != 2) {
(void) sleep(0); (void) sleep(0);
} }
free(ctx); free_context(ctx);
#if defined(_WIN32) #if defined(_WIN32)
(void) WSACleanup(); (void) WSACleanup();
#endif // _WIN32 #endif // _WIN32
} }
struct mg_context * mg_start(const struct mg_config *config) { struct mg_context *mg_start(mg_callback_t user_callback, const char **options) {
struct mg_context *ctx, fake_ctx; struct mg_context *ctx;
const char *name, *value;
int i; int i;
#if defined(_WIN32) #if defined(_WIN32)
...@@ -3769,35 +3798,52 @@ struct mg_context * mg_start(const struct mg_config *config) { ...@@ -3769,35 +3798,52 @@ struct mg_context * mg_start(const struct mg_config *config) {
WSAStartup(MAKEWORD(2,2), &data); WSAStartup(MAKEWORD(2,2), &data);
#endif // _WIN32 #endif // _WIN32
// TODO(lsm): make a copy of the config // Allocate context and initialize reasonable general case defaults.
fake_ctx.config = config; // TODO(lsm): do proper error handling here.
ctx = calloc(1, sizeof(*ctx));
if (config->listening_ports == NULL || ctx->user_callback = user_callback;
config->num_threads == NULL ||
config->auth_domain == NULL) { ctx->config[DOCUMENT_ROOT] = mg_strdup(".");
cry(fc(&fake_ctx), "Please specify " ctx->config[LISTENING_PORTS] = mg_strdup("8080");
"num_threads, listening_ports, auth_domain"); ctx->config[ENABLE_DIRECTORY_LISTING] = mg_strdup("yes");
ctx->config[ENABLE_KEEP_ALIVE] = mg_strdup("no");
ctx->config[AUTHENTICATION_DOMAIN] = mg_strdup("mydomain.com");
ctx->config[INDEX_FILES] = mg_strdup("index.html,index.htm,index.cgi");
ctx->config[CGI_EXTENSIONS] = mg_strdup(".cgi,.pl,.php");
ctx->config[SSI_EXTENSIONS] = mg_strdup(".shtml,.shtm");
ctx->config[NUM_THREADS] = mg_strdup("10");
while ((name = *options++) != NULL) {
if ((i = get_option_index(name)) == -1) {
cry(fc(ctx), "Invalid option: %s", name);
free_context(ctx);
return NULL; return NULL;
} else if (config->document_root != NULL && } else if ((value = *options++) == NULL) {
verify_document_root(&fake_ctx, config->document_root) != MG_TRUE) { cry(fc(ctx), "%s: option value cannot be NULL", name);
cry(fc(&fake_ctx), "Invalid root directory: \"%s\"", config->document_root); free_context(ctx);
return NULL; return NULL;
} else if ((ctx = calloc(1, sizeof(*ctx))) == NULL) { }
cry(fc(&fake_ctx), "Cannot allocate mongoose context"); if (ctx->config[i] != NULL) {
free(ctx->config[i]);
}
ctx->config[i] = mg_strdup(value);
}
if (!verify_document_root(ctx)) {
free_context(ctx);
return NULL; return NULL;
} }
ctx->config = config;
// NOTE(lsm): order is important here. SSL certificates must // NOTE(lsm): order is important here. SSL certificates must
// be initialized before listening ports. UID must be set last. // be initialized before listening ports. UID must be set last.
if (set_ssl_option(ctx) == MG_ERROR || if (!set_ssl_option(ctx) ||
set_ports_option(ctx) == MG_ERROR || !set_ports_option(ctx) ||
set_gpass_option(ctx) == MG_ERROR || !set_gpass_option(ctx) ||
#if !defined(_WIN32) #if !defined(_WIN32)
set_uid_option(ctx) == MG_ERROR || !set_uid_option(ctx) ||
#endif #endif
set_acl_option(ctx) == MG_ERROR) { !set_acl_option(ctx)) {
free(ctx); free_context(ctx);
return NULL; return NULL;
} }
...@@ -3816,7 +3862,7 @@ struct mg_context * mg_start(const struct mg_config *config) { ...@@ -3816,7 +3862,7 @@ struct mg_context * mg_start(const struct mg_config *config) {
start_thread(ctx, (mg_thread_func_t) master_thread, ctx); start_thread(ctx, (mg_thread_func_t) master_thread, ctx);
// Start worker threads // Start worker threads
for (i = 0; i < atoi(ctx->config->num_threads); i++) { for (i = 0; i < atoi(ctx->config[NUM_THREADS]); i++) {
if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) { if (start_thread(ctx, (mg_thread_func_t) worker_thread, ctx) != 0) {
cry(fc(ctx), "Cannot start worker thread: %d", ERRNO); cry(fc(ctx), "Cannot start worker thread: %d", ERRNO);
} else { } else {
......
...@@ -29,8 +29,7 @@ struct mg_context; // Handle for the HTTP service itself ...@@ -29,8 +29,7 @@ struct mg_context; // Handle for the HTTP service itself
struct mg_connection; // Handle for the individual connection struct mg_connection; // Handle for the individual connection
// This structure contains full information about the HTTP request. // This structure contains information about the HTTP request.
// It is passed to the user-specified callback function as a parameter.
struct mg_request_info { struct mg_request_info {
char *request_method; // "GET", "POST", etc char *request_method; // "GET", "POST", etc
char *uri; // URL-decoded URI char *uri; // URL-decoded URI
...@@ -40,7 +39,7 @@ struct mg_request_info { ...@@ -40,7 +39,7 @@ struct mg_request_info {
char *log_message; // Mongoose error log message char *log_message; // Mongoose error log message
long remote_ip; // Client's IP address long remote_ip; // Client's IP address
int remote_port; // Client's port int remote_port; // Client's port
int status_code; // HTTP status code int status_code; // HTTP reply status code
int is_ssl; // 1 if SSL-ed, 0 if not int is_ssl; // 1 if SSL-ed, 0 if not
int num_headers; // Number of headers int num_headers; // Number of headers
struct mg_header { struct mg_header {
...@@ -49,65 +48,57 @@ struct mg_request_info { ...@@ -49,65 +48,57 @@ struct mg_request_info {
} http_headers[64]; // Maximum 64 headers } http_headers[64]; // Maximum 64 headers
}; };
// User-defined handler function. It must return MG_SUCCESS or MG_ERROR. // Various events on which user-defined function is called by Mongoose.
enum mg_event {
MG_NEW_REQUEST, // New HTTP request has arrived from the client
MG_HTTP_ERROR, // HTTP error must be returned to the client
MG_EVENT_LOG, // Mongoose logs an event, request_info.log_message
MG_INIT_SSL, // Mongoose initializes SSL. Instead of mg_connection *,
// SSL context is passed to the callback function.
};
// Prototype for the user-defined function. Mongoose calls this function
// on every event mentioned above.
//
// Parameters:
// event: which event has been triggered.
// conn: opaque connection handler. Could be used to read, write data to the
// client, etc. See functions below that accept "mg_connection *".
// request_info: Information about HTTP request.
// //
// If handler returns MG_SUCCESS, that means that handler has processed the // Return:
// If handler returns non-NULL, that means that handler has processed the
// request by sending appropriate HTTP reply to the client. Mongoose treats // request by sending appropriate HTTP reply to the client. Mongoose treats
// the request as served. // the request as served.
// // If callback returns NULL, that means that callback has not processed
// If callback returns MG_ERROR, that means that callback has not processed
// the request. Handler must not send any data to the client in this case. // the request. Handler must not send any data to the client in this case.
// Mongoose proceeds with request handling as if nothing happened. // Mongoose proceeds with request handling as if nothing happened.
// typedef void * (*mg_callback_t)(enum mg_event event,
// NOTE: ssl_password_handler must have the following prototype: struct mg_connection *conn,
// int (*)(char *, int, int, void *) struct mg_request_info *request_info);
// Refer to OpenSSL documentation for more details.
enum mg_error_t {
MG_ERROR,
MG_SUCCESS,
MG_NOT_FOUND,
MG_BUFFER_TOO_SMALL
};
typedef enum mg_error_t (*mg_callback_t)(struct mg_connection *,
const struct mg_request_info *);
// This structure describes Mongoose configuration.
struct mg_config {
char *document_root;
char *index_files;
char *ssl_certificate;
char *listening_ports;
char *cgi_extensions;
char *cgi_interpreter;
char *cgi_environment;
char *ssi_extensions;
char *auth_domain;
char *protect;
char *global_passwords_file;
char *put_delete_passwords_file;
char *access_log_file;
char *error_log_file;
char *acl;
char *uid;
char *mime_types;
char *enable_directory_listing;
char *num_threads;
mg_callback_t new_request_handler;
mg_callback_t http_error_handler;
mg_callback_t event_log_handler;
mg_callback_t ssl_password_handler;
};
// Start the web server. // Start web server.
//
// Parameters:
// callback: user defined event handling function or NULL.
// options: NULL terminated list of option_name, option_value pairs that
// specify Mongoose configuration parameters.
//
// Example:
// const char *options[] = {
// "document_root", "/var/www",
// "listening_ports", "80,443s",
// NULL
// };
// struct mg_context *ctx = mg_start(&my_func, options);
//
// Please refer to http://code.google.com/p/mongoose/wiki/MongooseManual
// for the list of valid option and their possible values.
// //
// This must be the first function called by the application. // Return:
// It creates a serving thread, and returns a context structure that // web server context, or NULL on error.
// can be used to stop the server. struct mg_context *mg_start(mg_callback_t callback, const char **options);
// After calling mg_start(), configuration data must not be changed.
struct mg_context *mg_start(const struct mg_config *);
// Stop the web server. // Stop the web server.
...@@ -118,6 +109,19 @@ struct mg_context *mg_start(const struct mg_config *); ...@@ -118,6 +109,19 @@ struct mg_context *mg_start(const struct mg_config *);
void mg_stop(struct mg_context *); void mg_stop(struct mg_context *);
// Get the value of particular configuration parameter.
// The value returned is read-only. Mongoose does not allow changing
// configuration at run time.
// If given parameter name is not valid, NULL is returned. For valid
// names, return value is guaranteed to be non-NULL. If parameter is not
// set, zero-length string is returned.
const char *mg_get_option(const struct mg_context *ctx, const char *name);
// Return array of valid configuration options.
const char **mg_get_valid_option_names(void);
// Add, edit or delete the entry in the passwords file. // Add, edit or delete the entry in the passwords file.
// //
// This function allows an application to manipulate .htpasswd files on the // This function allows an application to manipulate .htpasswd files on the
...@@ -129,9 +133,9 @@ void mg_stop(struct mg_context *); ...@@ -129,9 +133,9 @@ void mg_stop(struct mg_context *);
// If password is NULL, entry is deleted. // If password is NULL, entry is deleted.
// //
// Return: // Return:
// MG_ERROR, MG_SUCCESS // 1 on success, 0 on error.
enum mg_error_t mg_modify_passwords_file(struct mg_context *ctx, int mg_modify_passwords_file(struct mg_context *ctx,
const char *file_name, const char *user, const char *password); const char *passwords_file_name, const char *user, const char *password);
// Send data to the client. // Send data to the client.
int mg_write(struct mg_connection *, const void *buf, size_t len); int mg_write(struct mg_connection *, const void *buf, size_t len);
...@@ -160,32 +164,35 @@ const char *mg_get_header(const struct mg_connection *, const char *name); ...@@ -160,32 +164,35 @@ const char *mg_get_header(const struct mg_connection *, const char *name);
// Get a value of particular form variable. // Get a value of particular form variable.
// //
// Either request_info->query_string or read POST data can be scanned. // Parameters:
// mg_get_qsvar() is convenience method to get variable from the query string. // data: pointer to form-uri-encoded buffer. This could be either POST data,
// Destination buffer is guaranteed to be '\0' - terminated. In case of // or request_info.query_string.
// failure, dst[0] == '\0'. // data_len: length of the encoded data.
// var_name: variable name to decode from the buffer
// buf: destination buffer for the decoded variable
// buf_len: length of the destination buffer
// //
// Return: // Return:
// MG_SUCCESS Variable value was successfully copied in the buffer. // On success, length of the decoded variable.
// MG_NOT_FOUND Requested variable not found. // On error, -1 (variable not found, or destination buffer is too small).
// MG_BUFFER_TOO_SMALL Destination buffer is too small to hold the value. //
enum mg_error_t mg_get_var(const char *data, size_t data_len, // Destination buffer is guaranteed to be '\0' - terminated. In case of
const char *var_name, char *buf, size_t buf_len); // failure, dst[0] == '\0'.
enum mg_error_t mg_get_qsvar(const struct mg_request_info *, int mg_get_var(const char *data, size_t data_len,
const char *var_name, char *buf, size_t buf_len); const char *var_name, char *buf, size_t buf_len);
// Fetch value of certain cookie variable into the destination buffer. // Fetch value of certain cookie variable into the destination buffer.
// //
// Destination buffer is guaranteed to be '\0' - terminated. In case of // Destination buffer is guaranteed to be '\0' - terminated. In case of
// failure, dst[0] == '\0'. Note that RFC allows many occurences of the same // failure, dst[0] == '\0'. Note that RFC allows many occurrences of the same
// parameter. This function returns only first occurance. // parameter. This function returns only first occurrence.
// //
// Return: // Return:
// MG_SUCCESS Cookie parameter was successfully copied in the buffer. // On success, value length.
// MG_NOT_FOUND Either "Cookie:" header is not present at all, or the // On error, -1 (either "Cookie:" header is not present at all, or the
// requested parameter is not found. // requested parameter is not found, or destination buffer is too small
// MG_BUFFER_TOO_SMALL Destination buffer is too small to hold the value. // to hold the value).
enum mg_error_t mg_get_cookie(const struct mg_connection *, int mg_get_cookie(const struct mg_connection *,
const char *cookie_name, char *buf, size_t buf_len); const char *cookie_name, char *buf, size_t buf_len);
......
/* // Copyright (c) 2004-2009 Sergey Lyubka
* Copyright (c) 2004-2009 Sergey Lyubka //
* // Permission is hereby granted, free of charge, to any person obtaining a copy
* Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal
* of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights
* in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is
* copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions:
* furnished to do so, subject to the following conditions: //
* // The above copyright notice and this permission notice shall be included in
* The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software.
* all copies or substantial portions of the Software. //
* // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE.
* THE SOFTWARE. //
* // Unit test for the mongoose web server. Tests embedded API.
* $Id: embed.c 471 2009-08-30 14:30:21Z valenok $
* Unit test for the mongoose web server. Tests embedded API.
*/
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include "mongoose.h" #include "mongoose.h"
#if !defined(LISTENING_PORT) #if !defined(LISTENING_PORT)
#define LISTENING_PORT "23456" #define LISTENING_PORT "23456"
#endif /* !LISTENING_PORT */ #endif
static const char *standard_reply = "HTTP/1.1 200 OK\r\n" static const char *standard_reply = "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n" "Content-Type: text/plain\r\n"
"Connection: close\r\n\r\n"; "Connection: close\r\n\r\n";
static void static void test_get_var(struct mg_connection *conn,
test_get_var(struct mg_connection *conn, const struct mg_request_info *ri, const struct mg_request_info *ri) {
void *user_data) char *var, *buf;
{ size_t buf_len;
char *value; const char *cl;
int var_len;
mg_printf(conn, "%s", standard_reply); mg_printf(conn, "%s", standard_reply);
value = mg_get_var(conn, "my_var"); buf_len = 0;
if (value != NULL) { var = buf = NULL;
mg_printf(conn, "Value: [%s]\n", value); cl = mg_get_header(conn, "Content-Length");
mg_printf(conn, "Value size: [%u]\n", (unsigned) strlen(value)); mg_printf(conn, "cl: %p\n", cl);
free(value); if (!strcmp(ri->request_method, "POST") && cl != NULL) {
buf_len = atoi(cl);
buf = malloc(buf_len);
mg_read(conn, buf, buf_len);
} else if (ri->query_string != NULL) {
buf_len = strlen(ri->query_string);
buf = malloc(buf_len + 1);
strcpy(buf, ri->query_string);
} }
var = malloc(buf_len + 1);
var_len = mg_get_var(buf, buf_len, "my_var", var, buf_len + 1);
mg_printf(conn, "Value: [%s]\n", var);
mg_printf(conn, "Value size: [%d]\n", var_len);
free(buf);
free(var);
} }
static void static void test_get_header(struct mg_connection *conn,
test_get_header(struct mg_connection *conn, const struct mg_request_info *ri, const struct mg_request_info *ri) {
void *user_data)
{
const char *value; const char *value;
int i;
mg_printf(conn, "%s", standard_reply); mg_printf(conn, "%s", standard_reply);
{
int i;
printf("HTTP headers: %d\n", ri->num_headers); printf("HTTP headers: %d\n", ri->num_headers);
for (i = 0; i < ri->num_headers; i++) for (i = 0; i < ri->num_headers; i++) {
printf("[%s]: [%s]\n", printf("[%s]: [%s]\n", ri->http_headers[i].name, ri->http_headers[i].value);
ri->http_headers[i].name,
ri->http_headers[i].value);
} }
value = mg_get_header(conn, "Host"); value = mg_get_header(conn, "Host");
if (value != NULL) if (value != NULL) {
mg_printf(conn, "Value: [%s]", value); mg_printf(conn, "Value: [%s]", value);
}
} }
static void static void test_get_request_info(struct mg_connection *conn,
test_get_ri(struct mg_connection *conn, const struct mg_request_info *ri, const struct mg_request_info *ri) {
void *user_data)
{
int i; int i;
mg_printf(conn, "%s", standard_reply); mg_printf(conn, "%s", standard_reply);
...@@ -88,110 +95,79 @@ test_get_ri(struct mg_connection *conn, const struct mg_request_info *ri, ...@@ -88,110 +95,79 @@ test_get_ri(struct mg_connection *conn, const struct mg_request_info *ri,
mg_printf(conn, "URI: [%s]\n", ri->uri); mg_printf(conn, "URI: [%s]\n", ri->uri);
mg_printf(conn, "HTTP version: [%s]\n", ri->http_version); mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
for (i = 0; i < ri->num_headers; i++) for (i = 0; i < ri->num_headers; i++) {
mg_printf(conn, "HTTP header [%s]: [%s]\n", mg_printf(conn, "HTTP header [%s]: [%s]\n",
ri->http_headers[i].name, ri->http_headers[i].name,
ri->http_headers[i].value); ri->http_headers[i].value);
}
mg_printf(conn, "Query string: [%s]\n", mg_printf(conn, "Query string: [%s]\n",
ri->query_string ? ri->query_string: ""); ri->query_string ? ri->query_string: "");
mg_printf(conn, "POST data: [%.*s]\n",
ri->post_data_len, ri->post_data);
mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip); mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
mg_printf(conn, "Remote port: [%d]\n", ri->remote_port); mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
mg_printf(conn, "Remote user: [%s]\n", mg_printf(conn, "Remote user: [%s]\n",
ri->remote_user ? ri->remote_user : ""); ri->remote_user ? ri->remote_user : "");
} }
static void static void test_error(struct mg_connection *conn,
test_error(struct mg_connection *conn, const struct mg_request_info *ri, const struct mg_request_info *ri) {
void *user_data)
{
const char *value;
mg_printf(conn, "HTTP/1.1 %d XX\r\n" mg_printf(conn, "HTTP/1.1 %d XX\r\n"
"Conntection: close\r\n\r\n", ri->status_code); "Conntection: close\r\n\r\n", ri->status_code);
mg_printf(conn, "Error: [%d]", ri->status_code); mg_printf(conn, "Error: [%d]", ri->status_code);
} }
static void static void test_post(struct mg_connection *conn,
test_user_data(struct mg_connection *conn, const struct mg_request_info *ri, const struct mg_request_info *ri) {
void *user_data) const char *cl;
{ char *buf;
const char *value; int len;
mg_printf(conn, "%s", standard_reply);
mg_printf(conn, "User data: [%d]", * (int *) user_data);
}
static void
test_protect(struct mg_connection *conn, const struct mg_request_info *ri,
void *user_data)
{
const char *allowed_user = * (char **) user_data;
const char *remote_user = ri->remote_user;
int allowed;
allowed = remote_user != NULL && !strcmp(allowed_user, remote_user);
* (long *) user_data = allowed ? 1 : 0;
}
static void
test_post(struct mg_connection *conn, const struct mg_request_info *ri,
void *user_data)
{
mg_printf(conn, "%s", standard_reply); mg_printf(conn, "%s", standard_reply);
mg_write(conn, ri->post_data, ri->post_data_len); if (strcmp(ri->request_method, "POST") == 0 &&
} (cl = mg_get_header(conn, "Content-Length")) != NULL) {
len = atoi(cl);
static void if ((buf = malloc(len)) != NULL) {
test_put(struct mg_connection *conn, const struct mg_request_info *ri, mg_write(conn, buf, len);
void *user_data) free(buf);
{ }
mg_printf(conn, "%s", standard_reply); }
mg_write(conn, ri->post_data, ri->post_data_len);
} }
static void static const struct test_config {
test_remove_callback(struct mg_connection *conn, enum mg_event event;
const struct mg_request_info *ri, void *user_data) const char *uri;
{ void (*func)(struct mg_connection *, const struct mg_request_info *);
struct mg_context *ctx = (struct mg_context *) user_data; } test_config[] = {
const char *uri_regex = "/foo/*"; {MG_NEW_REQUEST, "/test_get_header", &test_get_header},
{MG_NEW_REQUEST, "/test_get_var", &test_get_var},
{MG_NEW_REQUEST, "/test_get_request_info", &test_get_request_info},
{MG_NEW_REQUEST, "/test_post", &test_post},
{MG_HTTP_ERROR, "", &test_error},
{0, NULL, NULL}
};
static void *callback(enum mg_event event,
struct mg_connection *conn,
struct mg_request_info *request_info) {
int i;
mg_printf(conn, "%sRemoving callbacks bound to [%s]", for (i = 0; test_config[i].uri != NULL; i++) {
standard_reply, uri_regex); if (event == test_config[i].event &&
(event == MG_HTTP_ERROR ||
!strcmp(request_info->uri, test_config[i].uri))) {
test_config[i].func(conn, request_info);
return "processed";
}
}
/* Un-bind bound callback */ return NULL;
mg_set_uri_callback(ctx, uri_regex, NULL, NULL);
} }
int main(void) int main(void) {
{
int user_data = 1234;
struct mg_context *ctx; struct mg_context *ctx;
const char *options[] = {"listening_ports", LISTENING_PORT, NULL};
ctx = mg_start(); ctx = mg_start(callback, options);
mg_set_option(ctx, "ports", LISTENING_PORT); pause();
return 0;
mg_set_uri_callback(ctx, "/test_get_header", &test_get_header, NULL);
mg_set_uri_callback(ctx, "/test_get_var", &test_get_var, NULL);
mg_set_uri_callback(ctx, "/test_get_request_info", &test_get_ri, NULL);
mg_set_uri_callback(ctx, "/foo/*", &test_get_ri, NULL);
mg_set_uri_callback(ctx, "/test_user_data",
&test_user_data, &user_data);
mg_set_uri_callback(ctx, "/p", &test_post, NULL);
mg_set_uri_callback(ctx, "/put", &test_put, NULL);
mg_set_uri_callback(ctx, "/test_remove_callback",
&test_remove_callback, ctx);
mg_set_error_callback(ctx, 404, &test_error, NULL);
mg_set_error_callback(ctx, 0, &test_error, NULL);
mg_set_auth_callback(ctx, "/foo/secret", &test_protect, (void *) "joe");
for (;;)
(void) getchar();
} }
...@@ -146,7 +146,7 @@ if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') { ...@@ -146,7 +146,7 @@ if (scalar(@ARGV) > 0 and $ARGV[0] eq 'embedded') {
} }
# Make sure we load config file if no options are given # Make sure we load config file if no options are given
write_file($config, "ports 12345\naccess_log access.log\n"); write_file($config, "listening_ports 12345\naccess_log_file access.log\n");
spawn($exe); spawn($exe);
my $saved_port = $port; my $saved_port = $port;
$port = 12345; $port = 12345;
...@@ -156,11 +156,13 @@ unlink $config; ...@@ -156,11 +156,13 @@ unlink $config;
kill_spawned_child(); kill_spawned_child();
# Spawn the server on port $port # Spawn the server on port $port
my $cmd = "$exe -ports $port -access_log access.log -error_log debug.log ". my $cmd = "$exe -listening_ports $port -access_log_file access.log ".
"-cgi_env CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " . "-error_log_file debug.log ".
"-mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " . "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
"-root $root,/aiased=/etc/,/ta=$test_dir"; "-extra_mime_types .bar=foo/bar,.tar.gz=blah,.baz=foo " .
$cmd .= ' -cgi_interp perl' if on_windows(); '-put_delete_passwords_file test/passfile ' .
"-document_root $root,/aiased=/etc/,/ta=$test_dir";
$cmd .= ' -cgi_interpreter perl' if on_windows();
spawn($cmd); spawn($cmd);
# Try to overflow: Send very long request # Try to overflow: Send very long request
...@@ -349,15 +351,12 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") { ...@@ -349,15 +351,12 @@ unless (scalar(@ARGV) > 0 and $ARGV[0] eq "basic_tests") {
$content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file"); $content =~ /^b:a:\w+$/gs or fail("Bad content of the passwd file");
unlink $path; unlink $path;
kill_spawned_child();
do_PUT_test(); do_PUT_test();
#do_embedded_test(); kill_spawned_child();
do_embedded_test();
} }
sub do_PUT_test { sub do_PUT_test {
$cmd .= ' -auth_PUT test/passfile';
spawn($cmd);
my $auth_header = "Authorization: Digest username=guest, ". my $auth_header = "Authorization: Digest username=guest, ".
"realm=mydomain.com, nonce=1145872809, uri=/put.txt, ". "realm=mydomain.com, nonce=1145872809, uri=/put.txt, ".
"response=896327350763836180c61d87578037d9, qop=auth, ". "response=896327350763836180c61d87578037d9, qop=auth, ".
...@@ -379,16 +378,14 @@ sub do_PUT_test { ...@@ -379,16 +378,14 @@ sub do_PUT_test {
o("PUT /put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n". o("PUT /put.txt HTTP/1.0\nExpect: 100-continue\nContent-Length: 4\n".
"$auth_header\nabcd", "$auth_header\nabcd",
"HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue'); "HTTP/1.1 100 Continue.+HTTP/1.1 200", 'PUT 100-Continue');
kill_spawned_child();
} }
sub do_embedded_test { sub do_embedded_test {
my $cmd = "cc -o $embed_exe $root/embed.c mongoose.c -I. ". my $cmd = "cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. ".
"-DNO_SSL -lpthread -DLISTENING_PORT=\\\"$port\\\""; "-pthread -DLISTENING_PORT=\\\"$port\\\"";
if (on_windows()) { if (on_windows()) {
$cmd = "cl $root/embed.c mongoose.c /I. /nologo ". $cmd = "cl $root/embed.c mongoose.c /I. /nologo ".
"/DNO_SSL /DLISTENING_PORT=\\\"$port\\\" ". "/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
"/link /out:$embed_exe.exe ws2_32.lib ";
} }
print $cmd, "\n"; print $cmd, "\n";
system($cmd) == 0 or fail("Cannot compile embedded unit test"); system($cmd) == 0 or fail("Cannot compile embedded unit test");
...@@ -415,30 +412,24 @@ sub do_embedded_test { ...@@ -415,30 +412,24 @@ sub do_embedded_test {
# + in form data MUST be decoded to space # + in form data MUST be decoded to space
o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n". o("POST /test_get_var HTTP/1.0\nContent-Length: 10\n\n".
"my_var=b+c", 'Value: \[b c\]', 'mg_get_var 7', 0); "my_var=b+c", 'Value: \[b c\]', 'mg_get_var 9', 0);
# Test that big POSTed vars are not truncated # Test that big POSTed vars are not truncated
my $my_var = 'x' x 64000; my $my_var = 'x' x 64000;
o("POST /test_get_var HTTP/1.0\nContent-Length: 64007\n\n". o("POST /test_get_var HTTP/1.0\nContent-Length: 64007\n\n".
"my_var=$my_var", 'Value size: \[64000\]', 'mg_get_var 8', 0); "my_var=$my_var", 'Value size: \[64000\]', 'mg_get_var 10', 0);
# Test PUT
o("PUT /put HTTP/1.0\nContent-Length: 3\n\nabc",
'\nabc$', 'put callback', 0);
o("POST /test_get_request_info?xx=yy HTTP/1.0\nFoo: bar\n". o("POST /test_get_request_info?xx=yy HTTP/1.0\nFoo: bar\n".
"Content-Length: 3\n\na=b", "Content-Length: 3\n\na=b",
'Method: \[POST\].URI: \[/test_get_request_info\].'. 'Method: \[POST\].URI: \[/test_get_request_info\].'.
'HTTP version: \[1.0\].HTTP header \[Foo\]: \[bar\].'. 'HTTP version: \[1.0\].HTTP header \[Foo\]: \[bar\].'.
'HTTP header \[Content-Length\]: \[3\].'. 'HTTP header \[Content-Length\]: \[3\].'.
'Query string: \[xx=yy\].POST data: \[a=b\].'. 'Query string: \[xx=yy\].'.
'Remote IP: \[\d+\].Remote port: \[\d+\].'. 'Remote IP: \[\d+\].Remote port: \[\d+\].'.
'Remote user: \[\]' 'Remote user: \[\]'
, 'request_info', 0); , 'request_info', 0);
o("GET /not_exist HTTP/1.0\n\n", 'Error: \[404\]', '404 handler', 0); o("GET /not_exist HTTP/1.0\n\n", 'Error: \[404\]', '404 handler', 0);
o("bad request\n\n", 'Error: \[400\]', '* error handler', 0); o("bad request\n\n", 'Error: \[400\]', '* error handler', 0);
o("GET /test_user_data HTTP/1.0\n\n",
'User data: \[1234\]', 'user data in callback', 0);
# o("GET /foo/secret HTTP/1.0\n\n", # o("GET /foo/secret HTTP/1.0\n\n",
# '401 Unauthorized', 'mg_protect_uri', 0); # '401 Unauthorized', 'mg_protect_uri', 0);
# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n", # o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
...@@ -446,12 +437,6 @@ sub do_embedded_test { ...@@ -446,12 +437,6 @@ sub do_embedded_test {
# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=joe\n\n", # o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=joe\n\n",
# '200 OK', 'mg_protect_uri (joe)', 0); # '200 OK', 'mg_protect_uri (joe)', 0);
# Test un-binding the URI
o("GET /foo/bar HTTP/1.0\n\n", 'HTTP/1.1 200 OK', '/foo bound', 0);
o("GET /test_remove_callback HTTP/1.0\n\n",
'Removing callbacks', 'Callback removal', 0);
o("GET /foo/bar HTTP/1.0\n\n", 'HTTP/1.1 404', '/foo unbound', 0);
kill_spawned_child(); kill_spawned_child();
} }
......
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