Browse Source

Merge remote-tracking branch 'upstream/master'

Jordan 11 năm trước cách đây
mục cha
commit
5e7362ea5a
5 tập tin đã thay đổi với 187 bổ sung43 xóa
  1. 4 1
      include/CivetServer.h
  2. 2 0
      include/civetweb.h
  3. 8 2
      src/civetweb.c
  4. 21 2
      src/main.c
  5. 152 38
      test/unit_test.c

+ 4 - 1
include/CivetServer.h

@@ -159,7 +159,10 @@ public:
      * occurance value is a zero-based index of a particular key name.  This
      * should not be confused with the index over all of the keys.  Note that this
      * function assumes that parameters are sent as text in http query string
-     * format, which is the default for web forms.
+     * format, which is the default for web forms. This function will work for
+     * html forms with method="GET" and method="POST" attributes. In other cases,
+     * you may use a getParam version that directly takes the data instead of the
+     * connection as a first argument.
      *
      * @param conn - parameters are read from the data sent through this connection
      * @param name - the key to search for

+ 2 - 0
include/civetweb.h

@@ -62,6 +62,8 @@ struct mg_request_info {
     const char *remote_user;    /* Authenticated user, or NULL if no auth
                                    used */
     long remote_ip;             /* Client's IP address */
+    long long content_length;   /* Length (in bytes) of the request body,
+                                   can be -1 if no length was given. */
     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() */

+ 8 - 2
src/civetweb.c

@@ -2299,8 +2299,11 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len)
 
         /* We have returned all buffered data. Read new data from the remote
            socket. */
-        n = pull_all(NULL, conn, (char *) buf, (int64_t) len);
-        nread = n >= 0 ? nread + n : n;
+        if ((n = pull_all(NULL, conn, (char *) buf, (int64_t) len)) >= 0) {
+            nread += n;
+        } else {
+            nread = (nread > 0 ? nread : n);
+        }
     }
     return nread;
 }
@@ -6378,6 +6381,7 @@ static void reset_per_request_attributes(struct mg_connection *conn)
     conn->num_bytes_sent = conn->consumed_content = 0;
     conn->status_code = -1;
     conn->must_close = conn->request_len = conn->throttle = 0;
+    conn->request_info.content_length = -1;
 }
 
 static void close_socket_gracefully(struct mg_connection *conn)
@@ -6540,6 +6544,8 @@ static int getreq(struct mg_connection *conn, char *ebuf, size_t ebuf_len)
         if ((cl = get_header(&conn->request_info, "Content-Length")) != NULL) {
             /* Request/response has content length set */
             conn->content_len = strtoll(cl, NULL, 10);
+            /* Publish the content length back to the request info. */
+            conn->request_info.content_length = conn->content_len;
         } else if (!mg_strcasecmp(conn->request_info.request_method, "POST") ||
                    !mg_strcasecmp(conn->request_info.request_method, "PUT")) {
             /* POST or PUT request without content length set */

+ 21 - 2
src/main.c

@@ -273,17 +273,32 @@ static int set_option(char **options, const char *name, const char *value)
             /* unknown option */
             return 0;
         case CONFIG_TYPE_NUMBER:
+            /* integer number > 0, e.g. number of threads */
             if (atol(value)<1) {
                 /* invalid number */
                 return 0;
             }
             break;
+        case CONFIG_TYPE_STRING:
+            /* any text */
+            break;
         case CONFIG_TYPE_BOOLEAN:
+            /* boolean value, yes or no */
             if ((0!=strcmp(value,"yes")) && (0!=strcmp(value,"no"))) {
                 /* invalid boolean */
                 return 0;
             }
             break;
+        case CONFIG_TYPE_FILE:
+        case CONFIG_TYPE_DIRECTORY:
+            /* TODO: check this option when it is set, instead of calling verify_existence later */
+            break;
+        case CONFIG_TYPE_EXT_PATTERN:
+            /* list of file extentions */
+            break;
+        default:
+            die("Unknown option type - option %s", name);
+            break;
     }
 
     for (i = 0; i < MAX_OPTIONS; i++) {
@@ -300,10 +315,14 @@ static int set_option(char **options, const char *name, const char *value)
     }
 
     if (i == MAX_OPTIONS) {
-        die("%s", "Too many options specified");
+        die("Too many options specified");
+    }
+
+    if (options[2*i] == NULL || options[2*i + 1] == NULL) {
+        die("Out of memory");
     }
 
-    /* TODO: check if this option is defined and the correct data type, return 1 (OK) or 0 (false) */
+    /* option set correctly */
     return 1;
 }
 

+ 152 - 38
test/unit_test.c

@@ -49,22 +49,14 @@ static int s_failed_tests = 0;
     if (!(expr)) FAIL(#expr, __LINE__); \
 } while (0)
 
-/* TODO(bel):
-#define HTTP_PORT "56789"
-#define HTTPS_PORT "56790"
-#define HTTP_PORT2 "56791"
-#define LISTENING_ADDR          \
-    "127.0.0.1:" HTTP_PORT "r"    \
-    ",127.0.0.1:" HTTPS_PORT "s"  \
-    ",127.0.0.1:" HTTP_PORT2
-*/
 #define HTTP_PORT "8080"
 #ifdef NO_SSL
 #define HTTPS_PORT HTTP_PORT
 #define LISTENING_ADDR "127.0.0.1:" HTTP_PORT
 #else
-#define HTTPS_PORT "443"
-#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT ",127.0.0.1:" HTTPS_PORT "s"
+#define HTTP_REDIRECT_PORT "8088"
+#define HTTPS_PORT "8443"
+#define LISTENING_ADDR "127.0.0.1:" HTTP_PORT ",127.0.0.1:" HTTP_REDIRECT_PORT "r" ",127.0.0.1:" HTTPS_PORT "s"
 #endif
 
 static void test_parse_http_message() {
@@ -214,7 +206,8 @@ static char *read_file(const char *path, int *size) {
     return data;
 }
 
-static const char *fetch_data = "hello world!\n";
+static long fetch_data_size = 1024*1024;
+static char *fetch_data;
 static const char *inmemory_file_data = "hi there";
 static const char *upload_filename = "upload_test.txt";
 static const char *upload_filename2 = "upload_test2.txt";
@@ -270,12 +263,65 @@ static void upload_cb(struct mg_connection *conn, const char *path) {
 }
 
 static int begin_request_handler_cb(struct mg_connection *conn) {
+
     const struct mg_request_info *ri = mg_get_request_info(conn);
+    int req_len = (int)(ri->content_length);
+    const char * s_req_len = mg_get_header(conn, "Content-Length");
+    char *data;
+    long to_write, write_now;
+    int bytes_read, bytes_written;
+
+    ASSERT( ((req_len == -1) && (s_req_len==NULL)) || ((s_req_len != NULL) && (req_len = atol(s_req_len))) );
 
-    if (!strcmp(ri->uri, "/data")) {
+    if (!strncmp(ri->uri, "/data/", 6)) {
+        if (!strcmp(ri->uri+6, "all")) {
+            to_write = fetch_data_size;
+        } else {
+            to_write = atol(ri->uri+6);
+        }
         mg_printf(conn, "HTTP/1.1 200 OK\r\n"
-            "Content-Type: text/plain\r\n\r\n"
-            "%s", fetch_data);
+            "Connection: close\r\n"
+            "Content-Length: %li\r\n"
+            "Content-Type: text/plain\r\n\r\n",
+            to_write);
+        while (to_write>0) {
+            write_now = to_write > fetch_data_size ? fetch_data_size : to_write;
+            bytes_written = mg_write(conn, fetch_data, write_now);
+            ASSERT(bytes_written == write_now);
+            to_write -= bytes_written;
+        }
+        close_connection(conn);
+        return 1;
+    }
+
+    if (!strcmp(ri->uri, "/content_length")) {
+        if (req_len>0) {
+            data = mg_malloc(req_len);
+            assert(data != NULL);
+            bytes_read = mg_read(conn, data, req_len);
+            ASSERT(bytes_read == req_len);
+
+            mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                "Connection: close\r\n"
+                "Content-Length: %d\r\n"   /* The official definition */
+                "Content-Type: text/plain\r\n\r\n",
+                bytes_read);
+            mg_write(conn, data, bytes_read);
+
+            mg_free(data);
+        } else {
+            data = mg_malloc(1024);
+            assert(data != NULL);
+            bytes_read = mg_read(conn, data, 1024);
+
+            mg_printf(conn, "HTTP/1.1 200 OK\r\n"
+                "Connection: close\r\n"
+                "Content-Type: text/plain\r\n\r\n"
+                );
+            mg_write(conn, data, bytes_read);
+
+            mg_free(data);
+        }
         close_connection(conn);
         return 1;
     }
@@ -338,8 +384,12 @@ static char *read_conn(struct mg_connection *conn, int *size) {
 }
 
 static void test_mg_download(int use_ssl) {
+
+    const char *test_data = "123456789A123456789B";
+
     char *p1, *p2, ebuf[100];
-    int len1, len2, port;
+    const char *h;
+    int i, len1, len2, port;
     struct mg_connection *conn;
     struct mg_context *ctx;
     if (use_ssl) port = atoi(HTTPS_PORT); else port = atoi(HTTP_PORT);
@@ -369,6 +419,7 @@ static void test_mg_download(int use_ssl) {
     /* Fetch unit_test.c, should succeed */
     ASSERT((conn = mg_download("localhost", port, use_ssl, ebuf, sizeof(ebuf), "%s",
         "GET /unit_test.c HTTP/1.0\r\n\r\n")) != NULL);
+    ASSERT(&conn->request_info == mg_get_request_info(conn));
     ASSERT(!strcmp(conn->request_info.uri, "200"));
     ASSERT((p1 = read_conn(conn, &len1)) != NULL);
     ASSERT((p2 = read_file("unit_test.c", &len2)) != NULL);
@@ -388,34 +439,88 @@ static void test_mg_download(int use_ssl) {
 
     /* Fetch in-memory data with no Content-Length, should succeed. */
     ASSERT((conn = mg_download("localhost", port, use_ssl, ebuf, sizeof(ebuf), "%s",
-        "GET /data HTTP/1.1\r\n\r\n")) != NULL);
+        "GET /data/all HTTP/1.1\r\n\r\n")) != NULL);
+    ASSERT(conn->request_info.content_length == fetch_data_size);
     ASSERT((p1 = read_conn(conn, &len1)) != NULL);
-    ASSERT(len1 == (int) strlen(fetch_data));
+    ASSERT(len1 == (int) fetch_data_size);
     ASSERT(memcmp(p1, fetch_data, len1) == 0);
     mg_free(p1);
     mg_close_connection(conn);
 
-    /* Test SSL redirect, IP address */
-    /* TODO(bel):
-    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);
+    /* Fetch in-memory data with no Content-Length, should succeed. */
+    for (i=0; i<=1024*1024*8; i += (i<2 ? 1 : i)) {
+        ASSERT((conn = mg_download("localhost", port, use_ssl, ebuf, sizeof(ebuf),
+            "GET /data/%i HTTP/1.1\r\n\r\n", i)) != NULL);
+        ASSERT(conn->request_info.content_length == i);
+        len1 = -1;
+        p1 = read_conn(conn, &len1);
+        if (i==0) {
+            ASSERT(len1 == 0);
+            ASSERT(p1 == 0);
+        } else if (i<=fetch_data_size) {
+            ASSERT(p1 != NULL);
+            ASSERT(len1 == i);
+            ASSERT(memcmp(p1, fetch_data, len1) == 0);
+        } else {
+            ASSERT(p1 != NULL);
+            ASSERT(len1 == i);
+            ASSERT(memcmp(p1, fetch_data, fetch_data_size) == 0);
+        }
+
+        mg_free(p1);
+        mg_close_connection(conn);
+    }
+
+    /* Fetch data with Content-Length, should succeed and return the defined length. */
+    ASSERT((conn = mg_download("localhost", port, use_ssl, ebuf, sizeof(ebuf),
+        "POST /content_length HTTP/1.1\r\nContent-Length: %u\r\n\r\n%s",
+        strlen(test_data), test_data)) != NULL);
+    h = mg_get_header(conn, "Content-Length");
+    ASSERT((h != NULL) && (atoi(h)==strlen(test_data)));
+    ASSERT((p1 = read_conn(conn, &len1)) != NULL);
+    ASSERT(len1 == (int) strlen(test_data));
+    ASSERT(conn->request_info.content_length == strlen(test_data));
+    ASSERT(memcmp(p1, test_data, len1) == 0);
+    ASSERT(strcmp(conn->request_info.request_method, "HTTP/1.1") == 0);
+    ASSERT(strcmp(conn->request_info.uri, "200") == 0);
+    ASSERT(strcmp(conn->request_info.http_version, "OK") == 0);
+    mg_free(p1);
     mg_close_connection(conn);
-    */
 
-    /* Test SSL redirect, Host: */
-    /* TODO(bel):
-    ASSERT((conn = mg_download("localhost", atoi(HTTP_PORT), 0,
+    /* Fetch data without Content-Length, should succeed. It has no content-length header field,
+       but still returns the correct amount of data. It is much slower, since it needs to wait
+       for the connection shutdown. */
+    ASSERT((conn = mg_download("localhost", port, use_ssl, ebuf, sizeof(ebuf),
+        "POST /content_length HTTP/1.1\r\n\r\n%s", test_data)) != NULL);
+    h = mg_get_header(conn, "Content-Length");
+    ASSERT(h == NULL);
+    ASSERT(conn->request_info.content_length == -1);
+    ASSERT((p1 = read_conn(conn, &len1)) != NULL);
+    ASSERT(len1 == (int) strlen(test_data));
+    ASSERT(memcmp(p1, test_data, len1) == 0);
+    mg_free(p1);
+    mg_close_connection(conn);
+
+    /* Test non existent */
+    ASSERT((conn = mg_download("localhost", port, use_ssl,
         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);
+        "GET /non_exist HTTP/1.1\r\n\r\n")) != NULL);
+    ASSERT(strcmp(conn->request_info.request_method, "HTTP/1.1") == 0);
+    ASSERT(strcmp(conn->request_info.uri, "404") == 0);
+    ASSERT(strcmp(conn->request_info.http_version, "Not Found") == 0);
     mg_close_connection(conn);
-    */
+
+    if (use_ssl) {
+        /* Test SSL redirect */
+        ASSERT((conn = mg_download("localhost", atoi(HTTP_REDIRECT_PORT), 0,
+            ebuf, sizeof(ebuf), "%s",
+            "GET /data/4711 HTTP/1.1\r\n\r\n")) != NULL);
+        ASSERT(strcmp(conn->request_info.uri, "302") == 0);
+        h = mg_get_header(conn, "Location");
+        ASSERT(h != NULL);
+        ASSERT(strcmp(h, "https://127.0.0.1:" HTTPS_PORT "/data/4711") == 0);
+        mg_close_connection(conn);
+    }
 
     mg_stop(ctx);
 }
@@ -674,7 +779,7 @@ static void test_request_replies(void) {
     }
     mg_stop(ctx);
 
-/* TODO(bel):
+#ifndef NO_SSL
     ASSERT((ctx = mg_start(&CALLBACKS, NULL, OPTIONS)) != NULL);
     for (i = 0; tests[i].request != NULL; i++) {
         ASSERT((conn = mg_download("localhost", atoi(HTTPS_PORT), 1, ebuf, sizeof(ebuf), "%s",
@@ -682,7 +787,7 @@ static void test_request_replies(void) {
         mg_close_connection(conn);
     }
     mg_stop(ctx);
-*/
+#endif
 }
 
 static int api_callback(struct mg_connection *conn) {
@@ -852,6 +957,7 @@ int __cdecl main(void) {
     char buffer[512];
     FILE * f;
     struct mg_context *ctx;
+    int i;
 
     /* print headline */
     printf("Civetweb %s unit test\n", mg_version());
@@ -899,6 +1005,12 @@ int __cdecl main(void) {
     mg_sleep(1000);
     mg_stop(ctx);
 
+    /* create test data */
+    fetch_data = (char *) mg_malloc(fetch_data_size);
+    for (i=0; i<fetch_data_size; i++) {
+        fetch_data[i] = 'a' + i%10;
+    }
+
     /* tests with network access */
     test_mg_download(0);
 #ifndef NO_SSL
@@ -912,7 +1024,9 @@ int __cdecl main(void) {
     test_lua();
 #endif
 
-    printf("TOTAL TESTS: %d, FAILED: %d\n", s_total_tests, s_failed_tests);
+    /* test completed */
+    mg_free(fetch_data);
 
+    printf("TOTAL TESTS: %d, FAILED: %d\n", s_total_tests, s_failed_tests);
     return s_failed_tests == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 }