From a4b0e3a140d6d750aea4f568679ecfd2a8e98d0e Mon Sep 17 00:00:00 2001
From: Shenhao Wang <wangsh.fnst@fujitsu.com>
Date: Sun, 26 Sep 2021 15:42:11 +0800
Subject: [PATCH 1/3] v1 make canonicalize_path remove all '..' in path

---
 src/port/path.c | 193 +++++++++++++++++++++++++++---------------------
 1 file changed, 109 insertions(+), 84 deletions(-)

diff --git a/src/port/path.c b/src/port/path.c
index c39d4688cd..d6741181ba 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -46,7 +46,7 @@
 
 static void make_relative_path(char *ret_path, const char *target_path,
 							   const char *bin_path, const char *my_exec_path);
-static void trim_directory(char *path);
+static int trim_directory(char *path);
 static void trim_trailing_separator(char *path);
 
 
@@ -240,6 +240,69 @@ join_path_components(char *ret_path,
 	}
 }
 
+static inline void
+canonicalize_path_sub(bool isabs, char *path, int *pathlen,
+					  char *sub, int sublen, int *nstrips)
+{
+	bool need_copy = true;
+
+	if (sublen == '\0')
+		return;
+
+	if (strcmp(sub, ".") == 0)
+	{
+		/* Don't remove the leading '.' if this is a relative path */
+
+		if (isabs || path[0] != '\0')
+			need_copy = false;
+	}
+	else if (strcmp(sub, "..") == 0 )
+	{
+		if (*nstrips == 0)
+		{
+			if (isabs)
+				need_copy = false;
+
+			/* handle leading './..', this should be '..' */
+			else if (strcmp(path, ".") == 0)
+				*pathlen = *pathlen - 1;
+		}
+		else
+		{
+			*nstrips = *nstrips - 1;
+			*pathlen = trim_directory(path);
+
+			/* foo/.. should become ".", not empty */
+			if (*pathlen == 0)
+			{
+				sub = ".";
+				sublen = 1;
+			}
+			else
+				need_copy = false;
+		}
+	}
+	else
+	{
+		/* handle leading './dir', this should be 'dir' */
+		if (!isabs && strcmp(path, ".") == 0)
+			*pathlen = *pathlen - 1;
+
+		*nstrips = *nstrips + 1;
+	}
+
+	if (need_copy)
+	{
+		if (*pathlen > 0 && path[*pathlen-1] != '/')
+		{
+			strcpy(&path[*pathlen], "/");
+			*pathlen = *pathlen + 1;
+		}
+
+		strcpy(&path[*pathlen], sub);
+		*pathlen = *pathlen + sublen;
+	}
+}
 
 /*
  *	Clean up path by:
@@ -247,17 +310,18 @@ join_path_components(char *ret_path,
  *		o  remove trailing quote on Win32
  *		o  remove trailing slash
  *		o  remove duplicate adjacent separators
- *		o  remove trailing '.'
- *		o  process trailing '..' ourselves
+ *		o  remove '.' (excpet leading '.' in relative path)
+ *		o  remove '..' (excpet leading '..' in relative path)
  */
 void
 canonicalize_path(char *path)
 {
-	char	   *p,
-			   *to_p;
+	char	   *p;
 	char	   *spath;
-	bool		was_sep = false;
-	int			pending_strips;
+	char	   *tmppath;
+	bool		isabs;
+	int			nstrips = 0;
+	int			len = 0;
 
 #ifdef WIN32
 
@@ -279,89 +343,47 @@ canonicalize_path(char *path)
 		*(p - 1) = '/';
 #endif
 
-	/*
-	 * Removing the trailing slash on a path means we never get ugly double
-	 * trailing slashes. Also, Win32 can't stat() a directory with a trailing
-	 * slash. Don't remove a leading slash, though.
-	 */
-	trim_trailing_separator(path);
-
-	/*
-	 * Remove duplicate adjacent separators
-	 */
-	p = path;
-#ifdef WIN32
-	/* Don't remove leading double-slash on Win32 */
-	if (*p)
-		p++;
-#endif
-	to_p = p;
-	for (; *p; p++, to_p++)
+	isabs = is_absolute_path(path);
+	tmppath = strdup(path);
+	if (!tmppath)
 	{
-		/* Handle many adjacent slashes, like "/a///b" */
-		while (*p == '/' && was_sep)
-			p++;
-		if (to_p != p)
-			*to_p = *p;
-		was_sep = (*p == '/');
+#ifndef FRONTEND
+			ereport(ERROR,
+					(errcode(ERRCODE_OUT_OF_MEMORY),
+					 errmsg("out of memory")));
+#else
+			fprintf(stderr, _("out of memory\n"));
+			return;
+#endif
 	}
-	*to_p = '\0';
 
-	/*
-	 * Remove any trailing uses of "." and process ".." ourselves
-	 *
-	 * Note that "/../.." should reduce to just "/", while "../.." has to be
-	 * kept as-is.  In the latter case we put back mistakenly trimmed ".."
-	 * components below.  Also note that we want a Windows drive spec to be
-	 * visible to trim_directory(), but it's not part of the logic that's
-	 * looking at the name components; hence distinction between path and
-	 * spath.
-	 */
-	spath = skip_drive(path);
-	pending_strips = 0;
-	for (;;)
-	{
-		int			len = strlen(spath);
+	if (isabs)
+		len = skip_drive(path) - path + 1;
 
-		if (len >= 2 && strcmp(spath + len - 2, "/.") == 0)
-			trim_directory(path);
-		else if (strcmp(spath, ".") == 0)
-		{
-			/* Want to leave "." alone, but "./.." has to become ".." */
-			if (pending_strips > 0)
-				*spath = '\0';
-			break;
-		}
-		else if ((len >= 3 && strcmp(spath + len - 3, "/..") == 0) ||
-				 strcmp(spath, "..") == 0)
-		{
-			trim_directory(path);
-			pending_strips++;
-		}
-		else if (pending_strips > 0 && *spath != '\0')
+	path[len] = '\0';
+
+	if (isabs)
+		spath = skip_drive(tmppath) + 1;
+	else
+		spath = tmppath;
+
+	for (p = spath; *p; p++)
+	{
+		if (IS_DIR_SEP(*p))
 		{
-			/* trim a regular directory name canceled by ".." */
-			trim_directory(path);
-			pending_strips--;
-			/* foo/.. should become ".", not empty */
-			if (*spath == '\0')
-				strcpy(spath, ".");
+			*p = '\0';
+
+			canonicalize_path_sub(isabs, path, &len,
+				spath, strlen(spath), &nstrips);
+
+			spath = p + 1;
 		}
-		else
-			break;
 	}
 
-	if (pending_strips > 0)
-	{
-		/*
-		 * We could only get here if path is now totally empty (other than a
-		 * possible drive specifier on Windows). We have to put back one or
-		 * more ".."'s that we took off.
-		 */
-		while (--pending_strips > 0)
-			strcat(path, "../");
-		strcat(path, "..");
-	}
+	canonicalize_path_sub(isabs, path, &len,
+		spath, strlen(spath), &nstrips);
+
+	free(tmppath);
 }
 
 /*
@@ -864,15 +886,16 @@ get_parent_directory(char *path)
  *	the last pathname component, and the slash just ahead of it --- but never
  *	remove a leading slash.
  */
-static void
+static int
 trim_directory(char *path)
 {
 	char	   *p;
+	char	   *opath = path;
 
 	path = skip_drive(path);
 
 	if (path[0] == '\0')
-		return;
+		return 0;
 
 	/* back up over trailing slash(es) */
 	for (p = path + strlen(path) - 1; IS_DIR_SEP(*p) && p > path; p--)
@@ -887,6 +910,8 @@ trim_directory(char *path)
 	if (p == path && IS_DIR_SEP(*p))
 		p++;
 	*p = '\0';
+
+	return (int)(p - opath);
 }
 
 
-- 
2.26.2

