From 368e798adb37c055aac76a0841a3d37285dae106 Mon Sep 17 00:00:00 2001
From: Shenhao Wang <wangsh.fnst@fujitsu.com>
Date: Sun, 12 Sep 2021 14:51:44 +0800
Subject: [PATCH 2/3] WIP:make canonicalize_path remove all '..' in path

---
 src/port/path.c | 178 ++++++++++++++++++++++++++----------------------
 1 file changed, 98 insertions(+), 80 deletions(-)

diff --git a/src/port/path.c b/src/port/path.c
index c39d4688cd..5fa2669801 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -240,6 +240,61 @@ join_path_components(char *ret_path,
 	}
 }
 
+static inline void
+canonicalize_path_sub(char *path, bool isabs, char *sub, int *nstrips)
+{
+	if (*sub == '\0')
+	{
+		/* remove multi slashes, like "/a///b" */
+	}
+	else if (strcmp(sub, ".") == 0)
+	{
+		/* if this is a relative path, don't remove the leading '.' */
+		if (!isabs && *nstrips == 0 && strcmp(path, "./") != 0)
+			strcpy(path, "./");
+	}
+	else if (strcmp(sub, "..") == 0 )
+	{
+		/* handle leading '..', like '../../a/b' */
+		if (!isabs && *nstrips == 0)
+		{
+			/* handle leading './..', this should be '..' */
+			if (strcmp(path, "./") == 0)
+				strcpy(path, "../");
+
+			/* handle leading '../..' */
+			else
+				strcat(path, "../");
+		}
+		else
+		{
+			*nstrips = *nstrips - 1 >= 0 ? *nstrips - 1 : 0;
+			trim_directory(path);
+
+			/* foo/.. should become ".", not empty */
+			if (path[0] == '\0')
+				strcpy(path, "./");
+
+			/* trim_directory never remove a leading slash. */
+			else if (strcmp(skip_drive(path), "/") != 0)
+			{
+				strcat(path, "/");
+			}
+		}
+	}
+	else
+	{
+		/* handle leading './dir', this should be 'dir' */
+		if (!isabs && strcmp(path, "./") == 0)
+			strcpy(path, sub);
+		else
+			strcat(path, sub);
+
+		strcat(path, "/");
+
+		*nstrips = *nstrips + 1;
+	}
+}
 
 /*
  *	Clean up path by:
@@ -247,17 +302,17 @@ 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 '.' (absolute path or relative path excpet leading .)
+ *		o  remove '..' (absolute path or relative path excpet leading ..)
  */
 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;
 
 #ifdef WIN32
 
@@ -279,89 +334,52 @@ 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 && spath[0] != '\0')
+		spath[1] = '\0';
+	else
+		spath[0] = '\0';
 
-		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')
+	spath = skip_drive(tmppath);
+	if (isabs && spath[0] != '\0')
+		spath++;
+
+	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(path, isabs, 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(path, isabs, spath, &nstrips);
+
+	/*
+	 * 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);
+
+	free(tmppath);
 }
 
 /*
-- 
2.26.2

