Commit 75d9a6c8 authored by Sergey Lyubka's avatar Sergey Lyubka

Removed mg_connect() and mg_fetch(). Added mg_download()

parent 179761fd
...@@ -1627,16 +1627,13 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) { ...@@ -1627,16 +1627,13 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
return (int) total; return (int) total;
} }
int mg_printf(struct mg_connection *conn, const char *fmt, ...) { int mg_vprintf(struct mg_connection *conn, const char *fmt, va_list ap) {
char mem[MG_BUF_LEN], *buf = mem; char mem[MG_BUF_LEN], *buf = mem;
int len; int len;
va_list ap;
// Print in a local buffer first, hoping that it is large enough to // Print in a local buffer first, hoping that it is large enough to
// hold the whole message // hold the whole message
va_start(ap, fmt);
len = vsnprintf(mem, sizeof(mem), fmt, ap); len = vsnprintf(mem, sizeof(mem), fmt, ap);
va_end(ap);
if (len == 0) { if (len == 0) {
// Do nothing. mg_printf(conn, "%s", "") was called. // Do nothing. mg_printf(conn, "%s", "") was called.
...@@ -1647,9 +1644,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) { ...@@ -1647,9 +1644,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
} else if (len > (int) sizeof(mem) && } else if (len > (int) sizeof(mem) &&
(buf = (char *) malloc(len + 1)) != NULL) { (buf = (char *) malloc(len + 1)) != NULL) {
// Local buffer is not large enough, allocate big buffer on heap // Local buffer is not large enough, allocate big buffer on heap
va_start(ap, fmt);
vsnprintf(buf, len + 1, fmt, ap); vsnprintf(buf, len + 1, fmt, ap);
va_end(ap);
len = mg_write(conn, buf, (size_t) len); len = mg_write(conn, buf, (size_t) len);
free(buf); free(buf);
} else if (len > (int) sizeof(mem)) { } else if (len > (int) sizeof(mem)) {
...@@ -1665,6 +1660,12 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) { ...@@ -1665,6 +1660,12 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
return len; return len;
} }
int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
return mg_vprintf(conn, fmt, ap);
}
// URL-decode input buffer into destination buffer. // URL-decode input buffer into destination buffer.
// 0-terminate the destination buffer. Return the length of decoded data. // 0-terminate the destination buffer. Return the length of decoded data.
// form-url-encoded data differs from URI encoding in a way that it // form-url-encoded data differs from URI encoding in a way that it
...@@ -2811,7 +2812,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path, ...@@ -2811,7 +2812,7 @@ static void handle_file_request(struct mg_connection *conn, const char *path,
if (!mg_fopen(conn, path, "rb", filep)) { if (!mg_fopen(conn, path, "rb", filep)) {
send_http_error(conn, 500, http_500_error, send_http_error(conn, 500, http_500_error,
"fopen(%s): %s", path, strerror(ERRNO)); "fopen(%s): %s", path, strerror(ERRNO));
return; return;
} }
fclose_on_exec(filep); fclose_on_exec(filep);
...@@ -2891,7 +2892,7 @@ static int is_valid_http_method(const char *method) { ...@@ -2891,7 +2892,7 @@ static int is_valid_http_method(const char *method) {
// This function modifies the buffer by NUL-terminating // This function modifies the buffer by NUL-terminating
// HTTP request components, header names and header values. // HTTP request components, header names and header values.
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) { static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
int request_length = get_request_len(buf, len); int is_request, request_length = get_request_len(buf, len);
if (request_length > 0) { if (request_length > 0) {
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port // Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL; ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
...@@ -2906,28 +2907,20 @@ static int parse_http_message(char *buf, int len, struct mg_request_info *ri) { ...@@ -2906,28 +2907,20 @@ static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
ri->request_method = skip(&buf, " "); ri->request_method = skip(&buf, " ");
ri->uri = skip(&buf, " "); ri->uri = skip(&buf, " ");
ri->http_version = skip(&buf, "\r\n"); ri->http_version = skip(&buf, "\r\n");
parse_http_headers(&buf, ri); if (((is_request = is_valid_http_method(ri->request_method)) &&
memcmp(ri->http_version, "HTTP/", 5) != 0) ||
(!is_request && memcmp(ri->request_method, "HTTP/", 5)) != 0) {
request_length = -1;
} else {
if (is_request) {
ri->http_version += 5;
}
parse_http_headers(&buf, ri);
}
} }
return request_length; return request_length;
} }
static int parse_http_request(char *buf, int len, struct mg_request_info *ri) {
int result = parse_http_message(buf, len, ri);
if (result > 0 &&
is_valid_http_method(ri->request_method) &&
!strncmp(ri->http_version, "HTTP/", 5)) {
ri->http_version += 5; // Skip "HTTP/"
} else {
result = -1;
}
return result;
}
static int parse_http_response(char *buf, int len, struct mg_request_info *ri) {
int result = parse_http_message(buf, len, ri);
return result > 0 && !strncmp(ri->request_method, "HTTP/", 5) ? result : -1;
}
// Keep reading the input (either opened file descriptor fd, or socket sock, // Keep reading the input (either opened file descriptor fd, or socket sock,
// or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the // or SSL descriptor ssl) into buffer buf, until \r\n\r\n appears in the
// buffer (which marks the end of HTTP request). Buffer buf may already // buffer (which marks the end of HTTP request). Buffer buf may already
...@@ -4734,74 +4727,109 @@ void mg_close_connection(struct mg_connection *conn) { ...@@ -4734,74 +4727,109 @@ void mg_close_connection(struct mg_connection *conn) {
free(conn); free(conn);
} }
struct mg_connection *mg_connect(struct mg_context *ctx, struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
const char *host, int port, int use_ssl) { char *ebuf, size_t ebuf_len) {
struct mg_connection *newconn = NULL; static struct mg_context fake_ctx;
struct mg_connection *conn = NULL;
struct sockaddr_in sin; struct sockaddr_in sin;
struct hostent *he; struct hostent *he;
SSL_CTX *ssl = NULL;
int sock; int sock;
if (use_ssl && (ctx == NULL || ctx->client_ssl_ctx == NULL)) { if (host == NULL) {
cry(fc(ctx), "%s: SSL is not initialized", __func__); snprintf(ebuf, ebuf_len, "%s", "NULL host");
} else if (use_ssl && SSLv23_client_method == NULL) {
snprintf(ebuf, ebuf_len, "%s", "SSL is not initialized");
#ifndef NO_SSL
} else if (use_ssl && (ssl = SSL_CTX_new(SSLv23_client_method())) == NULL) {
snprintf(ebuf, ebuf_len, "SSL_CTX_new: %s", ssl_error());
#endif // NO_SSL
} else if ((he = gethostbyname(host)) == NULL) { } else if ((he = gethostbyname(host)) == NULL) {
cry(fc(ctx), "%s: gethostbyname(%s): %s", __func__, host, strerror(ERRNO)); snprintf(ebuf, ebuf_len, "gethostbyname(%s): %s", host, strerror(ERRNO));
} else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { } else if ((sock = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
cry(fc(ctx), "%s: socket: %s", __func__, strerror(ERRNO)); snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO));
} else { } else {
sin.sin_family = AF_INET; sin.sin_family = AF_INET;
sin.sin_port = htons((uint16_t) port); sin.sin_port = htons((uint16_t) port);
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0]; sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) { if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
cry(fc(ctx), "%s: connect(%s:%d): %s", __func__, host, port, snprintf(ebuf, ebuf_len, "connect(%s:%d): %s",
strerror(ERRNO)); host, port, strerror(ERRNO));
closesocket(sock); closesocket(sock);
} else if ((newconn = (struct mg_connection *) } else if ((conn = (struct mg_connection *)
calloc(1, sizeof(*newconn))) == NULL) { calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
cry(fc(ctx), "%s: calloc: %s", __func__, strerror(ERRNO)); snprintf(ebuf, ebuf_len, "calloc(): %s", strerror(ERRNO));
closesocket(sock); closesocket(sock);
} else { } else {
newconn->ctx = ctx; conn->buf_size = MAX_REQUEST_SIZE;
newconn->client.sock = sock; conn->buf = (char *) (conn + 1);
newconn->client.rsa.sin = sin; conn->ctx = &fake_ctx;
newconn->client.is_ssl = use_ssl; conn->client.sock = sock;
conn->client.rsa.sin = sin;
conn->client.is_ssl = use_ssl;
if (use_ssl) { if (use_ssl) {
sslize(newconn, ctx->client_ssl_ctx, SSL_connect); sslize(conn, ssl, SSL_connect);
} }
} }
} }
if (ssl != NULL) {
SSL_CTX_free(ssl);
}
return newconn; return conn;
} }
FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path, static int is_valid_uri(const char *uri) {
char *buf, size_t buf_len, struct mg_request_info *ri) { // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
struct mg_connection *newconn; // URI can be an asterisk (*) or should start with slash.
int n, req_length, data_length, port; return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0');
char host[1025], proto[10], buf2[MG_BUF_LEN]; }
FILE *fp = NULL;
if (sscanf(url, "%9[htps]://%1024[^:]:%d/%n", proto, host, &port, &n) == 3) { static int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len) {
} else if (sscanf(url, "%9[htps]://%1024[^/]/%n", proto, host, &n) == 2) { const char *cl;
port = mg_strcasecmp(proto, "https") == 0 ? 443 : 80;
ebuf[0] = '\0';
reset_per_request_attributes(conn);
conn->request_len = read_request(NULL, conn, conn->buf, conn->buf_size,
&conn->data_len);
assert(conn->request_len < 0 || conn->data_len >= conn->request_len);
if (conn->request_len == 0 && conn->data_len == conn->buf_size) {
snprintf(ebuf, ebuf_len, "%s", "Request Too Large");
} if (conn->request_len <= 0) {
snprintf(ebuf, ebuf_len, "%s", "Client closed connection");
} else if (parse_http_message(conn->buf, conn->buf_size,
&conn->request_info) <= 0) {
snprintf(ebuf, ebuf_len, "Bad request: [%.*s]", conn->data_len, conn->buf);
} else { } else {
cry(fc(ctx), "%s: invalid URL: [%s]", __func__, url); // Request is valid
return NULL; if ((cl = get_header(&conn->request_info, "Content-Length")) != NULL) {
conn->content_len = strtoll(cl, NULL, 10);
} else if (!mg_strcasecmp(conn->request_info.request_method, "POST") ||
!mg_strcasecmp(conn->request_info.request_method, "PUT")) {
conn->content_len = -1;
} else {
conn->content_len = 0;
}
conn->birth_time = time(NULL);
} }
return ebuf[0] == '\0';
}
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
char *ebuf, size_t ebuf_len,
const char *fmt, ...) {
struct mg_connection *conn;
va_list ap;
if ((newconn = mg_connect(ctx, host, port, va_start(ap, fmt);
!strcmp(proto, "https"))) == NULL) { ebuf[0] = '\0';
cry(fc(ctx), "%s: mg_connect(%s): %s", __func__, url, strerror(ERRNO)); if ((conn = mg_connect(host, port, use_ssl, ebuf, ebuf_len)) == NULL) {
} else if (mg_vprintf(conn, fmt, ap) <= 0) {
snprintf(ebuf, ebuf_len, "%s", "Error sending request");
} else if (!getreq(conn, ebuf, ebuf_len)) {
} else { } else {
mg_printf(newconn, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", url + n, host); #if 0
data_length = 0;
req_length = read_request(NULL, newconn, buf, buf_len, &data_length);
if (req_length <= 0) {
cry(fc(ctx), "%s(%s): invalid HTTP reply", __func__, url);
} else if (parse_http_response(buf, req_length, ri) <= 0) {
cry(fc(ctx), "%s(%s): cannot parse HTTP headers", __func__, url);
} else if ((fp = fopen(path, "w+b")) == NULL) {
cry(fc(ctx), "%s: fopen(%s): %s", __func__, path, strerror(ERRNO));
} else {
// Write chunk of data that may be in the user's buffer // Write chunk of data that may be in the user's buffer
data_length -= req_length; data_length -= req_length;
if (data_length > 0 && if (data_length > 0 &&
...@@ -4811,8 +4839,8 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path, ...@@ -4811,8 +4839,8 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
fp = NULL; fp = NULL;
} }
// Read the rest of the response and write it to the file. Do not use // Read the rest of the response and write it to the file. Do not use
// mg_read() cause we didn't set newconn->content_len properly. // mg_read() cause we didn't set conn->content_len properly.
while (fp && (data_length = pull(0, newconn, buf2, sizeof(buf2))) > 0) { while (fp && (data_length = pull(0, conn, buf2, sizeof(buf2))) > 0) {
if (fwrite(buf2, 1, data_length, fp) != (size_t) data_length) { if (fwrite(buf2, 1, data_length, fp) != (size_t) data_length) {
cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO)); cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
fclose(fp); fclose(fp);
...@@ -4820,23 +4848,20 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path, ...@@ -4820,23 +4848,20 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
break; break;
} }
} }
} #endif
mg_close_connection(newconn); }
if (ebuf[0] != '\0' && conn != NULL) {
mg_close_connection(conn);
conn = NULL;
} }
return fp; return conn;
}
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 process_new_connection(struct mg_connection *conn) { static void process_new_connection(struct mg_connection *conn) {
struct mg_request_info *ri = &conn->request_info; struct mg_request_info *ri = &conn->request_info;
int keep_alive_enabled, keep_alive, discard_len; int keep_alive_enabled, keep_alive, discard_len;
const char *cl; char ebuf[100];
keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes"); keep_alive_enabled = !strcmp(conn->ctx->config[ENABLE_KEEP_ALIVE], "yes");
keep_alive = 0; keep_alive = 0;
...@@ -4845,38 +4870,18 @@ static void process_new_connection(struct mg_connection *conn) { ...@@ -4845,38 +4870,18 @@ static void process_new_connection(struct mg_connection *conn) {
// to crule42. // to crule42.
conn->data_len = 0; conn->data_len = 0;
do { do {
reset_per_request_attributes(conn); if (!getreq(conn, ebuf, sizeof(ebuf))) {
conn->request_len = read_request(NULL, conn, conn->buf, conn->buf_size, send_http_error(conn, 500, "Server Error", "%s", ebuf);
&conn->data_len); } else if (!is_valid_uri(conn->request_info.uri)) {
assert(conn->request_len < 0 || conn->data_len >= conn->request_len); snprintf(ebuf, sizeof(ebuf), "Invalid URI: [%s]", ri->uri);
if (conn->request_len == 0 && conn->data_len == conn->buf_size) { send_http_error(conn, 400, "Bad Request", "%s", ebuf);
send_http_error(conn, 413, "Request Too Large", "%s", "");
return;
} if (conn->request_len <= 0) {
return; // Remote end closed the connection
}
if (parse_http_request(conn->buf, conn->buf_size, ri) <= 0 ||
!is_valid_uri(ri->uri)) {
// Do not put garbage in the access log, just send it back to the client
send_http_error(conn, 400, "Bad Request",
"Cannot parse HTTP request: [%.*s]", conn->data_len, conn->buf);
conn->must_close = 1;
} else if (strcmp(ri->http_version, "1.0") && } else if (strcmp(ri->http_version, "1.0") &&
strcmp(ri->http_version, "1.1")) { strcmp(ri->http_version, "1.1")) {
// Request seems valid, but HTTP version is strange snprintf(ebuf, sizeof(ebuf), "Bad HTTP version: [%s]", ri->http_version);
send_http_error(conn, 505, "HTTP version not supported", "%s", ""); send_http_error(conn, 505, "Bad HTTP version", "%s", ebuf);
log_access(conn); }
} else {
// Request is valid, handle it if (ebuf[0] == '\0') {
if ((cl = get_header(ri, "Content-Length")) != NULL) {
conn->content_len = strtoll(cl, NULL, 10);
} else if (!mg_strcasecmp(ri->request_method, "POST") ||
!mg_strcasecmp(ri->request_method, "PUT")) {
conn->content_len = -1;
} else {
conn->content_len = 0;
}
conn->birth_time = time(NULL);
handle_request(conn); handle_request(conn);
conn->request_info.ev_data = (void *) (long) conn->status_code; conn->request_info.ev_data = (void *) (long) conn->status_code;
call_user(conn, MG_REQUEST_COMPLETE); call_user(conn, MG_REQUEST_COMPLETE);
......
...@@ -236,12 +236,6 @@ struct mg_request_info *mg_get_request_info(struct mg_connection *); ...@@ -236,12 +236,6 @@ struct mg_request_info *mg_get_request_info(struct mg_connection *);
int mg_write(struct mg_connection *, const void *buf, size_t len); int mg_write(struct mg_connection *, const void *buf, size_t len);
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Below are the macros for enabling compiler-specific checks for
// printf-like arguments.
#undef PRINTF_FORMAT_STRING #undef PRINTF_FORMAT_STRING
#if _MSC_VER >= 1400 #if _MSC_VER >= 1400
#include <sal.h> #include <sal.h>
...@@ -260,6 +254,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len); ...@@ -260,6 +254,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len);
#define PRINTF_ARGS(x, y) #define PRINTF_ARGS(x, y)
#endif #endif
// Send data to the browser using printf() semantics.
//
// Works exactly like mg_write(), but allows to do message formatting.
// Below are the macros for enabling compiler-specific checks for
// printf-like arguments.
int mg_printf(struct mg_connection *, int mg_printf(struct mg_connection *,
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3); PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
...@@ -316,32 +315,29 @@ int mg_get_cookie(const struct mg_connection *, ...@@ -316,32 +315,29 @@ 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);
// Connect to the remote web server. // Download data from the remote web server.
// host: host name to connect to, e.g. "foo.com", or "10.12.40.1".
// port: port number, e.g. 80.
// use_ssl: wether to use SSL connection.
// error_buffer, error_buffer_size: error message placeholder.
// request_fmt,...: HTTP request.
// Return: // Return:
// On success, valid pointer to the new connection // On success, valid pointer to the new connection, suitable for mg_read().
// On error, NULL // On error, NULL.
struct mg_connection *mg_connect(struct mg_context *ctx, // Example:
const char *host, int port, int use_ssl); // char ebuf[100];
// struct mg_connection *conn;
// conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf),
// "%s", "GET / HTTP/1.0\r\n\r\nHost: google.com\r\n\r\n");
struct mg_connection *mg_download(const char *host, int port, int use_ssl,
char *error_buffer, size_t error_buffer_size,
const char *request_fmt, ...);
// Close the connection opened by mg_connect(). // Close the connection opened by mg_download().
void mg_close_connection(struct mg_connection *conn); void mg_close_connection(struct mg_connection *conn);
// Download given URL to a given file.
// url: URL to download
// path: file name where to save the data
// request_info: pointer to a structure that will hold parsed reply headers
// buf, bul_len: a buffer for the reply headers
// Return:
// On error, NULL
// On success, opened file stream to the downloaded contents. The stream
// is positioned to the end of the file. It is the user's responsibility
// to fclose() the opened file stream.
FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
char *buf, size_t buf_len, struct mg_request_info *request_info);
// File upload functionality. Each uploaded file gets saved into a temporary // File upload functionality. Each uploaded file gets saved into a temporary
// file and MG_UPLOAD event is sent. // file and MG_UPLOAD event is sent.
// Return number of uploaded files. // Return number of uploaded files.
......
...@@ -167,7 +167,7 @@ kill_spawned_child(); ...@@ -167,7 +167,7 @@ kill_spawned_child();
# Spawn the server on port $port # Spawn the server on port $port
my $cmd = "$exe ". my $cmd = "$exe ".
"-listening_ports $port ". "-listening_ports 127.0.0.1:$port ".
"-access_log_file access.log ". "-access_log_file access.log ".
"-error_log_file debug.log ". "-error_log_file debug.log ".
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " . "-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
...@@ -220,11 +220,10 @@ write_file("$root/a+.txt", ''); ...@@ -220,11 +220,10 @@ write_file("$root/a+.txt", '');
o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI'); o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');
# Test HTTP version parsing # Test HTTP version parsing
o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0); o("GET / HTTPX/1.0\r\n\r\n", '^HTTP/1.1 500', 'Bad HTTP Version', 0);
o("GET / HTTP/x.1\r\n\r\n", '505 HTTP', 'Bad HTTP maj Version'); o("GET / HTTP/x.1\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP maj Version', 0);
o("GET / HTTP/1.1z\r\n\r\n", '505 HTTP', 'Bad HTTP min Version'); o("GET / HTTP/1.1z\r\n\r\n", '^HTTP/1.1 505', 'Bad HTTP min Version', 0);
o("GET / HTTP/02.0\r\n\r\n", '505 HTTP version not supported', o("GET / HTTP/02.0\r\n\r\n", '^HTTP/1.1 505', 'HTTP Version >1.1', 0);
'HTTP Version >1.1');
# File with leading single dot # File with leading single dot
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1'); o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
...@@ -463,7 +462,7 @@ sub do_unit_test { ...@@ -463,7 +462,7 @@ sub do_unit_test {
sub do_embedded_test { sub do_embedded_test {
my $cmd = "cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. ". my $cmd = "cc -W -Wall -o $embed_exe $root/embed.c mongoose.c -I. ".
"-pthread -DNO_SSL -DLISTENING_PORT=\\\"$port\\\""; "-pthread -DNO_SSL -DLISTENING_PORT=\\\"127.0.0.1:$port\\\"";
if (on_windows()) { if (on_windows()) {
$cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ". $cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ".
"/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib "; "/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
...@@ -514,7 +513,7 @@ sub do_embedded_test { ...@@ -514,7 +513,7 @@ sub do_embedded_test {
'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: \[500\]', '* error handler', 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",
......
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
// Unit test for the mongoose web server. Tests embedded API. // Unit test for the mongoose web server. Tests embedded API.
#define USE_WEBSOCKET #define USE_WEBSOCKET
#define USE_LUA
#include "mongoose.c" #include "mongoose.c"
#define FATAL(str, line) do { \ #define FATAL(str, line) do { \
...@@ -29,24 +30,35 @@ ...@@ -29,24 +30,35 @@
} while (0) } while (0)
#define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } while (0) #define ASSERT(expr) do { if (!(expr)) FATAL(#expr, __LINE__); } while (0)
#define LISTENING_ADDR "127.0.0.1:56789" #define HTTP_PORT "56789"
#define HTTPS_PORT "56790"
#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT "r,127.0.0.1:" HTTPS_PORT "s"
static void test_parse_http_request() { static void test_parse_http_message() {
struct mg_request_info ri; struct mg_request_info ri;
char req1[] = "GET / HTTP/1.1\r\n\r\n"; char req1[] = "GET / HTTP/1.1\r\n\r\n";
char req2[] = "BLAH / HTTP/1.1\r\n\r\n"; char req2[] = "BLAH / HTTP/1.1\r\n\r\n";
char req3[] = "GET / HTTP/1.1\r\nBah\r\n"; char req3[] = "GET / HTTP/1.1\r\nBah\r\n";
char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n"; char req4[] = "GET / HTTP/1.1\r\nA: foo bar\r\nB: bar\r\nbaz\r\n\r\n";
char req5[] = "GET / HTTP/1.1\r\n\r\n";
char req6[] = "G";
char req7[] = " blah ";
char req8[] = " HTTP/1.1 200 OK \n\n";
ASSERT(parse_http_request(req1, sizeof(req1), &ri) == sizeof(req1) - 1); ASSERT(parse_http_message(req1, sizeof(req1), &ri) == sizeof(req1) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0); ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 0); ASSERT(ri.num_headers == 0);
ASSERT(parse_http_request(req2, sizeof(req2), &ri) == -1); ASSERT(parse_http_message(req2, sizeof(req2), &ri) == -1);
ASSERT(parse_http_request(req3, sizeof(req3), &ri) == -1); ASSERT(parse_http_message(req3, sizeof(req3), &ri) == 0);
ASSERT(parse_http_message(req6, sizeof(req6), &ri) == 0);
ASSERT(parse_http_message(req7, sizeof(req7), &ri) == 0);
ASSERT(parse_http_message("", 0, &ri) == 0);
ASSERT(parse_http_message(req8, sizeof(req8), &ri) == sizeof(req8) - 1);
// TODO(lsm): Fix this. Header value may span multiple lines. // TODO(lsm): Fix this. Header value may span multiple lines.
ASSERT(parse_http_request(req4, sizeof(req4), &ri) == sizeof(req4) - 1); ASSERT(parse_http_message(req4, sizeof(req4), &ri) == sizeof(req4) - 1);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
ASSERT(ri.num_headers == 3); ASSERT(ri.num_headers == 3);
ASSERT(strcmp(ri.http_headers[0].name, "A") == 0); ASSERT(strcmp(ri.http_headers[0].name, "A") == 0);
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0); ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
...@@ -55,7 +67,9 @@ static void test_parse_http_request() { ...@@ -55,7 +67,9 @@ static void test_parse_http_request() {
ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0); ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0);
ASSERT(strcmp(ri.http_headers[2].value, "") == 0); ASSERT(strcmp(ri.http_headers[2].value, "") == 0);
// TODO(lsm): add more tests. ASSERT(parse_http_message(req5, sizeof(req5), &ri) == sizeof(req5) - 1);
ASSERT(strcmp(ri.request_method, "GET") == 0);
ASSERT(strcmp(ri.http_version, "1.1") == 0);
} }
static void test_should_keep_alive(void) { static void test_should_keep_alive(void) {
...@@ -68,7 +82,8 @@ static void test_should_keep_alive(void) { ...@@ -68,7 +82,8 @@ static void test_should_keep_alive(void) {
memset(&conn, 0, sizeof(conn)); memset(&conn, 0, sizeof(conn));
conn.ctx = &ctx; conn.ctx = &ctx;
parse_http_request(req1, sizeof(req1), &conn.request_info); ASSERT(parse_http_message(req1, sizeof(req1), &conn.request_info) ==
sizeof(req1) - 1);
ctx.config[ENABLE_KEEP_ALIVE] = "no"; ctx.config[ENABLE_KEEP_ALIVE] = "no";
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
...@@ -80,13 +95,13 @@ static void test_should_keep_alive(void) { ...@@ -80,13 +95,13 @@ static void test_should_keep_alive(void) {
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
conn.must_close = 0; conn.must_close = 0;
parse_http_request(req2, sizeof(req2), &conn.request_info); parse_http_message(req2, sizeof(req2), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
parse_http_request(req3, sizeof(req3), &conn.request_info); parse_http_message(req3, sizeof(req3), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 0); ASSERT(should_keep_alive(&conn) == 0);
parse_http_request(req4, sizeof(req4), &conn.request_info); parse_http_message(req4, sizeof(req4), &conn.request_info);
ASSERT(should_keep_alive(&conn) == 1); ASSERT(should_keep_alive(&conn) == 1);
conn.status_code = 401; conn.status_code = 401;
...@@ -143,7 +158,9 @@ static void test_remove_double_dots() { ...@@ -143,7 +158,9 @@ static void test_remove_double_dots() {
size_t i; size_t i;
for (i = 0; i < ARRAY_SIZE(data); i++) { for (i = 0; i < ARRAY_SIZE(data); i++) {
#if 0
printf("[%s] -> [%s]\n", data[i].before, data[i].after); printf("[%s] -> [%s]\n", data[i].before, data[i].after);
#endif
remove_double_dots_and_double_slashes(data[i].before); remove_double_dots_and_double_slashes(data[i].before);
ASSERT(strcmp(data[i].before, data[i].after) == 0); ASSERT(strcmp(data[i].before, data[i].after) == 0);
} }
...@@ -162,14 +179,12 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) { ...@@ -162,14 +179,12 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) {
return ""; return "";
} else if (event == MG_OPEN_FILE) { } else if (event == MG_OPEN_FILE) {
const char *path = request_info->ev_data; const char *path = request_info->ev_data;
printf("%s: [%s]\n", __func__, path);
if (strcmp(path, "./blah") == 0) { if (strcmp(path, "./blah") == 0) {
mg_get_request_info(conn)->ev_data = mg_get_request_info(conn)->ev_data =
(void *) (int) strlen(inmemory_file_data); (void *) (int) strlen(inmemory_file_data);
return (void *) inmemory_file_data; return (void *) inmemory_file_data;
} }
} else if (event == MG_EVENT_LOG) { } else if (event == MG_EVENT_LOG) {
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
} }
return NULL; return NULL;
...@@ -178,6 +193,7 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) { ...@@ -178,6 +193,7 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) {
static const char *OPTIONS[] = { static const char *OPTIONS[] = {
"document_root", ".", "document_root", ".",
"listening_ports", LISTENING_ADDR, "listening_ports", LISTENING_ADDR,
"ssl_certificate", "build/ssl_cert.pem",
NULL, NULL,
}; };
...@@ -187,69 +203,87 @@ static void test_mg_upload(void) { ...@@ -187,69 +203,87 @@ static void test_mg_upload(void) {
mg_stop(ctx); mg_stop(ctx);
} }
static void test_mg_fetch(void) { static char *read_file(const char *path, int *size) {
char buf[2000], buf2[2000];
int n, length;
struct mg_context *ctx;
struct mg_request_info ri;
const char *tmp_file = "temporary_file_name_for_unit_test.txt";
struct file file;
FILE *fp; FILE *fp;
struct stat st;
char *data = NULL;
if ((fp = fopen(path, "r")) != NULL && !fstat(fileno(fp), &st)) {
*size = st.st_size;
ASSERT((data = malloc(*size)) != NULL);
ASSERT(fread(data, 1, *size, fp) == (size_t) *size);
fclose(fp);
}
return data;
}
static char *read_conn(struct mg_connection *conn, int *size) {
char buf[100], *data = NULL;
int len;
*size = 0;
while ((len = mg_read(conn, buf, sizeof(buf))) > 0) {
*size += len;
ASSERT((data = realloc(data, *size)) != NULL);
memcpy(data + *size - len, buf, len);
}
return data;
}
static void test_mg_download(void) {
char *p1, *p2, ebuf[100];
int len1, len2, port = atoi(HTTPS_PORT);
struct mg_connection *conn;
struct mg_context *ctx;
ASSERT((ctx = mg_start(event_handler, NULL, OPTIONS)) != NULL); ASSERT((ctx = mg_start(event_handler, NULL, OPTIONS)) != NULL);
// Failed fetch, pass invalid URL ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "") == NULL);
ASSERT(mg_fetch(ctx, "localhost", tmp_file, buf, sizeof(buf), &ri) == NULL); ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "") == NULL);
ASSERT(mg_fetch(ctx, LISTENING_ADDR, tmp_file, ASSERT(mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "") == NULL);
buf, sizeof(buf), &ri) == NULL);
ASSERT(mg_fetch(ctx, "http://$$$.$$$", tmp_file, // Fetch nonexistent file, should see 404
buf, sizeof(buf), &ri) == NULL); ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
"GET /gimbec HTTP/1.0\r\n\r\n")) != NULL);
// Failed fetch, pass invalid file name ASSERT(strcmp(conn->request_info.uri, "404") == 0);
ASSERT(mg_fetch(ctx, "http://" LISTENING_ADDR "/data", mg_close_connection(conn);
"/this/file/must/not/exist/ever",
buf, sizeof(buf), &ri) == NULL); // Fetch mongoose.c, should succeed
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
// Successful fetch "GET /mongoose.c HTTP/1.0\r\n\r\n")) != NULL);
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/data", ASSERT(!strcmp(conn->request_info.uri, "200"));
tmp_file, buf, sizeof(buf), &ri)) != NULL); ASSERT((p1 = read_conn(conn, &len1)) != NULL);
ASSERT(ri.num_headers == 2); ASSERT((p2 = read_file("mongoose.c", &len2)) != NULL);
ASSERT(!strcmp(ri.request_method, "HTTP/1.1")); ASSERT(len1 == len2);
ASSERT(!strcmp(ri.uri, "200")); ASSERT(memcmp(p1, p2, len1) == 0);
ASSERT(!strcmp(ri.http_version, "OK")); free(p1), free(p2);
ASSERT((length = ftell(fp)) == (int) strlen(fetch_data)); mg_close_connection(conn);
fseek(fp, 0, SEEK_SET);
ASSERT(fread(buf2, 1, length, fp) == (size_t) length); // Fetch in-memory file, should succeed.
ASSERT(memcmp(buf2, fetch_data, length) == 0); ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
fclose(fp); "GET /blah HTTP/1.1\r\n\r\n")) != NULL);
ASSERT((p1 = read_conn(conn, &len1)) != NULL);
// Fetch big file, mongoose.c ASSERT(len1 == (int) strlen(inmemory_file_data));
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/mongoose.c", ASSERT(memcmp(p1, inmemory_file_data, len1) == 0);
tmp_file, buf, sizeof(buf), &ri)) != NULL); free(p1);
ASSERT(mg_stat(fc(ctx), "mongoose.c", &file)); mg_close_connection(conn);
ASSERT(file.size == ftell(fp));
ASSERT(!strcmp(ri.request_method, "HTTP/1.1")); // Test SSL redirect, IP address
ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
// Fetch nonexistent file, /blah ebuf, sizeof(ebuf), "%s",
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/boo", "GET /foo HTTP/1.1\r\n\r\n")) != NULL);
tmp_file, buf, sizeof(buf), &ri)) != NULL); ASSERT(strcmp(conn->request_info.uri, "302") == 0);
ASSERT(!mg_strcasecmp(ri.uri, "404")); ASSERT(strcmp(mg_get_header(conn, "Location"),
fclose(fp); "https://127.0.0.1:" HTTPS_PORT "/foo") == 0);
mg_close_connection(conn);
// Fetch existing inmemory file
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/blah", // Test SSL redirect, Host:
tmp_file, buf, sizeof(buf), &ri)) != NULL); ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
ASSERT(!mg_strcasecmp(ri.uri, "200")); ebuf, sizeof(ebuf), "%s",
n = ftell(fp); "GET /foo HTTP/1.1\r\nHost: a.b:77\n\n")) != NULL);
fseek(fp, 0, SEEK_SET); ASSERT(strcmp(conn->request_info.uri, "302") == 0);
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2); ASSERT(strcmp(mg_get_header(conn, "Location"),
n = fread(buf2, 1, n, fp); "https://a.b:" HTTPS_PORT "/foo") == 0);
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2); mg_close_connection(conn);
ASSERT((size_t) ftell(fp) == (size_t) strlen(inmemory_file_data));
ASSERT(!memcmp(inmemory_file_data, buf2, ftell(fp)));
fclose(fp);
remove(tmp_file);
mg_stop(ctx); mg_stop(ctx);
} }
...@@ -261,7 +295,6 @@ static void test_base64_encode(void) { ...@@ -261,7 +295,6 @@ static void test_base64_encode(void) {
for (i = 0; in[i] != NULL; i++) { for (i = 0; in[i] != NULL; i++) {
base64_encode((unsigned char *) in[i], strlen(in[i]), buf); base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
printf("[%s] [%s]\n", out[i], buf);
ASSERT(!strcmp(buf, out[i])); ASSERT(!strcmp(buf, out[i]));
} }
} }
...@@ -329,7 +362,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) { ...@@ -329,7 +362,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) {
luaL_dostring(L, buf); luaL_dostring(L, buf);
lua_getglobal(L, var_name); lua_getglobal(L, var_name);
v = lua_tostring(L, -1); v = lua_tostring(L, -1);
#if 0
printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value); printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value);
#endif
ASSERT((value == NULL && v == NULL) || ASSERT((value == NULL && v == NULL) ||
(value != NULL && v != NULL && !strcmp(value, v))); (value != NULL && v != NULL && !strcmp(value, v)));
} }
...@@ -341,13 +376,12 @@ static void test_lua(void) { ...@@ -341,13 +376,12 @@ static void test_lua(void) {
char http_request[] = "POST /foo/bar HTTP/1.1\r\n" char http_request[] = "POST /foo/bar HTTP/1.1\r\n"
"Content-Length: 12\r\n" "Content-Length: 12\r\n"
"Connection: close\r\n\r\nhello world!"; "Connection: close\r\n\r\nhello world!";
const char *page = "<? print('hi') ?>";
lua_State *L = luaL_newstate(); lua_State *L = luaL_newstate();
conn.ctx = &ctx; conn.ctx = &ctx;
conn.buf = http_request; conn.buf = http_request;
conn.buf_size = conn.data_len = strlen(http_request); conn.buf_size = conn.data_len = strlen(http_request);
conn.request_len = parse_http_request(conn.buf, conn.data_len, conn.request_len = parse_http_message(conn.buf, conn.data_len,
&conn.request_info); &conn.request_info);
conn.content_len = conn.data_len - conn.request_len; conn.content_len = conn.data_len - conn.request_len;
...@@ -371,7 +405,7 @@ static void test_lua(void) { ...@@ -371,7 +405,7 @@ static void test_lua(void) {
static void *user_data_tester(enum mg_event event, struct mg_connection *conn) { static void *user_data_tester(enum mg_event event, struct mg_connection *conn) {
struct mg_request_info *ri = mg_get_request_info(conn); struct mg_request_info *ri = mg_get_request_info(conn);
ASSERT(ri->user_data == (void *) 123); ASSERT(ri->user_data == (void *) 123);
ASSERT(event == MG_NEW_REQUEST); ASSERT(event == MG_NEW_REQUEST || event == MG_INIT_SSL);
return NULL; return NULL;
} }
...@@ -413,8 +447,8 @@ int __cdecl main(void) { ...@@ -413,8 +447,8 @@ int __cdecl main(void) {
test_match_prefix(); test_match_prefix();
test_remove_double_dots(); test_remove_double_dots();
test_should_keep_alive(); test_should_keep_alive();
test_parse_http_request(); test_parse_http_message();
test_mg_fetch(); test_mg_download();
test_mg_get_var(); test_mg_get_var();
test_set_throttle(); test_set_throttle();
test_next_option(); test_next_option();
...@@ -425,5 +459,6 @@ int __cdecl main(void) { ...@@ -425,5 +459,6 @@ int __cdecl main(void) {
test_lua(); test_lua();
#endif #endif
test_skip_quoted(); test_skip_quoted();
printf("%s\n", "PASSED");
return 0; return 0;
} }
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