Newer
Older
if (skip_user == 0 && conn->server->event_handler != NULL) {
{
const char *cl = mg_get_header(&conn->mg_conn, "Content-Length");
if ((strcmp(conn->mg_conn.request_method, "POST") == 0 ||
strcmp(conn->mg_conn.request_method, "PUT") == 0) &&
(cl == NULL || to64(cl) > MONGOOSE_POST_SIZE_LIMIT)) {
send_http_error(conn, 500, "POST size > %zu",
#ifdef MONGOOSE_NO_FILESYSTEM
if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
send_options(conn);
} else {
send_http_error(conn, 404, NULL);
}
exists = convert_uri_to_file_name(conn, path, sizeof(path), &st);
is_directory = S_ISDIR(st.st_mode);
if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
send_options(conn);
} else if (conn->server->config_options[DOCUMENT_ROOT] == NULL) {
send_http_error(conn, 404, NULL);
#ifndef MONGOOSE_NO_AUTH
} else if ((!is_dav_request(conn) && !is_authorized(conn, path)) ||
(is_dav_request(conn) && !is_authorized_for_dav(conn))) {
mg_send_digest_auth_request(&conn->mg_conn);
close_local_endpoint(conn);
} else if (!strcmp(conn->mg_conn.request_method, "PROPFIND")) {
handle_propfind(conn, path, &st, exists);
} else if (!strcmp(conn->mg_conn.request_method, "MKCOL")) {
handle_mkcol(conn, path);
} else if (!strcmp(conn->mg_conn.request_method, "DELETE")) {
handle_delete(conn, path);
} else if (!strcmp(conn->mg_conn.request_method, "PUT")) {
handle_put(conn, path);
} else if (!exists || must_hide_file(conn, path)) {
send_http_error(conn, 404, NULL);
} else if (is_directory &&
conn->mg_conn.uri[strlen(conn->mg_conn.uri) - 1] != '/') {
conn->mg_conn.status_code = 301;
mg_printf(&conn->mg_conn, "HTTP/1.1 301 Moved Permanently\r\n"
"Location: %s/\r\n\r\n", conn->mg_conn.uri);
close_local_endpoint(conn);
} else if (is_directory && !find_index_file(conn, path, sizeof(path), &st)) {
if (!mg_strcasecmp(dir_lst, "yes")) {
#ifndef MONGOOSE_NO_DIRECTORY_LISTING
send_directory_listing(conn, path);
#else
send_http_error(conn, 501, NULL);
send_http_error(conn, 403, NULL);
} else if (mg_match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) {
#if !defined(MONGOOSE_NO_CGI)
#endif // !MONGOOSE_NO_CGI
} else if (mg_match_prefix(conn->server->config_options[SSI_PATTERN],
strlen(conn->server->config_options[SSI_PATTERN]),
path) > 0) {
} else if (is_not_modified(conn, &st)) {
send_http_error(conn, 304, NULL);
} else if ((conn->endpoint.fd = open(path, O_RDONLY | O_BINARY)) != -1) {
// O_BINARY is required for Windows, otherwise in default text mode
// two bytes \r\n will be read as one.
open_file_endpoint(conn, path, &st);
} else {
send_http_error(conn, 404, NULL);
#endif // MONGOOSE_NO_FILESYSTEM
static void send_continue_if_expected(struct connection *conn) {
static const char expect_response[] = "HTTP/1.1 100 Continue\r\n\r\n";
const char *expect_hdr = mg_get_header(&conn->mg_conn, "Expect");
if (expect_hdr != NULL && !mg_strcasecmp(expect_hdr, "100-continue")) {
ns_send(conn->ns_conn, expect_response, sizeof(expect_response) - 1);
static int is_valid_uri(const char *uri) {
// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
// URI can be an asterisk (*) or should start with slash.
return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
}
static void try_parse(struct connection *conn) {
struct iobuf *io = &conn->ns_conn->recv_iobuf;
if (conn->request_len == 0 &&
(conn->request_len = get_request_len(io->buf, io->len)) > 0) {
// If request is buffered in, remove it from the iobuf. This is because
// iobuf could be reallocated, and pointers in parsed request could
// become invalid.
conn->request = (char *) malloc(conn->request_len);
memcpy(conn->request, io->buf, conn->request_len);
DBG(("%p [%.*s]", conn, conn->request_len, conn->request));
conn->request_len = parse_http_message(conn->request, conn->request_len,
&conn->mg_conn);
if (conn->request_len > 0) {
const char *cl_hdr = mg_get_header(&conn->mg_conn, "Content-Length");
conn->cl = cl_hdr == NULL ? 0 : to64(cl_hdr);
conn->mg_conn.content_len = (size_t) conn->cl;
static void process_request(struct connection *conn) {
try_parse(conn);
DBG(("%p %d %d %d [%.*s]", conn, conn->request_len, io->len,
conn->ns_conn->flags, io->len, io->buf));
if (conn->request_len < 0 ||
(conn->request_len > 0 && !is_valid_uri(conn->mg_conn.uri))) {
send_http_error(conn, 400, NULL);
} else if (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE) {
send_http_error(conn, 413, NULL);
} else if (conn->request_len > 0 &&
strcmp(conn->mg_conn.http_version, "1.0") != 0 &&
strcmp(conn->mg_conn.http_version, "1.1") != 0) {
send_http_error(conn, 505, NULL);
} else if (conn->request_len > 0 && conn->endpoint_type == EP_NONE) {
#ifndef MONGOOSE_NO_WEBSOCKET
send_websocket_handshake_if_requested(&conn->mg_conn);
#endif
send_continue_if_expected(conn);
open_local_endpoint(conn, 0);
if (conn->endpoint_type == EP_CGI && io->len > 0) {
forward_post_data(conn);
}
#endif
if (conn->endpoint_type == EP_USER) {
call_request_handler_if_data_is_buffered(conn);
if (conn->endpoint_type == EP_PUT && io->len > 0) {
forward_put_data(conn);
Sergey Lyubka
committed
static void call_http_client_handler(struct connection *conn) {
//conn->mg_conn.status_code = code;
// For responses without Content-Lengh, use the whole buffer
Sergey Lyubka
committed
if (conn->cl == 0) {
conn->mg_conn.content_len = conn->ns_conn->recv_iobuf.len;
conn->mg_conn.content = conn->ns_conn->recv_iobuf.buf;
Sergey Lyubka
committed
if (call_user(conn, MG_REPLY) == MG_FALSE) {
iobuf_remove(&conn->ns_conn->recv_iobuf, conn->mg_conn.content_len);
conn->mg_conn.status_code = 0;
conn->cl = conn->num_bytes_sent = conn->request_len = 0;
free(conn->request);
conn->request = NULL;
}
static void process_response(struct connection *conn) {
DBG(("%p %d %d [%.*s]", conn, conn->request_len, io->len,
io->len > 40 ? 40 : io->len, io->buf));
if (conn->request_len < 0 ||
(conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) {
Sergey Lyubka
committed
call_http_client_handler(conn);
Sergey Lyubka
committed
call_http_client_handler(conn);
struct mg_connection *mg_connect(struct mg_server *server, const char *host,
int port, int use_ssl) {
struct ns_connection *nsconn;
struct connection *conn;
nsconn = ns_connect(&server->ns_server, host, port, use_ssl, NULL);
if ((conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
nsconn->flags |= NSF_CLOSE_IMMEDIATELY;
// Interlink two structs
conn->ns_conn = nsconn;
nsconn->connection_data = conn;
//conn->handler = handler;
conn->mg_conn.server_param = server->ns_server.server_data;
conn->ns_conn->flags = NSF_CONNECTING;
return &conn->mg_conn;
#ifndef MONGOOSE_NO_LOGGING
static void log_header(const struct mg_connection *conn, const char *header,
FILE *fp) {
const char *header_value;
if ((header_value = mg_get_header(conn, header)) == NULL) {
(void) fprintf(fp, "%s", " -");
} else {
(void) fprintf(fp, " \"%s\"", header_value);
static void log_access(const struct connection *conn, const char *path) {
const struct mg_connection *c = &conn->mg_conn;
FILE *fp = (path == NULL) ? NULL : fopen(path, "a+");
now = time(NULL);
strftime(date, sizeof(date), "%d/%b/%Y:%H:%M:%S %z", localtime(&now));
flockfile(fp);
mg_parse_header(mg_get_header(&conn->mg_conn, "Authorization"), "username",
user, sizeof(user));
fprintf(fp, "%s - %s [%s] \"%s %s%s%s HTTP/%s\" %d %" INT64_FMT,
c->remote_ip, user[0] == '\0' ? "-" : user, date,
c->request_method ? c->request_method : "-",
c->uri ? c->uri : "-", c->query_string ? "?" : "",
c->query_string ? c->query_string : "",
c->http_version, c->status_code, conn->num_bytes_sent);
log_header(c, "Referer", fp);
log_header(c, "User-Agent", fp);
fputc('\n', fp);
fflush(fp);
static void close_local_endpoint(struct connection *conn) {
struct mg_connection *c = &conn->mg_conn;
// Must be done before free()
int keep_alive = should_keep_alive(&conn->mg_conn) &&
(conn->endpoint_type == EP_FILE || conn->endpoint_type == EP_USER);
DBG(("%p %d %d %d", conn, conn->endpoint_type, keep_alive,
conn->ns_conn->flags));
case EP_PUT:
case EP_FILE:
close(conn->endpoint.fd);
break;
case EP_CGI:
if (conn->endpoint.cgi_conn != NULL) {
conn->endpoint.cgi_conn->flags |= NSF_CLOSE_IMMEDIATELY;
conn->endpoint.cgi_conn->connection_data = NULL;
}
break;
#ifndef MONGOOSE_NO_LOGGING
if (c->status_code > 0 && conn->endpoint_type != EP_CLIENT &&
c->status_code != 400) {
log_access(conn, conn->server->config_options[ACCESS_LOG_FILE]);
}
#endif
// Gobble possible POST data sent to the URI handler
iobuf_remove(&conn->ns_conn->recv_iobuf, conn->mg_conn.content_len);
conn->cl = conn->num_bytes_sent = conn->request_len = 0;
conn->ns_conn->flags &= ~(NSF_FINISHED_SENDING_DATA |
NSF_BUFFER_BUT_DONT_SEND | NSF_CLOSE_IMMEDIATELY |
MG_HEADERS_SENT | MG_LONG_RUNNING);
c->request_method = c->uri = c->http_version = c->query_string = NULL;
c->num_headers = c->status_code = c->is_websocket = c->content_len = 0;
free(conn->request); conn->request = NULL;
free(conn->path_info); conn->path_info = NULL;
if (keep_alive) {
process_request(conn); // Can call us recursively if pipelining is used
conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len == 0 ?
NSF_CLOSE_IMMEDIATELY : NSF_FINISHED_SENDING_DATA;
static void transfer_file_data(struct connection *conn) {
char buf[IOBUF_SIZE];
int n = read(conn->endpoint.fd, buf, conn->cl < (int64_t) sizeof(buf) ?
(int) conn->cl : (int) sizeof(buf));
close_local_endpoint(conn);
} else if (n > 0) {
conn->cl -= n;
if (conn->cl <= 0) {
close_local_endpoint(conn);
int mg_poll_server(struct mg_server *server, int milliseconds) {
return ns_server_poll(&server->ns_server, milliseconds);
void mg_destroy_server(struct mg_server **server) {
if (server != NULL && *server != NULL) {
for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) {
free(s->config_options[i]); // It is OK to free(NULL)
struct mg_iterator {
mg_handler_t cb;
void *param;
};
static void iter(struct ns_connection *nsconn, enum ns_event ev, void *param) {
if (ev == NS_POLL) {
struct mg_iterator *it = (struct mg_iterator *) param;
struct connection *c = (struct connection *) nsconn->connection_data;
Sergey Lyubka
committed
if (c != NULL) c->mg_conn.callback_param = it->param;
it->cb(&c->mg_conn, MG_POLL);
Sergey Lyubka
committed
// Apply function to all active connections.
void mg_iterate_over_connections(struct mg_server *server, mg_handler_t cb,
void *param) {
struct mg_iterator it = { cb, param };
ns_iterate(&server->ns_server, iter, &it);
static int get_var(const char *data, size_t data_len, const char *name,
char *dst, size_t dst_len) {
const char *p, *e, *s;
size_t name_len;
int len;
if (dst == NULL || dst_len == 0) {
len = -2;
} else if (data == NULL || name == NULL || data_len == 0) {
len = -1;
dst[0] = '\0';
} else {
name_len = strlen(name);
e = data + data_len;
len = -1;
dst[0] = '\0';
// data is "var1=val1&var2=val2...". Find variable first
for (p = data; p + name_len < e; p++) {
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
!mg_strncasecmp(name, p, name_len)) {
// Point p to variable value
p += name_len + 1;
// Point s to the end of the value
s = (const char *) memchr(p, '&', (size_t)(e - p));
if (s == NULL) {
s = e;
}
assert(s >= p);
// Decode variable into destination buffer
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
// Redirect error code from -1 to -2 (destination buffer too small).
if (len == -1) {
len = -2;
}
break;
}
}
}
int mg_get_var(const struct mg_connection *conn, const char *name,
char *dst, size_t dst_len) {
int len = get_var(conn->query_string, conn->query_string == NULL ? 0 :
strlen(conn->query_string), name, dst, dst_len);
if (len < 0) {
len = get_var(conn->content, conn->content_len, name, dst, dst_len);
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
static int get_line_len(const char *buf, int buf_len) {
int len = 0;
while (len < buf_len && buf[len] != '\n') len++;
return buf[len] == '\n' ? len + 1: -1;
}
int mg_parse_multipart(const char *buf, int buf_len,
char *var_name, int var_name_len,
char *file_name, int file_name_len,
const char **data, int *data_len) {
static const char cd[] = "Content-Disposition: ";
//struct mg_connection c;
int hl, bl, n, ll, pos, cdl = sizeof(cd) - 1;
//char *p;
if (buf == NULL || buf_len <= 0) return 0;
if ((hl = get_request_len(buf, buf_len)) <= 0) return 0;
if (buf[0] != '-' || buf[1] != '-' || buf[2] == '\n') return 0;
// Get boundary length
bl = get_line_len(buf, buf_len);
// Loop through headers, fetch variable name and file name
var_name[0] = file_name[0] = '\0';
for (n = bl; (ll = get_line_len(buf + n, hl - n)) > 0; n += ll) {
if (mg_strncasecmp(cd, buf + n, cdl) == 0) {
parse_header(buf + n + cdl, ll - (cdl + 2), "name",
var_name, var_name_len);
parse_header(buf + n + cdl, ll - (cdl + 2), "filename",
file_name, file_name_len);
}
}
// Scan body, search for terminating boundary
for (pos = hl; pos + (bl - 2) < buf_len; pos++) {
if (buf[pos] == '-' && !memcmp(buf, &buf[pos], bl - 2)) {
if (data_len != NULL) *data_len = (pos - 2) - hl;
if (data != NULL) *data = buf + hl;
return pos;
}
}
return 0;
}
const char **mg_get_valid_option_names(void) {
return static_config_options;
static int get_option_index(const char *name) {
for (i = 0; static_config_options[i * 2] != NULL; i++) {
if (strcmp(static_config_options[i * 2], name) == 0) {
return i;
}
return -1;
}
static void set_default_option_values(char **opts) {
const char *value, **all_opts = mg_get_valid_option_names();
int i;
for (i = 0; all_opts[i * 2] != NULL; i++) {
value = all_opts[i * 2 + 1];
if (opts[i] == NULL && value != NULL) {
opts[i] = mg_strdup(value);
const char *mg_set_option(struct mg_server *server, const char *name,
const char *value) {
int ind = get_option_index(name);
const char *error_msg = NULL;
Sergey Lyubka
committed
char **v = NULL;
Sergey Lyubka
committed
if (ind < 0) return "No such option";
v = &server->config_options[ind];
Sergey Lyubka
committed
// Return success immediately if setting to the same value
if ((*v == NULL && value == NULL) ||
(value != NULL && *v != NULL && !strcmp(value, *v))) {
return NULL;
}
if (*v != NULL) {
free(*v);
*v = NULL;
}
if (value == NULL || value[0] == '\0') return NULL;
Sergey Lyubka
committed
*v = mg_strdup(value);
DBG(("%s [%s]", name, *v));
if (ind == LISTENING_PORT) {
int port = ns_bind(&server->ns_server, value);
if (port < 0) {
error_msg = "Cannot bind to port";
} else {
if (!strcmp(value, "0")) {
char buf[10];
mg_snprintf(buf, sizeof(buf), "%d", port);
free(*v);
*v = mg_strdup(buf);
Sergey Lyubka
committed
}
Sergey Lyubka
committed
} else if (ind == RUN_AS_USER) {
struct passwd *pw;
if ((pw = getpwnam(value)) == NULL) {
error_msg = "Unknown user";
} else if (setgid(pw->pw_gid) != 0) {
error_msg = "setgid() failed";
} else if (setuid(pw->pw_uid) != 0) {
error_msg = "setuid() failed";
}
Sergey Lyubka
committed
} else if (ind == SSL_CERTIFICATE) {
int res = ns_set_ssl_cert(&server->ns_server, value);
if (res == -2) {
error_msg = "Cannot load PEM";
} else if (res == -3) {
error_msg = "SSL not enabled";
} else if (res == -1) {
error_msg = "SSL_CTX_new() failed";
} else if (ind == SSL_CA_CERTIFICATE) {
if (ns_set_ssl_ca_cert(&server->ns_server, value) != 0) {
error_msg = "Error setting CA cert";
}
Sergey Lyubka
committed
#endif
static void set_ips(struct connection *conn, int is_rem) {
struct mg_connection *c = &conn->mg_conn;
char buf[100];
ns_sock_to_str(conn->ns_conn->sock, buf, sizeof(buf), is_rem ? 7 : 3);
sscanf(buf, "%47[^:]:%hu",
is_rem ? c->remote_ip : c->local_ip,
is_rem ? &c->remote_port : &c->local_port);
}
static void on_accept(struct ns_connection *nc, union socket_address *sa) {
struct mg_server *server = (struct mg_server *) nc->server;
struct connection *conn;
if (!check_acl(server->config_options[ACCESS_CONTROL_LIST],
ntohl(* (uint32_t *) &sa->sin.sin_addr)) ||
(conn = (struct connection *) calloc(1, sizeof(*conn))) == NULL) {
nc->flags |= NSF_CLOSE_IMMEDIATELY;
} else {
// Circularly link two connection structures
nc->connection_data = conn;
conn->ns_conn = nc;
// Initialize the rest of connection attributes
conn->server = server;
conn->mg_conn.server_param = nc->server->server_data;
set_ips(conn, 1);
set_ips(conn, 0);
#ifndef MONGOOSE_NO_FILESYSTEM
static void hexdump(struct ns_connection *nc, const char *path,
int num_bytes, int is_sent) {
struct connection *mc = (struct connection *) nc->connection_data;
const struct iobuf *io = is_sent ? &nc->send_iobuf : &nc->recv_iobuf;
FILE *fp;
char *buf;
int buf_size = num_bytes * 5 + 100;
if (path != NULL && num_bytes > 0 && (fp = fopen(path, "a")) != NULL) {
fprintf(fp, "%lu %s:%d %s %s:%d %d\n", (unsigned long) time(NULL),
mc->mg_conn.local_ip, mc->mg_conn.local_port,
is_sent ? "->" : "<-",
mc->mg_conn.remote_ip, mc->mg_conn.remote_port,
num_bytes);
if ((buf = (char *) malloc(buf_size)) != NULL) {
ns_hexdump(io->buf + (is_sent ? 0 : io->len) - (is_sent ? 0 : num_bytes),
num_bytes, buf, buf_size);
fprintf(fp, "%s", buf);
free(buf);
}
fclose(fp);
}
}
static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) {
struct connection *conn = (struct connection *) nc->connection_data;
struct mg_server *server = (struct mg_server *) nc->server;
Sergey Lyubka
committed
// Send NS event to the handler. Note that call_user won't send an event
// if conn == NULL. Therefore, repeat this for NS_ACCEPT event as well.
#ifdef MONGOOSE_SEND_NS_EVENTS
{
struct connection *conn = (struct connection *) nc->connection_data;
if (conn != NULL) conn->mg_conn.callback_param = p;
call_user(conn, (enum mg_event) ev);
}
#endif
switch (ev) {
case NS_ACCEPT:
on_accept(nc, (union socket_address *) p);
Sergey Lyubka
committed
#ifdef MONGOOSE_SEND_NS_EVENTS
{
struct connection *conn = (struct connection *) nc->connection_data;
if (conn != NULL) conn->mg_conn.callback_param = p;
call_user(conn, (enum mg_event) ev);
}
#endif
conn->mg_conn.status_code = * (int *) p;
if (conn->mg_conn.status_code != 0 ||
call_user(conn, MG_CONNECT) == MG_FALSE) {
nc->flags |= NSF_CLOSE_IMMEDIATELY;
#ifndef MONGOOSE_NO_FILESYSTEM
hexdump(nc, server->config_options[HEXDUMP_FILE], * (int *) p, 0);
if (nc->flags & NSF_ACCEPTED) {
process_request(conn);
#ifndef MONGOOSE_NO_CGI
} else if (nc->flags & MG_CGI_CONN) {
on_cgi_data(nc);
} else {
process_response(conn);
}
break;
case NS_SEND:
#ifndef MONGOOSE_NO_FILESYSTEM
hexdump(nc, server->config_options[HEXDUMP_FILE], * (int *) p, 1);
break;
case NS_CLOSE:
nc->connection_data = NULL;
if ((nc->flags & MG_CGI_CONN) && conn && conn->ns_conn) {
conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND;
conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len > 0 ?
NSF_FINISHED_SENDING_DATA : NSF_CLOSE_IMMEDIATELY;
conn->endpoint.cgi_conn = NULL;
} else if (conn != NULL) {
DBG(("%p %d closing", conn, conn->endpoint_type));
if (conn->endpoint_type == EP_CLIENT && nc->recv_iobuf.len > 0) {
call_user(conn, MG_CLOSE);
close_local_endpoint(conn);
free(conn);
}
break;
case NS_POLL:
if (call_user(conn, MG_POLL) == MG_TRUE) {
nc->flags |= NSF_FINISHED_SENDING_DATA;
}
if (conn != NULL && conn->endpoint_type == EP_FILE) {
transfer_file_data(conn);
}
// Expire idle connections
{
time_t current_time = * (time_t *) p;
if (conn != NULL && conn->mg_conn.is_websocket) {
ping_idle_websocket_connection(conn, current_time);
}
if (nc->last_io_time + MONGOOSE_IDLE_TIMEOUT_SECONDS < current_time) {
mg_ev_handler(nc, NS_CLOSE, NULL);
nc->flags |= NSF_CLOSE_IMMEDIATELY;
}
}
break;
default:
break;
}
}
void mg_wakeup_server(struct mg_server *server) {
ns_server_wakeup(&server->ns_server);
}
Sergey Lyubka
committed
void mg_set_listening_socket(struct mg_server *server, int sock) {
if (server->ns_server.listening_sock != INVALID_SOCKET) {
closesocket(server->ns_server.listening_sock);
Sergey Lyubka
committed
}
server->ns_server.listening_sock = (sock_t) sock;
Sergey Lyubka
committed
}
int mg_get_listening_socket(struct mg_server *server) {
Sergey Lyubka
committed
}
const char *mg_get_option(const struct mg_server *server, const char *name) {
const char **opts = (const char **) server->config_options;
int i = get_option_index(name);
return i == -1 ? NULL : opts[i] == NULL ? "" : opts[i];
}
struct mg_server *mg_create_server(void *server_data, mg_handler_t handler) {
struct mg_server *server = (struct mg_server *) calloc(1, sizeof(*server));
ns_server_init(&server->ns_server, server_data, mg_ev_handler);
set_default_option_values(server->config_options);
server->event_handler = handler;