libpurple/protocols/zephyr/zephyr_html.c

changeset 40681
675b8605dca2
child 41084
72d6941bfa2e
equal deleted inserted replaced
40680:f9ea6d5e8992 40681:675b8605dca2
1 /*
2 * Purple - Internet Messaging Library
3 * Copyright (C) Pidgin Developers <devel@pidgin.im>
4 *
5 * Purple is the legal property of its developers, whose names are too numerous
6 * to list here. Please refer to the COPYRIGHT file distributed with this
7 * source distribution.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
21 */
22
23 #include <ctype.h>
24
25 #include <purple.h>
26
27 #include "zephyr_html.h"
28
29 typedef struct _zframe zframe;
30
31 struct _zframe {
32 /* common part */
33 /* true for everything but @color, since inside the parens of that one is
34 * the color. */
35 gboolean has_closer;
36 /* </i>, </font>, </b>, etc. */
37 const char *closing;
38 /* text including the opening html thingie. */
39 GString *text;
40
41 /* html_to_zephyr */
42 /* @i, @b, etc. */
43 const char *env;
44 /* }=1, ]=2, )=4, >=8 */
45 int closer_mask;
46 /* href for links */
47 gboolean is_href;
48 GString *href;
49
50 /* zephyr_to_html */
51 /* }, ], ), > */
52 char *closer;
53 };
54
55 static zframe *
56 zframe_new_with_text(const gchar *text, const gchar *closing, gboolean has_closer)
57 {
58 zframe *frame = g_new(zframe, 1);
59
60 frame->text = g_string_new(text);
61 frame->closing = closing;
62 frame->has_closer = has_closer;
63
64 return frame;
65 }
66
67 static inline zframe *
68 zframe_new(const gchar *closing, gboolean has_closer)
69 {
70 return zframe_new_with_text("", closing, has_closer);
71 }
72
73 static gboolean
74 zframe_href_has_prefix(const zframe *frame, const gchar *prefix)
75 {
76 gsize prefix_len = strlen(prefix);
77
78 return (frame->href->len == (prefix_len + frame->text->len)) &&
79 !strncmp(frame->href->str, prefix, prefix_len) &&
80 purple_strequal(frame->href->str + prefix_len, frame->text->str);
81 }
82
83 static gsize
84 html_to_zephyr_pop(GQueue *frames)
85 {
86 zframe *popped = (zframe *)g_queue_pop_head(frames);
87 zframe *head = (zframe *)g_queue_peek_head(frames);
88 gsize result = strlen(popped->closing);
89
90 if (popped->is_href) {
91 head->href = popped->text;
92 } else {
93 g_string_append(head->text, popped->env);
94 if (popped->has_closer) {
95 g_string_append_c(head->text,
96 (popped->closer_mask & 1) ? '{' :
97 (popped->closer_mask & 2) ? '[' :
98 (popped->closer_mask & 4) ? '(' :
99 '<');
100 }
101 g_string_append(head->text, popped->text->str);
102 if (popped->href) {
103 if (!purple_strequal(popped->href->str, popped->text->str) &&
104 !zframe_href_has_prefix(popped, "http://") &&
105 !zframe_href_has_prefix(popped, "mailto:")) {
106 g_string_append(head->text, " <");
107 g_string_append(head->text, popped->href->str);
108 if (popped->closer_mask & ~8) {
109 g_string_append_c(head->text, '>');
110 popped->closer_mask &= ~8;
111 } else {
112 g_string_append(head->text, "@{>}");
113 }
114 }
115 g_string_free(popped->href, TRUE);
116 }
117 if (popped->has_closer) {
118 g_string_append_c(head->text,
119 (popped->closer_mask & 1) ? '}' :
120 (popped->closer_mask & 2) ? ']' :
121 (popped->closer_mask & 4) ? ')' :
122 '>');
123 }
124 if (!popped->has_closer) {
125 head->closer_mask = popped->closer_mask;
126 }
127 g_string_free(popped->text, TRUE);
128 }
129 g_free(popped);
130 return result;
131 }
132
133 char *
134 html_to_zephyr(const char *message)
135 {
136 GQueue frames = G_QUEUE_INIT;
137 zframe *frame, *new_f;
138 char *ret;
139
140 if (*message == '\0')
141 return g_strdup("");
142
143 frame = zframe_new(NULL, FALSE);
144 frame->href = NULL;
145 frame->is_href = FALSE;
146 frame->env = "";
147 frame->closer_mask = 15;
148
149 g_queue_push_head(&frames, frame);
150
151 purple_debug_info("zephyr", "html received %s\n", message);
152 while (*message) {
153 frame = (zframe *)g_queue_peek_head(&frames);
154 if (frame->closing && purple_str_has_caseprefix(message, frame->closing)) {
155 message += html_to_zephyr_pop(&frames);
156 } else if (*message == '<') {
157 if (!g_ascii_strncasecmp(message + 1, "i>", 2)) {
158 new_f = zframe_new("</i>", TRUE);
159 new_f->href = NULL;
160 new_f->is_href = FALSE;
161 new_f->env = "@i";
162 new_f->closer_mask = 15;
163 g_queue_push_head(&frames, new_f);
164 message += 3;
165 } else if (!g_ascii_strncasecmp(message + 1, "b>", 2)) {
166 new_f = zframe_new("</b>", TRUE);
167 new_f->href = NULL;
168 new_f->is_href = FALSE;
169 new_f->env = "@b";
170 new_f->closer_mask = 15;
171 g_queue_push_head(&frames, new_f);
172 message += 3;
173 } else if (!g_ascii_strncasecmp(message + 1, "br>", 3)) {
174 g_string_append_c(frame->text, '\n');
175 message += 4;
176 } else if (!g_ascii_strncasecmp(message + 1, "a href=\"", 8)) {
177 message += 9;
178 new_f = zframe_new("</a>", FALSE);
179 new_f->href = NULL;
180 new_f->is_href = FALSE;
181 new_f->env = "";
182 new_f->closer_mask = frame->closer_mask;
183 g_queue_push_head(&frames, new_f);
184 new_f = zframe_new("\">", FALSE);
185 new_f->href = NULL;
186 new_f->is_href = TRUE;
187 new_f->closer_mask = frame->closer_mask;
188 g_queue_push_head(&frames, new_f);
189 } else if (!g_ascii_strncasecmp(message + 1, "font", 4)) {
190 new_f = zframe_new("</font>", TRUE);
191 new_f->href = NULL;
192 new_f->is_href = FALSE;
193 new_f->closer_mask = 15;
194 g_queue_push_head(&frames, new_f);
195 message += 5;
196 while (*message == ' ') {
197 message++;
198 }
199 if (!g_ascii_strncasecmp(message, "color=\"", 7)) {
200 message += 7;
201 new_f->env = "@";
202 new_f = zframe_new("\">", TRUE);
203 new_f->env = "@color";
204 new_f->href = NULL;
205 new_f->is_href = FALSE;
206 new_f->closer_mask = 15;
207 g_queue_push_head(&frames, new_f);
208 } else if (!g_ascii_strncasecmp(message, "face=\"", 6)) {
209 message += 6;
210 new_f->env = "@";
211 new_f = zframe_new("\">", TRUE);
212 new_f->env = "@font";
213 new_f->href = NULL;
214 new_f->is_href = FALSE;
215 new_f->closer_mask = 15;
216 g_queue_push_head(&frames, new_f);
217 } else if (!g_ascii_strncasecmp(message, "size=\"", 6)) {
218 message += 6;
219 if ((*message == '1') || (*message == '2')) {
220 new_f->env = "@small";
221 } else if ((*message == '3') || (*message == '4')) {
222 new_f->env = "@medium";
223 } else if ((*message == '5') || (*message == '6')
224 || (*message == '7')) {
225 new_f->env = "@large";
226 } else {
227 new_f->env = "";
228 new_f->has_closer = FALSE;
229 new_f->closer_mask = frame->closer_mask;
230 }
231 message += 3;
232 } else {
233 /* Drop all unrecognized/misparsed font tags */
234 new_f->env = "";
235 new_f->has_closer = FALSE;
236 new_f->closer_mask = frame->closer_mask;
237 while (g_ascii_strncasecmp(message, "\">", 2) != 0) {
238 message++;
239 }
240 if (*message != '\0') {
241 message += 2;
242 }
243 }
244 } else {
245 /* Catch all for all unrecognized/misparsed <foo> tage */
246 g_string_append_c(frame->text, *message++);
247 }
248 } else if (*message == '@') {
249 g_string_append(frame->text, "@@");
250 message++;
251 } else if (*message == '}') {
252 if (frame->closer_mask & ~1) {
253 frame->closer_mask &= ~1;
254 g_string_append_c(frame->text, *message++);
255 } else {
256 g_string_append(frame->text, "@[}]");
257 message++;
258 }
259 } else if (*message == ']') {
260 if (frame->closer_mask & ~2) {
261 frame->closer_mask &= ~2;
262 g_string_append_c(frame->text, *message++);
263 } else {
264 g_string_append(frame->text, "@{]}");
265 message++;
266 }
267 } else if (*message == ')') {
268 if (frame->closer_mask & ~4) {
269 frame->closer_mask &= ~4;
270 g_string_append_c(frame->text, *message++);
271 } else {
272 g_string_append(frame->text, "@{)}");
273 message++;
274 }
275 } else if (!g_ascii_strncasecmp(message, "&gt;", 4)) {
276 if (frame->closer_mask & ~8) {
277 frame->closer_mask &= ~8;
278 g_string_append_c(frame->text, *message++);
279 } else {
280 g_string_append(frame->text, "@{>}");
281 message += 4;
282 }
283 } else {
284 g_string_append_c(frame->text, *message++);
285 }
286 }
287 frame = (zframe *)g_queue_pop_head(&frames);
288 ret = g_string_free(frame->text, FALSE);
289 g_free(frame);
290 purple_debug_info("zephyr", "zephyr outputted %s\n", ret);
291 return ret;
292 }
293
294 static void
295 zephyr_to_html_pop(GQueue *frames, gboolean *last_had_closer)
296 {
297 zframe *popped = (zframe *)g_queue_pop_head(frames);
298 zframe *head = (zframe *)g_queue_peek_head(frames);
299
300 g_string_append(head->text, popped->text->str);
301 g_string_append(head->text, popped->closing);
302
303 if (last_had_closer != NULL) {
304 *last_had_closer = popped->has_closer;
305 }
306
307 g_string_free(popped->text, TRUE);
308 g_free(popped);
309 }
310
311 char *
312 zephyr_to_html(const char *message)
313 {
314 GQueue frames = G_QUEUE_INIT;
315 zframe *frame;
316 char *ret;
317
318 frame = zframe_new("", FALSE);
319 frame->closer = NULL;
320
321 g_queue_push_head(&frames, frame);
322
323 while (*message) {
324 frame = (zframe *)g_queue_peek_head(&frames);
325 if (*message == '@' && message[1] == '@') {
326 g_string_append(frame->text, "@");
327 message += 2;
328 } else if (*message == '@') {
329 int end = 1;
330 while (message[end] && (isalnum(message[end]) || message[end] == '_')) {
331 end++;
332 }
333 if (message[end] &&
334 (message[end] == '{' || message[end] == '[' || message[end] == '(' ||
335 !g_ascii_strncasecmp(message + end, "&lt;", 4))) {
336 zframe *new_f;
337 char *buf;
338 char *closer;
339 buf = g_new0(char, end);
340 g_snprintf(buf, end, "%s", message + 1);
341 message += end;
342 closer = (*message == '{' ? "}" :
343 *message == '[' ? "]" :
344 *message == '(' ? ")" :
345 "&gt;");
346 message += (*message == '&' ? 4 : 1);
347 if (!g_ascii_strcasecmp(buf, "italic") || !g_ascii_strcasecmp(buf, "i")) {
348 new_f = zframe_new_with_text("<i>", "</i>", TRUE);
349 } else if (!g_ascii_strcasecmp(buf, "small")) {
350 new_f = zframe_new_with_text("<font size=\"1\">", "</font>", TRUE);
351 } else if (!g_ascii_strcasecmp(buf, "medium")) {
352 new_f = zframe_new_with_text("<font size=\"3\">", "</font>", TRUE);
353 } else if (!g_ascii_strcasecmp(buf, "large")) {
354 new_f = zframe_new_with_text("<font size=\"7\">", "</font>", TRUE);
355 } else if (!g_ascii_strcasecmp(buf, "bold")
356 || !g_ascii_strcasecmp(buf, "b")) {
357 new_f = zframe_new_with_text("<b>", "</b>", TRUE);
358 } else if (!g_ascii_strcasecmp(buf, "font")) {
359 zframe *extra_f;
360 extra_f = zframe_new("</font>", FALSE);
361 extra_f->closer = frame->closer;
362 g_queue_push_head(&frames, extra_f);
363 new_f = zframe_new_with_text("<font face=\"", "\">", TRUE);
364 } else if (!g_ascii_strcasecmp(buf, "color")) {
365 zframe *extra_f;
366 extra_f = zframe_new("</font>", FALSE);
367 extra_f->closer = frame->closer;
368 g_queue_push_head(&frames, extra_f);
369 new_f = zframe_new_with_text("<font color=\"", "\">", TRUE);
370 } else {
371 new_f = zframe_new("", TRUE);
372 }
373 new_f->closer = closer;
374 g_queue_push_head(&frames, new_f);
375 g_free(buf);
376 } else {
377 /* Not a formatting tag, add the character as normal. */
378 g_string_append_c(frame->text, *message++);
379 }
380 } else if (frame->closer && purple_str_has_caseprefix(message, frame->closer)) {
381 message += strlen(frame->closer);
382 if (g_queue_get_length(&frames) > 1) {
383 gboolean last_had_closer;
384
385 do {
386 zephyr_to_html_pop(&frames, &last_had_closer);
387 } while (g_queue_get_length(&frames) > 1 && !last_had_closer);
388 } else {
389 g_string_append_c(frame->text, *message);
390 }
391 } else if (*message == '\n') {
392 g_string_append(frame->text, "<br>");
393 message++;
394 } else {
395 g_string_append_c(frame->text, *message++);
396 }
397 }
398 /* go through all the stuff that they didn't close */
399 while (g_queue_get_length(&frames) > 1) {
400 zephyr_to_html_pop(&frames, NULL);
401 }
402 frame = (zframe *)g_queue_pop_head(&frames);
403 ret = g_string_free(frame->text, FALSE);
404 g_free(frame);
405 return ret;
406 }

mercurial