Newer
Older
static void bin2str(char *to, const unsigned char *p, size_t len) {
static const char *hex = "0123456789abcdef";
for (; len--; p++) {
*to++ = hex[p[0] >> 4];
*to++ = hex[p[0] & 0x0f];
// Return stringified MD5 hash for list of strings. Buffer must be 33 bytes.
char *mg_md5(char buf[33], ...) {
unsigned char hash[16];
const char *p;
va_list ap;
MD5_CTX ctx;
va_start(ap, buf);
while ((p = va_arg(ap, const char *)) != NULL) {
MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
MD5Final(hash, &ctx);
bin2str(buf, hash, sizeof(hash));
return buf;
// Check the user's password, return 1 if OK
static int check_password(const char *method, const char *ha1, const char *uri,
const char *nonce, const char *nc, const char *cnonce,
const char *qop, const char *response) {
char ha2[32 + 1], expected_response[32 + 1];
#if 0
// Check for authentication timeout
if ((unsigned long) time(NULL) - (unsigned long) to64(nonce) > 3600 * 2) {
mg_md5(ha2, method, ":", uri, NULL);
mg_md5(expected_response, ha1, ":", nonce, ":", nc,
":", cnonce, ":", qop, ":", ha2, NULL);
return mg_strcasecmp(response, expected_response) == 0 ? MG_TRUE : MG_FALSE;
// Authorize against the opened passwords file. Return 1 if authorized.
int mg_authorize_digest(struct mg_connection *c, FILE *fp) {
char line[256], f_user[256], ha1[256], f_domain[256], user[100], nonce[100],
uri[MAX_REQUEST_SIZE], cnonce[100], resp[100], qop[100], nc[100];
if (c == NULL || fp == NULL) return 0;
if ((hdr = mg_get_header(c, "Authorization")) == NULL ||
mg_strncasecmp(hdr, "Digest ", 7) != 0) return 0;
if (!mg_parse_header(hdr, "username", user, sizeof(user))) return 0;
if (!mg_parse_header(hdr, "cnonce", cnonce, sizeof(cnonce))) return 0;
if (!mg_parse_header(hdr, "response", resp, sizeof(resp))) return 0;
if (!mg_parse_header(hdr, "uri", uri, sizeof(uri))) return 0;
if (!mg_parse_header(hdr, "qop", qop, sizeof(qop))) return 0;
if (!mg_parse_header(hdr, "nc", nc, sizeof(nc))) return 0;
if (!mg_parse_header(hdr, "nonce", nonce, sizeof(nonce))) return 0;
while (fgets(line, sizeof(line), fp) != NULL) {
if (sscanf(line, "%[^:]:%[^:]:%s", f_user, f_domain, ha1) == 3 &&
!strcmp(user, f_user) &&
// NOTE(lsm): due to a bug in MSIE, we do not compare URIs
!strcmp(conn->server->config_options[AUTH_DOMAIN], f_domain))
return check_password(c->request_method, ha1, uri,
// Return 1 if request is authorised, 0 otherwise.
static int is_authorized(struct connection *conn, const char *path,
int authorized = MG_TRUE;
if ((fp = open_auth_file(conn, path, is_directory)) != NULL) {
authorized = mg_authorize_digest(&conn->mg_conn, fp);
static int is_authorized_for_dav(struct connection *conn) {
const char *auth_file = conn->server->config_options[DAV_AUTH_FILE];
const char *method = conn->mg_conn.request_method;
int authorized = MG_FALSE;
// If dav_auth_file is not set, allow non-authorized PROPFIND
if (method != NULL && !strcmp(method, "PROPFIND") && auth_file == NULL) {
authorized = MG_TRUE;
} else if (auth_file != NULL && (fp = fopen(auth_file, "r")) != NULL) {
authorized = mg_authorize_digest(&conn->mg_conn, fp);
static int is_dav_request(const struct connection *conn) {
const char *s = conn->mg_conn.request_method;
return !strcmp(s, "PUT") || !strcmp(s, "DELETE") ||
!strcmp(s, "MKCOL") || !strcmp(s, "PROPFIND");
#endif // MONGOOSE_NO_AUTH
static int parse_header(const char *str, int str_len, const char *var_name,
char *buf, size_t buf_size) {
const char *p, *end = str + str_len, *s = NULL;
if (buf != NULL && buf_size > 0) buf[0] = '\0';
for (s = str; s != NULL && s + n < end; s++) {
if ((s == str || s[-1] == ch || s[-1] == ch1) && s[n] == '=' &&
while (p < end && p[0] != ch && p[0] != ch1 && len < (int) buf_size) {
if (ch == ch1 && p[0] == '\\' && p[1] == ch) p++;
buf[len++] = *p++;
}
if (len >= (int) buf_size || (ch != ' ' && *p != ch)) {
len = 0;
} else {
if (len > 0 && s[len - 1] == ',') len--;
int mg_parse_header(const char *s, const char *var_name, char *buf,
return parse_header(s, s == NULL ? 0 : strlen(s), var_name, buf, buf_size);
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
#ifndef MONGOOSE_NO_SSI
static void send_ssi_file(struct mg_connection *, const char *, FILE *, int);
static void send_file_data(struct mg_connection *conn, FILE *fp) {
char buf[IOBUF_SIZE];
int n;
while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) {
mg_write(conn, buf, n);
}
}
static void do_ssi_include(struct mg_connection *conn, const char *ssi,
char *tag, int include_level) {
char file_name[IOBUF_SIZE], path[MAX_PATH_SIZE], *p;
char **opts = (MG_CONN_2_CONN(conn))->server->config_options;
FILE *fp;
// sscanf() is safe here, since send_ssi_file() also uses buffer
// of size MG_BUF_LEN to get the tag. So strlen(tag) is always < MG_BUF_LEN.
if (sscanf(tag, " virtual=\"%[^\"]\"", file_name) == 1) {
// File name is relative to the webserver root
mg_snprintf(path, sizeof(path), "%s%c%s",
opts[DOCUMENT_ROOT], '/', file_name);
} else if (sscanf(tag, " abspath=\"%[^\"]\"", file_name) == 1) {
// File name is relative to the webserver working directory
// or it is absolute system path
mg_snprintf(path, sizeof(path), "%s", file_name);
} else if (sscanf(tag, " file=\"%[^\"]\"", file_name) == 1 ||
sscanf(tag, " \"%[^\"]\"", file_name) == 1) {
// File name is relative to the currect document
mg_snprintf(path, sizeof(path), "%s", ssi);
if ((p = strrchr(path, '/')) != NULL) {
p[1] = '\0';
}
mg_snprintf(path + strlen(path), sizeof(path) - strlen(path), "%s",
file_name);
} else {
mg_printf(conn, "Bad SSI #include: [%s]", tag);
return;
}
if ((fp = fopen(path, "rb")) == NULL) {
mg_printf(conn, "Cannot open SSI #include: [%s]: fopen(%s): %s",
tag, path, strerror(errno));
} else {
ns_set_close_on_exec(fileno(fp));
if (mg_match_prefix(opts[SSI_PATTERN], strlen(opts[SSI_PATTERN]),
path) > 0) {
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
send_ssi_file(conn, path, fp, include_level + 1);
} else {
send_file_data(conn, fp);
}
fclose(fp);
}
}
#ifndef MONGOOSE_NO_POPEN
static void do_ssi_exec(struct mg_connection *conn, char *tag) {
char cmd[IOBUF_SIZE];
FILE *fp;
if (sscanf(tag, " \"%[^\"]\"", cmd) != 1) {
mg_printf(conn, "Bad SSI #exec: [%s]", tag);
} else if ((fp = popen(cmd, "r")) == NULL) {
mg_printf(conn, "Cannot SSI #exec: [%s]: %s", cmd, strerror(errno));
} else {
send_file_data(conn, fp);
pclose(fp);
}
}
#endif // !MONGOOSE_NO_POPEN
static void send_ssi_file(struct mg_connection *conn, const char *path,
FILE *fp, int include_level) {
char buf[IOBUF_SIZE];
int ch, offset, len, in_ssi_tag;
if (include_level > 10) {
mg_printf(conn, "SSI #include level is too deep (%s)", path);
return;
}
in_ssi_tag = len = offset = 0;
while ((ch = fgetc(fp)) != EOF) {
if (in_ssi_tag && ch == '>') {
in_ssi_tag = 0;
buf[len++] = (char) ch;
buf[len] = '\0';
assert(len <= (int) sizeof(buf));
if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
// Not an SSI tag, pass it
(void) mg_write(conn, buf, (size_t) len);
} else {
if (!memcmp(buf + 5, "include", 7)) {
do_ssi_include(conn, path, buf + 12, include_level);
#if !defined(MONGOOSE_NO_POPEN)
} else if (!memcmp(buf + 5, "exec", 4)) {
do_ssi_exec(conn, buf + 9);
#endif // !NO_POPEN
} else {
mg_printf(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);
}
}
len = 0;
} else if (in_ssi_tag) {
if (len == 5 && memcmp(buf, "<!--#", 5) != 0) {
// Not an SSI tag
in_ssi_tag = 0;
} else if (len == (int) sizeof(buf) - 2) {
mg_printf(conn, "%s: SSI tag is too large", path);
len = 0;
}
buf[len++] = ch & 0xff;
} else if (ch == '<') {
in_ssi_tag = 1;
if (len > 0) {
mg_write(conn, buf, (size_t) len);
}
len = 0;
buf[len++] = ch & 0xff;
} else {
buf[len++] = ch & 0xff;
if (len == (int) sizeof(buf)) {
mg_write(conn, buf, (size_t) len);
len = 0;
}
}
}
// Send the rest of buffered data
if (len > 0) {
mg_write(conn, buf, (size_t) len);
}
}
static void handle_ssi_request(struct connection *conn, const char *path) {
FILE *fp;
struct vec mime_vec;
if ((fp = fopen(path, "rb")) == NULL) {
send_http_error(conn, 500, "fopen(%s): %s", path, strerror(errno));
} else {
ns_set_close_on_exec(fileno(fp));
get_mime_type(conn->server, path, &mime_vec);
conn->mg_conn.status_code = 200;
mg_printf(&conn->mg_conn,
"HTTP/1.1 %d OK\r\n"
"Content-Type: %.*s\r\n"
"Connection: close\r\n\r\n",
conn->mg_conn.status_code, (int) mime_vec.len, mime_vec.ptr);
send_ssi_file(&conn->mg_conn, path, fp, 0);
fclose(fp);
close_local_endpoint(conn);
}
}
#endif
static void proxy_request(struct ns_connection *pc, struct mg_connection *c) {
int i, sent_close_header = 0;
ns_printf(pc, "%s %s%s%s HTTP/%s\r\n", c->request_method, c->uri,
c->query_string ? "?" : "",
c->query_string ? c->query_string : "",
c->http_version);
for (i = 0; i < c->num_headers; i++) {
if (mg_strcasecmp(c->http_headers[i].name, "Connection") == 0) {
// Force connection close, cause we don't parse proxy replies
// therefore we don't know message boundaries
ns_printf(pc, "%s: %s\r\n", "Connection", "close");
ns_printf(pc, "%s: %s\r\n", c->http_headers[i].name,
c->http_headers[i].value);
}
if (!sent_close_header) {
ns_printf(pc, "%s: %s\r\n", "Connection", "close");
}
ns_printf(pc, "%s", "\r\n");
ns_send(pc, c->content, c->content_len);
}
Sergey Lyubka
committed
#ifdef NS_ENABLE_SSL
int mg_terminate_ssl(struct mg_connection *c, const char *cert) {
static const char ok[] = "HTTP/1.0 200 OK\r\n\r\n";
struct connection *conn = MG_CONN_2_CONN(c);
SSL_CTX *ctx;
DBG(("%p MITM", conn));
if ((ctx = SSL_CTX_new(SSLv23_server_method())) == NULL) return 0;
SSL_CTX_use_certificate_file(ctx, cert, 1);
SSL_CTX_use_PrivateKey_file(ctx, cert, 1);
SSL_CTX_use_certificate_chain_file(ctx, cert);
// When clear-text reply is pushed to client, switch to SSL mode.
// TODO(lsm): check for send() failure
send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0);
//DBG(("%p %lu %d SEND", c, (unsigned long) sizeof(ok) - 1, n));
Sergey Lyubka
committed
conn->ns_conn->send_iobuf.len = 0;
conn->endpoint_type = EP_USER; // To keep-alive in close_local_endpoint()
close_local_endpoint(conn); // Clean up current CONNECT request
if ((conn->ns_conn->ssl = SSL_new(ctx)) != NULL) {
SSL_set_fd(conn->ns_conn->ssl, conn->ns_conn->sock);
}
SSL_CTX_free(ctx);
return 1;
}
#endif
int mg_forward(struct mg_connection *c, const char *addr) {
static const char ok[] = "HTTP/1.1 200 OK\r\n\r\n";
struct connection *conn = MG_CONN_2_CONN(c);
struct ns_connection *pc;
if ((pc = ns_connect(&conn->server->ns_mgr, addr,
mg_ev_handler, conn)) == NULL) {
conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
return 0;
}
// Interlink two connections
pc->flags |= MG_PROXY_CONN;
conn->endpoint_type = EP_PROXY;
conn->endpoint.nc = pc;
DBG(("%p [%s] [%s] -> %p %p", conn, c->uri, addr, pc, conn->ns_conn->ssl));
if (strcmp(c->request_method, "CONNECT") == 0) {
// For CONNECT request, reply with 200 OK. Tunnel is established.
// TODO(lsm): check for send() failure
(void) send(conn->ns_conn->sock, ok, sizeof(ok) - 1, 0);
} else {
// Strip "http://host:port" part from the URI
if (memcmp(c->uri, "http://", 7) == 0) c->uri += 7;
while (*c->uri != '\0' && *c->uri != '/') c->uri++;
proxy_request(pc, c);
}
return 1;
}
static void proxify_connection(struct connection *conn) {
char proto[10], host[500], cert[500], addr[1000];
Sergey Lyubka
committed
int n = 0;
proto[0] = host[0] = cert[0] = '\0';
if (sscanf(url, "%499[^: ]:%hu%n", host, &port, &n) != 2 &&
sscanf(url, "%9[a-z]://%499[^: ]:%hu%n", proto, host, &port, &n) != 3 &&
sscanf(url, "%9[a-z]://%499[^/ ]%n", proto, host, &n) != 2) {
n = 0;
}
snprintf(addr, sizeof(addr), "%s://%s:%hu",
conn->ns_conn->ssl != NULL ? "ssl" : "tcp", host, port);
if (n <= 0 || !mg_forward(c, addr)) {
conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY;
}
}
#ifndef MONGOOSE_NO_FILESYSTEM
void mg_send_file_internal(struct mg_connection *c, const char *file_name,
file_stat_t *st, int exists,
const char *extra_headers) {
struct connection *conn = MG_CONN_2_CONN(c);
const int is_directory = S_ISDIR(st->st_mode);
#ifndef MONGOOSE_NO_CGI
const char *cgi_pat = conn->server->config_options[CGI_PATTERN];
#else
const char *cgi_pat = DEFAULT_CGI_PATTERN;
#endif
#ifndef MONGOOSE_NO_DIRECTORY_LISTING
const char *dir_lst = conn->server->config_options[ENABLE_DIRECTORY_LISTING];
#else
const char *dir_lst = "yes";
#endif
mg_snprintf(path, sizeof(path), "%s", file_name);
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);
#endif
} else {
send_http_error(conn, 403, NULL);
}
} else if (mg_match_prefix(cgi_pat, strlen(cgi_pat), path) > 0) {
#if !defined(MONGOOSE_NO_CGI)
open_cgi_endpoint(conn, path);
#else
send_http_error(conn, 501, NULL);
#endif // !MONGOOSE_NO_CGI
#ifndef MONGOOSE_NO_SSI
} else if (mg_match_prefix(conn->server->config_options[SSI_PATTERN],
strlen(conn->server->config_options[SSI_PATTERN]),
path) > 0) {
handle_ssi_request(conn, path);
send_http_error(conn, 304, NULL);
} else if ((conn->endpoint.fd = open(path, O_RDONLY | O_BINARY, 0)) != -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, extra_headers);
} else {
send_http_error(conn, 404, NULL);
}
}
void mg_send_file(struct mg_connection *c, const char *file_name,
const char *extra_headers) {
const int exists = stat(file_name, &st) == 0;
mg_send_file_internal(c, file_name, &st, exists, extra_headers);
#endif // !MONGOOSE_NO_FILESYSTEM
static void open_local_endpoint(struct connection *conn, int skip_user) {
#ifndef MONGOOSE_NO_FILESYSTEM
char path[MAX_PATH_SIZE];
file_stat_t st;
int exists = 0;
// If EP_USER was set in a prev call, reset it
conn->endpoint_type = EP_NONE;
#ifndef MONGOOSE_NO_AUTH
if (conn->server->event_handler && call_user(conn, MG_AUTH) == MG_FALSE) {
mg_send_digest_auth_request(&conn->mg_conn);
return;
}
// Call URI handler if one is registered for this URI
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 > %lu",
(unsigned long) MONGOOSE_POST_SIZE_LIMIT);
if (strcmp(conn->mg_conn.request_method, "CONNECT") == 0 ||
mg_strncasecmp(conn->mg_conn.uri, "http", 4) == 0) {
const char *enp = conn->server->config_options[ENABLE_PROXY];
if (enp == NULL || strcmp(enp, "yes") != 0) {
send_http_error(conn, 405, NULL);
} else {
proxify_connection(conn);
}
if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) {
send_options(conn);
return;
#ifdef MONGOOSE_NO_FILESYSTEM
send_http_error(conn, 404, NULL);
exists = convert_uri_to_file_name(conn, path, sizeof(path), &st);
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);
Alex Skalozub
committed
} else if (must_hide_file(conn, path)) {
send_http_error(conn, 404, NULL);
} 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);
mg_send_file_internal(&conn->mg_conn, path, &st, exists, 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);
// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
static int is_valid_uri(const char *uri) {
unsigned short n;
return uri[0] == '/' ||
strcmp(uri, "*") == 0 || // OPTIONS method can use asterisk URI
mg_strncasecmp(uri, "http", 4) == 0 || // Naive check for the absolute URI
sscanf(uri, "%*[^ :]:%hu", &n) > 0; // CONNECT method can use host:port
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 *) NS_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 do_proxy(struct connection *conn) {
DBG(("%p parsing -> %d", conn, conn->request_len));
if (conn->request_len > 0 && call_user(conn, MG_REQUEST) == MG_FALSE) {
proxy_request(conn->endpoint.nc, &conn->mg_conn);
} else if (conn->request_len < 0) {
ns_forward(conn->ns_conn, conn->endpoint.nc);
}
} else {
DBG(("%p forwarding", conn));
ns_forward(conn->ns_conn, conn->endpoint.nc);
}
static void on_recv_data(struct connection *conn) {
if (conn->endpoint_type == EP_PROXY) {
if (conn->endpoint.nc != NULL) do_proxy(conn);
DBG(("%p %d %lu %d", conn, conn->request_len, (unsigned long)io->len,
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 && conn->endpoint.nc != NULL) {
ns_forward(conn->ns_conn, conn->endpoint.nc);
}
#endif
if (conn->endpoint_type == EP_USER) {
conn->mg_conn.content = io->buf;
conn->mg_conn.content_len = io->len;
n = call_user(conn, MG_RECV);
if (n < 0) {
conn->ns_conn->flags |= NSF_FINISHED_SENDING_DATA;
} else if ((size_t) n <= io->len) {
iobuf_remove(io, n);
}
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_recv = conn->request_len = 0;
NS_FREE(conn->request);
conn->request = NULL;
}
static void process_response(struct connection *conn) {
DBG(("%p %d %lu", conn, conn->request_len, (unsigned long)io->len));
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 *addr) {
struct ns_connection *nsconn;
struct connection *conn;
nsconn = ns_connect(&server->ns_mgr, addr, mg_ev_handler, NULL);
if ((conn = (struct connection *) NS_CALLOC(1, sizeof(*conn))) == NULL) {
// Interlink two structs
conn->ns_conn = nsconn;
//conn->handler = handler;
conn->mg_conn.server_param = server->ns_mgr.user_data;
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 0",
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);
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:
case EP_PROXY:
if (conn->endpoint.nc != NULL) {
DBG(("%p %p %p :-)", conn, conn->ns_conn, conn->endpoint.nc));
conn->endpoint.nc->flags |= NSF_CLOSE_IMMEDIATELY;
#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_free(&conn->ns_conn->recv_iobuf);
NS_FREE(conn->request);
NS_FREE(conn->path_info);
conn->endpoint.nc = NULL;
conn->request = conn->path_info = NULL;
conn->cl = conn->num_bytes_recv = 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);
// Do not memset() the whole structure, as some of the fields
// (IP addresses & ports, server_param) must survive. Nullify the rest.
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;
c->connection_param = c->callback_param = NULL;
on_recv_data(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;
// If output buffer is too big, don't send anything. Wait until
// mongoose drains already buffered data to the client.
if (conn->ns_conn->send_iobuf.len > sizeof(buf) * 2) return;
// Do not send anyt
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_mgr_poll(&server->ns_mgr, milliseconds);
void mg_destroy_server(struct mg_server **server) {
if (server != NULL && *server != NULL) {
ns_mgr_free(&s->ns_mgr);
for (i = 0; i < (int) ARRAY_SIZE(s->config_options); i++) {
NS_FREE(s->config_options[i]); // It is OK to free(NULL)
struct mg_connection *mg_next(struct mg_server *s, struct mg_connection *c) {
Sergey Lyubka
committed
struct ns_connection *nc = ns_next(&s->ns_mgr, c == NULL ? NULL :
MG_CONN_2_CONN(c)->ns_conn);
if (nc != NULL && nc->user_data != NULL) {
return & ((struct connection *) nc->user_data)->mg_conn;
} else {
return NULL;
}
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);
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
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;
Sergey Lyubka
committed
void mg_copy_listeners(struct mg_server *s, struct mg_server *to) {
struct ns_connection *c;
for (c = ns_next(&s->ns_mgr, NULL); c != NULL; c = ns_next(&s->ns_mgr, c)) {
struct ns_connection *tmp;
(tmp = (struct ns_connection *) NS_MALLOC(sizeof(*tmp))) != NULL) {
Sergey Lyubka
committed
memcpy(tmp, c, sizeof(*tmp));
tmp->mgr = &to->ns_mgr;
ns_add_conn(tmp->mgr, tmp);
}
}