Migrate certificates, icons, logs to XDG dirs
[pidgin-git.git] / libpurple / protocols / yahoo / util.c
blob89c01132265218c2ed81e0e6c1cf5c229c3682ea
1 /*
2 * purple
4 * Some code copyright 2003 Tim Ringenbach <omarvo@hotmail.com>
5 * (marv on irc.freenode.net)
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif /* HAVE_CONFIG_H */
27 #include "debug.h"
28 #include "internal.h"
29 #include "protocol.h"
31 #include "ymsg.h"
33 #include <string.h>
35 gboolean
36 yahoo_account_use_http_proxy(PurpleConnection *pc)
38 PurpleAccount *account = purple_connection_get_account(pc);
39 PurpleProxyInfo *ppi = NULL;
40 PurpleProxyType type = PURPLE_PROXY_NONE;
41 #if 0
42 gboolean proxy_ssl = purple_account_get_bool(account, "proxy_ssl", FALSE);
44 if(proxy_ssl)
45 ppi = purple_proxy_get_setup(account);
46 else
47 ppi = purple_proxy_get_setup(NULL);
48 #else
49 ppi = purple_proxy_get_setup(account);
50 #endif
51 type = purple_proxy_info_get_proxy_type(ppi);
53 return (type == PURPLE_PROXY_HTTP || type == PURPLE_PROXY_USE_ENVVAR);
57 * Returns cookies formatted as a null terminated string for the given connection.
58 * Must g_free return value.
60 * TODO:will work, but must test for strict correctness
62 gchar* yahoo_get_cookies(PurpleConnection *gc)
64 gchar *ans = NULL;
65 gchar *cur;
66 char firstflag = 1;
67 gchar *t1,*t2,*t3;
68 GSList *tmp;
69 YahooData *yd = purple_connection_get_protocol_data(gc);
70 GSList *cookies = yd->cookies;
72 tmp = cookies;
73 while(tmp)
75 cur = tmp->data;
76 t1 = ans;
77 t2 = g_strrstr(cur, ";expires=");
78 if(t2 == NULL)
79 t2 = g_strrstr(cur, "; expires=");
80 if(t2 == NULL)
82 if(firstflag)
83 ans = g_strdup_printf("%c=%s", cur[0], cur+2);
84 else
85 ans = g_strdup_printf("%s; %c=%s", t1, cur[0], cur+2);
87 else
89 t3 = strstr(t2+1, ";");
90 if(t3 != NULL)
92 t2[0] = '\0';
94 if(firstflag)
95 ans = g_strdup_printf("%c=%s%s", cur[0], cur+2, t3);
96 else
97 ans = g_strdup_printf("%s; %c=%s%s", t1, cur[0], cur+2, t3);
99 t2[0] = ';';
101 else
103 t2[0] = '\0';
105 if(firstflag)
106 ans = g_strdup_printf("%c=%s", cur[0], cur+2);
107 else
108 ans = g_strdup_printf("%s; %c=%s", t1, cur[0], cur+2);
110 t2[0] = ';';
113 if(firstflag)
114 firstflag = 0;
115 else
116 g_free(t1);
117 tmp = g_slist_next(tmp);
119 return ans;
122 char *yahoo_string_encode(PurpleConnection *gc, const char *str, gboolean utf8)
124 char *ret;
125 const char *to_codeset;
126 GError *error = NULL;
128 if (utf8) /* FIXME: maybe don't use utf8 if it'll fit in latin1 */
129 return g_strdup(str);
131 to_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset", "ISO-8859-1");
132 ret = g_convert_with_fallback(str, -1, to_codeset, "UTF-8", "?", NULL, NULL, &error);
133 if (!ret) {
134 if (error) {
135 purple_debug_error("yahoo", "Could not convert %s from UTF-8 to "
136 "%s: %d - %s\n", str ? str : "(null)", to_codeset,
137 error->code,
138 error->message ? error->message : "(null)");
139 g_error_free(error);
140 } else {
141 purple_debug_error("yahoo", "Could not convert %s from UTF-8 to "
142 "%s: unkown error\n", str ? str : "(null)", to_codeset);
144 return g_strdup("");
147 return ret;
151 * Decode some text received from the server.
153 * @param gc The gc handle.
154 * @param str The null terminated string to decode.
155 * @param utf8 Did the server tell us it was supposed to be utf8?
156 * @return The decoded, utf-8 string, which must be g_free()'d.
158 char *yahoo_string_decode(PurpleConnection *gc, const char *str, gboolean utf8)
160 char *ret;
161 const char *from_codeset;
162 GError *error = NULL;
164 if (utf8) {
165 if (g_utf8_validate(str, -1, NULL))
166 return g_strdup(str);
167 purple_debug_warning("yahoo", "Server told us a string was supposed "
168 "to be UTF-8, but it was not. Will try another encoding.\n");
171 from_codeset = purple_account_get_string(purple_connection_get_account(gc), "local_charset", "ISO-8859-1");
173 ret = g_convert_with_fallback(str, -1, "UTF-8", from_codeset, NULL, NULL, NULL, &error);
174 if (!ret) {
175 if (error) {
176 purple_debug_error("yahoo", "Could not convert %s from %s to "
177 "UTF-8: %d - %s\n", str ? str : "(null)", from_codeset,
178 error->code, error->message ? error->message : "(null)");
179 g_error_free(error);
180 } else {
181 purple_debug_error("yahoo", "Could not convert %s from %s to "
182 "UTF-8: unkown error\n", str ? str : "(null)",
183 from_codeset);
185 return g_strdup("");
188 return ret;
191 char *yahoo_convert_to_numeric(const char *str)
193 GString *gstr = NULL;
194 const unsigned char *p;
196 gstr = g_string_sized_new(strlen(str) * 6 + 1);
198 for (p = (unsigned char *)str; *p; p++) {
199 g_string_append_printf(gstr, "&#%u;", *p);
202 return g_string_free(gstr, FALSE);
206 * The values in this hash table should probably be lowercase, since that's
207 * what xhtml expects. Also because yahoo_codes_to_html() does
208 * case-sensitive comparisons.
210 * I found these on some website but i don't know that they actually
211 * work (or are supposed to work). I didn't implement them yet.
213 * [0;30m ---black
214 * [1;37m ---white
215 * [0;37m ---tan
216 * [0;38m ---light black
217 * [1;39m ---dark blue
218 * [0;32m ---green
219 * [0;33m ---yellow
220 * [0;35m ---pink
221 * [1;35m ---purple
222 * [1;30m ---light blue
223 * [0;31m ---red
224 * [0;34m ---blue
225 * [0;36m ---aqua
226 * (shift+comma)lyellow(shift+period) ---light yellow
227 * (shift+comma)lgreen(shift+period) ---light green
228 * [2;30m <--white out
231 static GHashTable *esc_codes_ht = NULL;
232 static GHashTable *tags_ht = NULL;
234 void yahoo_init_colorht()
236 if (esc_codes_ht != NULL)
237 /* Hash table has already been initialized */
238 return;
240 /* Key is the escape code string. Value is the HTML that should be
241 * inserted in place of the escape code. */
242 esc_codes_ht = g_hash_table_new(g_str_hash, g_str_equal);
244 /* Key is the name of the HTML tag, for example "font" or "/font"
245 * value is the HTML that should be inserted in place of the old tag */
246 tags_ht = g_hash_table_new(g_str_hash, g_str_equal);
248 /* the numbers in comments are what gyach uses, but i think they're incorrect */
249 #ifdef USE_CSS_FORMATTING
250 g_hash_table_insert(esc_codes_ht, "30", "<span style=\"color: #000000\">"); /* black */
251 g_hash_table_insert(esc_codes_ht, "31", "<span style=\"color: #0000FF\">"); /* blue */
252 g_hash_table_insert(esc_codes_ht, "32", "<span style=\"color: #008080\">"); /* cyan */ /* 00b2b2 */
253 g_hash_table_insert(esc_codes_ht, "33", "<span style=\"color: #808080\">"); /* gray */ /* 808080 */
254 g_hash_table_insert(esc_codes_ht, "34", "<span style=\"color: #008000\">"); /* green */ /* 00c200 */
255 g_hash_table_insert(esc_codes_ht, "35", "<span style=\"color: #FF0080\">"); /* pink */ /* ffafaf */
256 g_hash_table_insert(esc_codes_ht, "36", "<span style=\"color: #800080\">"); /* purple */ /* b200b2 */
257 g_hash_table_insert(esc_codes_ht, "37", "<span style=\"color: #FF8000\">"); /* orange */ /* ffff00 */
258 g_hash_table_insert(esc_codes_ht, "38", "<span style=\"color: #FF0000\">"); /* red */
259 g_hash_table_insert(esc_codes_ht, "39", "<span style=\"color: #808000\">"); /* olive */ /* 546b50 */
260 #else
261 g_hash_table_insert(esc_codes_ht, "30", "<font color=\"#000000\">"); /* black */
262 g_hash_table_insert(esc_codes_ht, "31", "<font color=\"#0000FF\">"); /* blue */
263 g_hash_table_insert(esc_codes_ht, "32", "<font color=\"#008080\">"); /* cyan */ /* 00b2b2 */
264 g_hash_table_insert(esc_codes_ht, "33", "<font color=\"#808080\">"); /* gray */ /* 808080 */
265 g_hash_table_insert(esc_codes_ht, "34", "<font color=\"#008000\">"); /* green */ /* 00c200 */
266 g_hash_table_insert(esc_codes_ht, "35", "<font color=\"#FF0080\">"); /* pink */ /* ffafaf */
267 g_hash_table_insert(esc_codes_ht, "36", "<font color=\"#800080\">"); /* purple */ /* b200b2 */
268 g_hash_table_insert(esc_codes_ht, "37", "<font color=\"#FF8000\">"); /* orange */ /* ffff00 */
269 g_hash_table_insert(esc_codes_ht, "38", "<font color=\"#FF0000\">"); /* red */
270 g_hash_table_insert(esc_codes_ht, "39", "<font color=\"#808000\">"); /* olive */ /* 546b50 */
271 #endif /* !USE_CSS_FORMATTING */
273 g_hash_table_insert(esc_codes_ht, "1", "<b>");
274 g_hash_table_insert(esc_codes_ht, "x1", "</b>");
275 g_hash_table_insert(esc_codes_ht, "2", "<i>");
276 g_hash_table_insert(esc_codes_ht, "x2", "</i>");
277 g_hash_table_insert(esc_codes_ht, "4", "<u>");
278 g_hash_table_insert(esc_codes_ht, "x4", "</u>");
280 /* these just tell us the text they surround is supposed
281 * to be a link. purple figures that out on its own so we
282 * just ignore it.
284 g_hash_table_insert(esc_codes_ht, "l", ""); /* link start */
285 g_hash_table_insert(esc_codes_ht, "xl", ""); /* link end */
287 #ifdef USE_CSS_FORMATTING
288 g_hash_table_insert(tags_ht, "black", "<span style=\"color: #000000\">");
289 g_hash_table_insert(tags_ht, "blue", "<span style=\"color: #0000FF\">");
290 g_hash_table_insert(tags_ht, "cyan", "<span style=\"color: #008284\">");
291 g_hash_table_insert(tags_ht, "gray", "<span style=\"color: #848284\">");
292 g_hash_table_insert(tags_ht, "green", "<span style=\"color: #008200\">");
293 g_hash_table_insert(tags_ht, "pink", "<span style=\"color: #FF0084\">");
294 g_hash_table_insert(tags_ht, "purple", "<span style=\"color: #840084\">");
295 g_hash_table_insert(tags_ht, "orange", "<span style=\"color: #FF8000\">");
296 g_hash_table_insert(tags_ht, "red", "<span style=\"color: #FF0000\">");
297 g_hash_table_insert(tags_ht, "yellow", "<span style=\"color: #848200\">");
299 g_hash_table_insert(tags_ht, "/black", "</span>");
300 g_hash_table_insert(tags_ht, "/blue", "</span>");
301 g_hash_table_insert(tags_ht, "/cyan", "</span>");
302 g_hash_table_insert(tags_ht, "/gray", "</span>");
303 g_hash_table_insert(tags_ht, "/green", "</span>");
304 g_hash_table_insert(tags_ht, "/pink", "</span>");
305 g_hash_table_insert(tags_ht, "/purple", "</span>");
306 g_hash_table_insert(tags_ht, "/orange", "</span>");
307 g_hash_table_insert(tags_ht, "/red", "</span>");
308 g_hash_table_insert(tags_ht, "/yellow", "</span>");
309 #else
310 g_hash_table_insert(tags_ht, "black", "<font color=\"#000000\">");
311 g_hash_table_insert(tags_ht, "blue", "<font color=\"#0000FF\">");
312 g_hash_table_insert(tags_ht, "cyan", "<font color=\"#008284\">");
313 g_hash_table_insert(tags_ht, "gray", "<font color=\"#848284\">");
314 g_hash_table_insert(tags_ht, "green", "<font color=\"#008200\">");
315 g_hash_table_insert(tags_ht, "pink", "<font color=\"#FF0084\">");
316 g_hash_table_insert(tags_ht, "purple", "<font color=\"#840084\">");
317 g_hash_table_insert(tags_ht, "orange", "<font color=\"#FF8000\">");
318 g_hash_table_insert(tags_ht, "red", "<font color=\"#FF0000\">");
319 g_hash_table_insert(tags_ht, "yellow", "<font color=\"#848200\">");
321 g_hash_table_insert(tags_ht, "/black", "</font>");
322 g_hash_table_insert(tags_ht, "/blue", "</font>");
323 g_hash_table_insert(tags_ht, "/cyan", "</font>");
324 g_hash_table_insert(tags_ht, "/gray", "</font>");
325 g_hash_table_insert(tags_ht, "/green", "</font>");
326 g_hash_table_insert(tags_ht, "/pink", "</font>");
327 g_hash_table_insert(tags_ht, "/purple", "</font>");
328 g_hash_table_insert(tags_ht, "/orange", "</font>");
329 g_hash_table_insert(tags_ht, "/red", "</font>");
330 g_hash_table_insert(tags_ht, "/yellow", "</font>");
331 #endif /* !USE_CSS_FORMATTING */
333 /* We don't support these tags, so discard them */
334 g_hash_table_insert(tags_ht, "alt", "");
335 g_hash_table_insert(tags_ht, "fade", "");
336 g_hash_table_insert(tags_ht, "snd", "");
337 g_hash_table_insert(tags_ht, "/alt", "");
338 g_hash_table_insert(tags_ht, "/fade", "");
340 /* Official clients don't seem to send b, i or u tags. They use
341 * the escape codes listed above. Official clients definitely send
342 * font tags, though. I wonder if we can remove the opening and
343 * closing b, i and u tags from here? */
344 g_hash_table_insert(tags_ht, "b", "<b>");
345 g_hash_table_insert(tags_ht, "i", "<i>");
346 g_hash_table_insert(tags_ht, "u", "<u>");
347 g_hash_table_insert(tags_ht, "font", "<font>");
349 g_hash_table_insert(tags_ht, "/b", "</b>");
350 g_hash_table_insert(tags_ht, "/i", "</i>");
351 g_hash_table_insert(tags_ht, "/u", "</u>");
352 g_hash_table_insert(tags_ht, "/font", "</font>");
355 void yahoo_dest_colorht()
357 if (esc_codes_ht == NULL)
358 /* Hash table has already been destroyed */
359 return;
361 g_hash_table_destroy(esc_codes_ht);
362 esc_codes_ht = NULL;
363 g_hash_table_destroy(tags_ht);
364 tags_ht = NULL;
367 #ifndef USE_CSS_FORMATTING
368 static int point_to_html(int x)
370 if (x < 9)
371 return 1;
372 if (x < 11)
373 return 2;
374 if (x < 13)
375 return 3;
376 if (x < 17)
377 return 4;
378 if (x < 25)
379 return 5;
380 if (x < 35)
381 return 6;
382 return 7;
384 #endif /* !USE_CSS_FORMATTING */
386 static void append_attrs_datalist_foreach_cb(GQuark key_id, gpointer data, gpointer user_data)
388 const char *key;
389 const char *value;
390 PurpleXmlNode *cur;
392 key = g_quark_to_string(key_id);
393 value = data;
394 cur = user_data;
396 purple_xmlnode_set_attrib(cur, key, value);
400 * @param cur A pointer to the position in the XML tree that we're
401 * currently building. This will be modified when opening a tag
402 * or closing an existing tag.
404 static void yahoo_codes_to_html_add_tag(PurpleXmlNode **cur, const char *tag, gboolean is_closing_tag, const gchar *tag_name, gboolean is_font_tag)
406 if (is_closing_tag) {
407 PurpleXmlNode *tmp;
408 GSList *dangling_tags = NULL;
410 /* Move up the DOM until we find the opening tag */
411 for (tmp = *cur; tmp != NULL; tmp = purple_xmlnode_get_parent(tmp)) {
412 /* Add one to tag_name when doing this comparison because it starts with a / */
413 if (g_str_equal(tmp->name, tag_name + 1))
414 /* Found */
415 break;
416 dangling_tags = g_slist_prepend(dangling_tags, tmp);
418 if (tmp == NULL) {
419 /* This is a closing tag with no opening tag. Useless. */
420 purple_debug_error("yahoo", "Ignoring unmatched tag %s", tag);
421 g_slist_free(dangling_tags);
422 return;
425 /* Move our current position up, now that we've closed a tag */
426 *cur = purple_xmlnode_get_parent(tmp);
428 /* Re-open any tags that were nested below the tag we just closed */
429 while (dangling_tags != NULL) {
430 tmp = dangling_tags->data;
431 dangling_tags = g_slist_delete_link(dangling_tags, dangling_tags);
433 /* Create a copy of this tag+attributes (but not child tags or
434 * data) at our new location */
435 *cur = purple_xmlnode_new_child(*cur, tmp->name);
436 for (tmp = tmp->child; tmp != NULL; tmp = tmp->next)
437 if (tmp->type == PURPLE_XMLNODE_TYPE_ATTRIB)
438 purple_xmlnode_set_attrib_full(*cur, tmp->name,
439 tmp->xmlns, tmp->prefix, tmp->data);
441 } else {
442 const char *start;
443 const char *end;
444 GData *attributes;
445 char *fontsize = NULL;
447 if (!purple_markup_find_tag(tag_name, tag, &start, &end, &attributes))
448 g_return_if_reached();
449 *cur = purple_xmlnode_new_child(*cur, tag_name);
451 if (is_font_tag) {
452 /* Special case for the font size attribute */
453 fontsize = g_strdup(g_datalist_get_data(&attributes, "size"));
454 if (fontsize != NULL)
455 g_datalist_remove_data(&attributes, "size");
458 /* Add all font tag attributes */
459 g_datalist_foreach(&attributes, append_attrs_datalist_foreach_cb, *cur);
460 g_datalist_clear(&attributes);
462 if (fontsize != NULL) {
463 #ifdef USE_CSS_FORMATTING
465 * The Yahoo font size value is given in pt, even though the HTML
466 * standard for <font size="x"> treats the size as a number on a
467 * scale between 1 and 7. So we insert the font size as a CSS
468 * style on a span tag.
470 gchar *tmp = g_strdup_printf("font-size: %spt", fontsize);
471 *cur = purple_xmlnode_new_child(*cur, "span");
472 purple_xmlnode_set_attrib(*cur, "style", tmp);
473 g_free(tmp);
474 #else
476 * The Yahoo font size value is given in pt, even though the HTML
477 * standard for <font size="x"> treats the size as a number on a
478 * scale between 1 and 7. So we convert it to an appropriate
479 * value. This loses precision, which is why CSS formatting is
480 * preferred. The "absz" attribute remains here for backward
481 * compatibility with UIs that might use it, but it is totally
482 * not standard at all.
484 int size, htmlsize;
485 gchar tmp[11];
486 size = strtol(fontsize, NULL, 10);
487 htmlsize = point_to_html(size);
488 sprintf(tmp, "%u", htmlsize);
489 purple_xmlnode_set_attrib(*cur, "size", tmp);
490 purple_xmlnode_set_attrib(*cur, "absz", fontsize);
491 #endif /* !USE_CSS_FORMATTING */
492 g_free(fontsize);
498 * Similar to purple_markup_get_tag_name(), but works with closing tags.
500 * @return The lowercase name of the tag. If this is a closing tag then
501 * this value starts with a forward slash. The caller must free
502 * this string with g_free.
504 static gchar *yahoo_markup_get_tag_name(const char *tag, gboolean *is_closing_tag)
506 size_t len;
508 *is_closing_tag = (tag[1] == '/');
509 if (*is_closing_tag)
510 len = strcspn(tag + 1, "> ");
511 else
512 len = strcspn(tag + 1, "> /");
514 return g_utf8_strdown(tag + 1, len);
518 * Yahoo! messages generally aren't well-formed. Their markup is
519 * more of a flow from start to finish rather than a hierarchy from
520 * outer to inner. They tend to open tags and close them only when
521 * necessary.
523 * Example: <font size="8">size 8 <font size="16">size 16 <font size="8">size 8 again
525 * But we want to send well-formed HTML to the core, so we step through
526 * the input string and build an PurpleXmlNode tree containing sanitized HTML.
528 char *yahoo_codes_to_html(const char *x)
530 size_t x_len;
531 PurpleXmlNode *html, *cur;
532 GString *cdata = g_string_new(NULL);
533 guint i, j;
534 gboolean no_more_gt_brackets = FALSE;
535 const char *match;
536 gchar *xmlstr1, *xmlstr2, *esc;
538 x_len = strlen(x);
539 html = purple_xmlnode_new("html");
541 cur = html;
542 for (i = 0; i < x_len; i++) {
543 if ((x[i] == 0x1b) && (x[i+1] == '[')) {
544 /* This escape sequence signifies the beginning of some
545 * text formatting code */
546 j = i + 1;
548 while (j++ < x_len) {
549 gchar *code;
551 if (x[j] != 'm')
552 /* Keep looking for the end of this sequence */
553 continue;
555 /* We've reached the end of the formatting sequence, yay */
557 /* Append any character data that belongs in the current node */
558 if (cdata->len > 0) {
559 purple_xmlnode_insert_data(cur, cdata->str, cdata->len);
560 g_string_truncate(cdata, 0);
563 code = g_strndup(x + i + 2, j - i - 2);
564 if (code[0] == '#') {
565 #ifdef USE_CSS_FORMATTING
566 gchar *tmp = g_strdup_printf("color: %s", code);
567 cur = purple_xmlnode_new_child(cur, "span");
568 purple_xmlnode_set_attrib(cur, "style", tmp);
569 g_free(tmp);
570 #else
571 cur = purple_xmlnode_new_child(cur, "font");
572 purple_xmlnode_set_attrib(cur, "color", code);
573 #endif /* !USE_CSS_FORMATTING */
575 } else if ((match = g_hash_table_lookup(esc_codes_ht, code))) {
576 /* Some tags are in the hash table only because we
577 * want to ignore them */
578 if (match[0] != '\0') {
579 gboolean is_closing_tag;
580 gchar *tag_name;
581 tag_name = yahoo_markup_get_tag_name(match, &is_closing_tag);
582 yahoo_codes_to_html_add_tag(&cur, match, is_closing_tag, tag_name, FALSE);
583 g_free(tag_name);
586 } else {
587 purple_debug_error("yahoo",
588 "Ignoring unknown ansi code 'ESC[%sm'.\n", code);
591 g_free(code);
592 i = j;
593 break;
596 } else if (x[i] == '<' && !no_more_gt_brackets) {
597 /* The start of an HTML tag */
598 j = i;
600 while (j++ < x_len) {
601 gchar *tag;
602 gboolean is_closing_tag;
603 gchar *tag_name;
605 if (x[j] != '>') {
606 if (x[j] == '"') {
607 /* We're inside a quoted attribute value. Skip to the end */
608 j++;
609 while (j != x_len && x[j] != '"')
610 j++;
611 } else if (x[j] == '\'') {
612 /* We're inside a quoted attribute value. Skip to the end */
613 j++;
614 while (j != x_len && x[j] != '\'')
615 j++;
617 if (j != x_len)
618 /* Keep looking for the end of this tag */
619 continue;
621 /* This < has no corresponding > */
622 g_string_append_c(cdata, x[i]);
623 no_more_gt_brackets = TRUE;
624 break;
627 tag = g_strndup(x + i, j - i + 1);
628 tag_name = yahoo_markup_get_tag_name(tag, &is_closing_tag);
630 match = g_hash_table_lookup(tags_ht, tag_name);
631 if (match == NULL) {
632 /* Unknown tag. The user probably typed a less-than sign */
633 g_string_append_c(cdata, x[i]);
634 g_free(tag);
635 g_free(tag_name);
636 break;
639 /* Some tags are in the hash table only because we
640 * want to ignore them */
641 if (match[0] != '\0') {
642 /* Append any character data that belongs in the current node */
643 if (cdata->len > 0) {
644 purple_xmlnode_insert_data(cur, cdata->str, cdata->len);
645 g_string_truncate(cdata, 0);
647 if (g_str_equal(tag_name, "font"))
648 /* Font tags are a special case. We don't
649 * necessarily want to replace the whole thing--
650 * we just want to fix the size attribute. */
651 yahoo_codes_to_html_add_tag(&cur, tag, is_closing_tag, tag_name, TRUE);
652 else
653 yahoo_codes_to_html_add_tag(&cur, match, is_closing_tag, tag_name, FALSE);
656 i = j;
657 g_free(tag);
658 g_free(tag_name);
659 break;
662 } else {
663 g_string_append_c(cdata, x[i]);
667 /* Append any remaining character data */
668 if (cdata->len > 0)
669 purple_xmlnode_insert_data(cur, cdata->str, cdata->len);
670 g_string_free(cdata, TRUE);
672 /* Serialize our HTML */
673 xmlstr1 = purple_xmlnode_to_str(html, NULL);
674 purple_xmlnode_free(html);
676 /* Strip off the outter HTML node */
677 /* This probably isn't necessary, especially if we made the outter HTML
678 * node an empty span. But the HTML is simpler this way. */
679 if (!purple_strequal(xmlstr1, "<html/>"))
680 xmlstr2 = g_strndup(xmlstr1 + 6, strlen(xmlstr1) - 13);
681 else
682 xmlstr2 = g_strdup("");
683 g_free(xmlstr1);
685 esc = g_strescape(x, NULL);
686 purple_debug_misc("yahoo", "yahoo_codes_to_html(%s)=%s\n", esc, xmlstr2);
687 g_free(esc);
689 return xmlstr2;
692 /* borrowed from gtkimhtml */
693 #define MAX_FONT_SIZE 7
694 #define POINT_SIZE(x) (_point_sizes [MIN ((x > 0 ? x : 1), MAX_FONT_SIZE) - 1])
695 static const gint _point_sizes [] = { 8, 10, 12, 14, 20, 30, 40 };
697 typedef struct
699 gboolean bold;
700 gboolean italic;
701 gboolean underline;
702 gboolean in_link;
703 int font_size;
704 char *font_face;
705 char *font_color;
706 } CurrentMsgState;
708 static void yahoo_htc_list_cleanup(GSList *l)
710 while (l != NULL) {
711 g_free(l->data);
712 l = g_slist_delete_link(l, l);
716 static void parse_font_tag(GString *dest, const char *tag_name, const char *tag,
717 GSList **colors, GSList **tags)
719 const char *start;
720 const char *end;
721 GData *attributes;
722 const char *attribute;
723 gboolean needendtag;
724 GString *tmp;
726 if (!purple_markup_find_tag(tag_name, tag, &start, &end, &attributes))
727 g_return_if_reached();
729 needendtag = FALSE;
730 tmp = g_string_new(NULL);
732 attribute = g_datalist_get_data(&attributes, "color");
733 if (attribute != NULL) {
734 g_string_append(tmp, *colors ? (*colors)->data : "\033[#000000m");
735 g_string_append_printf(dest, "\033[%sm", attribute);
736 *colors = g_slist_prepend(*colors,
737 g_strdup_printf("\033[%sm", attribute));
738 } else {
739 /* We need to add a value to the colors stack even if we're not
740 * setting a color because we ALWAYS pop exactly 1 element from
741 * this stack for every </font> tag. If we don't add anything
742 * then we'll pop something that we shouldn't when we hit this
743 * corresponding </font>. */
744 *colors = g_slist_prepend(*colors,
745 *colors ? g_strdup((*colors)->data) : g_strdup("\033[#000000m"));
748 attribute = g_datalist_get_data(&attributes, "face");
749 if (attribute != NULL) {
750 needendtag = TRUE;
751 g_string_append(dest, "<font ");
752 g_string_append_printf(dest, "face=\"%s\" ", attribute);
755 attribute = g_datalist_get_data(&attributes, "size");
756 if (attribute != NULL) {
757 if (!needendtag) {
758 needendtag = TRUE;
759 g_string_append(dest, "<font ");
762 g_string_append_printf(dest, "size=\"%d\" ",
763 POINT_SIZE(strtol(attribute, NULL, 10)));
766 if (needendtag) {
767 dest->str[dest->len-1] = '>';
768 *tags = g_slist_prepend(*tags, g_strdup("</font>"));
769 g_string_free(tmp, TRUE);
770 } else {
771 *tags = g_slist_prepend(*tags, tmp->str);
772 g_string_free(tmp, FALSE);
775 g_datalist_clear(&attributes);
778 char *yahoo_html_to_codes(const char *src)
780 GSList *colors = NULL;
783 * A stack of char*s where each char* is the string that should be
784 * appended to dest in order to close all the tags that were opened
785 * by a <font> tag.
787 GSList *tags = NULL;
789 size_t src_len;
790 guint i, j;
791 GString *dest;
792 char *esc;
793 gboolean no_more_gt_brackets = FALSE;
794 gchar *tag, *tag_name;
795 gboolean is_closing_tag;
796 CurrentMsgState current_state;
798 memset(&current_state, 0, sizeof(current_state));
800 src_len = strlen(src);
801 dest = g_string_sized_new(src_len);
803 for (i = 0; i < src_len; i++) {
804 if (src[i] == '<' && !no_more_gt_brackets) {
805 /* The start of an HTML tag */
806 j = i;
808 while (j++ < src_len) {
809 if (src[j] != '>') {
810 if (src[j] == '"') {
811 /* We're inside a quoted attribute value. Skip to the end */
812 j++;
813 while (j != src_len && src[j] != '"')
814 j++;
815 } else if (src[j] == '\'') {
816 /* We're inside a quoted attribute value. Skip to the end */
817 j++;
818 while (j != src_len && src[j] != '\'')
819 j++;
821 if (j != src_len)
822 /* Keep looking for the end of this tag */
823 continue;
825 /* This < has no corresponding > */
826 g_string_append_c(dest, src[i]);
827 no_more_gt_brackets = TRUE;
828 break;
831 tag = g_strndup(src + i, j - i + 1);
832 tag_name = yahoo_markup_get_tag_name(tag, &is_closing_tag);
834 if (g_str_equal(tag_name, "a")) {
835 const char *start;
836 const char *end;
837 GData *attributes;
838 const char *attribute;
841 * TODO: Ideally we would replace this:
842 * <a href="https://pidgin.im/">Pidgin</a>
843 * with this:
844 * Pidgin (https://pidgin.im/)
846 * Currently we drop the text within the <a> tag and
847 * just show the URL. Doing it the fancy way is
848 * complicated when dealing with HTML tags within the
849 * <a> tag.
852 /* Append the URL */
853 if (!purple_markup_find_tag(tag_name,
854 tag, &start, &end, &attributes))
856 g_warn_if_reached();
857 i = j;
858 g_free(tag);
859 g_free(tag_name);
860 break;
863 attribute = g_datalist_get_data(&attributes, "href");
864 if (attribute != NULL) {
865 if (purple_str_has_prefix(attribute, "mailto:"))
866 attribute += 7;
867 g_string_append(dest, attribute);
869 g_datalist_clear(&attributes);
871 /* Skip past the closing </a> tag */
872 end = purple_strcasestr(src + j, "</a>");
873 if (end != NULL)
874 j = end - src + 3;
876 } else if (g_str_equal(tag_name, "font")) {
877 parse_font_tag(dest, tag_name, tag, &colors, &tags);
878 } else if (g_str_equal(tag_name, "b")) {
879 g_string_append(dest, "\033[1m");
880 current_state.bold = TRUE;
881 } else if (g_str_equal(tag_name, "/b")) {
882 if (current_state.bold) {
883 g_string_append(dest, "\033[x1m");
884 current_state.bold = FALSE;
886 } else if (g_str_equal(tag_name, "i")) {
887 current_state.italic = TRUE;
888 g_string_append(dest, "\033[2m");
889 } else if (g_str_equal(tag_name, "/i")) {
890 if (current_state.italic) {
891 g_string_append(dest, "\033[x2m");
892 current_state.italic = FALSE;
894 } else if (g_str_equal(tag_name, "u")) {
895 current_state.underline = TRUE;
896 g_string_append(dest, "\033[4m");
897 } else if (g_str_equal(tag_name, "/u")) {
898 if (current_state.underline) {
899 g_string_append(dest, "\033[x4m");
900 current_state.underline = FALSE;
902 } else if (g_str_equal(tag_name, "/a")) {
903 /* Do nothing */
904 } else if (g_str_equal(tag_name, "br")) {
905 g_string_append_c(dest, '\n');
906 } else if (g_str_equal(tag_name, "/font")) {
907 if (tags != NULL) {
908 char *etag = tags->data;
909 tags = g_slist_delete_link(tags, tags);
910 g_string_append(dest, etag);
911 if (colors != NULL) {
912 g_free(colors->data);
913 colors = g_slist_delete_link(colors, colors);
915 g_free(etag);
917 } else if (g_str_equal(tag_name, "span") || g_str_equal(tag_name, "/span")) {
918 /* Do nothing */
919 } else {
920 /* We don't know what the tag is. Send it unmodified. */
921 g_string_append(dest, tag);
924 i = j;
925 g_free(tag);
926 g_free(tag_name);
927 break;
930 } else {
931 const char *entity;
932 int length;
934 entity = purple_markup_unescape_entity(src + i, &length);
935 if (entity != NULL) {
936 /* src[i] is the start of an HTML entity */
937 g_string_append(dest, entity);
938 i += length - 1;
939 } else
940 /* src[i] is a normal character */
941 g_string_append_c(dest, src[i]);
945 esc = g_strescape(dest->str, NULL);
946 purple_debug_misc("yahoo", "yahoo_html_to_codes(%s)=%s\n", src, esc);
947 g_free(esc);
949 yahoo_htc_list_cleanup(colors);
950 yahoo_htc_list_cleanup(tags);
952 return g_string_free(dest, FALSE);