Doh. Upon further investigation I think these translations were made
[pidgin-git.git] / libpurple / util.c
blobf7d039add5d1e9f775871d8b398c2589746925bd
1 /*
2 * @file util.h Utility Functions
3 * @ingroup core
4 */
6 /* Purple is the legal property of its developers, whose names are too numerous
7 * to list here. Please refer to the COPYRIGHT file distributed with this
8 * source distribution.
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
26 #include "cipher.h"
27 #include "conversation.h"
28 #include "core.h"
29 #include "debug.h"
30 #include "notify.h"
31 #include "ntlm.h"
32 #include "prpl.h"
33 #include "prefs.h"
34 #include "util.h"
36 struct _PurpleUtilFetchUrlData
38 PurpleUtilFetchUrlCallback callback;
39 void *user_data;
41 struct
43 char *user;
44 char *passwd;
45 char *address;
46 int port;
47 char *page;
49 } website;
51 char *url;
52 int num_times_redirected;
53 gboolean full;
54 char *user_agent;
55 gboolean http11;
56 char *request;
57 gsize request_written;
58 gboolean include_headers;
60 gboolean is_ssl;
61 PurpleSslConnection *ssl_connection;
62 PurpleProxyConnectData *connect_data;
63 int fd;
64 guint inpa;
66 gboolean got_headers;
67 gboolean has_explicit_data_len;
68 char *webdata;
69 gsize len;
70 unsigned long data_len;
71 gssize max_len;
72 gboolean chunked;
73 PurpleAccount *account;
76 static char *custom_user_dir = NULL;
77 static char *user_dir = NULL;
80 PurpleMenuAction *
81 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
82 GList *children)
84 PurpleMenuAction *act = g_new0(PurpleMenuAction, 1);
85 act->label = g_strdup(label);
86 act->callback = callback;
87 act->data = data;
88 act->children = children;
89 return act;
92 void
93 purple_menu_action_free(PurpleMenuAction *act)
95 g_return_if_fail(act != NULL);
97 g_free(act->label);
98 g_free(act);
101 void
102 purple_util_init(void)
104 /* This does nothing right now. It exists for symmetry with
105 * purple_util_uninit() and forwards compatibility. */
108 void
109 purple_util_uninit(void)
111 /* Free these so we don't have leaks at shutdown. */
113 g_free(custom_user_dir);
114 custom_user_dir = NULL;
116 g_free(user_dir);
117 user_dir = NULL;
120 /**************************************************************************
121 * Base16 Functions
122 **************************************************************************/
123 gchar *
124 purple_base16_encode(const guchar *data, gsize len)
126 int i;
127 gchar *ascii = NULL;
129 g_return_val_if_fail(data != NULL, NULL);
130 g_return_val_if_fail(len > 0, NULL);
132 ascii = g_malloc(len * 2 + 1);
134 for (i = 0; i < len; i++)
135 g_snprintf(&ascii[i * 2], 3, "%02hhx", data[i]);
137 return ascii;
140 guchar *
141 purple_base16_decode(const char *str, gsize *ret_len)
143 int len, i, accumulator = 0;
144 guchar *data;
146 g_return_val_if_fail(str != NULL, NULL);
148 len = strlen(str);
150 g_return_val_if_fail(strlen(str) > 0, 0);
151 g_return_val_if_fail(len % 2 == 0, 0);
153 data = g_malloc(len / 2);
155 for (i = 0; i < len; i++)
157 if ((i % 2) == 0)
158 accumulator = 0;
159 else
160 accumulator <<= 4;
162 if (isdigit(str[i]))
163 accumulator |= str[i] - 48;
164 else
166 switch(tolower(str[i]))
168 case 'a': accumulator |= 10; break;
169 case 'b': accumulator |= 11; break;
170 case 'c': accumulator |= 12; break;
171 case 'd': accumulator |= 13; break;
172 case 'e': accumulator |= 14; break;
173 case 'f': accumulator |= 15; break;
177 if (i % 2)
178 data[(i - 1) / 2] = accumulator;
181 if (ret_len != NULL)
182 *ret_len = len / 2;
184 return data;
187 gchar *
188 purple_base16_encode_chunked(const guchar *data, gsize len)
190 int i;
191 gchar *ascii = NULL;
193 g_return_val_if_fail(data != NULL, NULL);
194 g_return_val_if_fail(len > 0, NULL);
196 /* For each byte of input, we need 2 bytes for the hex representation
197 * and 1 for the colon.
198 * The final colon will be replaced by a terminating NULL
200 ascii = g_malloc(len * 3 + 1);
202 for (i = 0; i < len; i++)
203 g_snprintf(&ascii[i * 3], 4, "%02hhx:", data[i]);
205 /* Replace the final colon with NULL */
206 ascii[len * 3 - 1] = 0;
208 return ascii;
212 /**************************************************************************
213 * Base64 Functions
214 **************************************************************************/
215 static const char alphabet[] =
216 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
217 "0123456789+/";
219 static const char xdigits[] =
220 "0123456789abcdef";
222 gchar *
223 purple_base64_encode(const guchar *data, gsize len)
225 return g_base64_encode(data, len);
228 guchar *
229 purple_base64_decode(const char *str, gsize *ret_len)
232 * We want to allow ret_len to be NULL for backward compatibility,
233 * but g_base64_decode() requires a valid length variable. So if
234 * ret_len is NULL then pass in a dummy variable.
236 gsize unused;
237 return g_base64_decode(str, ret_len != NULL ? ret_len : &unused);
240 /**************************************************************************
241 * Quoted Printable Functions (see RFC 2045).
242 **************************************************************************/
243 guchar *
244 purple_quotedp_decode(const char *str, gsize *ret_len)
246 char *n, *new;
247 const char *end, *p;
249 n = new = g_malloc(strlen (str) + 1);
250 end = str + strlen(str);
252 for (p = str; p < end; p++, n++) {
253 if (*p == '=') {
254 if (p[1] == '\r' && p[2] == '\n') { /* 5.1 #5 */
255 n -= 1;
256 p += 2;
257 } else if (p[1] == '\n') { /* fuzzy case for 5.1 #5 */
258 n -= 1;
259 p += 1;
260 } else if (p[1] && p[2]) {
261 char *nibble1 = strchr(xdigits, tolower(p[1]));
262 char *nibble2 = strchr(xdigits, tolower(p[2]));
263 if (nibble1 && nibble2) { /* 5.1 #1 */
264 *n = ((nibble1 - xdigits) << 4) | (nibble2 - xdigits);
265 p += 2;
266 } else { /* This should never happen */
267 *n = *p;
269 } else { /* This should never happen */
270 *n = *p;
273 else if (*p == '_')
274 *n = ' ';
275 else
276 *n = *p;
279 *n = '\0';
281 if (ret_len != NULL)
282 *ret_len = n - new;
284 /* Resize to take less space */
285 /* new = realloc(new, n - new); */
287 return (guchar *)new;
290 /**************************************************************************
291 * MIME Functions
292 **************************************************************************/
293 char *
294 purple_mime_decode_field(const char *str)
297 * This is wing's version, partially based on revo/shx's version
298 * See RFC2047 [which apparently obsoletes RFC1342]
300 typedef enum {
301 state_start, state_equal1, state_question1,
302 state_charset, state_question2,
303 state_encoding, state_question3,
304 state_encoded_text, state_question4, state_equal2 = state_start
305 } encoded_word_state_t;
306 encoded_word_state_t state = state_start;
307 const char *cur, *mark;
308 const char *charset0 = NULL, *encoding0 = NULL, *encoded_text0 = NULL;
309 GString *new;
311 /* token can be any CHAR (supposedly ISO8859-1/ISO2022), not just ASCII */
312 #define token_char_p(c) \
313 (c != ' ' && !iscntrl(c) && !strchr("()<>@,;:\"/[]?.=", c))
315 /* But encoded-text must be ASCII; alas, isascii() may not exist */
316 #define encoded_text_char_p(c) \
317 ((c & 0x80) == 0 && c != '?' && c != ' ' && isgraph(c))
319 g_return_val_if_fail(str != NULL, NULL);
321 new = g_string_new(NULL);
323 /* Here we will be looking for encoded words and if they seem to be
324 * valid then decode them.
325 * They are of this form: =?charset?encoding?text?=
328 for (cur = str, mark = NULL; *cur; cur += 1) {
329 switch (state) {
330 case state_equal1:
331 if (*cur == '?') {
332 state = state_question1;
333 } else {
334 g_string_append_len(new, mark, cur - mark + 1);
335 state = state_start;
337 break;
338 case state_question1:
339 if (token_char_p(*cur)) {
340 charset0 = cur;
341 state = state_charset;
342 } else { /* This should never happen */
343 g_string_append_len(new, mark, cur - mark + 1);
344 state = state_start;
346 break;
347 case state_charset:
348 if (*cur == '?') {
349 state = state_question2;
350 } else if (!token_char_p(*cur)) { /* This should never happen */
351 g_string_append_len(new, mark, cur - mark + 1);
352 state = state_start;
354 break;
355 case state_question2:
356 if (token_char_p(*cur)) {
357 encoding0 = cur;
358 state = state_encoding;
359 } else { /* This should never happen */
360 g_string_append_len(new, mark, cur - mark + 1);
361 state = state_start;
363 break;
364 case state_encoding:
365 if (*cur == '?') {
366 state = state_question3;
367 } else if (!token_char_p(*cur)) { /* This should never happen */
368 g_string_append_len(new, mark, cur - mark + 1);
369 state = state_start;
371 break;
372 case state_question3:
373 if (encoded_text_char_p(*cur)) {
374 encoded_text0 = cur;
375 state = state_encoded_text;
376 } else if (*cur == '?') { /* empty string */
377 encoded_text0 = cur;
378 state = state_question4;
379 } else { /* This should never happen */
380 g_string_append_len(new, mark, cur - mark + 1);
381 state = state_start;
383 break;
384 case state_encoded_text:
385 if (*cur == '?') {
386 state = state_question4;
387 } else if (!encoded_text_char_p(*cur)) {
388 g_string_append_len(new, mark, cur - mark + 1);
389 state = state_start;
391 break;
392 case state_question4:
393 if (*cur == '=') { /* Got the whole encoded-word */
394 char *charset = g_strndup(charset0, encoding0 - charset0 - 1);
395 char *encoding = g_strndup(encoding0, encoded_text0 - encoding0 - 1);
396 char *encoded_text = g_strndup(encoded_text0, cur - encoded_text0 - 1);
397 guchar *decoded = NULL;
398 gsize dec_len;
399 if (g_ascii_strcasecmp(encoding, "Q") == 0)
400 decoded = purple_quotedp_decode(encoded_text, &dec_len);
401 else if (g_ascii_strcasecmp(encoding, "B") == 0)
402 decoded = purple_base64_decode(encoded_text, &dec_len);
403 else
404 decoded = NULL;
405 if (decoded) {
406 gsize len;
407 char *converted = g_convert((const gchar *)decoded, dec_len, "utf-8", charset, NULL, &len, NULL);
409 if (converted) {
410 g_string_append_len(new, converted, len);
411 g_free(converted);
413 g_free(decoded);
415 g_free(charset);
416 g_free(encoding);
417 g_free(encoded_text);
418 state = state_equal2; /* Restart the FSM */
419 } else { /* This should never happen */
420 g_string_append_len(new, mark, cur - mark + 1);
421 state = state_start;
423 break;
424 default:
425 if (*cur == '=') {
426 mark = cur;
427 state = state_equal1;
428 } else {
429 /* Some unencoded text. */
430 g_string_append_c(new, *cur);
432 break;
433 } /* switch */
434 } /* for */
436 if (state != state_start)
437 g_string_append_len(new, mark, cur - mark + 1);
439 return g_string_free(new, FALSE);;
443 /**************************************************************************
444 * Date/Time Functions
445 **************************************************************************/
447 const char *purple_get_tzoff_str(const struct tm *tm, gboolean iso)
449 static char buf[7];
450 long off;
451 gint8 min;
452 gint8 hrs;
453 struct tm new_tm = *tm;
455 mktime(&new_tm);
457 if (new_tm.tm_isdst < 0)
458 g_return_val_if_reached("");
460 #ifdef _WIN32
461 if ((off = wpurple_get_tz_offset()) == -1)
462 return "";
463 #else
464 # ifdef HAVE_TM_GMTOFF
465 off = new_tm.tm_gmtoff;
466 # else
467 # ifdef HAVE_TIMEZONE
468 tzset();
469 off = -1 * timezone;
470 # endif /* HAVE_TIMEZONE */
471 # endif /* !HAVE_TM_GMTOFF */
472 #endif /* _WIN32 */
474 min = (off / 60) % 60;
475 hrs = ((off / 60) - min) / 60;
477 if(iso) {
478 if (0 == off) {
479 strcpy(buf, "Z");
480 } else {
481 /* please leave the colons...they're optional for iso, but jabber
482 * wants them */
483 if(g_snprintf(buf, sizeof(buf), "%+03d:%02d", hrs, ABS(min)) > 6)
484 g_return_val_if_reached("");
486 } else {
487 if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5)
488 g_return_val_if_reached("");
491 return buf;
494 /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */
495 #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32)
496 static size_t purple_internal_strftime(char *s, size_t max, const char *format, const struct tm *tm)
498 const char *start;
499 const char *c;
500 char *fmt = NULL;
502 /* Yes, this is checked in purple_utf8_strftime(),
503 * but better safe than sorry. -- rlaager */
504 g_return_val_if_fail(format != NULL, 0);
506 /* This is fairly efficient, and it only gets
507 * executed on Windows or if the underlying
508 * system doesn't support the %z format string,
509 * for strftime() so I think it's good enough.
510 * -- rlaager */
511 for (c = start = format; *c ; c++)
513 if (*c != '%')
514 continue;
516 c++;
518 #ifndef HAVE_STRFTIME_Z_FORMAT
519 if (*c == 'z')
521 char *tmp = g_strdup_printf("%s%.*s%s",
522 fmt ? fmt : "",
523 c - start - 1,
524 start,
525 purple_get_tzoff_str(tm, FALSE));
526 g_free(fmt);
527 fmt = tmp;
528 start = c + 1;
530 #endif
531 #ifdef _WIN32
532 if (*c == 'Z')
534 char *tmp = g_strdup_printf("%s%.*s%s",
535 fmt ? fmt : "",
536 c - start - 1,
537 start,
538 wpurple_get_timezone_abbreviation(tm));
539 g_free(fmt);
540 fmt = tmp;
541 start = c + 1;
543 #endif
546 if (fmt != NULL)
548 size_t ret;
550 if (*start)
552 char *tmp = g_strconcat(fmt, start, NULL);
553 g_free(fmt);
554 fmt = tmp;
557 ret = strftime(s, max, fmt, tm);
558 g_free(fmt);
560 return ret;
563 return strftime(s, max, format, tm);
565 #else /* HAVE_STRFTIME_Z_FORMAT && !_WIN32 */
566 #define purple_internal_strftime strftime
567 #endif
569 const char *
570 purple_utf8_strftime(const char *format, const struct tm *tm)
572 static char buf[128];
573 char *locale;
574 GError *err = NULL;
575 int len;
576 char *utf8;
578 g_return_val_if_fail(format != NULL, NULL);
580 if (tm == NULL)
582 time_t now = time(NULL);
583 tm = localtime(&now);
586 locale = g_locale_from_utf8(format, -1, NULL, NULL, &err);
587 if (err != NULL)
589 purple_debug_error("util", "Format conversion failed in purple_utf8_strftime(): %s\n", err->message);
590 g_error_free(err);
591 err = NULL;
592 locale = g_strdup(format);
595 /* A return value of 0 is either an error (in
596 * which case, the contents of the buffer are
597 * undefined) or the empty string (in which
598 * case, no harm is done here). */
599 if ((len = purple_internal_strftime(buf, sizeof(buf), locale, tm)) == 0)
601 g_free(locale);
602 return "";
605 g_free(locale);
607 utf8 = g_locale_to_utf8(buf, len, NULL, NULL, &err);
608 if (err != NULL)
610 purple_debug_error("util", "Result conversion failed in purple_utf8_strftime(): %s\n", err->message);
611 g_error_free(err);
613 else
615 purple_strlcpy(buf, utf8);
616 g_free(utf8);
619 return buf;
622 const char *
623 purple_date_format_short(const struct tm *tm)
625 return purple_utf8_strftime("%x", tm);
628 const char *
629 purple_date_format_long(const struct tm *tm)
632 * This string determines how some dates are displayed. The default
633 * string "%x %X" shows the date then the time. Translators can
634 * change this to "%X %x" if they want the time to be shown first,
635 * followed by the date.
637 return purple_utf8_strftime(_("%x %X"), tm);
640 const char *
641 purple_date_format_full(const struct tm *tm)
643 return purple_utf8_strftime("%c", tm);
646 const char *
647 purple_time_format(const struct tm *tm)
649 return purple_utf8_strftime("%X", tm);
652 time_t
653 purple_time_build(int year, int month, int day, int hour, int min, int sec)
655 struct tm tm;
657 tm.tm_year = year - 1900;
658 tm.tm_mon = month - 1;
659 tm.tm_mday = day;
660 tm.tm_hour = hour;
661 tm.tm_min = min;
662 tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
664 return mktime(&tm);
667 /* originally taken from GLib trunk 1-6-11 */
668 /* originally licensed as LGPL 2+ */
669 static time_t
670 mktime_utc(struct tm *tm)
672 time_t retval;
674 #ifndef HAVE_TIMEGM
675 static const gint days_before[] =
677 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
679 #endif
681 #ifndef HAVE_TIMEGM
682 if (tm->tm_mon < 0 || tm->tm_mon > 11)
683 return (time_t) -1;
685 retval = (tm->tm_year - 70) * 365;
686 retval += (tm->tm_year - 68) / 4;
687 retval += days_before[tm->tm_mon] + tm->tm_mday - 1;
689 if (tm->tm_year % 4 == 0 && tm->tm_mon < 2)
690 retval -= 1;
692 retval = ((((retval * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec;
693 #else
694 retval = timegm (tm);
695 #endif /* !HAVE_TIMEGM */
697 return retval;
700 time_t
701 purple_str_to_time(const char *timestamp, gboolean utc,
702 struct tm *tm, long *tz_off, const char **rest)
704 struct tm t;
705 const gchar *str;
706 gint year = 0;
707 long tzoff = PURPLE_NO_TZ_OFF;
708 time_t retval;
709 gboolean mktime_with_utc = TRUE;
711 if (rest != NULL)
712 *rest = NULL;
714 g_return_val_if_fail(timestamp != NULL, 0);
716 memset(&t, 0, sizeof(struct tm));
718 str = timestamp;
720 /* Strip leading whitespace */
721 while (g_ascii_isspace(*str))
722 str++;
724 if (*str == '\0') {
725 if (rest != NULL && *str != '\0')
726 *rest = str;
728 return 0;
731 if (!g_ascii_isdigit(*str) && *str != '-' && *str != '+') {
732 if (rest != NULL && *str != '\0')
733 *rest = str;
735 return 0;
738 /* 4 digit year */
739 if (sscanf(str, "%04d", &year) && year >= 1900) {
740 str += 4;
742 if (*str == '-' || *str == '/')
743 str++;
745 t.tm_year = year - 1900;
748 /* 2 digit month */
749 if (!sscanf(str, "%02d", &t.tm_mon)) {
750 if (rest != NULL && *str != '\0')
751 *rest = str;
753 return 0;
756 str += 2;
757 t.tm_mon -= 1;
759 if (*str == '-' || *str == '/')
760 str++;
762 /* 2 digit day */
763 if (!sscanf(str, "%02d", &t.tm_mday)) {
764 if (rest != NULL && *str != '\0')
765 *rest = str;
767 return 0;
770 str += 2;
772 /* Grab the year off the end if there's still stuff */
773 if (*str == '/' || *str == '-') {
774 /* But make sure we don't read the year twice */
775 if (year >= 1900) {
776 if (rest != NULL && *str != '\0')
777 *rest = str;
779 return 0;
782 str++;
784 if (!sscanf(str, "%04d", &t.tm_year)) {
785 if (rest != NULL && *str != '\0')
786 *rest = str;
788 return 0;
791 t.tm_year -= 1900;
792 } else if (*str == 'T' || *str == '.') {
793 str++;
795 /* Continue grabbing the hours/minutes/seconds */
796 if ((sscanf(str, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
797 (str += 8)) ||
798 (sscanf(str, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
799 (str += 6)))
801 gint sign, tzhrs, tzmins;
803 if (*str == '.') {
804 /* Cut off those pesky micro-seconds */
805 do {
806 str++;
807 } while (*str >= '0' && *str <= '9');
810 sign = (*str == '+') ? -1 : 1;
812 /* Process the timezone */
813 if (*str == '+' || *str == '-') {
814 str++;
816 if (((sscanf(str, "%02d:%02d", &tzhrs, &tzmins) == 2 && (str += 5)) ||
817 (sscanf(str, "%02d%02d", &tzhrs, &tzmins) == 2 && (str += 4))))
819 tzoff = tzhrs * 60 * 60 + tzmins * 60;
820 tzoff *= sign;
821 } else {
822 if (rest != NULL && *str != '\0')
823 *rest = str;
825 return 0;
827 } else if (*str == 'Z') {
828 /* 'Z' = Zulu = UTC */
829 str++;
830 utc = TRUE;
831 } else if (!utc) {
832 /* Local Time */
833 t.tm_isdst = -1;
834 mktime_with_utc = FALSE;
837 if (utc)
838 tzoff = 0;
842 if (rest != NULL && *str != '\0') {
843 /* Strip trailing whitespace */
844 while (g_ascii_isspace(*str))
845 str++;
847 if (*str != '\0')
848 *rest = str;
851 if (mktime_with_utc)
852 retval = mktime_utc(&t);
853 else
854 retval = mktime(&t);
856 if (tm != NULL)
857 *tm = t;
859 if (tzoff != PURPLE_NO_TZ_OFF)
860 retval += tzoff;
862 if (tz_off != NULL)
863 *tz_off = tzoff;
865 return retval;
868 /**************************************************************************
869 * Markup Functions
870 **************************************************************************/
873 * This function is stolen from glib's gmarkup.c and modified to not
874 * replace ' with &apos;
876 static void append_escaped_text(GString *str,
877 const gchar *text, gssize length)
879 const gchar *p;
880 const gchar *end;
881 gunichar c;
883 p = text;
884 end = text + length;
886 while (p != end)
888 const gchar *next;
889 next = g_utf8_next_char (p);
891 switch (*p)
893 case '&':
894 g_string_append (str, "&amp;");
895 break;
897 case '<':
898 g_string_append (str, "&lt;");
899 break;
901 case '>':
902 g_string_append (str, "&gt;");
903 break;
905 case '"':
906 g_string_append (str, "&quot;");
907 break;
909 default:
910 c = g_utf8_get_char (p);
911 if ((0x1 <= c && c <= 0x8) ||
912 (0xb <= c && c <= 0xc) ||
913 (0xe <= c && c <= 0x1f) ||
914 (0x7f <= c && c <= 0x84) ||
915 (0x86 <= c && c <= 0x9f))
916 g_string_append_printf (str, "&#x%x;", c);
917 else
918 g_string_append_len (str, p, next - p);
919 break;
922 p = next;
926 /* This function is stolen from glib's gmarkup.c */
927 gchar *purple_markup_escape_text(const gchar *text, gssize length)
929 GString *str;
931 g_return_val_if_fail(text != NULL, NULL);
933 if (length < 0)
934 length = strlen(text);
936 /* prealloc at least as long as original text */
937 str = g_string_sized_new(length);
938 append_escaped_text(str, text, length);
940 return g_string_free(str, FALSE);
943 const char *
944 purple_markup_unescape_entity(const char *text, int *length)
946 const char *pln;
947 int len, pound;
948 char temp[2];
950 if (!text || *text != '&')
951 return NULL;
953 #define IS_ENTITY(s) (!g_ascii_strncasecmp(text, s, (len = sizeof(s) - 1)))
955 if(IS_ENTITY("&amp;"))
956 pln = "&";
957 else if(IS_ENTITY("&lt;"))
958 pln = "<";
959 else if(IS_ENTITY("&gt;"))
960 pln = ">";
961 else if(IS_ENTITY("&nbsp;"))
962 pln = " ";
963 else if(IS_ENTITY("&copy;"))
964 pln = "\302\251"; /* or use g_unichar_to_utf8(0xa9); */
965 else if(IS_ENTITY("&quot;"))
966 pln = "\"";
967 else if(IS_ENTITY("&reg;"))
968 pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */
969 else if(IS_ENTITY("&apos;"))
970 pln = "\'";
971 else if(*(text+1) == '#' &&
972 (sscanf(text, "&#%u%1[;]", &pound, temp) == 2 ||
973 sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
974 pound != 0) {
975 static char buf[7];
976 int buflen = g_unichar_to_utf8((gunichar)pound, buf);
977 buf[buflen] = '\0';
978 pln = buf;
980 len = (*(text+2) == 'x' ? 3 : 2);
981 while(isxdigit((gint) text[len])) len++;
982 if(text[len] == ';') len++;
984 else
985 return NULL;
987 if (length)
988 *length = len;
989 return pln;
992 char *
993 purple_markup_get_css_property(const gchar *style,
994 const gchar *opt)
996 const gchar *css_str = style;
997 const gchar *css_value_start;
998 const gchar *css_value_end;
999 gchar *tmp;
1000 gchar *ret;
1002 g_return_val_if_fail(opt != NULL, NULL);
1004 if (!css_str)
1005 return NULL;
1007 /* find the CSS property */
1008 while (1)
1010 /* skip whitespace characters */
1011 while (*css_str && g_ascii_isspace(*css_str))
1012 css_str++;
1013 if (!g_ascii_isalpha(*css_str))
1014 return NULL;
1015 if (g_ascii_strncasecmp(css_str, opt, strlen(opt)))
1017 /* go to next css property positioned after the next ';' */
1018 while (*css_str && *css_str != '"' && *css_str != ';')
1019 css_str++;
1020 if(*css_str != ';')
1021 return NULL;
1022 css_str++;
1024 else
1025 break;
1028 /* find the CSS value position in the string */
1029 css_str += strlen(opt);
1030 while (*css_str && g_ascii_isspace(*css_str))
1031 css_str++;
1032 if (*css_str != ':')
1033 return NULL;
1034 css_str++;
1035 while (*css_str && g_ascii_isspace(*css_str))
1036 css_str++;
1037 if (*css_str == '\0' || *css_str == '"' || *css_str == ';')
1038 return NULL;
1040 /* mark the CSS value */
1041 css_value_start = css_str;
1042 while (*css_str && *css_str != '"' && *css_str != ';')
1043 css_str++;
1044 css_value_end = css_str - 1;
1046 /* Removes trailing whitespace */
1047 while (css_value_end > css_value_start && g_ascii_isspace(*css_value_end))
1048 css_value_end--;
1050 tmp = g_strndup(css_value_start, css_value_end - css_value_start + 1);
1051 ret = purple_unescape_html(tmp);
1052 g_free(tmp);
1054 return ret;
1057 gboolean purple_markup_is_rtl(const char *html)
1059 GData *attributes;
1060 const gchar *start, *end;
1061 gboolean res = FALSE;
1063 if (purple_markup_find_tag("span", html, &start, &end, &attributes))
1065 /* tmp is a member of attributes and is free with g_datalist_clear call */
1066 const char *tmp = g_datalist_get_data(&attributes, "dir");
1067 if (tmp && !g_ascii_strcasecmp(tmp, "RTL"))
1068 res = TRUE;
1069 if (!res)
1071 tmp = g_datalist_get_data(&attributes, "style");
1072 if (tmp)
1074 char *tmp2 = purple_markup_get_css_property(tmp, "direction");
1075 if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL"))
1076 res = TRUE;
1077 g_free(tmp2);
1081 g_datalist_clear(&attributes);
1083 return res;
1086 gboolean
1087 purple_markup_find_tag(const char *needle, const char *haystack,
1088 const char **start, const char **end, GData **attributes)
1090 GData *attribs;
1091 const char *cur = haystack;
1092 char *name = NULL;
1093 gboolean found = FALSE;
1094 gboolean in_tag = FALSE;
1095 gboolean in_attr = FALSE;
1096 const char *in_quotes = NULL;
1097 size_t needlelen;
1099 g_return_val_if_fail( needle != NULL, FALSE);
1100 g_return_val_if_fail( *needle != '\0', FALSE);
1101 g_return_val_if_fail( haystack != NULL, FALSE);
1102 g_return_val_if_fail( start != NULL, FALSE);
1103 g_return_val_if_fail( end != NULL, FALSE);
1104 g_return_val_if_fail(attributes != NULL, FALSE);
1106 needlelen = strlen(needle);
1107 g_datalist_init(&attribs);
1109 while (*cur && !found) {
1110 if (in_tag) {
1111 if (in_quotes) {
1112 const char *close = cur;
1114 while (*close && *close != *in_quotes)
1115 close++;
1117 /* if we got the close quote, store the value and carry on from *
1118 * after it. if we ran to the end of the string, point to the NULL *
1119 * and we're outta here */
1120 if (*close) {
1121 /* only store a value if we have an attribute name */
1122 if (name) {
1123 size_t len = close - cur;
1124 char *val = g_strndup(cur, len);
1126 g_datalist_set_data_full(&attribs, name, val, g_free);
1127 g_free(name);
1128 name = NULL;
1131 in_quotes = NULL;
1132 cur = close + 1;
1133 } else {
1134 cur = close;
1136 } else if (in_attr) {
1137 const char *close = cur;
1139 while (*close && *close != '>' && *close != '"' &&
1140 *close != '\'' && *close != ' ' && *close != '=')
1141 close++;
1143 /* if we got the equals, store the name of the attribute. if we got
1144 * the quote, save the attribute and go straight to quote mode.
1145 * otherwise the tag closed or we reached the end of the string,
1146 * so we can get outta here */
1147 switch (*close) {
1148 case '"':
1149 case '\'':
1150 in_quotes = close;
1151 case '=':
1153 size_t len = close - cur;
1155 /* don't store a blank attribute name */
1156 if (len) {
1157 g_free(name);
1158 name = g_ascii_strdown(cur, len);
1161 in_attr = FALSE;
1162 cur = close + 1;
1163 break;
1165 case ' ':
1166 case '>':
1167 in_attr = FALSE;
1168 default:
1169 cur = close;
1170 break;
1172 } else {
1173 switch (*cur) {
1174 case ' ':
1175 /* swallow extra spaces inside tag */
1176 while (*cur && *cur == ' ') cur++;
1177 in_attr = TRUE;
1178 break;
1179 case '>':
1180 found = TRUE;
1181 *end = cur;
1182 break;
1183 case '"':
1184 case '\'':
1185 in_quotes = cur;
1186 default:
1187 cur++;
1188 break;
1191 } else {
1192 /* if we hit a < followed by the name of our tag... */
1193 if (*cur == '<' && !g_ascii_strncasecmp(cur + 1, needle, needlelen)) {
1194 *start = cur;
1195 cur = cur + needlelen + 1;
1197 /* if we're pointing at a space or a >, we found the right tag. if *
1198 * we're not, we've found a longer tag, so we need to skip to the *
1199 * >, but not being distracted by >s inside quotes. */
1200 if (*cur == ' ' || *cur == '>') {
1201 in_tag = TRUE;
1202 } else {
1203 while (*cur && *cur != '"' && *cur != '\'' && *cur != '>') {
1204 if (*cur == '"') {
1205 cur++;
1206 while (*cur && *cur != '"')
1207 cur++;
1208 } else if (*cur == '\'') {
1209 cur++;
1210 while (*cur && *cur != '\'')
1211 cur++;
1212 } else {
1213 cur++;
1217 } else {
1218 cur++;
1223 /* clean up any attribute name from a premature termination */
1224 g_free(name);
1226 if (found) {
1227 *attributes = attribs;
1228 } else {
1229 *start = NULL;
1230 *end = NULL;
1231 *attributes = NULL;
1234 return found;
1237 gboolean
1238 purple_markup_extract_info_field(const char *str, int len, PurpleNotifyUserInfo *user_info,
1239 const char *start_token, int skip,
1240 const char *end_token, char check_value,
1241 const char *no_value_token,
1242 const char *display_name, gboolean is_link,
1243 const char *link_prefix,
1244 PurpleInfoFieldFormatCallback format_cb)
1246 const char *p, *q;
1248 g_return_val_if_fail(str != NULL, FALSE);
1249 g_return_val_if_fail(user_info != NULL, FALSE);
1250 g_return_val_if_fail(start_token != NULL, FALSE);
1251 g_return_val_if_fail(end_token != NULL, FALSE);
1252 g_return_val_if_fail(display_name != NULL, FALSE);
1254 p = strstr(str, start_token);
1256 if (p == NULL)
1257 return FALSE;
1259 p += strlen(start_token) + skip;
1261 if (p >= str + len)
1262 return FALSE;
1264 if (check_value != '\0' && *p == check_value)
1265 return FALSE;
1267 q = strstr(p, end_token);
1269 /* Trim leading blanks */
1270 while (*p != '\n' && g_ascii_isspace(*p)) {
1271 p += 1;
1274 /* Trim trailing blanks */
1275 while (q > p && g_ascii_isspace(*(q - 1))) {
1276 q -= 1;
1279 /* Don't bother with null strings */
1280 if (p == q)
1281 return FALSE;
1283 if (q != NULL && (!no_value_token ||
1284 (no_value_token && strncmp(p, no_value_token,
1285 strlen(no_value_token)))))
1287 GString *dest = g_string_new("");
1289 if (is_link)
1291 g_string_append(dest, "<a href=\"");
1293 if (link_prefix)
1294 g_string_append(dest, link_prefix);
1296 if (format_cb != NULL)
1298 char *reformatted = format_cb(p, q - p);
1299 g_string_append(dest, reformatted);
1300 g_free(reformatted);
1302 else
1303 g_string_append_len(dest, p, q - p);
1304 g_string_append(dest, "\">");
1306 if (link_prefix)
1307 g_string_append(dest, link_prefix);
1309 g_string_append_len(dest, p, q - p);
1310 g_string_append(dest, "</a>");
1312 else
1314 if (format_cb != NULL)
1316 char *reformatted = format_cb(p, q - p);
1317 g_string_append(dest, reformatted);
1318 g_free(reformatted);
1320 else
1321 g_string_append_len(dest, p, q - p);
1324 purple_notify_user_info_add_pair(user_info, display_name, dest->str);
1325 g_string_free(dest, TRUE);
1327 return TRUE;
1330 return FALSE;
1333 struct purple_parse_tag {
1334 char *src_tag;
1335 char *dest_tag;
1336 gboolean ignore;
1339 /* NOTE: Do not put `do {} while(0)` around this macro (as this is the method
1340 recommended in the GCC docs). It contains 'continue's that should
1341 affect the while-loop in purple_markup_html_to_xhtml and doing the
1342 above would break that.
1343 Also, remember to put braces in constructs that require them for
1344 multiple statements when using this macro. */
1345 #define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \
1346 const char *o = c + strlen("<" x); \
1347 const char *p = NULL, *q = NULL, *r = NULL; \
1348 /* o = iterating over full tag \
1349 * p = > (end of tag) \
1350 * q = start of quoted bit \
1351 * r = < inside tag \
1352 */ \
1353 GString *innards = g_string_new(""); \
1354 while(o && *o) { \
1355 if(!q && (*o == '\"' || *o == '\'') ) { \
1356 q = o; \
1357 } else if(q) { \
1358 if(*o == *q) { /* end of quoted bit */ \
1359 char *unescaped = g_strndup(q+1, o-q-1); \
1360 char *escaped = g_markup_escape_text(unescaped, -1); \
1361 g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \
1362 g_free(unescaped); \
1363 g_free(escaped); \
1364 q = NULL; \
1365 } else if(*c == '\\') { \
1366 o++; \
1368 } else if(*o == '<') { \
1369 r = o; \
1370 } else if(*o == '>') { \
1371 p = o; \
1372 break; \
1373 } else { \
1374 innards = g_string_append_c(innards, *o); \
1376 o++; \
1378 if(p && !r) { /* got an end of tag and no other < earlier */\
1379 if(*(p-1) != '/') { \
1380 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1381 pt->src_tag = x; \
1382 pt->dest_tag = y; \
1383 tags = g_list_prepend(tags, pt); \
1385 if(xhtml) { \
1386 xhtml = g_string_append(xhtml, "<" y); \
1387 xhtml = g_string_append(xhtml, innards->str); \
1388 xhtml = g_string_append_c(xhtml, '>'); \
1390 c = p + 1; \
1391 } else { /* got end of tag with earlier < *or* didn't get anything */ \
1392 if(xhtml) \
1393 xhtml = g_string_append(xhtml, "&lt;"); \
1394 if(plain) \
1395 plain = g_string_append_c(plain, '<'); \
1396 c++; \
1398 g_string_free(innards, TRUE); \
1399 continue; \
1401 if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \
1402 (*(c+strlen("<" x)) == '>' || \
1403 !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \
1404 if(xhtml) \
1405 xhtml = g_string_append(xhtml, "<" y); \
1406 c += strlen("<" x); \
1407 if(*c != '/') { \
1408 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1409 pt->src_tag = x; \
1410 pt->dest_tag = y; \
1411 tags = g_list_prepend(tags, pt); \
1412 if(xhtml) \
1413 xhtml = g_string_append_c(xhtml, '>'); \
1414 } else { \
1415 if(xhtml) \
1416 xhtml = g_string_append(xhtml, "/>");\
1418 c = strchr(c, '>') + 1; \
1419 continue; \
1421 /* Don't forget to check the note above for ALLOW_TAG_ALT. */
1422 #define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x)
1423 void
1424 purple_markup_html_to_xhtml(const char *html, char **xhtml_out,
1425 char **plain_out)
1427 GString *xhtml = NULL;
1428 GString *plain = NULL;
1429 GString *url = NULL;
1430 GString *cdata = NULL;
1431 GList *tags = NULL, *tag;
1432 const char *c = html;
1433 char quote = '\0';
1435 #define CHECK_QUOTE(ptr) if (*(ptr) == '\'' || *(ptr) == '\"') \
1436 quote = *(ptr++); \
1437 else \
1438 quote = '\0';
1440 #define VALID_CHAR(ptr) (*(ptr) && *(ptr) != quote && (quote || (*(ptr) != ' ' && *(ptr) != '>')))
1442 g_return_if_fail(xhtml_out != NULL || plain_out != NULL);
1444 if(xhtml_out)
1445 xhtml = g_string_new("");
1446 if(plain_out)
1447 plain = g_string_new("");
1449 while(c && *c) {
1450 if(*c == '<') {
1451 if(*(c+1) == '/') { /* closing tag */
1452 tag = tags;
1453 while(tag) {
1454 struct purple_parse_tag *pt = tag->data;
1455 if(!g_ascii_strncasecmp((c+2), pt->src_tag, strlen(pt->src_tag)) && *(c+strlen(pt->src_tag)+2) == '>') {
1456 c += strlen(pt->src_tag) + 3;
1457 break;
1459 tag = tag->next;
1461 if(tag) {
1462 while(tags) {
1463 struct purple_parse_tag *pt = tags->data;
1464 if(xhtml && !pt->ignore)
1465 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
1466 if(plain && purple_strequal(pt->src_tag, "a")) {
1467 /* if this is a link, we have to add the url to the plaintext, too */
1468 if (cdata && url &&
1469 (!g_string_equal(cdata, url) && (g_ascii_strncasecmp(url->str, "mailto:", 7) != 0 ||
1470 g_utf8_collate(url->str + 7, cdata->str) != 0)))
1471 g_string_append_printf(plain, " <%s>", g_strstrip(url->str));
1472 if (cdata) {
1473 g_string_free(cdata, TRUE);
1474 cdata = NULL;
1478 if(tags == tag)
1479 break;
1480 tags = g_list_remove(tags, pt);
1481 g_free(pt);
1483 g_free(tag->data);
1484 tags = g_list_remove(tags, tag->data);
1485 } else {
1486 /* a closing tag we weren't expecting...
1487 * we'll let it slide, if it's really a tag...if it's
1488 * just a </ we'll escape it properly */
1489 const char *end = c+2;
1490 while(*end && g_ascii_isalpha(*end))
1491 end++;
1492 if(*end == '>') {
1493 c = end+1;
1494 } else {
1495 if(xhtml)
1496 xhtml = g_string_append(xhtml, "&lt;");
1497 if(plain)
1498 plain = g_string_append_c(plain, '<');
1499 c++;
1502 } else { /* opening tag */
1503 ALLOW_TAG("blockquote");
1504 ALLOW_TAG("cite");
1505 ALLOW_TAG("div");
1506 ALLOW_TAG("em");
1507 ALLOW_TAG("h1");
1508 ALLOW_TAG("h2");
1509 ALLOW_TAG("h3");
1510 ALLOW_TAG("h4");
1511 ALLOW_TAG("h5");
1512 ALLOW_TAG("h6");
1513 /* we only allow html to start the message */
1514 if(c == html) {
1515 ALLOW_TAG("html");
1517 ALLOW_TAG_ALT("i", "em");
1518 ALLOW_TAG_ALT("italic", "em");
1519 ALLOW_TAG("li");
1520 ALLOW_TAG("ol");
1521 ALLOW_TAG("p");
1522 ALLOW_TAG("pre");
1523 ALLOW_TAG("q");
1524 ALLOW_TAG("span");
1525 ALLOW_TAG("ul");
1528 /* we skip <HR> because it's not legal in XHTML-IM. However,
1529 * we still want to send something sensible, so we put a
1530 * linebreak in its place. <BR> also needs special handling
1531 * because putting a </BR> to close it would just be dumb. */
1532 if((!g_ascii_strncasecmp(c, "<br", 3)
1533 || !g_ascii_strncasecmp(c, "<hr", 3))
1534 && (*(c+3) == '>' ||
1535 !g_ascii_strncasecmp(c+3, "/>", 2) ||
1536 !g_ascii_strncasecmp(c+3, " />", 3))) {
1537 c = strchr(c, '>') + 1;
1538 if(xhtml)
1539 xhtml = g_string_append(xhtml, "<br/>");
1540 if(plain && *c != '\n')
1541 plain = g_string_append_c(plain, '\n');
1542 continue;
1544 if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c, "<strong>", strlen("<strong>"))) {
1545 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1546 if (*(c+2) == '>')
1547 pt->src_tag = "b";
1548 else if (*(c+2) == 'o')
1549 pt->src_tag = "bold";
1550 else
1551 pt->src_tag = "strong";
1552 pt->dest_tag = "span";
1553 tags = g_list_prepend(tags, pt);
1554 c = strchr(c, '>') + 1;
1555 if(xhtml)
1556 xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>");
1557 continue;
1559 if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) {
1560 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1561 pt->src_tag = *(c+2) == '>' ? "u" : "underline";
1562 pt->dest_tag = "span";
1563 tags = g_list_prepend(tags, pt);
1564 c = strchr(c, '>') + 1;
1565 if (xhtml)
1566 xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>");
1567 continue;
1569 if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) {
1570 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1571 pt->src_tag = *(c+2) == '>' ? "s" : "strike";
1572 pt->dest_tag = "span";
1573 tags = g_list_prepend(tags, pt);
1574 c = strchr(c, '>') + 1;
1575 if(xhtml)
1576 xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>");
1577 continue;
1579 if(!g_ascii_strncasecmp(c, "<sub>", 5)) {
1580 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1581 pt->src_tag = "sub";
1582 pt->dest_tag = "span";
1583 tags = g_list_prepend(tags, pt);
1584 c = strchr(c, '>') + 1;
1585 if(xhtml)
1586 xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>");
1587 continue;
1589 if(!g_ascii_strncasecmp(c, "<sup>", 5)) {
1590 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1591 pt->src_tag = "sup";
1592 pt->dest_tag = "span";
1593 tags = g_list_prepend(tags, pt);
1594 c = strchr(c, '>') + 1;
1595 if(xhtml)
1596 xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>");
1597 continue;
1599 if (!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) {
1600 const char *p = c + 4;
1601 GString *src = NULL, *alt = NULL;
1602 while (*p && *p != '>') {
1603 if (!g_ascii_strncasecmp(p, "src=", 4)) {
1604 const char *q = p + 4;
1605 if (src)
1606 g_string_free(src, TRUE);
1607 src = g_string_new("");
1608 CHECK_QUOTE(q);
1609 while (VALID_CHAR(q)) {
1610 src = g_string_append_c(src, *q);
1611 q++;
1613 p = q;
1614 } else if (!g_ascii_strncasecmp(p, "alt=", 4)) {
1615 const char *q = p + 4;
1616 if (alt)
1617 g_string_free(alt, TRUE);
1618 alt = g_string_new("");
1619 CHECK_QUOTE(q);
1620 while (VALID_CHAR(q)) {
1621 alt = g_string_append_c(alt, *q);
1622 q++;
1624 p = q;
1625 } else {
1626 p++;
1629 if ((c = strchr(p, '>')) != NULL)
1630 c++;
1631 else
1632 c = p;
1633 /* src and alt are required! */
1634 if(src && xhtml)
1635 g_string_append_printf(xhtml, "<img src='%s' alt='%s' />", g_strstrip(src->str), alt ? alt->str : "");
1636 if(alt) {
1637 if(plain)
1638 plain = g_string_append(plain, alt->str);
1639 if(!src && xhtml)
1640 xhtml = g_string_append(xhtml, alt->str);
1641 g_string_free(alt, TRUE);
1643 g_string_free(src, TRUE);
1644 continue;
1646 if (!g_ascii_strncasecmp(c, "<a", 2) && (*(c+2) == '>' || *(c+2) == ' ')) {
1647 const char *p = c + 2;
1648 struct purple_parse_tag *pt;
1649 while (*p && *p != '>') {
1650 if (!g_ascii_strncasecmp(p, "href=", 5)) {
1651 const char *q = p + 5;
1652 if (url)
1653 g_string_free(url, TRUE);
1654 url = g_string_new("");
1655 if (cdata)
1656 g_string_free(cdata, TRUE);
1657 cdata = g_string_new("");
1658 CHECK_QUOTE(q);
1659 while (VALID_CHAR(q)) {
1660 int len;
1661 if ((*q == '&') && (purple_markup_unescape_entity(q, &len) == NULL))
1662 url = g_string_append(url, "&amp;");
1663 else
1664 url = g_string_append_c(url, *q);
1665 q++;
1667 p = q;
1668 } else {
1669 p++;
1672 if ((c = strchr(p, '>')) != NULL)
1673 c++;
1674 else
1675 c = p;
1676 pt = g_new0(struct purple_parse_tag, 1);
1677 pt->src_tag = "a";
1678 pt->dest_tag = "a";
1679 tags = g_list_prepend(tags, pt);
1680 if(xhtml)
1681 g_string_append_printf(xhtml, "<a href=\"%s\">", url ? g_strstrip(url->str) : "");
1682 continue;
1684 if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) {
1685 const char *p = c + 5;
1686 GString *style = g_string_new("");
1687 struct purple_parse_tag *pt;
1688 while (*p && *p != '>') {
1689 if (!g_ascii_strncasecmp(p, "back=", 5)) {
1690 const char *q = p + 5;
1691 GString *color = g_string_new("");
1692 CHECK_QUOTE(q);
1693 while (VALID_CHAR(q)) {
1694 color = g_string_append_c(color, *q);
1695 q++;
1697 g_string_append_printf(style, "background: %s; ", color->str);
1698 g_string_free(color, TRUE);
1699 p = q;
1700 } else if (!g_ascii_strncasecmp(p, "color=", 6)) {
1701 const char *q = p + 6;
1702 GString *color = g_string_new("");
1703 CHECK_QUOTE(q);
1704 while (VALID_CHAR(q)) {
1705 color = g_string_append_c(color, *q);
1706 q++;
1708 g_string_append_printf(style, "color: %s; ", color->str);
1709 g_string_free(color, TRUE);
1710 p = q;
1711 } else if (!g_ascii_strncasecmp(p, "face=", 5)) {
1712 const char *q = p + 5;
1713 GString *face = g_string_new("");
1714 CHECK_QUOTE(q);
1715 while (VALID_CHAR(q)) {
1716 face = g_string_append_c(face, *q);
1717 q++;
1719 g_string_append_printf(style, "font-family: %s; ", g_strstrip(face->str));
1720 g_string_free(face, TRUE);
1721 p = q;
1722 } else if (!g_ascii_strncasecmp(p, "size=", 5)) {
1723 const char *q = p + 5;
1724 int sz;
1725 const char *size = "medium";
1726 CHECK_QUOTE(q);
1727 sz = atoi(q);
1728 switch (sz)
1730 case 1:
1731 size = "xx-small";
1732 break;
1733 case 2:
1734 size = "small";
1735 break;
1736 case 3:
1737 size = "medium";
1738 break;
1739 case 4:
1740 size = "large";
1741 break;
1742 case 5:
1743 size = "x-large";
1744 break;
1745 case 6:
1746 case 7:
1747 size = "xx-large";
1748 break;
1749 default:
1750 break;
1752 g_string_append_printf(style, "font-size: %s; ", size);
1753 p = q;
1754 } else {
1755 p++;
1758 if ((c = strchr(p, '>')) != NULL)
1759 c++;
1760 else
1761 c = p;
1762 pt = g_new0(struct purple_parse_tag, 1);
1763 pt->src_tag = "font";
1764 pt->dest_tag = "span";
1765 tags = g_list_prepend(tags, pt);
1766 if(style->len && xhtml)
1767 g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str));
1768 else
1769 pt->ignore = TRUE;
1770 g_string_free(style, TRUE);
1771 continue;
1773 if (!g_ascii_strncasecmp(c, "<body ", 6)) {
1774 const char *p = c + 6;
1775 gboolean did_something = FALSE;
1776 while (*p && *p != '>') {
1777 if (!g_ascii_strncasecmp(p, "bgcolor=", 8)) {
1778 const char *q = p + 8;
1779 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1780 GString *color = g_string_new("");
1781 CHECK_QUOTE(q);
1782 while (VALID_CHAR(q)) {
1783 color = g_string_append_c(color, *q);
1784 q++;
1786 if (xhtml)
1787 g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str));
1788 g_string_free(color, TRUE);
1789 if ((c = strchr(p, '>')) != NULL)
1790 c++;
1791 else
1792 c = p;
1793 pt->src_tag = "body";
1794 pt->dest_tag = "span";
1795 tags = g_list_prepend(tags, pt);
1796 did_something = TRUE;
1797 break;
1799 p++;
1801 if (did_something) continue;
1803 /* this has to come after the special case for bgcolor */
1804 ALLOW_TAG("body");
1805 if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) {
1806 char *p = strstr(c + strlen("<!--"), "-->");
1807 if(p) {
1808 if(xhtml)
1809 xhtml = g_string_append(xhtml, "<!--");
1810 c += strlen("<!--");
1811 continue;
1815 if(xhtml)
1816 xhtml = g_string_append(xhtml, "&lt;");
1817 if(plain)
1818 plain = g_string_append_c(plain, '<');
1819 c++;
1821 } else if(*c == '&') {
1822 char buf[7];
1823 const char *pln;
1824 int len;
1826 if ((pln = purple_markup_unescape_entity(c, &len)) == NULL) {
1827 len = 1;
1828 g_snprintf(buf, sizeof(buf), "%c", *c);
1829 pln = buf;
1831 if(xhtml)
1832 xhtml = g_string_append_len(xhtml, c, len);
1833 if(plain)
1834 plain = g_string_append(plain, pln);
1835 if(cdata)
1836 cdata = g_string_append_len(cdata, c, len);
1837 c += len;
1838 } else {
1839 if(xhtml)
1840 xhtml = g_string_append_c(xhtml, *c);
1841 if(plain)
1842 plain = g_string_append_c(plain, *c);
1843 if(cdata)
1844 cdata = g_string_append_c(cdata, *c);
1845 c++;
1848 if(xhtml) {
1849 for (tag = tags; tag ; tag = tag->next) {
1850 struct purple_parse_tag *pt = tag->data;
1851 if(!pt->ignore)
1852 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
1855 g_list_free(tags);
1856 if(xhtml_out)
1857 *xhtml_out = g_string_free(xhtml, FALSE);
1858 if(plain_out)
1859 *plain_out = g_string_free(plain, FALSE);
1860 if(url)
1861 g_string_free(url, TRUE);
1862 if (cdata)
1863 g_string_free(cdata, TRUE);
1864 #undef CHECK_QUOTE
1865 #undef VALID_CHAR
1868 /* The following are probably reasonable changes:
1869 * - \n should be converted to a normal space
1870 * - in addition to <br>, <p> and <div> etc. should also be converted into \n
1871 * - We want to turn </td>#whitespace<td> sequences into a single tab
1872 * - We want to turn <td> into a single tab (for msn profile "parsing")
1873 * - We want to turn </tr>#whitespace<tr> sequences into a single \n
1874 * - <script>...</script> and <style>...</style> should be completely removed
1877 char *
1878 purple_markup_strip_html(const char *str)
1880 int i, j, k, entlen;
1881 gboolean visible = TRUE;
1882 gboolean closing_td_p = FALSE;
1883 gchar *str2;
1884 const gchar *cdata_close_tag = NULL, *ent;
1885 gchar *href = NULL;
1886 int href_st = 0;
1888 if(!str)
1889 return NULL;
1891 str2 = g_strdup(str);
1893 for (i = 0, j = 0; str2[i]; i++)
1895 if (str2[i] == '<')
1897 if (cdata_close_tag)
1899 /* Note: Don't even assume any other tag is a tag in CDATA */
1900 if (g_ascii_strncasecmp(str2 + i, cdata_close_tag,
1901 strlen(cdata_close_tag)) == 0)
1903 i += strlen(cdata_close_tag) - 1;
1904 cdata_close_tag = NULL;
1906 continue;
1908 else if (g_ascii_strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p)
1910 str2[j++] = '\t';
1911 visible = TRUE;
1913 else if (g_ascii_strncasecmp(str2 + i, "</td>", 5) == 0)
1915 closing_td_p = TRUE;
1916 visible = FALSE;
1918 else
1920 closing_td_p = FALSE;
1921 visible = TRUE;
1924 k = i + 1;
1926 if(g_ascii_isspace(str2[k]))
1927 visible = TRUE;
1928 else if (str2[k])
1930 /* Scan until we end the tag either implicitly (closed start
1931 * tag) or explicitly, using a sloppy method (i.e., < or >
1932 * inside quoted attributes will screw us up)
1934 while (str2[k] && str2[k] != '<' && str2[k] != '>')
1936 k++;
1939 /* If we've got an <a> tag with an href, save the address
1940 * to print later. */
1941 if (g_ascii_strncasecmp(str2 + i, "<a", 2) == 0 &&
1942 g_ascii_isspace(str2[i+2]))
1944 int st; /* start of href, inclusive [ */
1945 int end; /* end of href, exclusive ) */
1946 char delim = ' ';
1947 /* Find start of href */
1948 for (st = i + 3; st < k; st++)
1950 if (g_ascii_strncasecmp(str2+st, "href=", 5) == 0)
1952 st += 5;
1953 if (str2[st] == '"' || str2[st] == '\'')
1955 delim = str2[st];
1956 st++;
1958 break;
1961 /* find end of address */
1962 for (end = st; end < k && str2[end] != delim; end++)
1964 /* All the work is done in the loop construct above. */
1967 /* If there's an address, save it. If there was
1968 * already one saved, kill it. */
1969 if (st < k)
1971 char *tmp;
1972 g_free(href);
1973 tmp = g_strndup(str2 + st, end - st);
1974 href = purple_unescape_html(tmp);
1975 g_free(tmp);
1976 href_st = j;
1980 /* Replace </a> with an ascii representation of the
1981 * address the link was pointing to. */
1982 else if (href != NULL && g_ascii_strncasecmp(str2 + i, "</a>", 4) == 0)
1985 size_t hrlen = strlen(href);
1987 /* Only insert the href if it's different from the CDATA. */
1988 if ((hrlen != j - href_st ||
1989 strncmp(str2 + href_st, href, hrlen)) &&
1990 (hrlen != j - href_st + 7 || /* 7 == strlen("http://") */
1991 strncmp(str2 + href_st, href + 7, hrlen - 7)))
1993 str2[j++] = ' ';
1994 str2[j++] = '(';
1995 g_memmove(str2 + j, href, hrlen);
1996 j += hrlen;
1997 str2[j++] = ')';
1998 g_free(href);
1999 href = NULL;
2003 /* Check for tags which should be mapped to newline (but ignore some of
2004 * the tags at the beginning of the text) */
2005 else if ((j && (g_ascii_strncasecmp(str2 + i, "<p>", 3) == 0
2006 || g_ascii_strncasecmp(str2 + i, "<tr", 3) == 0
2007 || g_ascii_strncasecmp(str2 + i, "<hr", 3) == 0
2008 || g_ascii_strncasecmp(str2 + i, "<li", 3) == 0
2009 || g_ascii_strncasecmp(str2 + i, "<div", 4) == 0))
2010 || g_ascii_strncasecmp(str2 + i, "<br", 3) == 0
2011 || g_ascii_strncasecmp(str2 + i, "</table>", 8) == 0)
2013 str2[j++] = '\n';
2015 /* Check for tags which begin CDATA and need to be closed */
2016 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */
2017 else if (g_ascii_strncasecmp(str2 + i, "<option", 7) == 0)
2019 /* FIXME: We should not do this if the OPTION is SELECT'd */
2020 cdata_close_tag = "</option>";
2022 #endif
2023 else if (g_ascii_strncasecmp(str2 + i, "<script", 7) == 0)
2025 cdata_close_tag = "</script>";
2027 else if (g_ascii_strncasecmp(str2 + i, "<style", 6) == 0)
2029 cdata_close_tag = "</style>";
2031 /* Update the index and continue checking after the tag */
2032 i = (str2[k] == '<' || str2[k] == '\0')? k - 1: k;
2033 continue;
2036 else if (cdata_close_tag)
2038 continue;
2040 else if (!g_ascii_isspace(str2[i]))
2042 visible = TRUE;
2045 if (str2[i] == '&' && (ent = purple_markup_unescape_entity(str2 + i, &entlen)) != NULL)
2047 while (*ent)
2048 str2[j++] = *ent++;
2049 i += entlen - 1;
2050 continue;
2053 if (visible)
2054 str2[j++] = g_ascii_isspace(str2[i])? ' ': str2[i];
2057 g_free(href);
2059 str2[j] = '\0';
2061 return str2;
2064 static gboolean
2065 badchar(char c)
2067 switch (c) {
2068 case ' ':
2069 case ',':
2070 case '\0':
2071 case '\n':
2072 case '\r':
2073 case '<':
2074 case '>':
2075 case '"':
2076 return TRUE;
2077 default:
2078 return FALSE;
2082 static gboolean
2083 badentity(const char *c)
2085 if (!g_ascii_strncasecmp(c, "&lt;", 4) ||
2086 !g_ascii_strncasecmp(c, "&gt;", 4) ||
2087 !g_ascii_strncasecmp(c, "&quot;", 6)) {
2088 return TRUE;
2090 return FALSE;
2093 static const char *
2094 process_link(GString *ret,
2095 const char *start, const char *c,
2096 int matchlen,
2097 const char *urlprefix,
2098 int inside_paren)
2100 char *url_buf, *tmpurlbuf;
2101 const char *t;
2103 for (t = c;; t++) {
2104 if (!badchar(*t) && !badentity(t))
2105 continue;
2107 if (t - c == matchlen)
2108 break;
2110 if (*t == ',' && *(t + 1) != ' ') {
2111 continue;
2114 if (t > start && *(t - 1) == '.')
2115 t--;
2116 if (t > start && *(t - 1) == ')' && inside_paren > 0)
2117 t--;
2119 url_buf = g_strndup(c, t - c);
2120 tmpurlbuf = purple_unescape_html(url_buf);
2121 g_string_append_printf(ret, "<A HREF=\"%s%s\">%s</A>",
2122 urlprefix,
2123 tmpurlbuf, url_buf);
2124 g_free(tmpurlbuf);
2125 g_free(url_buf);
2126 return t;
2129 return c;
2132 char *
2133 purple_markup_linkify(const char *text)
2135 const char *c, *t, *q = NULL;
2136 char *tmpurlbuf, *url_buf;
2137 gunichar g;
2138 gboolean inside_html = FALSE;
2139 int inside_paren = 0;
2140 GString *ret;
2142 if (text == NULL)
2143 return NULL;
2145 ret = g_string_new("");
2147 c = text;
2148 while (*c) {
2150 if(*c == '(' && !inside_html) {
2151 inside_paren++;
2152 ret = g_string_append_c(ret, *c);
2153 c++;
2156 if(inside_html) {
2157 if(*c == '>') {
2158 inside_html = FALSE;
2159 } else if(!q && (*c == '\"' || *c == '\'')) {
2160 q = c;
2161 } else if(q) {
2162 if(*c == *q)
2163 q = NULL;
2165 } else if(*c == '<') {
2166 inside_html = TRUE;
2167 if (!g_ascii_strncasecmp(c, "<A", 2)) {
2168 while (1) {
2169 if (!g_ascii_strncasecmp(c, "/A>", 3)) {
2170 inside_html = FALSE;
2171 break;
2173 ret = g_string_append_c(ret, *c);
2174 c++;
2175 if (!(*c))
2176 break;
2179 } else if (!g_ascii_strncasecmp(c, "http://", 7)) {
2180 c = process_link(ret, text, c, 7, "", inside_paren);
2181 } else if (!g_ascii_strncasecmp(c, "https://", 8)) {
2182 c = process_link(ret, text, c, 8, "", inside_paren);
2183 } else if (!g_ascii_strncasecmp(c, "ftp://", 6)) {
2184 c = process_link(ret, text, c, 6, "", inside_paren);
2185 } else if (!g_ascii_strncasecmp(c, "sftp://", 7)) {
2186 c = process_link(ret, text, c, 7, "", inside_paren);
2187 } else if (!g_ascii_strncasecmp(c, "file://", 7)) {
2188 c = process_link(ret, text, c, 7, "", inside_paren);
2189 } else if (!g_ascii_strncasecmp(c, "www.", 4) && c[4] != '.' && (c == text || badchar(c[-1]) || badentity(c-1))) {
2190 c = process_link(ret, text, c, 4, "http://", inside_paren);
2191 } else if (!g_ascii_strncasecmp(c, "ftp.", 4) && c[4] != '.' && (c == text || badchar(c[-1]) || badentity(c-1))) {
2192 c = process_link(ret, text, c, 4, "ftp://", inside_paren);
2193 } else if (!g_ascii_strncasecmp(c, "xmpp:", 5) && (c == text || badchar(c[-1]) || badentity(c-1))) {
2194 c = process_link(ret, text, c, 5, "", inside_paren);
2195 } else if (!g_ascii_strncasecmp(c, "mailto:", 7)) {
2196 t = c;
2197 while (1) {
2198 if (badchar(*t) || badentity(t)) {
2199 char *d;
2200 if (t - c == 7) {
2201 break;
2203 if (t > text && *(t - 1) == '.')
2204 t--;
2205 if ((d = strstr(c + 7, "?")) != NULL && d < t)
2206 url_buf = g_strndup(c + 7, d - c - 7);
2207 else
2208 url_buf = g_strndup(c + 7, t - c - 7);
2209 if (!purple_email_is_valid(url_buf)) {
2210 g_free(url_buf);
2211 break;
2213 g_free(url_buf);
2214 url_buf = g_strndup(c, t - c);
2215 tmpurlbuf = purple_unescape_html(url_buf);
2216 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2217 tmpurlbuf, url_buf);
2218 g_free(url_buf);
2219 g_free(tmpurlbuf);
2220 c = t;
2221 break;
2223 t++;
2225 } else if (c != text && (*c == '@')) {
2226 int flag;
2227 GString *gurl_buf = NULL;
2228 const char illegal_chars[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0";
2230 if (strchr(illegal_chars,*(c - 1)) || strchr(illegal_chars, *(c + 1)))
2231 flag = 0;
2232 else {
2233 flag = 1;
2234 gurl_buf = g_string_new("");
2237 t = c;
2238 while (flag) {
2239 /* iterate backwards grabbing the local part of an email address */
2240 g = g_utf8_get_char(t);
2241 if (badchar(*t) || (g >= 127) || (*t == '(') ||
2242 ((*t == ';') && ((t > (text+2) && (!g_ascii_strncasecmp(t - 3, "&lt;", 4) ||
2243 !g_ascii_strncasecmp(t - 3, "&gt;", 4))) ||
2244 (t > (text+4) && (!g_ascii_strncasecmp(t - 5, "&quot;", 6)))))) {
2245 /* local part will already be part of ret, strip it out */
2246 ret = g_string_truncate(ret, ret->len - (c - t));
2247 ret = g_string_append_unichar(ret, g);
2248 break;
2249 } else {
2250 g_string_prepend_unichar(gurl_buf, g);
2251 t = g_utf8_find_prev_char(text, t);
2252 if (t < text) {
2253 ret = g_string_assign(ret, "");
2254 break;
2259 t = g_utf8_find_next_char(c, NULL);
2261 while (flag) {
2262 /* iterate forwards grabbing the domain part of an email address */
2263 g = g_utf8_get_char(t);
2264 if (badchar(*t) || (g >= 127) || (*t == ')') || badentity(t)) {
2265 char *d;
2267 url_buf = g_string_free(gurl_buf, FALSE);
2269 /* strip off trailing periods */
2270 if (strlen(url_buf) > 0) {
2271 for (d = url_buf + strlen(url_buf) - 1; *d == '.'; d--, t--)
2272 *d = '\0';
2275 tmpurlbuf = purple_unescape_html(url_buf);
2276 if (purple_email_is_valid(tmpurlbuf)) {
2277 g_string_append_printf(ret, "<A HREF=\"mailto:%s\">%s</A>",
2278 tmpurlbuf, url_buf);
2279 } else {
2280 g_string_append(ret, url_buf);
2282 g_free(url_buf);
2283 g_free(tmpurlbuf);
2284 c = t;
2286 break;
2287 } else {
2288 g_string_append_unichar(gurl_buf, g);
2289 t = g_utf8_find_next_char(t, NULL);
2294 if(*c == ')' && !inside_html) {
2295 inside_paren--;
2296 ret = g_string_append_c(ret, *c);
2297 c++;
2300 if (*c == 0)
2301 break;
2303 ret = g_string_append_c(ret, *c);
2304 c++;
2307 return g_string_free(ret, FALSE);
2310 char *purple_unescape_text(const char *in)
2312 GString *ret;
2313 const char *c = in;
2315 if (in == NULL)
2316 return NULL;
2318 ret = g_string_new("");
2319 while (*c) {
2320 int len;
2321 const char *ent;
2323 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2324 g_string_append(ret, ent);
2325 c += len;
2326 } else {
2327 g_string_append_c(ret, *c);
2328 c++;
2332 return g_string_free(ret, FALSE);
2335 char *purple_unescape_html(const char *html)
2337 GString *ret;
2338 const char *c = html;
2340 if (html == NULL)
2341 return NULL;
2343 ret = g_string_new("");
2344 while (*c) {
2345 int len;
2346 const char *ent;
2348 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2349 g_string_append(ret, ent);
2350 c += len;
2351 } else if (!strncmp(c, "<br>", 4)) {
2352 g_string_append_c(ret, '\n');
2353 c += 4;
2354 } else {
2355 g_string_append_c(ret, *c);
2356 c++;
2360 return g_string_free(ret, FALSE);
2363 char *
2364 purple_markup_slice(const char *str, guint x, guint y)
2366 GString *ret;
2367 GQueue *q;
2368 guint z = 0;
2369 gboolean appended = FALSE;
2370 gunichar c;
2371 char *tag;
2373 g_return_val_if_fail(str != NULL, NULL);
2374 g_return_val_if_fail(x <= y, NULL);
2376 if (x == y)
2377 return g_strdup("");
2379 ret = g_string_new("");
2380 q = g_queue_new();
2382 while (*str && (z < y)) {
2383 c = g_utf8_get_char(str);
2385 if (c == '<') {
2386 char *end = strchr(str, '>');
2388 if (!end) {
2389 g_string_free(ret, TRUE);
2390 while ((tag = g_queue_pop_head(q)))
2391 g_free(tag);
2392 g_queue_free(q);
2393 return NULL;
2396 if (!g_ascii_strncasecmp(str, "<img ", 5)) {
2397 z += strlen("[Image]");
2398 } else if (!g_ascii_strncasecmp(str, "<br", 3)) {
2399 z += 1;
2400 } else if (!g_ascii_strncasecmp(str, "<hr>", 4)) {
2401 z += strlen("\n---\n");
2402 } else if (!g_ascii_strncasecmp(str, "</", 2)) {
2403 /* pop stack */
2404 char *tmp;
2406 tmp = g_queue_pop_head(q);
2407 g_free(tmp);
2408 /* z += 0; */
2409 } else {
2410 /* push it unto the stack */
2411 char *tmp;
2413 tmp = g_strndup(str, end - str + 1);
2414 g_queue_push_head(q, tmp);
2415 /* z += 0; */
2418 if (z >= x) {
2419 g_string_append_len(ret, str, end - str + 1);
2422 str = end;
2423 } else if (c == '&') {
2424 char *end = strchr(str, ';');
2425 if (!end) {
2426 g_string_free(ret, TRUE);
2427 while ((tag = g_queue_pop_head(q)))
2428 g_free(tag);
2429 g_queue_free(q);
2431 return NULL;
2434 if (z >= x)
2435 g_string_append_len(ret, str, end - str + 1);
2437 z++;
2438 str = end;
2439 } else {
2440 if (z == x && z > 0 && !appended) {
2441 GList *l = q->tail;
2443 while (l) {
2444 tag = l->data;
2445 g_string_append(ret, tag);
2446 l = l->prev;
2448 appended = TRUE;
2451 if (z >= x)
2452 g_string_append_unichar(ret, c);
2453 z++;
2456 str = g_utf8_next_char(str);
2459 while ((tag = g_queue_pop_head(q))) {
2460 char *name;
2462 name = purple_markup_get_tag_name(tag);
2463 g_string_append_printf(ret, "</%s>", name);
2464 g_free(name);
2465 g_free(tag);
2468 g_queue_free(q);
2469 return g_string_free(ret, FALSE);
2472 char *
2473 purple_markup_get_tag_name(const char *tag)
2475 int i;
2476 g_return_val_if_fail(tag != NULL, NULL);
2477 g_return_val_if_fail(*tag == '<', NULL);
2479 for (i = 1; tag[i]; i++)
2480 if (tag[i] == '>' || tag[i] == ' ' || tag[i] == '/')
2481 break;
2483 return g_strndup(tag+1, i-1);
2486 /**************************************************************************
2487 * Path/Filename Functions
2488 **************************************************************************/
2489 const char *
2490 purple_home_dir(void)
2492 #ifndef _WIN32
2493 return g_get_home_dir();
2494 #else
2495 return wpurple_data_dir();
2496 #endif
2499 /* Returns the argument passed to -c IFF it was present, or ~/.purple. */
2500 const char *
2501 purple_user_dir(void)
2503 if (custom_user_dir != NULL)
2504 return custom_user_dir;
2505 else if (!user_dir)
2506 user_dir = g_build_filename(purple_home_dir(), ".purple", NULL);
2508 return user_dir;
2511 void purple_util_set_user_dir(const char *dir)
2513 g_free(custom_user_dir);
2515 if (dir != NULL && *dir)
2516 custom_user_dir = g_strdup(dir);
2517 else
2518 custom_user_dir = NULL;
2521 int purple_build_dir (const char *path, int mode)
2523 return g_mkdir_with_parents(path, mode);
2527 * This function is long and beautiful, like my--um, yeah. Anyway,
2528 * it includes lots of error checking so as we don't overwrite
2529 * people's settings if there is a problem writing the new values.
2531 gboolean
2532 purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
2534 const char *user_dir = purple_user_dir();
2535 gchar *filename_full;
2536 gboolean ret = FALSE;
2538 g_return_val_if_fail(user_dir != NULL, FALSE);
2540 purple_debug_info("util", "Writing file %s to directory %s\n",
2541 filename, user_dir);
2543 /* Ensure the user directory exists */
2544 if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
2546 if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
2548 purple_debug_error("util", "Error creating directory %s: %s\n",
2549 user_dir, g_strerror(errno));
2550 return FALSE;
2554 filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename);
2556 ret = purple_util_write_data_to_file_absolute(filename_full, data, size);
2558 g_free(filename_full);
2559 return ret;
2562 gboolean
2563 purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, gssize size)
2565 gchar *filename_temp;
2566 FILE *file;
2567 size_t real_size, byteswritten;
2568 struct stat st;
2569 #ifndef HAVE_FILENO
2570 int fd;
2571 #endif
2573 purple_debug_info("util", "Writing file %s\n",
2574 filename_full);
2576 g_return_val_if_fail((size >= -1), FALSE);
2578 filename_temp = g_strdup_printf("%s.save", filename_full);
2580 /* Remove an old temporary file, if one exists */
2581 if (g_file_test(filename_temp, G_FILE_TEST_EXISTS))
2583 if (g_unlink(filename_temp) == -1)
2585 purple_debug_error("util", "Error removing old file "
2586 "%s: %s\n",
2587 filename_temp, g_strerror(errno));
2591 /* Open file */
2592 file = g_fopen(filename_temp, "wb");
2593 if (file == NULL)
2595 purple_debug_error("util", "Error opening file %s for "
2596 "writing: %s\n",
2597 filename_temp, g_strerror(errno));
2598 g_free(filename_temp);
2599 return FALSE;
2602 /* Write to file */
2603 real_size = (size == -1) ? strlen(data) : (size_t) size;
2604 byteswritten = fwrite(data, 1, real_size, file);
2606 #ifdef HAVE_FILENO
2607 /* Apparently XFS (and possibly other filesystems) do not
2608 * guarantee that file data is flushed before file metadata,
2609 * so this procedure is insufficient without some flushage. */
2610 if (fflush(file) < 0) {
2611 purple_debug_error("util", "Error flushing %s: %s\n",
2612 filename_temp, g_strerror(errno));
2613 g_free(filename_temp);
2614 fclose(file);
2615 return FALSE;
2617 if (fsync(fileno(file)) < 0) {
2618 purple_debug_error("util", "Error syncing file contents for %s: %s\n",
2619 filename_temp, g_strerror(errno));
2620 g_free(filename_temp);
2621 fclose(file);
2622 return FALSE;
2624 #endif
2626 /* Close file */
2627 if (fclose(file) != 0)
2629 purple_debug_error("util", "Error closing file %s: %s\n",
2630 filename_temp, g_strerror(errno));
2631 g_free(filename_temp);
2632 return FALSE;
2635 #ifndef HAVE_FILENO
2636 /* This is the same effect (we hope) as the HAVE_FILENO block
2637 * above, but for systems without fileno(). */
2638 if ((fd = open(filename_temp, O_RDWR)) < 0) {
2639 purple_debug_error("util", "Error opening file %s for flush: %s\n",
2640 filename_temp, g_strerror(errno));
2641 g_free(filename_temp);
2642 return FALSE;
2644 if (fsync(fd) < 0) {
2645 purple_debug_error("util", "Error syncing %s: %s\n",
2646 filename_temp, g_strerror(errno));
2647 g_free(filename_temp);
2648 close(fd);
2649 return FALSE;
2651 if (close(fd) < 0) {
2652 purple_debug_error("util", "Error closing %s after sync: %s\n",
2653 filename_temp, g_strerror(errno));
2654 g_free(filename_temp);
2655 return FALSE;
2657 #endif
2659 /* Ensure the file is the correct size */
2660 if (byteswritten != real_size)
2662 purple_debug_error("util", "Error writing to file %s: Wrote %"
2663 G_GSIZE_FORMAT " bytes "
2664 "but should have written %" G_GSIZE_FORMAT
2665 "; is your disk full?\n",
2666 filename_temp, byteswritten, real_size);
2667 g_free(filename_temp);
2668 return FALSE;
2670 /* Use stat to be absolutely sure. */
2671 if ((g_stat(filename_temp, &st) == -1) || (st.st_size != real_size))
2673 purple_debug_error("util", "Error writing data to file %s: "
2674 "Incomplete file written; is your disk "
2675 "full?\n",
2676 filename_temp);
2677 g_free(filename_temp);
2678 return FALSE;
2681 #ifndef _WIN32
2682 /* Set file permissions */
2683 if (chmod(filename_temp, S_IRUSR | S_IWUSR) == -1)
2685 purple_debug_error("util", "Error setting permissions of file %s: %s\n",
2686 filename_temp, g_strerror(errno));
2688 #endif
2690 /* Rename to the REAL name */
2691 if (g_rename(filename_temp, filename_full) == -1)
2693 purple_debug_error("util", "Error renaming %s to %s: %s\n",
2694 filename_temp, filename_full,
2695 g_strerror(errno));
2698 g_free(filename_temp);
2700 return TRUE;
2703 xmlnode *
2704 purple_util_read_xml_from_file(const char *filename, const char *description)
2706 return xmlnode_from_file(purple_user_dir(), filename, description, "util");
2710 * Like mkstemp() but returns a file pointer, uses a pre-set template,
2711 * uses the semantics of tempnam() for the directory to use and allocates
2712 * the space for the filepath.
2714 * Caller is responsible for closing the file and removing it when done,
2715 * as well as freeing the space pointed-to by "path" with g_free().
2717 * Returns NULL on failure and cleans up after itself if so.
2719 static const char *purple_mkstemp_templ = {"purpleXXXXXX"};
2721 FILE *
2722 purple_mkstemp(char **fpath, gboolean binary)
2724 const gchar *tmpdir;
2725 int fd;
2726 FILE *fp = NULL;
2728 g_return_val_if_fail(fpath != NULL, NULL);
2730 if((tmpdir = (gchar*)g_get_tmp_dir()) != NULL) {
2731 if((*fpath = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", tmpdir, purple_mkstemp_templ)) != NULL) {
2732 fd = g_mkstemp(*fpath);
2733 if(fd == -1) {
2734 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
2735 "Couldn't make \"%s\", error: %d\n",
2736 *fpath, errno);
2737 } else {
2738 if((fp = fdopen(fd, "r+")) == NULL) {
2739 close(fd);
2740 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
2741 "Couldn't fdopen(), error: %d\n", errno);
2745 if(!fp) {
2746 g_free(*fpath);
2747 *fpath = NULL;
2750 } else {
2751 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
2752 "g_get_tmp_dir() failed!\n");
2755 return fp;
2758 const char *
2759 purple_util_get_image_extension(gconstpointer data, size_t len)
2761 g_return_val_if_fail(data != NULL, NULL);
2762 g_return_val_if_fail(len > 0, NULL);
2764 if (len >= 4)
2766 if (!strncmp((char *)data, "GIF8", 4))
2767 return "gif";
2768 else if (!strncmp((char *)data, "\xff\xd8\xff", 3)) /* 4th may be e0 through ef */
2769 return "jpg";
2770 else if (!strncmp((char *)data, "\x89PNG", 4))
2771 return "png";
2772 else if (!strncmp((char *)data, "MM", 2) ||
2773 !strncmp((char *)data, "II", 2))
2774 return "tif";
2775 else if (!strncmp((char *)data, "BM", 2))
2776 return "bmp";
2779 return "icon";
2783 * We thought about using non-cryptographic hashes like CRC32 here.
2784 * They would be faster, but we think using something more secure is
2785 * important, so that it is more difficult for someone to maliciously
2786 * replace one buddy's icon with something else.
2788 char *
2789 purple_util_get_image_checksum(gconstpointer image_data, size_t image_len)
2791 PurpleCipherContext *context;
2792 gchar digest[41];
2794 context = purple_cipher_context_new_by_name("sha1", NULL);
2795 if (context == NULL)
2797 purple_debug_error("util", "Could not find sha1 cipher\n");
2798 g_return_val_if_reached(NULL);
2801 /* Hash the image data */
2802 purple_cipher_context_append(context, image_data, image_len);
2803 if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
2805 purple_debug_error("util", "Failed to get SHA-1 digest.\n");
2806 g_return_val_if_reached(NULL);
2808 purple_cipher_context_destroy(context);
2810 return g_strdup(digest);
2813 char *
2814 purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
2816 /* Return the filename */
2817 char *checksum = purple_util_get_image_checksum(image_data, image_len);
2818 char *filename = g_strdup_printf("%s.%s", checksum,
2819 purple_util_get_image_extension(image_data, image_len));
2820 g_free(checksum);
2821 return filename;
2824 gboolean
2825 purple_program_is_valid(const char *program)
2827 GError *error = NULL;
2828 char **argv;
2829 gchar *progname;
2830 gboolean is_valid = FALSE;
2832 g_return_val_if_fail(program != NULL, FALSE);
2833 g_return_val_if_fail(*program != '\0', FALSE);
2835 if (!g_shell_parse_argv(program, NULL, &argv, &error)) {
2836 purple_debug(PURPLE_DEBUG_ERROR, "program_is_valid",
2837 "Could not parse program '%s': %s\n",
2838 program, error->message);
2839 g_error_free(error);
2840 return FALSE;
2843 if (argv == NULL) {
2844 return FALSE;
2847 progname = g_find_program_in_path(argv[0]);
2848 is_valid = (progname != NULL);
2850 if(purple_debug_is_verbose())
2851 purple_debug_info("program_is_valid", "Tested program %s. %s.\n", program,
2852 is_valid ? "Valid" : "Invalid");
2854 g_strfreev(argv);
2855 g_free(progname);
2857 return is_valid;
2861 gboolean
2862 purple_running_gnome(void)
2864 #ifndef _WIN32
2865 gchar *tmp = g_find_program_in_path("gnome-open");
2867 if (tmp == NULL)
2868 return FALSE;
2869 g_free(tmp);
2871 tmp = (gchar *)g_getenv("GNOME_DESKTOP_SESSION_ID");
2873 return ((tmp != NULL) && (*tmp != '\0'));
2874 #else
2875 return FALSE;
2876 #endif
2879 gboolean
2880 purple_running_kde(void)
2882 #ifndef _WIN32
2883 gchar *tmp = g_find_program_in_path("kfmclient");
2884 const char *session;
2886 if (tmp == NULL)
2887 return FALSE;
2888 g_free(tmp);
2890 session = g_getenv("KDE_FULL_SESSION");
2891 if (purple_strequal(session, "true"))
2892 return TRUE;
2894 /* If you run Purple from Konsole under !KDE, this will provide a
2895 * a false positive. Since we do the GNOME checks first, this is
2896 * only a problem if you're running something !(KDE || GNOME) and
2897 * you run Purple from Konsole. This really shouldn't be a problem. */
2898 return ((g_getenv("KDEDIR") != NULL) || g_getenv("KDEDIRS") != NULL);
2899 #else
2900 return FALSE;
2901 #endif
2904 gboolean
2905 purple_running_osx(void)
2907 #if defined(__APPLE__)
2908 return TRUE;
2909 #else
2910 return FALSE;
2911 #endif
2914 typedef union purple_sockaddr {
2915 struct sockaddr sa;
2916 struct sockaddr_in sa_in;
2917 #if defined(AF_INET6)
2918 struct sockaddr_in6 sa_in6;
2919 #endif
2920 struct sockaddr_storage sa_stor;
2921 } PurpleSockaddr;
2923 char *
2924 purple_fd_get_ip(int fd)
2926 PurpleSockaddr addr;
2927 socklen_t namelen = sizeof(addr);
2928 int family;
2930 g_return_val_if_fail(fd != 0, NULL);
2932 if (getsockname(fd, &(addr.sa), &namelen))
2933 return NULL;
2935 family = addr.sa.sa_family;
2937 if (family == AF_INET) {
2938 return g_strdup(inet_ntoa(addr.sa_in.sin_addr));
2940 #if defined(AF_INET6) && defined(HAVE_INET_NTOP)
2941 else if (family == AF_INET6) {
2942 char host[INET6_ADDRSTRLEN];
2943 const char *tmp;
2945 tmp = inet_ntop(family, &(addr.sa_in6.sin6_addr), host, sizeof(host));
2946 return g_strdup(tmp);
2948 #endif
2950 return NULL;
2954 purple_socket_get_family(int fd)
2956 PurpleSockaddr addr;
2957 socklen_t len = sizeof(addr);
2959 g_return_val_if_fail(fd >= 0, -1);
2961 if (getsockname(fd, &(addr.sa), &len))
2962 return -1;
2964 return addr.sa.sa_family;
2967 gboolean
2968 purple_socket_speaks_ipv4(int fd)
2970 int family;
2972 g_return_val_if_fail(fd >= 0, FALSE);
2974 family = purple_socket_get_family(fd);
2976 switch (family) {
2977 case AF_INET:
2978 return TRUE;
2979 #if defined(IPV6_V6ONLY)
2980 case AF_INET6:
2982 int val = 0;
2983 guint len = sizeof(val);
2985 if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0)
2986 return FALSE;
2987 return !val;
2989 #endif
2990 default:
2991 return FALSE;
2995 /**************************************************************************
2996 * String Functions
2997 **************************************************************************/
2998 gboolean
2999 purple_strequal(const gchar *left, const gchar *right)
3001 #if GLIB_CHECK_VERSION(2,16,0)
3002 return (g_strcmp0(left, right) == 0);
3003 #else
3004 return ((left == NULL && right == NULL) ||
3005 (left != NULL && right != NULL && strcmp(left, right) == 0));
3006 #endif
3009 const char *
3010 purple_normalize(const PurpleAccount *account, const char *str)
3012 const char *ret = NULL;
3013 static char buf[BUF_LEN];
3015 /* This should prevent a crash if purple_normalize gets called with NULL str, see #10115 */
3016 g_return_val_if_fail(str != NULL, "");
3018 if (account != NULL)
3020 PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account));
3022 if (prpl != NULL)
3024 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3026 if (prpl_info->normalize)
3027 ret = prpl_info->normalize(account, str);
3031 if (ret == NULL)
3033 char *tmp;
3035 tmp = g_utf8_normalize(str, -1, G_NORMALIZE_DEFAULT);
3036 g_snprintf(buf, sizeof(buf), "%s", tmp);
3037 g_free(tmp);
3039 ret = buf;
3042 return ret;
3046 * You probably don't want to call this directly, it is
3047 * mainly for use as a PRPL callback function. See the
3048 * comments in util.h.
3050 const char *
3051 purple_normalize_nocase(const PurpleAccount *account, const char *str)
3053 static char buf[BUF_LEN];
3054 char *tmp1, *tmp2;
3056 g_return_val_if_fail(str != NULL, NULL);
3058 tmp1 = g_utf8_strdown(str, -1);
3059 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
3060 g_snprintf(buf, sizeof(buf), "%s", tmp2 ? tmp2 : "");
3061 g_free(tmp2);
3062 g_free(tmp1);
3064 return buf;
3067 gchar *
3068 purple_strdup_withhtml(const gchar *src)
3070 gulong destsize, i, j;
3071 gchar *dest;
3073 g_return_val_if_fail(src != NULL, NULL);
3075 /* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */
3076 destsize = 1;
3077 for (i = 0; src[i] != '\0'; i++)
3079 if (src[i] == '\n')
3080 destsize += 4;
3081 else if (src[i] != '\r')
3082 destsize++;
3085 dest = g_malloc(destsize);
3087 /* Copy stuff, ignoring \r's, because they are dumb */
3088 for (i = 0, j = 0; src[i] != '\0'; i++) {
3089 if (src[i] == '\n') {
3090 strcpy(&dest[j], "<BR>");
3091 j += 4;
3092 } else if (src[i] != '\r')
3093 dest[j++] = src[i];
3096 dest[destsize-1] = '\0';
3098 return dest;
3101 gboolean
3102 purple_str_has_prefix(const char *s, const char *p)
3104 return g_str_has_prefix(s, p);
3107 gboolean
3108 purple_str_has_suffix(const char *s, const char *x)
3110 return g_str_has_suffix(s, x);
3113 char *
3114 purple_str_add_cr(const char *text)
3116 char *ret = NULL;
3117 int count = 0, j;
3118 guint i;
3120 g_return_val_if_fail(text != NULL, NULL);
3122 if (text[0] == '\n')
3123 count++;
3124 for (i = 1; i < strlen(text); i++)
3125 if (text[i] == '\n' && text[i - 1] != '\r')
3126 count++;
3128 if (count == 0)
3129 return g_strdup(text);
3131 ret = g_malloc0(strlen(text) + count + 1);
3133 i = 0; j = 0;
3134 if (text[i] == '\n')
3135 ret[j++] = '\r';
3136 ret[j++] = text[i++];
3137 for (; i < strlen(text); i++) {
3138 if (text[i] == '\n' && text[i - 1] != '\r')
3139 ret[j++] = '\r';
3140 ret[j++] = text[i];
3143 return ret;
3146 void
3147 purple_str_strip_char(char *text, char thechar)
3149 int i, j;
3151 g_return_if_fail(text != NULL);
3153 for (i = 0, j = 0; text[i]; i++)
3154 if (text[i] != thechar)
3155 text[j++] = text[i];
3157 text[j] = '\0';
3160 void
3161 purple_util_chrreplace(char *string, char delimiter,
3162 char replacement)
3164 int i = 0;
3166 g_return_if_fail(string != NULL);
3168 while (string[i] != '\0')
3170 if (string[i] == delimiter)
3171 string[i] = replacement;
3172 i++;
3176 gchar *
3177 purple_strreplace(const char *string, const char *delimiter,
3178 const char *replacement)
3180 gchar **split;
3181 gchar *ret;
3183 g_return_val_if_fail(string != NULL, NULL);
3184 g_return_val_if_fail(delimiter != NULL, NULL);
3185 g_return_val_if_fail(replacement != NULL, NULL);
3187 split = g_strsplit(string, delimiter, 0);
3188 ret = g_strjoinv(replacement, split);
3189 g_strfreev(split);
3191 return ret;
3194 gchar *
3195 purple_strcasereplace(const char *string, const char *delimiter,
3196 const char *replacement)
3198 gchar *ret;
3199 int length_del, length_rep, i, j;
3201 g_return_val_if_fail(string != NULL, NULL);
3202 g_return_val_if_fail(delimiter != NULL, NULL);
3203 g_return_val_if_fail(replacement != NULL, NULL);
3205 length_del = strlen(delimiter);
3206 length_rep = strlen(replacement);
3208 /* Count how many times the delimiter appears */
3209 i = 0; /* position in the source string */
3210 j = 0; /* number of occurrences of "delimiter" */
3211 while (string[i] != '\0') {
3212 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3213 i += length_del;
3214 j += length_rep;
3215 } else {
3216 i++;
3217 j++;
3221 ret = g_malloc(j+1);
3223 i = 0; /* position in the source string */
3224 j = 0; /* position in the destination string */
3225 while (string[i] != '\0') {
3226 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3227 strncpy(&ret[j], replacement, length_rep);
3228 i += length_del;
3229 j += length_rep;
3230 } else {
3231 ret[j] = string[i];
3232 i++;
3233 j++;
3237 ret[j] = '\0';
3239 return ret;
3242 const char *
3243 purple_strcasestr(const char *haystack, const char *needle)
3245 size_t hlen, nlen;
3246 const char *tmp, *ret;
3248 g_return_val_if_fail(haystack != NULL, NULL);
3249 g_return_val_if_fail(needle != NULL, NULL);
3251 hlen = strlen(haystack);
3252 nlen = strlen(needle);
3253 tmp = haystack,
3254 ret = NULL;
3256 g_return_val_if_fail(hlen > 0, NULL);
3257 g_return_val_if_fail(nlen > 0, NULL);
3259 while (*tmp && !ret) {
3260 if (!g_ascii_strncasecmp(needle, tmp, nlen))
3261 ret = tmp;
3262 else
3263 tmp++;
3266 return ret;
3269 char *
3270 purple_str_size_to_units(size_t size)
3272 static const char * const size_str[] = { "bytes", "KiB", "MiB", "GiB" };
3273 float size_mag;
3274 int size_index = 0;
3276 if (size == -1) {
3277 return g_strdup(_("Calculating..."));
3279 else if (size == 0) {
3280 return g_strdup(_("Unknown."));
3282 else {
3283 size_mag = (float)size;
3285 while ((size_index < 3) && (size_mag > 1024)) {
3286 size_mag /= 1024;
3287 size_index++;
3290 if (size_index == 0) {
3291 return g_strdup_printf("%" G_GSIZE_FORMAT " %s", size, size_str[size_index]);
3292 } else {
3293 return g_strdup_printf("%.2f %s", size_mag, size_str[size_index]);
3298 char *
3299 purple_str_seconds_to_string(guint secs)
3301 char *ret = NULL;
3302 guint days, hrs, mins;
3304 if (secs < 60)
3306 return g_strdup_printf(dngettext(PACKAGE, "%d second", "%d seconds", secs), secs);
3309 days = secs / (60 * 60 * 24);
3310 secs = secs % (60 * 60 * 24);
3311 hrs = secs / (60 * 60);
3312 secs = secs % (60 * 60);
3313 mins = secs / 60;
3314 secs = secs % 60;
3316 if (days > 0)
3318 ret = g_strdup_printf(dngettext(PACKAGE, "%d day", "%d days", days), days);
3321 if (hrs > 0)
3323 if (ret != NULL)
3325 char *tmp = g_strdup_printf(
3326 dngettext(PACKAGE, "%s, %d hour", "%s, %d hours", hrs),
3327 ret, hrs);
3328 g_free(ret);
3329 ret = tmp;
3331 else
3332 ret = g_strdup_printf(dngettext(PACKAGE, "%d hour", "%d hours", hrs), hrs);
3335 if (mins > 0)
3337 if (ret != NULL)
3339 char *tmp = g_strdup_printf(
3340 dngettext(PACKAGE, "%s, %d minute", "%s, %d minutes", mins),
3341 ret, mins);
3342 g_free(ret);
3343 ret = tmp;
3345 else
3346 ret = g_strdup_printf(dngettext(PACKAGE, "%d minute", "%d minutes", mins), mins);
3349 return ret;
3353 char *
3354 purple_str_binary_to_ascii(const unsigned char *binary, guint len)
3356 GString *ret;
3357 guint i;
3359 g_return_val_if_fail(len > 0, NULL);
3361 ret = g_string_sized_new(len);
3363 for (i = 0; i < len; i++)
3364 if (binary[i] < 32 || binary[i] > 126)
3365 g_string_append_printf(ret, "\\x%02hhx", binary[i]);
3366 else if (binary[i] == '\\')
3367 g_string_append(ret, "\\\\");
3368 else
3369 g_string_append_c(ret, binary[i]);
3371 return g_string_free(ret, FALSE);
3374 /**************************************************************************
3375 * URI/URL Functions
3376 **************************************************************************/
3378 void purple_got_protocol_handler_uri(const char *uri)
3380 char proto[11];
3381 char delimiter;
3382 const char *tmp, *param_string;
3383 char *cmd;
3384 GHashTable *params = NULL;
3385 int len;
3386 if (!(tmp = strchr(uri, ':')) || tmp == uri) {
3387 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
3388 return;
3391 len = MIN(sizeof(proto) - 1, (tmp - uri));
3393 strncpy(proto, uri, len);
3394 proto[len] = '\0';
3396 tmp++;
3398 if (g_str_equal(proto, "xmpp"))
3399 delimiter = ';';
3400 else
3401 delimiter = '&';
3403 purple_debug_info("util", "Processing message '%s' for protocol '%s' using delimiter '%c'.\n", tmp, proto, delimiter);
3405 if ((param_string = strchr(tmp, '?'))) {
3406 const char *keyend = NULL, *pairstart;
3407 char *key, *value = NULL;
3409 cmd = g_strndup(tmp, (param_string - tmp));
3410 param_string++;
3412 params = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
3413 pairstart = tmp = param_string;
3415 while (*tmp || *pairstart) {
3416 if (*tmp == delimiter || !(*tmp)) {
3417 /* If there is no explicit value */
3418 if (keyend == NULL)
3419 keyend = tmp;
3421 if (keyend && keyend != pairstart) {
3422 char *p;
3423 key = g_strndup(pairstart, (keyend - pairstart));
3424 /* If there is an explicit value */
3425 if (keyend != tmp && keyend != (tmp - 1))
3426 value = g_strndup(keyend + 1, (tmp - keyend - 1));
3427 for (p = key; *p; ++p)
3428 *p = g_ascii_tolower(*p);
3429 g_hash_table_insert(params, key, value);
3431 keyend = value = NULL;
3432 pairstart = (*tmp) ? tmp + 1 : tmp;
3433 } else if (*tmp == '=')
3434 keyend = tmp;
3436 if (*tmp)
3437 tmp++;
3439 } else
3440 cmd = g_strdup(tmp);
3442 purple_signal_emit_return_1(purple_get_core(), "uri-handler", proto, cmd, params);
3444 g_free(cmd);
3445 if (params)
3446 g_hash_table_destroy(params);
3450 * TODO: Should probably add a "gboolean *ret_ishttps" parameter that
3451 * is set to TRUE if this URL is https, otherwise it is set to
3452 * FALSE. But that change will break the API.
3454 * This is important for Yahoo! web messenger login. They now
3455 * force https login, and if you access the web messenger login
3456 * page via http then it redirects you to the https version, but
3457 * purple_util_fetch_url() ignores the "https" and attempts to
3458 * fetch the URL via http again, which gets redirected again.
3460 gboolean
3461 purple_url_parse(const char *url, char **ret_host, int *ret_port,
3462 char **ret_path, char **ret_user, char **ret_passwd)
3464 gboolean is_https = FALSE;
3465 const char * scan_info;
3466 char port_str[6];
3467 int f;
3468 const char *at, *slash;
3469 const char *turl;
3470 char host[256], path[256], user[256], passwd[256];
3471 int port = 0;
3472 /* hyphen at end includes it in control set */
3474 #define ADDR_CTRL "A-Za-z0-9.-"
3475 #define PORT_CTRL "0-9"
3476 #define PAGE_CTRL "A-Za-z0-9.~_/:*!@&%%?=+^-"
3477 #define USER_CTRL "A-Za-z0-9.~_/*!&%%?=+^-"
3478 #define PASSWD_CTRL "A-Za-z0-9.~_/*!&%%?=+^-"
3480 g_return_val_if_fail(url != NULL, FALSE);
3482 if ((turl = purple_strcasestr(url, "http://")) != NULL)
3484 turl += 7;
3485 url = turl;
3487 else if ((turl = purple_strcasestr(url, "https://")) != NULL)
3489 is_https = TRUE;
3490 turl += 8;
3491 url = turl;
3494 /* parse out authentication information if supplied */
3495 /* Only care about @ char BEFORE the first / */
3496 at = strchr(url, '@');
3497 slash = strchr(url, '/');
3498 f = 0;
3499 if (at && (!slash || at < slash)) {
3500 scan_info = "%255[" USER_CTRL "]:%255[" PASSWD_CTRL "]^@";
3501 f = sscanf(url, scan_info, user, passwd);
3503 if (f == 1) {
3504 /* No passwd, possibly just username supplied */
3505 scan_info = "%255[" USER_CTRL "]^@";
3506 f = sscanf(url, scan_info, user);
3509 url = at+1; /* move pointer after the @ char */
3512 if (f < 1) {
3513 *user = '\0';
3514 *passwd = '\0';
3515 } else if (f == 1)
3516 *passwd = '\0';
3518 scan_info = "%255[" ADDR_CTRL "]:%5[" PORT_CTRL "]/%255[" PAGE_CTRL "]";
3519 f = sscanf(url, scan_info, host, port_str, path);
3521 if (f == 1)
3523 scan_info = "%255[" ADDR_CTRL "]/%255[" PAGE_CTRL "]";
3524 f = sscanf(url, scan_info, host, path);
3525 /* Use the default port */
3526 if (is_https)
3527 g_snprintf(port_str, sizeof(port_str), "443");
3528 else
3529 g_snprintf(port_str, sizeof(port_str), "80");
3532 if (f == 0)
3533 *host = '\0';
3535 if (f <= 1)
3536 *path = '\0';
3538 sscanf(port_str, "%d", &port);
3540 if (ret_host != NULL) *ret_host = g_strdup(host);
3541 if (ret_port != NULL) *ret_port = port;
3542 if (ret_path != NULL) *ret_path = g_strdup(path);
3543 if (ret_user != NULL) *ret_user = g_strdup(user);
3544 if (ret_passwd != NULL) *ret_passwd = g_strdup(passwd);
3546 return ((*host != '\0') ? TRUE : FALSE);
3548 #undef ADDR_CTRL
3549 #undef PORT_CTRL
3550 #undef PAGE_CTRL
3551 #undef USER_CTRL
3552 #undef PASSWD_CTRL
3556 * The arguments to this function are similar to printf.
3558 static void
3559 purple_util_fetch_url_error(PurpleUtilFetchUrlData *gfud, const char *format, ...)
3561 gchar *error_message;
3562 va_list args;
3564 va_start(args, format);
3565 error_message = g_strdup_vprintf(format, args);
3566 va_end(args);
3568 gfud->callback(gfud, gfud->user_data, NULL, 0, error_message);
3569 g_free(error_message);
3570 purple_util_fetch_url_cancel(gfud);
3573 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
3574 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
3575 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data);
3577 static gboolean
3578 parse_redirect(const char *data, size_t data_len,
3579 PurpleUtilFetchUrlData *gfud)
3581 gchar *s;
3582 gchar *new_url, *temp_url, *end;
3583 gboolean full;
3584 int len;
3586 if ((s = g_strstr_len(data, data_len, "\nLocation: ")) == NULL)
3587 /* We're not being redirected */
3588 return FALSE;
3590 s += strlen("Location: ");
3591 end = strchr(s, '\r');
3593 /* Just in case :) */
3594 if (end == NULL)
3595 end = strchr(s, '\n');
3597 if (end == NULL)
3598 return FALSE;
3600 len = end - s;
3602 new_url = g_malloc(len + 1);
3603 strncpy(new_url, s, len);
3604 new_url[len] = '\0';
3606 full = gfud->full;
3608 if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL)
3610 temp_url = new_url;
3612 new_url = g_strdup_printf("%s:%d%s", gfud->website.address,
3613 gfud->website.port, temp_url);
3615 g_free(temp_url);
3617 full = FALSE;
3620 purple_debug_info("util", "Redirecting to %s\n", new_url);
3622 gfud->num_times_redirected++;
3623 if (gfud->num_times_redirected >= 5)
3625 purple_util_fetch_url_error(gfud,
3626 _("Could not open %s: Redirected too many times"),
3627 gfud->url);
3628 return TRUE;
3632 * Try again, with this new location. This code is somewhat
3633 * ugly, but we need to reuse the gfud because whoever called
3634 * us is holding a reference to it.
3636 g_free(gfud->url);
3637 gfud->url = new_url;
3638 gfud->full = full;
3639 g_free(gfud->request);
3640 gfud->request = NULL;
3642 if (gfud->is_ssl) {
3643 gfud->is_ssl = FALSE;
3644 purple_ssl_close(gfud->ssl_connection);
3645 gfud->ssl_connection = NULL;
3646 } else {
3647 purple_input_remove(gfud->inpa);
3648 gfud->inpa = 0;
3649 close(gfud->fd);
3650 gfud->fd = -1;
3652 gfud->request_written = 0;
3653 gfud->len = 0;
3654 gfud->data_len = 0;
3656 g_free(gfud->website.user);
3657 g_free(gfud->website.passwd);
3658 g_free(gfud->website.address);
3659 g_free(gfud->website.page);
3660 purple_url_parse(new_url, &gfud->website.address, &gfud->website.port,
3661 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
3663 if (purple_strcasestr(new_url, "https://") != NULL) {
3664 gfud->is_ssl = TRUE;
3665 gfud->ssl_connection = purple_ssl_connect(gfud->account,
3666 gfud->website.address, gfud->website.port,
3667 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
3668 } else {
3669 gfud->connect_data = purple_proxy_connect(NULL, gfud->account,
3670 gfud->website.address, gfud->website.port,
3671 url_fetch_connect_cb, gfud);
3674 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
3676 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
3677 gfud->website.address);
3680 return TRUE;
3683 static const char *
3684 find_header_content(const char *data, size_t data_len, const char *header, size_t header_len)
3686 const char *p = NULL;
3688 if (header_len <= 0)
3689 header_len = strlen(header);
3691 /* Note: data is _not_ nul-terminated. */
3692 if (data_len > header_len) {
3693 if (header[0] == '\n')
3694 p = (g_ascii_strncasecmp(data, header + 1, header_len - 1) == 0) ? data : NULL;
3695 if (!p)
3696 p = purple_strcasestr(data, header);
3697 if (p)
3698 p += header_len;
3701 /* If we can find the header at all, try to sscanf it.
3702 * Response headers should end with at least \r\n, so sscanf is safe,
3703 * if we make sure that there is indeed a \n in our header.
3705 if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
3706 return p;
3709 return NULL;
3712 static size_t
3713 parse_content_len(const char *data, size_t data_len)
3715 size_t content_len = 0;
3716 const char *p = NULL;
3718 p = find_header_content(data, data_len, "\nContent-Length: ", sizeof("\nContent-Length: ") - 1);
3719 if (p) {
3720 sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
3721 purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len);
3724 return content_len;
3727 static gboolean
3728 content_is_chunked(const char *data, size_t data_len)
3730 const char *p = find_header_content(data, data_len, "\nTransfer-Encoding: ", sizeof("\nTransfer-Encoding: ") - 1);
3731 if (p && g_ascii_strncasecmp(p, "chunked", 7) == 0)
3732 return TRUE;
3734 return FALSE;
3737 /* Process in-place */
3738 static void
3739 process_chunked_data(char *data, gsize *len)
3741 gsize sz;
3742 gsize newlen = 0;
3743 char *p = data;
3744 char *s = data;
3746 while (*s) {
3747 /* Read the size of this chunk */
3748 if (sscanf(s, "%" G_GSIZE_MODIFIER "x", &sz) != 1)
3750 purple_debug_error("util", "Error processing chunked data: "
3751 "Expected data length, found: %s\n", s);
3752 break;
3754 if (sz == 0) {
3755 /* We've reached the last chunk */
3757 * TODO: The spec allows "footers" to follow the last chunk.
3758 * If there is more data after this line then we should
3759 * treat it like a header.
3761 break;
3764 /* Advance to the start of the data */
3765 s = strstr(s, "\r\n");
3766 if (s == NULL)
3767 break;
3768 s += 2;
3770 if (s + sz > data + *len) {
3771 purple_debug_error("util", "Error processing chunked data: "
3772 "Chunk size %" G_GSIZE_FORMAT " bytes was longer "
3773 "than the data remaining in the buffer (%"
3774 G_GSIZE_FORMAT " bytes)\n", sz, data + *len - s);
3777 /* Move all data overtop of the chunk length that we read in earlier */
3778 g_memmove(p, s, sz);
3779 p += sz;
3780 s += sz;
3781 newlen += sz;
3782 if (*s != '\r' && *(s + 1) != '\n') {
3783 purple_debug_error("util", "Error processing chunked data: "
3784 "Expected \\r\\n, found: %s\n", s);
3785 break;
3787 s += 2;
3790 /* NULL terminate the data */
3791 *p = 0;
3793 *len = newlen;
3796 static void
3797 url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond)
3799 PurpleUtilFetchUrlData *gfud = url_data;
3800 int len;
3801 char buf[4096];
3802 char *data_cursor;
3803 gboolean got_eof = FALSE;
3806 * Read data in a loop until we can't read any more! This is a
3807 * little confusing because we read using a different function
3808 * depending on whether the socket is ssl or cleartext.
3810 while ((gfud->is_ssl && ((len = purple_ssl_read(gfud->ssl_connection, buf, sizeof(buf))) > 0)) ||
3811 (!gfud->is_ssl && (len = read(source, buf, sizeof(buf))) > 0))
3813 if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) {
3814 purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"),
3815 gfud->website.address, gfud->max_len);
3816 return;
3819 /* If we've filled up our buffer, make it bigger */
3820 if((gfud->len + len) >= gfud->data_len) {
3821 while((gfud->len + len) >= gfud->data_len)
3822 gfud->data_len += sizeof(buf);
3824 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
3827 data_cursor = gfud->webdata + gfud->len;
3829 gfud->len += len;
3831 memcpy(data_cursor, buf, len);
3833 gfud->webdata[gfud->len] = '\0';
3835 if(!gfud->got_headers) {
3836 char *end_of_headers;
3838 /* See if we've reached the end of the headers yet */
3839 end_of_headers = strstr(gfud->webdata, "\r\n\r\n");
3840 if (end_of_headers) {
3841 char *new_data;
3842 guint header_len = (end_of_headers + 4 - gfud->webdata);
3843 size_t content_len;
3845 purple_debug_misc("util", "Response headers: '%.*s'\n",
3846 header_len, gfud->webdata);
3848 /* See if we can find a redirect. */
3849 if(parse_redirect(gfud->webdata, header_len, gfud))
3850 return;
3852 gfud->got_headers = TRUE;
3854 /* No redirect. See if we can find a content length. */
3855 content_len = parse_content_len(gfud->webdata, header_len);
3856 gfud->chunked = content_is_chunked(gfud->webdata, header_len);
3858 if (content_len == 0) {
3859 /* We'll stick with an initial 8192 */
3860 content_len = 8192;
3861 } else {
3862 gfud->has_explicit_data_len = TRUE;
3866 /* If we're returning the headers too, we don't need to clean them out */
3867 if (gfud->include_headers) {
3868 gfud->data_len = content_len + header_len;
3869 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
3870 } else {
3871 size_t body_len = gfud->len - header_len;
3873 content_len = MAX(content_len, body_len);
3875 new_data = g_try_malloc(content_len);
3876 if (new_data == NULL) {
3877 purple_debug_error("util",
3878 "Failed to allocate %" G_GSIZE_FORMAT " bytes: %s\n",
3879 content_len, g_strerror(errno));
3880 purple_util_fetch_url_error(gfud,
3881 _("Unable to allocate enough memory to hold "
3882 "the contents from %s. The web server may "
3883 "be trying something malicious."),
3884 gfud->website.address);
3886 return;
3889 /* We may have read part of the body when reading the headers, don't lose it */
3890 if (body_len > 0) {
3891 memcpy(new_data, end_of_headers + 4, body_len);
3894 /* Out with the old... */
3895 g_free(gfud->webdata);
3897 /* In with the new. */
3898 gfud->len = body_len;
3899 gfud->data_len = content_len;
3900 gfud->webdata = new_data;
3905 if(gfud->has_explicit_data_len && gfud->len >= gfud->data_len) {
3906 got_eof = TRUE;
3907 break;
3911 if(len < 0) {
3912 if(errno == EAGAIN) {
3913 return;
3914 } else {
3915 purple_util_fetch_url_error(gfud, _("Error reading from %s: %s"),
3916 gfud->website.address, g_strerror(errno));
3917 return;
3921 if((len == 0) || got_eof) {
3922 gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
3923 gfud->webdata[gfud->len] = '\0';
3925 if (!gfud->include_headers && gfud->chunked) {
3926 /* Process only if we don't want the headers. */
3927 process_chunked_data(gfud->webdata, &gfud->len);
3930 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
3931 purple_util_fetch_url_cancel(gfud);
3935 static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
3937 url_fetch_recv_cb(data, -1, cond);
3941 * This function is called when the socket is available to be written
3942 * to.
3944 * @param source The file descriptor that can be written to. This can
3945 * be an http connection or it can be the SSL connection of an
3946 * https request. So be careful what you use it for! If it's
3947 * an https request then use purple_ssl_write() instead of
3948 * writing to it directly.
3950 static void
3951 url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond)
3953 PurpleUtilFetchUrlData *gfud;
3954 int len, total_len;
3956 gfud = data;
3958 if (gfud->request == NULL) {
3960 PurpleProxyInfo *gpi = purple_proxy_get_setup(gfud->account);
3961 GString *request_str = g_string_new(NULL);
3963 g_string_append_printf(request_str, "GET %s%s HTTP/%s\r\n"
3964 "Connection: close\r\n",
3965 (gfud->full ? "" : "/"),
3966 (gfud->full ? (gfud->url ? gfud->url : "") : (gfud->website.page ? gfud->website.page : "")),
3967 (gfud->http11 ? "1.1" : "1.0"));
3969 if (gfud->user_agent)
3970 g_string_append_printf(request_str, "User-Agent: %s\r\n", gfud->user_agent);
3972 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
3973 * clients must know how to handle the "chunked" transfer encoding.
3974 * Purple doesn't know how to handle "chunked", so should always send
3975 * the Host header regardless, to get around some observed problems
3977 g_string_append_printf(request_str, "Accept: */*\r\n"
3978 "Host: %s\r\n",
3979 (gfud->website.address ? gfud->website.address : ""));
3981 if (purple_proxy_info_get_username(gpi) != NULL
3982 && (purple_proxy_info_get_type(gpi) == PURPLE_PROXY_USE_ENVVAR
3983 || purple_proxy_info_get_type(gpi) == PURPLE_PROXY_HTTP)) {
3984 /* This chunk of code was copied from proxy.c http_start_connect_tunneling()
3985 * This is really a temporary hack - we need a more complete proxy handling solution,
3986 * so I didn't think it was worthwhile to refactor for reuse
3988 char *t1, *t2, *ntlm_type1;
3989 char hostname[256];
3990 int ret;
3992 ret = gethostname(hostname, sizeof(hostname));
3993 hostname[sizeof(hostname) - 1] = '\0';
3994 if (ret < 0 || hostname[0] == '\0') {
3995 purple_debug_warning("util", "proxy - gethostname() failed -- is your hostname set?");
3996 strcpy(hostname, "localhost");
3999 t1 = g_strdup_printf("%s:%s",
4000 purple_proxy_info_get_username(gpi),
4001 purple_proxy_info_get_password(gpi) ?
4002 purple_proxy_info_get_password(gpi) : "");
4003 t2 = purple_base64_encode((const guchar *)t1, strlen(t1));
4004 g_free(t1);
4006 ntlm_type1 = purple_ntlm_gen_type1(hostname, "");
4008 g_string_append_printf(request_str,
4009 "Proxy-Authorization: Basic %s\r\n"
4010 "Proxy-Authorization: NTLM %s\r\n"
4011 "Proxy-Connection: Keep-Alive\r\n",
4012 t2, ntlm_type1);
4013 g_free(ntlm_type1);
4014 g_free(t2);
4017 g_string_append(request_str, "\r\n");
4019 gfud->request = g_string_free(request_str, FALSE);
4022 if(purple_debug_is_unsafe())
4023 purple_debug_misc("util", "Request: '%s'\n", gfud->request);
4024 else
4025 purple_debug_misc("util", "request constructed\n");
4027 total_len = strlen(gfud->request);
4029 if (gfud->is_ssl)
4030 len = purple_ssl_write(gfud->ssl_connection, gfud->request + gfud->request_written,
4031 total_len - gfud->request_written);
4032 else
4033 len = write(gfud->fd, gfud->request + gfud->request_written,
4034 total_len - gfud->request_written);
4036 if (len < 0 && errno == EAGAIN)
4037 return;
4038 else if (len < 0) {
4039 purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
4040 gfud->website.address, g_strerror(errno));
4041 return;
4043 gfud->request_written += len;
4045 if (gfud->request_written < total_len)
4046 return;
4048 /* We're done writing our request, now start reading the response */
4049 if (gfud->is_ssl) {
4050 purple_input_remove(gfud->inpa);
4051 gfud->inpa = 0;
4052 purple_ssl_input_add(gfud->ssl_connection, ssl_url_fetch_recv_cb, gfud);
4053 } else {
4054 purple_input_remove(gfud->inpa);
4055 gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
4056 gfud);
4060 static void
4061 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
4063 PurpleUtilFetchUrlData *gfud;
4065 gfud = url_data;
4066 gfud->connect_data = NULL;
4068 if (source == -1)
4070 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4071 (gfud->website.address ? gfud->website.address : ""), error_message);
4072 return;
4075 gfud->fd = source;
4077 gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE,
4078 url_fetch_send_cb, gfud);
4079 url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE);
4082 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
4084 PurpleUtilFetchUrlData *gfud;
4086 gfud = data;
4088 gfud->inpa = purple_input_add(ssl_connection->fd, PURPLE_INPUT_WRITE,
4089 url_fetch_send_cb, gfud);
4090 url_fetch_send_cb(gfud, ssl_connection->fd, PURPLE_INPUT_WRITE);
4093 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data)
4095 PurpleUtilFetchUrlData *gfud;
4097 gfud = data;
4098 gfud->ssl_connection = NULL;
4100 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4101 (gfud->website.address ? gfud->website.address : ""),
4102 purple_ssl_strerror(error));
4105 PurpleUtilFetchUrlData *
4106 purple_util_fetch_url_request(const char *url, gboolean full,
4107 const char *user_agent, gboolean http11,
4108 const char *request, gboolean include_headers,
4109 PurpleUtilFetchUrlCallback callback, void *user_data)
4111 return purple_util_fetch_url_request_len_with_account(NULL, url, full,
4112 user_agent, http11,
4113 request, include_headers, -1,
4114 callback, user_data);
4117 PurpleUtilFetchUrlData *
4118 purple_util_fetch_url_request_len(const char *url, gboolean full,
4119 const char *user_agent, gboolean http11,
4120 const char *request, gboolean include_headers, gssize max_len,
4121 PurpleUtilFetchUrlCallback callback, void *user_data)
4123 return purple_util_fetch_url_request_len_with_account(NULL, url, full,
4124 user_agent, http11, request, include_headers, max_len, callback,
4125 user_data);
4128 PurpleUtilFetchUrlData *
4129 purple_util_fetch_url_request_len_with_account(PurpleAccount *account,
4130 const char *url, gboolean full, const char *user_agent, gboolean http11,
4131 const char *request, gboolean include_headers, gssize max_len,
4132 PurpleUtilFetchUrlCallback callback, void *user_data)
4134 PurpleUtilFetchUrlData *gfud;
4136 g_return_val_if_fail(url != NULL, NULL);
4137 g_return_val_if_fail(callback != NULL, NULL);
4139 if(purple_debug_is_unsafe())
4140 purple_debug_info("util",
4141 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
4142 url, full, user_agent?user_agent:"(null)", http11);
4143 else
4144 purple_debug_info("util", "requesting to fetch a URL\n");
4146 gfud = g_new0(PurpleUtilFetchUrlData, 1);
4148 gfud->callback = callback;
4149 gfud->user_data = user_data;
4150 gfud->url = g_strdup(url);
4151 gfud->user_agent = g_strdup(user_agent);
4152 gfud->http11 = http11;
4153 gfud->full = full;
4154 gfud->request = g_strdup(request);
4155 gfud->include_headers = include_headers;
4156 gfud->fd = -1;
4157 gfud->max_len = max_len;
4158 gfud->account = account;
4160 purple_url_parse(url, &gfud->website.address, &gfud->website.port,
4161 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
4163 if (purple_strcasestr(url, "https://") != NULL) {
4164 if (!purple_ssl_is_supported()) {
4165 purple_util_fetch_url_error(gfud,
4166 _("Unable to connect to %s: %s"),
4167 gfud->website.address,
4168 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
4169 return NULL;
4172 gfud->is_ssl = TRUE;
4173 gfud->ssl_connection = purple_ssl_connect(account,
4174 gfud->website.address, gfud->website.port,
4175 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
4176 } else {
4177 gfud->connect_data = purple_proxy_connect(NULL, account,
4178 gfud->website.address, gfud->website.port,
4179 url_fetch_connect_cb, gfud);
4182 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
4184 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
4185 gfud->website.address);
4186 return NULL;
4189 return gfud;
4192 void
4193 purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud)
4195 if (gfud->ssl_connection != NULL)
4196 purple_ssl_close(gfud->ssl_connection);
4198 if (gfud->connect_data != NULL)
4199 purple_proxy_connect_cancel(gfud->connect_data);
4201 if (gfud->inpa > 0)
4202 purple_input_remove(gfud->inpa);
4204 if (gfud->fd >= 0)
4205 close(gfud->fd);
4207 g_free(gfud->website.user);
4208 g_free(gfud->website.passwd);
4209 g_free(gfud->website.address);
4210 g_free(gfud->website.page);
4211 g_free(gfud->url);
4212 g_free(gfud->user_agent);
4213 g_free(gfud->request);
4214 g_free(gfud->webdata);
4216 g_free(gfud);
4219 const char *
4220 purple_url_decode(const char *str)
4222 static char buf[BUF_LEN];
4223 guint i, j = 0;
4224 char *bum;
4225 char hex[3];
4227 g_return_val_if_fail(str != NULL, NULL);
4230 * XXX - This check could be removed and buf could be made
4231 * dynamically allocated, but this is easier.
4233 if (strlen(str) >= BUF_LEN)
4234 return NULL;
4236 for (i = 0; i < strlen(str); i++) {
4238 if (str[i] != '%')
4239 buf[j++] = str[i];
4240 else {
4241 strncpy(hex, str + ++i, 2);
4242 hex[2] = '\0';
4244 /* i is pointing to the start of the number */
4245 i++;
4248 * Now it's at the end and at the start of the for loop
4249 * will be at the next character.
4251 buf[j++] = strtol(hex, NULL, 16);
4255 buf[j] = '\0';
4257 if (!g_utf8_validate(buf, -1, (const char **)&bum))
4258 *bum = '\0';
4260 return buf;
4263 const char *
4264 purple_url_encode(const char *str)
4266 const char *iter;
4267 static char buf[BUF_LEN];
4268 char utf_char[6];
4269 guint i, j = 0;
4271 g_return_val_if_fail(str != NULL, NULL);
4272 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4274 iter = str;
4275 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4276 gunichar c = g_utf8_get_char(iter);
4277 /* If the character is an ASCII character and is alphanumeric
4278 * no need to escape */
4279 if (c < 128 && (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~')) {
4280 buf[j++] = c;
4281 } else {
4282 int bytes = g_unichar_to_utf8(c, utf_char);
4283 for (i = 0; i < bytes; i++) {
4284 if (j > (BUF_LEN - 4))
4285 break;
4286 sprintf(buf + j, "%%%02X", utf_char[i] & 0xff);
4287 j += 3;
4292 buf[j] = '\0';
4294 return buf;
4297 /* Originally lifted from
4298 * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html
4299 * ... and slightly modified to be a bit more rfc822 compliant
4300 * ... and modified a bit more to make domain checking rfc1035 compliant
4301 * with the exception permitted in rfc1101 for domains to start with digit
4302 * but not completely checking to avoid conflicts with IP addresses
4304 gboolean
4305 purple_email_is_valid(const char *address)
4307 const char *c, *domain;
4308 static char *rfc822_specials = "()<>@,;:\\\"[]";
4310 g_return_val_if_fail(address != NULL, FALSE);
4312 if (*address == '.') return FALSE;
4314 /* first we validate the name portion (name@domain) (rfc822)*/
4315 for (c = address; *c; c++) {
4316 if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) {
4317 while (*++c) {
4318 if (*c == '\\') {
4319 if (*c++ && *c < 127 && *c != '\n' && *c != '\r') continue;
4320 else return FALSE;
4322 if (*c == '\"') break;
4323 if (*c < ' ' || *c >= 127) return FALSE;
4325 if (!*c++) return FALSE;
4326 if (*c == '@') break;
4327 if (*c != '.') return FALSE;
4328 continue;
4330 if (*c == '@') break;
4331 if (*c <= ' ' || *c >= 127) return FALSE;
4332 if (strchr(rfc822_specials, *c)) return FALSE;
4335 /* It's obviously not an email address if we didn't find an '@' above */
4336 if (*c == '\0') return FALSE;
4338 /* strictly we should return false if (*(c - 1) == '.') too, but I think
4339 * we should permit user.@domain type addresses - they do work :) */
4340 if (c == address) return FALSE;
4342 /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */
4343 if (!*(domain = ++c)) return FALSE;
4344 do {
4345 if (*c == '.' && (c == domain || *(c - 1) == '.' || *(c - 1) == '-'))
4346 return FALSE;
4347 if (*c == '-' && (*(c - 1) == '.' || *(c - 1) == '@')) return FALSE;
4348 if ((*c < '0' && *c != '-' && *c != '.') || (*c > '9' && *c < 'A') ||
4349 (*c > 'Z' && *c < 'a') || (*c > 'z')) return FALSE;
4350 } while (*++c);
4352 if (*(c - 1) == '-') return FALSE;
4354 return ((c - domain) > 3 ? TRUE : FALSE);
4357 gboolean
4358 purple_ipv4_address_is_valid(const char *ip)
4360 int c, o1, o2, o3, o4;
4361 char end;
4363 g_return_val_if_fail(ip != NULL, FALSE);
4365 c = sscanf(ip, "%d.%d.%d.%d%c", &o1, &o2, &o3, &o4, &end);
4366 if (c != 4 || o1 < 0 || o1 > 255 || o2 < 0 || o2 > 255 || o3 < 0 || o3 > 255 || o4 < 0 || o4 > 255)
4367 return FALSE;
4368 return TRUE;
4371 gboolean
4372 purple_ipv6_address_is_valid(const gchar *ip)
4374 const gchar *c;
4375 gboolean double_colon = FALSE;
4376 gint chunks = 1;
4377 gint in = 0;
4379 g_return_val_if_fail(ip != NULL, FALSE);
4381 if (*ip == '\0')
4382 return FALSE;
4384 for (c = ip; *c; ++c) {
4385 if ((*c >= '0' && *c <= '9') ||
4386 (*c >= 'a' && *c <= 'f') ||
4387 (*c >= 'A' && *c <= 'F')) {
4388 if (++in > 4)
4389 /* Only four hex digits per chunk */
4390 return FALSE;
4391 continue;
4392 } else if (*c == ':') {
4393 /* The start of a new chunk */
4394 ++chunks;
4395 in = 0;
4396 if (*(c + 1) == ':') {
4398 * '::' indicates a consecutive series of chunks full
4399 * of zeroes. There can be only one of these per address.
4401 if (double_colon)
4402 return FALSE;
4403 double_colon = TRUE;
4405 } else
4406 return FALSE;
4410 * Either we saw a '::' and there were fewer than 8 chunks -or-
4411 * we didn't see a '::' and saw exactly 8 chunks.
4413 return (double_colon && chunks < 8) || (!double_colon && chunks == 8);
4416 /* TODO 3.0.0: Add ipv6 check, too */
4417 gboolean
4418 purple_ip_address_is_valid(const char *ip)
4420 return purple_ipv4_address_is_valid(ip);
4423 /* Stolen from gnome_uri_list_extract_uris */
4424 GList *
4425 purple_uri_list_extract_uris(const gchar *uri_list)
4427 const gchar *p, *q;
4428 gchar *retval;
4429 GList *result = NULL;
4431 g_return_val_if_fail (uri_list != NULL, NULL);
4433 p = uri_list;
4435 /* We don't actually try to validate the URI according to RFC
4436 * 2396, or even check for allowed characters - we just ignore
4437 * comments and trim whitespace off the ends. We also
4438 * allow LF delimination as well as the specified CRLF.
4440 while (p) {
4441 if (*p != '#') {
4442 while (isspace(*p))
4443 p++;
4445 q = p;
4446 while (*q && (*q != '\n') && (*q != '\r'))
4447 q++;
4449 if (q > p) {
4450 q--;
4451 while (q > p && isspace(*q))
4452 q--;
4454 retval = (gchar*)g_malloc (q - p + 2);
4455 strncpy (retval, p, q - p + 1);
4456 retval[q - p + 1] = '\0';
4458 result = g_list_prepend (result, retval);
4461 p = strchr (p, '\n');
4462 if (p)
4463 p++;
4466 return g_list_reverse (result);
4470 /* Stolen from gnome_uri_list_extract_filenames */
4471 GList *
4472 purple_uri_list_extract_filenames(const gchar *uri_list)
4474 GList *tmp_list, *node, *result;
4476 g_return_val_if_fail (uri_list != NULL, NULL);
4478 result = purple_uri_list_extract_uris(uri_list);
4480 tmp_list = result;
4481 while (tmp_list) {
4482 gchar *s = (gchar*)tmp_list->data;
4484 node = tmp_list;
4485 tmp_list = tmp_list->next;
4487 if (!strncmp (s, "file:", 5)) {
4488 node->data = g_filename_from_uri (s, NULL, NULL);
4489 /* not sure if this fallback is useful at all */
4490 if (!node->data) node->data = g_strdup (s+5);
4491 } else {
4492 result = g_list_delete_link(result, node);
4494 g_free (s);
4496 return result;
4499 /**************************************************************************
4500 * UTF8 String Functions
4501 **************************************************************************/
4502 gchar *
4503 purple_utf8_try_convert(const char *str)
4505 gsize converted;
4506 gchar *utf8;
4508 g_return_val_if_fail(str != NULL, NULL);
4510 if (g_utf8_validate(str, -1, NULL)) {
4511 return g_strdup(str);
4514 utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL);
4515 if (utf8 != NULL)
4516 return utf8;
4518 utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL);
4519 if ((utf8 != NULL) && (converted == strlen(str)))
4520 return utf8;
4522 g_free(utf8);
4524 return NULL;
4527 #define utf8_first(x) ((x & 0x80) == 0 || (x & 0xe0) == 0xc0 \
4528 || (x & 0xf0) == 0xe0 || (x & 0xf8) == 0xf)
4529 gchar *
4530 purple_utf8_salvage(const char *str)
4532 GString *workstr;
4533 const char *end;
4535 g_return_val_if_fail(str != NULL, NULL);
4537 workstr = g_string_sized_new(strlen(str));
4539 do {
4540 g_utf8_validate(str, -1, &end);
4541 workstr = g_string_append_len(workstr, str, end - str);
4542 str = end;
4543 if (*str == '\0')
4544 break;
4545 do {
4546 workstr = g_string_append_c(workstr, '?');
4547 str++;
4548 } while (!utf8_first(*str));
4549 } while (*str != '\0');
4551 return g_string_free(workstr, FALSE);
4554 gchar *
4555 purple_utf8_strip_unprintables(const gchar *str)
4557 gchar *workstr, *iter;
4558 const gchar *bad;
4560 if (str == NULL)
4561 /* Act like g_strdup */
4562 return NULL;
4564 if (!g_utf8_validate(str, -1, &bad)) {
4565 purple_debug_error("util", "purple_utf8_strip_unprintables(%s) failed; "
4566 "first bad character was %02x (%c)\n",
4567 str, *bad, *bad);
4568 g_return_val_if_reached(NULL);
4571 workstr = iter = g_new(gchar, strlen(str) + 1);
4572 while (*str) {
4573 gunichar ch = g_utf8_get_char(str);
4574 gchar *next = g_utf8_next_char(str);
4576 * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
4577 * [#x10000-#x10FFFF]
4579 if ((ch == '\t' || ch == '\n' || ch == '\r') ||
4580 (ch >= 0x20 && ch <= 0xD7FF) ||
4581 (ch >= 0xE000 && ch <= 0xFFFD) ||
4582 (ch >= 0x10000 && ch <= 0x10FFFF)) {
4583 memcpy(iter, str, next - str);
4584 iter += (next - str);
4587 str = next;
4590 /* nul-terminate the new string */
4591 *iter = '\0';
4593 return workstr;
4597 * This function is copied from g_strerror() but changed to use
4598 * gai_strerror().
4600 G_CONST_RETURN gchar *
4601 purple_gai_strerror(gint errnum)
4603 static GStaticPrivate msg_private = G_STATIC_PRIVATE_INIT;
4604 char *msg;
4605 int saved_errno = errno;
4607 const char *msg_locale;
4609 msg_locale = gai_strerror(errnum);
4610 if (g_get_charset(NULL))
4612 /* This string is already UTF-8--great! */
4613 errno = saved_errno;
4614 return msg_locale;
4616 else
4618 gchar *msg_utf8 = g_locale_to_utf8(msg_locale, -1, NULL, NULL, NULL);
4619 if (msg_utf8)
4621 /* Stick in the quark table so that we can return a static result */
4622 GQuark msg_quark = g_quark_from_string(msg_utf8);
4623 g_free(msg_utf8);
4625 msg_utf8 = (gchar *)g_quark_to_string(msg_quark);
4626 errno = saved_errno;
4627 return msg_utf8;
4631 msg = g_static_private_get(&msg_private);
4632 if (!msg)
4634 msg = g_new(gchar, 64);
4635 g_static_private_set(&msg_private, msg, g_free);
4638 sprintf(msg, "unknown error (%d)", errnum);
4640 errno = saved_errno;
4641 return msg;
4644 char *
4645 purple_utf8_ncr_encode(const char *str)
4647 GString *out;
4649 g_return_val_if_fail(str != NULL, NULL);
4650 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4652 out = g_string_new("");
4654 for(; *str; str = g_utf8_next_char(str)) {
4655 gunichar wc = g_utf8_get_char(str);
4657 /* super simple check. hopefully not too wrong. */
4658 if(wc >= 0x80) {
4659 g_string_append_printf(out, "&#%u;", (guint32) wc);
4660 } else {
4661 g_string_append_unichar(out, wc);
4665 return g_string_free(out, FALSE);
4669 char *
4670 purple_utf8_ncr_decode(const char *str)
4672 GString *out;
4673 char *buf, *b;
4675 g_return_val_if_fail(str != NULL, NULL);
4676 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4678 buf = (char *) str;
4679 out = g_string_new("");
4681 while( (b = strstr(buf, "&#")) ) {
4682 gunichar wc;
4683 int base = 0;
4685 /* append everything leading up to the &# */
4686 g_string_append_len(out, buf, b-buf);
4688 b += 2; /* skip past the &# */
4690 /* strtoul will treat 0x prefix as hex, but not just x */
4691 if(*b == 'x' || *b == 'X') {
4692 base = 16;
4693 b++;
4696 /* advances buf to the end of the ncr segment */
4697 wc = (gunichar) strtoul(b, &buf, base);
4699 /* this mimics the previous impl of ncr_decode */
4700 if(*buf == ';') {
4701 g_string_append_unichar(out, wc);
4702 buf++;
4706 /* append whatever's left */
4707 g_string_append(out, buf);
4709 return g_string_free(out, FALSE);
4714 purple_utf8_strcasecmp(const char *a, const char *b)
4716 char *a_norm = NULL;
4717 char *b_norm = NULL;
4718 int ret = -1;
4720 if(!a && b)
4721 return -1;
4722 else if(!b && a)
4723 return 1;
4724 else if(!a && !b)
4725 return 0;
4727 if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL))
4729 purple_debug_error("purple_utf8_strcasecmp",
4730 "One or both parameters are invalid UTF8\n");
4731 return ret;
4734 a_norm = g_utf8_casefold(a, -1);
4735 b_norm = g_utf8_casefold(b, -1);
4736 ret = g_utf8_collate(a_norm, b_norm);
4737 g_free(a_norm);
4738 g_free(b_norm);
4740 return ret;
4743 /* previously conversation::find_nick() */
4744 gboolean
4745 purple_utf8_has_word(const char *haystack, const char *needle)
4747 char *hay, *pin, *p;
4748 const char *start, *prev_char;
4749 gunichar before, after;
4750 int n;
4751 gboolean ret = FALSE;
4753 start = hay = g_utf8_strdown(haystack, -1);
4755 pin = g_utf8_strdown(needle, -1);
4756 n = strlen(pin);
4758 while ((p = strstr(start, pin)) != NULL) {
4759 prev_char = g_utf8_find_prev_char(hay, p);
4760 before = -2;
4761 if (prev_char) {
4762 before = g_utf8_get_char(prev_char);
4764 after = g_utf8_get_char_validated(p + n, - 1);
4766 if ((p == hay ||
4767 /* The character before is a reasonable guess for a word boundary
4768 ("!g_unichar_isalnum()" is not a valid way to determine word
4769 boundaries, but it is the only reasonable thing to do here),
4770 and isn't the '&' from a "&amp;" or some such entity*/
4771 (before != -2 && !g_unichar_isalnum(before) && *(p - 1) != '&'))
4772 && after != -2 && !g_unichar_isalnum(after)) {
4773 ret = TRUE;
4774 break;
4776 start = p + 1;
4779 g_free(pin);
4780 g_free(hay);
4782 return ret;
4785 void
4786 purple_print_utf8_to_console(FILE *filestream, char *message)
4788 gchar *message_conv;
4789 GError *error = NULL;
4791 /* Try to convert 'message' to user's locale */
4792 message_conv = g_locale_from_utf8(message, -1, NULL, NULL, &error);
4793 if (message_conv != NULL) {
4794 fputs(message_conv, filestream);
4795 g_free(message_conv);
4797 else
4799 /* use 'message' as a fallback */
4800 g_warning("%s\n", error->message);
4801 g_error_free(error);
4802 fputs(message, filestream);
4806 gboolean purple_message_meify(char *message, gssize len)
4808 char *c;
4809 gboolean inside_html = FALSE;
4811 g_return_val_if_fail(message != NULL, FALSE);
4813 if(len == -1)
4814 len = strlen(message);
4816 for (c = message; *c; c++, len--) {
4817 if(inside_html) {
4818 if(*c == '>')
4819 inside_html = FALSE;
4820 } else {
4821 if(*c == '<')
4822 inside_html = TRUE;
4823 else
4824 break;
4828 if(*c && !g_ascii_strncasecmp(c, "/me ", 4)) {
4829 memmove(c, c+4, len-3);
4830 return TRUE;
4833 return FALSE;
4836 char *purple_text_strip_mnemonic(const char *in)
4838 char *out;
4839 char *a;
4840 char *a0;
4841 const char *b;
4843 g_return_val_if_fail(in != NULL, NULL);
4845 out = g_malloc(strlen(in)+1);
4846 a = out;
4847 b = in;
4849 a0 = a; /* The last non-space char seen so far, or the first char */
4851 while(*b) {
4852 if(*b == '_') {
4853 if(a > out && b > in && *(b-1) == '(' && *(b+1) && !(*(b+1) & 0x80) && *(b+2) == ')') {
4854 /* Detected CJK style shortcut (Bug 875311) */
4855 a = a0; /* undo the left parenthesis */
4856 b += 3; /* and skip the whole mess */
4857 } else if(*(b+1) == '_') {
4858 *(a++) = '_';
4859 b += 2;
4860 a0 = a;
4861 } else {
4862 b++;
4864 /* We don't want to corrupt the middle of UTF-8 characters */
4865 } else if (!(*b & 0x80)) { /* other 1-byte char */
4866 if (*b != ' ')
4867 a0 = a;
4868 *(a++) = *(b++);
4869 } else {
4870 /* Multibyte utf8 char, don't look for _ inside these */
4871 int n = 0;
4872 int i;
4873 if ((*b & 0xe0) == 0xc0) {
4874 n = 2;
4875 } else if ((*b & 0xf0) == 0xe0) {
4876 n = 3;
4877 } else if ((*b & 0xf8) == 0xf0) {
4878 n = 4;
4879 } else if ((*b & 0xfc) == 0xf8) {
4880 n = 5;
4881 } else if ((*b & 0xfe) == 0xfc) {
4882 n = 6;
4883 } else { /* Illegal utf8 */
4884 n = 1;
4886 a0 = a; /* unless we want to delete CJK spaces too */
4887 for (i = 0; i < n && *b; i += 1) {
4888 *(a++) = *(b++);
4892 *a = '\0';
4894 return out;
4897 const char* purple_unescape_filename(const char *escaped) {
4898 return purple_url_decode(escaped);
4902 /* this is almost identical to purple_url_encode (hence purple_url_decode
4903 * being used above), but we want to keep certain characters unescaped
4904 * for compat reasons */
4905 const char *
4906 purple_escape_filename(const char *str)
4908 const char *iter;
4909 static char buf[BUF_LEN];
4910 char utf_char[6];
4911 guint i, j = 0;
4913 g_return_val_if_fail(str != NULL, NULL);
4914 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4916 iter = str;
4917 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4918 gunichar c = g_utf8_get_char(iter);
4919 /* If the character is an ASCII character and is alphanumeric,
4920 * or one of the specified values, no need to escape */
4921 if (c < 128 && (g_ascii_isalnum(c) || c == '@' || c == '-' ||
4922 c == '_' || c == '.' || c == '#')) {
4923 buf[j++] = c;
4924 } else {
4925 int bytes = g_unichar_to_utf8(c, utf_char);
4926 for (i = 0; i < bytes; i++) {
4927 if (j > (BUF_LEN - 4))
4928 break;
4929 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff);
4930 j += 3;
4934 #ifdef _WIN32
4935 /* File/Directory names in windows cannot end in periods/spaces.
4936 * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
4938 while (j > 0 && (buf[j - 1] == '.' || buf[j - 1] == ' '))
4939 j--;
4940 #endif
4941 buf[j] = '\0';
4943 return buf;
4946 const char *_purple_oscar_convert(const char *act, const char *protocol)
4948 if (act && purple_strequal(protocol, "prpl-oscar")) {
4949 int i;
4950 for (i = 0; act[i] != '\0'; i++)
4951 if (!isdigit(act[i]))
4952 return "prpl-aim";
4953 return "prpl-icq";
4955 return protocol;
4958 void purple_restore_default_signal_handlers(void)
4960 #ifndef _WIN32
4961 #ifdef HAVE_SIGNAL_H
4962 signal(SIGHUP, SIG_DFL); /* 1: terminal line hangup */
4963 signal(SIGINT, SIG_DFL); /* 2: interrupt program */
4964 signal(SIGQUIT, SIG_DFL); /* 3: quit program */
4965 signal(SIGILL, SIG_DFL); /* 4: illegal instruction (not reset when caught) */
4966 signal(SIGTRAP, SIG_DFL); /* 5: trace trap (not reset when caught) */
4967 signal(SIGABRT, SIG_DFL); /* 6: abort program */
4969 #ifdef SIGPOLL
4970 signal(SIGPOLL, SIG_DFL); /* 7: pollable event (POSIX) */
4971 #endif /* SIGPOLL */
4973 #ifdef SIGEMT
4974 signal(SIGEMT, SIG_DFL); /* 7: EMT instruction (Non-POSIX) */
4975 #endif /* SIGEMT */
4977 signal(SIGFPE, SIG_DFL); /* 8: floating point exception */
4978 signal(SIGBUS, SIG_DFL); /* 10: bus error */
4979 signal(SIGSEGV, SIG_DFL); /* 11: segmentation violation */
4980 signal(SIGSYS, SIG_DFL); /* 12: bad argument to system call */
4981 signal(SIGPIPE, SIG_DFL); /* 13: write on a pipe with no reader */
4982 signal(SIGALRM, SIG_DFL); /* 14: real-time timer expired */
4983 signal(SIGTERM, SIG_DFL); /* 15: software termination signal */
4984 signal(SIGCHLD, SIG_DFL); /* 20: child status has changed */
4985 signal(SIGXCPU, SIG_DFL); /* 24: exceeded CPU time limit */
4986 signal(SIGXFSZ, SIG_DFL); /* 25: exceeded file size limit */
4987 #endif /* HAVE_SIGNAL_H */
4988 #endif /* !_WIN32 */
4991 static void
4992 set_status_with_attrs(PurpleStatus *status, ...)
4994 va_list args;
4995 va_start(args, status);
4996 purple_status_set_active_with_attrs(status, TRUE, args);
4997 va_end(args);
5000 void purple_util_set_current_song(const char *title, const char *artist, const char *album)
5002 GList *list = purple_accounts_get_all();
5003 for (; list; list = list->next) {
5004 PurplePresence *presence;
5005 PurpleStatus *tune;
5006 PurpleAccount *account = list->data;
5007 if (!purple_account_get_enabled(account, purple_core_get_ui()))
5008 continue;
5010 presence = purple_account_get_presence(account);
5011 tune = purple_presence_get_status(presence, "tune");
5012 if (!tune)
5013 continue;
5014 if (title) {
5015 set_status_with_attrs(tune,
5016 PURPLE_TUNE_TITLE, title,
5017 PURPLE_TUNE_ARTIST, artist,
5018 PURPLE_TUNE_ALBUM, album,
5019 NULL);
5020 } else {
5021 purple_status_set_active(tune, FALSE);
5026 char * purple_util_format_song_info(const char *title, const char *artist, const char *album, gpointer unused)
5028 GString *string;
5029 char *esc;
5031 if (!title || !*title)
5032 return NULL;
5034 esc = g_markup_escape_text(title, -1);
5035 string = g_string_new("");
5036 g_string_append_printf(string, "%s", esc);
5037 g_free(esc);
5039 if (artist && *artist) {
5040 esc = g_markup_escape_text(artist, -1);
5041 g_string_append_printf(string, _(" - %s"), esc);
5042 g_free(esc);
5045 if (album && *album) {
5046 esc = g_markup_escape_text(album, -1);
5047 g_string_append_printf(string, _(" (%s)"), esc);
5048 g_free(esc);
5051 return g_string_free(string, FALSE);
5054 const gchar *
5055 purple_get_host_name(void)
5057 return g_get_host_name();
5060 gchar *
5061 purple_uuid_random(void)
5063 guint32 tmp, a, b;
5065 tmp = g_random_int();
5066 a = 0x4000 | (tmp & 0xFFF); /* 0x4000 to 0x4FFF */
5067 tmp >>= 12;
5068 b = ((1 << 3) << 12) | (tmp & 0x3FFF); /* 0x8000 to 0xBFFF */
5070 tmp = g_random_int();
5072 return g_strdup_printf("%08x-%04x-%04x-%04x-%04x%08x",
5073 g_random_int(),
5074 tmp & 0xFFFF,
5077 (tmp >> 16) & 0xFFFF, g_random_int());