From 368e798adb37c055aac76a0841a3d37285dae106 Mon Sep 17 00:00:00 2001 From: Shenhao Wang 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