Browse Source

Fix issue 180 - make mg_read() handle PUT requests, too. Stop checking for request method in mg_read().

Sergey Lyubka 14 years ago
parent
commit
9bda69f573
6 changed files with 125 additions and 81 deletions
  1. 4 2
      Makefile
  2. 1 1
      examples/chat.c
  3. 52 40
      mongoose.c
  4. 41 35
      test/embed.c
  5. 16 3
      test/test.pl
  6. 11 0
      test/timeout.cgi

+ 4 - 2
Makefile

@@ -14,13 +14,15 @@ all:
 # -DNO_SSL		- disable SSL functionality (-2kb)
 # -DCONFIG_FILE=\"file\" - use `file' as the default config file
 # -DHAVE_STRTOUI64	- use system strtoui64() function for strtoull()
+# -DSSL_LIB=\"libssl.so.<version>\" - use system versioned SSL shared object
+# -DCRYPTO_LIB=\"libcrypto.so.<version>\" - use system versioned CRYPTO so
 
 
 ##########################################################################
 ###                 UNIX build: linux, bsd, mac, rtems
 ##########################################################################
 
-CFLAGS=		-W -Wall -std=c99 -pedantic -O2 -fomit-frame-pointer $(COPT)
+CFLAGS=		-W -Wall -std=c99 -pedantic -O2 $(COPT)
 MAC_SHARED=	-flat_namespace -bundle -undefined suppress
 LINFLAGS=	-ldl -pthread $(CFLAGS)
 LIB=		_$(PROG).so
@@ -126,7 +128,7 @@ do_test:
 	perl test/test.pl $(TEST)
 
 release: clean
-	F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar --exclude \*.hg --exclude \*.svn --exclude \*.swp --exclude \*.nfs\* --exclude win32 -czf x mongoose && mv x mongoose/$$F
+	F=mongoose-`perl -lne '/define\s+MONGOOSE_VERSION\s+"(\S+)"/ and print $$1' mongoose.c`.tgz ; cd .. && tar --exclude \*.hg --exclude \*.svn --exclude \*.swp --exclude \*.nfs\* -czf x mongoose && mv x mongoose/$$F
 
 clean:
 	rm -rf *.o *.core $(PROG) *.obj $(PROG).txt *.dSYM *.tgz

+ 1 - 1
examples/chat.c

@@ -374,7 +374,7 @@ int main(void) {
   srand((unsigned) time(0));
 
   // Setup and start Mongoose
-  ctx = mg_start(&event_handler, options);
+  ctx = mg_start(&event_handler, NULL, options);
   assert(ctx != NULL);
 
   // Wait until enter is pressed, then exit

+ 52 - 40
mongoose.c

@@ -163,7 +163,6 @@ typedef struct DIR {
 #include <sys/wait.h>
 #include <sys/socket.h>
 #include <sys/select.h>
-#include <sys/mman.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <sys/time.h>
@@ -174,15 +173,21 @@ typedef struct DIR {
 #include <pwd.h>
 #include <unistd.h>
 #include <dirent.h>
+#if !defined(NO_SSL_DL) && !defined(NO_SSL)
 #include <dlfcn.h>
+#endif
 #include <pthread.h>
 #if defined(__MACH__)
 #define SSL_LIB   "libssl.dylib"
 #define CRYPTO_LIB  "libcrypto.dylib"
 #else
+#if !defined(SSL_LIB)
 #define SSL_LIB   "libssl.so"
+#endif
+#if !defined(CRYPTO_LIB)
 #define CRYPTO_LIB  "libcrypto.so"
 #endif
+#endif
 #define DIRSEP   '/'
 #define IS_DIRSEP_CHAR(c) ((c) == '/')
 #define O_BINARY  0
@@ -557,7 +562,7 @@ static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
 }
 
 static int lowercase(const char *s) {
-  return tolower(* (unsigned char *) s);
+  return tolower(* (const unsigned char *) s);
 }
 
 static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
@@ -1280,11 +1285,11 @@ static int64_t push(FILE *fp, SOCKET sock, SSL *ssl, const char *buf,
     if (ssl != NULL) {
       n = SSL_write(ssl, buf + sent, k);
     } else if (fp != NULL) {
-      n = fwrite(buf + sent, 1, k, fp);
+      n = fwrite(buf + sent, 1, (size_t)k, fp);
       if (ferror(fp))
         n = -1;
     } else {
-      n = send(sock, buf + sent, k, 0);
+      n = send(sock, buf + sent, (size_t)k, 0);
     }
 
     if (n < 0)
@@ -1304,7 +1309,10 @@ static int pull(FILE *fp, SOCKET sock, SSL *ssl, char *buf, int len) {
   if (ssl != NULL) {
     nread = SSL_read(ssl, buf, len);
   } else if (fp != NULL) {
-    nread = fread(buf, 1, (size_t) len, fp);
+    // 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
+    // to block and must pass all read bytes immediately to the client.
+    nread = read(fileno(fp), buf, (size_t) len);
     if (ferror(fp))
       nread = -1;
   } else {
@@ -1331,7 +1339,7 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len) {
     }
 
     // How many bytes of data we have buffered in the request buffer?
-    buffered = conn->buf + conn->request_len;
+    buffered = conn->buf + conn->request_len + conn->consumed_content;
     buffered_len = conn->data_len - conn->request_len;
     assert(buffered_len >= 0);
 
@@ -1341,7 +1349,7 @@ int mg_read(struct mg_connection *conn, void *buf, size_t len) {
       if (len < (size_t) buffered_len) {
         buffered_len = len;
       }
-      memcpy(buf, buffered, buffered_len);
+      memcpy(buf, buffered, (size_t)buffered_len);
       len -= buffered_len;
       buf = (char *) buf + buffered_len;
       conn->consumed_content += buffered_len;
@@ -1377,7 +1385,7 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) {
   len = mg_vsnprintf(conn, buf, sizeof(buf), fmt, ap);
   va_end(ap);
 
-  return mg_write(conn, buf, len);
+  return mg_write(conn, buf, (size_t)len);
 }
 
 // URL-decode input buffer into destination buffer.
@@ -1393,10 +1401,10 @@ static size_t url_decode(const char *src, size_t src_len, char *dst,
 
   for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
     if (src[i] == '%' &&
-        isxdigit(* (unsigned char *) (src + i + 1)) &&
-        isxdigit(* (unsigned char *) (src + i + 2))) {
-      a = tolower(* (unsigned char *) (src + i + 1));
-      b = tolower(* (unsigned char *) (src + i + 2));
+        isxdigit(* (const unsigned char *) (src + i + 1)) &&
+        isxdigit(* (const unsigned char *) (src + i + 2))) {
+      a = tolower(* (const unsigned char *) (src + i + 1));
+      b = tolower(* (const unsigned char *) (src + i + 2));
       dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
       i += 2;
     } else if (is_form_url_encoded && src[i] == '+') {
@@ -1434,7 +1442,7 @@ int mg_get_var(const char *buf, size_t buf_len, const char *name,
       p += name_len + 1;
 
       // Point s to the end of the value
-      s = (const char *) memchr(p, '&', e - p);
+      s = (const char *) memchr(p, '&', (size_t)(e - p));
       if (s == NULL) {
         s = e;
       }
@@ -1442,7 +1450,7 @@ int mg_get_var(const char *buf, size_t buf_len, const char *name,
 
       // Decode variable into destination buffer
       if ((size_t) (s - p) < dst_len) {
-        len = url_decode(p, s - p, dst, dst_len, 1);
+        len = url_decode(p, (size_t)(s - p), dst, dst_len, 1);
       }
       break;
     }
@@ -1477,7 +1485,7 @@ int mg_get_cookie(const struct mg_connection *conn, const char *cookie_name,
       }
       if ((size_t) (p - s) < dst_size) {
         len = (p - s) + 1;
-        mg_strlcpy(dst, s, len);
+        mg_strlcpy(dst, s, (size_t)len);
       }
       break;
     }
@@ -1531,7 +1539,7 @@ static int sslize(struct mg_connection *conn, int (*func)(SSL *)) {
     func(conn->ssl) == 1;
 }
 
-struct mg_connection *mg_connect(struct mg_connection *conn,
+static struct mg_connection *mg_connect(struct mg_connection *conn,
                                  const char *host, int port, int use_ssl) {
   struct mg_connection *newconn = NULL;
   struct sockaddr_in sin;
@@ -1578,8 +1586,8 @@ static int get_request_len(const char *buf, int buflen) {
   DEBUG_TRACE(("buf: %p, len: %d", buf, buflen));
   for (s = buf, e = s + buflen - 1; len <= 0 && s < e; s++)
     // Control characters are not allowed but >=128 is.
-    if (!isprint(* (unsigned char *) s) && *s != '\r' &&
-        *s != '\n' && * (unsigned char *) s < 128) {
+    if (!isprint(* (const unsigned char *) s) && *s != '\r' &&
+        *s != '\n' && * (const unsigned char *) s < 128) {
       len = -1;
     } else if (s[0] == '\n' && s[1] == '\n') {
       len = (int) (s - buf) + 2;
@@ -1757,7 +1765,7 @@ typedef struct MD5Context {
   unsigned char in[64];
 } MD5_CTX;
 
-#if __BYTE_ORDER == 1234
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == 1234)
 #define byteReverse(buf, len) // Do nothing
 #else
 static void byteReverse(unsigned char *buf, unsigned longs) {
@@ -1961,7 +1969,7 @@ void mg_md5(char *buf, ...) {
 
   va_start(ap, buf);
   while ((p = va_arg(ap, const char *)) != NULL) {
-    MD5Update(&ctx, (unsigned char *) p, (int) strlen(p));
+    MD5Update(&ctx, (const unsigned char *) p, (unsigned) strlen(p));
   }
   va_end(ap);
 
@@ -2052,7 +2060,7 @@ static int parse_auth_header(struct mg_connection *conn, char *buf,
   // Parse authorization header
   for (;;) {
     name = skip(&s, "=");
-    value = skip(&s, " ");
+    value = skip(&s, ", ");  // IE uses commas, FF uses spaces
 
     // Handle commas: Digest username="a", realm="b", ...
     if (value[strlen(value) - 1] == ',') {
@@ -2256,13 +2264,13 @@ static void url_encode(const char *src, char *dst, size_t dst_len) {
   const char *end = dst + dst_len - 1;
 
   for (; *src != '\0' && dst < end; src++, dst++) {
-    if (isalnum(*(unsigned char *) src) ||
-        strchr(dont_escape, * (unsigned char *) src) != NULL) {
+    if (isalnum(*(const unsigned char *) src) ||
+        strchr(dont_escape, * (const unsigned char *) src) != NULL) {
       *dst = *src;
     } else if (dst + 2 < end) {
       dst[0] = '%';
-      dst[1] = hex[(* (unsigned char *) src) >> 4];
-      dst[2] = hex[(* (unsigned char *) src) & 0xf];
+      dst[1] = hex[(* (const unsigned char *) src) >> 4];
+      dst[2] = hex[(* (const unsigned char *) src) & 0xf];
       dst += 2;
     }
   }
@@ -2306,7 +2314,7 @@ static void print_dir_entry(struct de *de) {
 // On windows, __cdecl specification is needed in case if project is built
 // with __stdcall convention. qsort always requires __cdels callback.
 static int WINCDECL compare_dir_entries(const void *p1, const void *p2) {
-  const struct de *a = (struct de *) p1, *b = (struct de *) p2;
+  const struct de *a = (const struct de *) p1, *b = (const struct de *) p2;
   const char *query_string = a->conn->request_info.query_string;
   int cmp_result = 0;
 
@@ -2408,7 +2416,7 @@ static void handle_directory_request(struct mg_connection *conn,
       conn->request_info.uri, "..", "Parent directory", "-", "-");
 
   // Sort and print directory entries
-  qsort(entries, num_entries, sizeof(entries[0]), compare_dir_entries);
+  qsort(entries, (size_t)num_entries, sizeof(entries[0]), compare_dir_entries);
   for (i = 0; i < num_entries; i++) {
     print_dir_entry(&entries[i]);
     free(entries[i].file_name);
@@ -2431,11 +2439,11 @@ static void send_file_data(struct mg_connection *conn, FILE *fp, int64_t len) {
       to_read = (int) len;
 
     // Read from file, exit the loop on error
-    if ((num_read = fread(buf, 1, to_read, fp)) == 0)
+    if ((num_read = fread(buf, 1, (size_t)to_read, fp)) == 0)
       break;
 
     // Send read bytes to the client, exit the loop on error
-    if ((num_written = mg_write(conn, buf, num_read)) != num_read)
+    if ((num_written = mg_write(conn, buf, (size_t)num_read)) != num_read)
       break;
 
     // Both read and were successful, adjust counters
@@ -2918,7 +2926,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog) {
 
   // Send chunk of data that may be read after the headers
   conn->num_bytes_sent += mg_write(conn, buf + headers_len,
-                                   data_len - headers_len);
+                                   (size_t)(data_len - headers_len));
 
   // Read the rest of CGI output and send to the client
   send_file_data(conn, out, INT64_MAX);
@@ -3062,6 +3070,7 @@ static void do_ssi_include(struct mg_connection *conn, const char *ssi,
   }
 }
 
+#if !defined(NO_POPEN)
 static void do_ssi_exec(struct mg_connection *conn, char *tag) {
   char cmd[BUFSIZ];
   FILE *fp;
@@ -3075,6 +3084,7 @@ static void do_ssi_exec(struct mg_connection *conn, char *tag) {
     (void) pclose(fp);
   }
 }
+#endif // !NO_POPEN
 
 static void send_ssi_file(struct mg_connection *conn, const char *path,
                           FILE *fp, int include_level) {
@@ -3097,12 +3107,14 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
       assert(len <= (int) sizeof(buf));
       if (len < 6 || memcmp(buf, "<!--#", 5) != 0) {
         // Not an SSI tag, pass it
-        (void) mg_write(conn, buf, len);
+        (void) mg_write(conn, buf, (size_t)len);
       } else {
         if (!memcmp(buf + 5, "include", 7)) {
           do_ssi_include(conn, path, buf + 12, include_level);
+#if !defined(NO_POPEN)
         } else if (!memcmp(buf + 5, "exec", 4)) {
           do_ssi_exec(conn, buf + 9);
+#endif // !NO_POPEN
         } else {
           cry(conn, "%s: unknown SSI " "command: \"%s\"", path, buf);
         }
@@ -3120,14 +3132,14 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
     } else if (ch == '<') {
       in_ssi_tag = 1;
       if (len > 0) {
-        (void) mg_write(conn, buf, len);
+        (void) mg_write(conn, buf, (size_t)len);
       }
       len = 0;
       buf[len++] = ch & 0xff;
     } else {
       buf[len++] = ch & 0xff;
       if (len == (int) sizeof(buf)) {
-        (void) mg_write(conn, buf, len);
+        (void) mg_write(conn, buf, (size_t)len);
         len = 0;
       }
     }
@@ -3135,7 +3147,7 @@ static void send_ssi_file(struct mg_connection *conn, const char *path,
 
   // Send the rest of buffered data
   if (len > 0) {
-    (void) mg_write(conn, buf, len);
+    (void) mg_write(conn, buf, (size_t)len);
   }
 }
 
@@ -3170,7 +3182,7 @@ static void handle_request(struct mg_connection *conn) {
     * conn->request_info.query_string++ = '\0';
   }
   uri_len = strlen(ri->uri);
-  (void) url_decode(ri->uri, uri_len, ri->uri, uri_len + 1, 0);
+  (void) url_decode(ri->uri, (size_t)uri_len, ri->uri, (size_t)(uri_len + 1), 0);
   remove_double_dots_and_double_slashes(ri->uri);
   convert_uri_to_file_name(conn, ri->uri, path, sizeof(path));
 
@@ -3548,7 +3560,7 @@ static int set_ssl_option(struct mg_context *ctx) {
   // Initialize locking callbacks, needed for thread safety.
   // http://www.openssl.org/support/faq.html#PROG1
   size = sizeof(pthread_mutex_t) * CRYPTO_num_locks();
-  if ((ssl_mutexes = (pthread_mutex_t *) malloc(size)) == NULL) {
+  if ((ssl_mutexes = (pthread_mutex_t *) malloc((size_t)size)) == NULL) {
     cry(fc(ctx), "%s: cannot allocate mutexes: %s", __func__, ssl_error());
     return 0;
   }
@@ -3643,7 +3655,7 @@ static void discard_current_request_from_buffer(struct mg_connection *conn) {
   }
 
   conn->data_len -= conn->request_len + body_len;
-  memmove(conn->buf, conn->buf + conn->request_len + body_len, conn->data_len);
+  memmove(conn->buf, conn->buf + conn->request_len + body_len, (size_t)conn->data_len);
 }
 
 static int parse_url(const char *url, char *host, int *port) {
@@ -3706,7 +3718,7 @@ static void handle_proxy_request(struct mg_connection *conn) {
   // Read data from the target and forward it to the client
   while ((n = pull(NULL, conn->peer->client.sock, conn->peer->ssl,
                    buf, sizeof(buf))) > 0) {
-    if (mg_write(conn, buf, n) != n) {
+    if (mg_write(conn, buf, (size_t)n) != n) {
       break;
     }
   }
@@ -3903,8 +3915,8 @@ static void master_thread(struct mg_context *ctx) {
       add_to_set(sp->sock, &read_set, &max_fd);
     }
 
-    tv.tv_sec = 1;
-    tv.tv_usec = 0;
+    tv.tv_sec = 0;
+    tv.tv_usec = 200 * 1000;
 
     if (select(max_fd + 1, &read_set, NULL, NULL, &tv) < 0) {
 #ifdef _WIN32

+ 41 - 35
test/embed.c

@@ -31,39 +31,45 @@
 #include "mongoose.h"
 
 #if !defined(LISTENING_PORT)
-#define LISTENING_PORT	"23456"
+#define LISTENING_PORT "23456"
 #endif
 
-static const char *standard_reply =	"HTTP/1.1 200 OK\r\n"
+static const char *standard_reply = "HTTP/1.1 200 OK\r\n"
   "Content-Type: text/plain\r\n"
   "Connection: close\r\n\r\n";
 
 static void test_get_var(struct mg_connection *conn,
                          const struct mg_request_info *ri) {
-	char *var, *buf;
+  char *var, *buf;
   size_t buf_len;
   const char *cl;
   int var_len;
 
-	mg_printf(conn, "%s", standard_reply);
+  mg_printf(conn, "%s", standard_reply);
 
   buf_len = 0;
   var = buf = NULL;
   cl = mg_get_header(conn, "Content-Length");
   mg_printf(conn, "cl: %p\n", cl);
-  printf("reqeust method = %s\n", ri->request_method);
-  if ((!strcmp(ri->request_method, "POST") || !strcmp(ri->request_method, "PUT"))
+  if ((!strcmp(ri->request_method, "POST") ||
+       !strcmp(ri->request_method, "PUT"))
       && cl != NULL) {
     buf_len = atoi(cl);
     buf = malloc(buf_len);
-    mg_read(conn, buf, buf_len);
+    /* Read in two pieces, to test continuation */
+    if (buf_len > 2) {
+      mg_read(conn, buf, 2);
+      mg_read(conn, buf + 2, buf_len - 2);
+    } else {
+      mg_read(conn, buf, buf_len);
+    }
   } else if (ri->query_string != NULL) {
     buf_len = strlen(ri->query_string);
     buf = malloc(buf_len + 1);
     strcpy(buf, ri->query_string);
   }
   var = malloc(buf_len + 1);
-	var_len = mg_get_var(buf, buf_len, "my_var", var, buf_len + 1);
+  var_len = mg_get_var(buf, buf_len, "my_var", var, buf_len + 1);
   mg_printf(conn, "Value: [%s]\n", var);
   mg_printf(conn, "Value size: [%d]\n", var_len);
   free(buf);
@@ -72,50 +78,50 @@ static void test_get_var(struct mg_connection *conn,
 
 static void test_get_header(struct mg_connection *conn,
                             const struct mg_request_info *ri) {
-	const char *value;
-  int	i;
+  const char *value;
+  int i;
 
-	mg_printf(conn, "%s", standard_reply);
+  mg_printf(conn, "%s", standard_reply);
   printf("HTTP headers: %d\n", ri->num_headers);
   for (i = 0; i < ri->num_headers; i++) {
     printf("[%s]: [%s]\n", ri->http_headers[i].name, ri->http_headers[i].value);
   }
 
-	value = mg_get_header(conn, "Host");
-	if (value != NULL) {
-		mg_printf(conn, "Value: [%s]", value);
+  value = mg_get_header(conn, "Host");
+  if (value != NULL) {
+    mg_printf(conn, "Value: [%s]", value);
   }
 }
 
 static void test_get_request_info(struct mg_connection *conn,
                                   const struct mg_request_info *ri) {
-	int	i;
+  int i;
 
-	mg_printf(conn, "%s", standard_reply);
+  mg_printf(conn, "%s", standard_reply);
 
-	mg_printf(conn, "Method: [%s]\n", ri->request_method);
-	mg_printf(conn, "URI: [%s]\n", ri->uri);
-	mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
+  mg_printf(conn, "Method: [%s]\n", ri->request_method);
+  mg_printf(conn, "URI: [%s]\n", ri->uri);
+  mg_printf(conn, "HTTP version: [%s]\n", ri->http_version);
 
-	for (i = 0; i < ri->num_headers; i++) {
-		mg_printf(conn, "HTTP header [%s]: [%s]\n",
-			 ri->http_headers[i].name,
-			 ri->http_headers[i].value);
+  for (i = 0; i < ri->num_headers; i++) {
+    mg_printf(conn, "HTTP header [%s]: [%s]\n",
+              ri->http_headers[i].name,
+              ri->http_headers[i].value);
   }
 
-	mg_printf(conn, "Query string: [%s]\n",
-			ri->query_string ? ri->query_string: "");
-	mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
-	mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
-	mg_printf(conn, "Remote user: [%s]\n",
-			ri->remote_user ? ri->remote_user : "");
+  mg_printf(conn, "Query string: [%s]\n",
+            ri->query_string ? ri->query_string: "");
+  mg_printf(conn, "Remote IP: [%lu]\n", ri->remote_ip);
+  mg_printf(conn, "Remote port: [%d]\n", ri->remote_port);
+  mg_printf(conn, "Remote user: [%s]\n",
+            ri->remote_user ? ri->remote_user : "");
 }
 
 static void test_error(struct mg_connection *conn,
                        const struct mg_request_info *ri) {
-	mg_printf(conn, "HTTP/1.1 %d XX\r\n"
-		"Conntection: close\r\n\r\n", ri->status_code);
-	mg_printf(conn, "Error: [%d]", ri->status_code);
+  mg_printf(conn, "HTTP/1.1 %d XX\r\n"
+            "Conntection: close\r\n\r\n", ri->status_code);
+  mg_printf(conn, "Error: [%d]", ri->status_code);
 }
 
 static void test_post(struct mg_connection *conn,
@@ -124,7 +130,7 @@ static void test_post(struct mg_connection *conn,
   char *buf;
   int len;
 
-	mg_printf(conn, "%s", standard_reply);
+  mg_printf(conn, "%s", standard_reply);
   if (strcmp(ri->request_method, "POST") == 0 &&
       (cl = mg_get_header(conn, "Content-Length")) != NULL) {
     len = atoi(cl);
@@ -166,10 +172,10 @@ static void *callback(enum mg_event event,
 }
 
 int main(void) {
-	struct mg_context	*ctx;
+  struct mg_context *ctx;
   const char *options[] = {"listening_ports", LISTENING_PORT, NULL};
 
-	ctx = mg_start(callback, NULL, options);
+  ctx = mg_start(callback, NULL, options);
   pause();
   return 0;
 }

+ 16 - 3
test/test.pl

@@ -50,7 +50,7 @@ sub get_num_of_log_entries {
 
 # Send the request to the 127.0.0.1:$port and return the reply
 sub req {
-  my ($request, $inc) = @_;
+  my ($request, $inc, $timeout) = @_;
   my $sock = IO::Socket::INET->new(Proto=>"tcp",
     PeerAddr=>'127.0.0.1', PeerPort=>$port);
   fail("Cannot connect: $!") unless $sock;
@@ -59,8 +59,12 @@ sub req {
     last unless print $sock $byte;
     select undef, undef, undef, .001 if length($request) < 256;
   }
-  my @lines = <$sock>;
-  my $out = join '', @lines;
+  my ($out, $buf) = ('', '');
+  eval {
+    alarm $timeout if $timeout;
+    $out .= $buf while (sysread($sock, $buf, 1024) > 0);
+    alarm 0 if $timeout;
+  };
   close $sock;
 
   $num_requests += defined($inc) ? $inc : 1;
@@ -129,6 +133,7 @@ sub kill_spawned_child {
 
 unlink @files_to_delete;
 $SIG{PIPE} = 'IGNORE';
+$SIG{ALRM} = sub { die "timeout\n" };
 #local $| =1;
 
 # Make sure we export only symbols that start with "mg_", and keep local
@@ -175,6 +180,14 @@ o("GET /hello.txt HTTP/1.0\n\n", 'Content-Length: 17\s',
 o("GET /%68%65%6c%6c%6f%2e%74%78%74 HTTP/1.0\n\n",
   'HTTP/1.1 200 OK', 'URL-decoding');
 
+# Break CGI reading after 1 second. We must get full output.
+# Since CGI script does sleep, we sleep as well and increase request count
+# manually.
+fail('Slow CGI output forward ') unless
+  req("GET /timeout.cgi HTTP/1.0\r\n\r\n", 0, 1) =~ /Some data/s;
+sleep 3;
+$num_requests++;
+
 # '+' in URI must not be URL-decoded to space
 write_file("$root/a+.txt", '');
 o("GET /a+.txt HTTP/1.0\n\n", 'HTTP/1.1 200 OK', 'URL-decoding, + in URI');

+ 11 - 0
test/timeout.cgi

@@ -0,0 +1,11 @@
+#!/usr/bin/env perl
+
+# Make stdout unbuffered
+$| = 1;
+
+# This script outputs some content, then sleeps for 5 seconds, then exits.
+# Web server should return the content immediately after it is sent,
+# not waiting until the script exits.
+print "Content-Type: text/html\r\n\r\n";
+print "Some data";
+sleep 3;