Browse Source

fallback_document_root and fallback_websocket_root initial implementation

Jeremy Friesner 2 years ago
parent
commit
68479acb5f
7 changed files with 85 additions and 34 deletions
  1. 16 0
      docs/UserManual.md
  2. 3 0
      examples/https/civetweb.conf
  3. 51 34
      src/civetweb.c
  4. 3 0
      src/main.c
  5. 8 0
      src/mod_lua.inl
  6. 2 0
      test/page3.ssjs
  7. 2 0
      unittest/private.c

+ 16 - 0
docs/UserManual.md

@@ -296,6 +296,15 @@ The current directory is commonly referenced as dot (`.`).
 It is recommended to use an absolute path for document\_root, in order to
 It is recommended to use an absolute path for document\_root, in order to
 avoid accidentally serving the wrong directory.
 avoid accidentally serving the wrong directory.
 
 
+### fallback\_document\_root `.`
+An optional second directory to check for a file to serve, if the requested
+filename was not found in the document\_root directory.
+This can be useful in cases where an app ships with a read-only HTML content
+directory as part of its install, but you nevertheless want to allow the user
+to customize the served content by placing modified or additional files into
+a writable directory, where they will take precedence over their read-only
+counterparts, on a per-file basis.
+
 ### enable\_auth\_domain\_check `yes`
 ### enable\_auth\_domain\_check `yes`
 When using absolute URLs, verify the host is identical to the authentication\_domain.
 When using absolute URLs, verify the host is identical to the authentication\_domain.
 If enabled, requests to absolute URLs will only be processed
 If enabled, requests to absolute URLs will only be processed
@@ -806,6 +815,11 @@ be used for websockets as well. Since websockets use a different URL scheme
 websockets may also be served from a different directory. By default,
 websockets may also be served from a different directory. By default,
 the document\_root is used as websocket\_root as well.
 the document\_root is used as websocket\_root as well.
 
 
+### fallback\_websocket\_root
+An optional second directory to check for websocket-files that were
+not found in the websocket\_root directory.  (See the documentation for
+fallback\_root for details)
+
 ### websocket\_timeout\_ms
 ### websocket\_timeout\_ms
 Timeout for network read and network write operations for websockets, WS(S),
 Timeout for network read and network write operations for websockets, WS(S),
 in milliseconds. If this value is not set, the value of request\_timeout\_ms
 in milliseconds. If this value is not set, the value of request\_timeout\_ms
@@ -969,6 +983,7 @@ mg (table):
     mg.onerror(msg)             -- error handler, can be overridden
     mg.onerror(msg)             -- error handler, can be overridden
     mg.auth_domain              -- a string that holds the HTTP authentication domain
     mg.auth_domain              -- a string that holds the HTTP authentication domain
     mg.document_root            -- a string that holds the document root directory
     mg.document_root            -- a string that holds the document root directory
+    mg.fallback_document_root   -- a string that holds an optional second document root directory
     mg.lua_type                 -- a string that holds the lua script type
     mg.lua_type                 -- a string that holds the lua script type
     mg.system                   -- a string that holds the operating system name
     mg.system                   -- a string that holds the operating system name
     mg.version                  -- a string that holds CivetWeb version
     mg.version                  -- a string that holds CivetWeb version
@@ -1028,6 +1043,7 @@ If websocket and timers support is enabled then the following is also available:
     mg.set_timeout(fn,delay,[interval])  -- call function after delay at an interval
     mg.set_timeout(fn,delay,[interval])  -- call function after delay at an interval
     mg.set_interval(fn,delay,[interval]) -- call function after delay at an interval
     mg.set_interval(fn,delay,[interval]) -- call function after delay at an interval
     mg.websocket_root                    -- a string that holds the websocket root
     mg.websocket_root                    -- a string that holds the websocket root
+    mg.fallback_websocket_root           -- a string that holds an optional second websocket root
 
 
 connect (function):
 connect (function):
 
 

+ 3 - 0
examples/https/civetweb.conf

@@ -36,6 +36,9 @@ listening_ports 80r,443s
 #document_root tdb
 #document_root tdb
 #authentication_domain mydomain.com
 #authentication_domain mydomain.com
 
 
+# Optional fallback document root, checked for file-paths not found in document_root
+#fallback_document_root tdb_fallback
+
 # Set the a certificate
 # Set the a certificate
 ssl_certificate ../../resources/cert/server.pem
 ssl_certificate ../../resources/cert/server.pem
 
 

+ 51 - 34
src/civetweb.c

@@ -1967,6 +1967,7 @@ enum {
 
 
 	/* Once for each domain */
 	/* Once for each domain */
 	DOCUMENT_ROOT,
 	DOCUMENT_ROOT,
+	FALLBACK_DOCUMENT_ROOT,
 
 
 	ACCESS_LOG_FILE,
 	ACCESS_LOG_FILE,
 	ERROR_LOG_FILE,
 	ERROR_LOG_FILE,
@@ -2048,6 +2049,7 @@ enum {
 
 
 #if defined(USE_WEBSOCKET)
 #if defined(USE_WEBSOCKET)
 	WEBSOCKET_ROOT,
 	WEBSOCKET_ROOT,
+	FALLBACK_WEBSOCKET_ROOT,
 #endif
 #endif
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	LUA_WEBSOCKET_EXTENSIONS,
 	LUA_WEBSOCKET_EXTENSIONS,
@@ -2111,6 +2113,7 @@ static const struct mg_option config_options[] = {
 
 
     /* Once for each domain */
     /* Once for each domain */
     {"document_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
     {"document_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
+    {"fallback_document_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
 
 
     {"access_log_file", MG_CONFIG_TYPE_FILE, NULL},
     {"access_log_file", MG_CONFIG_TYPE_FILE, NULL},
     {"error_log_file", MG_CONFIG_TYPE_FILE, NULL},
     {"error_log_file", MG_CONFIG_TYPE_FILE, NULL},
@@ -2209,6 +2212,7 @@ static const struct mg_option config_options[] = {
 
 
 #if defined(USE_WEBSOCKET)
 #if defined(USE_WEBSOCKET)
     {"websocket_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
     {"websocket_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
+    {"fallback_websocket_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
 #endif
 #endif
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
     {"lua_websocket_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"},
     {"lua_websocket_pattern", MG_CONFIG_TYPE_EXT_PATTERN, "**.lua$"},
@@ -7650,7 +7654,8 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 
 
 #if !defined(NO_FILES)
 #if !defined(NO_FILES)
 	const char *uri = conn->request_info.local_uri;
 	const char *uri = conn->request_info.local_uri;
-	const char *root = conn->dom_ctx->config[DOCUMENT_ROOT];
+	const char *roots[] = {conn->dom_ctx->config[DOCUMENT_ROOT], conn->dom_ctx->config[FALLBACK_DOCUMENT_ROOT], NULL};
+	int fileExists = 0;
 	const char *rewrite;
 	const char *rewrite;
 	struct vec a, b;
 	struct vec a, b;
 	ptrdiff_t match_len;
 	ptrdiff_t match_len;
@@ -7685,7 +7690,8 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 	*is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
 	*is_websocket_request = (conn->protocol_type == PROTOCOL_TYPE_WEBSOCKET);
 #if !defined(NO_FILES)
 #if !defined(NO_FILES)
 	if ((*is_websocket_request) && conn->dom_ctx->config[WEBSOCKET_ROOT]) {
 	if ((*is_websocket_request) && conn->dom_ctx->config[WEBSOCKET_ROOT]) {
-		root = conn->dom_ctx->config[WEBSOCKET_ROOT];
+		roots[0] = conn->dom_ctx->config[WEBSOCKET_ROOT];
+		roots[1] = conn->dom_ctx->config[FALLBACK_WEBSOCKET_ROOT];
 	}
 	}
 #endif /* !NO_FILES */
 #endif /* !NO_FILES */
 #else  /* USE_WEBSOCKET */
 #else  /* USE_WEBSOCKET */
@@ -7702,51 +7708,59 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 
 
 #if !defined(NO_FILES)
 #if !defined(NO_FILES)
 	/* Step 5: If there is no root directory, don't look for files. */
 	/* Step 5: If there is no root directory, don't look for files. */
-	/* Note that root == NULL is a regular use case here. This occurs,
+	/* Note that roots[0] == NULL is a regular use case here. This occurs,
 	 * if all requests are handled by callbacks, so the WEBSOCKET_ROOT
 	 * if all requests are handled by callbacks, so the WEBSOCKET_ROOT
 	 * config is not required. */
 	 * config is not required. */
-	if (root == NULL) {
+	if (roots[0] == NULL) {
 		/* all file related outputs have already been set to 0, just return
 		/* all file related outputs have already been set to 0, just return
 		 */
 		 */
 		return;
 		return;
 	}
 	}
 
 
-	/* Step 6: Determine the local file path from the root path and the
-	 * request uri. */
-	/* Using filename_buf_len - 1 because memmove() for PATH_INFO may shift
-	 * part of the path one byte on the right. */
-	truncated = 0;
-	mg_snprintf(
-	    conn, &truncated, filename, filename_buf_len - 1, "%s%s", root, uri);
+	for (int i=0; roots[i] != NULL; i++)
+	{
+		/* Step 6: Determine the local file path from the root path and the
+		 * request uri. */
+		/* Using filename_buf_len - 1 because memmove() for PATH_INFO may shift
+		 * part of the path one byte on the right. */
+		truncated = 0;
+		mg_snprintf(
+		    conn, &truncated, filename, filename_buf_len - 1, "%s%s", roots[i], uri);
 
 
-	if (truncated) {
-		goto interpret_cleanup;
-	}
+		if (truncated) {
+			goto interpret_cleanup;
+		}
 
 
-	/* Step 7: URI rewriting */
-	rewrite = conn->dom_ctx->config[URL_REWRITE_PATTERN];
-	while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
-		if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
-			mg_snprintf(conn,
-			            &truncated,
-			            filename,
-			            filename_buf_len - 1,
-			            "%.*s%s",
-			            (int)b.len,
-			            b.ptr,
-			            uri + match_len);
-			break;
+		/* Step 7: URI rewriting */
+		rewrite = conn->dom_ctx->config[URL_REWRITE_PATTERN];
+		while ((rewrite = next_option(rewrite, &a, &b)) != NULL) {
+			if ((match_len = match_prefix(a.ptr, a.len, uri)) > 0) {
+				mg_snprintf(conn,
+				            &truncated,
+				            filename,
+				            filename_buf_len - 1,
+				            "%.*s%s",
+				            (int)b.len,
+				            b.ptr,
+				            uri + match_len);
+				break;
+			}
 		}
 		}
-	}
 
 
-	if (truncated) {
-		goto interpret_cleanup;
+		if (truncated) {
+			goto interpret_cleanup;
+		}
+
+		/* Step 8: Check if the file exists at the server */
+		/* Local file path and name, corresponding to requested URI
+		 * is now stored in "filename" variable. */
+		if (mg_stat(conn, filename, filestat)) {
+			fileExists = 1;
+			break;
+		}
 	}
 	}
 
 
-	/* Step 8: Check if the file exists at the server */
-	/* Local file path and name, corresponding to requested URI
-	 * is now stored in "filename" variable. */
-	if (mg_stat(conn, filename, filestat)) {
+	if (fileExists) {
 		int uri_len = (int)strlen(uri);
 		int uri_len = (int)strlen(uri);
 		int is_uri_end_slash = (uri_len > 0) && (uri[uri_len - 1] == '/');
 		int is_uri_end_slash = (uri_len > 0) && (uri[uri_len - 1] == '/');
 
 
@@ -11404,6 +11418,9 @@ prepare_cgi_environment(struct mg_connection *conn,
 	addenv(env, "SERVER_NAME=%s", conn->dom_ctx->config[AUTHENTICATION_DOMAIN]);
 	addenv(env, "SERVER_NAME=%s", conn->dom_ctx->config[AUTHENTICATION_DOMAIN]);
 	addenv(env, "SERVER_ROOT=%s", conn->dom_ctx->config[DOCUMENT_ROOT]);
 	addenv(env, "SERVER_ROOT=%s", conn->dom_ctx->config[DOCUMENT_ROOT]);
 	addenv(env, "DOCUMENT_ROOT=%s", conn->dom_ctx->config[DOCUMENT_ROOT]);
 	addenv(env, "DOCUMENT_ROOT=%s", conn->dom_ctx->config[DOCUMENT_ROOT]);
+	if (conn->dom_ctx->config[FALLBACK_DOCUMENT_ROOT]) {
+		addenv(env, "FALLBACK_DOCUMENT_ROOT=%s", conn->dom_ctx->config[FALLBACK_DOCUMENT_ROOT]);
+	}
 	addenv(env, "SERVER_SOFTWARE=CivetWeb/%s", mg_version());
 	addenv(env, "SERVER_SOFTWARE=CivetWeb/%s", mg_version());
 
 
 	/* Prepare the environment block */
 	/* Prepare the environment block */

+ 3 - 0
src/main.c

@@ -1142,6 +1142,7 @@ sanitize_options(const char *options[] /* server options */,
 	int ok = 1;
 	int ok = 1;
 	/* Make sure we have absolute paths for files and directories */
 	/* Make sure we have absolute paths for files and directories */
 	set_absolute_path(options, "document_root", arg0);
 	set_absolute_path(options, "document_root", arg0);
+	set_absolute_path(options, "fallback_document_root", arg0);
 	set_absolute_path(options, "put_delete_auth_file", arg0);
 	set_absolute_path(options, "put_delete_auth_file", arg0);
 	set_absolute_path(options, "cgi_interpreter", arg0);
 	set_absolute_path(options, "cgi_interpreter", arg0);
 	set_absolute_path(options, "access_log_file", arg0);
 	set_absolute_path(options, "access_log_file", arg0);
@@ -1155,6 +1156,8 @@ sanitize_options(const char *options[] /* server options */,
 	/* Make extra verification for certain options */
 	/* Make extra verification for certain options */
 	if (!verify_existence(options, "document_root", 1))
 	if (!verify_existence(options, "document_root", 1))
 		ok = 0;
 		ok = 0;
+	if (!verify_existence(options, "fallback_document_root", 1))
+		ok = 0;
 	if (!verify_existence(options, "cgi_interpreter", 0))
 	if (!verify_existence(options, "cgi_interpreter", 0))
 		ok = 0;
 		ok = 0;
 	if (!verify_existence(options, "ssl_certificate", 0))
 	if (!verify_existence(options, "ssl_certificate", 0))

+ 8 - 0
src/mod_lua.inl

@@ -2935,6 +2935,9 @@ prepare_lua_environment(struct mg_context *ctx,
 
 
 	if ((conn != NULL) && (conn->dom_ctx != NULL)) {
 	if ((conn != NULL) && (conn->dom_ctx != NULL)) {
 		reg_string(L, "document_root", conn->dom_ctx->config[DOCUMENT_ROOT]);
 		reg_string(L, "document_root", conn->dom_ctx->config[DOCUMENT_ROOT]);
+		if (conn->dom_ctx->config[FALLBACK_DOCUMENT_ROOT]) {
+			reg_string(L, "fallback_document_root", conn->dom_ctx->config[FALLBACK_DOCUMENT_ROOT]);
+		}
 		reg_string(L,
 		reg_string(L,
 		           "auth_domain",
 		           "auth_domain",
 		           conn->dom_ctx->config[AUTHENTICATION_DOMAIN]);
 		           conn->dom_ctx->config[AUTHENTICATION_DOMAIN]);
@@ -2943,6 +2946,11 @@ prepare_lua_environment(struct mg_context *ctx,
 			reg_string(L,
 			reg_string(L,
 			           "websocket_root",
 			           "websocket_root",
 			           conn->dom_ctx->config[WEBSOCKET_ROOT]);
 			           conn->dom_ctx->config[WEBSOCKET_ROOT]);
+			if (conn->dom_ctx->config[FALLBACK_WEBSOCKET_ROOT]) {
+				reg_string(L,
+					"fallback_websocket_root",
+					conn->dom_ctx->config[FALLBACK_WEBSOCKET_ROOT]);
+			}
 		} else {
 		} else {
 			reg_string(L,
 			reg_string(L,
 			           "websocket_root",
 			           "websocket_root",

+ 2 - 0
test/page3.ssjs

@@ -19,6 +19,7 @@ opts = [
 "extra_mime_types",
 "extra_mime_types",
 "listening_ports",
 "listening_ports",
 "document_root",
 "document_root",
+"fallback_document_root",
 "ssl_certificate",
 "ssl_certificate",
 "num_threads",
 "num_threads",
 "run_as_user",
 "run_as_user",
@@ -32,6 +33,7 @@ opts = [
 "lua_server_page_pattern",
 "lua_server_page_pattern",
 "_experimental_duktape_script_pattern",
 "_experimental_duktape_script_pattern",
 "websocket_root",
 "websocket_root",
+"fallback_websocket_root",
 "lua_websocket_pattern",
 "lua_websocket_pattern",
 "access_control_allow_origin",
 "access_control_allow_origin",
 "error_pages",
 "error_pages",

+ 2 - 0
unittest/private.c

@@ -1621,6 +1621,7 @@ START_TEST(test_config_options)
 	ck_assert_str_eq("extra_mime_types", config_options[EXTRA_MIME_TYPES].name);
 	ck_assert_str_eq("extra_mime_types", config_options[EXTRA_MIME_TYPES].name);
 	ck_assert_str_eq("listening_ports", config_options[LISTENING_PORTS].name);
 	ck_assert_str_eq("listening_ports", config_options[LISTENING_PORTS].name);
 	ck_assert_str_eq("document_root", config_options[DOCUMENT_ROOT].name);
 	ck_assert_str_eq("document_root", config_options[DOCUMENT_ROOT].name);
+	ck_assert_str_eq("fallback_document_root", config_options[FALLBACK_DOCUMENT_ROOT].name);
 	ck_assert_str_eq("ssl_certificate", config_options[SSL_CERTIFICATE].name);
 	ck_assert_str_eq("ssl_certificate", config_options[SSL_CERTIFICATE].name);
 	ck_assert_str_eq("ssl_certificate_chain",
 	ck_assert_str_eq("ssl_certificate_chain",
 	                 config_options[SSL_CERTIFICATE_CHAIN].name);
 	                 config_options[SSL_CERTIFICATE_CHAIN].name);
@@ -1672,6 +1673,7 @@ START_TEST(test_config_options)
 #endif
 #endif
 #if defined(USE_WEBSOCKET)
 #if defined(USE_WEBSOCKET)
 	ck_assert_str_eq("websocket_root", config_options[WEBSOCKET_ROOT].name);
 	ck_assert_str_eq("websocket_root", config_options[WEBSOCKET_ROOT].name);
+	ck_assert_str_eq("fallback_websocket_root", config_options[FALLBACK_WEBSOCKET_ROOT].name);
 #endif
 #endif
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	ck_assert_str_eq("lua_websocket_pattern",
 	ck_assert_str_eq("lua_websocket_pattern",