mirror of
https://github.com/lammertb/libhttp.git
synced 2026-01-03 16:02:30 +03:00
Removed mg_connect() and mg_fetch(). Added mg_download()
This commit is contained in:
233
mongoose.c
233
mongoose.c
@@ -1627,16 +1627,13 @@ int mg_write(struct mg_connection *conn, const void *buf, size_t len) {
|
||||
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;
|
||||
int len;
|
||||
va_list ap;
|
||||
|
||||
// Print in a local buffer first, hoping that it is large enough to
|
||||
// hold the whole message
|
||||
va_start(ap, fmt);
|
||||
len = vsnprintf(mem, sizeof(mem), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if (len == 0) {
|
||||
// Do nothing. mg_printf(conn, "%s", "") was called.
|
||||
@@ -1647,9 +1644,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
|
||||
} else if (len > (int) sizeof(mem) &&
|
||||
(buf = (char *) malloc(len + 1)) != NULL) {
|
||||
// Local buffer is not large enough, allocate big buffer on heap
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, len + 1, fmt, ap);
|
||||
va_end(ap);
|
||||
len = mg_write(conn, buf, (size_t) len);
|
||||
free(buf);
|
||||
} else if (len > (int) sizeof(mem)) {
|
||||
@@ -1665,6 +1660,12 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
|
||||
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.
|
||||
// 0-terminate the destination buffer. Return the length of decoded data.
|
||||
// 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,
|
||||
|
||||
if (!mg_fopen(conn, path, "rb", filep)) {
|
||||
send_http_error(conn, 500, http_500_error,
|
||||
"fopen(%s): %s", path, strerror(ERRNO));
|
||||
"fopen(%s): %s", path, strerror(ERRNO));
|
||||
return;
|
||||
}
|
||||
fclose_on_exec(filep);
|
||||
@@ -2891,7 +2892,7 @@ static int is_valid_http_method(const char *method) {
|
||||
// This function modifies the buffer by NUL-terminating
|
||||
// HTTP request components, header names and header values.
|
||||
static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
|
||||
int request_length = get_request_len(buf, len);
|
||||
int is_request, request_length = get_request_len(buf, len);
|
||||
if (request_length > 0) {
|
||||
// Reset attributes. DO NOT TOUCH is_ssl, remote_ip, remote_port
|
||||
ri->remote_user = ri->request_method = ri->uri = ri->http_version = NULL;
|
||||
@@ -2906,28 +2907,20 @@ static int parse_http_message(char *buf, int len, struct mg_request_info *ri) {
|
||||
ri->request_method = skip(&buf, " ");
|
||||
ri->uri = skip(&buf, " ");
|
||||
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;
|
||||
}
|
||||
|
||||
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,
|
||||
// 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
|
||||
@@ -4734,74 +4727,109 @@ void mg_close_connection(struct mg_connection *conn) {
|
||||
free(conn);
|
||||
}
|
||||
|
||||
struct mg_connection *mg_connect(struct mg_context *ctx,
|
||||
const char *host, int port, int use_ssl) {
|
||||
struct mg_connection *newconn = NULL;
|
||||
struct mg_connection *mg_connect(const char *host, int port, int use_ssl,
|
||||
char *ebuf, size_t ebuf_len) {
|
||||
static struct mg_context fake_ctx;
|
||||
struct mg_connection *conn = NULL;
|
||||
struct sockaddr_in sin;
|
||||
struct hostent *he;
|
||||
SSL_CTX *ssl = NULL;
|
||||
int sock;
|
||||
|
||||
if (use_ssl && (ctx == NULL || ctx->client_ssl_ctx == NULL)) {
|
||||
cry(fc(ctx), "%s: SSL is not initialized", __func__);
|
||||
if (host == NULL) {
|
||||
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) {
|
||||
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) {
|
||||
cry(fc(ctx), "%s: socket: %s", __func__, strerror(ERRNO));
|
||||
snprintf(ebuf, ebuf_len, "socket(): %s", strerror(ERRNO));
|
||||
} else {
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = htons((uint16_t) port);
|
||||
sin.sin_addr = * (struct in_addr *) he->h_addr_list[0];
|
||||
if (connect(sock, (struct sockaddr *) &sin, sizeof(sin)) != 0) {
|
||||
cry(fc(ctx), "%s: connect(%s:%d): %s", __func__, host, port,
|
||||
strerror(ERRNO));
|
||||
snprintf(ebuf, ebuf_len, "connect(%s:%d): %s",
|
||||
host, port, strerror(ERRNO));
|
||||
closesocket(sock);
|
||||
} else if ((newconn = (struct mg_connection *)
|
||||
calloc(1, sizeof(*newconn))) == NULL) {
|
||||
cry(fc(ctx), "%s: calloc: %s", __func__, strerror(ERRNO));
|
||||
} else if ((conn = (struct mg_connection *)
|
||||
calloc(1, sizeof(*conn) + MAX_REQUEST_SIZE)) == NULL) {
|
||||
snprintf(ebuf, ebuf_len, "calloc(): %s", strerror(ERRNO));
|
||||
closesocket(sock);
|
||||
} else {
|
||||
newconn->ctx = ctx;
|
||||
newconn->client.sock = sock;
|
||||
newconn->client.rsa.sin = sin;
|
||||
newconn->client.is_ssl = use_ssl;
|
||||
conn->buf_size = MAX_REQUEST_SIZE;
|
||||
conn->buf = (char *) (conn + 1);
|
||||
conn->ctx = &fake_ctx;
|
||||
conn->client.sock = sock;
|
||||
conn->client.rsa.sin = sin;
|
||||
conn->client.is_ssl = use_ssl;
|
||||
if (use_ssl) {
|
||||
sslize(newconn, ctx->client_ssl_ctx, SSL_connect);
|
||||
sslize(conn, ssl, SSL_connect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newconn;
|
||||
}
|
||||
|
||||
FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
|
||||
char *buf, size_t buf_len, struct mg_request_info *ri) {
|
||||
struct mg_connection *newconn;
|
||||
int n, req_length, data_length, port;
|
||||
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) {
|
||||
} else if (sscanf(url, "%9[htps]://%1024[^/]/%n", proto, host, &n) == 2) {
|
||||
port = mg_strcasecmp(proto, "https") == 0 ? 443 : 80;
|
||||
} else {
|
||||
cry(fc(ctx), "%s: invalid URL: [%s]", __func__, url);
|
||||
return NULL;
|
||||
if (ssl != NULL) {
|
||||
SSL_CTX_free(ssl);
|
||||
}
|
||||
|
||||
if ((newconn = mg_connect(ctx, host, port,
|
||||
!strcmp(proto, "https"))) == NULL) {
|
||||
cry(fc(ctx), "%s: mg_connect(%s): %s", __func__, url, strerror(ERRNO));
|
||||
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 int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len) {
|
||||
const char *cl;
|
||||
|
||||
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 {
|
||||
mg_printf(newconn, "GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n", url + n, host);
|
||||
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));
|
||||
// Request is valid
|
||||
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;
|
||||
|
||||
va_start(ap, fmt);
|
||||
ebuf[0] = '\0';
|
||||
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 {
|
||||
#if 0
|
||||
// Write chunk of data that may be in the user's buffer
|
||||
data_length -= req_length;
|
||||
if (data_length > 0 &&
|
||||
@@ -4811,8 +4839,8 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
|
||||
fp = NULL;
|
||||
}
|
||||
// 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.
|
||||
while (fp && (data_length = pull(0, newconn, buf2, sizeof(buf2))) > 0) {
|
||||
// mg_read() cause we didn't set conn->content_len properly.
|
||||
while (fp && (data_length = pull(0, conn, buf2, sizeof(buf2))) > 0) {
|
||||
if (fwrite(buf2, 1, data_length, fp) != (size_t) data_length) {
|
||||
cry(fc(ctx), "%s: fwrite(%s): %s", __func__, path, strerror(ERRNO));
|
||||
fclose(fp);
|
||||
@@ -4820,23 +4848,20 @@ FILE *mg_fetch(struct mg_context *ctx, const char *url, const char *path,
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
mg_close_connection(newconn);
|
||||
#endif
|
||||
}
|
||||
if (ebuf[0] != '\0' && conn != NULL) {
|
||||
mg_close_connection(conn);
|
||||
conn = NULL;
|
||||
}
|
||||
|
||||
return fp;
|
||||
}
|
||||
|
||||
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');
|
||||
return conn;
|
||||
}
|
||||
|
||||
static void process_new_connection(struct mg_connection *conn) {
|
||||
struct mg_request_info *ri = &conn->request_info;
|
||||
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 = 0;
|
||||
@@ -4845,38 +4870,18 @@ static void process_new_connection(struct mg_connection *conn) {
|
||||
// to crule42.
|
||||
conn->data_len = 0;
|
||||
do {
|
||||
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) {
|
||||
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;
|
||||
if (!getreq(conn, ebuf, sizeof(ebuf))) {
|
||||
send_http_error(conn, 500, "Server Error", "%s", ebuf);
|
||||
} else if (!is_valid_uri(conn->request_info.uri)) {
|
||||
snprintf(ebuf, sizeof(ebuf), "Invalid URI: [%s]", ri->uri);
|
||||
send_http_error(conn, 400, "Bad Request", "%s", ebuf);
|
||||
} else if (strcmp(ri->http_version, "1.0") &&
|
||||
strcmp(ri->http_version, "1.1")) {
|
||||
// Request seems valid, but HTTP version is strange
|
||||
send_http_error(conn, 505, "HTTP version not supported", "%s", "");
|
||||
log_access(conn);
|
||||
} else {
|
||||
// Request is valid, handle it
|
||||
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);
|
||||
snprintf(ebuf, sizeof(ebuf), "Bad HTTP version: [%s]", ri->http_version);
|
||||
send_http_error(conn, 505, "Bad HTTP version", "%s", ebuf);
|
||||
}
|
||||
|
||||
if (ebuf[0] == '\0') {
|
||||
handle_request(conn);
|
||||
conn->request_info.ev_data = (void *) (long) conn->status_code;
|
||||
call_user(conn, MG_REQUEST_COMPLETE);
|
||||
|
||||
48
mongoose.h
48
mongoose.h
@@ -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);
|
||||
|
||||
|
||||
// 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
|
||||
#if _MSC_VER >= 1400
|
||||
#include <sal.h>
|
||||
@@ -260,6 +254,11 @@ int mg_write(struct mg_connection *, const void *buf, size_t len);
|
||||
#define PRINTF_ARGS(x, y)
|
||||
#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 *,
|
||||
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(2, 3);
|
||||
|
||||
@@ -316,32 +315,29 @@ int mg_get_cookie(const struct mg_connection *,
|
||||
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:
|
||||
// On success, valid pointer to the new connection
|
||||
// On error, NULL
|
||||
struct mg_connection *mg_connect(struct mg_context *ctx,
|
||||
const char *host, int port, int use_ssl);
|
||||
// On success, valid pointer to the new connection, suitable for mg_read().
|
||||
// On error, NULL.
|
||||
// Example:
|
||||
// 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);
|
||||
|
||||
|
||||
// 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 and MG_UPLOAD event is sent.
|
||||
// Return number of uploaded files.
|
||||
|
||||
15
test/test.pl
15
test/test.pl
@@ -167,7 +167,7 @@ kill_spawned_child();
|
||||
|
||||
# Spawn the server on port $port
|
||||
my $cmd = "$exe ".
|
||||
"-listening_ports $port ".
|
||||
"-listening_ports 127.0.0.1:$port ".
|
||||
"-access_log_file access.log ".
|
||||
"-error_log_file debug.log ".
|
||||
"-cgi_environment CGI_FOO=foo,CGI_BAR=bar,CGI_BAZ=baz " .
|
||||
@@ -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');
|
||||
|
||||
# Test HTTP version parsing
|
||||
o("GET / HTTPX/1.0\r\n\r\n", '400 Bad Request', 'Bad HTTP Version', 0);
|
||||
o("GET / HTTP/x.1\r\n\r\n", '505 HTTP', 'Bad HTTP maj Version');
|
||||
o("GET / HTTP/1.1z\r\n\r\n", '505 HTTP', 'Bad HTTP min Version');
|
||||
o("GET / HTTP/02.0\r\n\r\n", '505 HTTP version not supported',
|
||||
'HTTP Version >1.1');
|
||||
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", '^HTTP/1.1 505', 'Bad HTTP maj Version', 0);
|
||||
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", '^HTTP/1.1 505', 'HTTP Version >1.1', 0);
|
||||
|
||||
# File with leading single dot
|
||||
o("GET /.leading.dot.txt HTTP/1.0\n\n", 'abc123', 'Leading dot 1');
|
||||
@@ -463,7 +462,7 @@ sub do_unit_test {
|
||||
|
||||
sub do_embedded_test {
|
||||
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()) {
|
||||
$cmd = "cl $root/embed.c mongoose.c /I. /nologo /DNO_SSL ".
|
||||
"/DLISTENING_PORT=\\\"$port\\\" /link /out:$embed_exe.exe ws2_32.lib ";
|
||||
@@ -514,7 +513,7 @@ sub do_embedded_test {
|
||||
'Remote user: \[\]'
|
||||
, 'request_info', 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",
|
||||
# '401 Unauthorized', 'mg_protect_uri', 0);
|
||||
# o("GET /foo/secret HTTP/1.0\nAuthorization: Digest username=bill\n\n",
|
||||
|
||||
179
test/unit_test.c
179
test/unit_test.c
@@ -21,6 +21,7 @@
|
||||
// Unit test for the mongoose web server. Tests embedded API.
|
||||
|
||||
#define USE_WEBSOCKET
|
||||
#define USE_LUA
|
||||
#include "mongoose.c"
|
||||
|
||||
#define FATAL(str, line) do { \
|
||||
@@ -29,24 +30,35 @@
|
||||
} 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;
|
||||
char req1[] = "GET / 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 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(ri.num_headers == 0);
|
||||
|
||||
ASSERT(parse_http_request(req2, sizeof(req2), &ri) == -1);
|
||||
ASSERT(parse_http_request(req3, sizeof(req3), &ri) == -1);
|
||||
ASSERT(parse_http_message(req2, sizeof(req2), &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.
|
||||
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(strcmp(ri.http_headers[0].name, "A") == 0);
|
||||
ASSERT(strcmp(ri.http_headers[0].value, "foo bar") == 0);
|
||||
@@ -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].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) {
|
||||
@@ -68,7 +82,8 @@ static void test_should_keep_alive(void) {
|
||||
|
||||
memset(&conn, 0, sizeof(conn));
|
||||
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";
|
||||
ASSERT(should_keep_alive(&conn) == 0);
|
||||
@@ -80,13 +95,13 @@ static void test_should_keep_alive(void) {
|
||||
ASSERT(should_keep_alive(&conn) == 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);
|
||||
|
||||
parse_http_request(req3, sizeof(req3), &conn.request_info);
|
||||
parse_http_message(req3, sizeof(req3), &conn.request_info);
|
||||
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);
|
||||
|
||||
conn.status_code = 401;
|
||||
@@ -143,7 +158,9 @@ static void test_remove_double_dots() {
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(data); i++) {
|
||||
#if 0
|
||||
printf("[%s] -> [%s]\n", data[i].before, data[i].after);
|
||||
#endif
|
||||
remove_double_dots_and_double_slashes(data[i].before);
|
||||
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) {
|
||||
return "";
|
||||
} else if (event == MG_OPEN_FILE) {
|
||||
const char *path = request_info->ev_data;
|
||||
printf("%s: [%s]\n", __func__, path);
|
||||
if (strcmp(path, "./blah") == 0) {
|
||||
mg_get_request_info(conn)->ev_data =
|
||||
(void *) (int) strlen(inmemory_file_data);
|
||||
return (void *) inmemory_file_data;
|
||||
}
|
||||
} else if (event == MG_EVENT_LOG) {
|
||||
printf("%s\n", (const char *) mg_get_request_info(conn)->ev_data);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@@ -178,6 +193,7 @@ static void *event_handler(enum mg_event event, struct mg_connection *conn) {
|
||||
static const char *OPTIONS[] = {
|
||||
"document_root", ".",
|
||||
"listening_ports", LISTENING_ADDR,
|
||||
"ssl_certificate", "build/ssl_cert.pem",
|
||||
NULL,
|
||||
};
|
||||
|
||||
@@ -187,69 +203,87 @@ static void test_mg_upload(void) {
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
static void test_mg_fetch(void) {
|
||||
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;
|
||||
static char *read_file(const char *path, int *size) {
|
||||
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);
|
||||
|
||||
// Failed fetch, pass invalid URL
|
||||
ASSERT(mg_fetch(ctx, "localhost", tmp_file, buf, sizeof(buf), &ri) == NULL);
|
||||
ASSERT(mg_fetch(ctx, LISTENING_ADDR, tmp_file,
|
||||
buf, sizeof(buf), &ri) == NULL);
|
||||
ASSERT(mg_fetch(ctx, "http://$$$.$$$", tmp_file,
|
||||
buf, sizeof(buf), &ri) == NULL);
|
||||
ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "") == NULL);
|
||||
ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "") == NULL);
|
||||
ASSERT(mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "") == NULL);
|
||||
|
||||
// Failed fetch, pass invalid file name
|
||||
ASSERT(mg_fetch(ctx, "http://" LISTENING_ADDR "/data",
|
||||
"/this/file/must/not/exist/ever",
|
||||
buf, sizeof(buf), &ri) == NULL);
|
||||
// Fetch nonexistent file, should see 404
|
||||
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
|
||||
"GET /gimbec HTTP/1.0\r\n\r\n")) != NULL);
|
||||
ASSERT(strcmp(conn->request_info.uri, "404") == 0);
|
||||
mg_close_connection(conn);
|
||||
|
||||
// Successful fetch
|
||||
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/data",
|
||||
tmp_file, buf, sizeof(buf), &ri)) != NULL);
|
||||
ASSERT(ri.num_headers == 2);
|
||||
ASSERT(!strcmp(ri.request_method, "HTTP/1.1"));
|
||||
ASSERT(!strcmp(ri.uri, "200"));
|
||||
ASSERT(!strcmp(ri.http_version, "OK"));
|
||||
ASSERT((length = ftell(fp)) == (int) strlen(fetch_data));
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
ASSERT(fread(buf2, 1, length, fp) == (size_t) length);
|
||||
ASSERT(memcmp(buf2, fetch_data, length) == 0);
|
||||
fclose(fp);
|
||||
// Fetch mongoose.c, should succeed
|
||||
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
|
||||
"GET /mongoose.c HTTP/1.0\r\n\r\n")) != NULL);
|
||||
ASSERT(!strcmp(conn->request_info.uri, "200"));
|
||||
ASSERT((p1 = read_conn(conn, &len1)) != NULL);
|
||||
ASSERT((p2 = read_file("mongoose.c", &len2)) != NULL);
|
||||
ASSERT(len1 == len2);
|
||||
ASSERT(memcmp(p1, p2, len1) == 0);
|
||||
free(p1), free(p2);
|
||||
mg_close_connection(conn);
|
||||
|
||||
// Fetch big file, mongoose.c
|
||||
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/mongoose.c",
|
||||
tmp_file, buf, sizeof(buf), &ri)) != NULL);
|
||||
ASSERT(mg_stat(fc(ctx), "mongoose.c", &file));
|
||||
ASSERT(file.size == ftell(fp));
|
||||
ASSERT(!strcmp(ri.request_method, "HTTP/1.1"));
|
||||
// Fetch in-memory file, should succeed.
|
||||
ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s",
|
||||
"GET /blah HTTP/1.1\r\n\r\n")) != NULL);
|
||||
ASSERT((p1 = read_conn(conn, &len1)) != NULL);
|
||||
ASSERT(len1 == (int) strlen(inmemory_file_data));
|
||||
ASSERT(memcmp(p1, inmemory_file_data, len1) == 0);
|
||||
free(p1);
|
||||
mg_close_connection(conn);
|
||||
|
||||
// Fetch nonexistent file, /blah
|
||||
ASSERT((fp = mg_fetch(ctx, "http://" LISTENING_ADDR "/boo",
|
||||
tmp_file, buf, sizeof(buf), &ri)) != NULL);
|
||||
ASSERT(!mg_strcasecmp(ri.uri, "404"));
|
||||
fclose(fp);
|
||||
// Test SSL redirect, IP address
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
|
||||
ebuf, sizeof(ebuf), "%s",
|
||||
"GET /foo HTTP/1.1\r\n\r\n")) != NULL);
|
||||
ASSERT(strcmp(conn->request_info.uri, "302") == 0);
|
||||
ASSERT(strcmp(mg_get_header(conn, "Location"),
|
||||
"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",
|
||||
tmp_file, buf, sizeof(buf), &ri)) != NULL);
|
||||
ASSERT(!mg_strcasecmp(ri.uri, "200"));
|
||||
n = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2);
|
||||
n = fread(buf2, 1, n, fp);
|
||||
printf("%s %d %d [%.*s]\n", __func__, n, (int) feof(fp), n, buf2);
|
||||
ASSERT((size_t) ftell(fp) == (size_t) strlen(inmemory_file_data));
|
||||
ASSERT(!memcmp(inmemory_file_data, buf2, ftell(fp)));
|
||||
fclose(fp);
|
||||
// Test SSL redirect, Host:
|
||||
ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
|
||||
ebuf, sizeof(ebuf), "%s",
|
||||
"GET /foo HTTP/1.1\r\nHost: a.b:77\n\n")) != NULL);
|
||||
ASSERT(strcmp(conn->request_info.uri, "302") == 0);
|
||||
ASSERT(strcmp(mg_get_header(conn, "Location"),
|
||||
"https://a.b:" HTTPS_PORT "/foo") == 0);
|
||||
mg_close_connection(conn);
|
||||
|
||||
remove(tmp_file);
|
||||
mg_stop(ctx);
|
||||
}
|
||||
|
||||
@@ -261,7 +295,6 @@ static void test_base64_encode(void) {
|
||||
|
||||
for (i = 0; in[i] != NULL; i++) {
|
||||
base64_encode((unsigned char *) in[i], strlen(in[i]), buf);
|
||||
printf("[%s] [%s]\n", out[i], buf);
|
||||
ASSERT(!strcmp(buf, out[i]));
|
||||
}
|
||||
}
|
||||
@@ -329,7 +362,9 @@ static void check_lua_expr(lua_State *L, const char *expr, const char *value) {
|
||||
luaL_dostring(L, buf);
|
||||
lua_getglobal(L, var_name);
|
||||
v = lua_tostring(L, -1);
|
||||
#if 0
|
||||
printf("%s: %s: [%s] [%s]\n", __func__, expr, v == NULL ? "null" : v, value);
|
||||
#endif
|
||||
ASSERT((value == NULL && v == NULL) ||
|
||||
(value != NULL && v != NULL && !strcmp(value, v)));
|
||||
}
|
||||
@@ -341,13 +376,12 @@ static void test_lua(void) {
|
||||
char http_request[] = "POST /foo/bar HTTP/1.1\r\n"
|
||||
"Content-Length: 12\r\n"
|
||||
"Connection: close\r\n\r\nhello world!";
|
||||
const char *page = "<? print('hi') ?>";
|
||||
lua_State *L = luaL_newstate();
|
||||
|
||||
conn.ctx = &ctx;
|
||||
conn.buf = 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.content_len = conn.data_len - conn.request_len;
|
||||
|
||||
@@ -371,7 +405,7 @@ static void test_lua(void) {
|
||||
static void *user_data_tester(enum mg_event event, struct mg_connection *conn) {
|
||||
struct mg_request_info *ri = mg_get_request_info(conn);
|
||||
ASSERT(ri->user_data == (void *) 123);
|
||||
ASSERT(event == MG_NEW_REQUEST);
|
||||
ASSERT(event == MG_NEW_REQUEST || event == MG_INIT_SSL);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -413,8 +447,8 @@ int __cdecl main(void) {
|
||||
test_match_prefix();
|
||||
test_remove_double_dots();
|
||||
test_should_keep_alive();
|
||||
test_parse_http_request();
|
||||
test_mg_fetch();
|
||||
test_parse_http_message();
|
||||
test_mg_download();
|
||||
test_mg_get_var();
|
||||
test_set_throttle();
|
||||
test_next_option();
|
||||
@@ -425,5 +459,6 @@ int __cdecl main(void) {
|
||||
test_lua();
|
||||
#endif
|
||||
test_skip_quoted();
|
||||
printf("%s\n", "PASSED");
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user