Fix crashes when filenames end up being NULL in some prpls.
[pidgin-git.git] / libpurple / util.c
bloba71b89643090187d5087a3c44fd0749c1bb638c0
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 "prpl.h"
32 #include "prefs.h"
33 #include "util.h"
35 struct _PurpleUtilFetchUrlData
37 PurpleUtilFetchUrlCallback callback;
38 void *user_data;
40 struct
42 char *user;
43 char *passwd;
44 char *address;
45 int port;
46 char *page;
48 } website;
50 char *url;
51 int num_times_redirected;
52 gboolean full;
53 char *user_agent;
54 gboolean http11;
55 char *request;
56 gsize request_written;
57 gboolean include_headers;
59 gboolean is_ssl;
60 PurpleSslConnection *ssl_connection;
61 PurpleProxyConnectData *connect_data;
62 int fd;
63 guint inpa;
65 gboolean got_headers;
66 gboolean has_explicit_data_len;
67 char *webdata;
68 unsigned long len;
69 unsigned long data_len;
70 gssize max_len;
73 static char *custom_user_dir = NULL;
74 static char *user_dir = NULL;
77 PurpleMenuAction *
78 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
79 GList *children)
81 PurpleMenuAction *act = g_new0(PurpleMenuAction, 1);
82 act->label = g_strdup(label);
83 act->callback = callback;
84 act->data = data;
85 act->children = children;
86 return act;
89 void
90 purple_menu_action_free(PurpleMenuAction *act)
92 g_return_if_fail(act != NULL);
94 g_free(act->label);
95 g_free(act);
98 void
99 purple_util_init(void)
101 /* This does nothing right now. It exists for symmetry with
102 * purple_util_uninit() and forwards compatibility. */
105 void
106 purple_util_uninit(void)
108 /* Free these so we don't have leaks at shutdown. */
110 g_free(custom_user_dir);
111 custom_user_dir = NULL;
113 g_free(user_dir);
114 user_dir = NULL;
117 /**************************************************************************
118 * Base16 Functions
119 **************************************************************************/
120 gchar *
121 purple_base16_encode(const guchar *data, gsize len)
123 int i;
124 gchar *ascii = NULL;
126 g_return_val_if_fail(data != NULL, NULL);
127 g_return_val_if_fail(len > 0, NULL);
129 ascii = g_malloc(len * 2 + 1);
131 for (i = 0; i < len; i++)
132 snprintf(&ascii[i * 2], 3, "%02hhx", data[i]);
134 return ascii;
137 guchar *
138 purple_base16_decode(const char *str, gsize *ret_len)
140 int len, i, accumulator = 0;
141 guchar *data;
143 g_return_val_if_fail(str != NULL, NULL);
145 len = strlen(str);
147 g_return_val_if_fail(strlen(str) > 0, 0);
148 g_return_val_if_fail(len % 2 == 0, 0);
150 data = g_malloc(len / 2);
152 for (i = 0; i < len; i++)
154 if ((i % 2) == 0)
155 accumulator = 0;
156 else
157 accumulator <<= 4;
159 if (isdigit(str[i]))
160 accumulator |= str[i] - 48;
161 else
163 switch(tolower(str[i]))
165 case 'a': accumulator |= 10; break;
166 case 'b': accumulator |= 11; break;
167 case 'c': accumulator |= 12; break;
168 case 'd': accumulator |= 13; break;
169 case 'e': accumulator |= 14; break;
170 case 'f': accumulator |= 15; break;
174 if (i % 2)
175 data[(i - 1) / 2] = accumulator;
178 if (ret_len != NULL)
179 *ret_len = len / 2;
181 return data;
184 gchar *
185 purple_base16_encode_chunked(const guchar *data, gsize len)
187 int i;
188 gchar *ascii = NULL;
190 g_return_val_if_fail(data != NULL, NULL);
191 g_return_val_if_fail(len > 0, NULL);
193 /* For each byte of input, we need 2 bytes for the hex representation
194 * and 1 for the colon.
195 * The final colon will be replaced by a terminating NULL
197 ascii = g_malloc(len * 3 + 1);
199 for (i = 0; i < len; i++)
200 g_snprintf(&ascii[i * 3], 4, "%02hhx:", data[i]);
202 /* Replace the final colon with NULL */
203 ascii[len * 3 - 1] = 0;
205 return ascii;
209 /**************************************************************************
210 * Base64 Functions
211 **************************************************************************/
212 static const char alphabet[] =
213 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
214 "0123456789+/";
216 static const char xdigits[] =
217 "0123456789abcdef";
219 gchar *
220 purple_base64_encode(const guchar *data, gsize len)
222 char *out, *rv;
224 g_return_val_if_fail(data != NULL, NULL);
225 g_return_val_if_fail(len > 0, NULL);
227 rv = out = g_malloc(((len/3)+1)*4 + 1);
229 for (; len >= 3; len -= 3)
231 *out++ = alphabet[data[0] >> 2];
232 *out++ = alphabet[((data[0] << 4) & 0x30) | (data[1] >> 4)];
233 *out++ = alphabet[((data[1] << 2) & 0x3c) | (data[2] >> 6)];
234 *out++ = alphabet[data[2] & 0x3f];
235 data += 3;
238 if (len > 0)
240 unsigned char fragment;
242 *out++ = alphabet[data[0] >> 2];
243 fragment = (data[0] << 4) & 0x30;
245 if (len > 1)
246 fragment |= data[1] >> 4;
248 *out++ = alphabet[fragment];
249 *out++ = (len < 2) ? '=' : alphabet[(data[1] << 2) & 0x3c];
250 *out++ = '=';
253 *out = '\0';
255 return rv;
258 guchar *
259 purple_base64_decode(const char *str, gsize *ret_len)
261 guchar *out = NULL;
262 char tmp = 0;
263 const char *c;
264 gint32 tmp2 = 0;
265 int len = 0, n = 0;
267 g_return_val_if_fail(str != NULL, NULL);
269 c = str;
271 while (*c) {
272 if (*c >= 'A' && *c <= 'Z') {
273 tmp = *c - 'A';
274 } else if (*c >= 'a' && *c <= 'z') {
275 tmp = 26 + (*c - 'a');
276 } else if (*c >= '0' && *c <= 57) {
277 tmp = 52 + (*c - '0');
278 } else if (*c == '+') {
279 tmp = 62;
280 } else if (*c == '/') {
281 tmp = 63;
282 } else if (*c == '\r' || *c == '\n') {
283 c++;
284 continue;
285 } else if (*c == '=') {
286 if (n == 3) {
287 out = g_realloc(out, len + 2);
288 out[len] = (guchar)(tmp2 >> 10) & 0xff;
289 len++;
290 out[len] = (guchar)(tmp2 >> 2) & 0xff;
291 len++;
292 } else if (n == 2) {
293 out = g_realloc(out, len + 1);
294 out[len] = (guchar)(tmp2 >> 4) & 0xff;
295 len++;
297 break;
299 tmp2 = ((tmp2 << 6) | (tmp & 0xff));
300 n++;
301 if (n == 4) {
302 out = g_realloc(out, len + 3);
303 out[len] = (guchar)((tmp2 >> 16) & 0xff);
304 len++;
305 out[len] = (guchar)((tmp2 >> 8) & 0xff);
306 len++;
307 out[len] = (guchar)(tmp2 & 0xff);
308 len++;
309 tmp2 = 0;
310 n = 0;
312 c++;
315 out = g_realloc(out, len + 1);
316 out[len] = 0;
318 if (ret_len != NULL)
319 *ret_len = len;
321 return out;
324 /**************************************************************************
325 * Quoted Printable Functions (see RFC 2045).
326 **************************************************************************/
327 guchar *
328 purple_quotedp_decode(const char *str, gsize *ret_len)
330 char *n, *new;
331 const char *end, *p;
333 n = new = g_malloc(strlen (str) + 1);
334 end = str + strlen(str);
336 for (p = str; p < end; p++, n++) {
337 if (*p == '=') {
338 if (p[1] == '\r' && p[2] == '\n') { /* 5.1 #5 */
339 n -= 1;
340 p += 2;
341 } else if (p[1] == '\n') { /* fuzzy case for 5.1 #5 */
342 n -= 1;
343 p += 1;
344 } else if (p[1] && p[2]) {
345 char *nibble1 = strchr(xdigits, tolower(p[1]));
346 char *nibble2 = strchr(xdigits, tolower(p[2]));
347 if (nibble1 && nibble2) { /* 5.1 #1 */
348 *n = ((nibble1 - xdigits) << 4) | (nibble2 - xdigits);
349 p += 2;
350 } else { /* This should never happen */
351 *n = *p;
353 } else { /* This should never happen */
354 *n = *p;
357 else if (*p == '_')
358 *n = ' ';
359 else
360 *n = *p;
363 *n = '\0';
365 if (ret_len != NULL)
366 *ret_len = n - new;
368 /* Resize to take less space */
369 /* new = realloc(new, n - new); */
371 return (guchar *)new;
374 /**************************************************************************
375 * MIME Functions
376 **************************************************************************/
377 char *
378 purple_mime_decode_field(const char *str)
381 * This is wing's version, partially based on revo/shx's version
382 * See RFC2047 [which apparently obsoletes RFC1342]
384 typedef enum {
385 state_start, state_equal1, state_question1,
386 state_charset, state_question2,
387 state_encoding, state_question3,
388 state_encoded_text, state_question4, state_equal2 = state_start
389 } encoded_word_state_t;
390 encoded_word_state_t state = state_start;
391 const char *cur, *mark;
392 const char *charset0 = NULL, *encoding0 = NULL, *encoded_text0 = NULL;
393 char *n, *new;
395 /* token can be any CHAR (supposedly ISO8859-1/ISO2022), not just ASCII */
396 #define token_char_p(c) \
397 (c != ' ' && !iscntrl(c) && !strchr("()<>@,;:\"/[]?.=", c))
399 /* But encoded-text must be ASCII; alas, isascii() may not exist */
400 #define encoded_text_char_p(c) \
401 ((c & 0x80) == 0 && c != '?' && c != ' ' && isgraph(c))
403 #define RECOVER_MARKED_TEXT strncpy(n, mark, cur - mark + 1); \
404 n += cur - mark + 1
406 g_return_val_if_fail(str != NULL, NULL);
408 /* NOTE: Assuming that we need just strlen(str)+1 *may* be wrong.
409 * It would be wrong if one byte (in some unknown encoding) could
410 * expand to >=4 bytes of UTF-8; I don't know if there are such things.
412 n = new = g_malloc(strlen(str) + 1);
414 /* Here we will be looking for encoded words and if they seem to be
415 * valid then decode them.
416 * They are of this form: =?charset?encoding?text?=
419 for (cur = str, mark = NULL; *cur; cur += 1) {
420 switch (state) {
421 case state_equal1:
422 if (*cur == '?') {
423 state = state_question1;
424 } else {
425 RECOVER_MARKED_TEXT;
426 state = state_start;
428 break;
429 case state_question1:
430 if (token_char_p(*cur)) {
431 charset0 = cur;
432 state = state_charset;
433 } else { /* This should never happen */
434 RECOVER_MARKED_TEXT;
435 state = state_start;
437 break;
438 case state_charset:
439 if (*cur == '?') {
440 state = state_question2;
441 } else if (!token_char_p(*cur)) { /* This should never happen */
442 RECOVER_MARKED_TEXT;
443 state = state_start;
445 break;
446 case state_question2:
447 if (token_char_p(*cur)) {
448 encoding0 = cur;
449 state = state_encoding;
450 } else { /* This should never happen */
451 RECOVER_MARKED_TEXT;
452 state = state_start;
454 break;
455 case state_encoding:
456 if (*cur == '?') {
457 state = state_question3;
458 } else if (!token_char_p(*cur)) { /* This should never happen */
459 RECOVER_MARKED_TEXT;
460 state = state_start;
462 break;
463 case state_question3:
464 if (encoded_text_char_p(*cur)) {
465 encoded_text0 = cur;
466 state = state_encoded_text;
467 } else if (*cur == '?') { /* empty string */
468 encoded_text0 = cur;
469 state = state_question4;
470 } else { /* This should never happen */
471 RECOVER_MARKED_TEXT;
472 state = state_start;
474 break;
475 case state_encoded_text:
476 if (*cur == '?') {
477 state = state_question4;
478 } else if (!encoded_text_char_p(*cur)) {
479 RECOVER_MARKED_TEXT;
480 state = state_start;
482 break;
483 case state_question4:
484 if (*cur == '=') { /* Got the whole encoded-word */
485 char *charset = g_strndup(charset0, encoding0 - charset0 - 1);
486 char *encoding = g_strndup(encoding0, encoded_text0 - encoding0 - 1);
487 char *encoded_text = g_strndup(encoded_text0, cur - encoded_text0 - 1);
488 guchar *decoded = NULL;
489 gsize dec_len;
490 if (g_ascii_strcasecmp(encoding, "Q") == 0)
491 decoded = purple_quotedp_decode(encoded_text, &dec_len);
492 else if (g_ascii_strcasecmp(encoding, "B") == 0)
493 decoded = purple_base64_decode(encoded_text, &dec_len);
494 else
495 decoded = NULL;
496 if (decoded) {
497 gsize len;
498 char *converted = g_convert((const gchar *)decoded, dec_len, "utf-8", charset, NULL, &len, NULL);
500 if (converted) {
501 n = strncpy(n, converted, len) + len;
502 g_free(converted);
504 g_free(decoded);
506 g_free(charset);
507 g_free(encoding);
508 g_free(encoded_text);
509 state = state_equal2; /* Restart the FSM */
510 } else { /* This should never happen */
511 RECOVER_MARKED_TEXT;
512 state = state_start;
514 break;
515 default:
516 if (*cur == '=') {
517 mark = cur;
518 state = state_equal1;
519 } else {
520 /* Some unencoded text. */
521 *n = *cur;
522 n += 1;
524 break;
525 } /* switch */
526 } /* for */
528 if (state != state_start) {
529 RECOVER_MARKED_TEXT;
531 *n = '\0';
533 return new;
537 /**************************************************************************
538 * Date/Time Functions
539 **************************************************************************/
541 const char *purple_get_tzoff_str(const struct tm *tm, gboolean iso)
543 static char buf[7];
544 long off;
545 gint8 min;
546 gint8 hrs;
547 struct tm new_tm = *tm;
549 mktime(&new_tm);
551 if (new_tm.tm_isdst < 0)
552 g_return_val_if_reached("");
554 #ifdef _WIN32
555 if ((off = wpurple_get_tz_offset()) == -1)
556 return "";
557 #else
558 # ifdef HAVE_TM_GMTOFF
559 off = new_tm.tm_gmtoff;
560 # else
561 # ifdef HAVE_TIMEZONE
562 tzset();
563 off = -1 * timezone;
564 # endif /* HAVE_TIMEZONE */
565 # endif /* !HAVE_TM_GMTOFF */
566 #endif /* _WIN32 */
568 min = (off / 60) % 60;
569 hrs = ((off / 60) - min) / 60;
571 if(iso) {
572 if (0 == off) {
573 strcpy(buf, "Z");
574 } else {
575 /* please leave the colons...they're optional for iso, but jabber
576 * wants them */
577 if(g_snprintf(buf, sizeof(buf), "%+03d:%02d", hrs, ABS(min)) > 6)
578 g_return_val_if_reached("");
580 } else {
581 if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5)
582 g_return_val_if_reached("");
585 return buf;
588 /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */
589 #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32)
590 static size_t purple_internal_strftime(char *s, size_t max, const char *format, const struct tm *tm)
592 const char *start;
593 const char *c;
594 char *fmt = NULL;
596 /* Yes, this is checked in purple_utf8_strftime(),
597 * but better safe than sorry. -- rlaager */
598 g_return_val_if_fail(format != NULL, 0);
600 /* This is fairly efficient, and it only gets
601 * executed on Windows or if the underlying
602 * system doesn't support the %z format string,
603 * for strftime() so I think it's good enough.
604 * -- rlaager */
605 for (c = start = format; *c ; c++)
607 if (*c != '%')
608 continue;
610 c++;
612 #ifndef HAVE_STRFTIME_Z_FORMAT
613 if (*c == 'z')
615 char *tmp = g_strdup_printf("%s%.*s%s",
616 fmt ? fmt : "",
617 c - start - 1,
618 start,
619 purple_get_tzoff_str(tm, FALSE));
620 g_free(fmt);
621 fmt = tmp;
622 start = c + 1;
624 #endif
625 #ifdef _WIN32
626 if (*c == 'Z')
628 char *tmp = g_strdup_printf("%s%.*s%s",
629 fmt ? fmt : "",
630 c - start - 1,
631 start,
632 wpurple_get_timezone_abbreviation(tm));
633 g_free(fmt);
634 fmt = tmp;
635 start = c + 1;
637 #endif
640 if (fmt != NULL)
642 size_t ret;
644 if (*start)
646 char *tmp = g_strconcat(fmt, start, NULL);
647 g_free(fmt);
648 fmt = tmp;
651 ret = strftime(s, max, fmt, tm);
652 g_free(fmt);
654 return ret;
657 return strftime(s, max, format, tm);
659 #else /* HAVE_STRFTIME_Z_FORMAT && !_WIN32 */
660 #define purple_internal_strftime strftime
661 #endif
663 const char *
664 purple_utf8_strftime(const char *format, const struct tm *tm)
666 static char buf[128];
667 char *locale;
668 GError *err = NULL;
669 int len;
670 char *utf8;
672 g_return_val_if_fail(format != NULL, NULL);
674 if (tm == NULL)
676 time_t now = time(NULL);
677 tm = localtime(&now);
680 locale = g_locale_from_utf8(format, -1, NULL, NULL, &err);
681 if (err != NULL)
683 purple_debug_error("util", "Format conversion failed in purple_utf8_strftime(): %s\n", err->message);
684 g_error_free(err);
685 locale = g_strdup(format);
688 /* A return value of 0 is either an error (in
689 * which case, the contents of the buffer are
690 * undefined) or the empty string (in which
691 * case, no harm is done here). */
692 if ((len = purple_internal_strftime(buf, sizeof(buf), locale, tm)) == 0)
694 g_free(locale);
695 return "";
698 g_free(locale);
700 utf8 = g_locale_to_utf8(buf, len, NULL, NULL, &err);
701 if (err != NULL)
703 purple_debug_error("util", "Result conversion failed in purple_utf8_strftime(): %s\n", err->message);
704 g_error_free(err);
706 else
708 purple_strlcpy(buf, utf8);
709 g_free(utf8);
712 return buf;
715 const char *
716 purple_date_format_short(const struct tm *tm)
718 return purple_utf8_strftime("%x", tm);
721 const char *
722 purple_date_format_long(const struct tm *tm)
725 * This string determines how some dates are displayed. The default
726 * string "%x %X" shows the date then the time. Translators can
727 * change this to "%X %x" if they want the time to be shown first,
728 * followed by the date.
730 return purple_utf8_strftime(_("%x %X"), tm);
733 const char *
734 purple_date_format_full(const struct tm *tm)
736 return purple_utf8_strftime("%c", tm);
739 const char *
740 purple_time_format(const struct tm *tm)
742 return purple_utf8_strftime("%X", tm);
745 time_t
746 purple_time_build(int year, int month, int day, int hour, int min, int sec)
748 struct tm tm;
750 tm.tm_year = year - 1900;
751 tm.tm_mon = month - 1;
752 tm.tm_mday = day;
753 tm.tm_hour = hour;
754 tm.tm_min = min;
755 tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
757 return mktime(&tm);
760 time_t
761 purple_str_to_time(const char *timestamp, gboolean utc,
762 struct tm *tm, long *tz_off, const char **rest)
764 time_t retval = 0;
765 static struct tm t;
766 const char *c = timestamp;
767 int year = 0;
768 long tzoff = PURPLE_NO_TZ_OFF;
770 time(&retval);
771 localtime_r(&retval, &t);
773 if (rest != NULL)
774 *rest = NULL;
776 /* 4 digit year */
777 if (sscanf(c, "%04d", &year) && year > 1900)
779 c += 4;
780 if (*c == '-')
781 c++;
782 t.tm_year = year - 1900;
785 /* 2 digit month */
786 if (!sscanf(c, "%02d", &t.tm_mon))
788 if (rest != NULL && *c != '\0')
789 *rest = c;
790 return 0;
792 c += 2;
793 if (*c == '-' || *c == '/')
794 c++;
795 t.tm_mon -= 1;
797 /* 2 digit day */
798 if (!sscanf(c, "%02d", &t.tm_mday))
800 if (rest != NULL && *c != '\0')
801 *rest = c;
802 return 0;
804 c += 2;
805 if (*c == '/')
807 c++;
809 if (!sscanf(c, "%04d", &t.tm_year))
811 if (rest != NULL && *c != '\0')
812 *rest = c;
813 return 0;
815 t.tm_year -= 1900;
817 else if (*c == 'T' || *c == '.')
819 c++;
820 /* we have more than a date, keep going */
822 /* 2 digit hour */
823 if ((sscanf(c, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 && (c = c + 8)) ||
824 (sscanf(c, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 && (c = c + 6)))
826 gboolean offset_positive = FALSE;
827 int tzhrs;
828 int tzmins;
830 t.tm_isdst = -1;
832 if (*c == '.') {
833 do {
834 c++;
835 } while (*c >= '0' && *c <= '9'); /* dealing with precision we don't care about */
837 if (*c == '+')
838 offset_positive = TRUE;
839 if (((*c == '+' || *c == '-') && (c = c + 1)) &&
840 ((sscanf(c, "%02d:%02d", &tzhrs, &tzmins) == 2 && (c = c + 5)) ||
841 (sscanf(c, "%02d%02d", &tzhrs, &tzmins) == 2 && (c = c + 4))))
843 tzoff = tzhrs*60*60 + tzmins*60;
844 if (offset_positive)
845 tzoff *= -1;
847 else if ((*c == 'Z') && (c = c + 1))
849 /* 'Z' = Zulu = UTC */
850 tzoff = 0;
852 else if (utc)
854 static struct tm tmptm;
855 time_t tmp;
856 tmp = mktime(&t);
857 /* we care about whether it *was* dst, and the offset, here on this
858 * date, not whether we are currently observing dst locally *now*.
859 * This isn't perfect, because we would need to know in advance the
860 * offset we are trying to work out in advance to be sure this
861 * works for times around dst transitions but it'll have to do. */
862 localtime_r(&tmp, &tmptm);
863 t.tm_isdst = tmptm.tm_isdst;
864 #ifdef HAVE_TM_GMTOFF
865 t.tm_gmtoff = tmptm.tm_gmtoff;
866 #endif
869 if (rest != NULL && *c != '\0')
871 if (*c == ' ')
872 c++;
873 if (*c != '\0')
874 *rest = c;
877 if (tzoff != PURPLE_NO_TZ_OFF || utc)
879 #if defined(_WIN32)
880 long sys_tzoff;
881 #endif
883 #if defined(_WIN32) || defined(HAVE_TM_GMTOFF) || defined (HAVE_TIMEZONE)
884 if (tzoff == PURPLE_NO_TZ_OFF)
885 tzoff = 0;
886 #endif
888 #ifdef _WIN32
889 if ((sys_tzoff = wpurple_get_tz_offset()) == -1)
890 tzoff = PURPLE_NO_TZ_OFF;
891 else
892 tzoff += sys_tzoff;
893 #else
894 #ifdef HAVE_TM_GMTOFF
895 tzoff += t.tm_gmtoff;
896 #else
897 # ifdef HAVE_TIMEZONE
898 tzset(); /* making sure */
899 tzoff -= timezone;
900 # endif
901 #endif
902 #endif /* _WIN32 */
905 else
907 if (rest != NULL && *c != '\0')
908 *rest = c;
912 retval = mktime(&t);
914 if (tm != NULL)
915 *tm = t;
917 if (tzoff != PURPLE_NO_TZ_OFF)
918 retval += tzoff;
920 if (tz_off != NULL)
921 *tz_off = tzoff;
923 return retval;
926 /**************************************************************************
927 * Markup Functions
928 **************************************************************************/
930 const char *
931 purple_markup_unescape_entity(const char *text, int *length)
933 const char *pln;
934 int len, pound;
935 char temp[2];
937 if (!text || *text != '&')
938 return NULL;
940 #define IS_ENTITY(s) (!g_ascii_strncasecmp(text, s, (len = sizeof(s) - 1)))
942 if(IS_ENTITY("&amp;"))
943 pln = "&";
944 else if(IS_ENTITY("&lt;"))
945 pln = "<";
946 else if(IS_ENTITY("&gt;"))
947 pln = ">";
948 else if(IS_ENTITY("&nbsp;"))
949 pln = " ";
950 else if(IS_ENTITY("&copy;"))
951 pln = "\302\251"; /* or use g_unichar_to_utf8(0xa9); */
952 else if(IS_ENTITY("&quot;"))
953 pln = "\"";
954 else if(IS_ENTITY("&reg;"))
955 pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */
956 else if(IS_ENTITY("&apos;"))
957 pln = "\'";
958 else if(*(text+1) == '#' &&
959 (sscanf(text, "&#%u%1[;]", &pound, temp) == 2 ||
960 sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
961 pound != 0) {
962 static char buf[7];
963 int buflen = g_unichar_to_utf8((gunichar)pound, buf);
964 buf[buflen] = '\0';
965 pln = buf;
967 len = 2;
968 while(isdigit((gint) text[len])) len++;
969 if(text[len] == ';') len++;
971 else
972 return NULL;
974 if (length)
975 *length = len;
976 return pln;
979 char *
980 purple_markup_get_css_property(const gchar *style,
981 const gchar *opt)
983 const gchar *css_str = style;
984 const gchar *css_value_start;
985 const gchar *css_value_end;
986 gchar *tmp;
987 gchar *ret;
989 g_return_val_if_fail(opt != NULL, NULL);
991 if (!css_str)
992 return NULL;
994 /* find the CSS property */
995 while (1)
997 /* skip whitespace characters */
998 while (*css_str && g_ascii_isspace(*css_str))
999 css_str++;
1000 if (!g_ascii_isalpha(*css_str))
1001 return NULL;
1002 if (g_ascii_strncasecmp(css_str, opt, strlen(opt)))
1004 /* go to next css property positioned after the next ';' */
1005 while (*css_str && *css_str != '"' && *css_str != ';')
1006 css_str++;
1007 if(*css_str != ';')
1008 return NULL;
1009 css_str++;
1011 else
1012 break;
1015 /* find the CSS value position in the string */
1016 css_str += strlen(opt);
1017 while (*css_str && g_ascii_isspace(*css_str))
1018 css_str++;
1019 if (*css_str != ':')
1020 return NULL;
1021 css_str++;
1022 while (*css_str && g_ascii_isspace(*css_str))
1023 css_str++;
1024 if (*css_str == '\0' || *css_str == '"' || *css_str == ';')
1025 return NULL;
1027 /* mark the CSS value */
1028 css_value_start = css_str;
1029 while (*css_str && *css_str != '"' && *css_str != ';')
1030 css_str++;
1031 css_value_end = css_str - 1;
1033 /* Removes trailing whitespace */
1034 while (css_value_end > css_value_start && g_ascii_isspace(*css_value_end))
1035 css_value_end--;
1037 tmp = g_strndup(css_value_start, css_value_end - css_value_start + 1);
1038 ret = purple_unescape_html(tmp);
1039 g_free(tmp);
1041 return ret;
1044 gboolean
1045 purple_markup_find_tag(const char *needle, const char *haystack,
1046 const char **start, const char **end, GData **attributes)
1048 GData *attribs;
1049 const char *cur = haystack;
1050 char *name = NULL;
1051 gboolean found = FALSE;
1052 gboolean in_tag = FALSE;
1053 gboolean in_attr = FALSE;
1054 const char *in_quotes = NULL;
1055 size_t needlelen;
1057 g_return_val_if_fail( needle != NULL, FALSE);
1058 g_return_val_if_fail( *needle != '\0', FALSE);
1059 g_return_val_if_fail( haystack != NULL, FALSE);
1060 g_return_val_if_fail( start != NULL, FALSE);
1061 g_return_val_if_fail( end != NULL, FALSE);
1062 g_return_val_if_fail(attributes != NULL, FALSE);
1064 needlelen = strlen(needle);
1065 g_datalist_init(&attribs);
1067 while (*cur && !found) {
1068 if (in_tag) {
1069 if (in_quotes) {
1070 const char *close = cur;
1072 while (*close && *close != *in_quotes)
1073 close++;
1075 /* if we got the close quote, store the value and carry on from *
1076 * after it. if we ran to the end of the string, point to the NULL *
1077 * and we're outta here */
1078 if (*close) {
1079 /* only store a value if we have an attribute name */
1080 if (name) {
1081 size_t len = close - cur;
1082 char *val = g_strndup(cur, len);
1084 g_datalist_set_data_full(&attribs, name, val, g_free);
1085 g_free(name);
1086 name = NULL;
1089 in_quotes = NULL;
1090 cur = close + 1;
1091 } else {
1092 cur = close;
1094 } else if (in_attr) {
1095 const char *close = cur;
1097 while (*close && *close != '>' && *close != '"' &&
1098 *close != '\'' && *close != ' ' && *close != '=')
1099 close++;
1101 /* if we got the equals, store the name of the attribute. if we got
1102 * the quote, save the attribute and go straight to quote mode.
1103 * otherwise the tag closed or we reached the end of the string,
1104 * so we can get outta here */
1105 switch (*close) {
1106 case '"':
1107 case '\'':
1108 in_quotes = close;
1109 case '=':
1111 size_t len = close - cur;
1113 /* don't store a blank attribute name */
1114 if (len) {
1115 g_free(name);
1116 name = g_ascii_strdown(cur, len);
1119 in_attr = FALSE;
1120 cur = close + 1;
1121 break;
1123 case ' ':
1124 case '>':
1125 in_attr = FALSE;
1126 default:
1127 cur = close;
1128 break;
1130 } else {
1131 switch (*cur) {
1132 case ' ':
1133 /* swallow extra spaces inside tag */
1134 while (*cur && *cur == ' ') cur++;
1135 in_attr = TRUE;
1136 break;
1137 case '>':
1138 found = TRUE;
1139 *end = cur;
1140 break;
1141 case '"':
1142 case '\'':
1143 in_quotes = cur;
1144 default:
1145 cur++;
1146 break;
1149 } else {
1150 /* if we hit a < followed by the name of our tag... */
1151 if (*cur == '<' && !g_ascii_strncasecmp(cur + 1, needle, needlelen)) {
1152 *start = cur;
1153 cur = cur + needlelen + 1;
1155 /* if we're pointing at a space or a >, we found the right tag. if *
1156 * we're not, we've found a longer tag, so we need to skip to the *
1157 * >, but not being distracted by >s inside quotes. */
1158 if (*cur == ' ' || *cur == '>') {
1159 in_tag = TRUE;
1160 } else {
1161 while (*cur && *cur != '"' && *cur != '\'' && *cur != '>') {
1162 if (*cur == '"') {
1163 cur++;
1164 while (*cur && *cur != '"')
1165 cur++;
1166 } else if (*cur == '\'') {
1167 cur++;
1168 while (*cur && *cur != '\'')
1169 cur++;
1170 } else {
1171 cur++;
1175 } else {
1176 cur++;
1181 /* clean up any attribute name from a premature termination */
1182 g_free(name);
1184 if (found) {
1185 *attributes = attribs;
1186 } else {
1187 *start = NULL;
1188 *end = NULL;
1189 *attributes = NULL;
1192 return found;
1195 gboolean
1196 purple_markup_extract_info_field(const char *str, int len, PurpleNotifyUserInfo *user_info,
1197 const char *start_token, int skip,
1198 const char *end_token, char check_value,
1199 const char *no_value_token,
1200 const char *display_name, gboolean is_link,
1201 const char *link_prefix,
1202 PurpleInfoFieldFormatCallback format_cb)
1204 const char *p, *q;
1206 g_return_val_if_fail(str != NULL, FALSE);
1207 g_return_val_if_fail(user_info != NULL, FALSE);
1208 g_return_val_if_fail(start_token != NULL, FALSE);
1209 g_return_val_if_fail(end_token != NULL, FALSE);
1210 g_return_val_if_fail(display_name != NULL, FALSE);
1212 p = strstr(str, start_token);
1214 if (p == NULL)
1215 return FALSE;
1217 p += strlen(start_token) + skip;
1219 if (p >= str + len)
1220 return FALSE;
1222 if (check_value != '\0' && *p == check_value)
1223 return FALSE;
1225 q = strstr(p, end_token);
1227 /* Trim leading blanks */
1228 while (*p != '\n' && g_ascii_isspace(*p)) {
1229 p += 1;
1232 /* Trim trailing blanks */
1233 while (q > p && g_ascii_isspace(*(q - 1))) {
1234 q -= 1;
1237 /* Don't bother with null strings */
1238 if (p == q)
1239 return FALSE;
1241 if (q != NULL && (!no_value_token ||
1242 (no_value_token && strncmp(p, no_value_token,
1243 strlen(no_value_token)))))
1245 GString *dest = g_string_new("");
1247 if (is_link)
1249 g_string_append(dest, "<a href=\"");
1251 if (link_prefix)
1252 g_string_append(dest, link_prefix);
1254 if (format_cb != NULL)
1256 char *reformatted = format_cb(p, q - p);
1257 g_string_append(dest, reformatted);
1258 g_free(reformatted);
1260 else
1261 g_string_append_len(dest, p, q - p);
1262 g_string_append(dest, "\">");
1264 if (link_prefix)
1265 g_string_append(dest, link_prefix);
1267 g_string_append_len(dest, p, q - p);
1268 g_string_append(dest, "</a>");
1270 else
1272 if (format_cb != NULL)
1274 char *reformatted = format_cb(p, q - p);
1275 g_string_append(dest, reformatted);
1276 g_free(reformatted);
1278 else
1279 g_string_append_len(dest, p, q - p);
1282 purple_notify_user_info_add_pair(user_info, display_name, dest->str);
1283 g_string_free(dest, TRUE);
1285 return TRUE;
1288 return FALSE;
1291 struct purple_parse_tag {
1292 char *src_tag;
1293 char *dest_tag;
1294 gboolean ignore;
1297 #define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \
1298 const char *o = c + strlen("<" x); \
1299 const char *p = NULL, *q = NULL, *r = NULL; \
1300 GString *innards = g_string_new(""); \
1301 while(o && *o) { \
1302 if(!q && (*o == '\"' || *o == '\'') ) { \
1303 q = o; \
1304 } else if(q) { \
1305 if(*o == *q) { \
1306 char *unescaped = g_strndup(q+1, o-q-1); \
1307 char *escaped = g_markup_escape_text(unescaped, -1); \
1308 g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \
1309 g_free(unescaped); \
1310 g_free(escaped); \
1311 q = NULL; \
1312 } else if(*c == '\\') { \
1313 o++; \
1315 } else if(*o == '<') { \
1316 r = o; \
1317 } else if(*o == '>') { \
1318 p = o; \
1319 break; \
1320 } else { \
1321 innards = g_string_append_c(innards, *o); \
1323 o++; \
1325 if(p && !r) { \
1326 if(*(p-1) != '/') { \
1327 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1328 pt->src_tag = x; \
1329 pt->dest_tag = y; \
1330 tags = g_list_prepend(tags, pt); \
1332 if(xhtml) { \
1333 xhtml = g_string_append(xhtml, "<" y); \
1334 xhtml = g_string_append(xhtml, innards->str); \
1335 xhtml = g_string_append_c(xhtml, '>'); \
1337 c = p + 1; \
1338 } else { \
1339 if(xhtml) \
1340 xhtml = g_string_append(xhtml, "&lt;"); \
1341 if(plain) \
1342 plain = g_string_append_c(plain, '<'); \
1343 c++; \
1345 g_string_free(innards, TRUE); \
1346 continue; \
1348 if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \
1349 (*(c+strlen("<" x)) == '>' || \
1350 !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \
1351 if(xhtml) \
1352 xhtml = g_string_append(xhtml, "<" y); \
1353 c += strlen("<" x); \
1354 if(*c != '/') { \
1355 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1356 pt->src_tag = x; \
1357 pt->dest_tag = y; \
1358 tags = g_list_prepend(tags, pt); \
1359 if(xhtml) \
1360 xhtml = g_string_append_c(xhtml, '>'); \
1361 } else { \
1362 if(xhtml) \
1363 xhtml = g_string_append(xhtml, "/>");\
1365 c = strchr(c, '>') + 1; \
1366 continue; \
1368 #define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x)
1369 void
1370 purple_markup_html_to_xhtml(const char *html, char **xhtml_out,
1371 char **plain_out)
1373 GString *xhtml = NULL;
1374 GString *plain = NULL;
1375 GString *url = NULL;
1376 GString *cdata = NULL;
1377 GList *tags = NULL, *tag;
1378 const char *c = html;
1379 char quote = '\0';
1381 #define CHECK_QUOTE(ptr) if (*(ptr) == '\'' || *(ptr) == '\"') \
1382 quote = *(ptr++); \
1383 else \
1384 quote = '\0';
1386 #define VALID_CHAR(ptr) (*(ptr) && *(ptr) != quote && (quote || (*(ptr) != ' ' && *(ptr) != '>')))
1388 g_return_if_fail(xhtml_out != NULL || plain_out != NULL);
1390 if(xhtml_out)
1391 xhtml = g_string_new("");
1392 if(plain_out)
1393 plain = g_string_new("");
1395 while(c && *c) {
1396 if(*c == '<') {
1397 if(*(c+1) == '/') { /* closing tag */
1398 tag = tags;
1399 while(tag) {
1400 struct purple_parse_tag *pt = tag->data;
1401 if(!g_ascii_strncasecmp((c+2), pt->src_tag, strlen(pt->src_tag)) && *(c+strlen(pt->src_tag)+2) == '>') {
1402 c += strlen(pt->src_tag) + 3;
1403 break;
1405 tag = tag->next;
1407 if(tag) {
1408 while(tags) {
1409 struct purple_parse_tag *pt = tags->data;
1410 if(xhtml)
1411 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
1412 if(plain && !strcmp(pt->src_tag, "a")) {
1413 /* if this is a link, we have to add the url to the plaintext, too */
1414 if (cdata && url &&
1415 (!g_string_equal(cdata, url) && (g_ascii_strncasecmp(url->str, "mailto:", 7) != 0 ||
1416 g_utf8_collate(url->str + 7, cdata->str) != 0)))
1417 g_string_append_printf(plain, " <%s>", g_strstrip(url->str));
1418 if (cdata) {
1419 g_string_free(cdata, TRUE);
1420 cdata = NULL;
1424 if(tags == tag)
1425 break;
1426 tags = g_list_remove(tags, pt);
1427 g_free(pt);
1429 g_free(tag->data);
1430 tags = g_list_remove(tags, tag->data);
1431 } else {
1432 /* a closing tag we weren't expecting...
1433 * we'll let it slide, if it's really a tag...if it's
1434 * just a </ we'll escape it properly */
1435 const char *end = c+2;
1436 while(*end && g_ascii_isalpha(*end))
1437 end++;
1438 if(*end == '>') {
1439 c = end+1;
1440 } else {
1441 if(xhtml)
1442 xhtml = g_string_append(xhtml, "&lt;");
1443 if(plain)
1444 plain = g_string_append_c(plain, '<');
1445 c++;
1448 } else { /* opening tag */
1449 ALLOW_TAG("blockquote");
1450 ALLOW_TAG("cite");
1451 ALLOW_TAG("div");
1452 ALLOW_TAG("em");
1453 ALLOW_TAG("h1");
1454 ALLOW_TAG("h2");
1455 ALLOW_TAG("h3");
1456 ALLOW_TAG("h4");
1457 ALLOW_TAG("h5");
1458 ALLOW_TAG("h6");
1459 /* we only allow html to start the message */
1460 if(c == html)
1461 ALLOW_TAG("html");
1462 ALLOW_TAG_ALT("i", "em");
1463 ALLOW_TAG_ALT("italic", "em");
1464 ALLOW_TAG("li");
1465 ALLOW_TAG("ol");
1466 ALLOW_TAG("p");
1467 ALLOW_TAG("pre");
1468 ALLOW_TAG("q");
1469 ALLOW_TAG("span");
1470 ALLOW_TAG("ul");
1473 /* we skip <HR> because it's not legal in XHTML-IM. However,
1474 * we still want to send something sensible, so we put a
1475 * linebreak in its place. <BR> also needs special handling
1476 * because putting a </BR> to close it would just be dumb. */
1477 if((!g_ascii_strncasecmp(c, "<br", 3)
1478 || !g_ascii_strncasecmp(c, "<hr", 3))
1479 && (*(c+3) == '>' ||
1480 !g_ascii_strncasecmp(c+3, "/>", 2) ||
1481 !g_ascii_strncasecmp(c+3, " />", 3))) {
1482 c = strchr(c, '>') + 1;
1483 if(xhtml)
1484 xhtml = g_string_append(xhtml, "<br/>");
1485 if(plain && *c != '\n')
1486 plain = g_string_append_c(plain, '\n');
1487 continue;
1489 if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c, "<strong>", strlen("<strong>"))) {
1490 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1491 if (*(c+2) == '>')
1492 pt->src_tag = "b";
1493 else if (*(c+2) == 'o')
1494 pt->src_tag = "bold";
1495 else
1496 pt->src_tag = "strong";
1497 pt->dest_tag = "span";
1498 tags = g_list_prepend(tags, pt);
1499 c = strchr(c, '>') + 1;
1500 if(xhtml)
1501 xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>");
1502 continue;
1504 if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) {
1505 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1506 pt->src_tag = *(c+2) == '>' ? "u" : "underline";
1507 pt->dest_tag = "span";
1508 tags = g_list_prepend(tags, pt);
1509 c = strchr(c, '>') + 1;
1510 if (xhtml)
1511 xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>");
1512 continue;
1514 if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) {
1515 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1516 pt->src_tag = *(c+2) == '>' ? "s" : "strike";
1517 pt->dest_tag = "span";
1518 tags = g_list_prepend(tags, pt);
1519 c = strchr(c, '>') + 1;
1520 if(xhtml)
1521 xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>");
1522 continue;
1524 if(!g_ascii_strncasecmp(c, "<sub>", 5)) {
1525 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1526 pt->src_tag = "sub";
1527 pt->dest_tag = "span";
1528 tags = g_list_prepend(tags, pt);
1529 c = strchr(c, '>') + 1;
1530 if(xhtml)
1531 xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>");
1532 continue;
1534 if(!g_ascii_strncasecmp(c, "<sup>", 5)) {
1535 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1536 pt->src_tag = "sup";
1537 pt->dest_tag = "span";
1538 tags = g_list_prepend(tags, pt);
1539 c = strchr(c, '>') + 1;
1540 if(xhtml)
1541 xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>");
1542 continue;
1544 if (!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) {
1545 const char *p = c + 4;
1546 GString *src = NULL, *alt = NULL;
1547 while (*p && *p != '>') {
1548 if (!g_ascii_strncasecmp(p, "src=", 4)) {
1549 const char *q = p + 4;
1550 if (src)
1551 g_string_free(src, TRUE);
1552 src = g_string_new("");
1553 CHECK_QUOTE(q);
1554 while (VALID_CHAR(q)) {
1555 src = g_string_append_c(src, *q);
1556 q++;
1558 p = q;
1559 } else if (!g_ascii_strncasecmp(p, "alt=", 4)) {
1560 const char *q = p + 4;
1561 if (alt)
1562 g_string_free(alt, TRUE);
1563 alt = g_string_new("");
1564 CHECK_QUOTE(q);
1565 while (VALID_CHAR(q)) {
1566 alt = g_string_append_c(alt, *q);
1567 q++;
1569 p = q;
1570 } else {
1571 p++;
1574 if ((c = strchr(p, '>')) != NULL)
1575 c++;
1576 else
1577 c = p;
1578 /* src and alt are required! */
1579 if(src && xhtml)
1580 g_string_append_printf(xhtml, "<img src='%s' alt='%s' />", g_strstrip(src->str), alt ? alt->str : "");
1581 if(alt) {
1582 if(plain)
1583 plain = g_string_append(plain, alt->str);
1584 if(!src && xhtml)
1585 xhtml = g_string_append(xhtml, alt->str);
1586 g_string_free(alt, TRUE);
1588 g_string_free(src, TRUE);
1589 continue;
1591 if (!g_ascii_strncasecmp(c, "<a", 2) && (*(c+2) == '>' || *(c+2) == ' ')) {
1592 const char *p = c + 2;
1593 struct purple_parse_tag *pt;
1594 while (*p && *p != '>') {
1595 if (!g_ascii_strncasecmp(p, "href=", 5)) {
1596 const char *q = p + 5;
1597 if (url)
1598 g_string_free(url, TRUE);
1599 url = g_string_new("");
1600 if (cdata)
1601 g_string_free(cdata, TRUE);
1602 cdata = g_string_new("");
1603 CHECK_QUOTE(q);
1604 while (VALID_CHAR(q)) {
1605 int len;
1606 if ((*q == '&') && (purple_markup_unescape_entity(q, &len) == NULL))
1607 url = g_string_append(url, "&amp;");
1608 else
1609 url = g_string_append_c(url, *q);
1610 q++;
1612 p = q;
1613 } else {
1614 p++;
1617 if ((c = strchr(p, '>')) != NULL)
1618 c++;
1619 else
1620 c = p;
1621 pt = g_new0(struct purple_parse_tag, 1);
1622 pt->src_tag = "a";
1623 pt->dest_tag = "a";
1624 tags = g_list_prepend(tags, pt);
1625 if(xhtml)
1626 g_string_append_printf(xhtml, "<a href='%s'>", url ? g_strstrip(url->str) : "");
1627 continue;
1629 if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) {
1630 const char *p = c + 5;
1631 GString *style = g_string_new("");
1632 struct purple_parse_tag *pt;
1633 while (*p && *p != '>') {
1634 if (!g_ascii_strncasecmp(p, "back=", 5)) {
1635 const char *q = p + 5;
1636 GString *color = g_string_new("");
1637 CHECK_QUOTE(q);
1638 while (VALID_CHAR(q)) {
1639 color = g_string_append_c(color, *q);
1640 q++;
1642 g_string_append_printf(style, "background: %s; ", color->str);
1643 g_string_free(color, TRUE);
1644 p = q;
1645 } else if (!g_ascii_strncasecmp(p, "color=", 6)) {
1646 const char *q = p + 6;
1647 GString *color = g_string_new("");
1648 CHECK_QUOTE(q);
1649 while (VALID_CHAR(q)) {
1650 color = g_string_append_c(color, *q);
1651 q++;
1653 g_string_append_printf(style, "color: %s; ", color->str);
1654 g_string_free(color, TRUE);
1655 p = q;
1656 } else if (!g_ascii_strncasecmp(p, "face=", 5)) {
1657 const char *q = p + 5;
1658 GString *face = g_string_new("");
1659 CHECK_QUOTE(q);
1660 while (VALID_CHAR(q)) {
1661 face = g_string_append_c(face, *q);
1662 q++;
1664 g_string_append_printf(style, "font-family: %s; ", g_strstrip(face->str));
1665 g_string_free(face, TRUE);
1666 p = q;
1667 } else if (!g_ascii_strncasecmp(p, "size=", 5)) {
1668 const char *q = p + 5;
1669 int sz;
1670 const char *size = "medium";
1671 CHECK_QUOTE(q);
1672 sz = atoi(q);
1673 switch (sz)
1675 case 1:
1676 size = "xx-small";
1677 break;
1678 case 2:
1679 size = "small";
1680 break;
1681 case 3:
1682 size = "medium";
1683 break;
1684 case 4:
1685 size = "large";
1686 break;
1687 case 5:
1688 size = "x-large";
1689 break;
1690 case 6:
1691 case 7:
1692 size = "xx-large";
1693 break;
1694 default:
1695 break;
1697 g_string_append_printf(style, "font-size: %s; ", size);
1698 p = q;
1699 } else {
1700 p++;
1703 if ((c = strchr(p, '>')) != NULL)
1704 c++;
1705 else
1706 c = p;
1707 pt = g_new0(struct purple_parse_tag, 1);
1708 pt->src_tag = "font";
1709 pt->dest_tag = "span";
1710 tags = g_list_prepend(tags, pt);
1711 if(style->len && xhtml)
1712 g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str));
1713 else
1714 pt->ignore = TRUE;
1715 g_string_free(style, TRUE);
1716 continue;
1718 if (!g_ascii_strncasecmp(c, "<body ", 6)) {
1719 const char *p = c + 6;
1720 gboolean did_something = FALSE;
1721 while (*p && *p != '>') {
1722 if (!g_ascii_strncasecmp(p, "bgcolor=", 8)) {
1723 const char *q = p + 8;
1724 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
1725 GString *color = g_string_new("");
1726 CHECK_QUOTE(q);
1727 while (VALID_CHAR(q)) {
1728 color = g_string_append_c(color, *q);
1729 q++;
1731 if (xhtml)
1732 g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str));
1733 g_string_free(color, TRUE);
1734 if ((c = strchr(p, '>')) != NULL)
1735 c++;
1736 else
1737 c = p;
1738 pt->src_tag = "body";
1739 pt->dest_tag = "span";
1740 tags = g_list_prepend(tags, pt);
1741 did_something = TRUE;
1742 break;
1744 p++;
1746 if (did_something) continue;
1748 /* this has to come after the special case for bgcolor */
1749 ALLOW_TAG("body");
1750 if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) {
1751 char *p = strstr(c + strlen("<!--"), "-->");
1752 if(p) {
1753 if(xhtml)
1754 xhtml = g_string_append(xhtml, "<!--");
1755 c += strlen("<!--");
1756 continue;
1760 if(xhtml)
1761 xhtml = g_string_append(xhtml, "&lt;");
1762 if(plain)
1763 plain = g_string_append_c(plain, '<');
1764 c++;
1766 } else if(*c == '&') {
1767 char buf[7];
1768 const char *pln;
1769 int len;
1771 if ((pln = purple_markup_unescape_entity(c, &len)) == NULL) {
1772 len = 1;
1773 g_snprintf(buf, sizeof(buf), "%c", *c);
1774 pln = buf;
1776 if(xhtml)
1777 xhtml = g_string_append_len(xhtml, c, len);
1778 if(plain)
1779 plain = g_string_append(plain, pln);
1780 if(cdata)
1781 cdata = g_string_append_len(cdata, c, len);
1782 c += len;
1783 } else {
1784 if(xhtml)
1785 xhtml = g_string_append_c(xhtml, *c);
1786 if(plain)
1787 plain = g_string_append_c(plain, *c);
1788 if(cdata)
1789 cdata = g_string_append_c(cdata, *c);
1790 c++;
1793 if(xhtml) {
1794 for (tag = tags; tag ; tag = tag->next) {
1795 struct purple_parse_tag *pt = tag->data;
1796 if(!pt->ignore)
1797 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
1800 g_list_free(tags);
1801 if(xhtml_out)
1802 *xhtml_out = g_string_free(xhtml, FALSE);
1803 if(plain_out)
1804 *plain_out = g_string_free(plain, FALSE);
1805 if(url)
1806 g_string_free(url, TRUE);
1807 if (cdata)
1808 g_string_free(cdata, TRUE);
1809 #undef CHECK_QUOTE
1810 #undef VALID_CHAR
1813 /* The following are probably reasonable changes:
1814 * - \n should be converted to a normal space
1815 * - in addition to <br>, <p> and <div> etc. should also be converted into \n
1816 * - We want to turn </td>#whitespace<td> sequences into a single tab
1817 * - We want to turn <td> into a single tab (for msn profile "parsing")
1818 * - We want to turn </tr>#whitespace<tr> sequences into a single \n
1819 * - <script>...</script> and <style>...</style> should be completely removed
1822 char *
1823 purple_markup_strip_html(const char *str)
1825 int i, j, k, entlen;
1826 gboolean visible = TRUE;
1827 gboolean closing_td_p = FALSE;
1828 gchar *str2;
1829 const gchar *cdata_close_tag = NULL, *ent;
1830 gchar *href = NULL;
1831 int href_st = 0;
1833 if(!str)
1834 return NULL;
1836 str2 = g_strdup(str);
1838 for (i = 0, j = 0; str2[i]; i++)
1840 if (str2[i] == '<')
1842 if (cdata_close_tag)
1844 /* Note: Don't even assume any other tag is a tag in CDATA */
1845 if (g_ascii_strncasecmp(str2 + i, cdata_close_tag,
1846 strlen(cdata_close_tag)) == 0)
1848 i += strlen(cdata_close_tag) - 1;
1849 cdata_close_tag = NULL;
1851 continue;
1853 else if (g_ascii_strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p)
1855 str2[j++] = '\t';
1856 visible = TRUE;
1858 else if (g_ascii_strncasecmp(str2 + i, "</td>", 5) == 0)
1860 closing_td_p = TRUE;
1861 visible = FALSE;
1863 else
1865 closing_td_p = FALSE;
1866 visible = TRUE;
1869 k = i + 1;
1871 if(g_ascii_isspace(str2[k]))
1872 visible = TRUE;
1873 else if (str2[k])
1875 /* Scan until we end the tag either implicitly (closed start
1876 * tag) or explicitly, using a sloppy method (i.e., < or >
1877 * inside quoted attributes will screw us up)
1879 while (str2[k] && str2[k] != '<' && str2[k] != '>')
1881 k++;
1884 /* If we've got an <a> tag with an href, save the address
1885 * to print later. */
1886 if (g_ascii_strncasecmp(str2 + i, "<a", 2) == 0 &&
1887 g_ascii_isspace(str2[i+2]))
1889 int st; /* start of href, inclusive [ */
1890 int end; /* end of href, exclusive ) */
1891 char delim = ' ';
1892 /* Find start of href */
1893 for (st = i + 3; st < k; st++)
1895 if (g_ascii_strncasecmp(str2+st, "href=", 5) == 0)
1897 st += 5;
1898 if (str2[st] == '"' || str2[st] == '\'')
1900 delim = str2[st];
1901 st++;
1903 break;
1906 /* find end of address */
1907 for (end = st; end < k && str2[end] != delim; end++)
1909 /* All the work is done in the loop construct above. */
1912 /* If there's an address, save it. If there was
1913 * already one saved, kill it. */
1914 if (st < k)
1916 char *tmp;
1917 g_free(href);
1918 tmp = g_strndup(str2 + st, end - st);
1919 href = purple_unescape_html(tmp);
1920 g_free(tmp);
1921 href_st = j;
1925 /* Replace </a> with an ascii representation of the
1926 * address the link was pointing to. */
1927 else if (href != NULL && g_ascii_strncasecmp(str2 + i, "</a>", 4) == 0)
1930 size_t hrlen = strlen(href);
1932 /* Only insert the href if it's different from the CDATA. */
1933 if ((hrlen != j - href_st ||
1934 strncmp(str2 + href_st, href, hrlen)) &&
1935 (hrlen != j - href_st + 7 || /* 7 == strlen("http://") */
1936 strncmp(str2 + href_st, href + 7, hrlen - 7)))
1938 str2[j++] = ' ';
1939 str2[j++] = '(';
1940 g_memmove(str2 + j, href, hrlen);
1941 j += hrlen;
1942 str2[j++] = ')';
1943 g_free(href);
1944 href = NULL;
1948 /* Check for tags which should be mapped to newline */
1949 else if (g_ascii_strncasecmp(str2 + i, "<p>", 3) == 0
1950 || g_ascii_strncasecmp(str2 + i, "<tr", 3) == 0
1951 || g_ascii_strncasecmp(str2 + i, "<br", 3) == 0
1952 || g_ascii_strncasecmp(str2 + i, "<hr", 3) == 0
1953 || g_ascii_strncasecmp(str2 + i, "<li", 3) == 0
1954 || g_ascii_strncasecmp(str2 + i, "<div", 4) == 0
1955 || g_ascii_strncasecmp(str2 + i, "</table>", 8) == 0)
1957 str2[j++] = '\n';
1959 /* Check for tags which begin CDATA and need to be closed */
1960 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */
1961 else if (g_ascii_strncasecmp(str2 + i, "<option", 7) == 0)
1963 /* FIXME: We should not do this if the OPTION is SELECT'd */
1964 cdata_close_tag = "</option>";
1966 #endif
1967 else if (g_ascii_strncasecmp(str2 + i, "<script", 7) == 0)
1969 cdata_close_tag = "</script>";
1971 else if (g_ascii_strncasecmp(str2 + i, "<style", 6) == 0)
1973 cdata_close_tag = "</style>";
1975 /* Update the index and continue checking after the tag */
1976 i = (str2[k] == '<' || str2[k] == '\0')? k - 1: k;
1977 continue;
1980 else if (cdata_close_tag)
1982 continue;
1984 else if (!g_ascii_isspace(str2[i]))
1986 visible = TRUE;
1989 if (str2[i] == '&' && (ent = purple_markup_unescape_entity(str2 + i, &entlen)) != NULL)
1991 while (*ent)
1992 str2[j++] = *ent++;
1993 i += entlen - 1;
1994 continue;
1997 if (visible)
1998 str2[j++] = g_ascii_isspace(str2[i])? ' ': str2[i];
2001 g_free(href);
2003 str2[j] = '\0';
2005 return str2;
2008 static gboolean
2009 badchar(char c)
2011 switch (c) {
2012 case ' ':
2013 case ',':
2014 case '\0':
2015 case '\n':
2016 case '\r':
2017 case '<':
2018 case '>':
2019 case '"':
2020 case '\'':
2021 return TRUE;
2022 default:
2023 return FALSE;
2027 static gboolean
2028 badentity(const char *c)
2030 if (!g_ascii_strncasecmp(c, "&lt;", 4) ||
2031 !g_ascii_strncasecmp(c, "&gt;", 4) ||
2032 !g_ascii_strncasecmp(c, "&quot;", 6)) {
2033 return TRUE;
2035 return FALSE;
2038 char *
2039 purple_markup_linkify(const char *text)
2041 const char *c, *t, *q = NULL;
2042 char *tmpurlbuf, *url_buf;
2043 gunichar g;
2044 gboolean inside_html = FALSE;
2045 int inside_paren = 0;
2046 GString *ret;
2048 if (text == NULL)
2049 return NULL;
2051 ret = g_string_new("");
2053 c = text;
2054 while (*c) {
2056 if(*c == '(' && !inside_html) {
2057 inside_paren++;
2058 ret = g_string_append_c(ret, *c);
2059 c++;
2062 if(inside_html) {
2063 if(*c == '>') {
2064 inside_html = FALSE;
2065 } else if(!q && (*c == '\"' || *c == '\'')) {
2066 q = c;
2067 } else if(q) {
2068 if(*c == *q)
2069 q = NULL;
2071 } else if(*c == '<') {
2072 inside_html = TRUE;
2073 if (!g_ascii_strncasecmp(c, "<A", 2)) {
2074 while (1) {
2075 if (!g_ascii_strncasecmp(c, "/A>", 3)) {
2076 inside_html = FALSE;
2077 break;
2079 ret = g_string_append_c(ret, *c);
2080 c++;
2081 if (!(*c))
2082 break;
2085 } else if ((*c=='h') && (!g_ascii_strncasecmp(c, "http://", 7) ||
2086 (!g_ascii_strncasecmp(c, "https://", 8)))) {
2087 t = c;
2088 while (1) {
2089 if (badchar(*t) || badentity(t)) {
2091 if ((!g_ascii_strncasecmp(c, "http://", 7) && (t - c == 7)) ||
2092 (!g_ascii_strncasecmp(c, "https://", 8) && (t - c == 8))) {
2093 break;
2096 if (*(t) == ',' && (*(t + 1) != ' ')) {
2097 t++;
2098 continue;
2101 if (*(t - 1) == '.')
2102 t--;
2103 if ((*(t - 1) == ')' && (inside_paren > 0))) {
2104 t--;
2107 url_buf = g_strndup(c, t - c);
2108 tmpurlbuf = purple_unescape_html(url_buf);
2109 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2110 tmpurlbuf, url_buf);
2111 g_free(url_buf);
2112 g_free(tmpurlbuf);
2113 c = t;
2114 break;
2116 t++;
2119 } else if (!g_ascii_strncasecmp(c, "www.", 4) && (c == text || badchar(c[-1]) || badentity(c-1))) {
2120 if (c[4] != '.') {
2121 t = c;
2122 while (1) {
2123 if (badchar(*t) || badentity(t)) {
2124 if (t - c == 4) {
2125 break;
2128 if (*(t) == ',' && (*(t + 1) != ' ')) {
2129 t++;
2130 continue;
2133 if (*(t - 1) == '.')
2134 t--;
2135 if ((*(t - 1) == ')' && (inside_paren > 0))) {
2136 t--;
2138 url_buf = g_strndup(c, t - c);
2139 tmpurlbuf = purple_unescape_html(url_buf);
2140 g_string_append_printf(ret,
2141 "<A HREF=\"http://%s\">%s</A>", tmpurlbuf,
2142 url_buf);
2143 g_free(url_buf);
2144 g_free(tmpurlbuf);
2145 c = t;
2146 break;
2148 t++;
2151 } else if (!g_ascii_strncasecmp(c, "ftp://", 6) || !g_ascii_strncasecmp(c, "sftp://", 7)) {
2152 t = c;
2153 while (1) {
2154 if (badchar(*t) || badentity(t)) {
2156 if ((!g_ascii_strncasecmp(c, "ftp://", 6) && (t - c == 6)) ||
2157 (!g_ascii_strncasecmp(c, "sftp://", 7) && (t - c == 7))) {
2158 break;
2161 if (*(t - 1) == '.')
2162 t--;
2163 if ((*(t - 1) == ')' && (inside_paren > 0))) {
2164 t--;
2166 url_buf = g_strndup(c, t - c);
2167 tmpurlbuf = purple_unescape_html(url_buf);
2168 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2169 tmpurlbuf, url_buf);
2170 g_free(url_buf);
2171 g_free(tmpurlbuf);
2172 c = t;
2173 break;
2175 if (!t)
2176 break;
2177 t++;
2180 } else if (!g_ascii_strncasecmp(c, "ftp.", 4) && (c == text || badchar(c[-1]) || badentity(c-1))) {
2181 if (c[4] != '.') {
2182 t = c;
2183 while (1) {
2184 if (badchar(*t) || badentity(t)) {
2185 if (t - c == 4) {
2186 break;
2188 if (*(t - 1) == '.')
2189 t--;
2190 if ((*(t - 1) == ')' && (inside_paren > 0))) {
2191 t--;
2193 url_buf = g_strndup(c, t - c);
2194 tmpurlbuf = purple_unescape_html(url_buf);
2195 g_string_append_printf(ret,
2196 "<A HREF=\"ftp://%s\">%s</A>", tmpurlbuf,
2197 url_buf);
2198 g_free(url_buf);
2199 g_free(tmpurlbuf);
2200 c = t;
2201 break;
2203 if (!t)
2204 break;
2205 t++;
2208 } else if (!g_ascii_strncasecmp(c, "mailto:", 7)) {
2209 t = c;
2210 while (1) {
2211 if (badchar(*t) || badentity(t)) {
2212 char *d;
2213 if (t - c == 7) {
2214 break;
2216 if (*(t - 1) == '.')
2217 t--;
2218 if ((d = strstr(c + 7, "?")) != NULL && d < t)
2219 url_buf = g_strndup(c + 7, d - c - 7);
2220 else
2221 url_buf = g_strndup(c + 7, t - c - 7);
2222 if (!purple_email_is_valid(url_buf)) {
2223 g_free(url_buf);
2224 break;
2226 g_free(url_buf);
2227 url_buf = g_strndup(c, t - c);
2228 tmpurlbuf = purple_unescape_html(url_buf);
2229 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2230 tmpurlbuf, url_buf);
2231 g_free(url_buf);
2232 g_free(tmpurlbuf);
2233 c = t;
2234 break;
2236 if (!t)
2237 break;
2238 t++;
2241 } else if ((*c=='x') && (!g_ascii_strncasecmp(c, "xmpp:", 5)) &&
2242 (c == text || badchar(c[-1]) || badentity(c-1))) {
2243 t = c;
2244 while (1) {
2245 if (badchar(*t) || badentity(t)) {
2247 if (t - c == 5) {
2248 break;
2251 if (*(t) == ',' && (*(t + 1) != ' ')) {
2252 t++;
2253 continue;
2256 if (*(t - 1) == '.')
2257 t--;
2258 if ((*(t - 1) == ')' && (inside_paren > 0))) {
2259 t--;
2262 url_buf = g_strndup(c, t - c);
2263 tmpurlbuf = purple_unescape_html(url_buf);
2264 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2265 tmpurlbuf, url_buf);
2266 g_free(url_buf);
2267 g_free(tmpurlbuf);
2268 c = t;
2269 break;
2271 t++;
2274 } else if (c != text && (*c == '@')) {
2275 int flag;
2276 GString *gurl_buf = NULL;
2277 const char illegal_chars[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0";
2279 if (strchr(illegal_chars,*(c - 1)) || strchr(illegal_chars, *(c + 1)))
2280 flag = 0;
2281 else {
2282 flag = 1;
2283 gurl_buf = g_string_new("");
2286 t = c;
2287 while (flag) {
2288 /* iterate backwards grabbing the local part of an email address */
2289 g = g_utf8_get_char(t);
2290 if (badchar(*t) || (g >= 127) || (*t == '(') ||
2291 ((*t == ';') && ((t > (text+2) && (!g_ascii_strncasecmp(t - 3, "&lt;", 4) ||
2292 !g_ascii_strncasecmp(t - 3, "&gt;", 4))) ||
2293 (t > (text+4) && (!g_ascii_strncasecmp(t - 5, "&quot;", 6)))))) {
2294 /* local part will already be part of ret, strip it out */
2295 ret = g_string_truncate(ret, ret->len - (c - t));
2296 ret = g_string_append_unichar(ret, g);
2297 break;
2298 } else {
2299 g_string_prepend_unichar(gurl_buf, g);
2300 t = g_utf8_find_prev_char(text, t);
2301 if (t < text) {
2302 ret = g_string_assign(ret, "");
2303 break;
2308 t = g_utf8_find_next_char(c, NULL);
2310 while (flag) {
2311 /* iterate forwards grabbing the domain part of an email address */
2312 g = g_utf8_get_char(t);
2313 if (badchar(*t) || (g >= 127) || (*t == ')') || badentity(t)) {
2314 char *d;
2316 url_buf = g_string_free(gurl_buf, FALSE);
2318 /* strip off trailing periods */
2319 if (strlen(url_buf) > 0) {
2320 for (d = url_buf + strlen(url_buf) - 1; *d == '.'; d--, t--)
2321 *d = '\0';
2324 tmpurlbuf = purple_unescape_html(url_buf);
2325 if (purple_email_is_valid(tmpurlbuf)) {
2326 g_string_append_printf(ret, "<A HREF=\"mailto:%s\">%s</A>",
2327 tmpurlbuf, url_buf);
2328 } else {
2329 g_string_append(ret, url_buf);
2331 g_free(url_buf);
2332 g_free(tmpurlbuf);
2333 c = t;
2335 break;
2336 } else {
2337 g_string_append_unichar(gurl_buf, g);
2338 t = g_utf8_find_next_char(t, NULL);
2343 if(*c == ')' && !inside_html) {
2344 inside_paren--;
2345 ret = g_string_append_c(ret, *c);
2346 c++;
2349 if (*c == 0)
2350 break;
2352 ret = g_string_append_c(ret, *c);
2353 c++;
2356 return g_string_free(ret, FALSE);
2359 char *
2360 purple_unescape_html(const char *html) {
2361 if (html != NULL) {
2362 const char *c = html;
2363 GString *ret = g_string_new("");
2364 while (*c) {
2365 int len;
2366 const char *ent;
2368 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2369 ret = g_string_append(ret, ent);
2370 c += len;
2371 } else if (!strncmp(c, "<br>", 4)) {
2372 ret = g_string_append_c(ret, '\n');
2373 c += 4;
2374 } else {
2375 ret = g_string_append_c(ret, *c);
2376 c++;
2379 return g_string_free(ret, FALSE);
2382 return NULL;
2385 char *
2386 purple_markup_slice(const char *str, guint x, guint y)
2388 GString *ret;
2389 GQueue *q;
2390 guint z = 0;
2391 gboolean appended = FALSE;
2392 gunichar c;
2393 char *tag;
2395 g_return_val_if_fail(str != NULL, NULL);
2396 g_return_val_if_fail(x <= y, NULL);
2398 if (x == y)
2399 return g_strdup("");
2401 ret = g_string_new("");
2402 q = g_queue_new();
2404 while (*str && (z < y)) {
2405 c = g_utf8_get_char(str);
2407 if (c == '<') {
2408 char *end = strchr(str, '>');
2410 if (!end) {
2411 g_string_free(ret, TRUE);
2412 while ((tag = g_queue_pop_head(q)))
2413 g_free(tag);
2414 g_queue_free(q);
2415 return NULL;
2418 if (!g_ascii_strncasecmp(str, "<img ", 5)) {
2419 z += strlen("[Image]");
2420 } else if (!g_ascii_strncasecmp(str, "<br", 3)) {
2421 z += 1;
2422 } else if (!g_ascii_strncasecmp(str, "<hr>", 4)) {
2423 z += strlen("\n---\n");
2424 } else if (!g_ascii_strncasecmp(str, "</", 2)) {
2425 /* pop stack */
2426 char *tmp;
2428 tmp = g_queue_pop_head(q);
2429 g_free(tmp);
2430 /* z += 0; */
2431 } else {
2432 /* push it unto the stack */
2433 char *tmp;
2435 tmp = g_strndup(str, end - str + 1);
2436 g_queue_push_head(q, tmp);
2437 /* z += 0; */
2440 if (z >= x) {
2441 g_string_append_len(ret, str, end - str + 1);
2444 str = end;
2445 } else if (c == '&') {
2446 char *end = strchr(str, ';');
2447 if (!end) {
2448 g_string_free(ret, TRUE);
2449 while ((tag = g_queue_pop_head(q)))
2450 g_free(tag);
2451 g_queue_free(q);
2453 return NULL;
2456 if (z >= x)
2457 g_string_append_len(ret, str, end - str + 1);
2459 z++;
2460 str = end;
2461 } else {
2462 if (z == x && z > 0 && !appended) {
2463 GList *l = q->tail;
2465 while (l) {
2466 tag = l->data;
2467 g_string_append(ret, tag);
2468 l = l->prev;
2470 appended = TRUE;
2473 if (z >= x)
2474 g_string_append_unichar(ret, c);
2475 z++;
2478 str = g_utf8_next_char(str);
2481 while ((tag = g_queue_pop_head(q))) {
2482 char *name;
2484 name = purple_markup_get_tag_name(tag);
2485 g_string_append_printf(ret, "</%s>", name);
2486 g_free(name);
2487 g_free(tag);
2490 g_queue_free(q);
2491 return g_string_free(ret, FALSE);
2494 char *
2495 purple_markup_get_tag_name(const char *tag)
2497 int i;
2498 g_return_val_if_fail(tag != NULL, NULL);
2499 g_return_val_if_fail(*tag == '<', NULL);
2501 for (i = 1; tag[i]; i++)
2502 if (tag[i] == '>' || tag[i] == ' ' || tag[i] == '/')
2503 break;
2505 return g_strndup(tag+1, i-1);
2508 /**************************************************************************
2509 * Path/Filename Functions
2510 **************************************************************************/
2511 const char *
2512 purple_home_dir(void)
2514 #ifndef _WIN32
2515 return g_get_home_dir();
2516 #else
2517 return wpurple_data_dir();
2518 #endif
2521 /* Returns the argument passed to -c IFF it was present, or ~/.purple. */
2522 const char *
2523 purple_user_dir(void)
2525 if (custom_user_dir != NULL)
2526 return custom_user_dir;
2527 else if (!user_dir)
2528 user_dir = g_build_filename(purple_home_dir(), ".purple", NULL);
2530 return user_dir;
2533 void purple_util_set_user_dir(const char *dir)
2535 g_free(custom_user_dir);
2537 if (dir != NULL && *dir)
2538 custom_user_dir = g_strdup(dir);
2539 else
2540 custom_user_dir = NULL;
2543 int purple_build_dir (const char *path, int mode)
2545 #if GLIB_CHECK_VERSION(2,8,0)
2546 return g_mkdir_with_parents(path, mode);
2547 #else
2548 char *dir, **components, delim[] = { G_DIR_SEPARATOR, '\0' };
2549 int cur, len;
2551 g_return_val_if_fail(path != NULL, -1);
2553 dir = g_new0(char, strlen(path) + 1);
2554 components = g_strsplit(path, delim, -1);
2555 len = 0;
2556 for (cur = 0; components[cur] != NULL; cur++) {
2557 /* If you don't know what you're doing on both
2558 * win32 and *NIX, stay the hell away from this code */
2559 if(cur > 1)
2560 dir[len++] = G_DIR_SEPARATOR;
2561 strcpy(dir + len, components[cur]);
2562 len += strlen(components[cur]);
2563 if(cur == 0)
2564 dir[len++] = G_DIR_SEPARATOR;
2566 if(g_file_test(dir, G_FILE_TEST_IS_DIR)) {
2567 continue;
2568 #ifdef _WIN32
2569 /* allow us to create subdirs on UNC paths
2570 * (\\machinename\path\to\blah)
2571 * g_file_test() doesn't work on "\\machinename" */
2572 } else if (cur == 2 && dir[0] == '\\' && dir[1] == '\\'
2573 && components[cur + 1] != NULL) {
2574 continue;
2575 #endif
2576 } else if(g_file_test(dir, G_FILE_TEST_EXISTS)) {
2577 purple_debug_warning("build_dir", "bad path: %s\n", path);
2578 g_strfreev(components);
2579 g_free(dir);
2580 return -1;
2583 if (g_mkdir(dir, mode) < 0) {
2584 purple_debug_warning("build_dir", "mkdir: %s\n", g_strerror(errno));
2585 g_strfreev(components);
2586 g_free(dir);
2587 return -1;
2591 g_strfreev(components);
2592 g_free(dir);
2593 return 0;
2594 #endif
2598 * This function is long and beautiful, like my--um, yeah. Anyway,
2599 * it includes lots of error checking so as we don't overwrite
2600 * people's settings if there is a problem writing the new values.
2602 gboolean
2603 purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
2605 const char *user_dir = purple_user_dir();
2606 gchar *filename_full;
2607 gboolean ret = FALSE;
2609 g_return_val_if_fail(user_dir != NULL, FALSE);
2611 purple_debug_info("util", "Writing file %s to directory %s\n",
2612 filename, user_dir);
2614 /* Ensure the user directory exists */
2615 if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR))
2617 if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
2619 purple_debug_error("util", "Error creating directory %s: %s\n",
2620 user_dir, g_strerror(errno));
2621 return FALSE;
2625 filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename);
2627 ret = purple_util_write_data_to_file_absolute(filename_full, data, size);
2629 g_free(filename_full);
2630 return ret;
2633 gboolean
2634 purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, gssize size)
2636 gchar *filename_temp;
2637 FILE *file;
2638 size_t real_size, byteswritten;
2639 struct stat st;
2640 #ifndef HAVE_FILENO
2641 int fd;
2642 #endif
2644 purple_debug_info("util", "Writing file %s\n",
2645 filename_full);
2647 g_return_val_if_fail((size >= -1), FALSE);
2649 filename_temp = g_strdup_printf("%s.save", filename_full);
2651 /* Remove an old temporary file, if one exists */
2652 if (g_file_test(filename_temp, G_FILE_TEST_EXISTS))
2654 if (g_unlink(filename_temp) == -1)
2656 purple_debug_error("util", "Error removing old file "
2657 "%s: %s\n",
2658 filename_temp, g_strerror(errno));
2662 /* Open file */
2663 file = g_fopen(filename_temp, "wb");
2664 if (file == NULL)
2666 purple_debug_error("util", "Error opening file %s for "
2667 "writing: %s\n",
2668 filename_temp, g_strerror(errno));
2669 g_free(filename_temp);
2670 return FALSE;
2673 /* Write to file */
2674 real_size = (size == -1) ? strlen(data) : (size_t) size;
2675 byteswritten = fwrite(data, 1, real_size, file);
2677 #ifdef HAVE_FILENO
2678 /* Apparently XFS (and possibly other filesystems) do not
2679 * guarantee that file data is flushed before file metadata,
2680 * so this procedure is insufficient without some flushage. */
2681 if (fflush(file) < 0) {
2682 purple_debug_error("util", "Error flushing %s: %s\n",
2683 filename_temp, g_strerror(errno));
2684 g_free(filename_temp);
2685 fclose(file);
2686 return FALSE;
2688 if (fsync(fileno(file)) < 0) {
2689 purple_debug_error("util", "Error syncing file contents for %s: %s\n",
2690 filename_temp, g_strerror(errno));
2691 g_free(filename_temp);
2692 fclose(file);
2693 return FALSE;
2695 #endif
2697 /* Close file */
2698 if (fclose(file) != 0)
2700 purple_debug_error("util", "Error closing file %s: %s\n",
2701 filename_temp, g_strerror(errno));
2702 g_free(filename_temp);
2703 return FALSE;
2706 #ifndef HAVE_FILENO
2707 /* This is the same effect (we hope) as the HAVE_FILENO block
2708 * above, but for systems without fileno(). */
2709 if ((fd = open(filename_temp, O_RDWR)) < 0) {
2710 purple_debug_error("util", "Error opening file %s for flush: %s\n",
2711 filename_temp, g_strerror(errno));
2712 g_free(filename_temp);
2713 return FALSE;
2715 if (fsync(fd) < 0) {
2716 purple_debug_error("util", "Error syncing %s: %s\n",
2717 filename_temp, g_strerror(errno));
2718 g_free(filename_temp);
2719 close(fd);
2720 return FALSE;
2722 if (close(fd) < 0) {
2723 purple_debug_error("util", "Error closing %s after sync: %s\n",
2724 filename_temp, g_strerror(errno));
2725 g_free(filename_temp);
2726 return FALSE;
2728 #endif
2730 /* Ensure the file is the correct size */
2731 if (byteswritten != real_size)
2733 purple_debug_error("util", "Error writing to file %s: Wrote %"
2734 G_GSIZE_FORMAT " bytes "
2735 "but should have written %" G_GSIZE_FORMAT
2736 "; is your disk full?\n",
2737 filename_temp, byteswritten, real_size);
2738 g_free(filename_temp);
2739 return FALSE;
2741 /* Use stat to be absolutely sure. */
2742 if ((g_stat(filename_temp, &st) == -1) || (st.st_size != real_size))
2744 purple_debug_error("util", "Error writing data to file %s: "
2745 "Incomplete file written; is your disk "
2746 "full?\n",
2747 filename_temp);
2748 g_free(filename_temp);
2749 return FALSE;
2752 #ifndef _WIN32
2753 /* Set file permissions */
2754 if (chmod(filename_temp, S_IRUSR | S_IWUSR) == -1)
2756 purple_debug_error("util", "Error setting permissions of file %s: %s\n",
2757 filename_temp, g_strerror(errno));
2759 #endif
2761 /* Rename to the REAL name */
2762 if (g_rename(filename_temp, filename_full) == -1)
2764 purple_debug_error("util", "Error renaming %s to %s: %s\n",
2765 filename_temp, filename_full,
2766 g_strerror(errno));
2769 g_free(filename_temp);
2771 return TRUE;
2774 xmlnode *
2775 purple_util_read_xml_from_file(const char *filename, const char *description)
2777 const char *user_dir = purple_user_dir();
2778 gchar *filename_full;
2779 GError *error = NULL;
2780 gchar *contents = NULL;
2781 gsize length;
2782 xmlnode *node = NULL;
2784 g_return_val_if_fail(user_dir != NULL, NULL);
2786 purple_debug_info("util", "Reading file %s from directory %s\n",
2787 filename, user_dir);
2789 filename_full = g_build_filename(user_dir, filename, NULL);
2791 if (!g_file_test(filename_full, G_FILE_TEST_EXISTS))
2793 purple_debug_info("util", "File %s does not exist (this is not "
2794 "necessarily an error)\n", filename_full);
2795 g_free(filename_full);
2796 return NULL;
2799 if (!g_file_get_contents(filename_full, &contents, &length, &error))
2801 purple_debug_error("util", "Error reading file %s: %s\n",
2802 filename_full, error->message);
2803 g_error_free(error);
2806 if ((contents != NULL) && (length > 0))
2808 node = xmlnode_from_str(contents, length);
2810 /* If we were unable to parse the file then save its contents to a backup file */
2811 if (node == NULL)
2813 gchar *filename_temp;
2815 filename_temp = g_strdup_printf("%s~", filename);
2816 purple_debug_error("util", "Error parsing file %s. Renaming old "
2817 "file to %s\n", filename_full, filename_temp);
2818 purple_util_write_data_to_file(filename_temp, contents, length);
2819 g_free(filename_temp);
2822 g_free(contents);
2825 /* If we could not parse the file then show the user an error message */
2826 if (node == NULL)
2828 gchar *title, *msg;
2829 title = g_strdup_printf(_("Error Reading %s"), filename);
2830 msg = g_strdup_printf(_("An error was encountered reading your "
2831 "%s. They have not been loaded, and the old file "
2832 "has been renamed to %s~."), description, filename_full);
2833 purple_notify_error(NULL, NULL, title, msg);
2834 g_free(title);
2835 g_free(msg);
2838 g_free(filename_full);
2840 return node;
2844 * Like mkstemp() but returns a file pointer, uses a pre-set template,
2845 * uses the semantics of tempnam() for the directory to use and allocates
2846 * the space for the filepath.
2848 * Caller is responsible for closing the file and removing it when done,
2849 * as well as freeing the space pointed-to by "path" with g_free().
2851 * Returns NULL on failure and cleans up after itself if so.
2853 static const char *purple_mkstemp_templ = {"purpleXXXXXX"};
2855 FILE *
2856 purple_mkstemp(char **fpath, gboolean binary)
2858 const gchar *tmpdir;
2859 int fd;
2860 FILE *fp = NULL;
2862 g_return_val_if_fail(fpath != NULL, NULL);
2864 if((tmpdir = (gchar*)g_get_tmp_dir()) != NULL) {
2865 if((*fpath = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", tmpdir, purple_mkstemp_templ)) != NULL) {
2866 fd = g_mkstemp(*fpath);
2867 if(fd == -1) {
2868 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
2869 "Couldn't make \"%s\", error: %d\n",
2870 *fpath, errno);
2871 } else {
2872 if((fp = fdopen(fd, "r+")) == NULL) {
2873 close(fd);
2874 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
2875 "Couldn't fdopen(), error: %d\n", errno);
2879 if(!fp) {
2880 g_free(*fpath);
2881 *fpath = NULL;
2884 } else {
2885 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
2886 "g_get_tmp_dir() failed!\n");
2889 return fp;
2892 const char *
2893 purple_util_get_image_extension(gconstpointer data, size_t len)
2895 g_return_val_if_fail(data != NULL, NULL);
2896 g_return_val_if_fail(len > 0, NULL);
2898 if (len >= 4)
2900 if (!strncmp((char *)data, "GIF8", 4))
2901 return "gif";
2902 else if (!strncmp((char *)data, "\xff\xd8\xff", 3)) /* 4th may be e0 through ef */
2903 return "jpg";
2904 else if (!strncmp((char *)data, "\x89PNG", 4))
2905 return "png";
2906 else if (!strncmp((char *)data, "MM", 2) ||
2907 !strncmp((char *)data, "II", 2))
2908 return "tif";
2909 else if (!strncmp((char *)data, "BM", 2))
2910 return "bmp";
2913 return "icon";
2916 char *
2917 purple_util_get_image_checksum(gconstpointer image_data, size_t image_len)
2919 PurpleCipherContext *context;
2920 gchar digest[41];
2922 context = purple_cipher_context_new_by_name("sha1", NULL);
2923 if (context == NULL)
2925 purple_debug_error("util", "Could not find sha1 cipher\n");
2926 g_return_val_if_reached(NULL);
2929 /* Hash the image data */
2930 purple_cipher_context_append(context, image_data, image_len);
2931 if (!purple_cipher_context_digest_to_str(context, sizeof(digest), digest, NULL))
2933 purple_debug_error("util", "Failed to get SHA-1 digest.\n");
2934 g_return_val_if_reached(NULL);
2936 purple_cipher_context_destroy(context);
2938 return g_strdup(digest);
2941 char *
2942 purple_util_get_image_filename(gconstpointer image_data, size_t image_len)
2944 /* Return the filename */
2945 char *checksum = purple_util_get_image_checksum(image_data, image_len);
2946 char *filename = g_strdup_printf("%s.%s", checksum,
2947 purple_util_get_image_extension(image_data, image_len));
2948 g_free(checksum);
2949 return filename;
2952 gboolean
2953 purple_program_is_valid(const char *program)
2955 GError *error = NULL;
2956 char **argv;
2957 gchar *progname;
2958 gboolean is_valid = FALSE;
2960 g_return_val_if_fail(program != NULL, FALSE);
2961 g_return_val_if_fail(*program != '\0', FALSE);
2963 if (!g_shell_parse_argv(program, NULL, &argv, &error)) {
2964 purple_debug(PURPLE_DEBUG_ERROR, "program_is_valid",
2965 "Could not parse program '%s': %s\n",
2966 program, error->message);
2967 g_error_free(error);
2968 return FALSE;
2971 if (argv == NULL) {
2972 return FALSE;
2975 progname = g_find_program_in_path(argv[0]);
2976 is_valid = (progname != NULL);
2978 g_strfreev(argv);
2979 g_free(progname);
2981 return is_valid;
2985 gboolean
2986 purple_running_gnome(void)
2988 #ifndef _WIN32
2989 gchar *tmp = g_find_program_in_path("gnome-open");
2991 if (tmp == NULL)
2992 return FALSE;
2993 g_free(tmp);
2995 tmp = (gchar *)g_getenv("GNOME_DESKTOP_SESSION_ID");
2997 return ((tmp != NULL) && (*tmp != '\0'));
2998 #else
2999 return FALSE;
3000 #endif
3003 gboolean
3004 purple_running_kde(void)
3006 #ifndef _WIN32
3007 gchar *tmp = g_find_program_in_path("kfmclient");
3008 const char *session;
3010 if (tmp == NULL)
3011 return FALSE;
3012 g_free(tmp);
3014 session = g_getenv("KDE_FULL_SESSION");
3015 if (session != NULL && !strcmp(session, "true"))
3016 return TRUE;
3018 /* If you run Purple from Konsole under !KDE, this will provide a
3019 * a false positive. Since we do the GNOME checks first, this is
3020 * only a problem if you're running something !(KDE || GNOME) and
3021 * you run Purple from Konsole. This really shouldn't be a problem. */
3022 return ((g_getenv("KDEDIR") != NULL) || g_getenv("KDEDIRS") != NULL);
3023 #else
3024 return FALSE;
3025 #endif
3028 gboolean
3029 purple_running_osx(void)
3031 #if defined(__APPLE__)
3032 return TRUE;
3033 #else
3034 return FALSE;
3035 #endif
3038 char *
3039 purple_fd_get_ip(int fd)
3041 struct sockaddr addr;
3042 socklen_t namelen = sizeof(addr);
3044 g_return_val_if_fail(fd != 0, NULL);
3046 if (getsockname(fd, &addr, &namelen))
3047 return NULL;
3049 return g_strdup(inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr));
3053 /**************************************************************************
3054 * String Functions
3055 **************************************************************************/
3056 const char *
3057 purple_normalize(const PurpleAccount *account, const char *str)
3059 const char *ret = NULL;
3060 static char buf[BUF_LEN];
3062 if (account != NULL)
3064 PurplePlugin *prpl = purple_find_prpl(purple_account_get_protocol_id(account));
3066 if (prpl != NULL)
3068 PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
3070 if(prpl_info && prpl_info->normalize)
3071 ret = prpl_info->normalize(account, str);
3075 if (ret == NULL)
3077 char *tmp;
3079 tmp = g_utf8_normalize(str, -1, G_NORMALIZE_DEFAULT);
3080 g_snprintf(buf, sizeof(buf), "%s", tmp);
3081 g_free(tmp);
3083 ret = buf;
3086 return ret;
3090 * You probably don't want to call this directly, it is
3091 * mainly for use as a PRPL callback function. See the
3092 * comments in util.h.
3094 const char *
3095 purple_normalize_nocase(const PurpleAccount *account, const char *str)
3097 static char buf[BUF_LEN];
3098 char *tmp1, *tmp2;
3100 g_return_val_if_fail(str != NULL, NULL);
3102 tmp1 = g_utf8_strdown(str, -1);
3103 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
3104 g_snprintf(buf, sizeof(buf), "%s", tmp2 ? tmp2 : "");
3105 g_free(tmp2);
3106 g_free(tmp1);
3108 return buf;
3111 gchar *
3112 purple_strdup_withhtml(const gchar *src)
3114 gulong destsize, i, j;
3115 gchar *dest;
3117 g_return_val_if_fail(src != NULL, NULL);
3119 /* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */
3120 destsize = 1;
3121 for (i = 0; src[i] != '\0'; i++)
3123 if (src[i] == '\n')
3124 destsize += 4;
3125 else if (src[i] != '\r')
3126 destsize++;
3129 dest = g_malloc(destsize);
3131 /* Copy stuff, ignoring \r's, because they are dumb */
3132 for (i = 0, j = 0; src[i] != '\0'; i++) {
3133 if (src[i] == '\n') {
3134 strcpy(&dest[j], "<BR>");
3135 j += 4;
3136 } else if (src[i] != '\r')
3137 dest[j++] = src[i];
3140 dest[destsize-1] = '\0';
3142 return dest;
3145 gboolean
3146 purple_str_has_prefix(const char *s, const char *p)
3148 #if GLIB_CHECK_VERSION(2,2,0)
3149 return g_str_has_prefix(s, p);
3150 #else
3151 g_return_val_if_fail(s != NULL, FALSE);
3152 g_return_val_if_fail(p != NULL, FALSE);
3154 return (!strncmp(s, p, strlen(p)));
3155 #endif
3158 gboolean
3159 purple_str_has_suffix(const char *s, const char *x)
3161 #if GLIB_CHECK_VERSION(2,2,0)
3162 return g_str_has_suffix(s, x);
3163 #else
3164 int off;
3166 g_return_val_if_fail(s != NULL, FALSE);
3167 g_return_val_if_fail(x != NULL, FALSE);
3169 off = strlen(s) - strlen(x);
3170 return (off >= 0 && !strcmp(s + off, x));
3171 #endif
3174 char *
3175 purple_str_add_cr(const char *text)
3177 char *ret = NULL;
3178 int count = 0, j;
3179 guint i;
3181 g_return_val_if_fail(text != NULL, NULL);
3183 if (text[0] == '\n')
3184 count++;
3185 for (i = 1; i < strlen(text); i++)
3186 if (text[i] == '\n' && text[i - 1] != '\r')
3187 count++;
3189 if (count == 0)
3190 return g_strdup(text);
3192 ret = g_malloc0(strlen(text) + count + 1);
3194 i = 0; j = 0;
3195 if (text[i] == '\n')
3196 ret[j++] = '\r';
3197 ret[j++] = text[i++];
3198 for (; i < strlen(text); i++) {
3199 if (text[i] == '\n' && text[i - 1] != '\r')
3200 ret[j++] = '\r';
3201 ret[j++] = text[i];
3204 return ret;
3207 void
3208 purple_str_strip_char(char *text, char thechar)
3210 int i, j;
3212 g_return_if_fail(text != NULL);
3214 for (i = 0, j = 0; text[i]; i++)
3215 if (text[i] != thechar)
3216 text[j++] = text[i];
3218 text[j++] = '\0';
3221 void
3222 purple_util_chrreplace(char *string, char delimiter,
3223 char replacement)
3225 int i = 0;
3227 g_return_if_fail(string != NULL);
3229 while (string[i] != '\0')
3231 if (string[i] == delimiter)
3232 string[i] = replacement;
3233 i++;
3237 gchar *
3238 purple_strreplace(const char *string, const char *delimiter,
3239 const char *replacement)
3241 gchar **split;
3242 gchar *ret;
3244 g_return_val_if_fail(string != NULL, NULL);
3245 g_return_val_if_fail(delimiter != NULL, NULL);
3246 g_return_val_if_fail(replacement != NULL, NULL);
3248 split = g_strsplit(string, delimiter, 0);
3249 ret = g_strjoinv(replacement, split);
3250 g_strfreev(split);
3252 return ret;
3255 gchar *
3256 purple_strcasereplace(const char *string, const char *delimiter,
3257 const char *replacement)
3259 gchar *ret;
3260 int length_del, length_rep, i, j;
3262 g_return_val_if_fail(string != NULL, NULL);
3263 g_return_val_if_fail(delimiter != NULL, NULL);
3264 g_return_val_if_fail(replacement != NULL, NULL);
3266 length_del = strlen(delimiter);
3267 length_rep = strlen(replacement);
3269 /* Count how many times the delimiter appears */
3270 i = 0; /* position in the source string */
3271 j = 0; /* number of occurrences of "delimiter" */
3272 while (string[i] != '\0') {
3273 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3274 i += length_del;
3275 j += length_rep;
3276 } else {
3277 i++;
3278 j++;
3282 ret = g_malloc(j+1);
3284 i = 0; /* position in the source string */
3285 j = 0; /* position in the destination string */
3286 while (string[i] != '\0') {
3287 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3288 strncpy(&ret[j], replacement, length_rep);
3289 i += length_del;
3290 j += length_rep;
3291 } else {
3292 ret[j] = string[i];
3293 i++;
3294 j++;
3298 ret[j] = '\0';
3300 return ret;
3303 const char *
3304 purple_strcasestr(const char *haystack, const char *needle)
3306 size_t hlen, nlen;
3307 const char *tmp, *ret;
3309 g_return_val_if_fail(haystack != NULL, NULL);
3310 g_return_val_if_fail(needle != NULL, NULL);
3312 hlen = strlen(haystack);
3313 nlen = strlen(needle);
3314 tmp = haystack,
3315 ret = NULL;
3317 g_return_val_if_fail(hlen > 0, NULL);
3318 g_return_val_if_fail(nlen > 0, NULL);
3320 while (*tmp && !ret) {
3321 if (!g_ascii_strncasecmp(needle, tmp, nlen))
3322 ret = tmp;
3323 else
3324 tmp++;
3327 return ret;
3330 char *
3331 purple_str_size_to_units(size_t size)
3333 static const char * const size_str[] = { "bytes", "KiB", "MiB", "GiB" };
3334 float size_mag;
3335 int size_index = 0;
3337 if (size == -1) {
3338 return g_strdup(_("Calculating..."));
3340 else if (size == 0) {
3341 return g_strdup(_("Unknown."));
3343 else {
3344 size_mag = (float)size;
3346 while ((size_index < 3) && (size_mag > 1024)) {
3347 size_mag /= 1024;
3348 size_index++;
3351 if (size_index == 0) {
3352 return g_strdup_printf("%" G_GSIZE_FORMAT " %s", size, size_str[size_index]);
3353 } else {
3354 return g_strdup_printf("%.2f %s", size_mag, size_str[size_index]);
3359 char *
3360 purple_str_seconds_to_string(guint secs)
3362 char *ret = NULL;
3363 guint days, hrs, mins;
3365 if (secs < 60)
3367 return g_strdup_printf(dngettext(PACKAGE, "%d second", "%d seconds", secs), secs);
3370 days = secs / (60 * 60 * 24);
3371 secs = secs % (60 * 60 * 24);
3372 hrs = secs / (60 * 60);
3373 secs = secs % (60 * 60);
3374 mins = secs / 60;
3375 secs = secs % 60;
3377 if (days > 0)
3379 ret = g_strdup_printf(dngettext(PACKAGE, "%d day", "%d days", days), days);
3382 if (hrs > 0)
3384 if (ret != NULL)
3386 char *tmp = g_strdup_printf(
3387 dngettext(PACKAGE, "%s, %d hour", "%s, %d hours", hrs),
3388 ret, hrs);
3389 g_free(ret);
3390 ret = tmp;
3392 else
3393 ret = g_strdup_printf(dngettext(PACKAGE, "%d hour", "%d hours", hrs), hrs);
3396 if (mins > 0)
3398 if (ret != NULL)
3400 char *tmp = g_strdup_printf(
3401 dngettext(PACKAGE, "%s, %d minute", "%s, %d minutes", mins),
3402 ret, mins);
3403 g_free(ret);
3404 ret = tmp;
3406 else
3407 ret = g_strdup_printf(dngettext(PACKAGE, "%d minute", "%d minutes", mins), mins);
3410 return ret;
3414 char *
3415 purple_str_binary_to_ascii(const unsigned char *binary, guint len)
3417 GString *ret;
3418 guint i;
3420 g_return_val_if_fail(len > 0, NULL);
3422 ret = g_string_sized_new(len);
3424 for (i = 0; i < len; i++)
3425 if (binary[i] < 32 || binary[i] > 126)
3426 g_string_append_printf(ret, "\\x%02hhx", binary[i]);
3427 else if (binary[i] == '\\')
3428 g_string_append(ret, "\\\\");
3429 else
3430 g_string_append_c(ret, binary[i]);
3432 return g_string_free(ret, FALSE);
3435 /**************************************************************************
3436 * URI/URL Functions
3437 **************************************************************************/
3439 void purple_got_protocol_handler_uri(const char *uri)
3441 char proto[11];
3442 const char *tmp, *param_string;
3443 char *cmd;
3444 GHashTable *params = NULL;
3445 int len;
3446 if (!(tmp = strchr(uri, ':')) || tmp == uri) {
3447 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
3448 return;
3451 len = MIN(sizeof(proto) - 1, (tmp - uri));
3453 strncpy(proto, uri, len);
3454 proto[len] = '\0';
3456 tmp++;
3457 purple_debug_info("util", "Processing message '%s' for protocol '%s'.\n", tmp, proto);
3459 if ((param_string = strchr(tmp, '?'))) {
3460 const char *keyend = NULL, *pairstart;
3461 char *key, *value = NULL;
3463 cmd = g_strndup(tmp, (param_string - tmp));
3464 param_string++;
3466 params = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
3467 pairstart = tmp = param_string;
3469 while (*tmp || *pairstart) {
3470 if (*tmp == '&' || !(*tmp)) {
3471 /* If there is no explicit value */
3472 if (keyend == NULL)
3473 keyend = tmp;
3475 if (keyend && keyend != pairstart) {
3476 char *p;
3477 key = g_strndup(pairstart, (keyend - pairstart));
3478 /* If there is an explicit value */
3479 if (keyend != tmp && keyend != (tmp - 1))
3480 value = g_strndup(keyend + 1, (tmp - keyend - 1));
3481 for (p = key; *p; ++p)
3482 *p = g_ascii_tolower(*p);
3483 g_hash_table_insert(params, key, value);
3485 keyend = value = NULL;
3486 pairstart = (*tmp) ? tmp + 1 : tmp;
3487 } else if (*tmp == '=')
3488 keyend = tmp;
3490 if (*tmp)
3491 tmp++;
3493 } else
3494 cmd = g_strdup(tmp);
3496 purple_signal_emit_return_1(purple_get_core(), "uri-handler", proto, cmd, params);
3498 g_free(cmd);
3499 if (params)
3500 g_hash_table_destroy(params);
3504 * TODO: Should probably add a "gboolean *ret_ishttps" parameter that
3505 * is set to TRUE if this URL is https, otherwise it is set to
3506 * FALSE. But that change will break the API.
3508 * This is important for Yahoo! web messenger login. They now
3509 * force https login, and if you access the web messenger login
3510 * page via http then it redirects you to the https version, but
3511 * purple_util_fetch_url() ignores the "https" and attempts to
3512 * fetch the URL via http again, which gets redirected again.
3514 gboolean
3515 purple_url_parse(const char *url, char **ret_host, int *ret_port,
3516 char **ret_path, char **ret_user, char **ret_passwd)
3518 gboolean is_https = FALSE;
3519 char scan_info[255];
3520 char port_str[6];
3521 int f;
3522 const char *at, *slash;
3523 const char *turl;
3524 char host[256], path[256], user[256], passwd[256];
3525 int port = 0;
3526 /* hyphen at end includes it in control set */
3527 static const char addr_ctrl[] = "A-Za-z0-9.-";
3528 static const char port_ctrl[] = "0-9";
3529 static const char page_ctrl[] = "A-Za-z0-9.~_/:*!@&%%?=+^-";
3530 static const char user_ctrl[] = "A-Za-z0-9.~_/*!&%%?=+^-";
3531 static const char passwd_ctrl[] = "A-Za-z0-9.~_/*!&%%?=+^-";
3533 g_return_val_if_fail(url != NULL, FALSE);
3535 if ((turl = purple_strcasestr(url, "http://")) != NULL)
3537 turl += 7;
3538 url = turl;
3540 else if ((turl = purple_strcasestr(url, "https://")) != NULL)
3542 is_https = TRUE;
3543 turl += 8;
3544 url = turl;
3547 /* parse out authentication information if supplied */
3548 /* Only care about @ char BEFORE the first / */
3549 at = strchr(url, '@');
3550 slash = strchr(url, '/');
3551 if ((at != NULL) &&
3552 (((slash != NULL) && (strlen(at) > strlen(slash))) ||
3553 (slash == NULL))) {
3554 g_snprintf(scan_info, sizeof(scan_info),
3555 "%%255[%s]:%%255[%s]^@", user_ctrl, passwd_ctrl);
3556 f = sscanf(url, scan_info, user, passwd);
3558 if (f ==1 ) {
3559 /* No passwd, possibly just username supplied */
3560 g_snprintf(scan_info, sizeof(scan_info),
3561 "%%255[%s]^@", user_ctrl);
3562 f = sscanf(url, scan_info, user);
3563 *passwd = '\0';
3566 url = at+1; /* move pointer after the @ char */
3567 } else {
3568 *user = '\0';
3569 *passwd = '\0';
3572 g_snprintf(scan_info, sizeof(scan_info),
3573 "%%255[%s]:%%5[%s]/%%255[%s]", addr_ctrl, port_ctrl, page_ctrl);
3575 f = sscanf(url, scan_info, host, port_str, path);
3577 if (f == 1)
3579 g_snprintf(scan_info, sizeof(scan_info),
3580 "%%255[%s]/%%255[%s]",
3581 addr_ctrl, page_ctrl);
3582 f = sscanf(url, scan_info, host, path);
3583 /* Use the default port */
3584 if (is_https)
3585 g_snprintf(port_str, sizeof(port_str), "443");
3586 else
3587 g_snprintf(port_str, sizeof(port_str), "80");
3590 if (f == 0)
3591 *host = '\0';
3593 if (f <= 1)
3594 *path = '\0';
3596 sscanf(port_str, "%d", &port);
3598 if (ret_host != NULL) *ret_host = g_strdup(host);
3599 if (ret_port != NULL) *ret_port = port;
3600 if (ret_path != NULL) *ret_path = g_strdup(path);
3601 if (ret_user != NULL) *ret_user = g_strdup(user);
3602 if (ret_passwd != NULL) *ret_passwd = g_strdup(passwd);
3604 return ((*host != '\0') ? TRUE : FALSE);
3608 * The arguments to this function are similar to printf.
3610 static void
3611 purple_util_fetch_url_error(PurpleUtilFetchUrlData *gfud, const char *format, ...)
3613 gchar *error_message;
3614 va_list args;
3616 va_start(args, format);
3617 error_message = g_strdup_vprintf(format, args);
3618 va_end(args);
3620 gfud->callback(gfud, gfud->user_data, NULL, 0, error_message);
3621 g_free(error_message);
3622 purple_util_fetch_url_cancel(gfud);
3625 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message);
3626 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond);
3627 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data);
3629 static gboolean
3630 parse_redirect(const char *data, size_t data_len,
3631 PurpleUtilFetchUrlData *gfud)
3633 gchar *s;
3634 gchar *new_url, *temp_url, *end;
3635 gboolean full;
3636 int len;
3638 if ((s = g_strstr_len(data, data_len, "\nLocation: ")) == NULL)
3639 /* We're not being redirected */
3640 return FALSE;
3642 s += strlen("Location: ");
3643 end = strchr(s, '\r');
3645 /* Just in case :) */
3646 if (end == NULL)
3647 end = strchr(s, '\n');
3649 if (end == NULL)
3650 return FALSE;
3652 len = end - s;
3654 new_url = g_malloc(len + 1);
3655 strncpy(new_url, s, len);
3656 new_url[len] = '\0';
3658 full = gfud->full;
3660 if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL)
3662 temp_url = new_url;
3664 new_url = g_strdup_printf("%s:%d%s", gfud->website.address,
3665 gfud->website.port, temp_url);
3667 g_free(temp_url);
3669 full = FALSE;
3672 purple_debug_info("util", "Redirecting to %s\n", new_url);
3674 gfud->num_times_redirected++;
3675 if (gfud->num_times_redirected >= 5)
3677 purple_util_fetch_url_error(gfud,
3678 _("Could not open %s: Redirected too many times"),
3679 gfud->url);
3680 return TRUE;
3684 * Try again, with this new location. This code is somewhat
3685 * ugly, but we need to reuse the gfud because whoever called
3686 * us is holding a reference to it.
3688 g_free(gfud->url);
3689 gfud->url = new_url;
3690 gfud->full = full;
3691 g_free(gfud->request);
3692 gfud->request = NULL;
3694 if (gfud->is_ssl) {
3695 gfud->is_ssl = FALSE;
3696 purple_ssl_close(gfud->ssl_connection);
3697 gfud->ssl_connection = NULL;
3698 } else {
3699 purple_input_remove(gfud->inpa);
3700 gfud->inpa = 0;
3701 close(gfud->fd);
3702 gfud->fd = -1;
3704 gfud->request_written = 0;
3705 gfud->len = 0;
3706 gfud->data_len = 0;
3708 g_free(gfud->website.user);
3709 g_free(gfud->website.passwd);
3710 g_free(gfud->website.address);
3711 g_free(gfud->website.page);
3712 purple_url_parse(new_url, &gfud->website.address, &gfud->website.port,
3713 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
3715 if (purple_strcasestr(new_url, "https://") != NULL) {
3716 gfud->is_ssl = TRUE;
3717 gfud->ssl_connection = purple_ssl_connect(NULL,
3718 gfud->website.address, gfud->website.port,
3719 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
3720 } else {
3721 gfud->connect_data = purple_proxy_connect(NULL, NULL,
3722 gfud->website.address, gfud->website.port,
3723 url_fetch_connect_cb, gfud);
3726 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
3728 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
3729 gfud->website.address);
3732 return TRUE;
3735 static size_t
3736 parse_content_len(const char *data, size_t data_len)
3738 size_t content_len = 0;
3739 const char *p = NULL;
3741 /* This is still technically wrong, since headers are case-insensitive
3742 * [RFC 2616, section 4.2], though this ought to catch the normal case.
3743 * Note: data is _not_ nul-terminated.
3745 if(data_len > 16) {
3746 p = (strncmp(data, "Content-Length: ", 16) == 0) ? data : NULL;
3747 if(!p)
3748 p = (strncmp(data, "CONTENT-LENGTH: ", 16) == 0)
3749 ? data : NULL;
3750 if(!p) {
3751 p = g_strstr_len(data, data_len, "\nContent-Length: ");
3752 if (p)
3753 p++;
3755 if(!p) {
3756 p = g_strstr_len(data, data_len, "\nCONTENT-LENGTH: ");
3757 if (p)
3758 p++;
3761 if(p)
3762 p += 16;
3765 /* If we can find a Content-Length header at all, try to sscanf it.
3766 * Response headers should end with at least \r\n, so sscanf is safe,
3767 * if we make sure that there is indeed a \n in our header.
3769 if (p && g_strstr_len(p, data_len - (p - data), "\n")) {
3770 sscanf(p, "%" G_GSIZE_FORMAT, &content_len);
3771 purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT "\n", content_len);
3774 return content_len;
3778 static void
3779 url_fetch_recv_cb(gpointer url_data, gint source, PurpleInputCondition cond)
3781 PurpleUtilFetchUrlData *gfud = url_data;
3782 int len;
3783 char buf[4096];
3784 char *data_cursor;
3785 gboolean got_eof = FALSE;
3788 * Read data in a loop until we can't read any more! This is a
3789 * little confusing because we read using a different function
3790 * depending on whether the socket is ssl or cleartext.
3792 while ((gfud->is_ssl && ((len = purple_ssl_read(gfud->ssl_connection, buf, sizeof(buf))) > 0)) ||
3793 (!gfud->is_ssl && (len = read(source, buf, sizeof(buf))) > 0))
3795 if(gfud->max_len != -1 && (gfud->len + len) > gfud->max_len) {
3796 purple_util_fetch_url_error(gfud, _("Error reading from %s: response too long (%d bytes limit)"),
3797 gfud->website.address, gfud->max_len);
3798 return;
3801 /* If we've filled up our buffer, make it bigger */
3802 if((gfud->len + len) >= gfud->data_len) {
3803 while((gfud->len + len) >= gfud->data_len)
3804 gfud->data_len += sizeof(buf);
3806 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
3809 data_cursor = gfud->webdata + gfud->len;
3811 gfud->len += len;
3813 memcpy(data_cursor, buf, len);
3815 gfud->webdata[gfud->len] = '\0';
3817 if(!gfud->got_headers) {
3818 char *tmp;
3820 /* See if we've reached the end of the headers yet */
3821 if((tmp = strstr(gfud->webdata, "\r\n\r\n"))) {
3822 char * new_data;
3823 guint header_len = (tmp + 4 - gfud->webdata);
3824 size_t content_len;
3826 purple_debug_misc("util", "Response headers: '%.*s'\n",
3827 header_len, gfud->webdata);
3829 /* See if we can find a redirect. */
3830 if(parse_redirect(gfud->webdata, header_len, gfud))
3831 return;
3833 gfud->got_headers = TRUE;
3835 /* No redirect. See if we can find a content length. */
3836 content_len = parse_content_len(gfud->webdata, header_len);
3838 if(content_len == 0) {
3839 /* We'll stick with an initial 8192 */
3840 content_len = 8192;
3841 } else {
3842 gfud->has_explicit_data_len = TRUE;
3846 /* If we're returning the headers too, we don't need to clean them out */
3847 if(gfud->include_headers) {
3848 gfud->data_len = content_len + header_len;
3849 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len);
3850 } else {
3851 size_t body_len = 0;
3853 if(gfud->len > (header_len + 1))
3854 body_len = (gfud->len - header_len);
3856 content_len = MAX(content_len, body_len);
3858 new_data = g_try_malloc(content_len);
3859 if(new_data == NULL) {
3860 purple_debug_error("util",
3861 "Failed to allocate %" G_GSIZE_FORMAT " bytes: %s\n",
3862 content_len, g_strerror(errno));
3863 purple_util_fetch_url_error(gfud,
3864 _("Unable to allocate enough memory to hold "
3865 "the contents from %s. The web server may "
3866 "be trying something malicious."),
3867 gfud->website.address);
3869 return;
3872 /* We may have read part of the body when reading the headers, don't lose it */
3873 if(body_len > 0) {
3874 tmp += 4;
3875 memcpy(new_data, tmp, body_len);
3878 /* Out with the old... */
3879 g_free(gfud->webdata);
3881 /* In with the new. */
3882 gfud->len = body_len;
3883 gfud->data_len = content_len;
3884 gfud->webdata = new_data;
3889 if(gfud->has_explicit_data_len && gfud->len >= gfud->data_len) {
3890 got_eof = TRUE;
3891 break;
3895 if(len < 0) {
3896 if(errno == EAGAIN) {
3897 return;
3898 } else {
3899 purple_util_fetch_url_error(gfud, _("Error reading from %s: %s"),
3900 gfud->website.address, g_strerror(errno));
3901 return;
3905 if((len == 0) || got_eof) {
3906 gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1);
3907 gfud->webdata[gfud->len] = '\0';
3909 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL);
3910 purple_util_fetch_url_cancel(gfud);
3914 static void ssl_url_fetch_recv_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
3916 url_fetch_recv_cb(data, -1, cond);
3920 * This function is called when the socket is available to be written
3921 * to.
3923 * @param source The file descriptor that can be written to. This can
3924 * be an http connection or it can be the SSL connection of an
3925 * https request. So be careful what you use it for! If it's
3926 * an https request then use purple_ssl_write() instead of
3927 * writing to it directly.
3929 static void
3930 url_fetch_send_cb(gpointer data, gint source, PurpleInputCondition cond)
3932 PurpleUtilFetchUrlData *gfud;
3933 int len, total_len;
3935 gfud = data;
3937 if (gfud->request == NULL)
3939 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
3940 * clients must know how to handle the "chunked" transfer encoding.
3941 * Purple doesn't know how to handle "chunked", so should always send
3942 * the Host header regardless, to get around some observed problems
3944 if (gfud->user_agent) {
3945 gfud->request = g_strdup_printf(
3946 "GET %s%s HTTP/%s\r\n"
3947 "Connection: close\r\n"
3948 "User-Agent: %s\r\n"
3949 "Accept: */*\r\n"
3950 "Host: %s\r\n\r\n",
3951 (gfud->full ? "" : "/"),
3952 (gfud->full ? (gfud->url ? gfud->url : "") : (gfud->website.page ? gfud->website.page : "")),
3953 (gfud->http11 ? "1.1" : "1.0"),
3954 (gfud->user_agent ? gfud->user_agent : ""),
3955 (gfud->website.address ? gfud->website.address : ""));
3956 } else {
3957 gfud->request = g_strdup_printf(
3958 "GET %s%s HTTP/%s\r\n"
3959 "Connection: close\r\n"
3960 "Accept: */*\r\n"
3961 "Host: %s\r\n\r\n",
3962 (gfud->full ? "" : "/"),
3963 (gfud->full ? (gfud->url ? gfud->url : "") : (gfud->website.page ? gfud->website.page : "")),
3964 (gfud->http11 ? "1.1" : "1.0"),
3965 (gfud->website.address ? gfud->website.address : ""));
3969 if(g_getenv("PURPLE_UNSAFE_DEBUG"))
3970 purple_debug_misc("util", "Request: '%s'\n", gfud->request);
3971 else
3972 purple_debug_misc("util", "request constructed\n");
3974 total_len = strlen(gfud->request);
3976 if (gfud->is_ssl)
3977 len = purple_ssl_write(gfud->ssl_connection, gfud->request + gfud->request_written,
3978 total_len - gfud->request_written);
3979 else
3980 len = write(gfud->fd, gfud->request + gfud->request_written,
3981 total_len - gfud->request_written);
3983 if (len < 0 && errno == EAGAIN)
3984 return;
3985 else if (len < 0) {
3986 purple_util_fetch_url_error(gfud, _("Error writing to %s: %s"),
3987 gfud->website.address, g_strerror(errno));
3988 return;
3990 gfud->request_written += len;
3992 if (gfud->request_written < total_len)
3993 return;
3995 /* We're done writing our request, now start reading the response */
3996 if (gfud->is_ssl) {
3997 purple_input_remove(gfud->inpa);
3998 gfud->inpa = 0;
3999 purple_ssl_input_add(gfud->ssl_connection, ssl_url_fetch_recv_cb, gfud);
4000 } else {
4001 purple_input_remove(gfud->inpa);
4002 gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ, url_fetch_recv_cb,
4003 gfud);
4007 static void
4008 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message)
4010 PurpleUtilFetchUrlData *gfud;
4012 gfud = url_data;
4013 gfud->connect_data = NULL;
4015 if (source == -1)
4017 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4018 (gfud->website.address ? gfud->website.address : ""), error_message);
4019 return;
4022 gfud->fd = source;
4024 gfud->inpa = purple_input_add(source, PURPLE_INPUT_WRITE,
4025 url_fetch_send_cb, gfud);
4026 url_fetch_send_cb(gfud, source, PURPLE_INPUT_WRITE);
4029 static void ssl_url_fetch_connect_cb(gpointer data, PurpleSslConnection *ssl_connection, PurpleInputCondition cond)
4031 PurpleUtilFetchUrlData *gfud;
4033 gfud = data;
4035 gfud->inpa = purple_input_add(ssl_connection->fd, PURPLE_INPUT_WRITE,
4036 url_fetch_send_cb, gfud);
4037 url_fetch_send_cb(gfud, ssl_connection->fd, PURPLE_INPUT_WRITE);
4040 static void ssl_url_fetch_error_cb(PurpleSslConnection *ssl_connection, PurpleSslErrorType error, gpointer data)
4042 PurpleUtilFetchUrlData *gfud;
4044 gfud = data;
4045 gfud->ssl_connection = NULL;
4047 purple_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"),
4048 (gfud->website.address ? gfud->website.address : ""),
4049 purple_ssl_strerror(error));
4052 PurpleUtilFetchUrlData *
4053 purple_util_fetch_url_request(const char *url, gboolean full,
4054 const char *user_agent, gboolean http11,
4055 const char *request, gboolean include_headers,
4056 PurpleUtilFetchUrlCallback callback, void *user_data)
4058 return purple_util_fetch_url_request_len(url, full,
4059 user_agent, http11,
4060 request, include_headers, -1,
4061 callback, user_data);
4064 PurpleUtilFetchUrlData *
4065 purple_util_fetch_url_request_len(const char *url, gboolean full,
4066 const char *user_agent, gboolean http11,
4067 const char *request, gboolean include_headers, gssize max_len,
4068 PurpleUtilFetchUrlCallback callback, void *user_data)
4070 PurpleUtilFetchUrlData *gfud;
4072 g_return_val_if_fail(url != NULL, NULL);
4073 g_return_val_if_fail(callback != NULL, NULL);
4075 if(g_getenv("PURPLE_UNSAFE_DEBUG"))
4076 purple_debug_info("util",
4077 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
4078 url, full, user_agent?user_agent:"(null)", http11);
4079 else
4080 purple_debug_info("util", "requesting to fetch a URL\n");
4082 gfud = g_new0(PurpleUtilFetchUrlData, 1);
4084 gfud->callback = callback;
4085 gfud->user_data = user_data;
4086 gfud->url = g_strdup(url);
4087 gfud->user_agent = g_strdup(user_agent);
4088 gfud->http11 = http11;
4089 gfud->full = full;
4090 gfud->request = g_strdup(request);
4091 gfud->include_headers = include_headers;
4092 gfud->fd = -1;
4093 gfud->max_len = max_len;
4095 purple_url_parse(url, &gfud->website.address, &gfud->website.port,
4096 &gfud->website.page, &gfud->website.user, &gfud->website.passwd);
4098 if (purple_strcasestr(url, "https://") != NULL) {
4099 gfud->is_ssl = TRUE;
4100 gfud->ssl_connection = purple_ssl_connect(NULL,
4101 gfud->website.address, gfud->website.port,
4102 ssl_url_fetch_connect_cb, ssl_url_fetch_error_cb, gfud);
4103 } else {
4104 gfud->connect_data = purple_proxy_connect(NULL, NULL,
4105 gfud->website.address, gfud->website.port,
4106 url_fetch_connect_cb, gfud);
4109 if (gfud->ssl_connection == NULL && gfud->connect_data == NULL)
4111 purple_util_fetch_url_error(gfud, _("Unable to connect to %s"),
4112 gfud->website.address);
4113 return NULL;
4116 return gfud;
4119 void
4120 purple_util_fetch_url_cancel(PurpleUtilFetchUrlData *gfud)
4122 if (gfud->ssl_connection != NULL)
4123 purple_ssl_close(gfud->ssl_connection);
4125 if (gfud->connect_data != NULL)
4126 purple_proxy_connect_cancel(gfud->connect_data);
4128 if (gfud->inpa > 0)
4129 purple_input_remove(gfud->inpa);
4131 if (gfud->fd >= 0)
4132 close(gfud->fd);
4134 g_free(gfud->website.user);
4135 g_free(gfud->website.passwd);
4136 g_free(gfud->website.address);
4137 g_free(gfud->website.page);
4138 g_free(gfud->url);
4139 g_free(gfud->user_agent);
4140 g_free(gfud->request);
4141 g_free(gfud->webdata);
4143 g_free(gfud);
4146 const char *
4147 purple_url_decode(const char *str)
4149 static char buf[BUF_LEN];
4150 guint i, j = 0;
4151 char *bum;
4152 char hex[3];
4154 g_return_val_if_fail(str != NULL, NULL);
4157 * XXX - This check could be removed and buf could be made
4158 * dynamically allocated, but this is easier.
4160 if (strlen(str) >= BUF_LEN)
4161 return NULL;
4163 for (i = 0; i < strlen(str); i++) {
4165 if (str[i] != '%')
4166 buf[j++] = str[i];
4167 else {
4168 strncpy(hex, str + ++i, 2);
4169 hex[2] = '\0';
4171 /* i is pointing to the start of the number */
4172 i++;
4175 * Now it's at the end and at the start of the for loop
4176 * will be at the next character.
4178 buf[j++] = strtol(hex, NULL, 16);
4182 buf[j] = '\0';
4184 if (!g_utf8_validate(buf, -1, (const char **)&bum))
4185 *bum = '\0';
4187 return buf;
4190 const char *
4191 purple_url_encode(const char *str)
4193 const char *iter;
4194 static char buf[BUF_LEN];
4195 char utf_char[6];
4196 guint i, j = 0;
4198 g_return_val_if_fail(str != NULL, NULL);
4199 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4201 iter = str;
4202 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4203 gunichar c = g_utf8_get_char(iter);
4204 /* If the character is an ASCII character and is alphanumeric
4205 * no need to escape */
4206 if (c < 128 && isalnum(c)) {
4207 buf[j++] = c;
4208 } else {
4209 int bytes = g_unichar_to_utf8(c, utf_char);
4210 for (i = 0; i < bytes; i++) {
4211 if (j > (BUF_LEN - 4))
4212 break;
4213 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff);
4214 j += 3;
4219 buf[j] = '\0';
4221 return buf;
4224 /* Originally lifted from
4225 * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html
4226 * ... and slightly modified to be a bit more rfc822 compliant
4227 * ... and modified a bit more to make domain checking rfc1035 compliant
4228 * with the exception permitted in rfc1101 for domains to start with digit
4229 * but not completely checking to avoid conflicts with IP addresses
4231 gboolean
4232 purple_email_is_valid(const char *address)
4234 const char *c, *domain;
4235 static char *rfc822_specials = "()<>@,;:\\\"[]";
4237 g_return_val_if_fail(address != NULL, FALSE);
4239 /* first we validate the name portion (name@domain) (rfc822)*/
4240 for (c = address; *c; c++) {
4241 if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) {
4242 while (*++c) {
4243 if (*c == '\\') {
4244 if (*c++ && *c < 127 && *c != '\n' && *c != '\r') continue;
4245 else return FALSE;
4247 if (*c == '\"') break;
4248 if (*c < ' ' || *c >= 127) return FALSE;
4250 if (!*c++) return FALSE;
4251 if (*c == '@') break;
4252 if (*c != '.') return FALSE;
4253 continue;
4255 if (*c == '@') break;
4256 if (*c <= ' ' || *c >= 127) return FALSE;
4257 if (strchr(rfc822_specials, *c)) return FALSE;
4260 /* It's obviously not an email address if we didn't find an '@' above */
4261 if (*c == '\0') return FALSE;
4263 /* strictly we should return false if (*(c - 1) == '.') too, but I think
4264 * we should permit user.@domain type addresses - they do work :) */
4265 if (c == address) return FALSE;
4267 /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */
4268 if (!*(domain = ++c)) return FALSE;
4269 do {
4270 if (*c == '.' && (c == domain || *(c - 1) == '.' || *(c - 1) == '-'))
4271 return FALSE;
4272 if (*c == '-' && *(c - 1) == '.') return FALSE;
4273 if ((*c < '0' && *c != '-' && *c != '.') || (*c > '9' && *c < 'A') ||
4274 (*c > 'Z' && *c < 'a') || (*c > 'z')) return FALSE;
4275 } while (*++c);
4277 if (*(c - 1) == '-') return FALSE;
4279 return ((c - domain) > 3 ? TRUE : FALSE);
4282 gboolean
4283 purple_ip_address_is_valid(const char *ip)
4285 int c, o1, o2, o3, o4;
4286 char end;
4288 g_return_val_if_fail(ip != NULL, FALSE);
4290 c = sscanf(ip, "%d.%d.%d.%d%c", &o1, &o2, &o3, &o4, &end);
4291 if (c != 4 || o1 < 0 || o1 > 255 || o2 < 0 || o2 > 255 || o3 < 0 || o3 > 255 || o4 < 0 || o4 > 255)
4292 return FALSE;
4293 return TRUE;
4296 /* Stolen from gnome_uri_list_extract_uris */
4297 GList *
4298 purple_uri_list_extract_uris(const gchar *uri_list)
4300 const gchar *p, *q;
4301 gchar *retval;
4302 GList *result = NULL;
4304 g_return_val_if_fail (uri_list != NULL, NULL);
4306 p = uri_list;
4308 /* We don't actually try to validate the URI according to RFC
4309 * 2396, or even check for allowed characters - we just ignore
4310 * comments and trim whitespace off the ends. We also
4311 * allow LF delimination as well as the specified CRLF.
4313 while (p) {
4314 if (*p != '#') {
4315 while (isspace(*p))
4316 p++;
4318 q = p;
4319 while (*q && (*q != '\n') && (*q != '\r'))
4320 q++;
4322 if (q > p) {
4323 q--;
4324 while (q > p && isspace(*q))
4325 q--;
4327 retval = (gchar*)g_malloc (q - p + 2);
4328 strncpy (retval, p, q - p + 1);
4329 retval[q - p + 1] = '\0';
4331 result = g_list_prepend (result, retval);
4334 p = strchr (p, '\n');
4335 if (p)
4336 p++;
4339 return g_list_reverse (result);
4343 /* Stolen from gnome_uri_list_extract_filenames */
4344 GList *
4345 purple_uri_list_extract_filenames(const gchar *uri_list)
4347 GList *tmp_list, *node, *result;
4349 g_return_val_if_fail (uri_list != NULL, NULL);
4351 result = purple_uri_list_extract_uris(uri_list);
4353 tmp_list = result;
4354 while (tmp_list) {
4355 gchar *s = (gchar*)tmp_list->data;
4357 node = tmp_list;
4358 tmp_list = tmp_list->next;
4360 if (!strncmp (s, "file:", 5)) {
4361 node->data = g_filename_from_uri (s, NULL, NULL);
4362 /* not sure if this fallback is useful at all */
4363 if (!node->data) node->data = g_strdup (s+5);
4364 } else {
4365 result = g_list_delete_link(result, node);
4367 g_free (s);
4369 return result;
4372 /**************************************************************************
4373 * UTF8 String Functions
4374 **************************************************************************/
4375 gchar *
4376 purple_utf8_try_convert(const char *str)
4378 gsize converted;
4379 gchar *utf8;
4381 g_return_val_if_fail(str != NULL, NULL);
4383 if (g_utf8_validate(str, -1, NULL)) {
4384 return g_strdup(str);
4387 utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL);
4388 if (utf8 != NULL)
4389 return utf8;
4391 utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL);
4392 if ((utf8 != NULL) && (converted == strlen(str)))
4393 return utf8;
4395 g_free(utf8);
4397 return NULL;
4400 #define utf8_first(x) ((x & 0x80) == 0 || (x & 0xe0) == 0xc0 \
4401 || (x & 0xf0) == 0xe0 || (x & 0xf8) == 0xf)
4402 gchar *
4403 purple_utf8_salvage(const char *str)
4405 GString *workstr;
4406 const char *end;
4408 g_return_val_if_fail(str != NULL, NULL);
4410 workstr = g_string_sized_new(strlen(str));
4412 do {
4413 g_utf8_validate(str, -1, &end);
4414 workstr = g_string_append_len(workstr, str, end - str);
4415 str = end;
4416 if (*str == '\0')
4417 break;
4418 do {
4419 workstr = g_string_append_c(workstr, '?');
4420 str++;
4421 } while (!utf8_first(*str));
4422 } while (*str != '\0');
4424 return g_string_free(workstr, FALSE);
4428 * This function is copied from g_strerror() but changed to use
4429 * gai_strerror().
4431 G_CONST_RETURN gchar *
4432 purple_gai_strerror(gint errnum)
4434 static GStaticPrivate msg_private = G_STATIC_PRIVATE_INIT;
4435 char *msg;
4436 int saved_errno = errno;
4438 const char *msg_locale;
4440 msg_locale = gai_strerror(errnum);
4441 if (g_get_charset(NULL))
4443 /* This string is already UTF-8--great! */
4444 errno = saved_errno;
4445 return msg_locale;
4447 else
4449 gchar *msg_utf8 = g_locale_to_utf8(msg_locale, -1, NULL, NULL, NULL);
4450 if (msg_utf8)
4452 /* Stick in the quark table so that we can return a static result */
4453 GQuark msg_quark = g_quark_from_string(msg_utf8);
4454 g_free(msg_utf8);
4456 msg_utf8 = (gchar *)g_quark_to_string(msg_quark);
4457 errno = saved_errno;
4458 return msg_utf8;
4462 msg = g_static_private_get(&msg_private);
4463 if (!msg)
4465 msg = g_new(gchar, 64);
4466 g_static_private_set(&msg_private, msg, g_free);
4469 sprintf(msg, "unknown error (%d)", errnum);
4471 errno = saved_errno;
4472 return msg;
4475 char *
4476 purple_utf8_ncr_encode(const char *str)
4478 GString *out;
4480 g_return_val_if_fail(str != NULL, NULL);
4481 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4483 out = g_string_new("");
4485 for(; *str; str = g_utf8_next_char(str)) {
4486 gunichar wc = g_utf8_get_char(str);
4488 /* super simple check. hopefully not too wrong. */
4489 if(wc >= 0x80) {
4490 g_string_append_printf(out, "&#%u;", (guint32) wc);
4491 } else {
4492 g_string_append_unichar(out, wc);
4496 return g_string_free(out, FALSE);
4500 char *
4501 purple_utf8_ncr_decode(const char *str)
4503 GString *out;
4504 char *buf, *b;
4506 g_return_val_if_fail(str != NULL, NULL);
4507 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4509 buf = (char *) str;
4510 out = g_string_new("");
4512 while( (b = strstr(buf, "&#")) ) {
4513 gunichar wc;
4514 int base = 0;
4516 /* append everything leading up to the &# */
4517 g_string_append_len(out, buf, b-buf);
4519 b += 2; /* skip past the &# */
4521 /* strtoul will treat 0x prefix as hex, but not just x */
4522 if(*b == 'x' || *b == 'X') {
4523 base = 16;
4524 b++;
4527 /* advances buf to the end of the ncr segment */
4528 wc = (gunichar) strtoul(b, &buf, base);
4530 /* this mimics the previous impl of ncr_decode */
4531 if(*buf == ';') {
4532 g_string_append_unichar(out, wc);
4533 buf++;
4537 /* append whatever's left */
4538 g_string_append(out, buf);
4540 return g_string_free(out, FALSE);
4545 purple_utf8_strcasecmp(const char *a, const char *b)
4547 char *a_norm = NULL;
4548 char *b_norm = NULL;
4549 int ret = -1;
4551 if(!a && b)
4552 return -1;
4553 else if(!b && a)
4554 return 1;
4555 else if(!a && !b)
4556 return 0;
4558 if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL))
4560 purple_debug_error("purple_utf8_strcasecmp",
4561 "One or both parameters are invalid UTF8\n");
4562 return ret;
4565 a_norm = g_utf8_casefold(a, -1);
4566 b_norm = g_utf8_casefold(b, -1);
4567 ret = g_utf8_collate(a_norm, b_norm);
4568 g_free(a_norm);
4569 g_free(b_norm);
4571 return ret;
4574 /* previously conversation::find_nick() */
4575 gboolean
4576 purple_utf8_has_word(const char *haystack, const char *needle)
4578 char *hay, *pin, *p;
4579 const char *start, *prev_char;
4580 gunichar before, after;
4581 int n;
4582 gboolean ret = FALSE;
4584 start = hay = g_utf8_strdown(haystack, -1);
4586 pin = g_utf8_strdown(needle, -1);
4587 n = strlen(pin);
4589 while ((p = strstr(start, pin)) != NULL) {
4590 prev_char = g_utf8_find_prev_char(hay, p);
4591 before = -2;
4592 if (prev_char) {
4593 before = g_utf8_get_char(prev_char);
4595 after = g_utf8_get_char_validated(p + n, - 1);
4597 if ((p == hay ||
4598 /* The character before is a reasonable guess for a word boundary
4599 ("!g_unichar_isalnum()" is not a valid way to determine word
4600 boundaries, but it is the only reasonable thing to do here),
4601 and isn't the '&' from a "&amp;" or some such entity*/
4602 (before != -2 && !g_unichar_isalnum(before) && *(p - 1) != '&'))
4603 && after != -2 && !g_unichar_isalnum(after)) {
4604 ret = TRUE;
4605 break;
4607 start = p + 1;
4610 g_free(pin);
4611 g_free(hay);
4613 return ret;
4616 void
4617 purple_print_utf8_to_console(FILE *filestream, char *message)
4619 gchar *message_conv;
4620 GError *error = NULL;
4622 /* Try to convert 'message' to user's locale */
4623 message_conv = g_locale_from_utf8(message, -1, NULL, NULL, &error);
4624 if (message_conv != NULL) {
4625 fputs(message_conv, filestream);
4626 g_free(message_conv);
4628 else
4630 /* use 'message' as a fallback */
4631 g_warning("%s\n", error->message);
4632 g_error_free(error);
4633 fputs(message, filestream);
4637 gboolean purple_message_meify(char *message, gssize len)
4639 char *c;
4640 gboolean inside_html = FALSE;
4642 g_return_val_if_fail(message != NULL, FALSE);
4644 if(len == -1)
4645 len = strlen(message);
4647 for (c = message; *c; c++, len--) {
4648 if(inside_html) {
4649 if(*c == '>')
4650 inside_html = FALSE;
4651 } else {
4652 if(*c == '<')
4653 inside_html = TRUE;
4654 else
4655 break;
4659 if(*c && !g_ascii_strncasecmp(c, "/me ", 4)) {
4660 memmove(c, c+4, len-3);
4661 return TRUE;
4664 return FALSE;
4667 char *purple_text_strip_mnemonic(const char *in)
4669 char *out;
4670 char *a;
4671 char *a0;
4672 const char *b;
4674 g_return_val_if_fail(in != NULL, NULL);
4676 out = g_malloc(strlen(in)+1);
4677 a = out;
4678 b = in;
4680 a0 = a; /* The last non-space char seen so far, or the first char */
4682 while(*b) {
4683 if(*b == '_') {
4684 if(a > out && b > in && *(b-1) == '(' && *(b+1) && !(*(b+1) & 0x80) && *(b+2) == ')') {
4685 /* Detected CJK style shortcut (Bug 875311) */
4686 a = a0; /* undo the left parenthesis */
4687 b += 3; /* and skip the whole mess */
4688 } else if(*(b+1) == '_') {
4689 *(a++) = '_';
4690 b += 2;
4691 a0 = a;
4692 } else {
4693 b++;
4695 /* We don't want to corrupt the middle of UTF-8 characters */
4696 } else if (!(*b & 0x80)) { /* other 1-byte char */
4697 if (*b != ' ')
4698 a0 = a;
4699 *(a++) = *(b++);
4700 } else {
4701 /* Multibyte utf8 char, don't look for _ inside these */
4702 int n = 0;
4703 int i;
4704 if ((*b & 0xe0) == 0xc0) {
4705 n = 2;
4706 } else if ((*b & 0xf0) == 0xe0) {
4707 n = 3;
4708 } else if ((*b & 0xf8) == 0xf0) {
4709 n = 4;
4710 } else if ((*b & 0xfc) == 0xf8) {
4711 n = 5;
4712 } else if ((*b & 0xfe) == 0xfc) {
4713 n = 6;
4714 } else { /* Illegal utf8 */
4715 n = 1;
4717 a0 = a; /* unless we want to delete CJK spaces too */
4718 for (i = 0; i < n && *b; i += 1) {
4719 *(a++) = *(b++);
4723 *a = '\0';
4725 return out;
4728 const char* purple_unescape_filename(const char *escaped) {
4729 return purple_url_decode(escaped);
4733 /* this is almost identical to purple_url_encode (hence purple_url_decode
4734 * being used above), but we want to keep certain characters unescaped
4735 * for compat reasons */
4736 const char *
4737 purple_escape_filename(const char *str)
4739 const char *iter;
4740 static char buf[BUF_LEN];
4741 char utf_char[6];
4742 guint i, j = 0;
4744 g_return_val_if_fail(str != NULL, NULL);
4745 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4747 iter = str;
4748 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4749 gunichar c = g_utf8_get_char(iter);
4750 /* If the character is an ASCII character and is alphanumeric,
4751 * or one of the specified values, no need to escape */
4752 if (c < 128 && (g_ascii_isalnum(c) || c == '@' || c == '-' ||
4753 c == '_' || c == '.' || c == '#')) {
4754 buf[j++] = c;
4755 } else {
4756 int bytes = g_unichar_to_utf8(c, utf_char);
4757 for (i = 0; i < bytes; i++) {
4758 if (j > (BUF_LEN - 4))
4759 break;
4760 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff);
4761 j += 3;
4766 buf[j] = '\0';
4768 return buf;
4771 const char *_purple_oscar_convert(const char *act, const char *protocol)
4773 if (protocol && act && strcmp(protocol, "prpl-oscar") == 0) {
4774 int i;
4775 for (i = 0; act[i] != '\0'; i++)
4776 if (!isdigit(act[i]))
4777 return "prpl-aim";
4778 return "prpl-icq";
4780 return protocol;
4783 void purple_restore_default_signal_handlers(void)
4785 #ifndef _WIN32
4786 #ifdef HAVE_SIGNAL_H
4787 signal(SIGHUP, SIG_DFL); /* 1: terminal line hangup */
4788 signal(SIGINT, SIG_DFL); /* 2: interrupt program */
4789 signal(SIGQUIT, SIG_DFL); /* 3: quit program */
4790 signal(SIGILL, SIG_DFL); /* 4: illegal instruction (not reset when caught) */
4791 signal(SIGTRAP, SIG_DFL); /* 5: trace trap (not reset when caught) */
4792 signal(SIGABRT, SIG_DFL); /* 6: abort program */
4794 #ifdef SIGPOLL
4795 signal(SIGPOLL, SIG_DFL); /* 7: pollable event (POSIX) */
4796 #endif /* SIGPOLL */
4798 #ifdef SIGEMT
4799 signal(SIGEMT, SIG_DFL); /* 7: EMT instruction (Non-POSIX) */
4800 #endif /* SIGEMT */
4802 signal(SIGFPE, SIG_DFL); /* 8: floating point exception */
4803 signal(SIGBUS, SIG_DFL); /* 10: bus error */
4804 signal(SIGSEGV, SIG_DFL); /* 11: segmentation violation */
4805 signal(SIGSYS, SIG_DFL); /* 12: bad argument to system call */
4806 signal(SIGPIPE, SIG_DFL); /* 13: write on a pipe with no reader */
4807 signal(SIGALRM, SIG_DFL); /* 14: real-time timer expired */
4808 signal(SIGTERM, SIG_DFL); /* 15: software termination signal */
4809 signal(SIGCHLD, SIG_DFL); /* 20: child status has changed */
4810 signal(SIGXCPU, SIG_DFL); /* 24: exceeded CPU time limit */
4811 signal(SIGXFSZ, SIG_DFL); /* 25: exceeded file size limit */
4812 #endif /* HAVE_SIGNAL_H */
4813 #endif /* !_WIN32 */
4816 static void
4817 set_status_with_attrs(PurpleStatus *status, ...)
4819 va_list args;
4820 va_start(args, status);
4821 purple_status_set_active_with_attrs(status, TRUE, args);
4822 va_end(args);
4825 void purple_util_set_current_song(const char *title, const char *artist, const char *album)
4827 GList *list = purple_accounts_get_all();
4828 for (; list; list = list->next) {
4829 PurplePresence *presence;
4830 PurpleStatus *tune;
4831 PurpleAccount *account = list->data;
4832 if (!purple_account_get_enabled(account, purple_core_get_ui()))
4833 continue;
4835 presence = purple_account_get_presence(account);
4836 tune = purple_presence_get_status(presence, "tune");
4837 if (!tune)
4838 continue;
4839 if (title) {
4840 set_status_with_attrs(tune,
4841 PURPLE_TUNE_TITLE, title,
4842 PURPLE_TUNE_ARTIST, artist,
4843 PURPLE_TUNE_ALBUM, album,
4844 NULL);
4845 } else {
4846 purple_status_set_active(tune, FALSE);
4851 char * purple_util_format_song_info(const char *title, const char *artist, const char *album, gpointer unused)
4853 GString *string;
4854 char *esc;
4856 if (!title || !*title)
4857 return NULL;
4859 esc = g_markup_escape_text(title, -1);
4860 string = g_string_new("");
4861 g_string_append_printf(string, "%s", esc);
4862 g_free(esc);
4864 if (artist && *artist) {
4865 esc = g_markup_escape_text(artist, -1);
4866 g_string_append_printf(string, _(" - %s"), esc);
4867 g_free(esc);
4870 if (album && *album) {
4871 esc = g_markup_escape_text(album, -1);
4872 g_string_append_printf(string, _(" (%s)"), esc);
4873 g_free(esc);
4876 return g_string_free(string, FALSE);
4879 const gchar *
4880 purple_get_host_name(void)
4882 #if GLIB_CHECK_VERSION(2,8,0)
4883 return g_get_host_name();
4884 #else
4885 static char hostname[256];
4886 int ret = gethostname(hostname, sizeof(hostname));
4887 hostname[sizeof(hostname) - 1] = '\0';
4889 if (ret == -1 || hostname[0] == '\0') {
4890 purple_debug_info("purple_get_host_name: ", "could not find host name");
4891 return "localhost";
4892 } else {
4893 return hostname;
4895 #endif