Selaa lähdekoodia

Allow CGI environment to grow dynamically

See #187
bel2125 10 vuotta sitten
vanhempi
commit
e063d2e98c
1 muutettua tiedostoa jossa 141 lisäystä ja 103 poistoa
  1. 141 103
      src/civetweb.c

+ 141 - 103
src/civetweb.c

@@ -481,7 +481,7 @@ static void *pthread_getspecific(pthread_key_t key) { return TlsGetValue(key); }
 
 #define PASSWORDS_FILE_NAME ".htpasswd"
 #define CGI_ENVIRONMENT_SIZE (4096)
-#define MAX_CGI_ENVIR_VARS (64)
+#define MAX_CGI_ENVIR_VARS (256)
 #define MG_BUF_LEN (8192)
 
 #ifndef MAX_REQUEST_SIZE
@@ -6054,6 +6054,7 @@ forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl)
 }
 #endif
 
+
 #if !defined(NO_CGI)
 /* This structure helps to create an environment for the spawned CGI program.
  * Environment is an array of "VARIABLE=VALUE\0" ASCIIZ strings,
@@ -6063,219 +6064,253 @@ forward_body_data(struct mg_connection *conn, FILE *fp, SOCKET sock, SSL *ssl)
  * marked by two '\0' characters.
  * We satisfy both worlds: we create an envp array (which is vars), all
  * entries are actually pointers inside buf. */
-struct cgi_env_block {
+struct cgi_environment {
 	struct mg_connection *conn;
-	char buf[CGI_ENVIRONMENT_SIZE]; /* Environment buffer */
-	size_t len;                     /* Space taken */
-	char *vars[MAX_CGI_ENVIR_VARS]; /* char **envp */
-	unsigned int nvars;             /* Number of variables */
+	/* Data block */
+	char *buf;      /* Environment buffer */
+	size_t buflen;  /* Space available in buf */
+	size_t bufused; /* Space taken in buf */
+	                /* Index block */
+	char **var;     /* char **envp */
+	size_t varlen;  /* Number of variables available in var */
+	size_t varused; /* Number of variables stored in var */
 };
 
-static char *addenv(struct cgi_env_block *block,
-                    PRINTF_FORMAT_STRING(const char *fmt),
-                    ...) PRINTF_ARGS(2, 3);
+
+static void addenv(struct cgi_environment *env,
+                   PRINTF_FORMAT_STRING(const char *fmt),
+                   ...) PRINTF_ARGS(2, 3);
+
 
 /* Append VARIABLE=VALUE\0 string to the buffer, and add a respective
- * pointer into the vars array. */
-static char *addenv(struct cgi_env_block *block, const char *fmt, ...)
+ * pointer into the vars array. Assumes block != NULL and fmt != NULL. */
+static void addenv(struct cgi_environment *env, const char *fmt, ...)
 {
 	size_t n, space;
 	int truncated;
 	char *added;
 	va_list ap;
 
-	if (block == NULL || fmt == NULL) {
-		return NULL;
-	}
 
 	/* Calculate how much space is left in the buffer */
-	space = (sizeof(block->buf) - block->len) - 2;
-	if (space <= 0) {
-		return NULL;
-	}
+	space = (env->buflen - env->bufused);
 
-	/* Make a pointer to the free space int the buffer */
-	added = block->buf + block->len;
+	/* Calculate an estimate for the required space */
+	n = strlen(fmt) + 2 + 128;
 
-	/* Copy VARIABLE=VALUE\0 string into the free space */
-	va_start(ap, fmt);
-	mg_vsnprintf(block->conn, &truncated, added, (size_t)space, fmt, ap);
-	va_end(ap);
+	do {
+		if (space <= n) {
+			/* Allocate new buffer */
+			n = env->buflen + CGI_ENVIRONMENT_SIZE;
+			added = (char *)mg_realloc(env->buf, n);
+			if (!added) {
+				/* Out of memory */
+				mg_cry(env->conn,
+				       "%s: Cannot allocate memory for CGI variable [%s]",
+				       __func__,
+				       fmt);
+				return;
+			}
+			env->buf = added;
+			env->buflen = n;
+			space = (env->buflen - env->bufused);
+		}
 
-	/* Do not add truncated strings to the environment */
-	if (truncated) {
-		added[0] = 0;
-		return NULL;
-	}
+		/* Make a pointer to the free space int the buffer */
+		added = env->buf + env->bufused;
+
+		/* Copy VARIABLE=VALUE\0 string into the free space */
+		va_start(ap, fmt);
+		mg_vsnprintf(env->conn, &truncated, added, (size_t)space, fmt, ap);
+		va_end(ap);
 
-	/* Number of bytes added to the environment */
+		/* Do not add truncated strings to the environment */
+		if (truncated) {
+			/* Reallocate the buffer */
+			space = 0;
+			n = 1;
+		}
+	} while (truncated);
+
+	/* Calculate number of bytes added to the environment */
 	n = strlen(added) + 1;
+	env->bufused += n;
 
-	/* Make sure we do not overflow buffer and the envp array */
-	if (n < space && block->nvars + 2 < ARRAY_SIZE(block->vars)) {
-		/* Append a pointer to the added string into the envp array */
-		block->vars[block->nvars++] = added;
-		/* Bump up used length counter. Include \0 terminator */
-		block->len += n;
-	} else {
-		mg_cry(block->conn,
-		       "%s: CGI env buffer truncated for [%s]",
-		       __func__,
-		       fmt);
+	/* Now update the variable index */
+	space = (env->varlen - env->varused);
+	if (space < 2) {
+		mg_cry(
+		    env->conn, "%s: Cannot register CGI variable [%s]", __func__, fmt);
+		return;
 	}
 
-	return added;
+	/* Append a pointer to the added string into the envp array */
+	env->var[env->varused] = added;
+	env->varused++;
 }
 
+
 static void prepare_cgi_environment(struct mg_connection *conn,
                                     const char *prog,
-                                    struct cgi_env_block *blk)
+                                    struct cgi_environment *env)
 {
 	const char *s;
 	struct vec var_vec;
-	char *p, src_addr[IP_ADDR_STR_LEN];
-	int i;
+	char *p, src_addr[IP_ADDR_STR_LEN], http_var_name[128];
+	int i, truncated;
 
-	if (conn == NULL || prog == NULL || blk == NULL) {
+	if (conn == NULL || prog == NULL || env == NULL) {
 		return;
 	}
 
-	blk->len = blk->nvars = 0;
-	blk->conn = conn;
-	sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+	env->conn = conn;
+	env->buflen = CGI_ENVIRONMENT_SIZE;
+	env->bufused = 0;
+	env->buf = (char *)mg_malloc(env->buflen);
+	env->varlen = MAX_CGI_ENVIR_VARS;
+	env->varused = 0;
+	env->var = (char **)mg_malloc(env->buflen * sizeof(char *));
 
-	addenv(blk, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
-	addenv(blk, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
-	addenv(blk, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
-	addenv(blk, "SERVER_SOFTWARE=%s/%s", "Civetweb", mg_version());
+	addenv(env, "SERVER_NAME=%s", conn->ctx->config[AUTHENTICATION_DOMAIN]);
+	addenv(env, "SERVER_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
+	addenv(env, "DOCUMENT_ROOT=%s", conn->ctx->config[DOCUMENT_ROOT]);
+	addenv(env, "SERVER_SOFTWARE=%s/%s", "Civetweb", mg_version());
 
 	/* Prepare the environment block */
-	addenv(blk, "%s", "GATEWAY_INTERFACE=CGI/1.1");
-	addenv(blk, "%s", "SERVER_PROTOCOL=HTTP/1.1");
-	addenv(blk, "%s", "REDIRECT_STATUS=200"); /* For PHP */
+	addenv(env, "%s", "GATEWAY_INTERFACE=CGI/1.1");
+	addenv(env, "%s", "SERVER_PROTOCOL=HTTP/1.1");
+	addenv(env, "%s", "REDIRECT_STATUS=200"); /* For PHP */
 
 #if defined(USE_IPV6)
 	if (conn->client.lsa.sa.sa_family == AF_INET6) {
-		addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin6.sin6_port));
+		addenv(env, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin6.sin6_port));
 	} else
 #endif
 	{
-		addenv(blk, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port));
+		addenv(env, "SERVER_PORT=%d", ntohs(conn->client.lsa.sin.sin_port));
 	}
 
-	addenv(blk, "REQUEST_METHOD=%s", conn->request_info.request_method);
-	addenv(blk, "REMOTE_ADDR=%s", src_addr);
-	addenv(blk, "REMOTE_PORT=%d", conn->request_info.remote_port);
-	addenv(blk, "REQUEST_URI=%s", conn->request_info.uri);
+	sockaddr_to_string(src_addr, sizeof(src_addr), &conn->client.rsa);
+	addenv(env, "REMOTE_ADDR=%s", src_addr);
+
+	addenv(env, "REQUEST_METHOD=%s", conn->request_info.request_method);
+	addenv(env, "REMOTE_PORT=%d", conn->request_info.remote_port);
+	addenv(env, "REQUEST_URI=%s", conn->request_info.uri);
 
 	/* SCRIPT_NAME */
-	addenv(blk,
+	addenv(env,
 	       "SCRIPT_NAME=%.*s",
 	       (int)strlen(conn->request_info.uri) -
 	           ((conn->path_info == NULL) ? 0 : (int)strlen(conn->path_info)),
 	       conn->request_info.uri);
 
-	addenv(blk, "SCRIPT_FILENAME=%s", prog);
+	addenv(env, "SCRIPT_FILENAME=%s", prog);
 	if (conn->path_info == NULL) {
-		addenv(blk, "PATH_TRANSLATED=%s", conn->ctx->config[DOCUMENT_ROOT]);
+		addenv(env, "PATH_TRANSLATED=%s", conn->ctx->config[DOCUMENT_ROOT]);
 	} else {
-		addenv(blk,
+		addenv(env,
 		       "PATH_TRANSLATED=%s%s",
 		       conn->ctx->config[DOCUMENT_ROOT],
 		       conn->path_info);
 	}
 
-	addenv(blk, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
+	addenv(env, "HTTPS=%s", conn->ssl == NULL ? "off" : "on");
 
 	if ((s = mg_get_header(conn, "Content-Type")) != NULL) {
-		addenv(blk, "CONTENT_TYPE=%s", s);
+		addenv(env, "CONTENT_TYPE=%s", s);
 	}
 	if (conn->request_info.query_string != NULL) {
-		addenv(blk, "QUERY_STRING=%s", conn->request_info.query_string);
+		addenv(env, "QUERY_STRING=%s", conn->request_info.query_string);
 	}
 	if ((s = mg_get_header(conn, "Content-Length")) != NULL) {
-		addenv(blk, "CONTENT_LENGTH=%s", s);
+		addenv(env, "CONTENT_LENGTH=%s", s);
 	}
 	if ((s = getenv("PATH")) != NULL) {
-		addenv(blk, "PATH=%s", s);
+		addenv(env, "PATH=%s", s);
 	}
 	if (conn->path_info != NULL) {
-		addenv(blk, "PATH_INFO=%s", conn->path_info);
+		addenv(env, "PATH_INFO=%s", conn->path_info);
 	}
 
 	if (conn->status_code > 0) {
 		/* CGI error handler should show the status code */
-		addenv(blk, "STATUS=%d", conn->status_code);
+		addenv(env, "STATUS=%d", conn->status_code);
 	}
 
 #if defined(_WIN32)
 	if ((s = getenv("COMSPEC")) != NULL) {
-		addenv(blk, "COMSPEC=%s", s);
+		addenv(env, "COMSPEC=%s", s);
 	}
 	if ((s = getenv("SYSTEMROOT")) != NULL) {
-		addenv(blk, "SYSTEMROOT=%s", s);
+		addenv(env, "SYSTEMROOT=%s", s);
 	}
 	if ((s = getenv("SystemDrive")) != NULL) {
-		addenv(blk, "SystemDrive=%s", s);
+		addenv(env, "SystemDrive=%s", s);
 	}
 	if ((s = getenv("ProgramFiles")) != NULL) {
-		addenv(blk, "ProgramFiles=%s", s);
+		addenv(env, "ProgramFiles=%s", s);
 	}
 	if ((s = getenv("ProgramFiles(x86)")) != NULL) {
-		addenv(blk, "ProgramFiles(x86)=%s", s);
+		addenv(env, "ProgramFiles(x86)=%s", s);
 	}
 #else
 	if ((s = getenv("LD_LIBRARY_PATH")) != NULL) {
-		addenv(blk, "LD_LIBRARY_PATH=%s", s);
+		addenv(env, "LD_LIBRARY_PATH=%s", s);
 	}
 #endif /* _WIN32 */
 
 	if ((s = getenv("PERLLIB")) != NULL) {
-		addenv(blk, "PERLLIB=%s", s);
+		addenv(env, "PERLLIB=%s", s);
 	}
 
 	if (conn->request_info.remote_user != NULL) {
-		addenv(blk, "REMOTE_USER=%s", conn->request_info.remote_user);
-		addenv(blk, "%s", "AUTH_TYPE=Digest");
+		addenv(env, "REMOTE_USER=%s", conn->request_info.remote_user);
+		addenv(env, "%s", "AUTH_TYPE=Digest");
 	}
 
 	/* Add all headers as HTTP_* variables */
 	for (i = 0; i < conn->request_info.num_headers; i++) {
-		p = addenv(blk,
-		           "HTTP_%s=%s",
-		           conn->request_info.http_headers[i].name,
-		           conn->request_info.http_headers[i].value);
+
+		(void)mg_snprintf(conn,
+		                  &truncated,
+		                  http_var_name,
+		                  sizeof(http_var_name),
+		                  "HTTP_%s",
+		                  conn->request_info.http_headers[i].name);
+
+		if (truncated) {
+			mg_cry(conn,
+			       "%s: HTTP header variable too long [%s]",
+			       __func__,
+			       conn->request_info.http_headers[i].name);
+			continue;
+		}
 
 		/* Convert variable name into uppercase, and change - to _ */
-		for (; *p != '=' && *p != '\0'; p++) {
+		for (p = http_var_name; *p != '\0'; p++) {
 			if (*p == '-') {
 				*p = '_';
 			}
 			*p = (char)toupper(*(unsigned char *)p);
 		}
+
+		addenv(env,
+		       "%s=%s",
+		       http_var_name,
+		       conn->request_info.http_headers[i].value);
 	}
 
 	/* Add user-specified variables */
 	s = conn->ctx->config[CGI_ENVIRONMENT];
 	while ((s = next_option(s, &var_vec, NULL)) != NULL) {
-		addenv(blk, "%.*s", (int)var_vec.len, var_vec.ptr);
+		addenv(env, "%.*s", (int)var_vec.len, var_vec.ptr);
 	}
 
-	blk->vars[blk->nvars++] = NULL;
-	blk->buf[blk->len++] = '\0';
-
-	/* assert(blk->nvars < (int) ARRAY_SIZE(blk->vars)); */
-	/* assert(blk->len > 0); */
-	/* assert(blk->len < (int) sizeof(blk->buf)); */
-
-	if ((blk->nvars >= (int)ARRAY_SIZE(blk->vars)) || (blk->len <= 0) ||
-	    (blk->len >= (int)sizeof(blk->buf))) {
-		/* TODO (mid): this statement is useless. Check original intention and
-		 * proper replacement. */
-		return;
-	}
+	env->var[env->varused] = NULL;
+	env->buf[env->bufused] = '\0';
 }
 
+
 static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 {
 	char *buf;
@@ -6285,7 +6320,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 	const char *status, *status_text, *connection_state;
 	char *pbuf, dir[PATH_MAX], *p;
 	struct mg_request_info ri;
-	struct cgi_env_block blk;
+	struct cgi_environment blk;
 	FILE *in = NULL, *out = NULL;
 	struct file fout = STRUCT_FILE_INITIALIZER;
 	pid_t pid = (pid_t)-1;
@@ -6321,7 +6356,7 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 		goto done;
 	}
 
-	pid = spawn_process(conn, p, blk.buf, blk.vars, fdin[0], fdout[1], dir);
+	pid = spawn_process(conn, p, blk.buf, blk.var, fdin[0], fdout[1], dir);
 	if (pid == (pid_t)-1) {
 		send_http_error(conn,
 		                500,
@@ -6444,6 +6479,9 @@ static void handle_cgi_request(struct mg_connection *conn, const char *prog)
 	send_file_data(conn, &fout, 0, INT64_MAX);
 
 done:
+	mg_free(blk.var);
+	mg_free(blk.buf);
+
 	if (pid != (pid_t)-1) {
 		kill(pid, SIGKILL);
 #if !defined(_WIN32)