libpurple/protocols/zephyr/zephyr_html.c

changeset 40681
675b8605dca2
child 41084
72d6941bfa2e
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libpurple/protocols/zephyr/zephyr_html.c	Sat Jan 02 19:24:01 2021 -0600
@@ -0,0 +1,406 @@
+/*
+ * Purple - Internet Messaging Library
+ * Copyright (C) Pidgin Developers <devel@pidgin.im>
+ *
+ * Purple is the legal property of its developers, whose names are too numerous
+ * to list here.  Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <ctype.h>
+
+#include <purple.h>
+
+#include "zephyr_html.h"
+
+typedef struct _zframe zframe;
+
+struct _zframe {
+	/* common part */
+	/* true for everything but @color, since inside the parens of that one is
+	 * the color. */
+	gboolean has_closer;
+	/* </i>, </font>, </b>, etc. */
+	const char *closing;
+	/* text including the opening html thingie. */
+	GString *text;
+
+	/* html_to_zephyr */
+	/* @i, @b, etc. */
+	const char *env;
+	/* }=1, ]=2, )=4, >=8 */
+	int closer_mask;
+	/* href for links */
+	gboolean is_href;
+	GString *href;
+
+	/* zephyr_to_html */
+	/* }, ], ), > */
+	char *closer;
+};
+
+static zframe *
+zframe_new_with_text(const gchar *text, const gchar *closing, gboolean has_closer)
+{
+	zframe *frame = g_new(zframe, 1);
+
+	frame->text = g_string_new(text);
+	frame->closing = closing;
+	frame->has_closer = has_closer;
+
+	return frame;
+}
+
+static inline zframe *
+zframe_new(const gchar *closing, gboolean has_closer)
+{
+	return zframe_new_with_text("", closing, has_closer);
+}
+
+static gboolean
+zframe_href_has_prefix(const zframe *frame, const gchar *prefix)
+{
+	gsize prefix_len = strlen(prefix);
+
+	return (frame->href->len == (prefix_len + frame->text->len)) &&
+	       !strncmp(frame->href->str, prefix, prefix_len) &&
+	       purple_strequal(frame->href->str + prefix_len, frame->text->str);
+}
+
+static gsize
+html_to_zephyr_pop(GQueue *frames)
+{
+	zframe *popped = (zframe *)g_queue_pop_head(frames);
+	zframe *head = (zframe *)g_queue_peek_head(frames);
+	gsize result = strlen(popped->closing);
+
+	if (popped->is_href) {
+		head->href = popped->text;
+	} else {
+		g_string_append(head->text, popped->env);
+		if (popped->has_closer) {
+			g_string_append_c(head->text,
+			                  (popped->closer_mask & 1) ? '{' :
+			                  (popped->closer_mask & 2) ? '[' :
+			                  (popped->closer_mask & 4) ? '(' :
+			                  '<');
+		}
+		g_string_append(head->text, popped->text->str);
+		if (popped->href) {
+			if (!purple_strequal(popped->href->str, popped->text->str) &&
+			    !zframe_href_has_prefix(popped, "http://") &&
+			    !zframe_href_has_prefix(popped, "mailto:")) {
+				g_string_append(head->text, " <");
+				g_string_append(head->text, popped->href->str);
+				if (popped->closer_mask & ~8) {
+					g_string_append_c(head->text, '>');
+					popped->closer_mask &= ~8;
+				} else {
+					g_string_append(head->text, "@{>}");
+				}
+			}
+			g_string_free(popped->href, TRUE);
+		}
+		if (popped->has_closer) {
+			g_string_append_c(head->text,
+			                  (popped->closer_mask & 1) ? '}' :
+			                  (popped->closer_mask & 2) ? ']' :
+			                  (popped->closer_mask & 4) ? ')' :
+			                  '>');
+		}
+		if (!popped->has_closer) {
+			head->closer_mask = popped->closer_mask;
+		}
+		g_string_free(popped->text, TRUE);
+	}
+	g_free(popped);
+	return result;
+}
+
+char *
+html_to_zephyr(const char *message)
+{
+	GQueue frames = G_QUEUE_INIT;
+	zframe *frame, *new_f;
+	char *ret;
+
+	if (*message == '\0')
+		return g_strdup("");
+
+	frame = zframe_new(NULL, FALSE);
+	frame->href = NULL;
+	frame->is_href = FALSE;
+	frame->env = "";
+	frame->closer_mask = 15;
+
+	g_queue_push_head(&frames, frame);
+
+	purple_debug_info("zephyr", "html received %s\n", message);
+	while (*message) {
+		frame = (zframe *)g_queue_peek_head(&frames);
+		if (frame->closing && purple_str_has_caseprefix(message, frame->closing)) {
+			message += html_to_zephyr_pop(&frames);
+		} else if (*message == '<') {
+			if (!g_ascii_strncasecmp(message + 1, "i>", 2)) {
+				new_f = zframe_new("</i>", TRUE);
+				new_f->href = NULL;
+				new_f->is_href = FALSE;
+				new_f->env = "@i";
+				new_f->closer_mask = 15;
+				g_queue_push_head(&frames, new_f);
+				message += 3;
+			} else if (!g_ascii_strncasecmp(message + 1, "b>", 2)) {
+				new_f = zframe_new("</b>", TRUE);
+				new_f->href = NULL;
+				new_f->is_href = FALSE;
+				new_f->env = "@b";
+				new_f->closer_mask = 15;
+				g_queue_push_head(&frames, new_f);
+				message += 3;
+			} else if (!g_ascii_strncasecmp(message + 1, "br>", 3)) {
+				g_string_append_c(frame->text, '\n');
+				message += 4;
+			} else if (!g_ascii_strncasecmp(message + 1, "a href=\"", 8)) {
+				message += 9;
+				new_f = zframe_new("</a>", FALSE);
+				new_f->href = NULL;
+				new_f->is_href = FALSE;
+				new_f->env = "";
+				new_f->closer_mask = frame->closer_mask;
+				g_queue_push_head(&frames, new_f);
+				new_f = zframe_new("\">", FALSE);
+				new_f->href = NULL;
+				new_f->is_href = TRUE;
+				new_f->closer_mask = frame->closer_mask;
+				g_queue_push_head(&frames, new_f);
+			} else if (!g_ascii_strncasecmp(message + 1, "font", 4)) {
+				new_f = zframe_new("</font>", TRUE);
+				new_f->href = NULL;
+				new_f->is_href = FALSE;
+				new_f->closer_mask = 15;
+				g_queue_push_head(&frames, new_f);
+				message += 5;
+				while (*message == ' ') {
+					message++;
+				}
+				if (!g_ascii_strncasecmp(message, "color=\"", 7)) {
+					message += 7;
+					new_f->env = "@";
+					new_f = zframe_new("\">", TRUE);
+					new_f->env = "@color";
+					new_f->href = NULL;
+					new_f->is_href = FALSE;
+					new_f->closer_mask = 15;
+					g_queue_push_head(&frames, new_f);
+				} else if (!g_ascii_strncasecmp(message, "face=\"", 6)) {
+					message += 6;
+					new_f->env = "@";
+					new_f = zframe_new("\">", TRUE);
+					new_f->env = "@font";
+					new_f->href = NULL;
+					new_f->is_href = FALSE;
+					new_f->closer_mask = 15;
+					g_queue_push_head(&frames, new_f);
+				} else if (!g_ascii_strncasecmp(message, "size=\"", 6)) {
+					message += 6;
+					if ((*message == '1') || (*message == '2')) {
+						new_f->env = "@small";
+					} else if ((*message == '3') || (*message == '4')) {
+						new_f->env = "@medium";
+					} else if ((*message == '5') || (*message == '6')
+					           || (*message == '7')) {
+						new_f->env = "@large";
+					} else {
+						new_f->env = "";
+						new_f->has_closer = FALSE;
+						new_f->closer_mask = frame->closer_mask;
+					}
+					message += 3;
+				} else {
+					/* Drop all unrecognized/misparsed font tags */
+					new_f->env = "";
+					new_f->has_closer = FALSE;
+					new_f->closer_mask = frame->closer_mask;
+					while (g_ascii_strncasecmp(message, "\">", 2) != 0) {
+						message++;
+					}
+					if (*message != '\0') {
+						message += 2;
+					}
+				}
+			} else {
+				/* Catch all for all unrecognized/misparsed <foo> tage */
+				g_string_append_c(frame->text, *message++);
+			}
+		} else if (*message == '@') {
+			g_string_append(frame->text, "@@");
+			message++;
+		} else if (*message == '}') {
+			if (frame->closer_mask & ~1) {
+				frame->closer_mask &= ~1;
+				g_string_append_c(frame->text, *message++);
+			} else {
+				g_string_append(frame->text, "@[}]");
+				message++;
+			}
+		} else if (*message == ']') {
+			if (frame->closer_mask & ~2) {
+				frame->closer_mask &= ~2;
+				g_string_append_c(frame->text, *message++);
+			} else {
+				g_string_append(frame->text, "@{]}");
+				message++;
+			}
+		} else if (*message == ')') {
+			if (frame->closer_mask & ~4) {
+				frame->closer_mask &= ~4;
+				g_string_append_c(frame->text, *message++);
+			} else {
+				g_string_append(frame->text, "@{)}");
+				message++;
+			}
+		} else if (!g_ascii_strncasecmp(message, "&gt;", 4)) {
+			if (frame->closer_mask & ~8) {
+				frame->closer_mask &= ~8;
+				g_string_append_c(frame->text, *message++);
+			} else {
+				g_string_append(frame->text, "@{>}");
+				message += 4;
+			}
+		} else {
+			g_string_append_c(frame->text, *message++);
+		}
+	}
+	frame = (zframe *)g_queue_pop_head(&frames);
+	ret = g_string_free(frame->text, FALSE);
+	g_free(frame);
+	purple_debug_info("zephyr", "zephyr outputted  %s\n", ret);
+	return ret;
+}
+
+static void
+zephyr_to_html_pop(GQueue *frames, gboolean *last_had_closer)
+{
+	zframe *popped = (zframe *)g_queue_pop_head(frames);
+	zframe *head = (zframe *)g_queue_peek_head(frames);
+
+	g_string_append(head->text, popped->text->str);
+	g_string_append(head->text, popped->closing);
+
+	if (last_had_closer != NULL) {
+		*last_had_closer = popped->has_closer;
+	}
+
+	g_string_free(popped->text, TRUE);
+	g_free(popped);
+}
+
+char *
+zephyr_to_html(const char *message)
+{
+	GQueue frames = G_QUEUE_INIT;
+	zframe *frame;
+	char *ret;
+
+	frame = zframe_new("", FALSE);
+	frame->closer = NULL;
+
+	g_queue_push_head(&frames, frame);
+
+	while (*message) {
+		frame = (zframe *)g_queue_peek_head(&frames);
+		if (*message == '@' && message[1] == '@') {
+			g_string_append(frame->text, "@");
+			message += 2;
+		} else if (*message == '@') {
+			int end = 1;
+			while (message[end] && (isalnum(message[end]) || message[end] == '_')) {
+				end++;
+			}
+			if (message[end] &&
+			    (message[end] == '{' || message[end] == '[' || message[end] == '(' ||
+			     !g_ascii_strncasecmp(message + end, "&lt;", 4))) {
+				zframe *new_f;
+				char *buf;
+				char *closer;
+				buf = g_new0(char, end);
+				g_snprintf(buf, end, "%s", message + 1);
+				message += end;
+				closer = (*message == '{' ? "}" :
+				          *message == '[' ? "]" :
+				          *message == '(' ? ")" :
+				          "&gt;");
+				message += (*message == '&' ? 4 : 1);
+				if (!g_ascii_strcasecmp(buf, "italic") || !g_ascii_strcasecmp(buf, "i")) {
+					new_f = zframe_new_with_text("<i>", "</i>", TRUE);
+				} else if (!g_ascii_strcasecmp(buf, "small")) {
+					new_f = zframe_new_with_text("<font size=\"1\">", "</font>", TRUE);
+				} else if (!g_ascii_strcasecmp(buf, "medium")) {
+					new_f = zframe_new_with_text("<font size=\"3\">", "</font>", TRUE);
+				} else if (!g_ascii_strcasecmp(buf, "large")) {
+					new_f = zframe_new_with_text("<font size=\"7\">", "</font>", TRUE);
+				} else if (!g_ascii_strcasecmp(buf, "bold")
+				           || !g_ascii_strcasecmp(buf, "b")) {
+					new_f = zframe_new_with_text("<b>", "</b>", TRUE);
+				} else if (!g_ascii_strcasecmp(buf, "font")) {
+					zframe *extra_f;
+					extra_f = zframe_new("</font>", FALSE);
+					extra_f->closer = frame->closer;
+					g_queue_push_head(&frames, extra_f);
+					new_f = zframe_new_with_text("<font face=\"", "\">", TRUE);
+				} else if (!g_ascii_strcasecmp(buf, "color")) {
+					zframe *extra_f;
+					extra_f = zframe_new("</font>", FALSE);
+					extra_f->closer = frame->closer;
+					g_queue_push_head(&frames, extra_f);
+					new_f = zframe_new_with_text("<font color=\"", "\">", TRUE);
+				} else {
+					new_f = zframe_new("", TRUE);
+				}
+				new_f->closer = closer;
+				g_queue_push_head(&frames, new_f);
+				g_free(buf);
+			} else {
+				/* Not a formatting tag, add the character as normal. */
+				g_string_append_c(frame->text, *message++);
+			}
+		} else if (frame->closer && purple_str_has_caseprefix(message, frame->closer)) {
+			message += strlen(frame->closer);
+			if (g_queue_get_length(&frames) > 1) {
+				gboolean last_had_closer;
+
+				do {
+					zephyr_to_html_pop(&frames, &last_had_closer);
+				} while (g_queue_get_length(&frames) > 1 && !last_had_closer);
+			} else {
+				g_string_append_c(frame->text, *message);
+			}
+		} else if (*message == '\n') {
+			g_string_append(frame->text, "<br>");
+			message++;
+		} else {
+			g_string_append_c(frame->text, *message++);
+		}
+	}
+	/* go through all the stuff that they didn't close */
+	while (g_queue_get_length(&frames) > 1) {
+		zephyr_to_html_pop(&frames, NULL);
+	}
+	frame = (zframe *)g_queue_pop_head(&frames);
+	ret = g_string_free(frame->text, FALSE);
+	g_free(frame);
+	return ret;
+}

mercurial