From ecbf79135fd78c5e6554590f2981140e120e5664 Mon Sep 17 00:00:00 2001 From: Sergey Lyubka <valenok@gmail.com> Date: Sat, 28 Sep 2013 11:00:54 +0100 Subject: [PATCH] Refactored API, returned back to event-based handlers. Upload and Websocket API simplified --- build/main.c | 12 +- examples/chat.c | 19 +-- examples/hello.c | 54 ++++---- examples/post.c | 60 ++++---- examples/upload.c | 73 +++++----- examples/websocket.c | 56 +++++--- mongoose.c | 323 ++++++++++++++++++++----------------------- mongoose.h | 75 +++++----- test/unit_test.c | 228 +++++++++++------------------- 9 files changed, 410 insertions(+), 490 deletions(-) diff --git a/build/main.c b/build/main.c index 2e8bc5ba4..e0a29ba8f 100644 --- a/build/main.c +++ b/build/main.c @@ -271,9 +271,10 @@ static void init_server_name(void) { mg_version()); } -static int log_message(const struct mg_connection *conn, const char *message) { - (void) conn; - printf("%s\n", message); +static int event_handler(struct mg_event *event) { + if (event->type == MG_EVENT_LOG) { + printf("%s\n", (const char *) event->event_param); + } return 0; } @@ -341,7 +342,6 @@ static void set_absolute_path(char *options[], const char *option_name, } static void start_mongoose(int argc, char *argv[]) { - struct mg_callbacks callbacks; char *options[MAX_OPTIONS]; int i; @@ -385,9 +385,7 @@ static void start_mongoose(int argc, char *argv[]) { signal(SIGINT, signal_handler); // Start Mongoose - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.log_message = &log_message; - ctx = mg_start(&callbacks, NULL, (const char **) options); + ctx = mg_start((const char **) options, event_handler, NULL); for (i = 0; options[i] != NULL; i++) { free(options[i]); } diff --git a/examples/chat.c b/examples/chat.c index e39e89139..0afb5c0da 100644 --- a/examples/chat.c +++ b/examples/chat.c @@ -326,9 +326,12 @@ static void redirect_to_ssl(struct mg_connection *conn, } } -static int begin_request_handler(struct mg_connection *conn) { - const struct mg_request_info *request_info = mg_get_request_info(conn); - int processed = 1; +static int event_handler(struct mg_event *event) { + struct mg_request_info *request_info = event->request_info; + struct mg_connection *conn = event->conn; + int result = 1; + + if (event->type != MG_REQUEST_BEGIN) return 0; if (!request_info->is_ssl) { redirect_to_ssl(conn, request_info); @@ -343,9 +346,10 @@ static int begin_request_handler(struct mg_connection *conn) { } else { // No suitable handler found, mark as not processed. Mongoose will // try to serve the request. - processed = 0; + result = 0; } - return processed; + + return result; } static const char *options[] = { @@ -357,7 +361,6 @@ static const char *options[] = { }; int main(void) { - struct mg_callbacks callbacks; struct mg_context *ctx; // Initialize random number generator. It will be used later on for @@ -365,9 +368,7 @@ int main(void) { srand((unsigned) time(0)); // Setup and start Mongoose - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = begin_request_handler; - if ((ctx = mg_start(&callbacks, NULL, options)) == NULL) { + if ((ctx = mg_start(options, event_handler, NULL)) == NULL) { printf("%s\n", "Cannot start chat server, fatal exit"); exit(EXIT_FAILURE); } diff --git a/examples/hello.c b/examples/hello.c index 680a0122f..b516d7eb5 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -3,42 +3,42 @@ #include "mongoose.h" // This function will be called by mongoose on every new request. -static int begin_request_handler(struct mg_connection *conn) { - const struct mg_request_info *request_info = mg_get_request_info(conn); - char content[100]; - - // Prepare the message we're going to send - int content_length = snprintf(content, sizeof(content), - "Hello from mongoose! Remote port: %d", - request_info->remote_port); - - // Send HTTP reply to the client - mg_printf(conn, - "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n" - "Content-Length: %d\r\n" // Always set Content-Length - "\r\n" - "%s", - content_length, content); - - // Returning non-zero tells mongoose that our function has replied to - // the client, and mongoose should not send client any more data. - return 1; +static int event_handler(struct mg_event *event) { + + if (event->type == MG_REQUEST_BEGIN) { + char content[100]; + + // Prepare the message we're going to send + int content_length = snprintf(content, sizeof(content), + "Hello from mongoose! Requested: [%s] [%s]", + event->request_info->request_method, event->request_info->uri); + + // Send HTTP reply to the client + mg_printf(event->conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n" + "Content-Length: %d\r\n" // Always set Content-Length + "\r\n" + "%s", + content_length, content); + + // Returning non-zero tells mongoose that our function has replied to + // the client, and mongoose should not send client any more data. + return 1; + } + + // We do not handle any other event + return 0; } int main(void) { struct mg_context *ctx; - struct mg_callbacks callbacks; // List of options. Last element must be NULL. const char *options[] = {"listening_ports", "8080", NULL}; - // Prepare callbacks structure. We have only one callback, the rest are NULL. - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = begin_request_handler; - // Start the web server. - ctx = mg_start(&callbacks, NULL, options); + ctx = mg_start(options, &event_handler, NULL); // Wait until user hits "enter". Server is running in separate thread. // Navigating to http://localhost:8080 will invoke begin_request_handler(). diff --git a/examples/post.c b/examples/post.c index 1c0a476c7..fe3418666 100644 --- a/examples/post.c +++ b/examples/post.c @@ -10,45 +10,47 @@ static const char *html_form = "<input type=\"submit\" />" "</form></body></html>"; -static int begin_request_handler(struct mg_connection *conn) { - const struct mg_request_info *ri = mg_get_request_info(conn); +static int event_handler(struct mg_event *event) { char post_data[1024], input1[sizeof(post_data)], input2[sizeof(post_data)]; int post_data_len; - if (!strcmp(ri->uri, "/handle_post_request")) { - // User has submitted a form, show submitted data and a variable value - post_data_len = mg_read(conn, post_data, sizeof(post_data)); - - // Parse form data. input1 and input2 are guaranteed to be NUL-terminated - mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1)); - mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2)); - - // Send reply to the client, showing submitted form values. - mg_printf(conn, "HTTP/1.0 200 OK\r\n" - "Content-Type: text/plain\r\n\r\n" - "Submitted data: [%.*s]\n" - "Submitted data length: %d bytes\n" - "input_1: [%s]\n" - "input_2: [%s]\n", - post_data_len, post_data, post_data_len, input1, input2); - } else { - // Show HTML form. - mg_printf(conn, "HTTP/1.0 200 OK\r\n" - "Content-Length: %d\r\n" - "Content-Type: text/html\r\n\r\n%s", - (int) strlen(html_form), html_form); + if (event->type == MG_REQUEST_BEGIN) { + if (!strcmp(event->request_info->uri, "/handle_post_request")) { + // User has submitted a form, show submitted data and a variable value + post_data_len = mg_read(event->conn, post_data, sizeof(post_data)); + + // Parse form data. input1 and input2 are guaranteed to be NUL-terminated + mg_get_var(post_data, post_data_len, "input_1", input1, sizeof(input1)); + mg_get_var(post_data, post_data_len, "input_2", input2, sizeof(input2)); + + // Send reply to the client, showing submitted form values. + mg_printf(event->conn, "HTTP/1.0 200 OK\r\n" + "Content-Type: text/plain\r\n\r\n" + "Submitted data: [%.*s]\n" + "Submitted data length: %d bytes\n" + "input_1: [%s]\n" + "input_2: [%s]\n", + post_data_len, post_data, post_data_len, input1, input2); + } else { + // Show HTML form. + mg_printf(event->conn, "HTTP/1.0 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/html\r\n\r\n%s", + (int) strlen(html_form), html_form); + } + + return 1; // Mark event as processed } - return 1; // Mark request as processed + + // All other events are left not processed + return 0; } int main(void) { struct mg_context *ctx; const char *options[] = {"listening_ports", "8080", NULL}; - struct mg_callbacks callbacks; - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = begin_request_handler; - ctx = mg_start(&callbacks, NULL, options); + ctx = mg_start(options, &event_handler, NULL); getchar(); // Wait until user hits "enter" mg_stop(ctx); diff --git a/examples/upload.c b/examples/upload.c index 599da50e1..0ce0fac7c 100644 --- a/examples/upload.c +++ b/examples/upload.c @@ -3,57 +3,48 @@ #include <stdio.h> #include <string.h> -#include <fcntl.h> -#include <stdlib.h> -#ifdef _WIN32 -#include <windows.h> -#include <io.h> -#define strtoll strtol -typedef __int64 int64_t; -#else -#include <inttypes.h> -#include <unistd.h> -#endif // !_WIN32 - #include "mongoose.h" -static int begin_request_handler(struct mg_connection *conn) { - if (!strcmp(mg_get_request_info(conn)->uri, "/handle_post_request")) { - mg_printf(conn, "%s", "HTTP/1.0 200 OK\r\n\r\n"); - mg_upload(conn, "/tmp"); - } else { - // Show HTML form. Make sure it has enctype="multipart/form-data" attr. - static const char *html_form = - "<html><body>Upload example." - "<form method=\"POST\" action=\"/handle_post_request\" " - " enctype=\"multipart/form-data\">" - "<input type=\"file\" name=\"file\" /> <br/>" - "<input type=\"submit\" value=\"Upload\" />" - "</form></body></html>"; - - mg_printf(conn, "HTTP/1.0 200 OK\r\n" - "Content-Length: %d\r\n" - "Content-Type: text/html\r\n\r\n%s", - (int) strlen(html_form), html_form); +static int event_handler(struct mg_event *event) { + + if (event->type == MG_REQUEST_BEGIN) { + if (!strcmp(event->request_info->uri, "/handle_post_request")) { + char path[200]; + FILE *fp = mg_upload(event->conn, "/tmp", path, sizeof(path)); + if (fp != NULL) { + fclose(fp); + mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\nSaved: [%s]", path); + } else { + mg_printf(event->conn, "%s", "HTTP/1.0 200 OK\r\n\r\nNo files sent"); + } + } else { + // Show HTML form. Make sure it has enctype="multipart/form-data" attr. + static const char *html_form = + "<html><body>Upload example." + "<form method=\"POST\" action=\"/handle_post_request\" " + " enctype=\"multipart/form-data\">" + "<input type=\"file\" name=\"file\" /> <br/>" + "<input type=\"submit\" value=\"Upload\" />" + "</form></body></html>"; + + mg_printf(event->conn, "HTTP/1.0 200 OK\r\n" + "Content-Length: %d\r\n" + "Content-Type: text/html\r\n\r\n%s", + (int) strlen(html_form), html_form); + } + + // Mark request as processed + return 1; } - // Mark request as processed + // All other events left unprocessed return 1; } -static void upload_handler(struct mg_connection *conn, const char *path) { - mg_printf(conn, "Saved [%s]", path); -} - int main(void) { struct mg_context *ctx; const char *options[] = {"listening_ports", "8080", NULL}; - struct mg_callbacks callbacks; - - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = begin_request_handler; - callbacks.upload = upload_handler; - ctx = mg_start(&callbacks, NULL, options); + ctx = mg_start(options, event_handler, NULL); getchar(); // Wait until user hits "enter" mg_stop(ctx); diff --git a/examples/websocket.c b/examples/websocket.c index 8a6083c1c..050c51e5d 100644 --- a/examples/websocket.c +++ b/examples/websocket.c @@ -5,38 +5,52 @@ #include <string.h> #include "mongoose.h" -static void websocket_ready_handler(struct mg_connection *conn) { - static const char *message = "server ready"; - mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, message, strlen(message)); -} +static int event_handler(struct mg_event *event) { + + if (event->type == MG_REQUEST_BEGIN) { + const char *version_header = mg_get_header(event->conn, + "Sec-WebSocket-Version"); + + if (version_header != NULL) { + // Websocket request, process it + if (strcmp(version_header, "13") != 0) { + mg_printf(event->conn, "%s", "HTTP/1.1 426 Upgrade Required\r\n\r\n"); + } else { + static const char *server_ready_message = "server ready"; + char *data; + int bits, len; -// Arguments: -// flags: first byte of websocket frame, see websocket RFC, -// http://tools.ietf.org/html/rfc6455, section 5.2 -// data, data_len: payload data. Mask, if any, is already applied. -static int websocket_data_handler(struct mg_connection *conn, int flags, - char *data, size_t data_len) { - (void) flags; // Unused - mg_websocket_write(conn, WEBSOCKET_OPCODE_TEXT, data, data_len); - - // Returning zero means stoping websocket conversation. - // Close the conversation if client has sent us "exit" string. - return memcmp(data, "exit", 4); + // Handshake, and send initial server message + mg_websocket_handshake(event->conn); + mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT, + server_ready_message, strlen(server_ready_message)); + + while ((len = mg_websocket_read(event->conn, &bits, &data)) > 0) { + // Echo message back to the client + mg_websocket_write(event->conn, WEBSOCKET_OPCODE_TEXT, data, len); + if (memcmp(data, "exit", 4) == 0) { + mg_websocket_write(event->conn, + WEBSOCKET_OPCODE_CONNECTION_CLOSE, "", 0); + break; + } + } + } + return 1; + } + } + + return 0; } int main(void) { struct mg_context *ctx; - struct mg_callbacks callbacks; const char *options[] = { "listening_ports", "8080", "document_root", "websocket_html_root", NULL }; - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.websocket_ready = websocket_ready_handler; - callbacks.websocket_data = websocket_data_handler; - ctx = mg_start(&callbacks, NULL, options); + ctx = mg_start(options, &event_handler, NULL); getchar(); // Wait until user hits "enter" mg_stop(ctx); diff --git a/mongoose.c b/mongoose.c index d9940ab59..7a97dd3fd 100644 --- a/mongoose.c +++ b/mongoose.c @@ -345,7 +345,7 @@ struct ssl_func { #define SSL_CTX_use_certificate_file (* (int (*)(SSL_CTX *, \ const char *, int)) ssl_sw[12].ptr) #define SSL_CTX_set_default_passwd_cb \ - (* (void (*)(SSL_CTX *, mg_callback_t)) ssl_sw[13].ptr) + (* (void (*)(SSL_CTX *, mg_event_handler_t)) ssl_sw[13].ptr) #define SSL_CTX_free (* (void (*)(SSL_CTX *)) ssl_sw[14].ptr) #define SSL_load_error_strings (* (void (*)(void)) ssl_sw[15].ptr) #define SSL_CTX_use_certificate_chain_file \ @@ -490,8 +490,7 @@ struct mg_context { volatile int stop_flag; // Should we stop event loop SSL_CTX *ssl_ctx; // SSL context char *config[NUM_OPTIONS]; // Mongoose configuration parameters - struct mg_callbacks callbacks; // User-defined callback function - mg_callback_t user_callback; // User-defined callback function + mg_event_handler_t event_handler; // User-defined callback function void *user_data; // User-defined data struct socket *listening_sockets; @@ -510,6 +509,7 @@ struct mg_context { struct mg_connection { struct mg_request_info request_info; + struct mg_event event; struct mg_context *ctx; SSL *ssl; // SSL descriptor SSL_CTX *client_ssl_ctx; // SSL context for client connections @@ -517,7 +517,7 @@ struct mg_connection { time_t birth_time; // Time when request was received int64_t num_bytes_sent; // Total bytes sent to client int64_t content_len; // Content-Length header value - int64_t consumed_content; // How many bytes of content have been read + int64_t num_bytes_read; // Bytes read from a remote socket char *buf; // Buffer for received data char *path_info; // PATH_INFO part of the URL int must_close; // 1 if connection must be closed @@ -537,16 +537,25 @@ struct de { struct file file; }; +// Return number of bytes left to read for this connection +static int64_t left_to_read(const struct mg_connection *conn) { + return conn->content_len + conn->request_len - conn->num_bytes_read; +} + const char **mg_get_valid_option_names(void) { return config_options; } -static int call_user(enum mg_event ev, struct mg_connection *conn, void *p) { +static int call_user(int type, struct mg_connection *conn, void *p) { if (conn != NULL && conn->ctx != NULL) { - conn->request_info.user_data = conn->ctx->user_data; + conn->event.user_data = conn->ctx->user_data; + conn->event.type = type; + conn->event.event_param = p; + conn->event.request_info = &conn->request_info; + conn->event.conn = conn; } - return conn == NULL || conn->ctx == NULL || conn->ctx->user_callback == NULL ? - 0 : conn->ctx->user_callback(ev, conn, p); + return conn == NULL || conn->ctx == NULL || conn->ctx->event_handler == NULL ? + 0 : conn->ctx->event_handler(&conn->event); } static FILE *mg_fopen(const char *path, const char *mode) { @@ -614,8 +623,7 @@ static void cry(struct mg_connection *conn, const char *fmt, ...) { // Do not lock when getting the callback value, here and below. // I suppose this is fine, since function cannot disappear in the // same way string option can. - if (conn->ctx->callbacks.log_message == NULL || - conn->ctx->callbacks.log_message(conn, buf) == 0) { + if (call_user(MG_EVENT_LOG, conn, buf) == 0) { fp = conn->ctx == NULL || conn->ctx->config[ERROR_LOG_FILE] == NULL ? NULL : fopen(conn->ctx->config[ERROR_LOG_FILE], "a+"); @@ -646,7 +654,7 @@ static struct mg_connection *fc(struct mg_context *ctx) { static struct mg_connection fake_connection; fake_connection.ctx = ctx; // See https://github.com/cesanta/mongoose/issues/236 - fake_connection.request_info.user_data = ctx->user_data; + fake_connection.event.user_data = ctx->user_data; return &fake_connection; } @@ -1497,6 +1505,7 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf, static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) { int nread; + if (len <= 0) return 0; if (fp != NULL) { // Use read() instead of fread(), because if we're reading from the CGI // pipe, fread() may block until IO buffer is filled up. We cannot afford @@ -1509,6 +1518,9 @@ static int pull(FILE *fp, struct mg_connection *conn, char *buf, int len) { } else { nread = recv(conn->client.sock, buf, (size_t) len, 0); } + if (nread > 0) { + conn->num_bytes_read += nread; + } return conn->ctx->stop_flag ? -1 : nread; } @@ -1524,7 +1536,6 @@ static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) { } else if (n == 0) { break; // No more data to read } else { - conn->consumed_content += n; nread += n; len -= n; } @@ -1533,46 +1544,48 @@ static int pull_all(FILE *fp, struct mg_connection *conn, char *buf, int len) { return nread; } -int mg_read(struct mg_connection *conn, void *buf, size_t len) { - int n, buffered_len, nread; - const char *body; +int mg_read(struct mg_connection *conn, void *buf, int len) { + int n, buffered_len, nread = 0; + int64_t left; // If Content-Length is not set, read until socket is closed - if (conn->consumed_content == 0 && conn->content_len == 0) { + if (conn->content_len <= 0) { conn->content_len = INT64_MAX; conn->must_close = 1; } - nread = 0; - if (conn->consumed_content < conn->content_len) { - // Adjust number of bytes to read. - int64_t to_read = conn->content_len - conn->consumed_content; - if (to_read < (int64_t) len) { - len = (size_t) to_read; - } + // conn->buf body + // |=================|==========|===============| + // |<--request_len-->| | + // |<-----------data_len------->| conn->buf + conn->buf_size - // Return buffered data - body = conn->buf + conn->request_len + conn->consumed_content; - buffered_len = &conn->buf[conn->data_len] - body; - if (buffered_len > 0) { - if (len < (size_t) buffered_len) { - buffered_len = (int) len; - } - memcpy(buf, body, (size_t) buffered_len); - len -= buffered_len; - conn->consumed_content += buffered_len; - nread += buffered_len; - buf = (char *) buf + buffered_len; - } + // First, check for data buffered in conn->buf by read_request(). + if (len > 0 && (buffered_len = conn->data_len - conn->request_len) > 0) { + char *body = conn->buf + conn->request_len; + if (buffered_len > len) buffered_len = len; + if (buffered_len > conn->content_len) buffered_len = conn->content_len; - // We have returned all buffered data. Read new data from the remote socket. - n = pull_all(NULL, conn, (char *) buf, (int) len); + memcpy(buf, body, (size_t) buffered_len); + memmove(body, body + buffered_len, + &conn->buf[conn->data_len] - &body[buffered_len]); + len -= buffered_len; + conn->data_len -= buffered_len; + nread += buffered_len; + } + + // Read data from the socket. + if (len > 0 && (left = left_to_read(conn)) > 0) { + if (left < len) { + len = (int) left; + } + n = pull_all(NULL, conn, (char *) buf + nread, (int) len); nread = n >= 0 ? nread + n : n; } + return nread; } -int mg_write(struct mg_connection *conn, const void *buf, size_t len) { +int mg_write(struct mg_connection *conn, const void *buf, int len) { time_t now; int64_t n, total, allowed; @@ -1864,25 +1877,25 @@ static int convert_uri_to_file_name(struct mg_connection *conn, char *buf, // -1 if request is malformed // 0 if request is not yet fully buffered // >0 actual request length, including last \r\n\r\n -static int get_request_len(const char *buf, int buflen) { - const char *s, *e; - int len = 0; +static int get_request_len(const char *buf, int buf_len) { + int i; - for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++) + for (i = 0; i < buf_len; i++) { // Control characters are not allowed but >=128 is. - if (!isprint(* (const unsigned char *) s) && *s != '\r' && - *s != '\n' && * (const unsigned char *) s < 128) { - len = -1; - break; // [i_a] abort scan as soon as one malformed character is found; - // don't let subsequent \r\n\r\n win us over anyhow - } else if (s[0] == '\n' && s[1] == '\n') { - len = (int) (s - buf) + 2; - } else if (s[0] == '\n' && &s[1] < e && - s[1] == '\r' && s[2] == '\n') { - len = (int) (s - buf) + 3; + // Abort scan as soon as one malformed character is found; + // don't let subsequent \r\n\r\n win us over anyhow + if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' && + buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) { + return -1; + } else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') { + return i + 2; + } else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' && + buf[i + 2] == '\n') { + return i + 3; } + } - return len; + return 0; } // Convert month to the month number. Return -1 on error, or month number @@ -2840,7 +2853,7 @@ static void handle_directory_request(struct mg_connection *conn, static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t offset, int64_t len) { char buf[MG_BUF_LEN]; - int to_read, num_read, num_written; + int num_read, num_written, to_read; // If offset is beyond file boundaries, don't send anything if (offset > 0 && fseeko(fp, offset, SEEK_SET) != 0) { @@ -3054,7 +3067,8 @@ static int read_request(FILE *fp, struct mg_connection *conn, request_len = get_request_len(buf, *nread); while (conn->ctx->stop_flag == 0 && - *nread < bufsiz && request_len == 0 && + *nread < bufsiz && + request_len == 0 && (n = pull(fp, conn, buf + *nread, bufsiz - *nread)) > 0) { *nread += n; assert(*nread <= bufsiz); @@ -3126,7 +3140,8 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl) { const char *expect, *body; char buf[MG_BUF_LEN]; - int to_read, nread, buffered_len, success = 0; + int nread, buffered_len, success = 0; + int64_t left; expect = mg_get_header(conn, "Expect"); assert(fp != NULL); @@ -3140,33 +3155,32 @@ static int forward_body_data(struct mg_connection *conn, FILE *fp, (void) mg_printf(conn, "%s", "HTTP/1.1 100 Continue\r\n\r\n"); } - body = conn->buf + conn->request_len + conn->consumed_content; - buffered_len = &conn->buf[conn->data_len] - body; + buffered_len = conn->data_len - conn->request_len; + body = conn->buf + conn->request_len; assert(buffered_len >= 0); - assert(conn->consumed_content == 0); if (buffered_len > 0) { if ((int64_t) buffered_len > conn->content_len) { buffered_len = (int) conn->content_len; } push(fp, sock, ssl, body, (int64_t) buffered_len); - conn->consumed_content += buffered_len; + memmove((char *) body, body + buffered_len, buffered_len); + conn->data_len -= buffered_len; } nread = 0; - while (conn->consumed_content < conn->content_len) { - to_read = sizeof(buf); - if ((int64_t) to_read > conn->content_len - conn->consumed_content) { - to_read = (int) (conn->content_len - conn->consumed_content); + while (conn->num_bytes_read < conn->content_len + conn->request_len) { + left = left_to_read(conn); + if (left > (int64_t) sizeof(buf)) { + left = sizeof(buf); } - nread = pull(NULL, conn, buf, to_read); + nread = pull(NULL, conn, buf, left); if (nread <= 0 || push(fp, sock, ssl, buf, nread) != nread) { break; } - conn->consumed_content += nread; } - if (conn->consumed_content == conn->content_len) { + if (left_to_read(conn) == 0) { success = nread >= 0; } @@ -3980,7 +3994,7 @@ static void base64_encode(const unsigned char *src, int src_len, char *dst) { dst[j++] = '\0'; } -static void send_websocket_handshake(struct mg_connection *conn) { +void mg_websocket_handshake(struct mg_connection *conn) { static const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; char buf[100], sha[20], b64_sha[sizeof(sha) * 2]; SHA1_CTX sha_ctx; @@ -3998,17 +4012,14 @@ static void send_websocket_handshake(struct mg_connection *conn) { "Sec-WebSocket-Accept: ", b64_sha, "\r\n\r\n"); } -static void read_websocket(struct mg_connection *conn) { +int mg_websocket_read(struct mg_connection *conn, int *bits, char **data) { // Pointer to the beginning of the portion of the incoming websocket message // queue. The original websocket upgrade request is never removed, // so the queue begins after it. unsigned char *buf = (unsigned char *) conn->buf + conn->request_len; - int bits, n, stop = 0; + int n, stop = 0; size_t i, len, mask_len, data_len, header_len, body_len; - // data points to the place where the message is stored when passed to the - // websocket_data callback. This is either mem on the stack, - // or a dynamically allocated buffer if it is too large. - char mem[4 * 1024], mask[4], *data; + char mask[4]; assert(conn->content_len == 0); @@ -4046,28 +4057,28 @@ static void read_websocket(struct mg_connection *conn) { if (header_len > 0) { // Allocate space to hold websocket payload - data = mem; - if (data_len > sizeof(mem) && (data = malloc(data_len)) == NULL) { + if ((*data = malloc(data_len)) == NULL) { // Allocation failed, exit the loop and then close the connection // TODO: notify user about the failure + data_len = 0; break; } // Save mask and bits, otherwise it may be clobbered by memmove below - bits = buf[0]; + *bits = buf[0]; memcpy(mask, buf + header_len - mask_len, mask_len); // Read frame payload into the allocated buffer. assert(body_len >= header_len); if (data_len + header_len > body_len) { len = body_len - header_len; - memcpy(data, buf + header_len, len); + memcpy(*data, buf + header_len, len); // TODO: handle pull error - pull_all(NULL, conn, data + len, data_len - len); + pull_all(NULL, conn, *data + len, data_len - len); conn->data_len = conn->request_len; } else { len = data_len + header_len; - memcpy(data, buf + header_len, data_len); + memcpy(*data, buf + header_len, data_len); memmove(buf, buf + len, body_len - len); conn->data_len -= len; } @@ -4075,21 +4086,17 @@ static void read_websocket(struct mg_connection *conn) { // Apply mask if necessary if (mask_len > 0) { for (i = 0; i < data_len; i++) { - data[i] ^= mask[i % 4]; + (*data)[i] ^= mask[i % 4]; } } // Exit the loop if callback signalled to exit, // or "connection close" opcode received. - if (((bits & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) || - (conn->ctx->callbacks.websocket_data != NULL && - !conn->ctx->callbacks.websocket_data(conn, bits, data, data_len))) { + if ((*bits & 0x0f) == WEBSOCKET_OPCODE_CONNECTION_CLOSE) { + return data_len; stop = 1; } - if (data != mem) { - free(data); - } // Not breaking the loop, process next websocket frame. } else { // Buffering websocket request @@ -4100,6 +4107,8 @@ static void read_websocket(struct mg_connection *conn) { conn->data_len += n; } } + + return 0; } int mg_websocket_write(struct mg_connection* conn, int opcode, @@ -4143,37 +4152,6 @@ int mg_websocket_write(struct mg_connection* conn, int opcode, return retval; } - -static void handle_websocket_request(struct mg_connection *conn) { - const char *version = mg_get_header(conn, "Sec-WebSocket-Version"); - if (version == NULL || strcmp(version, "13") != 0) { - send_http_error(conn, 426, "Upgrade Required", "%s", "Upgrade Required"); - } else if (conn->ctx->callbacks.websocket_connect != NULL && - conn->ctx->callbacks.websocket_connect(conn) != 0) { - // Callback has returned non-zero, do not proceed with handshake - } else { - send_websocket_handshake(conn); - if (conn->ctx->callbacks.websocket_ready != NULL) { - conn->ctx->callbacks.websocket_ready(conn); - } - read_websocket(conn); - } -} - -static int is_websocket_request(const struct mg_connection *conn) { - const char *host, *upgrade, *connection, *version, *key; - - host = mg_get_header(conn, "Host"); - upgrade = mg_get_header(conn, "Upgrade"); - connection = mg_get_header(conn, "Connection"); - key = mg_get_header(conn, "Sec-WebSocket-Key"); - version = mg_get_header(conn, "Sec-WebSocket-Version"); - - return host != NULL && upgrade != NULL && connection != NULL && - key != NULL && version != NULL && - mg_strcasestr(upgrade, "websocket") != NULL && - mg_strcasestr(connection, "Upgrade") != NULL; -} #endif // !USE_WEBSOCKET static int isbyte(int n) { @@ -4231,12 +4209,12 @@ static uint32_t get_remote_ip(const struct mg_connection *conn) { #include "build/mod_lua.c" #endif // USE_LUA -int mg_upload(struct mg_connection *conn, const char *destination_dir) { +FILE *mg_upload(struct mg_connection *conn, const char *destination_dir, + char *path, int path_len) { const char *content_type_header, *boundary_start; - char buf[MG_BUF_LEN], path[PATH_MAX], fname[1024], boundary[100], *s; + char *buf, fname[1024], boundary[100], *s; + int bl, n, i, j, headers_len, boundary_len, eof, buf_len, to_read, len = 0; FILE *fp; - int bl, n, i, j, headers_len, boundary_len, eof, - len = 0, num_uploaded_files = 0; // Request looks like this: // @@ -4260,15 +4238,31 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { (sscanf(boundary_start, "boundary=\"%99[^\"]\"", boundary) == 0 && sscanf(boundary_start, "boundary=%99s", boundary) == 0) || boundary[0] == '\0') { - return num_uploaded_files; + return NULL; } boundary_len = strlen(boundary); bl = boundary_len + 4; // \r\n--<boundary> + + // buf + // conn->buf |<--------- buf_len ------>| + // |=================|==========|===============| + // |<--request_len-->|<--len--->| | + // |<-----------data_len------->| conn->buf + conn->buf_size + + buf = conn->buf + conn->request_len; + buf_len = conn->buf_size - conn->request_len; + len = conn->data_len - conn->request_len; + for (;;) { // Pull in headers - assert(len >= 0 && len <= (int) sizeof(buf)); - while ((n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0) { + assert(len >= 0 && len <= buf_len); + to_read = buf_len - len; + if (to_read > left_to_read(conn)) { + to_read = left_to_read(conn); + } + while (len < buf_len && + (n = pull(NULL, conn, buf + len, to_read)) > 0) { len += n; } if ((headers_len = get_request_len(buf, len)) <= 0) { @@ -4297,10 +4291,12 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { assert(len >= headers_len); memmove(buf, &buf[headers_len], len - headers_len); len -= headers_len; + conn->data_len = conn->request_len + len; // We open the file with exclusive lock held. This guarantee us // there is no other thread can save into the same file simultaneously. fp = NULL; + // Construct destination file name. Do not allow paths to have slashes. if ((s = strrchr(fname, '/')) == NULL && (s = strrchr(fname, '\\')) == NULL) { @@ -4308,7 +4304,7 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { } // Open file in binary mode. TODO: set an exclusive lock. - snprintf(path, sizeof(path), "%s/%s", destination_dir, s); + snprintf(path, path_len, "%s/%s", destination_dir, s); if ((fp = fopen(path, "wb")) == NULL) { break; } @@ -4333,17 +4329,22 @@ int mg_upload(struct mg_connection *conn, const char *destination_dir) { memmove(buf, &buf[len - bl], bl); len = bl; } - } while (!eof && (n = mg_read(conn, buf + len, sizeof(buf) - len)) > 0); - fclose(fp); - if (eof) { - num_uploaded_files++; - if (conn->ctx->callbacks.upload != NULL) { - conn->ctx->callbacks.upload(conn, path); + to_read = buf_len - len; + if (to_read > left_to_read(conn)) { + to_read = left_to_read(conn); } + } while (!eof && (n = pull(NULL, conn, buf + len, to_read)) > 0); + conn->data_len = conn->request_len + len; + + if (eof) { + rewind(fp); + return fp; + } else { + fclose(fp); } } - return num_uploaded_files; + return NULL; } static int is_put_or_delete_request(const struct mg_connection *conn) { @@ -4417,7 +4418,6 @@ static void handle_request(struct mg_connection *conn) { path[0] = '\0'; convert_uri_to_file_name(conn, path, sizeof(path), &file); - DEBUG_TRACE(("%s", ri->uri)); // Perform redirect and auth checks before calling begin_request() handler. // Otherwise, begin_request() would need to perform auth checks and redirects. if (!conn->client.is_ssl && conn->client.ssl_redir && @@ -4426,13 +4426,8 @@ static void handle_request(struct mg_connection *conn) { } else if (!is_put_or_delete_request(conn) && !check_authorization(conn, path)) { send_authorization_request(conn); - } else if (conn->ctx->callbacks.begin_request != NULL && - conn->ctx->callbacks.begin_request(conn)) { + } else if (call_user(MG_REQUEST_BEGIN, conn, (void *) ri->uri) == 1) { // Do nothing, callback has served the request -#if defined(USE_WEBSOCKET) - } else if (is_websocket_request(conn)) { - handle_websocket_request(conn); -#endif } else if (!strcmp(ri->request_method, "OPTIONS")) { handle_options_request(conn); } else if (conn->ctx->config[DOCUMENT_ROOT] == NULL) { @@ -4767,8 +4762,9 @@ static int set_ssl_option(struct mg_context *ctx) { // If PEM file is not specified and the init_ssl callback // is not specified, skip SSL initialization. - if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL && - ctx->callbacks.init_ssl == NULL) { + if ((pem = ctx->config[SSL_CERTIFICATE]) == NULL) { + // MG_INIT_SSL + // ctx->callbacks.init_ssl == NULL) { return 1; } @@ -4790,10 +4786,9 @@ static int set_ssl_option(struct mg_context *ctx) { // If user callback returned non-NULL, that means that user callback has // set up certificate itself. In this case, skip sertificate setting. - if ((ctx->callbacks.init_ssl == NULL || - !ctx->callbacks.init_ssl(ctx->ssl_ctx, ctx->user_data)) && - (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 || - SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0)) { + // MG_INIT_SSL + if (SSL_CTX_use_certificate_file(ctx->ssl_ctx, pem, 1) == 0 || + SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, pem, 1) == 0) { cry(fc(ctx), "%s: cannot open %s: %s", __func__, pem, ssl_error()); return 0; } @@ -4849,7 +4844,7 @@ static int set_acl_option(struct mg_context *ctx) { static void reset_per_request_attributes(struct mg_connection *conn) { conn->path_info = NULL; - conn->num_bytes_sent = conn->consumed_content = 0; + conn->num_bytes_sent = conn->num_bytes_read = 0; conn->status_code = -1; conn->must_close = conn->request_len = conn->throttle = 0; } @@ -5039,9 +5034,7 @@ static void process_new_connection(struct mg_connection *conn) { if (ebuf[0] == '\0') { handle_request(conn); - if (conn->ctx->callbacks.end_request != NULL) { - conn->ctx->callbacks.end_request(conn, conn->status_code); - } + call_user(MG_REQUEST_END, conn, (void *) conn->status_code); log_access(conn); } if (ri->remote_user != NULL) { @@ -5111,12 +5104,9 @@ static void *worker_thread(void *thread_func_param) { conn->buf_size = MAX_REQUEST_SIZE; conn->buf = (char *) (conn + 1); conn->ctx = ctx; - conn->request_info.user_data = ctx->user_data; + conn->event.user_data = ctx->user_data; - if (ctx->callbacks.thread_start != NULL) { - ctx->callbacks.thread_start(&conn->request_info.user_data, - &conn->request_info.conn_data); - } + call_user(MG_THREAD_BEGIN, conn, NULL); // Call consume_socket() even when ctx->stop_flag > 0, to let it signal // sq_empty condvar to wake up the master waiting in produce_socket() @@ -5143,10 +5133,7 @@ static void *worker_thread(void *thread_func_param) { close_connection(conn); } - if (ctx->callbacks.thread_stop != NULL) { - ctx->callbacks.thread_stop(&conn->request_info.user_data, - &conn->request_info.conn_data); - } + call_user(MG_THREAD_END, conn, NULL); free(conn); } @@ -5241,9 +5228,7 @@ static void *master_thread(void *thread_func_param) { pthread_setschedparam(pthread_self(), SCHED_RR, &sched_param); #endif - if (ctx->callbacks.thread_start != NULL) { - ctx->callbacks.thread_start(&ctx->user_data, NULL); - } + call_user(MG_THREAD_BEGIN, fc(ctx), NULL); pfd = (struct pollfd *) calloc(ctx->num_listening_sockets, sizeof(pfd[0])); while (pfd != NULL && ctx->stop_flag == 0) { @@ -5291,9 +5276,7 @@ static void *master_thread(void *thread_func_param) { #endif DEBUG_TRACE(("exiting")); - if (ctx->callbacks.thread_stop != NULL) { - ctx->callbacks.thread_stop(&ctx->user_data, NULL); - } + call_user(MG_THREAD_END, fc(ctx), NULL); // Signal mg_stop() that we're done. // WARNING: This must be the very last thing this @@ -5340,9 +5323,9 @@ void mg_stop(struct mg_context *ctx) { #endif // _WIN32 } -struct mg_context *mg_start(const struct mg_callbacks *callbacks, - void *user_data, - const char **options) { +struct mg_context *mg_start(const char **options, + mg_event_handler_t func, + void *user_data) { struct mg_context *ctx; const char *name, *value, *default_value; int i; @@ -5357,7 +5340,7 @@ struct mg_context *mg_start(const struct mg_callbacks *callbacks, if ((ctx = (struct mg_context *) calloc(1, sizeof(*ctx))) == NULL) { return NULL; } - ctx->callbacks = *callbacks; + ctx->event_handler = func; ctx->user_data = user_data; while (options && (name = *options++) != NULL) { diff --git a/mongoose.h b/mongoose.h index a79e6090a..36aba6a1d 100644 --- a/mongoose.h +++ b/mongoose.h @@ -25,8 +25,8 @@ extern "C" { #endif // __cplusplus -struct mg_context; // Handle for the HTTP service itself -struct mg_connection; // Handle for the individual connection +struct mg_context; // Web server instance +struct mg_connection; // HTTP request descriptor // This structure contains information about the HTTP request. @@ -39,8 +39,6 @@ struct mg_request_info { long remote_ip; // Client's IP address int remote_port; // Client's port int is_ssl; // 1 if SSL-ed, 0 if not - void *user_data; // User data pointer passed to mg_start() - void *conn_data; // Connection-specific, per-thread user data. int num_headers; // Number of HTTP headers struct mg_header { @@ -49,37 +47,32 @@ struct mg_request_info { } http_headers[64]; // Maximum 64 headers }; -enum mg_event { - MG_REQUEST_BEGIN, - MG_REQUEST_END, - MG_HTTP_ERROR, - MG_EVENT_LOG, - MG_THREAD_BEGIN, - MG_THREAD_END -}; -typedef int (*mg_callback_t)(enum mg_event event, - struct mg_connection *conn, - void *data); - -struct mg_callbacks { - int (*begin_request)(struct mg_connection *); - void (*end_request)(const struct mg_connection *, int reply_status_code); - int (*log_message)(const struct mg_connection *, const char *message); - int (*init_ssl)(void *ssl_context, void *user_data); - int (*websocket_connect)(const struct mg_connection *); - void (*websocket_ready)(struct mg_connection *); - int (*websocket_data)(struct mg_connection *, int bits, - char *data, size_t data_len); - void (*upload)(struct mg_connection *, const char *file_name); - void (*thread_start)(void *user_data, void **conn_data); - void (*thread_stop)(void *user_data, void **conn_data); +struct mg_event { + int type; // Event type, possible types are defined below +#define MG_REQUEST_BEGIN 1 // event_param: NULL +#define MG_REQUEST_END 2 // event_param: NULL +#define MG_HTTP_ERROR 3 // event_param: int status_code +#define MG_EVENT_LOG 4 // event_param: const char *message +#define MG_THREAD_BEGIN 5 // event_param: NULL +#define MG_THREAD_END 6 // event_param: NULL + + void *user_data; // User data pointer passed to mg_start() + void *conn_data; // Connection-specific, per-thread user data. + void *event_param; // Event-specific parameter + + struct mg_connection *conn; + struct mg_request_info *request_info; }; -struct mg_context *mg_start(const struct mg_callbacks *callbacks, - void *user_data, - const char **configuration_options); +typedef int (*mg_event_handler_t)(struct mg_event *event); + +struct mg_context *mg_start(const char **configuration_options, + mg_event_handler_t func, void *user_data); void mg_stop(struct mg_context *); +void mg_websocket_handshake(struct mg_connection *); +int mg_websocket_read(struct mg_connection *, int *bits, char **data); + // Get the value of particular configuration parameter. // The value returned is read-only. Mongoose does not allow changing @@ -114,17 +107,12 @@ int mg_modify_passwords_file(const char *passwords_file_name, const char *user, const char *password); - -// Return information associated with the request. -struct mg_request_info *mg_get_request_info(struct mg_connection *); - - // Send data to the client. // Return: // 0 when the connection has been closed // -1 on error // >0 number of bytes written on success -int mg_write(struct mg_connection *, const void *buf, size_t len); +int mg_write(struct mg_connection *, const void *buf, int len); // Send data to a websocket client wrapped in a websocket frame. @@ -184,7 +172,7 @@ void mg_send_file(struct mg_connection *conn, const char *path); // 0 connection has been closed by peer. No more data could be read. // < 0 read error. No more data could be read from the connection. // > 0 number of bytes read into the buffer. -int mg_read(struct mg_connection *, void *buf, size_t len); +int mg_read(struct mg_connection *, void *buf, int len); // Get the value of particular HTTP header. @@ -258,10 +246,13 @@ struct mg_connection *mg_download(const char *host, int port, int use_ssl, void mg_close_connection(struct mg_connection *conn); -// File upload functionality. Each uploaded file gets saved into a temporary -// file and MG_UPLOAD event is sent. -// Return number of uploaded files. -int mg_upload(struct mg_connection *conn, const char *destination_dir); +// Read multipart-form-data POST buffer, save uploaded files into +// destination directory, and return path to the saved filed. +// This function can be called multiple times for the same connection, +// if more then one file is uploaded. +// Return: path to the uploaded file, or NULL if there are no more files. +FILE *mg_upload(struct mg_connection *conn, const char *destination_dir, + char *path, int path_len); // Convenience function -- create detached thread. diff --git a/test/unit_test.c b/test/unit_test.c index e5a77ee46..4da60b33b 100644 --- a/test/unit_test.c +++ b/test/unit_test.c @@ -1,24 +1,4 @@ -// Copyright (c) 2004-2013 Sergey Lyubka -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// -// Unit test for the mongoose web server. Tests embedded API. +// Unit test for the mongoose web server. #define USE_WEBSOCKET #define USE_LUA @@ -65,6 +45,14 @@ static void test_parse_http_message() { char req8[] = " HTTP/1.1 200 OK \n\n"; char req9[] = "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n"; + ASSERT(get_request_len("\r\n", 3) == -1); + ASSERT(get_request_len("\r\n", 2) == 0); + ASSERT(get_request_len("GET", 3) == 0); + ASSERT(get_request_len("\n\n", 2) == 2); + ASSERT(get_request_len("\n\r\n", 3) == 3); + ASSERT(get_request_len("\xdd\xdd", 2) == 0); + ASSERT(get_request_len("\xdd\x03", 2) == -1); + ASSERT(parse_http_message(req9, sizeof(req9), &ri) == sizeof(req9) - 1); ASSERT(ri.num_headers == 1); @@ -72,15 +60,15 @@ static void test_parse_http_message() { ASSERT(strcmp(ri.http_version, "1.1") == 0); ASSERT(ri.num_headers == 0); - 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(req2, sizeof(req2) - 1, &ri) == -1); + ASSERT(parse_http_message(req3, sizeof(req3) - 1, &ri) == 0); + ASSERT(parse_http_message(req6, sizeof(req6) - 1, &ri) == 0); + ASSERT(parse_http_message(req7, sizeof(req7) - 1, &ri) == 0); ASSERT(parse_http_message("", 0, &ri) == 0); - ASSERT(parse_http_message(req8, sizeof(req8), &ri) == sizeof(req8) - 1); + ASSERT(parse_http_message(req8, sizeof(req8) - 1, &ri) == sizeof(req8) - 1); // TODO(lsm): Fix this. Header value may span multiple lines. - ASSERT(parse_http_message(req4, sizeof(req4), &ri) == sizeof(req4) - 1); + ASSERT(parse_http_message(req4, sizeof(req4) - 1, &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); @@ -90,7 +78,7 @@ static void test_parse_http_message() { ASSERT(strcmp(ri.http_headers[2].name, "baz\r\n\r") == 0); ASSERT(strcmp(ri.http_headers[2].value, "") == 0); - ASSERT(parse_http_message(req5, sizeof(req5), &ri) == sizeof(req5) - 1); + ASSERT(parse_http_message(req5, sizeof(req5) - 1, &ri) == sizeof(req5) - 1); ASSERT(strcmp(ri.request_method, "GET") == 0); ASSERT(strcmp(ri.http_version, "1.1") == 0); } @@ -201,79 +189,55 @@ static char *read_file(const char *path, int *size) { } static const char *fetch_data = "hello world!\n"; -static const char *upload_filename = "upload_test.txt"; -static const char *upload_filename2 = "upload_test2.txt"; static const char *upload_ok_message = "upload successful"; -static void upload_cb(struct mg_connection *conn, const char *path) { - const struct mg_request_info *ri = mg_get_request_info(conn); - char *p1, *p2; +static void test_upload(struct mg_connection *conn, const char *orig_path, + const char *uploaded_path) { int len1, len2; + char path[500], *p1, *p2; + FILE *fp; - if (atoi(ri->query_string) == 1) { - ASSERT(!strcmp(path, "./upload_test.txt")); - ASSERT((p1 = read_file("main.c", &len1)) != NULL); - ASSERT((p2 = read_file(path, &len2)) != NULL); - ASSERT(len1 == len2); - ASSERT(memcmp(p1, p2, len1) == 0); - free(p1), free(p2); - remove(upload_filename); - } else if (atoi(ri->query_string) == 2) { - if (!strcmp(path, "./upload_test.txt")) { - ASSERT((p1 = read_file("lua_5.2.1.h", &len1)) != NULL); - ASSERT((p2 = read_file(path, &len2)) != NULL); - ASSERT(len1 == len2); - ASSERT(memcmp(p1, p2, len1) == 0); - free(p1), free(p2); - remove(upload_filename); - } else if (!strcmp(path, "./upload_test2.txt")) { - ASSERT((p1 = read_file("mod_lua.c", &len1)) != NULL); - ASSERT((p2 = read_file(path, &len2)) != NULL); - ASSERT(len1 == len2); - ASSERT(memcmp(p1, p2, len1) == 0); - free(p1), free(p2); - remove(upload_filename); - } else { - ASSERT(0); - } - } else { - ASSERT(0); - } - - mg_printf(conn, "HTTP/1.0 200 OK\r\nContent-Length: %d\r\n\r\n%s", - (int) strlen(upload_ok_message), upload_ok_message); + ASSERT((fp = mg_upload(conn, ".", path, sizeof(path))) != NULL); + fclose(fp); + ASSERT(!strcmp(path, uploaded_path)); + ASSERT((p1 = read_file(orig_path, &len1)) != NULL); + ASSERT((p2 = read_file(path, &len2)) != NULL); + ASSERT(len1 == len2); + ASSERT(memcmp(p1, p2, len1) == 0); + free(p1), free(p2); + remove(path); } -static int begin_request_handler_cb(struct mg_connection *conn) { - const struct mg_request_info *ri = mg_get_request_info(conn); +static int event_handler(struct mg_event *event) { + struct mg_request_info *ri = event->request_info; - if (!strcmp(ri->uri, "/data")) { - mg_printf(conn, "HTTP/1.1 200 OK\r\n" - "Content-Type: text/plain\r\n\r\n" - "%s", fetch_data); - close_connection(conn); - return 1; - } + if (event->type == MG_REQUEST_BEGIN) { + if (!strcmp(ri->uri, "/data")) { + mg_printf(event->conn, "HTTP/1.1 200 OK\r\n" + "Content-Type: text/plain\r\n\r\n" + "%s", fetch_data); + close_connection(event->conn); + return 1; + } - if (!strcmp(ri->uri, "/upload")) { - ASSERT(ri->query_string != NULL); - ASSERT(mg_upload(conn, ".") == atoi(ri->query_string)); - } + if (!strcmp(ri->uri, "/upload")) { + test_upload(event->conn, "lua_5.2.1.h", "./f1.txt"); + test_upload(event->conn, "mod_lua.c", "./f2.txt"); + ASSERT(mg_upload(event->conn, ".", NULL, 0) == NULL); - return 0; -} + mg_printf(event->conn, "HTTP/1.0 200 OK\r\n" + "Content-Type: text/plain\r\n\r\n" + "%s", upload_ok_message); + close_connection(event->conn); + return 1; + } + } else if (event->type == MG_EVENT_LOG) { + printf("%s\n", (const char *) event->event_param); + } -static int log_message_cb(const struct mg_connection *conn, const char *msg) { - (void) conn; - printf("%s\n", msg); return 0; } -static const struct mg_callbacks CALLBACKS = { - &begin_request_handler_cb, NULL, &log_message_cb, NULL, NULL, NULL, NULL, - &upload_cb, NULL, NULL -}; - static const char *OPTIONS[] = { "document_root", ".", "listening_ports", LISTENING_ADDR, @@ -283,7 +247,7 @@ static const char *OPTIONS[] = { }; static char *read_conn(struct mg_connection *conn, int *size) { - char buf[100], *data = NULL; + char buf[MG_BUF_LEN], *data = NULL; int len; *size = 0; while ((len = mg_read(conn, buf, sizeof(buf))) > 0) { @@ -300,7 +264,7 @@ static void test_mg_download(void) { struct mg_connection *conn; struct mg_context *ctx; - ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL); + ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL); ASSERT(mg_download(NULL, port, 0, ebuf, sizeof(ebuf), "%s", "") == NULL); ASSERT(mg_download("localhost", 0, 0, ebuf, sizeof(ebuf), "%s", "") == NULL); @@ -362,33 +326,7 @@ static void test_mg_upload(void) { char ebuf[100], buf[20], *file_data, *file2_data, *post_data; int file_len, file2_len, post_data_len; - ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL); - - // Upload one file - ASSERT((file_data = read_file("main.c", &file_len)) != NULL); - post_data = NULL; - post_data_len = alloc_printf(&post_data, 0, - "--%s\r\n" - "Content-Disposition: form-data; " - "name=\"file\"; " - "filename=\"%s\"\r\n\r\n" - "%.*s\r\n" - "--%s--\r\n", - boundary, upload_filename, - file_len, file_data, boundary); - ASSERT(post_data_len > 0); - ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1, - ebuf, sizeof(ebuf), - "POST /upload?1 HTTP/1.1\r\n" - "Content-Length: %d\r\n" - "Content-Type: multipart/form-data; " - "boundary=%s\r\n\r\n" - "%.*s", post_data_len, boundary, - post_data_len, post_data)) != NULL); - free(file_data), free(post_data); - ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message)); - ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0); - mg_close_connection(conn); + ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL); // Upload two files ASSERT((file_data = read_file("lua_5.2.1.h", &file_len)) != NULL); @@ -411,21 +349,20 @@ static void test_mg_upload(void) { // Final boundary "--%s--\r\n", - boundary, upload_filename, + boundary, "f1.txt", file_len, file_data, - boundary, upload_filename2, + boundary, "f2.txt", file2_len, file2_data, boundary); ASSERT(post_data_len > 0); ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1, ebuf, sizeof(ebuf), - "POST /upload?2 HTTP/1.1\r\n" + "POST /upload HTTP/1.1\r\n" "Content-Length: %d\r\n" "Content-Type: multipart/form-data; " "boundary=%s\r\n\r\n" "%.*s", post_data_len, boundary, post_data_len, post_data)) != NULL); - free(file_data), free(file2_data), free(post_data); ASSERT(mg_read(conn, buf, sizeof(buf)) == (int) strlen(upload_ok_message)); ASSERT(memcmp(buf, upload_ok_message, strlen(upload_ok_message)) == 0); mg_close_connection(conn); @@ -523,7 +460,7 @@ static void test_lua(void) { conn.ctx = &ctx; conn.buf = http_request; - conn.buf_size = conn.data_len = strlen(http_request); + conn.buf_size = conn.data_len = conn.num_bytes_read = strlen(http_request); conn.request_len = parse_http_message(conn.buf, conn.data_len, &conn.request_info); conn.content_len = conn.data_len - conn.request_len; @@ -595,7 +532,7 @@ static void test_request_replies(void) { {NULL, NULL}, }; - ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL); + ASSERT((ctx = mg_start(OPTIONS, event_handler, NULL)) != NULL); for (i = 0; tests[i].request != NULL; i++) { ASSERT((conn = mg_download("localhost", port, 1, ebuf, sizeof(ebuf), "%s", tests[i].request)) != NULL); @@ -604,39 +541,42 @@ static void test_request_replies(void) { mg_stop(ctx); } -static int api_callback(struct mg_connection *conn) { - struct mg_request_info *ri = mg_get_request_info(conn); +static const char *api_uri = "/?a=%20&b=&c=xx"; +static int api_cb(struct mg_event *event) { + struct mg_request_info *ri = event->request_info; char post_data[100] = ""; - ASSERT(ri->user_data == (void *) 123); - ASSERT(ri->num_headers == 2); - ASSERT(strcmp(mg_get_header(conn, "host"), "blah.com") == 0); - ASSERT(mg_read(conn, post_data, sizeof(post_data)) == 3); - ASSERT(memcmp(post_data, "b=1", 3) == 0); - ASSERT(ri->query_string != NULL); - ASSERT(ri->remote_ip > 0); - ASSERT(ri->remote_port > 0); - ASSERT(strcmp(ri->http_version, "1.0") == 0); - - mg_printf(conn, "HTTP/1.0 200 OK\r\n\r\n"); - return 1; + if (event->type == MG_REQUEST_BEGIN) { + ASSERT(event->user_data == (void *) 123); + ASSERT(ri->num_headers == 2); + ASSERT(strcmp(mg_get_header(event->conn, "host"), "blah.com") == 0); + ASSERT(mg_read(event->conn, post_data, sizeof(post_data)) == 3); + ASSERT(memcmp(post_data, "b=1", 3) == 0); + ASSERT(ri->query_string != NULL); + ASSERT(strcmp(ri->query_string, api_uri + 2) == 0); + ASSERT(ri->remote_ip > 0); + ASSERT(ri->remote_port > 0); + ASSERT(strcmp(ri->http_version, "1.0") == 0); + + mg_printf(event->conn, "HTTP/1.0 200 OK\r\n\r\n"); + return 1; + } + + return 0; } static void test_api_calls(void) { char ebuf[100]; - struct mg_callbacks callbacks; struct mg_connection *conn; struct mg_context *ctx; - static const char *request = "POST /?a=%20&b=&c=xx HTTP/1.0\r\n" + static const char *fmt = "POST %s HTTP/1.0\r\n" "Host: blah.com\n" // More spaces before "content-length: 3\r\n" // Lower case header name "\r\nb=123456"; // Content size > content-length, test for mg_read() - memset(&callbacks, 0, sizeof(callbacks)); - callbacks.begin_request = api_callback; - ASSERT((ctx = mg_start(&callbacks, (void *) 123, OPTIONS)) != NULL); + ASSERT((ctx = mg_start(OPTIONS, api_cb, (void *) 123)) != NULL); ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1, - ebuf, sizeof(ebuf), "%s", request)) != NULL); + ebuf, sizeof(ebuf), fmt, api_uri)) != NULL); mg_close_connection(conn); mg_stop(ctx); } @@ -732,8 +672,8 @@ int __cdecl main(void) { test_base64_encode(); test_match_prefix(); test_remove_double_dots(); - test_should_keep_alive(); test_parse_http_message(); + test_should_keep_alive(); test_mg_download(); test_mg_get_var(); test_set_throttle(); -- GitLab