Use proper directories for writing data.
[pidgin-git.git] / libpurple / util.c
blob521af79c9e54a2d9c1343f5700c3117b3279f65a
1 /* Purple is the legal property of its developers, whose names are too numerous
2 * to list here. Please refer to the COPYRIGHT file distributed with this
3 * source distribution.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
19 #include "internal.h"
21 #include "ciphers/md5hash.h"
22 #include "conversation.h"
23 #include "core.h"
24 #include "debug.h"
25 #include "glibcompat.h"
26 #include "notify.h"
27 #include "protocol.h"
28 #include "prefs.h"
29 #include "util.h"
31 #include <json-glib/json-glib.h>
33 struct _PurpleMenuAction
35 char *label;
36 PurpleCallback callback;
37 gpointer data;
38 GList *children;
39 gchar *stock_icon;
42 static char *custom_user_dir = NULL;
43 static char *user_dir = NULL;
44 static char *cache_dir = NULL;
45 static char *config_dir = NULL;
46 static char *data_dir = NULL;
48 static JsonNode *escape_js_node = NULL;
49 static JsonGenerator *escape_js_gen = NULL;
51 static void
52 move_to_xdg_base_dir(const char *purple_xdg_dir, char *subdir)
54 char *xdg_dir;
55 gboolean xdg_dir_exists;
57 xdg_dir_exists = g_file_test(purple_xdg_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
58 if (!xdg_dir_exists) {
59 gint mkdir_res;
61 mkdir_res = purple_build_dir(purple_xdg_dir, (S_IRUSR | S_IWUSR | S_IXUSR));
62 if (mkdir_res == -1) {
63 purple_debug_error("util", "Error creating xdg directory %s: %s; failed migration\n",
64 purple_xdg_dir, g_strerror(errno));
65 return;
69 xdg_dir = g_build_filename(purple_xdg_dir, subdir, NULL);
70 xdg_dir_exists = g_file_test(xdg_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
71 if (!xdg_dir_exists) {
72 char *old_dir;
73 gboolean old_dir_exists;
75 old_dir = g_build_filename(purple_user_dir(), subdir, NULL);
76 old_dir_exists = g_file_test(old_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
78 if (old_dir_exists) {
79 g_rename(old_dir, xdg_dir);
82 g_free(old_dir);
83 old_dir = NULL;
86 g_free(xdg_dir);
87 xdg_dir = NULL;
89 return;
92 /* If legacy directory for libpurple exists, move it to location following
93 * xdg base dir spec. https://developer.pidgin.im/ticket/10029
95 static void
96 migrate_to_xdg_base_dirs(void)
98 const char *legacy_purple_dir;
99 gboolean dir_exists;
101 legacy_purple_dir = purple_user_dir();
102 dir_exists = g_file_test(legacy_purple_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
103 if (dir_exists) {
104 move_to_xdg_base_dir(purple_data_dir(), "certificates");
105 move_to_xdg_base_dir(purple_cache_dir(), "icons");
106 move_to_xdg_base_dir(purple_data_dir(), "logs");
109 return;
112 PurpleMenuAction *
113 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
114 GList *children)
116 PurpleMenuAction *act = g_new0(PurpleMenuAction, 1);
117 act->label = g_strdup(label);
118 act->callback = callback;
119 act->data = data;
120 act->children = children;
121 return act;
124 void
125 purple_menu_action_free(PurpleMenuAction *act)
127 g_return_if_fail(act != NULL);
129 g_free(act->stock_icon);
130 g_free(act->label);
131 g_free(act);
134 char * purple_menu_action_get_label(const PurpleMenuAction *act)
136 g_return_val_if_fail(act != NULL, NULL);
138 return act->label;
141 PurpleCallback purple_menu_action_get_callback(const PurpleMenuAction *act)
143 g_return_val_if_fail(act != NULL, NULL);
145 return act->callback;
148 gpointer purple_menu_action_get_data(const PurpleMenuAction *act)
150 g_return_val_if_fail(act != NULL, NULL);
152 return act->data;
155 GList* purple_menu_action_get_children(const PurpleMenuAction *act)
157 g_return_val_if_fail(act != NULL, NULL);
159 return act->children;
162 void purple_menu_action_set_label(PurpleMenuAction *act, char *label)
164 g_return_if_fail(act != NULL);
166 act-> label = label;
169 void purple_menu_action_set_callback(PurpleMenuAction *act, PurpleCallback callback)
171 g_return_if_fail(act != NULL);
173 act->callback = callback;
176 void purple_menu_action_set_data(PurpleMenuAction *act, gpointer data)
178 g_return_if_fail(act != NULL);
180 act->data = data;
183 void purple_menu_action_set_children(PurpleMenuAction *act, GList *children)
185 g_return_if_fail(act != NULL);
187 act->children = children;
190 void purple_menu_action_set_stock_icon(PurpleMenuAction *act,
191 const gchar *stock)
193 g_return_if_fail(act != NULL);
195 g_free(act->stock_icon);
196 act->stock_icon = g_strdup(stock);
199 const gchar *
200 purple_menu_action_get_stock_icon(PurpleMenuAction *act)
202 return act->stock_icon;
205 void
206 purple_util_init(void)
208 escape_js_node = json_node_new(JSON_NODE_VALUE);
209 escape_js_gen = json_generator_new();
210 json_node_set_boolean(escape_js_node, FALSE);
212 migrate_to_xdg_base_dirs();
215 void
216 purple_util_uninit(void)
218 /* Free these so we don't have leaks at shutdown. */
220 g_free(custom_user_dir);
221 custom_user_dir = NULL;
223 g_free(user_dir);
224 user_dir = NULL;
226 g_free(cache_dir);
227 cache_dir = NULL;
229 g_free(config_dir);
230 config_dir = NULL;
232 g_free(data_dir);
233 data_dir = NULL;
235 json_node_free(escape_js_node);
236 escape_js_node = NULL;
238 g_object_unref(escape_js_gen);
239 escape_js_gen = NULL;
242 /**************************************************************************
243 * Base16 Functions
244 **************************************************************************/
245 gchar *
246 purple_base16_encode(const guchar *data, gsize len)
248 gsize i;
249 gchar *ascii = NULL;
251 g_return_val_if_fail(data != NULL, NULL);
252 g_return_val_if_fail(len > 0, NULL);
254 ascii = g_malloc(len * 2 + 1);
256 for (i = 0; i < len; i++)
257 g_snprintf(&ascii[i * 2], 3, "%02x", data[i] & 0xFF);
259 return ascii;
262 guchar *
263 purple_base16_decode(const char *str, gsize *ret_len)
265 gsize len, i, accumulator = 0;
266 guchar *data;
268 g_return_val_if_fail(str != NULL, NULL);
270 len = strlen(str);
272 g_return_val_if_fail(*str, 0);
273 g_return_val_if_fail(len % 2 == 0, 0);
275 data = g_malloc(len / 2);
277 for (i = 0; i < len; i++)
279 if ((i % 2) == 0)
280 accumulator = 0;
281 else
282 accumulator <<= 4;
284 if (isdigit(str[i]))
285 accumulator |= str[i] - 48;
286 else
288 switch(tolower(str[i]))
290 case 'a': accumulator |= 10; break;
291 case 'b': accumulator |= 11; break;
292 case 'c': accumulator |= 12; break;
293 case 'd': accumulator |= 13; break;
294 case 'e': accumulator |= 14; break;
295 case 'f': accumulator |= 15; break;
299 if (i % 2)
300 data[(i - 1) / 2] = accumulator;
303 if (ret_len != NULL)
304 *ret_len = len / 2;
306 return data;
309 gchar *
310 purple_base16_encode_chunked(const guchar *data, gsize len)
312 gsize i;
313 gchar *ascii = NULL;
315 g_return_val_if_fail(data != NULL, NULL);
316 g_return_val_if_fail(len > 0, NULL);
318 /* For each byte of input, we need 2 bytes for the hex representation
319 * and 1 for the colon.
320 * The final colon will be replaced by a terminating NULL
322 ascii = g_malloc(len * 3 + 1);
324 for (i = 0; i < len; i++)
325 g_snprintf(&ascii[i * 3], 4, "%02x:", data[i] & 0xFF);
327 /* Replace the final colon with NULL */
328 ascii[len * 3 - 1] = 0;
330 return ascii;
334 /**************************************************************************
335 * Base64 Functions
336 **************************************************************************/
337 static const char xdigits[] =
338 "0123456789abcdef";
340 gchar *
341 purple_base64_encode(const guchar *data, gsize len)
343 return g_base64_encode(data, len);
346 guchar *
347 purple_base64_decode(const char *str, gsize *ret_len)
350 * We want to allow ret_len to be NULL for backward compatibility,
351 * but g_base64_decode() requires a valid length variable. So if
352 * ret_len is NULL then pass in a dummy variable.
354 gsize unused;
355 return g_base64_decode(str, ret_len != NULL ? ret_len : &unused);
358 /**************************************************************************
359 * Quoted Printable Functions (see RFC 2045).
360 **************************************************************************/
361 guchar *
362 purple_quotedp_decode(const char *str, gsize *ret_len)
364 char *n, *new;
365 const char *end, *p;
367 n = new = g_malloc(strlen (str) + 1);
368 end = str + strlen(str);
370 for (p = str; p < end; p++, n++) {
371 if (*p == '=') {
372 if (p[1] == '\r' && p[2] == '\n') { /* 5.1 #5 */
373 n -= 1;
374 p += 2;
375 } else if (p[1] == '\n') { /* fuzzy case for 5.1 #5 */
376 n -= 1;
377 p += 1;
378 } else if (p[1] && p[2]) {
379 char *nibble1 = strchr(xdigits, tolower(p[1]));
380 char *nibble2 = strchr(xdigits, tolower(p[2]));
381 if (nibble1 && nibble2) { /* 5.1 #1 */
382 *n = ((nibble1 - xdigits) << 4) | (nibble2 - xdigits);
383 p += 2;
384 } else { /* This should never happen */
385 *n = *p;
387 } else { /* This should never happen */
388 *n = *p;
391 else if (*p == '_')
392 *n = ' ';
393 else
394 *n = *p;
397 *n = '\0';
399 if (ret_len != NULL)
400 *ret_len = n - new;
402 /* Resize to take less space */
403 /* new = realloc(new, n - new); */
405 return (guchar *)new;
408 /**************************************************************************
409 * MIME Functions
410 **************************************************************************/
411 char *
412 purple_mime_decode_field(const char *str)
415 * This is wing's version, partially based on revo/shx's version
416 * See RFC2047 [which apparently obsoletes RFC1342]
418 typedef enum {
419 state_start, state_equal1, state_question1,
420 state_charset, state_question2,
421 state_encoding, state_question3,
422 state_encoded_text, state_question4, state_equal2 = state_start
423 } encoded_word_state_t;
424 encoded_word_state_t state = state_start;
425 const char *cur, *mark;
426 const char *charset0 = NULL, *encoding0 = NULL, *encoded_text0 = NULL;
427 GString *new;
429 /* token can be any CHAR (supposedly ISO8859-1/ISO2022), not just ASCII */
430 #define token_char_p(c) \
431 (c != ' ' && !iscntrl(c) && !strchr("()<>@,;:\"/[]?.=", c))
433 /* But encoded-text must be ASCII; alas, isascii() may not exist */
434 #define encoded_text_char_p(c) \
435 ((c & 0x80) == 0 && c != '?' && c != ' ' && isgraph(c))
437 g_return_val_if_fail(str != NULL, NULL);
439 new = g_string_new(NULL);
441 /* Here we will be looking for encoded words and if they seem to be
442 * valid then decode them.
443 * They are of this form: =?charset?encoding?text?=
446 for (cur = str, mark = NULL; *cur; cur += 1) {
447 switch (state) {
448 case state_equal1:
449 if (*cur == '?') {
450 state = state_question1;
451 } else {
452 g_string_append_len(new, mark, cur - mark + 1);
453 state = state_start;
455 break;
456 case state_question1:
457 if (token_char_p(*cur)) {
458 charset0 = cur;
459 state = state_charset;
460 } else { /* This should never happen */
461 g_string_append_len(new, mark, cur - mark + 1);
462 state = state_start;
464 break;
465 case state_charset:
466 if (*cur == '?') {
467 state = state_question2;
468 } else if (!token_char_p(*cur)) { /* This should never happen */
469 g_string_append_len(new, mark, cur - mark + 1);
470 state = state_start;
472 break;
473 case state_question2:
474 if (token_char_p(*cur)) {
475 encoding0 = cur;
476 state = state_encoding;
477 } else { /* This should never happen */
478 g_string_append_len(new, mark, cur - mark + 1);
479 state = state_start;
481 break;
482 case state_encoding:
483 if (*cur == '?') {
484 state = state_question3;
485 } else if (!token_char_p(*cur)) { /* This should never happen */
486 g_string_append_len(new, mark, cur - mark + 1);
487 state = state_start;
489 break;
490 case state_question3:
491 if (encoded_text_char_p(*cur)) {
492 encoded_text0 = cur;
493 state = state_encoded_text;
494 } else if (*cur == '?') { /* empty string */
495 encoded_text0 = cur;
496 state = state_question4;
497 } else { /* This should never happen */
498 g_string_append_len(new, mark, cur - mark + 1);
499 state = state_start;
501 break;
502 case state_encoded_text:
503 if (*cur == '?') {
504 state = state_question4;
505 } else if (!encoded_text_char_p(*cur)) {
506 g_string_append_len(new, mark, cur - mark + 1);
507 state = state_start;
509 break;
510 case state_question4:
511 if (*cur == '=') { /* Got the whole encoded-word */
512 char *charset = g_strndup(charset0, encoding0 - charset0 - 1);
513 char *encoding = g_strndup(encoding0, encoded_text0 - encoding0 - 1);
514 char *encoded_text = g_strndup(encoded_text0, cur - encoded_text0 - 1);
515 guchar *decoded = NULL;
516 gsize dec_len;
517 if (g_ascii_strcasecmp(encoding, "Q") == 0)
518 decoded = purple_quotedp_decode(encoded_text, &dec_len);
519 else if (g_ascii_strcasecmp(encoding, "B") == 0)
520 decoded = purple_base64_decode(encoded_text, &dec_len);
521 else
522 decoded = NULL;
523 if (decoded) {
524 gsize len;
525 char *converted = g_convert((const gchar *)decoded, dec_len, "utf-8", charset, NULL, &len, NULL);
527 if (converted) {
528 g_string_append_len(new, converted, len);
529 g_free(converted);
531 g_free(decoded);
533 g_free(charset);
534 g_free(encoding);
535 g_free(encoded_text);
536 state = state_equal2; /* Restart the FSM */
537 } else { /* This should never happen */
538 g_string_append_len(new, mark, cur - mark + 1);
539 state = state_start;
541 break;
542 default:
543 if (*cur == '=') {
544 mark = cur;
545 state = state_equal1;
546 } else {
547 /* Some unencoded text. */
548 g_string_append_c(new, *cur);
550 break;
551 } /* switch */
552 } /* for */
554 if (state != state_start)
555 g_string_append_len(new, mark, cur - mark + 1);
557 return g_string_free(new, FALSE);;
561 /**************************************************************************
562 * Date/Time Functions
563 **************************************************************************/
565 const char *purple_get_tzoff_str(const struct tm *tm, gboolean iso)
567 static char buf[7];
568 long off;
569 gint8 min;
570 gint8 hrs;
571 struct tm new_tm = *tm;
573 mktime(&new_tm);
575 if (new_tm.tm_isdst < 0)
576 g_return_val_if_reached("");
578 #ifdef _WIN32
579 if ((off = wpurple_get_tz_offset()) == -1)
580 return "";
581 #elif defined(HAVE_TM_GMTOFF)
582 off = new_tm.tm_gmtoff;
583 #elif defined(HAVE_TIMEZONE)
584 tzset();
585 off = -1 * timezone;
586 #else
587 purple_debug_warning("util",
588 "there is no possibility to obtain tz offset");
589 return "";
590 #endif
592 min = (off / 60) % 60;
593 hrs = ((off / 60) - min) / 60;
595 if(iso) {
596 if (0 == off) {
597 strcpy(buf, "Z");
598 } else {
599 /* please leave the colons...they're optional for iso, but jabber
600 * wants them */
601 if(g_snprintf(buf, sizeof(buf), "%+03d:%02d", hrs, ABS(min)) > 6)
602 g_return_val_if_reached("");
604 } else {
605 if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5)
606 g_return_val_if_reached("");
609 return buf;
612 /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */
613 #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32)
614 static size_t purple_internal_strftime(char *s, size_t max, const char *format, const struct tm *tm)
616 const char *start;
617 const char *c;
618 char *fmt = NULL;
620 /* Yes, this is checked in purple_utf8_strftime(),
621 * but better safe than sorry. -- rlaager */
622 g_return_val_if_fail(format != NULL, 0);
624 /* This is fairly efficient, and it only gets
625 * executed on Windows or if the underlying
626 * system doesn't support the %z format string,
627 * for strftime() so I think it's good enough.
628 * -- rlaager */
629 for (c = start = format; *c ; c++)
631 if (*c != '%')
632 continue;
634 c++;
636 #ifndef HAVE_STRFTIME_Z_FORMAT
637 if (*c == 'z')
639 char *tmp = g_strdup_printf("%s%.*s%s",
640 fmt ? fmt : "",
641 (int)(c - start - 1),
642 start,
643 purple_get_tzoff_str(tm, FALSE));
644 g_free(fmt);
645 fmt = tmp;
646 start = c + 1;
648 #endif
649 #ifdef _WIN32
650 if (*c == 'Z')
652 char *tmp = g_strdup_printf("%s%.*s%s",
653 fmt ? fmt : "",
654 (int)(c - start - 1),
655 start,
656 wpurple_get_timezone_abbreviation(tm));
657 g_free(fmt);
658 fmt = tmp;
659 start = c + 1;
661 #endif
664 if (fmt != NULL)
666 size_t ret;
668 if (*start)
670 char *tmp = g_strconcat(fmt, start, NULL);
671 g_free(fmt);
672 fmt = tmp;
675 ret = strftime(s, max, fmt, tm);
676 g_free(fmt);
678 return ret;
681 return strftime(s, max, format, tm);
683 #else /* HAVE_STRFTIME_Z_FORMAT && !_WIN32 */
684 #define purple_internal_strftime strftime
685 #endif
687 const char *
688 purple_utf8_strftime(const char *format, const struct tm *tm)
690 static char buf[128];
691 char *locale;
692 GError *err = NULL;
693 int len;
694 char *utf8;
696 g_return_val_if_fail(format != NULL, NULL);
698 if (tm == NULL)
700 time_t now = time(NULL);
701 tm = localtime(&now);
704 locale = g_locale_from_utf8(format, -1, NULL, NULL, &err);
705 if (err != NULL)
707 purple_debug_error("util", "Format conversion failed in purple_utf8_strftime(): %s\n", err->message);
708 g_error_free(err);
709 err = NULL;
710 locale = g_strdup(format);
713 /* A return value of 0 is either an error (in
714 * which case, the contents of the buffer are
715 * undefined) or the empty string (in which
716 * case, no harm is done here). */
717 if ((len = purple_internal_strftime(buf, sizeof(buf), locale, tm)) == 0)
719 g_free(locale);
720 return "";
723 g_free(locale);
725 utf8 = g_locale_to_utf8(buf, len, NULL, NULL, &err);
726 if (err != NULL)
728 purple_debug_error("util", "Result conversion failed in purple_utf8_strftime(): %s\n", err->message);
729 g_error_free(err);
731 else
733 g_strlcpy(buf, utf8, sizeof(buf));
734 g_free(utf8);
737 return buf;
740 const char *
741 purple_date_format_short(const struct tm *tm)
743 return purple_utf8_strftime("%x", tm);
746 const char *
747 purple_date_format_long(const struct tm *tm)
750 * This string determines how some dates are displayed. The default
751 * string "%x %X" shows the date then the time. Translators can
752 * change this to "%X %x" if they want the time to be shown first,
753 * followed by the date.
755 return purple_utf8_strftime(_("%x %X"), tm);
758 const char *
759 purple_date_format_full(const struct tm *tm)
761 return purple_utf8_strftime("%c", tm);
764 const char *
765 purple_time_format(const struct tm *tm)
767 return purple_utf8_strftime("%X", tm);
770 time_t
771 purple_time_build(int year, int month, int day, int hour, int min, int sec)
773 struct tm tm;
775 tm.tm_year = year - 1900;
776 tm.tm_mon = month - 1;
777 tm.tm_mday = day;
778 tm.tm_hour = hour;
779 tm.tm_min = min;
780 tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
782 return mktime(&tm);
785 /* originally taken from GLib trunk 1-6-11 */
786 /* originally licensed as LGPL 2+ */
787 static time_t
788 mktime_utc(struct tm *tm)
790 time_t retval;
792 #ifndef HAVE_TIMEGM
793 static const gint days_before[] =
795 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
797 #endif
799 #ifndef HAVE_TIMEGM
800 if (tm->tm_mon < 0 || tm->tm_mon > 11)
801 return (time_t) -1;
803 retval = (tm->tm_year - 70) * 365;
804 retval += (tm->tm_year - 68) / 4;
805 retval += days_before[tm->tm_mon] + tm->tm_mday - 1;
807 if (tm->tm_year % 4 == 0 && tm->tm_mon < 2)
808 retval -= 1;
810 retval = ((((retval * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec;
811 #else
812 retval = timegm (tm);
813 #endif /* !HAVE_TIMEGM */
815 return retval;
818 time_t
819 purple_str_to_time(const char *timestamp, gboolean utc,
820 struct tm *tm, long *tz_off, const char **rest)
822 struct tm t;
823 const gchar *str;
824 gint year = 0;
825 long tzoff = PURPLE_NO_TZ_OFF;
826 time_t retval;
827 gboolean mktime_with_utc = FALSE;
829 if (rest != NULL)
830 *rest = NULL;
832 g_return_val_if_fail(timestamp != NULL, 0);
834 memset(&t, 0, sizeof(struct tm));
836 str = timestamp;
838 /* Strip leading whitespace */
839 while (g_ascii_isspace(*str))
840 str++;
842 if (*str == '\0') {
843 if (rest != NULL && *str != '\0')
844 *rest = str;
846 return 0;
849 if (!g_ascii_isdigit(*str) && *str != '-' && *str != '+') {
850 if (rest != NULL && *str != '\0')
851 *rest = str;
853 return 0;
856 /* 4 digit year */
857 if (sscanf(str, "%04d", &year) && year >= 1900) {
858 str += 4;
860 if (*str == '-' || *str == '/')
861 str++;
863 t.tm_year = year - 1900;
866 /* 2 digit month */
867 if (!sscanf(str, "%02d", &t.tm_mon)) {
868 if (rest != NULL && *str != '\0')
869 *rest = str;
871 return 0;
874 str += 2;
875 t.tm_mon -= 1;
877 if (*str == '-' || *str == '/')
878 str++;
880 /* 2 digit day */
881 if (!sscanf(str, "%02d", &t.tm_mday)) {
882 if (rest != NULL && *str != '\0')
883 *rest = str;
885 return 0;
888 str += 2;
890 /* Grab the year off the end if there's still stuff */
891 if (*str == '/' || *str == '-') {
892 /* But make sure we don't read the year twice */
893 if (year >= 1900) {
894 if (rest != NULL && *str != '\0')
895 *rest = str;
897 return 0;
900 str++;
902 if (!sscanf(str, "%04d", &t.tm_year)) {
903 if (rest != NULL && *str != '\0')
904 *rest = str;
906 return 0;
909 t.tm_year -= 1900;
910 } else if (*str == 'T' || *str == '.') {
911 str++;
913 /* Continue grabbing the hours/minutes/seconds */
914 if ((sscanf(str, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
915 (str += 8)) ||
916 (sscanf(str, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
917 (str += 6)))
919 gint sign, tzhrs, tzmins;
921 if (*str == '.') {
922 /* Cut off those pesky micro-seconds */
923 do {
924 str++;
925 } while (*str >= '0' && *str <= '9');
928 sign = (*str == '+') ? 1 : -1;
930 /* Process the timezone */
931 if (*str == '+' || *str == '-') {
932 str++;
934 if (((sscanf(str, "%02d:%02d", &tzhrs, &tzmins) == 2 && (str += 5)) ||
935 (sscanf(str, "%02d%02d", &tzhrs, &tzmins) == 2 && (str += 4))))
937 mktime_with_utc = TRUE;
938 tzoff = tzhrs * 60 * 60 + tzmins * 60;
939 tzoff *= sign;
941 } else if (*str == 'Z') {
942 /* 'Z' = Zulu = UTC */
943 str++;
944 mktime_with_utc = TRUE;
945 tzoff = 0;
948 if (!mktime_with_utc)
950 /* No timezone specified. */
952 if (utc) {
953 mktime_with_utc = TRUE;
954 tzoff = 0;
955 } else {
956 /* Local Time */
957 t.tm_isdst = -1;
963 if (rest != NULL && *str != '\0') {
964 /* Strip trailing whitespace */
965 while (g_ascii_isspace(*str))
966 str++;
968 if (*str != '\0')
969 *rest = str;
972 if (mktime_with_utc)
973 retval = mktime_utc(&t);
974 else
975 retval = mktime(&t);
977 if (tm != NULL)
978 *tm = t;
980 if (tzoff != PURPLE_NO_TZ_OFF)
981 retval -= tzoff;
983 if (tz_off != NULL)
984 *tz_off = tzoff;
986 return retval;
989 char *
990 purple_uts35_to_str(const char *format, size_t len, struct tm *tm)
992 GString *string;
993 guint i, count;
995 if (tm == NULL) {
996 time_t now = time(NULL);
997 tm = localtime(&now);
1000 string = g_string_sized_new(len);
1001 i = 0;
1002 while (i < len) {
1003 count = 1;
1004 while ((i + count) < len && format[i] == format[i+count])
1005 count++;
1007 switch (format[i]) {
1008 /* Era Designator */
1009 case 'G':
1010 if (count <= 3) {
1011 /* Abbreviated */
1012 } else if (count == 4) {
1013 /* Full */
1014 } else if (count >= 5) {
1015 /* Narrow */
1016 count = 5;
1018 break;
1021 /* Year */
1022 case 'y':
1023 if (count == 2) {
1024 /* Two-digits only */
1025 g_string_append(string, purple_utf8_strftime("%y", tm));
1026 } else {
1027 /* Zero-padding */
1028 char *tmp = g_strdup_printf("%%0%dY", count);
1029 g_string_append(string, purple_utf8_strftime(tmp, tm));
1030 g_free(tmp);
1032 break;
1034 /* Year (in "Week of Year" based calendars) */
1035 case 'Y':
1036 if (count == 2) {
1037 /* Two-digits only */
1038 } else {
1039 /* Zero-padding */
1041 break;
1043 /* Extended Year */
1044 case 'u':
1045 break;
1047 /* Cyclic Year Name */
1048 case 'U':
1049 if (count <= 3) {
1050 /* Abbreviated */
1051 } else if (count == 4) {
1052 /* Full */
1053 } else if (count >= 5) {
1054 /* Narrow */
1055 count = 5;
1057 break;
1060 /* Quarter */
1061 case 'Q':
1062 if (count <= 2) {
1063 /* Numerical */
1064 } else if (count == 3) {
1065 /* Abbreviation */
1066 } else if (count >= 4) {
1067 /* Full */
1068 count = 4;
1070 break;
1072 /* Stand-alone Quarter */
1073 case 'q':
1074 if (count <= 2) {
1075 /* Numerical */
1076 } else if (count == 3) {
1077 /* Abbreviation */
1078 } else if (count >= 4) {
1079 /* Full */
1080 count = 4;
1082 break;
1084 /* Month */
1085 case 'M':
1086 if (count <= 2) {
1087 /* Numerical */
1088 g_string_append(string, purple_utf8_strftime("%m", tm));
1089 } else if (count == 3) {
1090 /* Abbreviation */
1091 g_string_append(string, purple_utf8_strftime("%b", tm));
1092 } else if (count == 4) {
1093 /* Full */
1094 g_string_append(string, purple_utf8_strftime("%B", tm));
1095 } else if (count >= 5) {
1096 g_string_append_len(string, purple_utf8_strftime("%b", tm), 1);
1097 count = 5;
1099 break;
1101 /* Stand-alone Month */
1102 case 'L':
1103 if (count <= 2) {
1104 /* Numerical */
1105 g_string_append(string, purple_utf8_strftime("%m", tm));
1106 } else if (count == 3) {
1107 /* Abbreviation */
1108 g_string_append(string, purple_utf8_strftime("%b", tm));
1109 } else if (count == 4) {
1110 /* Full */
1111 g_string_append(string, purple_utf8_strftime("%B", tm));
1112 } else if (count >= 5) {
1113 g_string_append_len(string, purple_utf8_strftime("%b", tm), 1);
1114 count = 5;
1116 break;
1118 /* Ignored */
1119 case 'l':
1120 break;
1123 /* Week of Year */
1124 case 'w':
1125 g_string_append(string, purple_utf8_strftime("%W", tm));
1126 count = MIN(count, 2);
1127 break;
1129 /* Week of Month */
1130 case 'W':
1131 count = 1;
1132 break;
1135 /* Day of Month */
1136 case 'd':
1137 g_string_append(string, purple_utf8_strftime("%d", tm));
1138 count = MIN(count, 2);
1139 break;
1141 /* Day of Year */
1142 case 'D':
1143 g_string_append(string, purple_utf8_strftime("%j", tm));
1144 count = MIN(count, 3);
1145 break;
1147 /* Day of Year in Month */
1148 case 'F':
1149 count = 1;
1150 break;
1152 /* Modified Julian Day */
1153 case 'g':
1154 break;
1157 /* Day of Week */
1158 case 'E':
1159 if (count <= 3) {
1160 /* Short */
1161 g_string_append(string, purple_utf8_strftime("%a", tm));
1162 } else if (count == 4) {
1163 /* Full */
1164 g_string_append(string, purple_utf8_strftime("%A", tm));
1165 } else if (count >= 5) {
1166 /* Narrow */
1167 g_string_append_len(string, purple_utf8_strftime("%a", tm), 1);
1168 count = 5;
1170 break;
1172 /* Local Day of Week */
1173 case 'e':
1174 if (count <= 2) {
1175 /* Numeric */
1176 g_string_append(string, purple_utf8_strftime("%u", tm));
1177 } else if (count == 3) {
1178 /* Short */
1179 g_string_append(string, purple_utf8_strftime("%a", tm));
1180 } else if (count == 4) {
1181 /* Full */
1182 g_string_append(string, purple_utf8_strftime("%A", tm));
1183 } else if (count >= 5) {
1184 /* Narrow */
1185 g_string_append_len(string, purple_utf8_strftime("%a", tm), 1);
1186 count = 5;
1188 break;
1190 /* Stand-alone Local Day of Week */
1191 case 'c':
1192 if (count <= 2) {
1193 /* Numeric */
1194 g_string_append(string, purple_utf8_strftime("%u", tm));
1195 count = 1;
1196 } else if (count == 3) {
1197 /* Short */
1198 g_string_append(string, purple_utf8_strftime("%a", tm));
1199 } else if (count == 4) {
1200 /* Full */
1201 g_string_append(string, purple_utf8_strftime("%A", tm));
1202 } else if (count >= 5) {
1203 /* Narrow */
1204 g_string_append_len(string, purple_utf8_strftime("%a", tm), 1);
1205 count = 5;
1207 break;
1210 /* AM/PM */
1211 case 'a':
1212 g_string_append(string, purple_utf8_strftime("%p", tm));
1213 break;
1216 /* Hour (1-12) */
1217 case 'h':
1218 if (count == 1) {
1219 /* No padding */
1220 g_string_append(string, purple_utf8_strftime("%I", tm));
1221 } else if (count >= 2) {
1222 /* Zero-padded */
1223 g_string_append(string, purple_utf8_strftime("%I", tm));
1224 count = 2;
1226 break;
1228 /* Hour (0-23) */
1229 case 'H':
1230 if (count == 1) {
1231 /* No padding */
1232 g_string_append(string, purple_utf8_strftime("%H", tm));
1233 } else if (count >= 2) {
1234 /* Zero-padded */
1235 g_string_append(string, purple_utf8_strftime("%H", tm));
1236 count = 2;
1238 break;
1240 /* Hour (0-11) */
1241 case 'K':
1242 if (count == 1) {
1243 /* No padding */
1244 } else if (count >= 2) {
1245 /* Zero-padded */
1246 count = 2;
1248 break;
1250 /* Hour (1-24) */
1251 case 'k':
1252 if (count == 1) {
1253 /* No padding */
1254 } else if (count >= 2) {
1255 /* Zero-padded */
1256 count = 2;
1258 break;
1260 /* Hour (hHkK by locale) */
1261 case 'j':
1262 break;
1265 /* Minute */
1266 case 'm':
1267 g_string_append(string, purple_utf8_strftime("%M", tm));
1268 count = MIN(count, 2);
1269 break;
1272 /* Second */
1273 case 's':
1274 g_string_append(string, purple_utf8_strftime("%S", tm));
1275 count = MIN(count, 2);
1276 break;
1278 /* Fractional Sub-second */
1279 case 'S':
1280 break;
1282 /* Millisecond */
1283 case 'A':
1284 break;
1287 /* Time Zone (specific non-location format) */
1288 case 'z':
1289 if (count <= 3) {
1290 /* Short */
1291 } else if (count >= 4) {
1292 /* Full */
1293 count = 4;
1295 break;
1297 /* Time Zone */
1298 case 'Z':
1299 if (count <= 3) {
1300 /* RFC822 */
1301 g_string_append(string, purple_utf8_strftime("%z", tm));
1302 } else if (count == 4) {
1303 /* Localized GMT */
1304 } else if (count >= 5) {
1305 /* ISO8601 */
1306 g_string_append(string, purple_utf8_strftime("%z", tm));
1307 count = 5;
1309 break;
1311 /* Time Zone (generic non-location format) */
1312 case 'v':
1313 if (count <= 3) {
1314 /* Short */
1315 g_string_append(string, purple_utf8_strftime("%Z", tm));
1316 count = 1;
1317 } else if (count >= 4) {
1318 /* Long */
1319 g_string_append(string, purple_utf8_strftime("%Z", tm));
1320 count = 4;
1322 break;
1324 /* Time Zone */
1325 case 'V':
1326 if (count <= 3) {
1327 /* Same as z */
1328 count = 1;
1329 } else if (count >= 4) {
1330 /* Generic Location Format) */
1331 g_string_append(string, purple_utf8_strftime("%Z", tm));
1332 count = 4;
1334 break;
1337 default:
1338 g_string_append_len(string, format + i, count);
1339 break;
1342 i += count;
1345 return g_string_free(string, FALSE);
1348 /**************************************************************************
1349 * Markup Functions
1350 **************************************************************************/
1353 * This function is stolen from glib's gmarkup.c and modified to not
1354 * replace ' with &apos;
1356 static void append_escaped_text(GString *str,
1357 const gchar *text, gssize length)
1359 const gchar *p;
1360 const gchar *end;
1361 gunichar c;
1363 p = text;
1364 end = text + length;
1366 while (p != end)
1368 const gchar *next;
1369 next = g_utf8_next_char (p);
1371 switch (*p)
1373 case '&':
1374 g_string_append (str, "&amp;");
1375 break;
1377 case '<':
1378 g_string_append (str, "&lt;");
1379 break;
1381 case '>':
1382 g_string_append (str, "&gt;");
1383 break;
1385 case '"':
1386 g_string_append (str, "&quot;");
1387 break;
1389 default:
1390 c = g_utf8_get_char (p);
1391 if ((0x1 <= c && c <= 0x8) ||
1392 (0xb <= c && c <= 0xc) ||
1393 (0xe <= c && c <= 0x1f) ||
1394 (0x7f <= c && c <= 0x84) ||
1395 (0x86 <= c && c <= 0x9f))
1396 g_string_append_printf (str, "&#x%x;", c);
1397 else
1398 g_string_append_len (str, p, next - p);
1399 break;
1402 p = next;
1406 /* This function is stolen from glib's gmarkup.c */
1407 gchar *purple_markup_escape_text(const gchar *text, gssize length)
1409 GString *str;
1411 g_return_val_if_fail(text != NULL, NULL);
1413 if (length < 0)
1414 length = strlen(text);
1416 /* prealloc at least as long as original text */
1417 str = g_string_sized_new(length);
1418 append_escaped_text(str, text, length);
1420 return g_string_free(str, FALSE);
1423 const char *
1424 purple_markup_unescape_entity(const char *text, int *length)
1426 const char *pln;
1427 int len, pound;
1428 char temp[2];
1430 if (!text || *text != '&')
1431 return NULL;
1433 #define IS_ENTITY(s) (!g_ascii_strncasecmp(text, s, (len = sizeof(s) - 1)))
1435 if(IS_ENTITY("&amp;"))
1436 pln = "&";
1437 else if(IS_ENTITY("&lt;"))
1438 pln = "<";
1439 else if(IS_ENTITY("&gt;"))
1440 pln = ">";
1441 else if(IS_ENTITY("&nbsp;"))
1442 pln = " ";
1443 else if(IS_ENTITY("&copy;"))
1444 pln = "\302\251"; /* or use g_unichar_to_utf8(0xa9); */
1445 else if(IS_ENTITY("&quot;"))
1446 pln = "\"";
1447 else if(IS_ENTITY("&reg;"))
1448 pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */
1449 else if(IS_ENTITY("&apos;"))
1450 pln = "\'";
1451 else if(*(text+1) == '#' &&
1452 (sscanf(text, "&#%u%1[;]", &pound, temp) == 2 ||
1453 sscanf(text, "&#x%x%1[;]", &pound, temp) == 2) &&
1454 pound != 0) {
1455 static char buf[7];
1456 int buflen = g_unichar_to_utf8((gunichar)pound, buf);
1457 buf[buflen] = '\0';
1458 pln = buf;
1460 len = (*(text+2) == 'x' ? 3 : 2);
1461 while(isxdigit((gint) text[len])) len++;
1462 if(text[len] == ';') len++;
1464 else
1465 return NULL;
1467 if (length)
1468 *length = len;
1469 return pln;
1472 char *
1473 purple_markup_get_css_property(const gchar *style,
1474 const gchar *opt)
1476 const gchar *css_str = style;
1477 const gchar *css_value_start;
1478 const gchar *css_value_end;
1479 gchar *tmp;
1480 gchar *ret;
1482 g_return_val_if_fail(opt != NULL, NULL);
1484 if (!css_str)
1485 return NULL;
1487 /* find the CSS property */
1488 while (1)
1490 /* skip whitespace characters */
1491 while (*css_str && g_ascii_isspace(*css_str))
1492 css_str++;
1493 if (!g_ascii_isalpha(*css_str))
1494 return NULL;
1495 if (g_ascii_strncasecmp(css_str, opt, strlen(opt)))
1497 /* go to next css property positioned after the next ';' */
1498 while (*css_str && *css_str != '"' && *css_str != ';')
1499 css_str++;
1500 if(*css_str != ';')
1501 return NULL;
1502 css_str++;
1504 else
1505 break;
1508 /* find the CSS value position in the string */
1509 css_str += strlen(opt);
1510 while (*css_str && g_ascii_isspace(*css_str))
1511 css_str++;
1512 if (*css_str != ':')
1513 return NULL;
1514 css_str++;
1515 while (*css_str && g_ascii_isspace(*css_str))
1516 css_str++;
1517 if (*css_str == '\0' || *css_str == '"' || *css_str == ';')
1518 return NULL;
1520 /* mark the CSS value */
1521 css_value_start = css_str;
1522 while (*css_str && *css_str != '"' && *css_str != ';')
1523 css_str++;
1524 css_value_end = css_str - 1;
1526 /* Removes trailing whitespace */
1527 while (css_value_end > css_value_start && g_ascii_isspace(*css_value_end))
1528 css_value_end--;
1530 tmp = g_strndup(css_value_start, css_value_end - css_value_start + 1);
1531 ret = purple_unescape_html(tmp);
1532 g_free(tmp);
1534 return ret;
1537 gboolean purple_markup_is_rtl(const char *html)
1539 GData *attributes;
1540 const gchar *start, *end;
1541 gboolean res = FALSE;
1543 if (purple_markup_find_tag("span", html, &start, &end, &attributes))
1545 /* tmp is a member of attributes and is free with g_datalist_clear call */
1546 const char *tmp = g_datalist_get_data(&attributes, "dir");
1547 if (tmp && !g_ascii_strcasecmp(tmp, "RTL"))
1548 res = TRUE;
1549 if (!res)
1551 tmp = g_datalist_get_data(&attributes, "style");
1552 if (tmp)
1554 char *tmp2 = purple_markup_get_css_property(tmp, "direction");
1555 if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL"))
1556 res = TRUE;
1557 g_free(tmp2);
1561 g_datalist_clear(&attributes);
1563 return res;
1566 gboolean
1567 purple_markup_find_tag(const char *needle, const char *haystack,
1568 const char **start, const char **end, GData **attributes)
1570 GData *attribs;
1571 const char *cur = haystack;
1572 char *name = NULL;
1573 gboolean found = FALSE;
1574 gboolean in_tag = FALSE;
1575 gboolean in_attr = FALSE;
1576 const char *in_quotes = NULL;
1577 size_t needlelen;
1579 g_return_val_if_fail( needle != NULL, FALSE);
1580 g_return_val_if_fail( *needle != '\0', FALSE);
1581 g_return_val_if_fail( haystack != NULL, FALSE);
1582 g_return_val_if_fail( start != NULL, FALSE);
1583 g_return_val_if_fail( end != NULL, FALSE);
1584 g_return_val_if_fail(attributes != NULL, FALSE);
1586 needlelen = strlen(needle);
1587 g_datalist_init(&attribs);
1589 while (*cur && !found) {
1590 if (in_tag) {
1591 if (in_quotes) {
1592 const char *close = cur;
1594 while (*close && *close != *in_quotes)
1595 close++;
1597 /* if we got the close quote, store the value and carry on from *
1598 * after it. if we ran to the end of the string, point to the NULL *
1599 * and we're outta here */
1600 if (*close) {
1601 /* only store a value if we have an attribute name */
1602 if (name) {
1603 size_t len = close - cur;
1604 char *val = g_strndup(cur, len);
1606 g_datalist_set_data_full(&attribs, name, val, g_free);
1607 g_free(name);
1608 name = NULL;
1611 in_quotes = NULL;
1612 cur = close + 1;
1613 } else {
1614 cur = close;
1616 } else if (in_attr) {
1617 const char *close = cur;
1619 while (*close && *close != '>' && *close != '"' &&
1620 *close != '\'' && *close != ' ' && *close != '=')
1621 close++;
1623 /* if we got the equals, store the name of the attribute. if we got
1624 * the quote, save the attribute and go straight to quote mode.
1625 * otherwise the tag closed or we reached the end of the string,
1626 * so we can get outta here */
1627 switch (*close) {
1628 case '"':
1629 case '\'':
1630 in_quotes = close;
1631 /* fall through */
1632 case '=':
1634 size_t len = close - cur;
1636 /* don't store a blank attribute name */
1637 if (len) {
1638 g_free(name);
1639 name = g_ascii_strdown(cur, len);
1642 in_attr = FALSE;
1643 cur = close + 1;
1645 break;
1646 case ' ':
1647 case '>':
1648 in_attr = FALSE;
1649 /* fall through */
1650 default:
1651 cur = close;
1652 break;
1654 } else {
1655 switch (*cur) {
1656 case ' ':
1657 /* swallow extra spaces inside tag */
1658 while (*cur && *cur == ' ') cur++;
1659 in_attr = TRUE;
1660 break;
1661 case '>':
1662 found = TRUE;
1663 *end = cur;
1664 break;
1665 case '"':
1666 case '\'':
1667 in_quotes = cur;
1668 /* fall through */
1669 default:
1670 cur++;
1671 break;
1674 } else {
1675 /* if we hit a < followed by the name of our tag... */
1676 if (*cur == '<' && !g_ascii_strncasecmp(cur + 1, needle, needlelen)) {
1677 *start = cur;
1678 cur = cur + needlelen + 1;
1680 /* if we're pointing at a space or a >, we found the right tag. if *
1681 * we're not, we've found a longer tag, so we need to skip to the *
1682 * >, but not being distracted by >s inside quotes. */
1683 if (*cur == ' ' || *cur == '>') {
1684 in_tag = TRUE;
1685 } else {
1686 while (*cur && *cur != '"' && *cur != '\'' && *cur != '>') {
1687 if (*cur == '"') {
1688 cur++;
1689 while (*cur && *cur != '"')
1690 cur++;
1691 } else if (*cur == '\'') {
1692 cur++;
1693 while (*cur && *cur != '\'')
1694 cur++;
1695 } else {
1696 cur++;
1700 } else {
1701 cur++;
1706 /* clean up any attribute name from a premature termination */
1707 g_free(name);
1709 if (found) {
1710 *attributes = attribs;
1711 } else {
1712 *start = NULL;
1713 *end = NULL;
1714 *attributes = NULL;
1717 return found;
1720 gboolean
1721 purple_markup_extract_info_field(const char *str, int len, PurpleNotifyUserInfo *user_info,
1722 const char *start_token, int skip,
1723 const char *end_token, char check_value,
1724 const char *no_value_token,
1725 const char *display_name, gboolean is_link,
1726 const char *link_prefix,
1727 PurpleInfoFieldFormatCallback format_cb)
1729 const char *p, *q;
1731 g_return_val_if_fail(str != NULL, FALSE);
1732 g_return_val_if_fail(user_info != NULL, FALSE);
1733 g_return_val_if_fail(start_token != NULL, FALSE);
1734 g_return_val_if_fail(end_token != NULL, FALSE);
1735 g_return_val_if_fail(display_name != NULL, FALSE);
1737 p = strstr(str, start_token);
1739 if (p == NULL)
1740 return FALSE;
1742 p += strlen(start_token) + skip;
1744 if (p >= str + len)
1745 return FALSE;
1747 if (check_value != '\0' && *p == check_value)
1748 return FALSE;
1750 q = strstr(p, end_token);
1752 /* Trim leading blanks */
1753 while (*p != '\n' && g_ascii_isspace(*p)) {
1754 p += 1;
1757 /* Trim trailing blanks */
1758 while (q > p && g_ascii_isspace(*(q - 1))) {
1759 q -= 1;
1762 /* Don't bother with null strings */
1763 if (p == q)
1764 return FALSE;
1766 if (q != NULL && (!no_value_token ||
1767 (no_value_token && strncmp(p, no_value_token,
1768 strlen(no_value_token)))))
1770 GString *dest = g_string_new("");
1772 if (is_link)
1774 g_string_append(dest, "<a href=\"");
1776 if (link_prefix)
1777 g_string_append(dest, link_prefix);
1779 if (format_cb != NULL)
1781 char *reformatted = format_cb(p, q - p);
1782 g_string_append(dest, reformatted);
1783 g_free(reformatted);
1785 else
1786 g_string_append_len(dest, p, q - p);
1787 g_string_append(dest, "\">");
1789 if (link_prefix)
1790 g_string_append(dest, link_prefix);
1792 g_string_append_len(dest, p, q - p);
1793 g_string_append(dest, "</a>");
1795 else
1797 if (format_cb != NULL)
1799 char *reformatted = format_cb(p, q - p);
1800 g_string_append(dest, reformatted);
1801 g_free(reformatted);
1803 else
1804 g_string_append_len(dest, p, q - p);
1807 purple_notify_user_info_add_pair_html(user_info, display_name, dest->str);
1808 g_string_free(dest, TRUE);
1810 return TRUE;
1813 return FALSE;
1816 struct purple_parse_tag {
1817 char *src_tag;
1818 char *dest_tag;
1819 gboolean ignore;
1822 /* NOTE: Do not put `do {} while(0)` around this macro (as this is the method
1823 recommended in the GCC docs). It contains 'continue's that should
1824 affect the while-loop in purple_markup_html_to_xhtml and doing the
1825 above would break that.
1826 Also, remember to put braces in constructs that require them for
1827 multiple statements when using this macro. */
1828 #define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \
1829 const char *o = c + strlen("<" x); \
1830 const char *p = NULL, *q = NULL, *r = NULL; \
1831 /* o = iterating over full tag \
1832 * p = > (end of tag) \
1833 * q = start of quoted bit \
1834 * r = < inside tag \
1835 */ \
1836 GString *innards = g_string_new(""); \
1837 while(o && *o) { \
1838 if(!q && (*o == '\"' || *o == '\'') ) { \
1839 q = o; \
1840 } else if(q) { \
1841 if(*o == *q) { /* end of quoted bit */ \
1842 char *unescaped = g_strndup(q+1, o-q-1); \
1843 char *escaped = g_markup_escape_text(unescaped, -1); \
1844 g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \
1845 g_free(unescaped); \
1846 g_free(escaped); \
1847 q = NULL; \
1848 } else if(*c == '\\') { \
1849 o++; \
1851 } else if(*o == '<') { \
1852 r = o; \
1853 } else if(*o == '>') { \
1854 p = o; \
1855 break; \
1856 } else { \
1857 innards = g_string_append_c(innards, *o); \
1859 o++; \
1861 if(p && !r) { /* got an end of tag and no other < earlier */\
1862 if(*(p-1) != '/') { \
1863 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1864 pt->src_tag = x; \
1865 pt->dest_tag = y; \
1866 tags = g_list_prepend(tags, pt); \
1868 if(xhtml) { \
1869 xhtml = g_string_append(xhtml, "<" y); \
1870 xhtml = g_string_append(xhtml, innards->str); \
1871 xhtml = g_string_append_c(xhtml, '>'); \
1873 c = p + 1; \
1874 } else { /* got end of tag with earlier < *or* didn't get anything */ \
1875 if(xhtml) \
1876 xhtml = g_string_append(xhtml, "&lt;"); \
1877 if(plain) \
1878 plain = g_string_append_c(plain, '<'); \
1879 c++; \
1881 g_string_free(innards, TRUE); \
1882 continue; \
1884 if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \
1885 (*(c+strlen("<" x)) == '>' || \
1886 !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \
1887 if(xhtml) \
1888 xhtml = g_string_append(xhtml, "<" y); \
1889 c += strlen("<" x); \
1890 if(*c != '/') { \
1891 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1892 pt->src_tag = x; \
1893 pt->dest_tag = y; \
1894 tags = g_list_prepend(tags, pt); \
1895 if(xhtml) \
1896 xhtml = g_string_append_c(xhtml, '>'); \
1897 } else { \
1898 if(xhtml) \
1899 xhtml = g_string_append(xhtml, "/>");\
1901 c = strchr(c, '>') + 1; \
1902 continue; \
1904 /* Don't forget to check the note above for ALLOW_TAG_ALT. */
1905 #define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x)
1906 void
1907 purple_markup_html_to_xhtml(const char *html, char **xhtml_out,
1908 char **plain_out)
1910 GString *xhtml = NULL;
1911 GString *plain = NULL;
1912 GString *url = NULL;
1913 GString *cdata = NULL;
1914 GList *tags = NULL, *tag;
1915 const char *c = html;
1916 char quote = '\0';
1918 #define CHECK_QUOTE(ptr) if (*(ptr) == '\'' || *(ptr) == '\"') \
1919 quote = *(ptr++); \
1920 else \
1921 quote = '\0';
1923 #define VALID_CHAR(ptr) (*(ptr) && *(ptr) != quote && (quote || (*(ptr) != ' ' && *(ptr) != '>')))
1925 g_return_if_fail(xhtml_out != NULL || plain_out != NULL);
1927 if(xhtml_out)
1928 xhtml = g_string_new("");
1929 if(plain_out)
1930 plain = g_string_new("");
1932 while(c && *c) {
1933 if(*c == '<') {
1934 if(*(c+1) == '/') { /* closing tag */
1935 tag = tags;
1936 while(tag) {
1937 struct purple_parse_tag *pt = tag->data;
1938 if(!g_ascii_strncasecmp((c+2), pt->src_tag, strlen(pt->src_tag)) && *(c+strlen(pt->src_tag)+2) == '>') {
1939 c += strlen(pt->src_tag) + 3;
1940 break;
1942 tag = tag->next;
1944 if(tag) {
1945 while(tags) {
1946 struct purple_parse_tag *pt = tags->data;
1947 if(xhtml && !pt->ignore)
1948 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
1949 if(plain && purple_strequal(pt->src_tag, "a")) {
1950 /* if this is a link, we have to add the url to the plaintext, too */
1951 if (cdata && url &&
1952 (!g_string_equal(cdata, url) && (g_ascii_strncasecmp(url->str, "mailto:", 7) != 0 ||
1953 g_utf8_collate(url->str + 7, cdata->str) != 0)))
1954 g_string_append_printf(plain, " <%s>", g_strstrip(purple_unescape_html(url->str)));
1955 if (cdata) {
1956 g_string_free(cdata, TRUE);
1957 cdata = NULL;
1961 if(tags == tag)
1962 break;
1963 tags = g_list_remove(tags, pt);
1964 g_free(pt);
1966 g_free(tag->data);
1967 tags = g_list_remove(tags, tag->data);
1968 } else {
1969 /* a closing tag we weren't expecting...
1970 * we'll let it slide, if it's really a tag...if it's
1971 * just a </ we'll escape it properly */
1972 const char *end = c+2;
1973 while(*end && g_ascii_isalpha(*end))
1974 end++;
1975 if(*end == '>') {
1976 c = end+1;
1977 } else {
1978 if(xhtml)
1979 xhtml = g_string_append(xhtml, "&lt;");
1980 if(plain)
1981 plain = g_string_append_c(plain, '<');
1982 c++;
1985 } else { /* opening tag */
1986 ALLOW_TAG("blockquote");
1987 ALLOW_TAG("cite");
1988 ALLOW_TAG("div");
1989 ALLOW_TAG("em");
1990 ALLOW_TAG("h1");
1991 ALLOW_TAG("h2");
1992 ALLOW_TAG("h3");
1993 ALLOW_TAG("h4");
1994 ALLOW_TAG("h5");
1995 ALLOW_TAG("h6");
1996 /* we only allow html to start the message */
1997 if(c == html) {
1998 ALLOW_TAG("html");
2000 ALLOW_TAG_ALT("i", "em");
2001 ALLOW_TAG_ALT("italic", "em");
2002 ALLOW_TAG("li");
2003 ALLOW_TAG("ol");
2004 ALLOW_TAG("p");
2005 ALLOW_TAG("pre");
2006 ALLOW_TAG("q");
2007 ALLOW_TAG("span");
2008 ALLOW_TAG("ul");
2011 /* we skip <HR> because it's not legal in XHTML-IM. However,
2012 * we still want to send something sensible, so we put a
2013 * linebreak in its place. <BR> also needs special handling
2014 * because putting a </BR> to close it would just be dumb. */
2015 if((!g_ascii_strncasecmp(c, "<br", 3)
2016 || !g_ascii_strncasecmp(c, "<hr", 3))
2017 && (*(c+3) == '>' ||
2018 !g_ascii_strncasecmp(c+3, "/>", 2) ||
2019 !g_ascii_strncasecmp(c+3, " />", 3))) {
2020 c = strchr(c, '>') + 1;
2021 if(xhtml)
2022 xhtml = g_string_append(xhtml, "<br/>");
2023 if(plain && *c != '\n')
2024 plain = g_string_append_c(plain, '\n');
2025 continue;
2027 if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c, "<strong>", strlen("<strong>"))) {
2028 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2029 if (*(c+2) == '>')
2030 pt->src_tag = "b";
2031 else if (*(c+2) == 'o')
2032 pt->src_tag = "bold";
2033 else
2034 pt->src_tag = "strong";
2035 pt->dest_tag = "span";
2036 tags = g_list_prepend(tags, pt);
2037 c = strchr(c, '>') + 1;
2038 if(xhtml)
2039 xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>");
2040 continue;
2042 if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) {
2043 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2044 pt->src_tag = *(c+2) == '>' ? "u" : "underline";
2045 pt->dest_tag = "span";
2046 tags = g_list_prepend(tags, pt);
2047 c = strchr(c, '>') + 1;
2048 if (xhtml)
2049 xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>");
2050 continue;
2052 if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) {
2053 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2054 pt->src_tag = *(c+2) == '>' ? "s" : "strike";
2055 pt->dest_tag = "span";
2056 tags = g_list_prepend(tags, pt);
2057 c = strchr(c, '>') + 1;
2058 if(xhtml)
2059 xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>");
2060 continue;
2062 if(!g_ascii_strncasecmp(c, "<sub>", 5)) {
2063 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2064 pt->src_tag = "sub";
2065 pt->dest_tag = "span";
2066 tags = g_list_prepend(tags, pt);
2067 c = strchr(c, '>') + 1;
2068 if(xhtml)
2069 xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>");
2070 continue;
2072 if(!g_ascii_strncasecmp(c, "<sup>", 5)) {
2073 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2074 pt->src_tag = "sup";
2075 pt->dest_tag = "span";
2076 tags = g_list_prepend(tags, pt);
2077 c = strchr(c, '>') + 1;
2078 if(xhtml)
2079 xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>");
2080 continue;
2082 if (!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) {
2083 const char *p = c + 4;
2084 GString *src = NULL, *alt = NULL;
2085 #define ESCAPE(from, to) \
2086 CHECK_QUOTE(from); \
2087 while (VALID_CHAR(from)) { \
2088 int len; \
2089 if ((*from == '&') && (purple_markup_unescape_entity(from, &len) == NULL)) \
2090 to = g_string_append(to, "&amp;"); \
2091 else if (*from == '\'') \
2092 to = g_string_append(to, "&apos;"); \
2093 else \
2094 to = g_string_append_c(to, *from); \
2095 from++; \
2098 while (*p && *p != '>') {
2099 if (!g_ascii_strncasecmp(p, "src=", 4)) {
2100 const char *q = p + 4;
2101 if (src)
2102 g_string_free(src, TRUE);
2103 src = g_string_new("");
2104 ESCAPE(q, src);
2105 p = q;
2106 } else if (!g_ascii_strncasecmp(p, "alt=", 4)) {
2107 const char *q = p + 4;
2108 if (alt)
2109 g_string_free(alt, TRUE);
2110 alt = g_string_new("");
2111 ESCAPE(q, alt);
2112 p = q;
2113 } else {
2114 p++;
2117 #undef ESCAPE
2118 if ((c = strchr(p, '>')) != NULL)
2119 c++;
2120 else
2121 c = p;
2122 /* src and alt are required! */
2123 if(src && xhtml)
2124 g_string_append_printf(xhtml, "<img src='%s' alt='%s' />", g_strstrip(src->str), alt ? alt->str : "");
2125 if(alt) {
2126 if(plain)
2127 plain = g_string_append(plain, purple_unescape_html(alt->str));
2128 if(!src && xhtml)
2129 xhtml = g_string_append(xhtml, alt->str);
2130 g_string_free(alt, TRUE);
2132 g_string_free(src, TRUE);
2133 continue;
2135 if (!g_ascii_strncasecmp(c, "<a", 2) && (*(c+2) == '>' || *(c+2) == ' ')) {
2136 const char *p = c + 2;
2137 struct purple_parse_tag *pt;
2138 while (*p && *p != '>') {
2139 if (!g_ascii_strncasecmp(p, "href=", 5)) {
2140 const char *q = p + 5;
2141 if (url)
2142 g_string_free(url, TRUE);
2143 url = g_string_new("");
2144 if (cdata)
2145 g_string_free(cdata, TRUE);
2146 cdata = g_string_new("");
2147 CHECK_QUOTE(q);
2148 while (VALID_CHAR(q)) {
2149 int len;
2150 if ((*q == '&') && (purple_markup_unescape_entity(q, &len) == NULL))
2151 url = g_string_append(url, "&amp;");
2152 else if (*q == '"')
2153 url = g_string_append(url, "&quot;");
2154 else
2155 url = g_string_append_c(url, *q);
2156 q++;
2158 p = q;
2159 } else {
2160 p++;
2163 if ((c = strchr(p, '>')) != NULL)
2164 c++;
2165 else
2166 c = p;
2167 pt = g_new0(struct purple_parse_tag, 1);
2168 pt->src_tag = "a";
2169 pt->dest_tag = "a";
2170 tags = g_list_prepend(tags, pt);
2171 if(xhtml)
2172 g_string_append_printf(xhtml, "<a href=\"%s\">", url ? g_strstrip(url->str) : "");
2173 continue;
2175 #define ESCAPE(from, to) \
2176 CHECK_QUOTE(from); \
2177 while (VALID_CHAR(from)) { \
2178 int len; \
2179 if ((*from == '&') && (purple_markup_unescape_entity(from, &len) == NULL)) \
2180 to = g_string_append(to, "&amp;"); \
2181 else if (*from == '\'') \
2182 to = g_string_append_c(to, '\"'); \
2183 else \
2184 to = g_string_append_c(to, *from); \
2185 from++; \
2187 if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) {
2188 const char *p = c + 5;
2189 GString *style = g_string_new("");
2190 struct purple_parse_tag *pt;
2191 while (*p && *p != '>') {
2192 if (!g_ascii_strncasecmp(p, "back=", 5)) {
2193 const char *q = p + 5;
2194 GString *color = g_string_new("");
2195 ESCAPE(q, color);
2196 g_string_append_printf(style, "background: %s; ", color->str);
2197 g_string_free(color, TRUE);
2198 p = q;
2199 } else if (!g_ascii_strncasecmp(p, "color=", 6)) {
2200 const char *q = p + 6;
2201 GString *color = g_string_new("");
2202 ESCAPE(q, color);
2203 g_string_append_printf(style, "color: %s; ", color->str);
2204 g_string_free(color, TRUE);
2205 p = q;
2206 } else if (!g_ascii_strncasecmp(p, "face=", 5)) {
2207 const char *q = p + 5;
2208 GString *face = g_string_new("");
2209 ESCAPE(q, face);
2210 g_string_append_printf(style, "font-family: %s; ", g_strstrip(face->str));
2211 g_string_free(face, TRUE);
2212 p = q;
2213 } else if (!g_ascii_strncasecmp(p, "size=", 5)) {
2214 const char *q = p + 5;
2215 int sz;
2216 const char *size = "medium";
2217 CHECK_QUOTE(q);
2218 sz = atoi(q);
2219 switch (sz)
2221 case 1:
2222 size = "xx-small";
2223 break;
2224 case 2:
2225 size = "small";
2226 break;
2227 case 3:
2228 size = "medium";
2229 break;
2230 case 4:
2231 size = "large";
2232 break;
2233 case 5:
2234 size = "x-large";
2235 break;
2236 case 6:
2237 case 7:
2238 size = "xx-large";
2239 break;
2240 default:
2241 break;
2243 g_string_append_printf(style, "font-size: %s; ", size);
2244 p = q;
2245 } else {
2246 p++;
2249 if ((c = strchr(p, '>')) != NULL)
2250 c++;
2251 else
2252 c = p;
2253 pt = g_new0(struct purple_parse_tag, 1);
2254 pt->src_tag = "font";
2255 pt->dest_tag = "span";
2256 tags = g_list_prepend(tags, pt);
2257 if(style->len && xhtml)
2258 g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str));
2259 else
2260 pt->ignore = TRUE;
2261 g_string_free(style, TRUE);
2262 continue;
2264 #undef ESCAPE
2265 if (!g_ascii_strncasecmp(c, "<body ", 6)) {
2266 const char *p = c + 6;
2267 gboolean did_something = FALSE;
2268 while (*p && *p != '>') {
2269 if (!g_ascii_strncasecmp(p, "bgcolor=", 8)) {
2270 const char *q = p + 8;
2271 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2272 GString *color = g_string_new("");
2273 CHECK_QUOTE(q);
2274 while (VALID_CHAR(q)) {
2275 color = g_string_append_c(color, *q);
2276 q++;
2278 if (xhtml)
2279 g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str));
2280 g_string_free(color, TRUE);
2281 if ((c = strchr(p, '>')) != NULL)
2282 c++;
2283 else
2284 c = p;
2285 pt->src_tag = "body";
2286 pt->dest_tag = "span";
2287 tags = g_list_prepend(tags, pt);
2288 did_something = TRUE;
2289 break;
2291 p++;
2293 if (did_something) continue;
2295 /* this has to come after the special case for bgcolor */
2296 ALLOW_TAG("body");
2297 if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) {
2298 char *p = strstr(c + strlen("<!--"), "-->");
2299 if(p) {
2300 if(xhtml)
2301 xhtml = g_string_append(xhtml, "<!--");
2302 c += strlen("<!--");
2303 continue;
2307 if(xhtml)
2308 xhtml = g_string_append(xhtml, "&lt;");
2309 if(plain)
2310 plain = g_string_append_c(plain, '<');
2311 c++;
2313 } else if(*c == '&') {
2314 char buf[7];
2315 const char *pln;
2316 int len;
2318 if ((pln = purple_markup_unescape_entity(c, &len)) == NULL) {
2319 len = 1;
2320 g_snprintf(buf, sizeof(buf), "%c", *c);
2321 pln = buf;
2323 if(xhtml)
2324 xhtml = g_string_append_len(xhtml, c, len);
2325 if(plain)
2326 plain = g_string_append(plain, pln);
2327 if(cdata)
2328 cdata = g_string_append_len(cdata, c, len);
2329 c += len;
2330 } else {
2331 if(xhtml)
2332 xhtml = g_string_append_c(xhtml, *c);
2333 if(plain)
2334 plain = g_string_append_c(plain, *c);
2335 if(cdata)
2336 cdata = g_string_append_c(cdata, *c);
2337 c++;
2340 if(xhtml) {
2341 for (tag = tags; tag ; tag = tag->next) {
2342 struct purple_parse_tag *pt = tag->data;
2343 if(!pt->ignore)
2344 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
2347 g_list_free(tags);
2348 if(xhtml_out)
2349 *xhtml_out = g_string_free(xhtml, FALSE);
2350 if(plain_out)
2351 *plain_out = g_string_free(plain, FALSE);
2352 if(url)
2353 g_string_free(url, TRUE);
2354 if (cdata)
2355 g_string_free(cdata, TRUE);
2356 #undef CHECK_QUOTE
2357 #undef VALID_CHAR
2360 /* The following are probably reasonable changes:
2361 * - \n should be converted to a normal space
2362 * - in addition to <br>, <p> and <div> etc. should also be converted into \n
2363 * - We want to turn </td>#whitespace<td> sequences into a single tab
2364 * - We want to turn <td> into a single tab (for msn profile "parsing")
2365 * - We want to turn </tr>#whitespace<tr> sequences into a single \n
2366 * - <script>...</script> and <style>...</style> should be completely removed
2369 char *
2370 purple_markup_strip_html(const char *str)
2372 int i, j, k, entlen;
2373 gboolean visible = TRUE;
2374 gboolean closing_td_p = FALSE;
2375 gchar *str2;
2376 const gchar *cdata_close_tag = NULL, *ent;
2377 gchar *href = NULL;
2378 int href_st = 0;
2380 if(!str)
2381 return NULL;
2383 str2 = g_strdup(str);
2385 for (i = 0, j = 0; str2[i]; i++)
2387 if (str2[i] == '<')
2389 if (cdata_close_tag)
2391 /* Note: Don't even assume any other tag is a tag in CDATA */
2392 if (g_ascii_strncasecmp(str2 + i, cdata_close_tag,
2393 strlen(cdata_close_tag)) == 0)
2395 i += strlen(cdata_close_tag) - 1;
2396 cdata_close_tag = NULL;
2398 continue;
2400 else if (g_ascii_strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p)
2402 str2[j++] = '\t';
2403 visible = TRUE;
2405 else if (g_ascii_strncasecmp(str2 + i, "</td>", 5) == 0)
2407 closing_td_p = TRUE;
2408 visible = FALSE;
2410 else
2412 closing_td_p = FALSE;
2413 visible = TRUE;
2416 k = i + 1;
2418 if(g_ascii_isspace(str2[k]))
2419 visible = TRUE;
2420 else if (str2[k])
2422 /* Scan until we end the tag either implicitly (closed start
2423 * tag) or explicitly, using a sloppy method (i.e., < or >
2424 * inside quoted attributes will screw us up)
2426 while (str2[k] && str2[k] != '<' && str2[k] != '>')
2428 k++;
2431 /* If we've got an <a> tag with an href, save the address
2432 * to print later. */
2433 if (g_ascii_strncasecmp(str2 + i, "<a", 2) == 0 &&
2434 g_ascii_isspace(str2[i+2]))
2436 int st; /* start of href, inclusive [ */
2437 int end; /* end of href, exclusive ) */
2438 char delim = ' ';
2439 /* Find start of href */
2440 for (st = i + 3; st < k; st++)
2442 if (g_ascii_strncasecmp(str2+st, "href=", 5) == 0)
2444 st += 5;
2445 if (str2[st] == '"' || str2[st] == '\'')
2447 delim = str2[st];
2448 st++;
2450 break;
2453 /* find end of address */
2454 for (end = st; end < k && str2[end] != delim; end++)
2456 /* All the work is done in the loop construct above. */
2459 /* If there's an address, save it. If there was
2460 * already one saved, kill it. */
2461 if (st < k)
2463 char *tmp;
2464 g_free(href);
2465 tmp = g_strndup(str2 + st, end - st);
2466 href = purple_unescape_html(tmp);
2467 g_free(tmp);
2468 href_st = j;
2472 /* Replace </a> with an ascii representation of the
2473 * address the link was pointing to. */
2474 else if (href != NULL && g_ascii_strncasecmp(str2 + i, "</a>", 4) == 0)
2476 size_t hrlen = strlen(href);
2478 /* Only insert the href if it's different from the CDATA. */
2479 if ((hrlen != (gsize)(j - href_st) ||
2480 strncmp(str2 + href_st, href, hrlen)) &&
2481 (hrlen != (gsize)(j - href_st + 7) || /* 7 == strlen("http://") */
2482 strncmp(str2 + href_st, href + 7, hrlen - 7)))
2484 str2[j++] = ' ';
2485 str2[j++] = '(';
2486 g_memmove(str2 + j, href, hrlen);
2487 j += hrlen;
2488 str2[j++] = ')';
2489 g_free(href);
2490 href = NULL;
2494 /* Check for tags which should be mapped to newline (but ignore some of
2495 * the tags at the beginning of the text) */
2496 else if ((j && (g_ascii_strncasecmp(str2 + i, "<p>", 3) == 0
2497 || g_ascii_strncasecmp(str2 + i, "<tr", 3) == 0
2498 || g_ascii_strncasecmp(str2 + i, "<hr", 3) == 0
2499 || g_ascii_strncasecmp(str2 + i, "<li", 3) == 0
2500 || g_ascii_strncasecmp(str2 + i, "<div", 4) == 0))
2501 || g_ascii_strncasecmp(str2 + i, "<br", 3) == 0
2502 || g_ascii_strncasecmp(str2 + i, "</table>", 8) == 0)
2504 str2[j++] = '\n';
2506 /* Check for tags which begin CDATA and need to be closed */
2507 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */
2508 else if (g_ascii_strncasecmp(str2 + i, "<option", 7) == 0)
2510 /* FIXME: We should not do this if the OPTION is SELECT'd */
2511 cdata_close_tag = "</option>";
2513 #endif
2514 else if (g_ascii_strncasecmp(str2 + i, "<script", 7) == 0)
2516 cdata_close_tag = "</script>";
2518 else if (g_ascii_strncasecmp(str2 + i, "<style", 6) == 0)
2520 cdata_close_tag = "</style>";
2522 /* Update the index and continue checking after the tag */
2523 i = (str2[k] == '<' || str2[k] == '\0')? k - 1: k;
2524 continue;
2527 else if (cdata_close_tag)
2529 continue;
2531 else if (!g_ascii_isspace(str2[i]))
2533 visible = TRUE;
2536 if (str2[i] == '&' && (ent = purple_markup_unescape_entity(str2 + i, &entlen)) != NULL)
2538 while (*ent)
2539 str2[j++] = *ent++;
2540 i += entlen - 1;
2541 continue;
2544 if (visible)
2545 str2[j++] = g_ascii_isspace(str2[i])? ' ': str2[i];
2548 g_free(href);
2550 str2[j] = '\0';
2552 return str2;
2555 static gboolean
2556 badchar(char c)
2558 switch (c) {
2559 case ' ':
2560 case ',':
2561 case '\0':
2562 case '\n':
2563 case '\r':
2564 case '<':
2565 case '>':
2566 case '"':
2567 return TRUE;
2568 default:
2569 return FALSE;
2573 static gboolean
2574 badentity(const char *c)
2576 if (!g_ascii_strncasecmp(c, "&lt;", 4) ||
2577 !g_ascii_strncasecmp(c, "&gt;", 4) ||
2578 !g_ascii_strncasecmp(c, "&quot;", 6)) {
2579 return TRUE;
2581 return FALSE;
2584 static const char *
2585 process_link(GString *ret,
2586 const char *start, const char *c,
2587 int matchlen,
2588 const char *urlprefix,
2589 int inside_paren)
2591 char *url_buf, *tmpurlbuf;
2592 const char *t;
2594 for (t = c;; t++) {
2595 if (!badchar(*t) && !badentity(t))
2596 continue;
2598 if (t - c == matchlen)
2599 break;
2601 if (*t == ',' && *(t + 1) != ' ') {
2602 continue;
2605 if (t > start && *(t - 1) == '.')
2606 t--;
2607 if (t > start && *(t - 1) == ')' && inside_paren > 0)
2608 t--;
2610 url_buf = g_strndup(c, t - c);
2611 tmpurlbuf = purple_unescape_html(url_buf);
2612 g_string_append_printf(ret, "<A HREF=\"%s%s\">%s</A>",
2613 urlprefix,
2614 tmpurlbuf, url_buf);
2615 g_free(tmpurlbuf);
2616 g_free(url_buf);
2617 return t;
2620 return c;
2623 char *
2624 purple_markup_linkify(const char *text)
2626 const char *c, *t, *q = NULL;
2627 char *tmpurlbuf, *url_buf;
2628 gunichar g;
2629 gboolean inside_html = FALSE;
2630 int inside_paren = 0;
2631 GString *ret;
2633 if (text == NULL)
2634 return NULL;
2636 ret = g_string_new("");
2638 c = text;
2639 while (*c) {
2641 if(*c == '(' && !inside_html) {
2642 inside_paren++;
2643 ret = g_string_append_c(ret, *c);
2644 c++;
2647 if(inside_html) {
2648 if(*c == '>') {
2649 inside_html = FALSE;
2650 } else if(!q && (*c == '\"' || *c == '\'')) {
2651 q = c;
2652 } else if(q) {
2653 if(*c == *q)
2654 q = NULL;
2656 } else if(*c == '<') {
2657 inside_html = TRUE;
2658 if (!g_ascii_strncasecmp(c, "<A", 2)) {
2659 while (1) {
2660 if (!g_ascii_strncasecmp(c, "/A>", 3)) {
2661 inside_html = FALSE;
2662 break;
2664 ret = g_string_append_c(ret, *c);
2665 c++;
2666 if (!(*c))
2667 break;
2670 } else if (!g_ascii_strncasecmp(c, "http://", 7)) {
2671 c = process_link(ret, text, c, 7, "", inside_paren);
2672 } else if (!g_ascii_strncasecmp(c, "https://", 8)) {
2673 c = process_link(ret, text, c, 8, "", inside_paren);
2674 } else if (!g_ascii_strncasecmp(c, "ftp://", 6)) {
2675 c = process_link(ret, text, c, 6, "", inside_paren);
2676 } else if (!g_ascii_strncasecmp(c, "sftp://", 7)) {
2677 c = process_link(ret, text, c, 7, "", inside_paren);
2678 } else if (!g_ascii_strncasecmp(c, "file://", 7)) {
2679 c = process_link(ret, text, c, 7, "", inside_paren);
2680 } else if (!g_ascii_strncasecmp(c, "www.", 4) && c[4] != '.' && (c == text || badchar(c[-1]) || badentity(c-1))) {
2681 c = process_link(ret, text, c, 4, "http://", inside_paren);
2682 } else if (!g_ascii_strncasecmp(c, "ftp.", 4) && c[4] != '.' && (c == text || badchar(c[-1]) || badentity(c-1))) {
2683 c = process_link(ret, text, c, 4, "ftp://", inside_paren);
2684 } else if (!g_ascii_strncasecmp(c, "xmpp:", 5) && (c == text || badchar(c[-1]) || badentity(c-1))) {
2685 c = process_link(ret, text, c, 5, "", inside_paren);
2686 } else if (!g_ascii_strncasecmp(c, "mailto:", 7)) {
2687 t = c;
2688 while (1) {
2689 if (badchar(*t) || badentity(t)) {
2690 char *d;
2691 if (t - c == 7) {
2692 break;
2694 if (t > text && *(t - 1) == '.')
2695 t--;
2696 if ((d = strstr(c + 7, "?")) != NULL && d < t)
2697 url_buf = g_strndup(c + 7, d - c - 7);
2698 else
2699 url_buf = g_strndup(c + 7, t - c - 7);
2700 if (!purple_email_is_valid(url_buf)) {
2701 g_free(url_buf);
2702 break;
2704 g_free(url_buf);
2705 url_buf = g_strndup(c, t - c);
2706 tmpurlbuf = purple_unescape_html(url_buf);
2707 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2708 tmpurlbuf, url_buf);
2709 g_free(url_buf);
2710 g_free(tmpurlbuf);
2711 c = t;
2712 break;
2714 t++;
2716 } else if (c != text && (*c == '@')) {
2717 int flag;
2718 GString *gurl_buf = NULL;
2719 const char illegal_chars[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0";
2721 if (strchr(illegal_chars,*(c - 1)) || strchr(illegal_chars, *(c + 1)))
2722 flag = 0;
2723 else {
2724 flag = 1;
2725 gurl_buf = g_string_new("");
2728 t = c;
2729 while (flag) {
2730 /* iterate backwards grabbing the local part of an email address */
2731 g = g_utf8_get_char(t);
2732 if (badchar(*t) || (g >= 127) || (*t == '(') ||
2733 ((*t == ';') && ((t > (text+2) && (!g_ascii_strncasecmp(t - 3, "&lt;", 4) ||
2734 !g_ascii_strncasecmp(t - 3, "&gt;", 4))) ||
2735 (t > (text+4) && (!g_ascii_strncasecmp(t - 5, "&quot;", 6)))))) {
2736 /* local part will already be part of ret, strip it out */
2737 ret = g_string_truncate(ret, ret->len - (c - t));
2738 ret = g_string_append_unichar(ret, g);
2739 break;
2740 } else {
2741 g_string_prepend_unichar(gurl_buf, g);
2742 t = g_utf8_find_prev_char(text, t);
2743 if (t < text) {
2744 ret = g_string_assign(ret, "");
2745 break;
2750 t = g_utf8_find_next_char(c, NULL);
2752 while (flag) {
2753 /* iterate forwards grabbing the domain part of an email address */
2754 g = g_utf8_get_char(t);
2755 if (badchar(*t) || (g >= 127) || (*t == ')') || badentity(t)) {
2756 char *d;
2758 url_buf = g_string_free(gurl_buf, FALSE);
2760 /* strip off trailing periods */
2761 if (*url_buf) {
2762 for (d = url_buf + strlen(url_buf) - 1; *d == '.'; d--, t--)
2763 *d = '\0';
2766 tmpurlbuf = purple_unescape_html(url_buf);
2767 if (purple_email_is_valid(tmpurlbuf)) {
2768 g_string_append_printf(ret, "<A HREF=\"mailto:%s\">%s</A>",
2769 tmpurlbuf, url_buf);
2770 } else {
2771 g_string_append(ret, url_buf);
2773 g_free(url_buf);
2774 g_free(tmpurlbuf);
2775 c = t;
2777 break;
2778 } else {
2779 g_string_append_unichar(gurl_buf, g);
2780 t = g_utf8_find_next_char(t, NULL);
2785 if(*c == ')' && !inside_html) {
2786 inside_paren--;
2787 ret = g_string_append_c(ret, *c);
2788 c++;
2791 if (*c == 0)
2792 break;
2794 ret = g_string_append_c(ret, *c);
2795 c++;
2798 return g_string_free(ret, FALSE);
2801 char *purple_unescape_text(const char *in)
2803 GString *ret;
2804 const char *c = in;
2806 if (in == NULL)
2807 return NULL;
2809 ret = g_string_new("");
2810 while (*c) {
2811 int len;
2812 const char *ent;
2814 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2815 g_string_append(ret, ent);
2816 c += len;
2817 } else {
2818 g_string_append_c(ret, *c);
2819 c++;
2823 return g_string_free(ret, FALSE);
2826 char *purple_unescape_html(const char *html)
2828 GString *ret;
2829 const char *c = html;
2831 if (html == NULL)
2832 return NULL;
2834 ret = g_string_new("");
2835 while (*c) {
2836 int len;
2837 const char *ent;
2839 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2840 g_string_append(ret, ent);
2841 c += len;
2842 } else if (!strncmp(c, "<br>", 4)) {
2843 g_string_append_c(ret, '\n');
2844 c += 4;
2845 } else {
2846 g_string_append_c(ret, *c);
2847 c++;
2851 return g_string_free(ret, FALSE);
2854 char *
2855 purple_markup_slice(const char *str, guint x, guint y)
2857 GString *ret;
2858 GQueue *q;
2859 guint z = 0;
2860 gboolean appended = FALSE;
2861 gunichar c;
2862 char *tag;
2864 g_return_val_if_fail(str != NULL, NULL);
2865 g_return_val_if_fail(x <= y, NULL);
2867 if (x == y)
2868 return g_strdup("");
2870 ret = g_string_new("");
2871 q = g_queue_new();
2873 while (*str && (z < y)) {
2874 c = g_utf8_get_char(str);
2876 if (c == '<') {
2877 char *end = strchr(str, '>');
2879 if (!end) {
2880 g_string_free(ret, TRUE);
2881 while ((tag = g_queue_pop_head(q)))
2882 g_free(tag);
2883 g_queue_free(q);
2884 return NULL;
2887 if (!g_ascii_strncasecmp(str, "<img ", 5)) {
2888 z += strlen("[Image]");
2889 } else if (!g_ascii_strncasecmp(str, "<br", 3)) {
2890 z += 1;
2891 } else if (!g_ascii_strncasecmp(str, "<hr>", 4)) {
2892 z += strlen("\n---\n");
2893 } else if (!g_ascii_strncasecmp(str, "</", 2)) {
2894 /* pop stack */
2895 char *tmp;
2897 tmp = g_queue_pop_head(q);
2898 g_free(tmp);
2899 /* z += 0; */
2900 } else {
2901 /* push it unto the stack */
2902 char *tmp;
2904 tmp = g_strndup(str, end - str + 1);
2905 g_queue_push_head(q, tmp);
2906 /* z += 0; */
2909 if (z >= x) {
2910 g_string_append_len(ret, str, end - str + 1);
2913 str = end;
2914 } else if (c == '&') {
2915 char *end = strchr(str, ';');
2916 if (!end) {
2917 g_string_free(ret, TRUE);
2918 while ((tag = g_queue_pop_head(q)))
2919 g_free(tag);
2920 g_queue_free(q);
2922 return NULL;
2925 if (z >= x)
2926 g_string_append_len(ret, str, end - str + 1);
2928 z++;
2929 str = end;
2930 } else {
2931 if (z == x && z > 0 && !appended) {
2932 GList *l = q->tail;
2934 while (l) {
2935 tag = l->data;
2936 g_string_append(ret, tag);
2937 l = l->prev;
2939 appended = TRUE;
2942 if (z >= x)
2943 g_string_append_unichar(ret, c);
2944 z++;
2947 str = g_utf8_next_char(str);
2950 while ((tag = g_queue_pop_head(q))) {
2951 char *name;
2953 name = purple_markup_get_tag_name(tag);
2954 g_string_append_printf(ret, "</%s>", name);
2955 g_free(name);
2956 g_free(tag);
2959 g_queue_free(q);
2960 return g_string_free(ret, FALSE);
2963 char *
2964 purple_markup_get_tag_name(const char *tag)
2966 int i;
2967 g_return_val_if_fail(tag != NULL, NULL);
2968 g_return_val_if_fail(*tag == '<', NULL);
2970 for (i = 1; tag[i]; i++)
2971 if (tag[i] == '>' || tag[i] == ' ' || tag[i] == '/')
2972 break;
2974 return g_strndup(tag+1, i-1);
2977 /**************************************************************************
2978 * Path/Filename Functions
2979 **************************************************************************/
2980 const char *
2981 purple_home_dir(void)
2983 #ifndef _WIN32
2984 return g_get_home_dir();
2985 #else
2986 return wpurple_home_dir();
2987 #endif
2990 /* Returns the argument passed to -c IFF it was present, or ~/.purple. */
2991 const char *
2992 purple_user_dir(void)
2994 if (custom_user_dir != NULL)
2995 return custom_user_dir;
2996 else if (!user_dir)
2997 user_dir = g_build_filename(purple_home_dir(), ".purple", NULL);
2999 return user_dir;
3002 const char *
3003 purple_cache_dir(void)
3005 if (!cache_dir) {
3006 if (!custom_user_dir) {
3007 cache_dir = g_build_filename(g_get_user_cache_dir(), "purple", NULL);
3008 } else {
3009 cache_dir = g_build_filename(custom_user_dir, "cache", NULL);
3013 return cache_dir;
3016 const char *
3017 purple_config_dir(void)
3019 if (!config_dir) {
3020 if (!custom_user_dir) {
3021 config_dir = g_build_filename(g_get_user_config_dir(), "purple", NULL);
3022 } else {
3023 config_dir = g_build_filename(custom_user_dir, "config", NULL);
3027 return config_dir;
3030 const char *
3031 purple_data_dir(void)
3033 if (!data_dir) {
3034 if (!custom_user_dir) {
3035 data_dir = g_build_filename(g_get_user_data_dir(), "purple", NULL);
3036 } else {
3037 data_dir = g_build_filename(custom_user_dir, "data", NULL);
3041 return data_dir;
3044 void purple_util_set_user_dir(const char *dir)
3046 g_free(custom_user_dir);
3048 if (dir != NULL && *dir)
3049 custom_user_dir = g_strdup(dir);
3050 else
3051 custom_user_dir = NULL;
3054 int purple_build_dir(const char *path, int mode)
3056 return g_mkdir_with_parents(path, mode);
3059 static gboolean
3060 purple_util_write_data_to_file_common(const char *dir, const char *filename, const char *data, gssize size)
3062 gchar *filename_full;
3063 gboolean ret = FALSE;
3065 g_return_val_if_fail(dir != NULL, FALSE);
3067 purple_debug_misc("util", "Writing file %s to directory %s",
3068 filename, dir);
3070 /* Ensure the directory exists */
3071 if (!g_file_test(dir, G_FILE_TEST_IS_DIR))
3073 if (g_mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
3075 purple_debug_error("util", "Error creating directory %s: %s\n",
3076 dir, g_strerror(errno));
3077 return FALSE;
3081 filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dir, filename);
3083 ret = purple_util_write_data_to_file_absolute(filename_full, data, size);
3085 g_free(filename_full);
3086 return ret;
3089 gboolean
3090 purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
3092 const char *user_dir = purple_user_dir();
3093 gboolean ret = purple_util_write_data_to_file_common(user_dir, filename, data, size);
3095 return ret;
3098 gboolean
3099 purple_util_write_data_to_cache_file(const char *filename, const char *data, gssize size)
3101 const char *cache_dir = purple_cache_dir();
3102 gboolean ret = purple_util_write_data_to_file_common(cache_dir, filename, data, size);
3104 return ret;
3107 gboolean
3108 purple_util_write_data_to_config_file(const char *filename, const char *data, gssize size)
3110 const char *config_dir = purple_config_dir();
3111 gboolean ret = purple_util_write_data_to_file_common(config_dir, filename, data, size);
3113 return ret;
3116 gboolean
3117 purple_util_write_data_to_data_file(const char *filename, const char *data, gssize size)
3119 const char *data_dir = purple_data_dir();
3120 gboolean ret = purple_util_write_data_to_file_common(data_dir, filename, data, size);
3122 return ret;
3126 * This function is long and beautiful, like my--um, yeah. Anyway,
3127 * it includes lots of error checking so as we don't overwrite
3128 * people's settings if there is a problem writing the new values.
3130 gboolean
3131 purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, gssize size)
3133 gchar *filename_temp;
3134 FILE *file;
3135 gsize real_size, byteswritten;
3136 GStatBuf st;
3137 #ifndef HAVE_FILENO
3138 int fd;
3139 #endif
3141 purple_debug_misc("util", "Writing file %s",
3142 filename_full);
3144 g_return_val_if_fail((size >= -1), FALSE);
3146 filename_temp = g_strdup_printf("%s.save", filename_full);
3148 /* Remove an old temporary file, if one exists */
3149 if (g_file_test(filename_temp, G_FILE_TEST_EXISTS))
3151 if (g_unlink(filename_temp) == -1)
3153 purple_debug_error("util", "Error removing old file "
3154 "%s: %s\n",
3155 filename_temp, g_strerror(errno));
3159 /* Open file */
3160 file = g_fopen(filename_temp, "wb");
3161 if (file == NULL)
3163 purple_debug_error("util", "Error opening file %s for "
3164 "writing: %s\n",
3165 filename_temp, g_strerror(errno));
3166 g_free(filename_temp);
3167 return FALSE;
3170 /* Write to file */
3171 real_size = (size == -1) ? strlen(data) : (size_t) size;
3172 byteswritten = fwrite(data, 1, real_size, file);
3174 #ifdef HAVE_FILENO
3175 #ifndef _WIN32
3176 /* Set file permissions */
3177 if (fchmod(fileno(file), S_IRUSR | S_IWUSR) == -1) {
3178 purple_debug_error("util", "Error setting permissions of "
3179 "file %s: %s\n", filename_temp, g_strerror(errno));
3181 #endif
3183 /* Apparently XFS (and possibly other filesystems) do not
3184 * guarantee that file data is flushed before file metadata,
3185 * so this procedure is insufficient without some flushage. */
3186 if (fflush(file) < 0) {
3187 purple_debug_error("util", "Error flushing %s: %s\n",
3188 filename_temp, g_strerror(errno));
3189 g_free(filename_temp);
3190 fclose(file);
3191 return FALSE;
3193 if (fsync(fileno(file)) < 0) {
3194 purple_debug_error("util", "Error syncing file contents for %s: %s\n",
3195 filename_temp, g_strerror(errno));
3196 g_free(filename_temp);
3197 fclose(file);
3198 return FALSE;
3200 #endif
3202 /* Close file */
3203 if (fclose(file) != 0)
3205 purple_debug_error("util", "Error closing file %s: %s\n",
3206 filename_temp, g_strerror(errno));
3207 g_free(filename_temp);
3208 return FALSE;
3211 #ifndef HAVE_FILENO
3212 /* This is the same effect (we hope) as the HAVE_FILENO block
3213 * above, but for systems without fileno(). */
3214 if ((fd = open(filename_temp, O_RDWR)) < 0) {
3215 purple_debug_error("util", "Error opening file %s for flush: %s\n",
3216 filename_temp, g_strerror(errno));
3217 g_free(filename_temp);
3218 return FALSE;
3221 #ifndef _WIN32
3222 /* copy-pasta! */
3223 if (fchmod(fd, S_IRUSR | S_IWUSR) == -1) {
3224 purple_debug_error("util", "Error setting permissions of "
3225 "file %s: %s\n", filename_temp, g_strerror(errno));
3227 #endif
3229 if (fsync(fd) < 0) {
3230 purple_debug_error("util", "Error syncing %s: %s\n",
3231 filename_temp, g_strerror(errno));
3232 g_free(filename_temp);
3233 close(fd);
3234 return FALSE;
3236 if (close(fd) < 0) {
3237 purple_debug_error("util", "Error closing %s after sync: %s\n",
3238 filename_temp, g_strerror(errno));
3239 g_free(filename_temp);
3240 return FALSE;
3242 #endif
3244 /* Ensure the file is the correct size */
3245 if (byteswritten != real_size)
3247 purple_debug_error("util", "Error writing to file %s: Wrote %"
3248 G_GSIZE_FORMAT " bytes "
3249 "but should have written %" G_GSIZE_FORMAT
3250 "; is your disk full?\n",
3251 filename_temp, byteswritten, real_size);
3252 g_free(filename_temp);
3253 return FALSE;
3255 #ifndef __COVERITY__
3256 /* Use stat to be absolutely sure.
3257 * It causes TOCTOU coverity warning (against g_rename below),
3258 * but it's not a threat for us.
3260 if ((g_stat(filename_temp, &st) == -1) || ((gsize)st.st_size != real_size)) {
3261 purple_debug_error("util", "Error writing data to file %s: "
3262 "couldn't g_stat file", filename_temp);
3263 g_free(filename_temp);
3264 return FALSE;
3266 #endif /* __COVERITY__ */
3268 /* Rename to the REAL name */
3269 if (g_rename(filename_temp, filename_full) == -1)
3271 purple_debug_error("util", "Error renaming %s to %s: %s\n",
3272 filename_temp, filename_full,
3273 g_strerror(errno));
3276 g_free(filename_temp);
3278 return TRUE;
3281 PurpleXmlNode *
3282 purple_util_read_xml_from_file(const char *filename, const char *description)
3284 return purple_xmlnode_from_file(purple_user_dir(), filename, description, "util");
3288 * Like mkstemp() but returns a file pointer, uses a pre-set template,
3289 * uses the semantics of tempnam() for the directory to use and allocates
3290 * the space for the filepath.
3292 * Caller is responsible for closing the file and removing it when done,
3293 * as well as freeing the space pointed-to by "path" with g_free().
3295 * Returns NULL on failure and cleans up after itself if so.
3297 static const char *purple_mkstemp_templ = {"purpleXXXXXX"};
3299 FILE *
3300 purple_mkstemp(char **fpath, gboolean binary)
3302 const gchar *tmpdir;
3303 int fd;
3304 FILE *fp = NULL;
3306 g_return_val_if_fail(fpath != NULL, NULL);
3308 if((tmpdir = (gchar*)g_get_tmp_dir()) != NULL) {
3309 if((*fpath = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", tmpdir, purple_mkstemp_templ)) != NULL) {
3310 fd = g_mkstemp(*fpath);
3311 if(fd == -1) {
3312 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
3313 "Couldn't make \"%s\", error: %d\n",
3314 *fpath, errno);
3315 } else {
3316 if((fp = fdopen(fd, "r+")) == NULL) {
3317 close(fd);
3318 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
3319 "Couldn't fdopen(), error: %d\n", errno);
3323 if(!fp) {
3324 g_free(*fpath);
3325 *fpath = NULL;
3328 } else {
3329 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
3330 "g_get_tmp_dir() failed!\n");
3333 return fp;
3336 gboolean
3337 purple_program_is_valid(const char *program)
3339 GError *error = NULL;
3340 char **argv;
3341 gchar *progname;
3342 gboolean is_valid = FALSE;
3344 g_return_val_if_fail(program != NULL, FALSE);
3345 g_return_val_if_fail(*program != '\0', FALSE);
3347 if (!g_shell_parse_argv(program, NULL, &argv, &error)) {
3348 purple_debug(PURPLE_DEBUG_ERROR, "program_is_valid",
3349 "Could not parse program '%s': %s\n",
3350 program, error->message);
3351 g_error_free(error);
3352 return FALSE;
3355 if (argv == NULL) {
3356 return FALSE;
3359 progname = g_find_program_in_path(argv[0]);
3360 is_valid = (progname != NULL);
3362 if(purple_debug_is_verbose())
3363 purple_debug_info("program_is_valid", "Tested program %s. %s.\n", program,
3364 is_valid ? "Valid" : "Invalid");
3366 g_strfreev(argv);
3367 g_free(progname);
3369 return is_valid;
3373 gboolean
3374 purple_running_gnome(void)
3376 #ifndef _WIN32
3377 gchar *tmp = g_find_program_in_path("gvfs-open");
3379 if (tmp == NULL) {
3380 tmp = g_find_program_in_path("gnome-open");
3382 if (tmp == NULL) {
3383 return FALSE;
3387 g_free(tmp);
3389 tmp = (gchar *)g_getenv("GNOME_DESKTOP_SESSION_ID");
3391 return ((tmp != NULL) && (*tmp != '\0'));
3392 #else
3393 return FALSE;
3394 #endif
3397 gboolean
3398 purple_running_kde(void)
3400 #ifndef _WIN32
3401 gchar *tmp = g_find_program_in_path("kfmclient");
3402 const char *session;
3404 if (tmp == NULL)
3405 return FALSE;
3406 g_free(tmp);
3408 session = g_getenv("KDE_FULL_SESSION");
3409 if (purple_strequal(session, "true"))
3410 return TRUE;
3412 /* If you run Purple from Konsole under !KDE, this will provide a
3413 * a false positive. Since we do the GNOME checks first, this is
3414 * only a problem if you're running something !(KDE || GNOME) and
3415 * you run Purple from Konsole. This really shouldn't be a problem. */
3416 return ((g_getenv("KDEDIR") != NULL) || g_getenv("KDEDIRS") != NULL);
3417 #else
3418 return FALSE;
3419 #endif
3422 gboolean
3423 purple_running_osx(void)
3425 #if defined(__APPLE__)
3426 return TRUE;
3427 #else
3428 return FALSE;
3429 #endif
3432 typedef union purple_sockaddr {
3433 struct sockaddr sa;
3434 struct sockaddr_in sa_in;
3435 #if defined(AF_INET6)
3436 struct sockaddr_in6 sa_in6;
3437 #endif
3438 struct sockaddr_storage sa_stor;
3439 } PurpleSockaddr;
3441 char *
3442 purple_fd_get_ip(int fd)
3444 PurpleSockaddr addr;
3445 socklen_t namelen = sizeof(addr);
3446 int family;
3448 g_return_val_if_fail(fd != 0, NULL);
3450 if (getsockname(fd, &(addr.sa), &namelen))
3451 return NULL;
3453 family = addr.sa.sa_family;
3455 if (family == AF_INET) {
3456 return g_strdup(inet_ntoa(addr.sa_in.sin_addr));
3458 #if defined(AF_INET6) && defined(HAVE_INET_NTOP)
3459 else if (family == AF_INET6) {
3460 char host[INET6_ADDRSTRLEN];
3461 const char *tmp;
3463 tmp = inet_ntop(family, &(addr.sa_in6.sin6_addr), host, sizeof(host));
3464 return g_strdup(tmp);
3466 #endif
3468 return NULL;
3472 purple_socket_get_family(int fd)
3474 PurpleSockaddr addr;
3475 socklen_t len = sizeof(addr);
3477 g_return_val_if_fail(fd >= 0, -1);
3479 if (getsockname(fd, &(addr.sa), &len))
3480 return -1;
3482 return addr.sa.sa_family;
3485 gboolean
3486 purple_socket_speaks_ipv4(int fd)
3488 int family;
3490 g_return_val_if_fail(fd >= 0, FALSE);
3492 family = purple_socket_get_family(fd);
3494 switch (family) {
3495 case AF_INET:
3496 return TRUE;
3497 #if defined(IPV6_V6ONLY)
3498 case AF_INET6:
3500 int val = 0;
3501 socklen_t len = sizeof(val);
3503 if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0)
3504 return FALSE;
3505 return !val;
3507 #endif
3508 default:
3509 return FALSE;
3513 /**************************************************************************
3514 * String Functions
3515 **************************************************************************/
3516 gboolean
3517 purple_strequal(const gchar *left, const gchar *right)
3519 return (g_strcmp0(left, right) == 0);
3522 const char *
3523 purple_normalize(const PurpleAccount *account, const char *str)
3525 const char *ret = NULL;
3526 static char buf[BUF_LEN];
3528 /* This should prevent a crash if purple_normalize gets called with NULL str, see #10115 */
3529 g_return_val_if_fail(str != NULL, "");
3531 if (account != NULL)
3533 PurpleProtocol *protocol =
3534 purple_protocols_find(purple_account_get_protocol_id(account));
3536 if (protocol != NULL)
3537 ret = purple_protocol_client_iface_normalize(protocol, account, str);
3540 if (ret == NULL)
3542 char *tmp;
3544 tmp = g_utf8_normalize(str, -1, G_NORMALIZE_DEFAULT);
3545 g_snprintf(buf, sizeof(buf), "%s", tmp);
3546 g_free(tmp);
3548 ret = buf;
3551 return ret;
3555 * You probably don't want to call this directly, it is
3556 * mainly for use as a protocol callback function. See the
3557 * comments in util.h.
3559 const char *
3560 purple_normalize_nocase(const PurpleAccount *account, const char *str)
3562 static char buf[BUF_LEN];
3563 char *tmp1, *tmp2;
3565 g_return_val_if_fail(str != NULL, NULL);
3567 tmp1 = g_utf8_strdown(str, -1);
3568 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
3569 g_snprintf(buf, sizeof(buf), "%s", tmp2 ? tmp2 : "");
3570 g_free(tmp2);
3571 g_free(tmp1);
3573 return buf;
3576 gboolean
3577 purple_validate(const PurpleProtocol *protocol, const char *str)
3579 const char *normalized;
3581 g_return_val_if_fail(protocol != NULL, FALSE);
3582 g_return_val_if_fail(str != NULL, FALSE);
3584 if (str[0] == '\0')
3585 return FALSE;
3587 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT_IFACE, normalize))
3588 return TRUE;
3590 normalized = purple_protocol_client_iface_normalize(PURPLE_PROTOCOL(protocol),
3591 NULL, str);
3593 return (NULL != normalized);
3596 gchar *
3597 purple_strdup_withhtml(const gchar *src)
3599 gulong destsize, i, j;
3600 gchar *dest;
3602 g_return_val_if_fail(src != NULL, NULL);
3604 /* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */
3605 destsize = 1;
3606 for (i = 0; src[i] != '\0'; i++)
3608 if (src[i] == '\n')
3609 destsize += 4;
3610 else if (src[i] != '\r')
3611 destsize++;
3614 dest = g_malloc(destsize);
3616 /* Copy stuff, ignoring \r's, because they are dumb */
3617 for (i = 0, j = 0; src[i] != '\0'; i++) {
3618 if (src[i] == '\n') {
3619 strcpy(&dest[j], "<BR>");
3620 j += 4;
3621 } else if (src[i] != '\r')
3622 dest[j++] = src[i];
3625 dest[destsize-1] = '\0';
3627 return dest;
3630 gboolean
3631 purple_str_has_prefix(const char *s, const char *p)
3633 return g_str_has_prefix(s, p);
3636 gboolean
3637 purple_str_has_caseprefix(const gchar *s, const gchar *p)
3639 g_return_val_if_fail(s, FALSE);
3640 g_return_val_if_fail(p, FALSE);
3642 return (g_ascii_strncasecmp(s, p, strlen(p)) == 0);
3645 gboolean
3646 purple_str_has_suffix(const char *s, const char *x)
3648 return g_str_has_suffix(s, x);
3651 char *
3652 purple_str_add_cr(const char *text)
3654 char *ret = NULL;
3655 int count = 0, j;
3656 guint i;
3658 g_return_val_if_fail(text != NULL, NULL);
3660 if (text[0] == '\n')
3661 count++;
3662 for (i = 1; i < strlen(text); i++)
3663 if (text[i] == '\n' && text[i - 1] != '\r')
3664 count++;
3666 if (count == 0)
3667 return g_strdup(text);
3669 ret = g_malloc0(strlen(text) + count + 1);
3671 i = 0; j = 0;
3672 if (text[i] == '\n')
3673 ret[j++] = '\r';
3674 ret[j++] = text[i++];
3675 for (; i < strlen(text); i++) {
3676 if (text[i] == '\n' && text[i - 1] != '\r')
3677 ret[j++] = '\r';
3678 ret[j++] = text[i];
3681 return ret;
3684 void
3685 purple_str_strip_char(char *text, char thechar)
3687 int i, j;
3689 g_return_if_fail(text != NULL);
3691 for (i = 0, j = 0; text[i]; i++)
3692 if (text[i] != thechar)
3693 text[j++] = text[i];
3695 text[j] = '\0';
3698 void
3699 purple_util_chrreplace(char *string, char delimiter,
3700 char replacement)
3702 int i = 0;
3704 g_return_if_fail(string != NULL);
3706 while (string[i] != '\0')
3708 if (string[i] == delimiter)
3709 string[i] = replacement;
3710 i++;
3714 gchar *
3715 purple_strreplace(const char *string, const char *delimiter,
3716 const char *replacement)
3718 gchar **split;
3719 gchar *ret;
3721 g_return_val_if_fail(string != NULL, NULL);
3722 g_return_val_if_fail(delimiter != NULL, NULL);
3723 g_return_val_if_fail(replacement != NULL, NULL);
3725 split = g_strsplit(string, delimiter, 0);
3726 ret = g_strjoinv(replacement, split);
3727 g_strfreev(split);
3729 return ret;
3732 gchar *
3733 purple_strcasereplace(const char *string, const char *delimiter,
3734 const char *replacement)
3736 gchar *ret;
3737 int length_del, length_rep, i, j;
3739 g_return_val_if_fail(string != NULL, NULL);
3740 g_return_val_if_fail(delimiter != NULL, NULL);
3741 g_return_val_if_fail(replacement != NULL, NULL);
3743 length_del = strlen(delimiter);
3744 length_rep = strlen(replacement);
3746 /* Count how many times the delimiter appears */
3747 i = 0; /* position in the source string */
3748 j = 0; /* number of occurrences of "delimiter" */
3749 while (string[i] != '\0') {
3750 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3751 i += length_del;
3752 j += length_rep;
3753 } else {
3754 i++;
3755 j++;
3759 ret = g_malloc(j+1);
3761 i = 0; /* position in the source string */
3762 j = 0; /* position in the destination string */
3763 while (string[i] != '\0') {
3764 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3765 strncpy(&ret[j], replacement, length_rep);
3766 i += length_del;
3767 j += length_rep;
3768 } else {
3769 ret[j] = string[i];
3770 i++;
3771 j++;
3775 ret[j] = '\0';
3777 return ret;
3780 /** TODO: Expose this when we can add API */
3781 static const char *
3782 purple_strcasestr_len(const char *haystack, gssize hlen, const char *needle, gssize nlen)
3784 const char *tmp, *ret;
3786 g_return_val_if_fail(haystack != NULL, NULL);
3787 g_return_val_if_fail(needle != NULL, NULL);
3789 if (hlen == -1)
3790 hlen = strlen(haystack);
3791 if (nlen == -1)
3792 nlen = strlen(needle);
3793 tmp = haystack,
3794 ret = NULL;
3796 g_return_val_if_fail(hlen > 0, NULL);
3797 g_return_val_if_fail(nlen > 0, NULL);
3799 while (*tmp && !ret && (hlen - (tmp - haystack)) >= nlen) {
3800 if (!g_ascii_strncasecmp(needle, tmp, nlen))
3801 ret = tmp;
3802 else
3803 tmp++;
3806 return ret;
3809 const char *
3810 purple_strcasestr(const char *haystack, const char *needle)
3812 return purple_strcasestr_len(haystack, -1, needle, -1);
3815 char *
3816 purple_str_size_to_units(goffset size)
3818 static const char * const size_str[] = { "bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
3819 float size_mag;
3820 gsize size_index = 0;
3822 if (size == -1) {
3823 return g_strdup(_("Calculating..."));
3825 else if (size == 0) {
3826 return g_strdup(_("Unknown."));
3828 else {
3829 size_mag = (float)size;
3831 while ((size_index < G_N_ELEMENTS(size_str) - 1) && (size_mag > 1024)) {
3832 size_mag /= 1024;
3833 size_index++;
3836 if (size_index == 0) {
3837 return g_strdup_printf("%" G_GOFFSET_FORMAT " %s", size, _(size_str[size_index]));
3838 } else {
3839 return g_strdup_printf("%.2f %s", size_mag, _(size_str[size_index]));
3844 char *
3845 purple_str_seconds_to_string(guint secs)
3847 char *ret = NULL;
3848 guint days, hrs, mins;
3850 if (secs < 60)
3852 return g_strdup_printf(dngettext(PACKAGE, "%d second", "%d seconds", secs), secs);
3855 days = secs / (60 * 60 * 24);
3856 secs = secs % (60 * 60 * 24);
3857 hrs = secs / (60 * 60);
3858 secs = secs % (60 * 60);
3859 mins = secs / 60;
3860 /* secs = secs % 60; */
3862 if (days > 0)
3864 ret = g_strdup_printf(dngettext(PACKAGE, "%d day", "%d days", days), days);
3867 if (hrs > 0)
3869 if (ret != NULL)
3871 char *tmp = g_strdup_printf(
3872 dngettext(PACKAGE, "%s, %d hour", "%s, %d hours", hrs),
3873 ret, hrs);
3874 g_free(ret);
3875 ret = tmp;
3877 else
3878 ret = g_strdup_printf(dngettext(PACKAGE, "%d hour", "%d hours", hrs), hrs);
3881 if (mins > 0)
3883 if (ret != NULL)
3885 char *tmp = g_strdup_printf(
3886 dngettext(PACKAGE, "%s, %d minute", "%s, %d minutes", mins),
3887 ret, mins);
3888 g_free(ret);
3889 ret = tmp;
3891 else
3892 ret = g_strdup_printf(dngettext(PACKAGE, "%d minute", "%d minutes", mins), mins);
3895 return ret;
3899 char *
3900 purple_str_binary_to_ascii(const unsigned char *binary, guint len)
3902 GString *ret;
3903 guint i;
3905 g_return_val_if_fail(len > 0, NULL);
3907 ret = g_string_sized_new(len);
3909 for (i = 0; i < len; i++)
3910 if (binary[i] < 32 || binary[i] > 126)
3911 g_string_append_printf(ret, "\\x%02x", binary[i] & 0xFF);
3912 else if (binary[i] == '\\')
3913 g_string_append(ret, "\\\\");
3914 else
3915 g_string_append_c(ret, binary[i]);
3917 return g_string_free(ret, FALSE);
3920 size_t
3921 purple_utf16_size(const gunichar2 *str)
3923 /* UTF16 cannot contain two consequent NUL bytes starting at even
3924 * position - see Unicode standards Chapter 3.9 D91 or RFC2781
3925 * Chapter 2.
3928 size_t i = 0;
3930 g_return_val_if_fail(str != NULL, 0);
3932 while (str[i++]);
3934 return i * sizeof(gunichar2);
3937 void
3938 purple_str_wipe(gchar *str)
3940 if (str == NULL)
3941 return;
3942 memset(str, 0, strlen(str));
3943 g_free(str);
3946 void
3947 purple_utf16_wipe(gunichar2 *str)
3949 if (str == NULL)
3950 return;
3951 memset(str, 0, purple_utf16_size(str));
3952 g_free(str);
3955 /**************************************************************************
3956 * URI/URL Functions
3957 **************************************************************************/
3959 void purple_got_protocol_handler_uri(const char *uri)
3961 char proto[11];
3962 char delimiter;
3963 const char *tmp, *param_string;
3964 char *cmd;
3965 GHashTable *params = NULL;
3966 gsize len;
3967 if (!(tmp = strchr(uri, ':')) || tmp == uri) {
3968 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
3969 return;
3972 len = MIN(sizeof(proto) - 1, (gsize)(tmp - uri));
3974 strncpy(proto, uri, len);
3975 proto[len] = '\0';
3977 tmp++;
3979 if (g_str_equal(proto, "xmpp"))
3980 delimiter = ';';
3981 else
3982 delimiter = '&';
3984 purple_debug_info("util", "Processing message '%s' for protocol '%s' using delimiter '%c'.\n", tmp, proto, delimiter);
3986 if ((param_string = strchr(tmp, '?'))) {
3987 const char *keyend = NULL, *pairstart;
3988 char *key, *value = NULL;
3990 cmd = g_strndup(tmp, (param_string - tmp));
3991 param_string++;
3993 params = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
3994 pairstart = tmp = param_string;
3996 while (*tmp || *pairstart) {
3997 if (*tmp == delimiter || !(*tmp)) {
3998 /* If there is no explicit value */
3999 if (keyend == NULL) {
4000 keyend = tmp;
4002 /* without these brackets, clang won't
4003 * recognize tmp as a non-NULL
4006 if (keyend && keyend != pairstart) {
4007 char *p;
4008 key = g_strndup(pairstart, (keyend - pairstart));
4009 /* If there is an explicit value */
4010 if (keyend != tmp && keyend != (tmp - 1))
4011 value = g_strndup(keyend + 1, (tmp - keyend - 1));
4012 for (p = key; *p; ++p)
4013 *p = g_ascii_tolower(*p);
4014 g_hash_table_insert(params, key, value);
4016 keyend = value = NULL;
4017 pairstart = (*tmp) ? tmp + 1 : tmp;
4018 } else if (*tmp == '=')
4019 keyend = tmp;
4021 if (*tmp)
4022 tmp++;
4024 } else
4025 cmd = g_strdup(tmp);
4027 purple_signal_emit_return_1(purple_get_core(), "uri-handler", proto, cmd, params);
4029 g_free(cmd);
4030 if (params)
4031 g_hash_table_destroy(params);
4034 const char *
4035 purple_url_decode(const char *str)
4037 static char buf[BUF_LEN];
4038 guint i, j = 0;
4039 char *bum;
4040 char hex[3];
4042 g_return_val_if_fail(str != NULL, NULL);
4045 * XXX - This check could be removed and buf could be made
4046 * dynamically allocated, but this is easier.
4048 if (strlen(str) >= BUF_LEN)
4049 return NULL;
4051 for (i = 0; i < strlen(str); i++) {
4053 if (str[i] != '%')
4054 buf[j++] = str[i];
4055 else {
4056 strncpy(hex, str + ++i, 2);
4057 hex[2] = '\0';
4059 /* i is pointing to the start of the number */
4060 i++;
4063 * Now it's at the end and at the start of the for loop
4064 * will be at the next character.
4066 buf[j++] = strtol(hex, NULL, 16);
4070 buf[j] = '\0';
4072 if (!g_utf8_validate(buf, -1, (const char **)&bum))
4073 *bum = '\0';
4075 return buf;
4078 const char *
4079 purple_url_encode(const char *str)
4081 const char *iter;
4082 static char buf[BUF_LEN];
4083 char utf_char[6];
4084 guint i, j = 0;
4086 g_return_val_if_fail(str != NULL, NULL);
4087 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4089 iter = str;
4090 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4091 gunichar c = g_utf8_get_char(iter);
4092 /* If the character is an ASCII character and is alphanumeric
4093 * no need to escape */
4094 if (c < 128 && (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~')) {
4095 buf[j++] = c;
4096 } else {
4097 int bytes = g_unichar_to_utf8(c, utf_char);
4098 for (i = 0; (int)i < bytes; i++) {
4099 if (j > (BUF_LEN - 4))
4100 break;
4101 if (i >= sizeof(utf_char)) {
4102 g_warn_if_reached();
4103 break;
4105 sprintf(buf + j, "%%%02X", utf_char[i] & 0xff);
4106 j += 3;
4111 buf[j] = '\0';
4113 return buf;
4116 /* Originally lifted from
4117 * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html
4118 * ... and slightly modified to be a bit more rfc822 compliant
4119 * ... and modified a bit more to make domain checking rfc1035 compliant
4120 * with the exception permitted in rfc1101 for domains to start with digit
4121 * but not completely checking to avoid conflicts with IP addresses
4123 gboolean
4124 purple_email_is_valid(const char *address)
4126 const char *c, *domain;
4127 static char *rfc822_specials = "()<>@,;:\\\"[]";
4129 g_return_val_if_fail(address != NULL, FALSE);
4131 if (*address == '.') return FALSE;
4133 /* first we validate the name portion (name@domain) (rfc822)*/
4134 for (c = address; *c; c++) {
4135 if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) {
4136 while (*++c) {
4137 if (*c == '\\') {
4138 if (*c++ && *c < 127 && *c != '\n' && *c != '\r') continue;
4139 else return FALSE;
4141 if (*c == '\"') break;
4142 if (*c < ' ' || *c >= 127) return FALSE;
4144 if (!*c++) return FALSE;
4145 if (*c == '@') break;
4146 if (*c != '.') return FALSE;
4147 continue;
4149 if (*c == '@') break;
4150 if (*c <= ' ' || *c >= 127) return FALSE;
4151 if (strchr(rfc822_specials, *c)) return FALSE;
4154 /* It's obviously not an email address if we didn't find an '@' above */
4155 if (*c == '\0') return FALSE;
4157 /* strictly we should return false if (*(c - 1) == '.') too, but I think
4158 * we should permit user.@domain type addresses - they do work :) */
4159 if (c == address) return FALSE;
4161 /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */
4162 if (!*(domain = ++c)) return FALSE;
4163 do {
4164 if (*c == '.' && (c == domain || *(c - 1) == '.' || *(c - 1) == '-'))
4165 return FALSE;
4166 if (*c == '-' && (*(c - 1) == '.' || *(c - 1) == '@')) return FALSE;
4167 if ((*c < '0' && *c != '-' && *c != '.') || (*c > '9' && *c < 'A') ||
4168 (*c > 'Z' && *c < 'a') || (*c > 'z')) return FALSE;
4169 } while (*++c);
4171 if (*(c - 1) == '-') return FALSE;
4173 return ((c - domain) > 3 ? TRUE : FALSE);
4176 gboolean
4177 purple_ipv4_address_is_valid(const char *ip)
4179 int c, o1, o2, o3, o4;
4180 char end;
4182 g_return_val_if_fail(ip != NULL, FALSE);
4184 c = sscanf(ip, "%d.%d.%d.%d%c", &o1, &o2, &o3, &o4, &end);
4185 if (c != 4 || o1 < 0 || o1 > 255 || o2 < 0 || o2 > 255 || o3 < 0 || o3 > 255 || o4 < 0 || o4 > 255)
4186 return FALSE;
4187 return TRUE;
4190 gboolean
4191 purple_ipv6_address_is_valid(const gchar *ip)
4193 const gchar *c;
4194 gboolean double_colon = FALSE;
4195 gint chunks = 1;
4196 gint in = 0;
4198 g_return_val_if_fail(ip != NULL, FALSE);
4200 if (*ip == '\0')
4201 return FALSE;
4203 for (c = ip; *c; ++c) {
4204 if ((*c >= '0' && *c <= '9') ||
4205 (*c >= 'a' && *c <= 'f') ||
4206 (*c >= 'A' && *c <= 'F')) {
4207 if (++in > 4)
4208 /* Only four hex digits per chunk */
4209 return FALSE;
4210 continue;
4211 } else if (*c == ':') {
4212 /* The start of a new chunk */
4213 ++chunks;
4214 in = 0;
4215 if (*(c + 1) == ':') {
4217 * '::' indicates a consecutive series of chunks full
4218 * of zeroes. There can be only one of these per address.
4220 if (double_colon)
4221 return FALSE;
4222 double_colon = TRUE;
4224 } else
4225 return FALSE;
4229 * Either we saw a '::' and there were fewer than 8 chunks -or-
4230 * we didn't see a '::' and saw exactly 8 chunks.
4232 return (double_colon && chunks < 8) || (!double_colon && chunks == 8);
4235 gboolean
4236 purple_ip_address_is_valid(const char *ip)
4238 return (purple_ipv4_address_is_valid(ip) || purple_ipv6_address_is_valid(ip));
4241 /* Stolen from gnome_uri_list_extract_uris */
4242 GList *
4243 purple_uri_list_extract_uris(const gchar *uri_list)
4245 const gchar *p, *q;
4246 gchar *retval;
4247 GList *result = NULL;
4249 g_return_val_if_fail (uri_list != NULL, NULL);
4251 p = uri_list;
4253 /* We don't actually try to validate the URI according to RFC
4254 * 2396, or even check for allowed characters - we just ignore
4255 * comments and trim whitespace off the ends. We also
4256 * allow LF delimination as well as the specified CRLF.
4258 while (p) {
4259 if (*p != '#') {
4260 while (isspace(*p))
4261 p++;
4263 q = p;
4264 while (*q && (*q != '\n') && (*q != '\r'))
4265 q++;
4267 if (q > p) {
4268 q--;
4269 while (q > p && isspace(*q))
4270 q--;
4272 retval = (gchar*)g_malloc (q - p + 2);
4273 strncpy (retval, p, q - p + 1);
4274 retval[q - p + 1] = '\0';
4276 result = g_list_prepend (result, retval);
4279 p = strchr (p, '\n');
4280 if (p)
4281 p++;
4284 return g_list_reverse (result);
4288 /* Stolen from gnome_uri_list_extract_filenames */
4289 GList *
4290 purple_uri_list_extract_filenames(const gchar *uri_list)
4292 GList *tmp_list, *node, *result;
4294 g_return_val_if_fail (uri_list != NULL, NULL);
4296 result = purple_uri_list_extract_uris(uri_list);
4298 tmp_list = result;
4299 while (tmp_list) {
4300 gchar *s = (gchar*)tmp_list->data;
4302 node = tmp_list;
4303 tmp_list = tmp_list->next;
4305 if (!strncmp (s, "file:", 5)) {
4306 node->data = g_filename_from_uri (s, NULL, NULL);
4307 /* not sure if this fallback is useful at all */
4308 if (!node->data) node->data = g_strdup (s+5);
4309 } else {
4310 result = g_list_delete_link(result, node);
4312 g_free (s);
4314 return result;
4317 /**************************************************************************
4318 * UTF8 String Functions
4319 **************************************************************************/
4320 gchar *
4321 purple_utf8_try_convert(const char *str)
4323 gsize converted;
4324 gchar *utf8;
4326 g_return_val_if_fail(str != NULL, NULL);
4328 if (g_utf8_validate(str, -1, NULL)) {
4329 return g_strdup(str);
4332 utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL);
4333 if (utf8 != NULL)
4334 return utf8;
4336 utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL);
4337 if ((utf8 != NULL) && (converted == strlen(str)))
4338 return utf8;
4340 g_free(utf8);
4342 return NULL;
4345 #define utf8_first(x) ((x & 0x80) == 0 || (x & 0xe0) == 0xc0 \
4346 || (x & 0xf0) == 0xe0 || (x & 0xf8) == 0xf0)
4347 gchar *
4348 purple_utf8_salvage(const char *str)
4350 GString *workstr;
4351 const char *end;
4353 g_return_val_if_fail(str != NULL, NULL);
4355 workstr = g_string_sized_new(strlen(str));
4357 do {
4358 (void)g_utf8_validate(str, -1, &end);
4359 workstr = g_string_append_len(workstr, str, end - str);
4360 str = end;
4361 if (*str == '\0')
4362 break;
4363 do {
4364 workstr = g_string_append_c(workstr, '?');
4365 str++;
4366 } while (!utf8_first(*str));
4367 } while (*str != '\0');
4369 return g_string_free(workstr, FALSE);
4372 gchar *
4373 purple_utf8_strip_unprintables(const gchar *str)
4375 gchar *workstr, *iter;
4376 const gchar *bad;
4378 if (str == NULL)
4379 /* Act like g_strdup */
4380 return NULL;
4382 if (!g_utf8_validate(str, -1, &bad)) {
4383 purple_debug_error("util", "purple_utf8_strip_unprintables(%s) failed; "
4384 "first bad character was %02x (%c)\n",
4385 str, *bad, *bad);
4386 g_return_val_if_reached(NULL);
4389 workstr = iter = g_new(gchar, strlen(str) + 1);
4390 while (*str) {
4391 gunichar ch = g_utf8_get_char(str);
4392 gchar *next = g_utf8_next_char(str);
4394 * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
4395 * [#x10000-#x10FFFF]
4397 if ((ch == '\t' || ch == '\n' || ch == '\r') ||
4398 (ch >= 0x20 && ch <= 0xD7FF) ||
4399 (ch >= 0xE000 && ch <= 0xFFFD) ||
4400 (ch >= 0x10000 && ch <= 0x10FFFF)) {
4401 memcpy(iter, str, next - str);
4402 iter += (next - str);
4405 str = next;
4408 /* nul-terminate the new string */
4409 *iter = '\0';
4411 return workstr;
4415 * This function is copied from g_strerror() but changed to use
4416 * gai_strerror().
4418 const gchar *
4419 purple_gai_strerror(gint errnum)
4421 static GPrivate msg_private = G_PRIVATE_INIT(g_free);
4422 char *msg;
4423 int saved_errno = errno;
4425 const char *msg_locale;
4427 msg_locale = gai_strerror(errnum);
4428 if (g_get_charset(NULL))
4430 /* This string is already UTF-8--great! */
4431 errno = saved_errno;
4432 return msg_locale;
4434 else
4436 gchar *msg_utf8 = g_locale_to_utf8(msg_locale, -1, NULL, NULL, NULL);
4437 if (msg_utf8)
4439 /* Stick in the quark table so that we can return a static result */
4440 GQuark msg_quark = g_quark_from_string(msg_utf8);
4441 g_free(msg_utf8);
4443 msg_utf8 = (gchar *)g_quark_to_string(msg_quark);
4444 errno = saved_errno;
4445 return msg_utf8;
4449 msg = g_private_get(&msg_private);
4451 if (!msg)
4453 msg = g_new(gchar, 64);
4454 g_private_set(&msg_private, msg);
4457 sprintf(msg, "unknown error (%d)", errnum);
4459 errno = saved_errno;
4460 return msg;
4463 char *
4464 purple_utf8_ncr_encode(const char *str)
4466 GString *out;
4468 g_return_val_if_fail(str != NULL, NULL);
4469 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4471 out = g_string_new("");
4473 for(; *str; str = g_utf8_next_char(str)) {
4474 gunichar wc = g_utf8_get_char(str);
4476 /* super simple check. hopefully not too wrong. */
4477 if(wc >= 0x80) {
4478 g_string_append_printf(out, "&#%u;", (guint32) wc);
4479 } else {
4480 g_string_append_unichar(out, wc);
4484 return g_string_free(out, FALSE);
4488 char *
4489 purple_utf8_ncr_decode(const char *str)
4491 GString *out;
4492 char *buf, *b;
4494 g_return_val_if_fail(str != NULL, NULL);
4495 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4497 buf = (char *) str;
4498 out = g_string_new("");
4500 while( (b = strstr(buf, "&#")) ) {
4501 gunichar wc;
4502 int base = 0;
4504 /* append everything leading up to the &# */
4505 g_string_append_len(out, buf, b-buf);
4507 b += 2; /* skip past the &# */
4509 /* strtoul will treat 0x prefix as hex, but not just x */
4510 if(*b == 'x' || *b == 'X') {
4511 base = 16;
4512 b++;
4515 /* advances buf to the end of the ncr segment */
4516 wc = (gunichar) strtoul(b, &buf, base);
4518 /* this mimics the previous impl of ncr_decode */
4519 if(*buf == ';') {
4520 g_string_append_unichar(out, wc);
4521 buf++;
4525 /* append whatever's left */
4526 g_string_append(out, buf);
4528 return g_string_free(out, FALSE);
4533 purple_utf8_strcasecmp(const char *a, const char *b)
4535 char *a_norm = NULL;
4536 char *b_norm = NULL;
4537 int ret = -1;
4539 if(!a && b)
4540 return -1;
4541 else if(!b && a)
4542 return 1;
4543 else if(!a && !b)
4544 return 0;
4546 if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL))
4548 purple_debug_error("purple_utf8_strcasecmp",
4549 "One or both parameters are invalid UTF8\n");
4550 return ret;
4553 a_norm = g_utf8_casefold(a, -1);
4554 b_norm = g_utf8_casefold(b, -1);
4555 ret = g_utf8_collate(a_norm, b_norm);
4556 g_free(a_norm);
4557 g_free(b_norm);
4559 return ret;
4562 /* previously conversation::find_nick() */
4563 gboolean
4564 purple_utf8_has_word(const char *haystack, const char *needle)
4566 char *hay, *pin, *p;
4567 const char *start, *prev_char;
4568 gunichar before, after;
4569 int n;
4570 gboolean ret = FALSE;
4572 start = hay = g_utf8_strdown(haystack, -1);
4574 pin = g_utf8_strdown(needle, -1);
4575 n = strlen(pin);
4577 while ((p = strstr(start, pin)) != NULL) {
4578 prev_char = g_utf8_find_prev_char(hay, p);
4579 before = -2;
4580 if (prev_char) {
4581 before = g_utf8_get_char(prev_char);
4583 after = g_utf8_get_char_validated(p + n, - 1);
4585 if ((p == hay ||
4586 /* The character before is a reasonable guess for a word boundary
4587 ("!g_unichar_isalnum()" is not a valid way to determine word
4588 boundaries, but it is the only reasonable thing to do here),
4589 and isn't the '&' from a "&amp;" or some such entity*/
4590 (before != (gunichar)-2 && !g_unichar_isalnum(before) && *(p - 1) != '&'))
4591 && after != (gunichar)-2 && !g_unichar_isalnum(after)) {
4592 ret = TRUE;
4593 break;
4595 start = p + 1;
4598 g_free(pin);
4599 g_free(hay);
4601 return ret;
4604 void
4605 purple_print_utf8_to_console(FILE *filestream, char *message)
4607 gchar *message_conv;
4608 GError *error = NULL;
4610 /* Try to convert 'message' to user's locale */
4611 message_conv = g_locale_from_utf8(message, -1, NULL, NULL, &error);
4612 if (message_conv != NULL) {
4613 fputs(message_conv, filestream);
4614 g_free(message_conv);
4616 else
4618 /* use 'message' as a fallback */
4619 g_warning("%s\n", error->message);
4620 g_error_free(error);
4621 fputs(message, filestream);
4625 gboolean purple_message_meify(char *message, gssize len)
4627 char *c;
4628 gboolean inside_html = FALSE;
4630 g_return_val_if_fail(message != NULL, FALSE);
4632 if(len == -1)
4633 len = strlen(message);
4635 for (c = message; *c; c++, len--) {
4636 if(inside_html) {
4637 if(*c == '>')
4638 inside_html = FALSE;
4639 } else {
4640 if(*c == '<')
4641 inside_html = TRUE;
4642 else
4643 break;
4647 if(*c && !g_ascii_strncasecmp(c, "/me ", 4)) {
4648 memmove(c, c+4, len-3);
4649 return TRUE;
4652 return FALSE;
4655 char *purple_text_strip_mnemonic(const char *in)
4657 char *out;
4658 char *a;
4659 char *a0;
4660 const char *b;
4662 g_return_val_if_fail(in != NULL, NULL);
4664 out = g_malloc(strlen(in)+1);
4665 a = out;
4666 b = in;
4668 a0 = a; /* The last non-space char seen so far, or the first char */
4670 while(*b) {
4671 if(*b == '_') {
4672 if(a > out && b > in && *(b-1) == '(' && *(b+1) && !(*(b+1) & 0x80) && *(b+2) == ')') {
4673 /* Detected CJK style shortcut (Bug 875311) */
4674 a = a0; /* undo the left parenthesis */
4675 b += 3; /* and skip the whole mess */
4676 } else if(*(b+1) == '_') {
4677 *(a++) = '_';
4678 b += 2;
4679 a0 = a;
4680 } else {
4681 b++;
4683 /* We don't want to corrupt the middle of UTF-8 characters */
4684 } else if (!(*b & 0x80)) { /* other 1-byte char */
4685 if (*b != ' ')
4686 a0 = a;
4687 *(a++) = *(b++);
4688 } else {
4689 /* Multibyte utf8 char, don't look for _ inside these */
4690 int n = 0;
4691 int i;
4692 if ((*b & 0xe0) == 0xc0) {
4693 n = 2;
4694 } else if ((*b & 0xf0) == 0xe0) {
4695 n = 3;
4696 } else if ((*b & 0xf8) == 0xf0) {
4697 n = 4;
4698 } else if ((*b & 0xfc) == 0xf8) {
4699 n = 5;
4700 } else if ((*b & 0xfe) == 0xfc) {
4701 n = 6;
4702 } else { /* Illegal utf8 */
4703 n = 1;
4705 a0 = a; /* unless we want to delete CJK spaces too */
4706 for (i = 0; i < n && *b; i += 1) {
4707 *(a++) = *(b++);
4711 *a = '\0';
4713 return out;
4716 const char* purple_unescape_filename(const char *escaped) {
4717 return purple_url_decode(escaped);
4721 /* this is almost identical to purple_url_encode (hence purple_url_decode
4722 * being used above), but we want to keep certain characters unescaped
4723 * for compat reasons */
4724 const char *
4725 purple_escape_filename(const char *str)
4727 const char *iter;
4728 static char buf[BUF_LEN];
4729 char utf_char[6];
4730 guint i, j = 0;
4732 g_return_val_if_fail(str != NULL, NULL);
4733 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4735 iter = str;
4736 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4737 gunichar c = g_utf8_get_char(iter);
4738 /* If the character is an ASCII character and is alphanumeric,
4739 * or one of the specified values, no need to escape */
4740 if (c < 128 && (g_ascii_isalnum(c) || c == '@' || c == '-' ||
4741 c == '_' || c == '.' || c == '#')) {
4742 buf[j++] = c;
4743 } else {
4744 int bytes = g_unichar_to_utf8(c, utf_char);
4745 for (i = 0; (int)i < bytes; i++) {
4746 if (j > (BUF_LEN - 4))
4747 break;
4748 if (i >= sizeof(utf_char)) {
4749 g_warn_if_reached();
4750 break;
4752 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff);
4753 j += 3;
4757 #ifdef _WIN32
4758 /* File/Directory names in windows cannot end in periods/spaces.
4759 * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
4761 while (j > 0 && (buf[j - 1] == '.' || buf[j - 1] == ' '))
4762 j--;
4763 #endif
4764 buf[j] = '\0';
4766 return buf;
4769 gchar * purple_escape_js(const gchar *str)
4771 gchar *escaped;
4773 json_node_set_string(escape_js_node, str);
4774 json_generator_set_root(escape_js_gen, escape_js_node);
4775 escaped = json_generator_to_data(escape_js_gen, NULL);
4776 json_node_set_boolean(escape_js_node, FALSE);
4778 return escaped;
4781 void purple_restore_default_signal_handlers(void)
4783 #ifndef _WIN32
4784 signal(SIGHUP, SIG_DFL); /* 1: terminal line hangup */
4785 signal(SIGINT, SIG_DFL); /* 2: interrupt program */
4786 signal(SIGQUIT, SIG_DFL); /* 3: quit program */
4787 signal(SIGILL, SIG_DFL); /* 4: illegal instruction (not reset when caught) */
4788 signal(SIGTRAP, SIG_DFL); /* 5: trace trap (not reset when caught) */
4789 signal(SIGABRT, SIG_DFL); /* 6: abort program */
4791 #ifdef SIGPOLL
4792 signal(SIGPOLL, SIG_DFL); /* 7: pollable event (POSIX) */
4793 #endif /* SIGPOLL */
4795 #ifdef SIGEMT
4796 signal(SIGEMT, SIG_DFL); /* 7: EMT instruction (Non-POSIX) */
4797 #endif /* SIGEMT */
4799 signal(SIGFPE, SIG_DFL); /* 8: floating point exception */
4800 signal(SIGBUS, SIG_DFL); /* 10: bus error */
4801 signal(SIGSEGV, SIG_DFL); /* 11: segmentation violation */
4802 signal(SIGSYS, SIG_DFL); /* 12: bad argument to system call */
4803 signal(SIGPIPE, SIG_DFL); /* 13: write on a pipe with no reader */
4804 signal(SIGALRM, SIG_DFL); /* 14: real-time timer expired */
4805 signal(SIGTERM, SIG_DFL); /* 15: software termination signal */
4806 signal(SIGCHLD, SIG_DFL); /* 20: child status has changed */
4807 signal(SIGXCPU, SIG_DFL); /* 24: exceeded CPU time limit */
4808 signal(SIGXFSZ, SIG_DFL); /* 25: exceeded file size limit */
4809 #endif /* !_WIN32 */
4812 static void
4813 set_status_with_attrs(PurpleStatus *status, ...)
4815 va_list args;
4816 va_start(args, status);
4817 purple_status_set_active_with_attrs(status, TRUE, args);
4818 va_end(args);
4821 void purple_util_set_current_song(const char *title, const char *artist, const char *album)
4823 GList *list = purple_accounts_get_all();
4824 for (; list; list = list->next) {
4825 PurplePresence *presence;
4826 PurpleStatus *tune;
4827 PurpleAccount *account = list->data;
4828 if (!purple_account_get_enabled(account, purple_core_get_ui()))
4829 continue;
4831 presence = purple_account_get_presence(account);
4832 tune = purple_presence_get_status(presence, "tune");
4833 if (!tune)
4834 continue;
4835 if (title) {
4836 set_status_with_attrs(tune,
4837 PURPLE_TUNE_TITLE, title,
4838 PURPLE_TUNE_ARTIST, artist,
4839 PURPLE_TUNE_ALBUM, album,
4840 NULL);
4841 } else {
4842 purple_status_set_active(tune, FALSE);
4847 char * purple_util_format_song_info(const char *title, const char *artist, const char *album, gpointer unused)
4849 GString *string;
4850 char *esc;
4852 if (!title || !*title)
4853 return NULL;
4855 esc = g_markup_escape_text(title, -1);
4856 string = g_string_new("");
4857 g_string_append_printf(string, "%s", esc);
4858 g_free(esc);
4860 if (artist && *artist) {
4861 esc = g_markup_escape_text(artist, -1);
4862 g_string_append_printf(string, _(" - %s"), esc);
4863 g_free(esc);
4866 if (album && *album) {
4867 esc = g_markup_escape_text(album, -1);
4868 g_string_append_printf(string, _(" (%s)"), esc);
4869 g_free(esc);
4872 return g_string_free(string, FALSE);
4875 const gchar *
4876 purple_get_host_name(void)
4878 return g_get_host_name();
4881 gchar *
4882 purple_uuid_random(void)
4884 guint32 tmp, a, b;
4886 tmp = g_random_int();
4887 a = 0x4000 | (tmp & 0xFFF); /* 0x4000 to 0x4FFF */
4888 tmp >>= 12;
4889 b = ((1 << 3) << 12) | (tmp & 0x3FFF); /* 0x8000 to 0xBFFF */
4891 tmp = g_random_int();
4893 return g_strdup_printf("%08x-%04x-%04x-%04x-%04x%08x",
4894 g_random_int(),
4895 tmp & 0xFFFF,
4898 (tmp >> 16) & 0xFFFF, g_random_int());
4901 void purple_callback_set_zero(gpointer data)
4903 gpointer *ptr = data;
4905 g_return_if_fail(ptr != NULL);
4907 *ptr = NULL;
4910 GValue *
4911 purple_value_new(GType type)
4913 GValue *ret;
4915 g_return_val_if_fail(type != G_TYPE_NONE, NULL);
4917 ret = g_new0(GValue, 1);
4918 g_value_init(ret, type);
4920 return ret;
4923 GValue *
4924 purple_value_dup(GValue *value)
4926 GValue *ret;
4928 g_return_val_if_fail(value != NULL, NULL);
4930 ret = g_new0(GValue, 1);
4931 g_value_init(ret, G_VALUE_TYPE(value));
4932 g_value_copy(value, ret);
4934 return ret;
4937 void
4938 purple_value_free(GValue *value)
4940 g_return_if_fail(value != NULL);
4942 g_value_unset(value);
4943 g_free(value);
4946 gchar *purple_http_digest_calculate_session_key(
4947 const gchar *algorithm,
4948 const gchar *username,
4949 const gchar *realm,
4950 const gchar *password,
4951 const gchar *nonce,
4952 const gchar *client_nonce)
4954 PurpleHash *hasher;
4955 gchar hash[33]; /* We only support MD5. */
4956 gboolean digest_ok;
4958 g_return_val_if_fail(username != NULL, NULL);
4959 g_return_val_if_fail(realm != NULL, NULL);
4960 g_return_val_if_fail(password != NULL, NULL);
4961 g_return_val_if_fail(nonce != NULL, NULL);
4963 /* Check for a supported algorithm. */
4964 g_return_val_if_fail(algorithm == NULL ||
4965 *algorithm == '\0' ||
4966 g_ascii_strcasecmp(algorithm, "MD5") ||
4967 g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);
4969 hasher = purple_md5_hash_new();
4970 g_return_val_if_fail(hash != NULL, NULL);
4972 purple_hash_append(hasher, (guchar *)username, strlen(username));
4973 purple_hash_append(hasher, (guchar *)":", 1);
4974 purple_hash_append(hasher, (guchar *)realm, strlen(realm));
4975 purple_hash_append(hasher, (guchar *)":", 1);
4976 purple_hash_append(hasher, (guchar *)password, strlen(password));
4978 if (algorithm != NULL && !g_ascii_strcasecmp(algorithm, "MD5-sess"))
4980 guchar digest[16];
4982 if (client_nonce == NULL)
4984 g_object_unref(hasher);
4985 purple_debug_error("hash", "Required client_nonce missing for MD5-sess digest calculation.\n");
4986 return NULL;
4989 purple_hash_digest(hasher, digest, sizeof(digest));
4991 purple_hash_reset(hasher);
4992 purple_hash_append(hasher, digest, sizeof(digest));
4993 purple_hash_append(hasher, (guchar *)":", 1);
4994 purple_hash_append(hasher, (guchar *)nonce, strlen(nonce));
4995 purple_hash_append(hasher, (guchar *)":", 1);
4996 purple_hash_append(hasher, (guchar *)client_nonce, strlen(client_nonce));
4999 digest_ok = purple_hash_digest_to_str(hasher, hash, sizeof(hash));
5000 g_object_unref(hasher);
5002 g_return_val_if_fail(digest_ok, NULL);
5004 return g_strdup(hash);
5007 gchar *purple_http_digest_calculate_response(
5008 const gchar *algorithm,
5009 const gchar *method,
5010 const gchar *digest_uri,
5011 const gchar *qop,
5012 const gchar *entity,
5013 const gchar *nonce,
5014 const gchar *nonce_count,
5015 const gchar *client_nonce,
5016 const gchar *session_key)
5018 PurpleHash *hash;
5019 static gchar hash2[33]; /* We only support MD5. */
5020 gboolean digest_ok;
5022 g_return_val_if_fail(method != NULL, NULL);
5023 g_return_val_if_fail(digest_uri != NULL, NULL);
5024 g_return_val_if_fail(nonce != NULL, NULL);
5025 g_return_val_if_fail(session_key != NULL, NULL);
5027 /* Check for a supported algorithm. */
5028 g_return_val_if_fail(algorithm == NULL ||
5029 *algorithm == '\0' ||
5030 g_ascii_strcasecmp(algorithm, "MD5") ||
5031 g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);
5033 /* Check for a supported "quality of protection". */
5034 g_return_val_if_fail(qop == NULL ||
5035 *qop == '\0' ||
5036 g_ascii_strcasecmp(qop, "auth") ||
5037 g_ascii_strcasecmp(qop, "auth-int"), NULL);
5039 hash = purple_md5_hash_new();
5040 g_return_val_if_fail(hash != NULL, NULL);
5042 purple_hash_append(hash, (guchar *)method, strlen(method));
5043 purple_hash_append(hash, (guchar *)":", 1);
5044 purple_hash_append(hash, (guchar *)digest_uri, strlen(digest_uri));
5046 if (qop != NULL && !g_ascii_strcasecmp(qop, "auth-int"))
5048 PurpleHash *hash2;
5049 gchar entity_hash[33];
5051 if (entity == NULL)
5053 g_object_unref(hash);
5054 purple_debug_error("hash", "Required entity missing for auth-int digest calculation.\n");
5055 return NULL;
5058 hash2 = purple_md5_hash_new();
5059 purple_hash_append(hash2, (guchar *)entity, strlen(entity));
5060 digest_ok = purple_hash_digest_to_str(hash2, entity_hash, sizeof(entity_hash));
5061 g_object_unref(hash2);
5063 if (!digest_ok) {
5064 g_object_unref(hash);
5065 g_return_val_if_reached(NULL);
5068 purple_hash_append(hash, (guchar *)":", 1);
5069 purple_hash_append(hash, (guchar *)entity_hash, strlen(entity_hash));
5072 digest_ok = purple_hash_digest_to_str(hash, hash2, sizeof(hash2));
5073 purple_hash_reset(hash);
5075 if (!digest_ok) {
5076 g_object_unref(hash);
5077 g_return_val_if_reached(NULL);
5080 purple_hash_append(hash, (guchar *)session_key, strlen(session_key));
5081 purple_hash_append(hash, (guchar *)":", 1);
5082 purple_hash_append(hash, (guchar *)nonce, strlen(nonce));
5083 purple_hash_append(hash, (guchar *)":", 1);
5085 if (qop != NULL && *qop != '\0')
5087 if (nonce_count == NULL)
5089 g_object_unref(hash);
5090 purple_debug_error("hash", "Required nonce_count missing for digest calculation.\n");
5091 return NULL;
5094 if (client_nonce == NULL)
5096 g_object_unref(hash);
5097 purple_debug_error("hash", "Required client_nonce missing for digest calculation.\n");
5098 return NULL;
5101 purple_hash_append(hash, (guchar *)nonce_count, strlen(nonce_count));
5102 purple_hash_append(hash, (guchar *)":", 1);
5103 purple_hash_append(hash, (guchar *)client_nonce, strlen(client_nonce));
5104 purple_hash_append(hash, (guchar *)":", 1);
5106 purple_hash_append(hash, (guchar *)qop, strlen(qop));
5108 purple_hash_append(hash, (guchar *)":", 1);
5111 purple_hash_append(hash, (guchar *)hash2, strlen(hash2));
5112 digest_ok = purple_hash_digest_to_str(hash, hash2, sizeof(hash2));
5113 g_object_unref(hash);
5115 g_return_val_if_fail(digest_ok, NULL);
5117 return g_strdup(hash2);
5121 _purple_fstat(int fd, GStatBuf *st)
5123 int ret;
5125 g_return_val_if_fail(st != NULL, -1);
5127 #ifdef _WIN32
5128 ret = _fstat(fd, st);
5129 #else
5130 ret = fstat(fd, st);
5131 #endif
5133 return ret;
5136 #if 0
5138 /* Temporarily removed - re-add this when you need ini file support. */
5140 #define PURPLE_KEY_FILE_DEFAULT_MAX_SIZE 102400
5141 #define PURPLE_KEY_FILE_HARD_LIMIT 10485760
5143 gboolean
5144 purple_key_file_load_from_ini(GKeyFile *key_file, const gchar *file,
5145 gsize max_size)
5147 const gchar *header = "[default]\n\n";
5148 int header_len = strlen(header);
5149 int fd;
5150 GStatBuf st;
5151 gsize file_size, buff_size;
5152 gchar *buff;
5153 GError *error = NULL;
5155 g_return_val_if_fail(key_file != NULL, FALSE);
5156 g_return_val_if_fail(file != NULL, FALSE);
5157 g_return_val_if_fail(max_size < PURPLE_KEY_FILE_HARD_LIMIT, FALSE);
5159 if (max_size == 0)
5160 max_size = PURPLE_KEY_FILE_DEFAULT_MAX_SIZE;
5162 fd = g_open(file, O_RDONLY, S_IREAD);
5163 if (fd == -1) {
5164 purple_debug_error("util", "Failed to read ini file %s", file);
5165 return FALSE;
5168 if (_purple_fstat(fd, &st) != 0) {
5169 purple_debug_error("util", "Failed to fstat ini file %s", file);
5170 return FALSE;
5173 file_size = (st.st_size > max_size) ? max_size : st.st_size;
5175 buff_size = file_size + header_len;
5176 buff = g_new(gchar, buff_size);
5177 memcpy(buff, header, header_len);
5178 if (read(fd, buff + header_len, file_size) != (gssize)file_size) {
5179 purple_debug_error("util",
5180 "Failed to read whole ini file %s", file);
5181 g_close(fd, NULL);
5182 free(buff);
5183 return FALSE;
5185 g_close(fd, NULL);
5187 g_key_file_load_from_data(key_file, buff, buff_size,
5188 G_KEY_FILE_NONE, &error);
5190 free(buff);
5192 if (error) {
5193 purple_debug_error("util", "Failed parsing ini file %s: %s",
5194 file, error->message);
5195 return FALSE;
5198 return TRUE;
5200 #endif