Subject: [PATCH] Return canonicalized paths from FcConfigRealFilename

From: Tom Anderson <[email-masked]>
Date: Wed, 11 Jul 2018 15:50:26 -0700

FcConfigRealFilename() follows symlinks, but the link may be relative to the
directory containing the link.  For example, on my system, I have this file:

/etc/fonts/conf.d/99-language-selector-zh.conf ->
    ../conf.avail/99-language-selector-zh.conf

Since /etc/fonts/conf.d is probably not in PATH, open()ing the file would fail.
This change makes FcConfigRealFilename() return the canonicalized filename
instead.  So for the example above, it would return:

/etc/fonts/conf.avail/99-language-selector-zh.conf

This was causing bad font rendering in Chromium [1] after the regression I
introduced in 7ad010e80bdf8e41303e322882ece908f5e04c74.

[1] https://bugs.chromium.org/p/chromium/issues/detail?id=857511
---
 src/fccfg.c | 65 ++++++++++++++++++++++++++++-------------------------
 src/fcint.h |  3 +++
 src/fcstr.c | 11 +++++++++
 3 files changed, 49 insertions(+), 30 deletions(-)

diff --git a/src/fccfg.c b/src/fccfg.c
index 3d5c335..421b47c 100644
--- a/src/fccfg.c
+++ b/src/fccfg.c
@@ -2191,42 +2191,28 @@ FcConfigFilename (const FcChar8 *url)
     }
     file = 0;
 
-#ifdef _WIN32
-    if (isalpha (*url) &&
-	url[1] == ':' &&
-	(url[2] == '/' || url[2] == '\\'))
-	goto absolute_path;
-#endif
+    if (FcStrIsAbsoluteFilename(url))
+	return FcConfigFileExists (0, url);
 
-    switch (*url) {
-    case '~':
+    if (*url == '~')
+    {
 	dir = FcConfigHome ();
 	if (dir)
 	    file = FcConfigFileExists (dir, url + 1);
 	else
 	    file = 0;
-	break;
-#ifdef _WIN32
-    case '\\':
-    absolute_path:
-#endif
-    case '/':
-	file = FcConfigFileExists (0, url);
-	break;
-    default:
-	path = FcConfigGetPath ();
-	if (!path)
-	    return NULL;
-	for (p = path; *p; p++)
-	{
-	    file = FcConfigFileExists (*p, url);
-	    if (file)
-		break;
-	}
-	FcConfigFreePath (path);
-	break;
     }
 
+    path = FcConfigGetPath ();
+    if (!path)
+	return NULL;
+    for (p = path; *p; p++)
+    {
+	file = FcConfigFileExists (*p, url);
+	if (file)
+	    break;
+    }
+    FcConfigFreePath (path);
     return file;
 }
 
@@ -2252,8 +2238,27 @@ FcConfigRealFilename (FcConfig		*config,
 	if ((len = FcReadLink (nn, buf, sizeof (buf) - 1)) != -1)
 	{
 	    buf[len] = 0;
-	    FcStrFree (nn);
-	    nn = FcStrdup (buf);
+
+	    if (!FcStrIsAbsoluteFilename (buf))
+	    {
+		FcChar8 *dirname = FcStrDirname (nn);
+		FcStrFree (nn);
+		if (!dirname)
+		    return NULL;
+
+		FcChar8 *path = FcStrBuildFilename (dirname, buf, NULL);
+		FcStrFree (dirname);
+		if (!path)
+		    return NULL;
+
+		nn = FcStrCanonFilename (path);
+		FcStrFree (path);
+	    }
+	    else
+	    {
+		FcStrFree (nn);
+		nn = FcStrdup (buf);
+	    }
 	}
     }
 
diff --git a/src/fcint.h b/src/fcint.h
index 598b76c..de78cd8 100644
--- a/src/fcint.h
+++ b/src/fcint.h
@@ -1279,6 +1279,9 @@ FcStrGlobMatch (const FcChar8 *glob,
 FcPrivate FcBool
 FcStrUsesHome (const FcChar8 *s);
 
+FcPrivate FcBool
+FcStrIsAbsoluteFilename (const FcChar8 *s);
+
 FcPrivate FcChar8 *
 FcStrBuildFilename (const FcChar8 *path,
 		    ...);
diff --git a/src/fcstr.c b/src/fcstr.c
index b65492d..4d33ea5 100644
--- a/src/fcstr.c
+++ b/src/fcstr.c
@@ -867,6 +867,17 @@ FcStrUsesHome (const FcChar8 *s)
     return *s == '~';
 }
 
+FcBool
+FcStrIsAbsoluteFilename (const FcChar8 *s)
+{
+#ifdef _WIN32
+    if (*s == '\\' ||
+	(isalpha (*s) && s[1] == ':' && (s[2] == '/' || s[2] == '\\'))
+	return FcTrue;
+#endif
+    return *s == '/';
+}
+
 FcChar8 *
 FcStrBuildFilename (const FcChar8 *path,
 		    ...)
-- 
2.18.0.203.gfac676dfb9-goog