Forráskód Böngészése

Merge pull request #1162 from jfriesne/jaf-fallback-document-root

Issue #1159: fallback_document_root and fallback_websocket_root
bel2125 2 éve
szülő
commit
5dcd7e25e4
7 módosított fájl, 85 hozzáadás és 34 törlés
  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
 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`
 When using absolute URLs, verify the host is identical to the authentication\_domain.
 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,
 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
 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
@@ -969,6 +983,7 @@ mg (table):
     mg.onerror(msg)             -- error handler, can be overridden
     mg.auth_domain              -- a string that holds the HTTP authentication domain
     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.system                   -- a string that holds the operating system name
     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_interval(fn,delay,[interval]) -- call function after delay at an interval
     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):
 

+ 3 - 0
examples/https/civetweb.conf

@@ -36,6 +36,9 @@ listening_ports 80r,443s
 #document_root tdb
 #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
 ssl_certificate ../../resources/cert/server.pem
 

+ 51 - 34
src/civetweb.c

@@ -1967,6 +1967,7 @@ enum {
 
 	/* Once for each domain */
 	DOCUMENT_ROOT,
+	FALLBACK_DOCUMENT_ROOT,
 
 	ACCESS_LOG_FILE,
 	ERROR_LOG_FILE,
@@ -2048,6 +2049,7 @@ enum {
 
 #if defined(USE_WEBSOCKET)
 	WEBSOCKET_ROOT,
+	FALLBACK_WEBSOCKET_ROOT,
 #endif
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	LUA_WEBSOCKET_EXTENSIONS,
@@ -2111,6 +2113,7 @@ static const struct mg_option config_options[] = {
 
     /* Once for each domain */
     {"document_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
+    {"fallback_document_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
 
     {"access_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)
     {"websocket_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
+    {"fallback_websocket_root", MG_CONFIG_TYPE_DIRECTORY, NULL},
 #endif
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
     {"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)
 	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;
 	struct vec a, b;
 	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);
 #if !defined(NO_FILES)
 	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 */
 #else  /* USE_WEBSOCKET */
@@ -7702,51 +7708,59 @@ interpret_uri(struct mg_connection *conn, /* in/out: request (must be valid) */
 
 #if !defined(NO_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
 	 * config is not required. */
-	if (root == NULL) {
+	if (roots[0] == NULL) {
 		/* all file related outputs have already been set to 0, just 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 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_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());
 
 	/* Prepare the environment block */

+ 3 - 0
src/main.c

@@ -1142,6 +1142,7 @@ sanitize_options(const char *options[] /* server options */,
 	int ok = 1;
 	/* Make sure we have absolute paths for files and directories */
 	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, "cgi_interpreter", 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 */
 	if (!verify_existence(options, "document_root", 1))
 		ok = 0;
+	if (!verify_existence(options, "fallback_document_root", 1))
+		ok = 0;
 	if (!verify_existence(options, "cgi_interpreter", 0))
 		ok = 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)) {
 		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,
 		           "auth_domain",
 		           conn->dom_ctx->config[AUTHENTICATION_DOMAIN]);
@@ -2943,6 +2946,11 @@ prepare_lua_environment(struct mg_context *ctx,
 			reg_string(L,
 			           "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 {
 			reg_string(L,
 			           "websocket_root",

+ 2 - 0
test/page3.ssjs

@@ -19,6 +19,7 @@ opts = [
 "extra_mime_types",
 "listening_ports",
 "document_root",
+"fallback_document_root",
 "ssl_certificate",
 "num_threads",
 "run_as_user",
@@ -32,6 +33,7 @@ opts = [
 "lua_server_page_pattern",
 "_experimental_duktape_script_pattern",
 "websocket_root",
+"fallback_websocket_root",
 "lua_websocket_pattern",
 "access_control_allow_origin",
 "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("listening_ports", config_options[LISTENING_PORTS].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_chain",
 	                 config_options[SSL_CERTIFICATE_CHAIN].name);
@@ -1672,6 +1673,7 @@ START_TEST(test_config_options)
 #endif
 #if defined(USE_WEBSOCKET)
 	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
 #if defined(USE_LUA) && defined(USE_WEBSOCKET)
 	ck_assert_str_eq("lua_websocket_pattern",