From 789a877b379cd35c350610be62b971ae00feb542 Mon Sep 17 00:00:00 2001
From: Heghedus Razvan <heghedus.razvan@protonmail.com>
Date: Mon, 7 May 2018 19:30:45 +0300
Subject: [PATCH] Fix crash when using pango markup font

The characters & < > ' " needs to be escaped when using pango markup

Signed-off-by: Heghedus Razvan <heghedus.razvan@gmail.com>
---
 common/pango.c   | 73 ++++++++++++++++++++++++++++++++++++++++++++----
 include/pango.h  | 11 ++++++++
 sway/tree/view.c | 21 +++++++++++++-
 3 files changed, 99 insertions(+), 6 deletions(-)

diff --git a/common/pango.c b/common/pango.c
index 658d2876..9437c60d 100644
--- a/common/pango.c
+++ b/common/pango.c
@@ -8,6 +8,68 @@
 #include <string.h>
 #include "log.h"
 
+int escape_markup_text(const char *src, char *dest, int dest_length) {
+	int length = 0;
+
+	while (src[0]) {
+		switch (src[0]) {
+		case '&':
+			length += 5;
+			if (dest && dest_length - length >= 0) {
+				dest += sprintf(dest, "%s", "&amp;");
+			} else {
+				dest_length = -1;
+			}
+			break;
+		case '<':
+			length += 4;
+			if (dest && dest_length - length >= 0) {
+				dest += sprintf(dest, "%s", "&lt;");
+			} else {
+				dest_length = -1;
+			}
+			break;
+		case '>':
+			length += 4;
+			if (dest && dest_length - length >= 0) {
+				dest += sprintf(dest, "%s", "&gt;");
+			} else {
+				dest_length = -1;
+			}
+			break;
+		case '\'':
+			length += 6;
+			if (dest && dest_length - length >= 0) {
+				dest += sprintf(dest, "%s", "&apos;");
+			} else {
+				dest_length = -1;
+			}
+			break;
+		case '"':
+			length += 6;
+			if (dest && dest_length - length >= 0) {
+				dest += sprintf(dest, "%s", "&quot;");
+			} else {
+				dest_length = -1;
+			}
+			break;
+		default:
+			length += 1;
+			if (dest && dest_length - length >= 0) {
+				*(dest++) = *src;
+			} else {
+				dest_length = -1;
+			}
+		}
+		src++;
+	}
+	// if we could not fit the escaped string in dest, return -1
+	if (dest && dest_length == -1) {
+		return -1;
+	}
+	return length;
+}
+
 PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
 		const char *text, int32_t scale, bool markup) {
 	PangoLayout *layout = pango_cairo_create_layout(cairo);
@@ -15,13 +77,14 @@ PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
 	if (markup) {
 		char *buf;
 		GError *error = NULL;
-		if (!sway_assert(pango_parse_markup(
-					text, -1, 0, &attrs, &buf, NULL, &error),
-				"pango_parse_markup '%s' -> error %s", text,
-				error ? error->message : NULL)) {
+		bool result = pango_parse_markup(text, -1, 0, &attrs, &buf,
+				NULL, &error);
+		if (result) {
+			wlr_log(L_ERROR, "pango_parse_markup '%s' -> error %s", text,
+					error->message);
 			return NULL;
 		}
-		pango_layout_set_markup(layout, buf, -1);
+		pango_layout_set_markup(layout, text, -1);
 		free(buf);
 	} else {
 		attrs = pango_attr_list_new();
diff --git a/include/pango.h b/include/pango.h
index f6325f28..d8263f9e 100644
--- a/include/pango.h
+++ b/include/pango.h
@@ -6,6 +6,17 @@
 #include <cairo/cairo.h>
 #include <pango/pangocairo.h>
 
+/* Utility function which escape characters a & < > ' ".
+ *
+ * If the dest parameter is NULL, then the function returns the length of
+ * of the escaped src string. The dest_length doesn't matter.
+ *
+ * If the dest parameter is not NULL then the fuction escapes the src string
+ * an puts the escaped string in dest and returns the lenght of the escaped string.
+ * The dest_length parameter is the size of dest array. If the size of dest is not
+ * enough, then the function returns -1.
+ */
+int escape_markup_text(const char *src, char *dest, int dest_length);
 PangoLayout *get_pango_layout(cairo_t *cairo, const char *font,
 		const char *text, int32_t scale, bool markup);
 void get_text_size(cairo_t *cairo, const char *font, int *width, int *height,
diff --git a/sway/tree/view.c b/sway/tree/view.c
index e2cb8a7a..9bdc5198 100644
--- a/sway/tree/view.c
+++ b/sway/tree/view.c
@@ -14,6 +14,8 @@
 #include "sway/tree/layout.h"
 #include "sway/tree/view.h"
 #include "sway/tree/workspace.h"
+#include "sway/config.h"
+#include "pango.h"
 
 void view_init(struct sway_view *view, enum sway_view_type type,
 		const struct sway_view_impl *impl) {
@@ -612,6 +614,19 @@ static size_t parse_title_format(struct sway_view *view, char *buffer) {
 	return len;
 }
 
+static char *escape_title(char *buffer) {
+	int length = escape_markup_text(buffer, NULL, 0);
+	char *escaped_title = calloc(length + 1, sizeof(char));
+	int result = escape_markup_text(buffer, escaped_title, length);
+	if (result != length) {
+		wlr_log(L_ERROR, "Could not escape title: %s", buffer);
+		free(escaped_title);
+		return buffer;
+	}
+	free(buffer);
+	return escaped_title;
+}
+
 void view_update_title(struct sway_view *view, bool force) {
 	if (!view->swayc) {
 		return;
@@ -631,11 +646,15 @@ void view_update_title(struct sway_view *view, bool force) {
 	free(view->swayc->formatted_title);
 	if (title) {
 		size_t len = parse_title_format(view, NULL);
-		char *buffer = calloc(len + 1, 1);
+		char *buffer = calloc(len + 1, sizeof(char));
 		if (!sway_assert(buffer, "Unable to allocate title string")) {
 			return;
 		}
 		parse_title_format(view, buffer);
+		// now we have the title, but needs to be escaped when using pango markup
+		if (config->pango_markup) {
+			buffer = escape_title(buffer);
+		}
 
 		view->swayc->name = strdup(title);
 		view->swayc->formatted_title = buffer;