From 4f73f6a1d6a2cb7e599bf136e551526c6531a3f1 Mon Sep 17 00:00:00 2001
From: Christoph Reiter <reiter.christoph@gmail.com>
Date: Sat, 20 Mar 2021 11:01:14 +0100
Subject: [PATCH] Rework path handling on native Windows

The current approach was to parse the .pc and, detect the prefix, throw
everything together and at the end replace all \ with / to not produce invalid
escape sequences.

This has the problem that escaping in .pc files is ignored and no longer
possible. Also in case the prefix path has a space in it the result would be
invalid because of missing escaping.

This changes the following things:

* We no longer normalize values at the end. Instead we assume .pc files use "/"
  as a directory separator or "\\", same format as under Unix. "\" alone no
  longer works. This shouldn't be a problem since most build tools produce .pc
  files with "/" like meson, cmake, autotools.

* When injecting the prefix at runtime we convert the prefix to use "/" and
  escape spaces so that in combination with the .pc content the result is a
  valid escaped path value again.

This patch has been used in MSYS2 for some months now.

See #212
---
 libpkgconf/path.c | 13 ------------
 libpkgconf/pkg.c  | 50 +++++++++++++++++++++++++++++++++++------------
 2 files changed, 38 insertions(+), 25 deletions(-)

diff --git a/libpkgconf/path.c b/libpkgconf/path.c
index 89d2ce0..5884ed7 100644
--- a/libpkgconf/path.c
+++ b/libpkgconf/path.c
@@ -331,18 +331,5 @@ pkgconf_path_relocate(char *buf, size_t buflen)
 		free(tmpbuf);
 	}
 
-#ifdef _WIN32
-	/*
-	 * Rewrite any backslash path delimiters for best compatibility.
-	 * Originally, we did this in cygwin/msys case, but now we build pkgconf
-	 * natively on Windows without cygwin/msys, so do it in all cases.
-	 */
-	for (ti = buf; *ti != '\0'; ti++)
-	{
-		if (*ti == '\\')
-			*ti = '/';
-	}
-#endif
-
 	return true;
 }
diff --git a/libpkgconf/pkg.c b/libpkgconf/pkg.c
index f302df3..3eafa6e 100644
--- a/libpkgconf/pkg.c
+++ b/libpkgconf/pkg.c
@@ -214,6 +214,37 @@ determine_prefix(const pkgconf_pkg_t *pkg, char *buf, size_t buflen)
 	return buf;
 }
 
+/*
+ * Takes a real path and converts it to a pkgconf value. This means normalizing
+ * directory separators and escaping things (only spaces covered atm).
+ *
+ * This is useful for things like prefix/pcfiledir which might get injected
+ * at runtime and are not sourced from the .pc file.
+ *
+ * "C:\foo bar\baz" -> "C:/foo\ bar/baz"
+ * "/foo bar/baz" -> "/foo\ bar/baz"
+ */
+static char *
+convert_path_to_value(const char *path)
+{
+	char *buf = calloc((strlen(path) + 1) * 2, 1);
+	char *bptr = buf;
+	const char *i;
+
+	for (i = path; *i != '\0'; i++)
+	{
+		if (*i == PKG_DIR_SEP_S)
+			*bptr++ = '/';
+		else if (*i == ' ') {
+			*bptr++ = '\\';
+			*bptr++ = *i;
+		} else
+			*bptr++ = *i;
+	}
+
+	return buf;
+}
+
 static void
 remove_additional_separators(char *buf)
 {
@@ -238,16 +269,6 @@ remove_additional_separators(char *buf)
 static void
 canonicalize_path(char *buf)
 {
-#ifdef _WIN32
-	char *p = buf;
-
-	while (*p) {
-		if (*p == '\\')
-			*p = '/';
-		p++;
-	}
-#endif
-
 	remove_additional_separators(buf);
 }
 
@@ -294,8 +315,10 @@ pkgconf_pkg_parser_value_set(void *opaque, const size_t lineno, const char *keyw
 
 		if (relvalue != NULL)
 		{
+			char *prefix_value = convert_path_to_value(relvalue);
 			pkg->orig_prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, "orig_prefix", canonicalized_value, true);
-			pkg->prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, relvalue, false);
+			pkg->prefix = pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, prefix_value, false);
+			free(prefix_value);
 		}
 		else
 			pkgconf_tuple_add(pkg->owner, &pkg->vars, keyword, value, true);
@@ -376,7 +399,10 @@ pkgconf_pkg_new_from_file(pkgconf_client_t *client, const char *filename, FILE *
 	pkg->owner = client;
 	pkg->filename = strdup(filename);
 	pkg->pc_filedir = pkg_get_parent_dir(pkg);
-	pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pkg->pc_filedir, true);
+
+	char *pc_filedir_value = convert_path_to_value(pkg->pc_filedir);
+	pkgconf_tuple_add(client, &pkg->vars, "pcfiledir", pc_filedir_value, true);
+	free(pc_filedir_value);
 
 	/* make module id */
 	if ((idptr = strrchr(pkg->filename, PKG_DIR_SEP_S)) != NULL)
