From 75d9a6c844bf331810ca7f5d7057d575878f9f41 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka <valenok@gmail.com> Date: Wed, 23 Jan 2013 21:54:27 +0000 Subject: [PATCH] Removed mg_connect() and mg_fetch(). Added mg_download() --- mongoose.c | 225 ++++++++++++++++++++++++----------------------- mongoose.h | 48 +++++----- test/test.pl | 15 ++-- test/unit_test.c | 191 ++++++++++++++++++++++++---------------- 4 files changed, 257 insertions(+), 222 deletions(-) diff --git a/mongoose.c b/mongoose.c index a3d61f546..20f019f1e 100644 --- a/mongoose.c +++ b/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); } } } + if (ssl != NULL) { + SSL_CTX_free(ssl); + } - return newconn; + return conn; } -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; +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'); +} - 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; +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 { - cry(fc(ctx), "%s: invalid URL: [%s]", __func__, url); - return NULL; + // 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; - if ((newconn = mg_connect(ctx, host, port, - !strcmp(proto, "https"))) == NULL) { - cry(fc(ctx), "%s: mg_connect(%s): %s", __func__, url, strerror(ERRNO)); + 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 { - 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)); - } 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); diff --git a/mongoose.h b/mongoose.h index 8f67bdc37..17ae33c3a 100644 --- a/mongoose.h +++ b/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. diff --git a/test/test.pl b/test/test.pl index 8eed1e07f..1b1018cbf 100644 --- a/test/test.pl +++ b/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", diff --git a/test/unit_test.c b/test/unit_test.c index d599342be..af916eb7f 100644 --- a/test/unit_test.c +++ b/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); - - // 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); - - // 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 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 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); - - // 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); - - remove(tmp_file); + 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); + + // 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); + + // 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 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); + + // 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); + + // 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); + 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; } -- GitLab