Browse Source

Merge branch 'master' of https://github.com/civetweb/civetweb

bel2125 5 years ago
parent
commit
c8fa9de6ce
9 changed files with 234 additions and 108 deletions
  1. 1 0
      .gitignore
  2. 27 0
      cmake/check/check_run.patch
  3. 1 1
      docs/Building.md
  4. 2 2
      docs/README.md
  5. 57 12
      src/civetweb.c
  6. 10 12
      src/main.c
  7. 20 11
      unittest/CMakeLists.txt
  8. 38 27
      unittest/private.c
  9. 78 43
      unittest/public_server.c

+ 1 - 0
.gitignore

@@ -10,6 +10,7 @@ out
 *.msi
 *.exe
 *.zip
+*.trace
 [oO]utput
 [tT]esting
 

+ 27 - 0
cmake/check/check_run.patch

@@ -0,0 +1,27 @@
+--- check_run.c.orig	2020-01-12 03:06:59.992434700 +0100
++++ check_run.c.new	2020-01-12 04:40:42.224635800 +0100
+@@ -28,6 +28,8 @@
+ #include <stdarg.h>
+ #include <signal.h>
+ #include <setjmp.h>
++#include <sys/stat.h>
++#include <fcntl.h>
+ 
+ #include "check.h"
+ #include "check_error.h"
+@@ -486,6 +488,15 @@
+         eprintf("Error in call to fork:", __FILE__, __LINE__ - 2);
+     if(pid == 0)
+     {
++char fn[256];
++int fd1, fd2;
++sprintf(fn, "%s.stdout", srunner_log_fname(sr));
++fd1 = open(fn, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
++dup2(fd1, 1);
++sprintf(fn, "%s.stderr", srunner_log_fname(sr));
++fd2 = open(fn, O_WRONLY|O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
++dup2(fd2, 2);
++
+         setpgid(0, 0);
+         group_pid = getpgrp();
+         tr = tcase_run_checked_setup(sr, tc);

+ 1 - 1
docs/Building.md

@@ -149,7 +149,7 @@ make build COPT="-DNDEBUG -DNO_CGI"
 | `DEBUG`                      | build debug version (very noisy)                          |
 |                              |                                                           |
 | `NO_FILES`                   | do not serve files from a directory                       |
-| `NO_FILESYSTEMS`             | comletely disable filesystems usage (requires NO_FILES)   |
+| `NO_FILESYSTEMS`             | completely disable filesystems usage (requires NO_FILES)  |
 | `NO_SSL`                     | disable SSL functionality                                 |
 | `NO_CGI`                     | disable CGI support                                       |
 | `NO_CACHING`                 | disable caching functionality                             |

+ 2 - 2
docs/README.md

@@ -8,8 +8,8 @@ CivetWeb uses an [MIT license](https://github.com/civetweb/civetweb/blob/master/
 
 It can also be used by end users as a stand-alone web server. It is available as single executable, no installation is required.
 
-The current stable version is 1.10 - [release notes](https://github.com/civetweb/civetweb/blob/master/RELEASE_NOTES.md)
-
+The current stable version is 1.11 - [release notes](https://github.com/civetweb/civetweb/blob/master/RELEASE_NOTES.md)
+1.12 is in a release candidate state
 
 End users can download CivetWeb at SourceForge
 [https://sourceforge.net/projects/civetweb/](https://sourceforge.net/projects/civetweb/)

+ 57 - 12
src/civetweb.c

@@ -2692,6 +2692,7 @@ struct mg_context {
 	unsigned int
 	    cfg_worker_threads;      /* The number of configured worker threads. */
 	pthread_t *worker_threadids; /* The worker thread IDs */
+    unsigned long starter_thread_idx; /* thread index which called mg_start */
 
 /* Connection to thread dispatching */
 #if defined(ALTERNATIVE_QUEUE)
@@ -4000,7 +4001,7 @@ mg_get_request_link(const struct mg_connection *conn, char *buf, size_t buflen)
 			int auth_domain_check_enabled =
 			    conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK]
 			    && (!mg_strcasecmp(
-			        conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK], "yes"));
+			           conn->dom_ctx->config[ENABLE_AUTH_DOMAIN_CHECK], "yes"));
 			const char *server_domain =
 			    conn->dom_ctx->config[AUTHENTICATION_DOMAIN];
 
@@ -12227,7 +12228,7 @@ print_dav_dir_entry(struct de *de, void *data)
 	struct mg_connection *conn = (struct mg_connection *)data;
 	if (!de || !conn
 	    || !print_props(
-	        conn, conn->request_info.local_uri, de->file_name, &de->file)) {
+	           conn, conn->request_info.local_uri, de->file_name, &de->file)) {
 		return -1;
 	}
 	return 0;
@@ -13467,6 +13468,8 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 {
 	struct mg_handler_info *tmp_rh, **lastref;
 	size_t urilen = strlen(uri);
+    struct mg_workerTLS tls;
+    int is_tls_set = 0;
 
 	if (handler_type == WEBSOCKET_HANDLER) {
 		DEBUG_ASSERT(handler == NULL);
@@ -13523,6 +13526,23 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 		return;
 	}
 
+    /* Internal callbacks have their contexts set 
+     * if called from non-related thread, context must be set
+     * since internal function assumes it exists.
+     * For an example see how handler_info_wait_unused()
+     * waits for reference to become zero
+     */
+    if (NULL == pthread_getspecific(sTlsKey))
+    {
+        is_tls_set = 1;
+        tls.is_master = -1;
+        tls.thread_idx = phys_ctx->starter_thread_idx;
+#if defined(_WIN32)
+        tls.pthread_cond_helper_mutex = NULL;
+#endif
+        pthread_setspecific(sTlsKey, &tls);
+    }
+
 	mg_lock_context(phys_ctx);
 
 	/* first try to find an existing handler */
@@ -13564,6 +13584,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 					mg_free(tmp_rh);
 				}
 				mg_unlock_context(phys_ctx);
+                if (is_tls_set) {
+                    pthread_setspecific(sTlsKey, NULL);
+                }
 				return;
 			}
 		}
@@ -13574,6 +13597,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 		/* no handler to set, this was a remove request to a non-existing
 		 * handler */
 		mg_unlock_context(phys_ctx);
+        if (is_tls_set) {
+            pthread_setspecific(sTlsKey, NULL);
+        }
 		return;
 	}
 
@@ -13586,6 +13612,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 		mg_cry_ctx_internal(phys_ctx,
 		                    "%s",
 		                    "Cannot create new request handler struct, OOM");
+        if (is_tls_set) {
+            pthread_setspecific(sTlsKey, NULL);
+        }
 		return;
 	}
 	tmp_rh->uri = mg_strdup_ctx(uri, phys_ctx);
@@ -13595,6 +13624,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 		mg_cry_ctx_internal(phys_ctx,
 		                    "%s",
 		                    "Cannot create new request handler struct, OOM");
+        if (is_tls_set) {
+            pthread_setspecific(sTlsKey, NULL);
+        }
 		return;
 	}
 	tmp_rh->uri_len = urilen;
@@ -13604,6 +13636,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 			mg_unlock_context(phys_ctx);
 			mg_free(tmp_rh);
 			mg_cry_ctx_internal(phys_ctx, "%s", "Cannot init refcount mutex");
+            if (is_tls_set) {
+                pthread_setspecific(sTlsKey, NULL);
+            }
 			return;
 		}
 		if (0 != pthread_cond_init(&tmp_rh->refcount_cond, NULL)) {
@@ -13611,6 +13646,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 			pthread_mutex_destroy(&tmp_rh->refcount_mutex);
 			mg_free(tmp_rh);
 			mg_cry_ctx_internal(phys_ctx, "%s", "Cannot init refcount cond");
+            if (is_tls_set) {
+                pthread_setspecific(sTlsKey, NULL);
+            }
 			return;
 		}
 		tmp_rh->refcount = 0;
@@ -13630,6 +13668,9 @@ mg_set_handler_type(struct mg_context *phys_ctx,
 
 	*lastref = tmp_rh;
 	mg_unlock_context(phys_ctx);
+    if (is_tls_set) {
+        pthread_setspecific(sTlsKey, NULL);
+    }
 }
 
 
@@ -14567,7 +14608,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version)
 #if defined(USE_IPV6)
 	} else if (sscanf(vec->ptr, "[%49[^]]]:%u%n", buf, &port, &len) == 2
 	           && mg_inet_pton(
-	               AF_INET6, buf, &so->lsa.sin6, sizeof(so->lsa.sin6))) {
+	                  AF_INET6, buf, &so->lsa.sin6, sizeof(so->lsa.sin6))) {
 		/* IPv6 address, examples: see above */
 		/* so->lsa.sin6.sin6_family = AF_INET6; already set by mg_inet_pton
 		 */
@@ -14623,7 +14664,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version)
 		hostname[hostnlen] = 0;
 
 		if (mg_inet_pton(
-		        AF_INET, vec->ptr, &so->lsa.sin, sizeof(so->lsa.sin))) {
+		        AF_INET, hostname, &so->lsa.sin, sizeof(so->lsa.sin))) {
 			if (sscanf(cb + 1, "%u%n", &port, &len) == 1) {
 				*ip_version = 4;
 				so->lsa.sin.sin_family = AF_INET;
@@ -14635,7 +14676,7 @@ parse_port_string(const struct vec *vec, struct socket *so, int *ip_version)
 			}
 #if defined(USE_IPV6)
 		} else if (mg_inet_pton(AF_INET6,
-		                        vec->ptr,
+		                        hostname,
 		                        &so->lsa.sin6,
 		                        sizeof(so->lsa.sin6))) {
 			if (sscanf(cb + 1, "%u%n", &port, &len) == 1) {
@@ -16112,9 +16153,9 @@ init_ssl_ctx_impl(struct mg_context *phys_ctx,
 	callback_ret = (phys_ctx->callbacks.init_ssl_domain == NULL)
 	                   ? 0
 	                   : (phys_ctx->callbacks.init_ssl_domain(
-	                       dom_ctx->config[AUTHENTICATION_DOMAIN],
-	                       dom_ctx->ssl_ctx,
-	                       phys_ctx->user_data));
+	                         dom_ctx->config[AUTHENTICATION_DOMAIN],
+	                         dom_ctx->ssl_ctx,
+	                         phys_ctx->user_data));
 
 	/* If domain callback returns 0, civetweb sets up the SSL certificate.
 	 * If it returns 1, civetweb assumes the calback already did this.
@@ -16280,9 +16321,9 @@ init_ssl_ctx(struct mg_context *phys_ctx, struct mg_domain_context *dom_ctx)
 	callback_ret = (phys_ctx->callbacks.external_ssl_ctx_domain == NULL)
 	                   ? 0
 	                   : (phys_ctx->callbacks.external_ssl_ctx_domain(
-	                       dom_ctx->config[AUTHENTICATION_DOMAIN],
-	                       &ssl_ctx,
-	                       phys_ctx->user_data));
+	                         dom_ctx->config[AUTHENTICATION_DOMAIN],
+	                         &ssl_ctx,
+	                         phys_ctx->user_data));
 
 	if (callback_ret < 0) {
 		mg_cry_ctx_internal(
@@ -18915,8 +18956,12 @@ static
 	ctx->dd.auth_nonce_mask =
 	    (uint64_t)get_random() ^ (uint64_t)(ptrdiff_t)(options);
 
+    /* Save started thread index to reuse in other external API calls
+     * For the sake of thread synchronization all non-civetweb threads 
+     * can be considered as single external thread */
+    ctx->starter_thread_idx = (unsigned)mg_atomic_inc(&thread_idx_max);
 	tls.is_master = -1; /* Thread calling mg_start */
-	tls.thread_idx = (unsigned)mg_atomic_inc(&thread_idx_max);
+    tls.thread_idx = ctx->starter_thread_idx;
 #if defined(_WIN32)
 	tls.pthread_cond_helper_mutex = NULL;
 #endif

+ 10 - 12
src/main.c

@@ -2072,7 +2072,7 @@ get_password(const char *user,
 
 	ok = (IDOK
 	      == DialogBoxIndirectParam(
-	          NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param));
+	             NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param));
 
 	s_dlg_proc_param.hWnd = NULL;
 	s_dlg_proc_param.guard = 0;
@@ -2578,10 +2578,11 @@ change_password_file()
 		s_dlg_proc_param.name = path;
 		s_dlg_proc_param.fRetry = NULL;
 
-	} while ((IDOK
-	          == DialogBoxIndirectParam(
-	              NULL, dia, NULL, PasswordDlgProc, (LPARAM)&s_dlg_proc_param))
-	         && (!g_exit_flag));
+	} while (
+	    (IDOK
+	     == DialogBoxIndirectParam(
+	            NULL, dia, NULL, PasswordDlgProc, (LPARAM)&s_dlg_proc_param))
+	    && (!g_exit_flag));
 
 	s_dlg_proc_param.hWnd = NULL;
 	s_dlg_proc_param.guard = 0;
@@ -2704,7 +2705,7 @@ show_system_info()
 
 	ok = (IDOK
 	      == DialogBoxIndirectParam(
-	          NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param));
+	             NULL, dia, NULL, InputDlgProc, (LPARAM)&s_dlg_proc_param));
 
 	s_dlg_proc_param.hWnd = NULL;
 	s_dlg_proc_param.guard = 0;
@@ -3041,15 +3042,13 @@ main(int argc, char *argv[])
 @end
 
 @implementation Civetweb
-- (void)openBrowser
-{
+- (void)openBrowser {
 	[[NSWorkspace sharedWorkspace]
 	    openURL:[NSURL URLWithString:[NSString stringWithUTF8String:
 	                                               get_url_to_first_open_port(
 	                                                   g_ctx)]]];
 }
-- (void)editConfig
-{
+- (void)editConfig {
 	create_config_file(g_ctx, g_config_file_name);
 	NSString *path = [NSString stringWithUTF8String:g_config_file_name];
 	if (![[NSWorkspace sharedWorkspace] openFile:path
@@ -3062,8 +3061,7 @@ main(int argc, char *argv[])
 		(void)[alert runModal];
 	}
 }
-- (void)shutDown
-{
+- (void)shutDown {
 	[NSApp terminate:nil];
 }
 @end

+ 20 - 11
unittest/CMakeLists.txt

@@ -7,15 +7,23 @@ endif()
 
 # We use the check unit testing framework for our C unit tests
 include(ExternalProject)
-#if(NOT WIN32)
-#  # Apply the patch to check to fix CMake building on OS X
-#  set(CHECK_PATCH_COMMAND patch
-#     ${CIVETWEB_THIRD_PARTY_DIR}/src/check-unit-test-framework/CMakeLists.txt
-#     ${CMAKE_SOURCE_DIR}/cmake/check/c82fe8888aacfe784476112edd3878256d2e30bc.patch
-#   )
-#else()
-#  set(CHECK_PATCH_COMMAND "")
-#endif()
+if(NOT WIN32)
+  # Apply the patch to check to fix CMake building on OS X
+  set(CHECK_PATCH_COMMAND patch
+     ${CIVETWEB_THIRD_PARTY_DIR}/src/check-unit-test-framework/src/check_run.c
+     ${CMAKE_SOURCE_DIR}/cmake/check/check_run.patch
+   )
+else()
+  set(CHECK_PATCH_COMMAND "")
+endif()
+
+IF (DEFINED ENV{CHECK_URL})
+SET (CHECK_URL $ENV{CHECK_URL})
+ELSE()
+SET (CHECK_URL "https://github.com/civetweb/check/archive/master.zip")
+ENDIF()
+
+
 ExternalProject_Add(check-unit-test-framework
   DEPENDS civetweb-c-library
 
@@ -25,9 +33,9 @@ ExternalProject_Add(check-unit-test-framework
 #  URL_MD5 ${CIVETWEB_CHECK_MD5_HASH}
 
 ## Use a civetweb specific patched version
-URL "https://github.com/civetweb/check/archive/master.zip"
+
+URL ${CHECK_URL}
 DOWNLOAD_NAME "master.zip"
-# <Edit this file to flush AppVeyor build cache and force reloading check>
 
   PREFIX "${CIVETWEB_THIRD_PARTY_DIR}"
   BUILD_IN_SOURCE 1
@@ -248,3 +256,4 @@ if (${CMAKE_BUILD_TYPE} MATCHES "[Cc]overage")
       "  --coverage flag: ${C_FLAG_COVERAGE_MESSAGE}")
   endif()
 endif()
+

+ 38 - 27
unittest/private.c

@@ -602,51 +602,52 @@ START_TEST(test_parse_port_string)
 		const char *port_string;
 		int valid;
 		int ip_family;
+		uint16_t port_num;
 	};
 
 	static struct t_test_parse_port_string testdata[] =
-	{ {"0", 1, 4},
-	  {"1", 1, 4},
-	  {"65535", 1, 4},
-	  {"65536", 0, 0},
+	{ {"0", 1, 4, 0},
+	  {"1", 1, 4, 1},
+	  {"65535", 1, 4, 65535},
+	  {"65536", 0, 0, 0},
 
-	  {"1s", 1, 4},
-	  {"1r", 1, 4},
-	  {"1k", 0, 0},
+	  {"1s", 1, 4, 1},
+	  {"1r", 1, 4, 1},
+	  {"1k", 0, 0, 0},
 
-	  {"1.2.3", 0, 0},
-	  {"1.2.3.", 0, 0},
-	  {"1.2.3.4", 0, 0},
-	  {"1.2.3.4:", 0, 0},
+	  {"1.2.3", 0, 0, 0},
+	  {"1.2.3.", 0, 0, 0},
+	  {"1.2.3.4", 0, 0, 0},
+	  {"1.2.3.4:", 0, 0, 0},
 
-	  {"1.2.3.4:0", 1, 4},
-	  {"1.2.3.4:1", 1, 4},
-	  {"1.2.3.4:65535", 1, 4},
-	  {"1.2.3.4:65536", 0, 0},
+	  {"1.2.3.4:0", 1, 4, 0},
+	  {"1.2.3.4:1", 1, 4, 1},
+	  {"1.2.3.4:65535", 1, 4, 65535},
+	  {"1.2.3.4:65536", 0, 0, 0},
 
-	  {"1.2.3.4:1s", 1, 4},
-	  {"1.2.3.4:1r", 1, 4},
-	  {"1.2.3.4:1k", 0, 0},
+	  {"1.2.3.4:1s", 1, 4, 1},
+	  {"1.2.3.4:1r", 1, 4, 1},
+	  {"1.2.3.4:1k", 0, 0, 0},
 
 #if defined(USE_IPV6)
 	  /* IPv6 config */
-	  {"[::1]:123", 1, 6},
-	  {"[::]:80", 1, 6},
-	  {"[3ffe:2a00:100:7031::1]:900", 1, 6},
+	  {"[::1]:123", 1, 6, 123},
+	  {"[::]:80", 1, 6, 80},
+	  {"[3ffe:2a00:100:7031::1]:900", 1, 6, 900},
 
 	  /* IPv4 + IPv6 config */
-	  {"+80", 1, 4 + 6},
+	  {"+80", 1, 4 + 6, 80},
 #else
 	  /* IPv6 config: invalid if IPv6 is not activated */
-	  {"[::1]:123", 0, 0},
-	  {"[::]:80", 0, 0},
-	  {"[3ffe:2a00:100:7031::1]:900", 0, 0},
+	  {"[::1]:123", 0, 0, 123},
+	  {"[::]:80", 0, 0, 80},
+	  {"[3ffe:2a00:100:7031::1]:900", 0, 0, 900},
 
 	  /* IPv4 + IPv6 config: only IPv4 if IPv6 is not activated */
-	  {"+80", 1, 4},
+	  {"+80", 1, 4, 80},
 #endif
 
-	  {NULL, 0, 0} };
+	  {NULL, 0, 0, 0} };
 
 	struct socket so;
 	struct vec vec;
@@ -673,6 +674,16 @@ START_TEST(test_parse_port_string)
 			             ret,
 			             ip_family);
 		}
+		if (ip_family == 4) {
+			ck_assert_int_eq((int)so.lsa.sin.sin_family,(int)AF_INET);
+		}
+		if (ip_family == 6) {
+			ck_assert_int_eq((int)so.lsa.sin.sin_family,(int)AF_INET6);
+		}
+		if (ret) {
+			/* Test valid strings only */
+			ck_assert_int_eq(htons(so.lsa.sin.sin_port), testdata[i].port_num);
+		}
 	}
 }
 END_TEST

+ 78 - 43
unittest/public_server.c

@@ -232,6 +232,7 @@ START_TEST(test_threading)
 }
 END_TEST
 
+static const char *lastMessage = NULL;
 
 static int
 log_msg_func(const struct mg_connection *conn, const char *message)
@@ -250,6 +251,7 @@ log_msg_func(const struct mg_connection *conn, const char *message)
 
 	printf("LOG_MSG_FUNC: %s\n", message);
 	mark_point();
+	lastMessage = message;
 
 	return 1; /* Return 1 means "already handled" */
 }
@@ -262,6 +264,7 @@ test_log_message(const struct mg_connection *conn, const char *message)
 
 	printf("LOG_MESSAGE: %s\n", message);
 	mark_point();
+	lastMessage = message;
 
 	return 0; /* Return 0 means "not yet handled" */
 }
@@ -270,7 +273,8 @@ test_log_message(const struct mg_connection *conn, const char *message)
 static struct mg_context *
 test_mg_start(const struct mg_callbacks *callbacks,
               void *user_data,
-              const char **configuration_options)
+              const char **configuration_options,
+			  unsigned line)
 {
 	struct mg_context *ctx;
 	struct mg_callbacks cb;
@@ -291,9 +295,15 @@ test_mg_start(const struct mg_callbacks *callbacks,
 	ctx = mg_start(&cb, user_data, configuration_options);
 	mark_point();
 	if (ctx) {
-		/* Give the server some time to start in the test VM */
-		/* Don't need to do this if mg_start failed */
+		/* Give the server some time to start in the test VM. */
+		/* Don't need to do this if mg_start failed. */
 		test_sleep(SLEEP_AFTER_MG_START);
+	} else if (line>0) {
+		/* mg_start is not supposed to fail anywhere, except for
+		 * special tests (for them, line is 0). */
+		ck_abort_msg("mg_start failed in line %u, message %s",
+		             line,
+					 (lastMessage ? lastMessage : "<NULL>"));
 	}
 	mark_point();
 
@@ -302,10 +312,11 @@ test_mg_start(const struct mg_callbacks *callbacks,
 
 
 static void
-test_mg_stop(struct mg_context *ctx)
+test_mg_stop(struct mg_context *ctx, unsigned line)
 {
+	(void)line;
 #ifdef __MACH__
-	/* For unknown reasons, there are sporadic hands
+	/* For unknown reasons, there are sporadic hangs
 	 * for OSX if mark_point is called here */
 	test_sleep(SLEEP_BEFORE_MG_STOP);
 	mg_stop(ctx);
@@ -376,7 +387,7 @@ test_mg_start_stop_http_server_impl(int ipv6, int bound)
 
 	callbacks.log_message = log_msg_func;
 
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, __LINE__);
 
 	ck_assert_str_eq(errmsg, "");
 	ck_assert(ctx != NULL);
@@ -532,7 +543,7 @@ test_mg_start_stop_http_server_impl(int ipv6, int bound)
 	test_sleep(1);
 
 	/* End test */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 	mark_point();
 }
 
@@ -611,7 +622,7 @@ START_TEST(test_mg_start_stop_https_server)
 
 	callbacks.log_message = log_msg_func;
 
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, __LINE__);
 
 	ck_assert_str_eq(errmsg, "");
 	ck_assert(ctx != NULL);
@@ -683,7 +694,7 @@ START_TEST(test_mg_start_stop_https_server)
 
 	test_sleep(1);
 
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 	mark_point();
 #endif
 }
@@ -748,7 +759,7 @@ START_TEST(test_mg_server_and_client_tls)
 
 	callbacks.log_message = log_msg_func;
 
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, __LINE__);
 
 	ck_assert_str_eq(errmsg, "");
 	ck_assert(ctx != NULL);
@@ -814,7 +825,7 @@ START_TEST(test_mg_server_and_client_tls)
 
 	test_sleep(1);
 
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 #endif
 	mark_point();
 }
@@ -875,6 +886,7 @@ request_test_handler(struct mg_connection *conn, void *cbdata)
 	return 1;
 }
 
+static char request_handler2_url_expected[128]={0};
 
 /* Return the same as request_test_handler using new interfaces */
 static int
@@ -885,6 +897,8 @@ request_test_handler2(struct mg_connection *conn, void *cbdata)
 	const struct mg_request_info *ri;
 	struct mg_context *ctx;
 	void *ud, *ud2;
+	int err_ret;
+	char url_buffer[128];
 
 	ctx = mg_get_context(conn);
 	ud = mg_get_user_data(ctx);
@@ -896,6 +910,18 @@ request_test_handler2(struct mg_connection *conn, void *cbdata)
 	ck_assert(ud == &g_ctx);
 	ck_assert(ud == ud2);
 
+	err_ret = mg_get_request_link(NULL, url_buffer, sizeof(url_buffer)); /* param error */
+	ck_assert(err_ret < 0);
+	err_ret = mg_get_request_link(conn, NULL, sizeof(url_buffer)); /* param error */
+	ck_assert(err_ret < 0);
+	err_ret = mg_get_request_link(conn, url_buffer, 0); /* param error */
+	ck_assert(err_ret < 0);
+	err_ret = mg_get_request_link(conn, url_buffer, 5); /* buffer too small */
+	ck_assert(err_ret < 0);
+	err_ret = mg_get_request_link(conn, url_buffer, sizeof(url_buffer));
+	ck_assert(err_ret == 0);
+	ck_assert_str_eq(url_buffer, request_handler2_url_expected);
+
 	mg_send_http_ok(conn, "text/plain", -1);
 
 	for (i = 1; i <= 10; i++) {
@@ -1221,13 +1247,15 @@ START_TEST(test_request_handlers)
 	char cmd_buf[1024];
 	char *cgi_env_opt;
 
+	const char *server_host = "test.domain";
+
 	mark_point();
 
 	memset((void *)OPTIONS, 0, sizeof(OPTIONS));
 	OPTIONS[opt_idx++] = "listening_ports";
 	OPTIONS[opt_idx++] = HTTP_PORT;
 	OPTIONS[opt_idx++] = "authentication_domain";
-	OPTIONS[opt_idx++] = "test.domain";
+	OPTIONS[opt_idx++] = server_host;
 #if !defined(NO_FILES)
 	OPTIONS[opt_idx++] = "document_root";
 	OPTIONS[opt_idx++] = ".";
@@ -1254,7 +1282,7 @@ START_TEST(test_request_handlers)
 	ck_assert(OPTIONS[sizeof(OPTIONS) / sizeof(OPTIONS[0]) - 1] == NULL);
 	ck_assert(OPTIONS[sizeof(OPTIONS) / sizeof(OPTIONS[0]) - 2] == NULL);
 
-	ctx = test_mg_start(NULL, &g_ctx, OPTIONS);
+	ctx = test_mg_start(NULL, &g_ctx, OPTIONS, __LINE__);
 
 	ck_assert(ctx != NULL);
 	g_ctx = ctx;
@@ -1309,6 +1337,7 @@ START_TEST(test_request_handlers)
 		                       (void *)(ptrdiff_t)i);
 	}
 
+	sprintf(request_handler2_url_expected, "http://%s:%u/handler2", server_host, ipv4_port);
 	mg_set_request_handler(ctx, "/handler2", request_test_handler2, NULL);
 
 #ifdef USE_WEBSOCKET
@@ -1488,6 +1517,7 @@ START_TEST(test_request_handlers)
 
 #ifdef _WIN32
 	f = fopen("test.cgi", "wb");
+	ck_assert(f != NULL);
 	cgi_script_content = "#!test.cgi.cmd\r\n";
 	fwrite(cgi_script_content, strlen(cgi_script_content), 1, f);
 	fclose(f);
@@ -1502,6 +1532,7 @@ START_TEST(test_request_handlers)
 	fclose(f);
 #else
 	f = fopen("test.cgi", "w");
+	ck_assert(f != NULL);
 	cgi_script_content = "#!/bin/sh\n\n"
 	                     "printf \"Connection: close\\r\\n\"\n"
 	                     "printf \"Content-Type: text/plain\\r\\n\"\n"
@@ -1551,7 +1582,11 @@ START_TEST(test_request_handlers)
 	sprintf(ebuf, "%scgi_test.cgi", locate_test_exes());
 
 	if (stat(ebuf, &st) != 0) {
+		char cwd[512];
+		getcwd(cwd, sizeof(cwd));
+
 		fprintf(stderr, "\nFile %s not found\n", ebuf);
+		fprintf(stderr, "Working directory is %s\n", cwd);
 		fprintf(stderr,
 		        "This file needs to be compiled manually before "
 		        "starting the test\n");
@@ -1559,8 +1594,8 @@ START_TEST(test_request_handlers)
 		        "e.g. by gcc test/cgi_test.c -o output/cgi_test.cgi\n\n");
 
 		/* Abort test with diagnostic message */
-		ck_abort_msg("Mandatory file %s must be built before starting the test",
-		             ebuf);
+		ck_abort_msg("Mandatory file %s must be built before starting the test (cwd: %s)",
+		             ebuf, cwd);
 	}
 #endif
 
@@ -1824,9 +1859,9 @@ START_TEST(test_request_handlers)
 
 	mg_printf(client_conn,
 	          "GET /handler2 HTTP/1.1\r\n"
-	          "Host: localhost\r\n"
+	          "Host: %s\r\n"
 	          "\r\n",
-	          ipv4_port);
+	          server_host);
 
 	i = mg_get_response(client_conn, ebuf, sizeof(ebuf), 10000);
 	ck_assert_int_ge(i, 0);
@@ -2186,7 +2221,7 @@ START_TEST(test_request_handlers)
 
 	/* Close the server */
 	g_ctx = NULL;
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 	mark_point();
 
 #ifdef USE_WEBSOCKET
@@ -2595,7 +2630,7 @@ START_TEST(test_handle_form)
 	ck_assert(OPTIONS[sizeof(OPTIONS) / sizeof(OPTIONS[0]) - 1] == NULL);
 	ck_assert(OPTIONS[sizeof(OPTIONS) / sizeof(OPTIONS[0]) - 2] == NULL);
 
-	ctx = test_mg_start(NULL, &g_ctx, OPTIONS);
+	ctx = test_mg_start(NULL, &g_ctx, OPTIONS, __LINE__);
 
 	ck_assert(ctx != NULL);
 	g_ctx = ctx;
@@ -3119,7 +3154,7 @@ START_TEST(test_handle_form)
 
 	/* Close the server */
 	g_ctx = NULL;
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 	mark_point();
 }
 END_TEST
@@ -3166,7 +3201,7 @@ START_TEST(test_http_auth)
 	mark_point();
 
 	/* Start with default options */
-	ctx = test_mg_start(NULL, NULL, OPTIONS);
+	ctx = test_mg_start(NULL, NULL, OPTIONS, __LINE__);
 
 	ck_assert(ctx != NULL);
 	domain = mg_get_option(ctx, "authentication_domain");
@@ -3456,7 +3491,7 @@ START_TEST(test_http_auth)
 
 
 	/* Stop the server and clean up */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 	(void)remove(test_file);
 	(void)remove(passwd_file);
 	(void)remove("put_delete_auth_file.csv");
@@ -3493,7 +3528,7 @@ START_TEST(test_keep_alive)
 
 	mark_point();
 
-	ctx = test_mg_start(NULL, NULL, OPTIONS);
+	ctx = test_mg_start(NULL, NULL, OPTIONS, __LINE__);
 
 	ck_assert(ctx != NULL);
 
@@ -3540,7 +3575,7 @@ START_TEST(test_keep_alive)
 	 * (will only work if NO_FILES is not set). */
 
 	/* Stop the server and clean up */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	mark_point();
 }
@@ -3587,7 +3622,7 @@ START_TEST(test_error_handling)
 
 	/* test with unknown option */
 	memset(errmsg, 0, sizeof(errmsg));
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, 0);
 
 	/* Details of errmsg may vary, but it may not be empty */
 	ck_assert_str_ne(errmsg, "");
@@ -3603,7 +3638,7 @@ START_TEST(test_error_handling)
 
 	/* Test with bad num_thread option */
 	memset(errmsg, 0, sizeof(errmsg));
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, 0);
 
 	/* Details of errmsg may vary, but it may not be empty */
 	ck_assert_str_ne(errmsg, "");
@@ -3619,7 +3654,7 @@ START_TEST(test_error_handling)
 
 	/* Test with bad num_thread option */
 	memset(errmsg, 0, sizeof(errmsg));
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, 0);
 
 	/* Details of errmsg may vary, but it may not be empty */
 	ck_assert_str_ne(errmsg, "");
@@ -3642,7 +3677,7 @@ START_TEST(test_error_handling)
 	/* This time start the server with a valid configuration */
 	sprintf(bad_thread_num, "%i", 1);
 	memset(errmsg, 0, sizeof(errmsg));
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, __LINE__);
 
 	ck_assert_str_eq(errmsg, "");
 	ck_assert(ctx != NULL);
@@ -3807,7 +3842,7 @@ START_TEST(test_error_handling)
 
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 
 	/* HTTP 1.1 GET request - must not work, since server is already stopped  */
@@ -3858,7 +3893,7 @@ START_TEST(test_error_log_file)
 #endif
 	OPTIONS[opt_cnt] = NULL;
 
-	ctx = test_mg_start(NULL, 0, OPTIONS);
+	ctx = test_mg_start(NULL, 0, OPTIONS, __LINE__);
 	ck_assert(ctx != NULL);
 
 	/* Remove log files (they may exist from previous incomplete runs of
@@ -3894,7 +3929,7 @@ START_TEST(test_error_log_file)
 	mg_close_connection(client);
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 
 	/* Check access.log */
@@ -3930,7 +3965,7 @@ START_TEST(test_error_log_file)
 	ck_assert_str_eq(OPTIONS[0], "listening_ports");
 	OPTIONS[1] = "bad !"; /* no r or s in string */
 
-	ctx = test_mg_start(NULL, 0, OPTIONS);
+	ctx = test_mg_start(NULL, 0, OPTIONS, 0);
 	ck_assert_msg(
 	    ctx == NULL,
 	    "Should not be able to start server with bad port configuration");
@@ -4066,7 +4101,7 @@ START_TEST(test_throttle)
 	callbacks.begin_request = test_throttle_begin_request;
 	callbacks.end_request = test_throttle_end_request;
 
-	ctx = test_mg_start(&callbacks, 0, OPTIONS);
+	ctx = test_mg_start(&callbacks, 0, OPTIONS, __LINE__);
 	ck_assert(ctx != NULL);
 
 	/* connect client */
@@ -4122,7 +4157,7 @@ START_TEST(test_throttle)
 	mg_close_connection(client);
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	mark_point();
 }
@@ -4236,7 +4271,7 @@ START_TEST(test_large_file)
 	callbacks.begin_request = test_large_file_begin_request;
 	callbacks.log_message = log_msg_func;
 
-	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS);
+	ctx = test_mg_start(&callbacks, (void *)errmsg, OPTIONS, __LINE__);
 	ck_assert_str_eq(errmsg, "");
 	ck_assert(ctx != NULL);
 
@@ -4306,7 +4341,7 @@ START_TEST(test_large_file)
 #endif
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	mark_point();
 }
@@ -4480,7 +4515,7 @@ START_TEST(test_mg_store_body)
 	test_sleep(5);
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	/* Un-initialize the library */
 	mg_exit_library();
@@ -4558,7 +4593,7 @@ START_TEST(test_file_in_memory)
 	memset(&callbacks, 0, sizeof(callbacks));
 	callbacks.open_file = test_file_in_memory_open_file;
 
-	ctx = test_mg_start(&callbacks, 0, OPTIONS);
+	ctx = test_mg_start(&callbacks, 0, OPTIONS, __LINE__);
 	ck_assert(ctx != NULL);
 
 	/* connect client */
@@ -4608,7 +4643,7 @@ START_TEST(test_file_in_memory)
 	mg_close_connection(client);
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	/* Free test data */
 	free(file_in_mem_data);
@@ -4810,7 +4845,7 @@ START_TEST(test_minimal_http_server_callback)
 	mg_init_library(0);
 
 	/* Start the server */
-	ctx = test_mg_start(NULL, 0, NULL);
+	ctx = test_mg_start(NULL, 0, NULL, __LINE__);
 	ck_assert(ctx != NULL);
 
 	/* Add some handler */
@@ -4834,7 +4869,7 @@ START_TEST(test_minimal_http_server_callback)
 
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	/* Un-initialize the library */
 	mg_exit_library();
@@ -4903,7 +4938,7 @@ START_TEST(test_minimal_https_server_callback)
 
 
 	/* Start the server */
-	ctx = test_mg_start(NULL, 0, OPTIONS);
+	ctx = test_mg_start(NULL, 0, OPTIONS, __LINE__);
 	ck_assert(ctx != NULL);
 
 	/* Add some handler */
@@ -4927,7 +4962,7 @@ START_TEST(test_minimal_https_server_callback)
 
 
 	/* Stop the server */
-	test_mg_stop(ctx);
+	test_mg_stop(ctx, __LINE__);
 
 	/* Un-initialize the library */
 	mg_exit_library();