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