Subject: [PATCH 05/35] s3: lib: Add canonicalize_absolute_path().

From: Jeremy Allison <[email-masked]>
Date: Tue, 17 Jan 2017 11:33:18 -0800

Resolves any invalid path components (.) (..)
in an absolute POSIX path.

We will be re-using this in several places.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12531

Signed-off-by: Jeremy Allison <[email-masked]>
Reviewed-by: Uri Simchoni <[email-masked]>
(backported from commit 02599c39337c3049762a6b0bd6290577817ee5a5)
Signed-off-by: Arvid Requate <[email-masked]>
---
 source3/include/proto.h |   1 +
 source3/lib/util.c      | 139 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 140 insertions(+)

diff --git a/source3/include/proto.h b/source3/include/proto.h
index afa43d2..89f8478 100644
--- a/source3/include/proto.h
+++ b/source3/include/proto.h
@@ -465,6 +465,7 @@ bool map_open_params_to_ntcreate(const char *smb_base_fname,
 				 uint32_t *pprivate_flags);
 struct security_unix_token *copy_unix_token(TALLOC_CTX *ctx, const struct security_unix_token *tok);
 bool dir_check_ftype(uint32_t mode, uint32_t dirtype);
+char *canonicalize_absolute_path(TALLOC_CTX *ctx, const char *abs_path);
 void init_modules(void);
 
 /* The following definitions come from lib/util_builtin.c  */
diff --git a/source3/lib/util.c b/source3/lib/util.c
index d38d53a..56f0b6f 100644
--- a/source3/lib/util.c
+++ b/source3/lib/util.c
@@ -2420,3 +2420,142 @@ bool dir_check_ftype(uint32_t mode, uint32_t dirtype)
 
 	return true;
 }
+
+/**
+ * @brief Removes any invalid path components in an absolute POSIX path.
+ *
+ * @param ctx Talloc context to return string.
+ *
+ * @param abs_path Absolute path string to process.
+ *
+ * @retval Pointer to a talloc'ed string containing the absolute full path.
+ **/
+
+char *canonicalize_absolute_path(TALLOC_CTX *ctx, const char *abs_path)
+{
+	char *destname;
+	char *d;
+	const char *s = abs_path;
+	bool start_of_name_component = true;
+
+	/* Allocate for strlen + '\0' + possible leading '/' */
+	destname = (char *)talloc_size(ctx, strlen(abs_path) + 2);
+	if (destname == NULL) {
+		return NULL;
+        }
+	d = destname;
+
+	*d++ = '/'; /* Always start with root. */
+
+	while (*s) {
+		if (*s == '/') {
+			/* Eat multiple '/' */
+			while (*s == '/') {
+				s++;
+			}
+			if ((d > destname + 1) && (*s != '\0')) {
+				*d++ = '/';
+			}
+			start_of_name_component = true;
+			continue;
+		}
+
+		if (start_of_name_component) {
+			if ((s[0] == '.') && (s[1] == '.') &&
+					(s[2] == '/' || s[2] == '\0')) {
+				/* Uh oh - "/../" or "/..\0" ! */
+
+				/* Go past the .. leaving us on the / or '\0' */
+				s += 2;
+
+				/* If  we just added a '/' - delete it */
+				if ((d > destname) && (*(d-1) == '/')) {
+					*(d-1) = '\0';
+					d--;
+				}
+
+				/*
+				 * Are we at the start ?
+				 * Can't go back further if so.
+				 */
+				if (d <= destname) {
+					*d++ = '/'; /* Can't delete root */
+					continue;
+				}
+				/* Go back one level... */
+				/*
+				 * Decrement d first as d points to
+				 * the *next* char to write into.
+				 */
+				for (d--; d > destname; d--) {
+					if (*d == '/') {
+						break;
+					}
+				}
+
+				/*
+				 * Are we at the start ?
+				 * Can't go back further if so.
+				 */
+				if (d <= destname) {
+					*d++ = '/'; /* Can't delete root */
+					continue;
+				}
+
+				/*
+				 * We're still at the start of a name
+				 * component, just the previous one.
+				 */
+				continue;
+			} else if ((s[0] == '.') &&
+					((s[1] == '\0') || s[1] == '/')) {
+				/*
+				 * Component of pathname can't be "." only.
+				 * Skip the '.' .
+				 */
+				if (s[1] == '/') {
+					s += 2;
+				} else {
+					s++;
+				}
+				continue;
+			}
+		}
+
+		if (!(*s & 0x80)) {
+			*d++ = *s++;
+		} else {
+			size_t siz;
+			/* Get the size of the next MB character. */
+			next_codepoint(s,&siz);
+			switch(siz) {
+				case 5:
+					*d++ = *s++;
+					/*fall through*/
+				case 4:
+					*d++ = *s++;
+					/*fall through*/
+				case 3:
+					*d++ = *s++;
+					/*fall through*/
+				case 2:
+					*d++ = *s++;
+					/*fall through*/
+				case 1:
+					*d++ = *s++;
+					break;
+				default:
+					break;
+			}
+		}
+		start_of_name_component = false;
+	}
+	*d = '\0';
+
+	/* And must not end in '/' */
+	if (d > destname + 1 && (*(d-1) == '/')) {
+		*(d-1) = '\0';
+	}
+
+	return destname;
+}
-- 
2.1.4