Merged pidgin/main into default
[pidgin-git.git] / libpurple / util.c
blob67e34033a0df1272f85da9ad960bca17f2df7b07
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 "conversation.h"
22 #include "core.h"
23 #include "debug.h"
24 #include "glibcompat.h"
25 #include "notify.h"
26 #include "protocol.h"
27 #include "prefs.h"
28 #include "util.h"
30 #include <json-glib/json-glib.h>
32 struct _PurpleMenuAction
34 char *label;
35 PurpleCallback callback;
36 gpointer data;
37 GList *children;
38 gchar *stock_icon;
41 static char *custom_user_dir = NULL;
42 static char *user_dir = NULL;
43 static char *cache_dir = NULL;
44 static char *config_dir = NULL;
45 static char *data_dir = NULL;
47 static JsonNode *escape_js_node = NULL;
48 static JsonGenerator *escape_js_gen = NULL;
50 static void
51 move_to_xdg_base_dir(const char *purple_xdg_dir, char *subdir)
53 char *xdg_dir;
54 gboolean xdg_dir_exists;
56 xdg_dir_exists = g_file_test(purple_xdg_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
57 if (!xdg_dir_exists) {
58 gint mkdir_res;
60 mkdir_res = purple_build_dir(purple_xdg_dir, (S_IRUSR | S_IWUSR | S_IXUSR));
61 if (mkdir_res == -1) {
62 purple_debug_error("util", "Error creating xdg directory %s: %s; failed migration\n",
63 purple_xdg_dir, g_strerror(errno));
64 return;
68 xdg_dir = g_build_filename(purple_xdg_dir, subdir, NULL);
69 xdg_dir_exists = g_file_test(xdg_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
70 if (!xdg_dir_exists) {
71 char *old_dir;
72 gboolean old_dir_exists;
74 old_dir = g_build_filename(purple_user_dir(), subdir, NULL);
75 old_dir_exists = g_file_test(old_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
77 if (old_dir_exists) {
78 g_rename(old_dir, xdg_dir);
81 g_free(old_dir);
82 old_dir = NULL;
85 g_free(xdg_dir);
86 xdg_dir = NULL;
88 return;
91 /* If legacy directory for libpurple exists, move it to location following
92 * xdg base dir spec. https://developer.pidgin.im/ticket/10029
94 static void
95 migrate_to_xdg_base_dirs(void)
97 const char *legacy_purple_dir;
98 gboolean dir_exists;
100 legacy_purple_dir = purple_user_dir();
101 dir_exists = g_file_test(legacy_purple_dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR);
102 if (dir_exists) {
103 move_to_xdg_base_dir(purple_data_dir(), "certificates");
104 move_to_xdg_base_dir(purple_cache_dir(), "icons");
105 move_to_xdg_base_dir(purple_data_dir(), "logs");
108 return;
111 PurpleMenuAction *
112 purple_menu_action_new(const char *label, PurpleCallback callback, gpointer data,
113 GList *children)
115 PurpleMenuAction *act = g_new0(PurpleMenuAction, 1);
116 act->label = g_strdup(label);
117 act->callback = callback;
118 act->data = data;
119 act->children = children;
120 return act;
123 void
124 purple_menu_action_free(PurpleMenuAction *act)
126 g_return_if_fail(act != NULL);
128 g_free(act->stock_icon);
129 g_free(act->label);
130 g_free(act);
133 char * purple_menu_action_get_label(const PurpleMenuAction *act)
135 g_return_val_if_fail(act != NULL, NULL);
137 return act->label;
140 PurpleCallback purple_menu_action_get_callback(const PurpleMenuAction *act)
142 g_return_val_if_fail(act != NULL, NULL);
144 return act->callback;
147 gpointer purple_menu_action_get_data(const PurpleMenuAction *act)
149 g_return_val_if_fail(act != NULL, NULL);
151 return act->data;
154 GList* purple_menu_action_get_children(const PurpleMenuAction *act)
156 g_return_val_if_fail(act != NULL, NULL);
158 return act->children;
161 void purple_menu_action_set_label(PurpleMenuAction *act, char *label)
163 g_return_if_fail(act != NULL);
165 act-> label = label;
168 void purple_menu_action_set_callback(PurpleMenuAction *act, PurpleCallback callback)
170 g_return_if_fail(act != NULL);
172 act->callback = callback;
175 void purple_menu_action_set_data(PurpleMenuAction *act, gpointer data)
177 g_return_if_fail(act != NULL);
179 act->data = data;
182 void purple_menu_action_set_children(PurpleMenuAction *act, GList *children)
184 g_return_if_fail(act != NULL);
186 act->children = children;
189 void purple_menu_action_set_stock_icon(PurpleMenuAction *act,
190 const gchar *stock)
192 g_return_if_fail(act != NULL);
194 g_free(act->stock_icon);
195 act->stock_icon = g_strdup(stock);
198 const gchar *
199 purple_menu_action_get_stock_icon(PurpleMenuAction *act)
201 return act->stock_icon;
204 void
205 purple_util_init(void)
207 escape_js_node = json_node_new(JSON_NODE_VALUE);
208 escape_js_gen = json_generator_new();
209 json_node_set_boolean(escape_js_node, FALSE);
211 migrate_to_xdg_base_dirs();
214 void
215 purple_util_uninit(void)
217 /* Free these so we don't have leaks at shutdown. */
219 g_free(custom_user_dir);
220 custom_user_dir = NULL;
222 g_free(user_dir);
223 user_dir = NULL;
225 g_free(cache_dir);
226 cache_dir = NULL;
228 g_free(config_dir);
229 config_dir = NULL;
231 g_free(data_dir);
232 data_dir = NULL;
234 json_node_free(escape_js_node);
235 escape_js_node = NULL;
237 g_object_unref(escape_js_gen);
238 escape_js_gen = NULL;
241 /**************************************************************************
242 * Base16 Functions
243 **************************************************************************/
244 gchar *
245 purple_base16_encode(const guchar *data, gsize len)
247 gsize i;
248 gchar *ascii = NULL;
250 g_return_val_if_fail(data != NULL, NULL);
251 g_return_val_if_fail(len > 0, NULL);
253 ascii = g_malloc(len * 2 + 1);
255 for (i = 0; i < len; i++)
256 g_snprintf(&ascii[i * 2], 3, "%02x", data[i] & 0xFF);
258 return ascii;
261 guchar *
262 purple_base16_decode(const char *str, gsize *ret_len)
264 gsize len, i, accumulator = 0;
265 guchar *data;
267 g_return_val_if_fail(str != NULL, NULL);
269 len = strlen(str);
271 g_return_val_if_fail(*str, 0);
272 g_return_val_if_fail(len % 2 == 0, 0);
274 data = g_malloc(len / 2);
276 for (i = 0; i < len; i++)
278 if ((i % 2) == 0)
279 accumulator = 0;
280 else
281 accumulator <<= 4;
283 if (isdigit(str[i]))
284 accumulator |= str[i] - 48;
285 else
287 switch(tolower(str[i]))
289 case 'a': accumulator |= 10; break;
290 case 'b': accumulator |= 11; break;
291 case 'c': accumulator |= 12; break;
292 case 'd': accumulator |= 13; break;
293 case 'e': accumulator |= 14; break;
294 case 'f': accumulator |= 15; break;
298 if (i % 2)
299 data[(i - 1) / 2] = accumulator;
302 if (ret_len != NULL)
303 *ret_len = len / 2;
305 return data;
308 gchar *
309 purple_base16_encode_chunked(const guchar *data, gsize len)
311 gsize i;
312 gchar *ascii = NULL;
314 g_return_val_if_fail(data != NULL, NULL);
315 g_return_val_if_fail(len > 0, NULL);
317 /* For each byte of input, we need 2 bytes for the hex representation
318 * and 1 for the colon.
319 * The final colon will be replaced by a terminating NULL
321 ascii = g_malloc(len * 3 + 1);
323 for (i = 0; i < len; i++)
324 g_snprintf(&ascii[i * 3], 4, "%02x:", data[i] & 0xFF);
326 /* Replace the final colon with NULL */
327 ascii[len * 3 - 1] = 0;
329 return ascii;
332 /**************************************************************************
333 * Quoted Printable Functions (see RFC 2045).
334 **************************************************************************/
335 static const char xdigits[] =
336 "0123456789abcdef";
338 guchar *
339 purple_quotedp_decode(const char *str, gsize *ret_len)
341 char *n, *new;
342 const char *end, *p;
344 n = new = g_malloc(strlen (str) + 1);
345 end = str + strlen(str);
347 for (p = str; p < end; p++, n++) {
348 if (*p == '=') {
349 if (p[1] == '\r' && p[2] == '\n') { /* 5.1 #5 */
350 n -= 1;
351 p += 2;
352 } else if (p[1] == '\n') { /* fuzzy case for 5.1 #5 */
353 n -= 1;
354 p += 1;
355 } else if (p[1] && p[2]) {
356 char *nibble1 = strchr(xdigits, tolower(p[1]));
357 char *nibble2 = strchr(xdigits, tolower(p[2]));
358 if (nibble1 && nibble2) { /* 5.1 #1 */
359 *n = ((nibble1 - xdigits) << 4) | (nibble2 - xdigits);
360 p += 2;
361 } else { /* This should never happen */
362 *n = *p;
364 } else { /* This should never happen */
365 *n = *p;
368 else if (*p == '_')
369 *n = ' ';
370 else
371 *n = *p;
374 *n = '\0';
376 if (ret_len != NULL)
377 *ret_len = n - new;
379 /* Resize to take less space */
380 /* new = realloc(new, n - new); */
382 return (guchar *)new;
385 /**************************************************************************
386 * MIME Functions
387 **************************************************************************/
388 char *
389 purple_mime_decode_field(const char *str)
392 * This is wing's version, partially based on revo/shx's version
393 * See RFC2047 [which apparently obsoletes RFC1342]
395 typedef enum {
396 state_start, state_equal1, state_question1,
397 state_charset, state_question2,
398 state_encoding, state_question3,
399 state_encoded_text, state_question4, state_equal2 = state_start
400 } encoded_word_state_t;
401 encoded_word_state_t state = state_start;
402 const char *cur, *mark;
403 const char *charset0 = NULL, *encoding0 = NULL, *encoded_text0 = NULL;
404 GString *new;
406 /* token can be any CHAR (supposedly ISO8859-1/ISO2022), not just ASCII */
407 #define token_char_p(c) \
408 (c != ' ' && !iscntrl(c) && !strchr("()<>@,;:\"/[]?.=", c))
410 /* But encoded-text must be ASCII; alas, isascii() may not exist */
411 #define encoded_text_char_p(c) \
412 ((c & 0x80) == 0 && c != '?' && c != ' ' && isgraph(c))
414 g_return_val_if_fail(str != NULL, NULL);
416 new = g_string_new(NULL);
418 /* Here we will be looking for encoded words and if they seem to be
419 * valid then decode them.
420 * They are of this form: =?charset?encoding?text?=
423 for (cur = str, mark = NULL; *cur; cur += 1) {
424 switch (state) {
425 case state_equal1:
426 if (*cur == '?') {
427 state = state_question1;
428 } else {
429 g_string_append_len(new, mark, cur - mark + 1);
430 state = state_start;
432 break;
433 case state_question1:
434 if (token_char_p(*cur)) {
435 charset0 = cur;
436 state = state_charset;
437 } else { /* This should never happen */
438 g_string_append_len(new, mark, cur - mark + 1);
439 state = state_start;
441 break;
442 case state_charset:
443 if (*cur == '?') {
444 state = state_question2;
445 } else if (!token_char_p(*cur)) { /* This should never happen */
446 g_string_append_len(new, mark, cur - mark + 1);
447 state = state_start;
449 break;
450 case state_question2:
451 if (token_char_p(*cur)) {
452 encoding0 = cur;
453 state = state_encoding;
454 } else { /* This should never happen */
455 g_string_append_len(new, mark, cur - mark + 1);
456 state = state_start;
458 break;
459 case state_encoding:
460 if (*cur == '?') {
461 state = state_question3;
462 } else if (!token_char_p(*cur)) { /* This should never happen */
463 g_string_append_len(new, mark, cur - mark + 1);
464 state = state_start;
466 break;
467 case state_question3:
468 if (encoded_text_char_p(*cur)) {
469 encoded_text0 = cur;
470 state = state_encoded_text;
471 } else if (*cur == '?') { /* empty string */
472 encoded_text0 = cur;
473 state = state_question4;
474 } else { /* This should never happen */
475 g_string_append_len(new, mark, cur - mark + 1);
476 state = state_start;
478 break;
479 case state_encoded_text:
480 if (*cur == '?') {
481 state = state_question4;
482 } else if (!encoded_text_char_p(*cur)) {
483 g_string_append_len(new, mark, cur - mark + 1);
484 state = state_start;
486 break;
487 case state_question4:
488 if (*cur == '=') { /* Got the whole encoded-word */
489 char *charset = g_strndup(charset0, encoding0 - charset0 - 1);
490 char *encoding = g_strndup(encoding0, encoded_text0 - encoding0 - 1);
491 char *encoded_text = g_strndup(encoded_text0, cur - encoded_text0 - 1);
492 guchar *decoded = NULL;
493 gsize dec_len;
494 if (g_ascii_strcasecmp(encoding, "Q") == 0)
495 decoded = purple_quotedp_decode(encoded_text, &dec_len);
496 else if (g_ascii_strcasecmp(encoding, "B") == 0)
497 decoded = g_base64_decode(encoded_text, &dec_len);
498 else
499 decoded = NULL;
500 if (decoded) {
501 gsize len;
502 char *converted = g_convert((const gchar *)decoded, dec_len, "utf-8", charset, NULL, &len, NULL);
504 if (converted) {
505 g_string_append_len(new, converted, len);
506 g_free(converted);
508 g_free(decoded);
510 g_free(charset);
511 g_free(encoding);
512 g_free(encoded_text);
513 state = state_equal2; /* Restart the FSM */
514 } else { /* This should never happen */
515 g_string_append_len(new, mark, cur - mark + 1);
516 state = state_start;
518 break;
519 default:
520 if (*cur == '=') {
521 mark = cur;
522 state = state_equal1;
523 } else {
524 /* Some unencoded text. */
525 g_string_append_c(new, *cur);
527 break;
528 } /* switch */
529 } /* for */
531 if (state != state_start)
532 g_string_append_len(new, mark, cur - mark + 1);
534 return g_string_free(new, FALSE);;
538 /**************************************************************************
539 * Date/Time Functions
540 **************************************************************************/
542 const char *purple_get_tzoff_str(const struct tm *tm, gboolean iso)
544 static char buf[7];
545 long off;
546 gint8 min;
547 gint8 hrs;
548 struct tm new_tm = *tm;
550 mktime(&new_tm);
552 if (new_tm.tm_isdst < 0)
553 g_return_val_if_reached("");
555 #ifdef _WIN32
556 if ((off = wpurple_get_tz_offset()) == -1)
557 return "";
558 #elif defined(HAVE_TM_GMTOFF)
559 off = new_tm.tm_gmtoff;
560 #elif defined(HAVE_TIMEZONE)
561 tzset();
562 off = -1 * timezone;
563 #else
564 purple_debug_warning("util",
565 "there is no possibility to obtain tz offset");
566 return "";
567 #endif
569 min = (off / 60) % 60;
570 hrs = ((off / 60) - min) / 60;
572 if(iso) {
573 if (0 == off) {
574 strcpy(buf, "Z");
575 } else {
576 /* please leave the colons...they're optional for iso, but jabber
577 * wants them */
578 if(g_snprintf(buf, sizeof(buf), "%+03d:%02d", hrs, ABS(min)) > 6)
579 g_return_val_if_reached("");
581 } else {
582 if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5)
583 g_return_val_if_reached("");
586 return buf;
589 /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */
590 #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32)
591 static size_t purple_internal_strftime(char *s, size_t max, const char *format, const struct tm *tm)
593 const char *start;
594 const char *c;
595 char *fmt = NULL;
597 /* Yes, this is checked in purple_utf8_strftime(),
598 * but better safe than sorry. -- rlaager */
599 g_return_val_if_fail(format != NULL, 0);
601 /* This is fairly efficient, and it only gets
602 * executed on Windows or if the underlying
603 * system doesn't support the %z format string,
604 * for strftime() so I think it's good enough.
605 * -- rlaager */
606 for (c = start = format; *c ; c++)
608 if (*c != '%')
609 continue;
611 c++;
613 #ifndef HAVE_STRFTIME_Z_FORMAT
614 if (*c == 'z')
616 char *tmp = g_strdup_printf("%s%.*s%s",
617 fmt ? fmt : "",
618 (int)(c - start - 1),
619 start,
620 purple_get_tzoff_str(tm, FALSE));
621 g_free(fmt);
622 fmt = tmp;
623 start = c + 1;
625 #endif
626 #ifdef _WIN32
627 if (*c == 'Z')
629 char *tmp = g_strdup_printf("%s%.*s%s",
630 fmt ? fmt : "",
631 (int)(c - start - 1),
632 start,
633 wpurple_get_timezone_abbreviation(tm));
634 g_free(fmt);
635 fmt = tmp;
636 start = c + 1;
638 #endif
641 if (fmt != NULL)
643 size_t ret;
645 if (*start)
647 char *tmp = g_strconcat(fmt, start, NULL);
648 g_free(fmt);
649 fmt = tmp;
652 ret = strftime(s, max, fmt, tm);
653 g_free(fmt);
655 return ret;
658 return strftime(s, max, format, tm);
660 #else /* HAVE_STRFTIME_Z_FORMAT && !_WIN32 */
661 #define purple_internal_strftime strftime
662 #endif
664 const char *
665 purple_utf8_strftime(const char *format, const struct tm *tm)
667 static char buf[128];
668 char *locale;
669 GError *err = NULL;
670 int len;
671 char *utf8;
673 g_return_val_if_fail(format != NULL, NULL);
675 if (tm == NULL)
677 time_t now = time(NULL);
678 tm = localtime(&now);
681 locale = g_locale_from_utf8(format, -1, NULL, NULL, &err);
682 if (err != NULL)
684 purple_debug_error("util", "Format conversion failed in purple_utf8_strftime(): %s\n", err->message);
685 g_error_free(err);
686 err = NULL;
687 locale = g_strdup(format);
690 /* A return value of 0 is either an error (in
691 * which case, the contents of the buffer are
692 * undefined) or the empty string (in which
693 * case, no harm is done here). */
694 if ((len = purple_internal_strftime(buf, sizeof(buf), locale, tm)) == 0)
696 g_free(locale);
697 return "";
700 g_free(locale);
702 utf8 = g_locale_to_utf8(buf, len, NULL, NULL, &err);
703 if (err != NULL)
705 purple_debug_error("util", "Result conversion failed in purple_utf8_strftime(): %s\n", err->message);
706 g_error_free(err);
708 else
710 g_strlcpy(buf, utf8, sizeof(buf));
711 g_free(utf8);
714 return buf;
717 const char *
718 purple_date_format_short(const struct tm *tm)
720 return purple_utf8_strftime("%x", tm);
723 const char *
724 purple_date_format_long(const struct tm *tm)
727 * This string determines how some dates are displayed. The default
728 * string "%x %X" shows the date then the time. Translators can
729 * change this to "%X %x" if they want the time to be shown first,
730 * followed by the date.
732 return purple_utf8_strftime(_("%x %X"), tm);
735 const char *
736 purple_date_format_full(const struct tm *tm)
738 return purple_utf8_strftime("%c", tm);
741 const char *
742 purple_time_format(const struct tm *tm)
744 return purple_utf8_strftime("%X", tm);
747 time_t
748 purple_time_build(int year, int month, int day, int hour, int min, int sec)
750 struct tm tm;
752 tm.tm_year = year - 1900;
753 tm.tm_mon = month - 1;
754 tm.tm_mday = day;
755 tm.tm_hour = hour;
756 tm.tm_min = min;
757 tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60;
759 return mktime(&tm);
762 /* originally taken from GLib trunk 1-6-11 */
763 /* originally licensed as LGPL 2+ */
764 static time_t
765 mktime_utc(struct tm *tm)
767 time_t retval;
769 #ifndef HAVE_TIMEGM
770 static const gint days_before[] =
772 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
774 #endif
776 #ifndef HAVE_TIMEGM
777 if (tm->tm_mon < 0 || tm->tm_mon > 11)
778 return (time_t) -1;
780 retval = (tm->tm_year - 70) * 365;
781 retval += (tm->tm_year - 68) / 4;
782 retval += days_before[tm->tm_mon] + tm->tm_mday - 1;
784 if (tm->tm_year % 4 == 0 && tm->tm_mon < 2)
785 retval -= 1;
787 retval = ((((retval * 24) + tm->tm_hour) * 60) + tm->tm_min) * 60 + tm->tm_sec;
788 #else
789 retval = timegm (tm);
790 #endif /* !HAVE_TIMEGM */
792 return retval;
795 time_t
796 purple_str_to_time(const char *timestamp, gboolean utc,
797 struct tm *tm, long *tz_off, const char **rest)
799 struct tm t;
800 const gchar *str;
801 gint year = 0;
802 long tzoff = PURPLE_NO_TZ_OFF;
803 time_t retval;
804 gboolean mktime_with_utc = FALSE;
806 if (rest != NULL)
807 *rest = NULL;
809 g_return_val_if_fail(timestamp != NULL, 0);
811 memset(&t, 0, sizeof(struct tm));
813 str = timestamp;
815 /* Strip leading whitespace */
816 while (g_ascii_isspace(*str))
817 str++;
819 if (*str == '\0') {
820 if (rest != NULL && *str != '\0')
821 *rest = str;
823 return 0;
826 if (!g_ascii_isdigit(*str) && *str != '-' && *str != '+') {
827 if (rest != NULL && *str != '\0')
828 *rest = str;
830 return 0;
833 /* 4 digit year */
834 if (sscanf(str, "%04d", &year) && year >= 1900) {
835 str += 4;
837 if (*str == '-' || *str == '/')
838 str++;
840 t.tm_year = year - 1900;
843 /* 2 digit month */
844 if (!sscanf(str, "%02d", &t.tm_mon)) {
845 if (rest != NULL && *str != '\0')
846 *rest = str;
848 return 0;
851 str += 2;
852 t.tm_mon -= 1;
854 if (*str == '-' || *str == '/')
855 str++;
857 /* 2 digit day */
858 if (!sscanf(str, "%02d", &t.tm_mday)) {
859 if (rest != NULL && *str != '\0')
860 *rest = str;
862 return 0;
865 str += 2;
867 /* Grab the year off the end if there's still stuff */
868 if (*str == '/' || *str == '-') {
869 /* But make sure we don't read the year twice */
870 if (year >= 1900) {
871 if (rest != NULL && *str != '\0')
872 *rest = str;
874 return 0;
877 str++;
879 if (!sscanf(str, "%04d", &t.tm_year)) {
880 if (rest != NULL && *str != '\0')
881 *rest = str;
883 return 0;
886 t.tm_year -= 1900;
887 } else if (*str == 'T' || *str == '.') {
888 str++;
890 /* Continue grabbing the hours/minutes/seconds */
891 if ((sscanf(str, "%02d:%02d:%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
892 (str += 8)) ||
893 (sscanf(str, "%02d%02d%02d", &t.tm_hour, &t.tm_min, &t.tm_sec) == 3 &&
894 (str += 6)))
896 gint sign, tzhrs, tzmins;
898 if (*str == '.') {
899 /* Cut off those pesky micro-seconds */
900 do {
901 str++;
902 } while (*str >= '0' && *str <= '9');
905 sign = (*str == '+') ? 1 : -1;
907 /* Process the timezone */
908 if (*str == '+' || *str == '-') {
909 str++;
911 if (((sscanf(str, "%02d:%02d", &tzhrs, &tzmins) == 2 && (str += 5)) ||
912 (sscanf(str, "%02d%02d", &tzhrs, &tzmins) == 2 && (str += 4))))
914 mktime_with_utc = TRUE;
915 tzoff = tzhrs * 60 * 60 + tzmins * 60;
916 tzoff *= sign;
918 } else if (*str == 'Z') {
919 /* 'Z' = Zulu = UTC */
920 str++;
921 mktime_with_utc = TRUE;
922 tzoff = 0;
925 if (!mktime_with_utc)
927 /* No timezone specified. */
929 if (utc) {
930 mktime_with_utc = TRUE;
931 tzoff = 0;
932 } else {
933 /* Local Time */
934 t.tm_isdst = -1;
940 if (rest != NULL && *str != '\0') {
941 /* Strip trailing whitespace */
942 while (g_ascii_isspace(*str))
943 str++;
945 if (*str != '\0')
946 *rest = str;
949 if (mktime_with_utc)
950 retval = mktime_utc(&t);
951 else
952 retval = mktime(&t);
954 if (tm != NULL)
955 *tm = t;
957 if (tzoff != PURPLE_NO_TZ_OFF)
958 retval -= tzoff;
960 if (tz_off != NULL)
961 *tz_off = tzoff;
963 return retval;
966 char *
967 purple_uts35_to_str(const char *format, size_t len, struct tm *tm)
969 GString *string;
970 guint i, count;
972 if (tm == NULL) {
973 time_t now = time(NULL);
974 tm = localtime(&now);
977 string = g_string_sized_new(len);
978 i = 0;
979 while (i < len) {
980 count = 1;
981 while ((i + count) < len && format[i] == format[i+count])
982 count++;
984 switch (format[i]) {
985 /* Era Designator */
986 case 'G':
987 if (count <= 3) {
988 /* Abbreviated */
989 } else if (count == 4) {
990 /* Full */
991 } else if (count >= 5) {
992 /* Narrow */
993 count = 5;
995 break;
998 /* Year */
999 case 'y':
1000 if (count == 2) {
1001 /* Two-digits only */
1002 g_string_append(string, purple_utf8_strftime("%y", tm));
1003 } else {
1004 /* Zero-padding */
1005 char *tmp = g_strdup_printf("%%0%dY", count);
1006 g_string_append(string, purple_utf8_strftime(tmp, tm));
1007 g_free(tmp);
1009 break;
1011 /* Year (in "Week of Year" based calendars) */
1012 case 'Y':
1013 if (count == 2) {
1014 /* Two-digits only */
1015 } else {
1016 /* Zero-padding */
1018 break;
1020 /* Extended Year */
1021 case 'u':
1022 break;
1024 /* Cyclic Year Name */
1025 case 'U':
1026 if (count <= 3) {
1027 /* Abbreviated */
1028 } else if (count == 4) {
1029 /* Full */
1030 } else if (count >= 5) {
1031 /* Narrow */
1032 count = 5;
1034 break;
1037 /* Quarter */
1038 case 'Q':
1039 if (count <= 2) {
1040 /* Numerical */
1041 } else if (count == 3) {
1042 /* Abbreviation */
1043 } else if (count >= 4) {
1044 /* Full */
1045 count = 4;
1047 break;
1049 /* Stand-alone Quarter */
1050 case 'q':
1051 if (count <= 2) {
1052 /* Numerical */
1053 } else if (count == 3) {
1054 /* Abbreviation */
1055 } else if (count >= 4) {
1056 /* Full */
1057 count = 4;
1059 break;
1061 /* Month */
1062 case 'M':
1063 if (count <= 2) {
1064 /* Numerical */
1065 g_string_append(string, purple_utf8_strftime("%m", tm));
1066 } else if (count == 3) {
1067 /* Abbreviation */
1068 g_string_append(string, purple_utf8_strftime("%b", tm));
1069 } else if (count == 4) {
1070 /* Full */
1071 g_string_append(string, purple_utf8_strftime("%B", tm));
1072 } else if (count >= 5) {
1073 g_string_append_len(string, purple_utf8_strftime("%b", tm), 1);
1074 count = 5;
1076 break;
1078 /* Stand-alone Month */
1079 case 'L':
1080 if (count <= 2) {
1081 /* Numerical */
1082 g_string_append(string, purple_utf8_strftime("%m", tm));
1083 } else if (count == 3) {
1084 /* Abbreviation */
1085 g_string_append(string, purple_utf8_strftime("%b", tm));
1086 } else if (count == 4) {
1087 /* Full */
1088 g_string_append(string, purple_utf8_strftime("%B", tm));
1089 } else if (count >= 5) {
1090 g_string_append_len(string, purple_utf8_strftime("%b", tm), 1);
1091 count = 5;
1093 break;
1095 /* Ignored */
1096 case 'l':
1097 break;
1100 /* Week of Year */
1101 case 'w':
1102 g_string_append(string, purple_utf8_strftime("%W", tm));
1103 count = MIN(count, 2);
1104 break;
1106 /* Week of Month */
1107 case 'W':
1108 count = 1;
1109 break;
1112 /* Day of Month */
1113 case 'd':
1114 g_string_append(string, purple_utf8_strftime("%d", tm));
1115 count = MIN(count, 2);
1116 break;
1118 /* Day of Year */
1119 case 'D':
1120 g_string_append(string, purple_utf8_strftime("%j", tm));
1121 count = MIN(count, 3);
1122 break;
1124 /* Day of Year in Month */
1125 case 'F':
1126 count = 1;
1127 break;
1129 /* Modified Julian Day */
1130 case 'g':
1131 break;
1134 /* Day of Week */
1135 case 'E':
1136 if (count <= 3) {
1137 /* Short */
1138 g_string_append(string, purple_utf8_strftime("%a", tm));
1139 } else if (count == 4) {
1140 /* Full */
1141 g_string_append(string, purple_utf8_strftime("%A", tm));
1142 } else if (count >= 5) {
1143 /* Narrow */
1144 g_string_append_len(string, purple_utf8_strftime("%a", tm), 1);
1145 count = 5;
1147 break;
1149 /* Local Day of Week */
1150 case 'e':
1151 if (count <= 2) {
1152 /* Numeric */
1153 g_string_append(string, purple_utf8_strftime("%u", tm));
1154 } else if (count == 3) {
1155 /* Short */
1156 g_string_append(string, purple_utf8_strftime("%a", tm));
1157 } else if (count == 4) {
1158 /* Full */
1159 g_string_append(string, purple_utf8_strftime("%A", tm));
1160 } else if (count >= 5) {
1161 /* Narrow */
1162 g_string_append_len(string, purple_utf8_strftime("%a", tm), 1);
1163 count = 5;
1165 break;
1167 /* Stand-alone Local Day of Week */
1168 case 'c':
1169 if (count <= 2) {
1170 /* Numeric */
1171 g_string_append(string, purple_utf8_strftime("%u", tm));
1172 count = 1;
1173 } else if (count == 3) {
1174 /* Short */
1175 g_string_append(string, purple_utf8_strftime("%a", tm));
1176 } else if (count == 4) {
1177 /* Full */
1178 g_string_append(string, purple_utf8_strftime("%A", tm));
1179 } else if (count >= 5) {
1180 /* Narrow */
1181 g_string_append_len(string, purple_utf8_strftime("%a", tm), 1);
1182 count = 5;
1184 break;
1187 /* AM/PM */
1188 case 'a':
1189 g_string_append(string, purple_utf8_strftime("%p", tm));
1190 break;
1193 /* Hour (1-12) */
1194 case 'h':
1195 if (count == 1) {
1196 /* No padding */
1197 g_string_append(string, purple_utf8_strftime("%I", tm));
1198 } else if (count >= 2) {
1199 /* Zero-padded */
1200 g_string_append(string, purple_utf8_strftime("%I", tm));
1201 count = 2;
1203 break;
1205 /* Hour (0-23) */
1206 case 'H':
1207 if (count == 1) {
1208 /* No padding */
1209 g_string_append(string, purple_utf8_strftime("%H", tm));
1210 } else if (count >= 2) {
1211 /* Zero-padded */
1212 g_string_append(string, purple_utf8_strftime("%H", tm));
1213 count = 2;
1215 break;
1217 /* Hour (0-11) */
1218 case 'K':
1219 if (count == 1) {
1220 /* No padding */
1221 } else if (count >= 2) {
1222 /* Zero-padded */
1223 count = 2;
1225 break;
1227 /* Hour (1-24) */
1228 case 'k':
1229 if (count == 1) {
1230 /* No padding */
1231 } else if (count >= 2) {
1232 /* Zero-padded */
1233 count = 2;
1235 break;
1237 /* Hour (hHkK by locale) */
1238 case 'j':
1239 break;
1242 /* Minute */
1243 case 'm':
1244 g_string_append(string, purple_utf8_strftime("%M", tm));
1245 count = MIN(count, 2);
1246 break;
1249 /* Second */
1250 case 's':
1251 g_string_append(string, purple_utf8_strftime("%S", tm));
1252 count = MIN(count, 2);
1253 break;
1255 /* Fractional Sub-second */
1256 case 'S':
1257 break;
1259 /* Millisecond */
1260 case 'A':
1261 break;
1264 /* Time Zone (specific non-location format) */
1265 case 'z':
1266 if (count <= 3) {
1267 /* Short */
1268 } else if (count >= 4) {
1269 /* Full */
1270 count = 4;
1272 break;
1274 /* Time Zone */
1275 case 'Z':
1276 if (count <= 3) {
1277 /* RFC822 */
1278 g_string_append(string, purple_utf8_strftime("%z", tm));
1279 } else if (count == 4) {
1280 /* Localized GMT */
1281 } else if (count >= 5) {
1282 /* ISO8601 */
1283 g_string_append(string, purple_utf8_strftime("%z", tm));
1284 count = 5;
1286 break;
1288 /* Time Zone (generic non-location format) */
1289 case 'v':
1290 if (count <= 3) {
1291 /* Short */
1292 g_string_append(string, purple_utf8_strftime("%Z", tm));
1293 count = 1;
1294 } else if (count >= 4) {
1295 /* Long */
1296 g_string_append(string, purple_utf8_strftime("%Z", tm));
1297 count = 4;
1299 break;
1301 /* Time Zone */
1302 case 'V':
1303 if (count <= 3) {
1304 /* Same as z */
1305 count = 1;
1306 } else if (count >= 4) {
1307 /* Generic Location Format) */
1308 g_string_append(string, purple_utf8_strftime("%Z", tm));
1309 count = 4;
1311 break;
1314 default:
1315 g_string_append_len(string, format + i, count);
1316 break;
1319 i += count;
1322 return g_string_free(string, FALSE);
1325 /**************************************************************************
1326 * Markup Functions
1327 **************************************************************************/
1330 * This function is stolen from glib's gmarkup.c and modified to not
1331 * replace ' with &apos;
1333 static void append_escaped_text(GString *str,
1334 const gchar *text, gssize length)
1336 const gchar *p;
1337 const gchar *end;
1338 gunichar c;
1340 p = text;
1341 end = text + length;
1343 while (p != end)
1345 const gchar *next;
1346 next = g_utf8_next_char (p);
1348 switch (*p)
1350 case '&':
1351 g_string_append (str, "&amp;");
1352 break;
1354 case '<':
1355 g_string_append (str, "&lt;");
1356 break;
1358 case '>':
1359 g_string_append (str, "&gt;");
1360 break;
1362 case '"':
1363 g_string_append (str, "&quot;");
1364 break;
1366 default:
1367 c = g_utf8_get_char (p);
1368 if ((0x1 <= c && c <= 0x8) ||
1369 (0xb <= c && c <= 0xc) ||
1370 (0xe <= c && c <= 0x1f) ||
1371 (0x7f <= c && c <= 0x84) ||
1372 (0x86 <= c && c <= 0x9f))
1373 g_string_append_printf (str, "&#x%x;", c);
1374 else
1375 g_string_append_len (str, p, next - p);
1376 break;
1379 p = next;
1383 /* This function is stolen from glib's gmarkup.c */
1384 gchar *purple_markup_escape_text(const gchar *text, gssize length)
1386 GString *str;
1388 g_return_val_if_fail(text != NULL, NULL);
1390 if (length < 0)
1391 length = strlen(text);
1393 /* prealloc at least as long as original text */
1394 str = g_string_sized_new(length);
1395 append_escaped_text(str, text, length);
1397 return g_string_free(str, FALSE);
1400 const char *
1401 purple_markup_unescape_entity(const char *text, int *length)
1403 const char *pln;
1404 int len;
1406 if (!text || *text != '&')
1407 return NULL;
1409 #define IS_ENTITY(s) (!g_ascii_strncasecmp(text, s, (len = sizeof(s) - 1)))
1411 if(IS_ENTITY("&amp;"))
1412 pln = "&";
1413 else if(IS_ENTITY("&lt;"))
1414 pln = "<";
1415 else if(IS_ENTITY("&gt;"))
1416 pln = ">";
1417 else if(IS_ENTITY("&nbsp;"))
1418 pln = " ";
1419 else if(IS_ENTITY("&copy;"))
1420 pln = "\302\251"; /* or use g_unichar_to_utf8(0xa9); */
1421 else if(IS_ENTITY("&quot;"))
1422 pln = "\"";
1423 else if(IS_ENTITY("&reg;"))
1424 pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */
1425 else if(IS_ENTITY("&apos;"))
1426 pln = "\'";
1427 else if(text[1] == '#' && g_ascii_isxdigit(text[2])) {
1428 static char buf[7];
1429 const char *start = text + 2;
1430 char *end;
1431 guint64 pound;
1432 int base = 10;
1433 int buflen;
1435 if (*start == 'x') {
1436 base = 16;
1437 start++;
1440 pound = g_ascii_strtoull(start, &end, base);
1441 if (pound == 0 || pound > INT_MAX || *end != ';') {
1442 return NULL;
1445 len = (end - text) + 1;
1447 buflen = g_unichar_to_utf8((gunichar)pound, buf);
1448 buf[buflen] = '\0';
1449 pln = buf;
1451 else
1452 return NULL;
1454 if (length)
1455 *length = len;
1456 return pln;
1459 char *
1460 purple_markup_get_css_property(const gchar *style,
1461 const gchar *opt)
1463 const gchar *css_str = style;
1464 const gchar *css_value_start;
1465 const gchar *css_value_end;
1466 gchar *tmp;
1467 gchar *ret;
1469 g_return_val_if_fail(opt != NULL, NULL);
1471 if (!css_str)
1472 return NULL;
1474 /* find the CSS property */
1475 while (1)
1477 /* skip whitespace characters */
1478 while (*css_str && g_ascii_isspace(*css_str))
1479 css_str++;
1480 if (!g_ascii_isalpha(*css_str))
1481 return NULL;
1482 if (g_ascii_strncasecmp(css_str, opt, strlen(opt)))
1484 /* go to next css property positioned after the next ';' */
1485 while (*css_str && *css_str != '"' && *css_str != ';')
1486 css_str++;
1487 if(*css_str != ';')
1488 return NULL;
1489 css_str++;
1491 else
1492 break;
1495 /* find the CSS value position in the string */
1496 css_str += strlen(opt);
1497 while (*css_str && g_ascii_isspace(*css_str))
1498 css_str++;
1499 if (*css_str != ':')
1500 return NULL;
1501 css_str++;
1502 while (*css_str && g_ascii_isspace(*css_str))
1503 css_str++;
1504 if (*css_str == '\0' || *css_str == '"' || *css_str == ';')
1505 return NULL;
1507 /* mark the CSS value */
1508 css_value_start = css_str;
1509 while (*css_str && *css_str != '"' && *css_str != ';')
1510 css_str++;
1511 css_value_end = css_str - 1;
1513 /* Removes trailing whitespace */
1514 while (css_value_end > css_value_start && g_ascii_isspace(*css_value_end))
1515 css_value_end--;
1517 tmp = g_strndup(css_value_start, css_value_end - css_value_start + 1);
1518 ret = purple_unescape_html(tmp);
1519 g_free(tmp);
1521 return ret;
1524 gboolean purple_markup_is_rtl(const char *html)
1526 GData *attributes;
1527 const gchar *start, *end;
1528 gboolean res = FALSE;
1530 if (purple_markup_find_tag("span", html, &start, &end, &attributes))
1532 /* tmp is a member of attributes and is free with g_datalist_clear call */
1533 const char *tmp = g_datalist_get_data(&attributes, "dir");
1534 if (tmp && !g_ascii_strcasecmp(tmp, "RTL"))
1535 res = TRUE;
1536 if (!res)
1538 tmp = g_datalist_get_data(&attributes, "style");
1539 if (tmp)
1541 char *tmp2 = purple_markup_get_css_property(tmp, "direction");
1542 if (tmp2 && !g_ascii_strcasecmp(tmp2, "RTL"))
1543 res = TRUE;
1544 g_free(tmp2);
1548 g_datalist_clear(&attributes);
1550 return res;
1553 gboolean
1554 purple_markup_find_tag(const char *needle, const char *haystack,
1555 const char **start, const char **end, GData **attributes)
1557 GData *attribs;
1558 const char *cur = haystack;
1559 char *name = NULL;
1560 gboolean found = FALSE;
1561 gboolean in_tag = FALSE;
1562 gboolean in_attr = FALSE;
1563 const char *in_quotes = NULL;
1564 size_t needlelen;
1566 g_return_val_if_fail( needle != NULL, FALSE);
1567 g_return_val_if_fail( *needle != '\0', FALSE);
1568 g_return_val_if_fail( haystack != NULL, FALSE);
1569 g_return_val_if_fail( start != NULL, FALSE);
1570 g_return_val_if_fail( end != NULL, FALSE);
1571 g_return_val_if_fail(attributes != NULL, FALSE);
1573 needlelen = strlen(needle);
1574 g_datalist_init(&attribs);
1576 while (*cur && !found) {
1577 if (in_tag) {
1578 if (in_quotes) {
1579 const char *close = cur;
1581 while (*close && *close != *in_quotes)
1582 close++;
1584 /* if we got the close quote, store the value and carry on from *
1585 * after it. if we ran to the end of the string, point to the NULL *
1586 * and we're outta here */
1587 if (*close) {
1588 /* only store a value if we have an attribute name */
1589 if (name) {
1590 size_t len = close - cur;
1591 char *val = g_strndup(cur, len);
1593 g_datalist_set_data_full(&attribs, name, val, g_free);
1594 g_free(name);
1595 name = NULL;
1598 in_quotes = NULL;
1599 cur = close + 1;
1600 } else {
1601 cur = close;
1603 } else if (in_attr) {
1604 const char *close = cur;
1606 while (*close && *close != '>' && *close != '"' &&
1607 *close != '\'' && *close != ' ' && *close != '=')
1608 close++;
1610 /* if we got the equals, store the name of the attribute. if we got
1611 * the quote, save the attribute and go straight to quote mode.
1612 * otherwise the tag closed or we reached the end of the string,
1613 * so we can get outta here */
1614 switch (*close) {
1615 case '"':
1616 case '\'':
1617 in_quotes = close;
1618 /* fall through */
1619 case '=':
1621 size_t len = close - cur;
1623 /* don't store a blank attribute name */
1624 if (len) {
1625 g_free(name);
1626 name = g_ascii_strdown(cur, len);
1629 in_attr = FALSE;
1630 cur = close + 1;
1632 break;
1633 case ' ':
1634 case '>':
1635 in_attr = FALSE;
1636 /* fall through */
1637 default:
1638 cur = close;
1639 break;
1641 } else {
1642 switch (*cur) {
1643 case ' ':
1644 /* swallow extra spaces inside tag */
1645 while (*cur && *cur == ' ') cur++;
1646 in_attr = TRUE;
1647 break;
1648 case '>':
1649 found = TRUE;
1650 *end = cur;
1651 break;
1652 case '"':
1653 case '\'':
1654 in_quotes = cur;
1655 /* fall through */
1656 default:
1657 cur++;
1658 break;
1661 } else {
1662 /* if we hit a < followed by the name of our tag... */
1663 if (*cur == '<' && !g_ascii_strncasecmp(cur + 1, needle, needlelen)) {
1664 *start = cur;
1665 cur = cur + needlelen + 1;
1667 /* if we're pointing at a space or a >, we found the right tag. if *
1668 * we're not, we've found a longer tag, so we need to skip to the *
1669 * >, but not being distracted by >s inside quotes. */
1670 if (*cur == ' ' || *cur == '>') {
1671 in_tag = TRUE;
1672 } else {
1673 while (*cur && *cur != '"' && *cur != '\'' && *cur != '>') {
1674 if (*cur == '"') {
1675 cur++;
1676 while (*cur && *cur != '"')
1677 cur++;
1678 } else if (*cur == '\'') {
1679 cur++;
1680 while (*cur && *cur != '\'')
1681 cur++;
1682 } else {
1683 cur++;
1687 } else {
1688 cur++;
1693 /* clean up any attribute name from a premature termination */
1694 g_free(name);
1696 if (found) {
1697 *attributes = attribs;
1698 } else {
1699 *start = NULL;
1700 *end = NULL;
1701 *attributes = NULL;
1704 return found;
1707 gboolean
1708 purple_markup_extract_info_field(const char *str, int len, PurpleNotifyUserInfo *user_info,
1709 const char *start_token, int skip,
1710 const char *end_token, char check_value,
1711 const char *no_value_token,
1712 const char *display_name, gboolean is_link,
1713 const char *link_prefix,
1714 PurpleInfoFieldFormatCallback format_cb)
1716 const char *p, *q;
1718 g_return_val_if_fail(str != NULL, FALSE);
1719 g_return_val_if_fail(user_info != NULL, FALSE);
1720 g_return_val_if_fail(start_token != NULL, FALSE);
1721 g_return_val_if_fail(end_token != NULL, FALSE);
1722 g_return_val_if_fail(display_name != NULL, FALSE);
1724 p = strstr(str, start_token);
1726 if (p == NULL)
1727 return FALSE;
1729 p += strlen(start_token) + skip;
1731 if (p >= str + len)
1732 return FALSE;
1734 if (check_value != '\0' && *p == check_value)
1735 return FALSE;
1737 q = strstr(p, end_token);
1739 /* Trim leading blanks */
1740 while (*p != '\n' && g_ascii_isspace(*p)) {
1741 p += 1;
1744 /* Trim trailing blanks */
1745 while (q > p && g_ascii_isspace(*(q - 1))) {
1746 q -= 1;
1749 /* Don't bother with null strings */
1750 if (p == q)
1751 return FALSE;
1753 if (q != NULL && (!no_value_token ||
1754 (no_value_token && strncmp(p, no_value_token,
1755 strlen(no_value_token)))))
1757 GString *dest = g_string_new("");
1759 if (is_link)
1761 g_string_append(dest, "<a href=\"");
1763 if (link_prefix)
1764 g_string_append(dest, link_prefix);
1766 if (format_cb != NULL)
1768 char *reformatted = format_cb(p, q - p);
1769 g_string_append(dest, reformatted);
1770 g_free(reformatted);
1772 else
1773 g_string_append_len(dest, p, q - p);
1774 g_string_append(dest, "\">");
1776 if (link_prefix)
1777 g_string_append(dest, link_prefix);
1779 g_string_append_len(dest, p, q - p);
1780 g_string_append(dest, "</a>");
1782 else
1784 if (format_cb != NULL)
1786 char *reformatted = format_cb(p, q - p);
1787 g_string_append(dest, reformatted);
1788 g_free(reformatted);
1790 else
1791 g_string_append_len(dest, p, q - p);
1794 purple_notify_user_info_add_pair_html(user_info, display_name, dest->str);
1795 g_string_free(dest, TRUE);
1797 return TRUE;
1800 return FALSE;
1803 struct purple_parse_tag {
1804 char *src_tag;
1805 char *dest_tag;
1806 gboolean ignore;
1809 /* NOTE: Do not put `do {} while(0)` around this macro (as this is the method
1810 recommended in the GCC docs). It contains 'continue's that should
1811 affect the while-loop in purple_markup_html_to_xhtml and doing the
1812 above would break that.
1813 Also, remember to put braces in constructs that require them for
1814 multiple statements when using this macro. */
1815 #define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \
1816 const char *o = c + strlen("<" x); \
1817 const char *p = NULL, *q = NULL, *r = NULL; \
1818 /* o = iterating over full tag \
1819 * p = > (end of tag) \
1820 * q = start of quoted bit \
1821 * r = < inside tag \
1822 */ \
1823 GString *innards = g_string_new(""); \
1824 while(o && *o) { \
1825 if(!q && (*o == '\"' || *o == '\'') ) { \
1826 q = o; \
1827 } else if(q) { \
1828 if(*o == *q) { /* end of quoted bit */ \
1829 char *unescaped = g_strndup(q+1, o-q-1); \
1830 char *escaped = g_markup_escape_text(unescaped, -1); \
1831 g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \
1832 g_free(unescaped); \
1833 g_free(escaped); \
1834 q = NULL; \
1835 } else if(*c == '\\') { \
1836 o++; \
1838 } else if(*o == '<') { \
1839 r = o; \
1840 } else if(*o == '>') { \
1841 p = o; \
1842 break; \
1843 } else { \
1844 innards = g_string_append_c(innards, *o); \
1846 o++; \
1848 if(p && !r) { /* got an end of tag and no other < earlier */\
1849 if(*(p-1) != '/') { \
1850 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1851 pt->src_tag = x; \
1852 pt->dest_tag = y; \
1853 tags = g_list_prepend(tags, pt); \
1855 if(xhtml) { \
1856 xhtml = g_string_append(xhtml, "<" y); \
1857 xhtml = g_string_append(xhtml, innards->str); \
1858 xhtml = g_string_append_c(xhtml, '>'); \
1860 c = p + 1; \
1861 } else { /* got end of tag with earlier < *or* didn't get anything */ \
1862 if(xhtml) \
1863 xhtml = g_string_append(xhtml, "&lt;"); \
1864 if(plain) \
1865 plain = g_string_append_c(plain, '<'); \
1866 c++; \
1868 g_string_free(innards, TRUE); \
1869 continue; \
1871 if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \
1872 (*(c+strlen("<" x)) == '>' || \
1873 !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \
1874 if(xhtml) \
1875 xhtml = g_string_append(xhtml, "<" y); \
1876 c += strlen("<" x); \
1877 if(*c != '/') { \
1878 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1879 pt->src_tag = x; \
1880 pt->dest_tag = y; \
1881 tags = g_list_prepend(tags, pt); \
1882 if(xhtml) \
1883 xhtml = g_string_append_c(xhtml, '>'); \
1884 } else { \
1885 if(xhtml) \
1886 xhtml = g_string_append(xhtml, "/>");\
1888 c = strchr(c, '>') + 1; \
1889 continue; \
1891 /* Don't forget to check the note above for ALLOW_TAG_ALT. */
1892 #define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x)
1893 void
1894 purple_markup_html_to_xhtml(const char *html, char **xhtml_out,
1895 char **plain_out)
1897 GString *xhtml = NULL;
1898 GString *plain = NULL;
1899 GString *url = NULL;
1900 GString *cdata = NULL;
1901 GList *tags = NULL, *tag;
1902 const char *c = html;
1903 char quote = '\0';
1905 #define CHECK_QUOTE(ptr) if (*(ptr) == '\'' || *(ptr) == '\"') \
1906 quote = *(ptr++); \
1907 else \
1908 quote = '\0';
1910 #define VALID_CHAR(ptr) (*(ptr) && *(ptr) != quote && (quote || (*(ptr) != ' ' && *(ptr) != '>')))
1912 g_return_if_fail(xhtml_out != NULL || plain_out != NULL);
1914 if(xhtml_out)
1915 xhtml = g_string_new("");
1916 if(plain_out)
1917 plain = g_string_new("");
1919 while(c && *c) {
1920 if(*c == '<') {
1921 if(*(c+1) == '/') { /* closing tag */
1922 tag = tags;
1923 while(tag) {
1924 struct purple_parse_tag *pt = tag->data;
1925 if(!g_ascii_strncasecmp((c+2), pt->src_tag, strlen(pt->src_tag)) && *(c+strlen(pt->src_tag)+2) == '>') {
1926 c += strlen(pt->src_tag) + 3;
1927 break;
1929 tag = tag->next;
1931 if(tag) {
1932 while(tags) {
1933 struct purple_parse_tag *pt = tags->data;
1934 if(xhtml && !pt->ignore)
1935 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
1936 if(plain && purple_strequal(pt->src_tag, "a")) {
1937 /* if this is a link, we have to add the url to the plaintext, too */
1938 if (cdata && url &&
1939 (!g_string_equal(cdata, url) && (g_ascii_strncasecmp(url->str, "mailto:", 7) != 0 ||
1940 g_utf8_collate(url->str + 7, cdata->str) != 0)))
1941 g_string_append_printf(plain, " <%s>", g_strstrip(purple_unescape_html(url->str)));
1942 if (cdata) {
1943 g_string_free(cdata, TRUE);
1944 cdata = NULL;
1948 if(tags == tag)
1949 break;
1950 tags = g_list_remove(tags, pt);
1951 g_free(pt);
1953 g_free(tag->data);
1954 tags = g_list_remove(tags, tag->data);
1955 } else {
1956 /* a closing tag we weren't expecting...
1957 * we'll let it slide, if it's really a tag...if it's
1958 * just a </ we'll escape it properly */
1959 const char *end = c+2;
1960 while(*end && g_ascii_isalpha(*end))
1961 end++;
1962 if(*end == '>') {
1963 c = end+1;
1964 } else {
1965 if(xhtml)
1966 xhtml = g_string_append(xhtml, "&lt;");
1967 if(plain)
1968 plain = g_string_append_c(plain, '<');
1969 c++;
1972 } else { /* opening tag */
1973 ALLOW_TAG("blockquote");
1974 ALLOW_TAG("cite");
1975 ALLOW_TAG("div");
1976 ALLOW_TAG("em");
1977 ALLOW_TAG("h1");
1978 ALLOW_TAG("h2");
1979 ALLOW_TAG("h3");
1980 ALLOW_TAG("h4");
1981 ALLOW_TAG("h5");
1982 ALLOW_TAG("h6");
1983 /* we only allow html to start the message */
1984 if(c == html) {
1985 ALLOW_TAG("html");
1987 ALLOW_TAG_ALT("i", "em");
1988 ALLOW_TAG_ALT("italic", "em");
1989 ALLOW_TAG("li");
1990 ALLOW_TAG("ol");
1991 ALLOW_TAG("p");
1992 ALLOW_TAG("pre");
1993 ALLOW_TAG("q");
1994 ALLOW_TAG("span");
1995 ALLOW_TAG("ul");
1998 /* we skip <HR> because it's not legal in XHTML-IM. However,
1999 * we still want to send something sensible, so we put a
2000 * linebreak in its place. <BR> also needs special handling
2001 * because putting a </BR> to close it would just be dumb. */
2002 if((!g_ascii_strncasecmp(c, "<br", 3)
2003 || !g_ascii_strncasecmp(c, "<hr", 3))
2004 && (*(c+3) == '>' ||
2005 !g_ascii_strncasecmp(c+3, "/>", 2) ||
2006 !g_ascii_strncasecmp(c+3, " />", 3))) {
2007 c = strchr(c, '>') + 1;
2008 if(xhtml)
2009 xhtml = g_string_append(xhtml, "<br/>");
2010 if(plain && *c != '\n')
2011 plain = g_string_append_c(plain, '\n');
2012 continue;
2014 if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c, "<strong>", strlen("<strong>"))) {
2015 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2016 if (*(c+2) == '>')
2017 pt->src_tag = "b";
2018 else if (*(c+2) == 'o')
2019 pt->src_tag = "bold";
2020 else
2021 pt->src_tag = "strong";
2022 pt->dest_tag = "span";
2023 tags = g_list_prepend(tags, pt);
2024 c = strchr(c, '>') + 1;
2025 if(xhtml)
2026 xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>");
2027 continue;
2029 if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) {
2030 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2031 pt->src_tag = *(c+2) == '>' ? "u" : "underline";
2032 pt->dest_tag = "span";
2033 tags = g_list_prepend(tags, pt);
2034 c = strchr(c, '>') + 1;
2035 if (xhtml)
2036 xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>");
2037 continue;
2039 if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) {
2040 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2041 pt->src_tag = *(c+2) == '>' ? "s" : "strike";
2042 pt->dest_tag = "span";
2043 tags = g_list_prepend(tags, pt);
2044 c = strchr(c, '>') + 1;
2045 if(xhtml)
2046 xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>");
2047 continue;
2049 if(!g_ascii_strncasecmp(c, "<sub>", 5)) {
2050 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2051 pt->src_tag = "sub";
2052 pt->dest_tag = "span";
2053 tags = g_list_prepend(tags, pt);
2054 c = strchr(c, '>') + 1;
2055 if(xhtml)
2056 xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>");
2057 continue;
2059 if(!g_ascii_strncasecmp(c, "<sup>", 5)) {
2060 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2061 pt->src_tag = "sup";
2062 pt->dest_tag = "span";
2063 tags = g_list_prepend(tags, pt);
2064 c = strchr(c, '>') + 1;
2065 if(xhtml)
2066 xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>");
2067 continue;
2069 if (!g_ascii_strncasecmp(c, "<img", 4) && (*(c+4) == '>' || *(c+4) == ' ')) {
2070 const char *p = c + 4;
2071 GString *src = NULL, *alt = NULL;
2072 #define ESCAPE(from, to) \
2073 CHECK_QUOTE(from); \
2074 while (VALID_CHAR(from)) { \
2075 int len; \
2076 if ((*from == '&') && (purple_markup_unescape_entity(from, &len) == NULL)) \
2077 to = g_string_append(to, "&amp;"); \
2078 else if (*from == '\'') \
2079 to = g_string_append(to, "&apos;"); \
2080 else \
2081 to = g_string_append_c(to, *from); \
2082 from++; \
2085 while (*p && *p != '>') {
2086 if (!g_ascii_strncasecmp(p, "src=", 4)) {
2087 const char *q = p + 4;
2088 if (src)
2089 g_string_free(src, TRUE);
2090 src = g_string_new("");
2091 ESCAPE(q, src);
2092 p = q;
2093 } else if (!g_ascii_strncasecmp(p, "alt=", 4)) {
2094 const char *q = p + 4;
2095 if (alt)
2096 g_string_free(alt, TRUE);
2097 alt = g_string_new("");
2098 ESCAPE(q, alt);
2099 p = q;
2100 } else {
2101 p++;
2104 #undef ESCAPE
2105 if ((c = strchr(p, '>')) != NULL)
2106 c++;
2107 else
2108 c = p;
2109 /* src and alt are required! */
2110 if(src && xhtml)
2111 g_string_append_printf(xhtml, "<img src='%s' alt='%s' />", g_strstrip(src->str), alt ? alt->str : "");
2112 if(alt) {
2113 if(plain)
2114 plain = g_string_append(plain, purple_unescape_html(alt->str));
2115 if(!src && xhtml)
2116 xhtml = g_string_append(xhtml, alt->str);
2117 g_string_free(alt, TRUE);
2119 g_string_free(src, TRUE);
2120 continue;
2122 if (!g_ascii_strncasecmp(c, "<a", 2) && (*(c+2) == '>' || *(c+2) == ' ')) {
2123 const char *p = c + 2;
2124 struct purple_parse_tag *pt;
2125 while (*p && *p != '>') {
2126 if (!g_ascii_strncasecmp(p, "href=", 5)) {
2127 const char *q = p + 5;
2128 if (url)
2129 g_string_free(url, TRUE);
2130 url = g_string_new("");
2131 if (cdata)
2132 g_string_free(cdata, TRUE);
2133 cdata = g_string_new("");
2134 CHECK_QUOTE(q);
2135 while (VALID_CHAR(q)) {
2136 int len;
2137 if ((*q == '&') && (purple_markup_unescape_entity(q, &len) == NULL))
2138 url = g_string_append(url, "&amp;");
2139 else if (*q == '"')
2140 url = g_string_append(url, "&quot;");
2141 else
2142 url = g_string_append_c(url, *q);
2143 q++;
2145 p = q;
2146 } else {
2147 p++;
2150 if ((c = strchr(p, '>')) != NULL)
2151 c++;
2152 else
2153 c = p;
2154 pt = g_new0(struct purple_parse_tag, 1);
2155 pt->src_tag = "a";
2156 pt->dest_tag = "a";
2157 tags = g_list_prepend(tags, pt);
2158 if(xhtml)
2159 g_string_append_printf(xhtml, "<a href=\"%s\">", url ? g_strstrip(url->str) : "");
2160 continue;
2162 #define ESCAPE(from, to) \
2163 CHECK_QUOTE(from); \
2164 while (VALID_CHAR(from)) { \
2165 int len; \
2166 if ((*from == '&') && (purple_markup_unescape_entity(from, &len) == NULL)) \
2167 to = g_string_append(to, "&amp;"); \
2168 else if (*from == '\'') \
2169 to = g_string_append_c(to, '\"'); \
2170 else \
2171 to = g_string_append_c(to, *from); \
2172 from++; \
2174 if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) {
2175 const char *p = c + 5;
2176 GString *style = g_string_new("");
2177 struct purple_parse_tag *pt;
2178 while (*p && *p != '>') {
2179 if (!g_ascii_strncasecmp(p, "back=", 5)) {
2180 const char *q = p + 5;
2181 GString *color = g_string_new("");
2182 ESCAPE(q, color);
2183 g_string_append_printf(style, "background: %s; ", color->str);
2184 g_string_free(color, TRUE);
2185 p = q;
2186 } else if (!g_ascii_strncasecmp(p, "color=", 6)) {
2187 const char *q = p + 6;
2188 GString *color = g_string_new("");
2189 ESCAPE(q, color);
2190 g_string_append_printf(style, "color: %s; ", color->str);
2191 g_string_free(color, TRUE);
2192 p = q;
2193 } else if (!g_ascii_strncasecmp(p, "face=", 5)) {
2194 const char *q = p + 5;
2195 GString *face = g_string_new("");
2196 ESCAPE(q, face);
2197 g_string_append_printf(style, "font-family: %s; ", g_strstrip(face->str));
2198 g_string_free(face, TRUE);
2199 p = q;
2200 } else if (!g_ascii_strncasecmp(p, "size=", 5)) {
2201 const char *q = p + 5;
2202 int sz;
2203 const char *size = "medium";
2204 CHECK_QUOTE(q);
2205 sz = atoi(q);
2206 switch (sz)
2208 case 1:
2209 size = "xx-small";
2210 break;
2211 case 2:
2212 size = "small";
2213 break;
2214 case 3:
2215 size = "medium";
2216 break;
2217 case 4:
2218 size = "large";
2219 break;
2220 case 5:
2221 size = "x-large";
2222 break;
2223 case 6:
2224 case 7:
2225 size = "xx-large";
2226 break;
2227 default:
2228 break;
2230 g_string_append_printf(style, "font-size: %s; ", size);
2231 p = q;
2232 } else {
2233 p++;
2236 if ((c = strchr(p, '>')) != NULL)
2237 c++;
2238 else
2239 c = p;
2240 pt = g_new0(struct purple_parse_tag, 1);
2241 pt->src_tag = "font";
2242 pt->dest_tag = "span";
2243 tags = g_list_prepend(tags, pt);
2244 if(style->len && xhtml)
2245 g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str));
2246 else
2247 pt->ignore = TRUE;
2248 g_string_free(style, TRUE);
2249 continue;
2251 #undef ESCAPE
2252 if (!g_ascii_strncasecmp(c, "<body ", 6)) {
2253 const char *p = c + 6;
2254 gboolean did_something = FALSE;
2255 while (*p && *p != '>') {
2256 if (!g_ascii_strncasecmp(p, "bgcolor=", 8)) {
2257 const char *q = p + 8;
2258 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1);
2259 GString *color = g_string_new("");
2260 CHECK_QUOTE(q);
2261 while (VALID_CHAR(q)) {
2262 color = g_string_append_c(color, *q);
2263 q++;
2265 if (xhtml)
2266 g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str));
2267 g_string_free(color, TRUE);
2268 if ((c = strchr(p, '>')) != NULL)
2269 c++;
2270 else
2271 c = p;
2272 pt->src_tag = "body";
2273 pt->dest_tag = "span";
2274 tags = g_list_prepend(tags, pt);
2275 did_something = TRUE;
2276 break;
2278 p++;
2280 if (did_something) continue;
2282 /* this has to come after the special case for bgcolor */
2283 ALLOW_TAG("body");
2284 if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) {
2285 char *p = strstr(c + strlen("<!--"), "-->");
2286 if(p) {
2287 if(xhtml)
2288 xhtml = g_string_append(xhtml, "<!--");
2289 c += strlen("<!--");
2290 continue;
2294 if(xhtml)
2295 xhtml = g_string_append(xhtml, "&lt;");
2296 if(plain)
2297 plain = g_string_append_c(plain, '<');
2298 c++;
2300 } else if(*c == '&') {
2301 char buf[7];
2302 const char *pln;
2303 int len;
2305 if ((pln = purple_markup_unescape_entity(c, &len)) == NULL) {
2306 len = 1;
2307 g_snprintf(buf, sizeof(buf), "%c", *c);
2308 pln = buf;
2310 if(xhtml)
2311 xhtml = g_string_append_len(xhtml, c, len);
2312 if(plain)
2313 plain = g_string_append(plain, pln);
2314 if(cdata)
2315 cdata = g_string_append_len(cdata, c, len);
2316 c += len;
2317 } else {
2318 if(xhtml)
2319 xhtml = g_string_append_c(xhtml, *c);
2320 if(plain)
2321 plain = g_string_append_c(plain, *c);
2322 if(cdata)
2323 cdata = g_string_append_c(cdata, *c);
2324 c++;
2327 if(xhtml) {
2328 for (tag = tags; tag ; tag = tag->next) {
2329 struct purple_parse_tag *pt = tag->data;
2330 if(!pt->ignore)
2331 g_string_append_printf(xhtml, "</%s>", pt->dest_tag);
2334 g_list_free(tags);
2335 if(xhtml_out)
2336 *xhtml_out = g_string_free(xhtml, FALSE);
2337 if(plain_out)
2338 *plain_out = g_string_free(plain, FALSE);
2339 if(url)
2340 g_string_free(url, TRUE);
2341 if (cdata)
2342 g_string_free(cdata, TRUE);
2343 #undef CHECK_QUOTE
2344 #undef VALID_CHAR
2347 /* The following are probably reasonable changes:
2348 * - \n should be converted to a normal space
2349 * - in addition to <br>, <p> and <div> etc. should also be converted into \n
2350 * - We want to turn </td>#whitespace<td> sequences into a single tab
2351 * - We want to turn </tr>#whitespace<tr> sequences into a single \n
2352 * - <script>...</script> and <style>...</style> should be completely removed
2355 char *
2356 purple_markup_strip_html(const char *str)
2358 int i, j, k, entlen;
2359 gboolean visible = TRUE;
2360 gboolean closing_td_p = FALSE;
2361 gchar *str2;
2362 const gchar *cdata_close_tag = NULL, *ent;
2363 gchar *href = NULL;
2364 int href_st = 0;
2366 if(!str)
2367 return NULL;
2369 str2 = g_strdup(str);
2371 for (i = 0, j = 0; str2[i]; i++)
2373 if (str2[i] == '<')
2375 if (cdata_close_tag)
2377 /* Note: Don't even assume any other tag is a tag in CDATA */
2378 if (g_ascii_strncasecmp(str2 + i, cdata_close_tag,
2379 strlen(cdata_close_tag)) == 0)
2381 i += strlen(cdata_close_tag) - 1;
2382 cdata_close_tag = NULL;
2384 continue;
2386 else if (g_ascii_strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p)
2388 str2[j++] = '\t';
2389 visible = TRUE;
2391 else if (g_ascii_strncasecmp(str2 + i, "</td>", 5) == 0)
2393 closing_td_p = TRUE;
2394 visible = FALSE;
2396 else
2398 closing_td_p = FALSE;
2399 visible = TRUE;
2402 k = i + 1;
2404 if(g_ascii_isspace(str2[k]))
2405 visible = TRUE;
2406 else if (str2[k])
2408 /* Scan until we end the tag either implicitly (closed start
2409 * tag) or explicitly, using a sloppy method (i.e., < or >
2410 * inside quoted attributes will screw us up)
2412 while (str2[k] && str2[k] != '<' && str2[k] != '>')
2414 k++;
2417 /* If we've got an <a> tag with an href, save the address
2418 * to print later. */
2419 if (g_ascii_strncasecmp(str2 + i, "<a", 2) == 0 &&
2420 g_ascii_isspace(str2[i+2]))
2422 int st; /* start of href, inclusive [ */
2423 int end; /* end of href, exclusive ) */
2424 char delim = ' ';
2425 /* Find start of href */
2426 for (st = i + 3; st < k; st++)
2428 if (g_ascii_strncasecmp(str2+st, "href=", 5) == 0)
2430 st += 5;
2431 if (str2[st] == '"' || str2[st] == '\'')
2433 delim = str2[st];
2434 st++;
2436 break;
2439 /* find end of address */
2440 for (end = st; end < k && str2[end] != delim; end++)
2442 /* All the work is done in the loop construct above. */
2445 /* If there's an address, save it. If there was
2446 * already one saved, kill it. */
2447 if (st < k)
2449 char *tmp;
2450 g_free(href);
2451 tmp = g_strndup(str2 + st, end - st);
2452 href = purple_unescape_html(tmp);
2453 g_free(tmp);
2454 href_st = j;
2458 /* Replace </a> with an ascii representation of the
2459 * address the link was pointing to. */
2460 else if (href != NULL && g_ascii_strncasecmp(str2 + i, "</a>", 4) == 0)
2462 size_t hrlen = strlen(href);
2464 /* Only insert the href if it's different from the CDATA. */
2465 if ((hrlen != (gsize)(j - href_st) ||
2466 strncmp(str2 + href_st, href, hrlen)) &&
2467 (hrlen != (gsize)(j - href_st + 7) || /* 7 == strlen("http://") */
2468 strncmp(str2 + href_st, href + 7, hrlen - 7)))
2470 str2[j++] = ' ';
2471 str2[j++] = '(';
2472 g_memmove(str2 + j, href, hrlen);
2473 j += hrlen;
2474 str2[j++] = ')';
2475 g_free(href);
2476 href = NULL;
2480 /* Check for tags which should be mapped to newline (but ignore some of
2481 * the tags at the beginning of the text) */
2482 else if ((j && (g_ascii_strncasecmp(str2 + i, "<p>", 3) == 0
2483 || g_ascii_strncasecmp(str2 + i, "<tr", 3) == 0
2484 || g_ascii_strncasecmp(str2 + i, "<hr", 3) == 0
2485 || g_ascii_strncasecmp(str2 + i, "<li", 3) == 0
2486 || g_ascii_strncasecmp(str2 + i, "<div", 4) == 0))
2487 || g_ascii_strncasecmp(str2 + i, "<br", 3) == 0
2488 || g_ascii_strncasecmp(str2 + i, "</table>", 8) == 0)
2490 str2[j++] = '\n';
2492 /* Check for tags which begin CDATA and need to be closed */
2493 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */
2494 else if (g_ascii_strncasecmp(str2 + i, "<option", 7) == 0)
2496 /* FIXME: We should not do this if the OPTION is SELECT'd */
2497 cdata_close_tag = "</option>";
2499 #endif
2500 else if (g_ascii_strncasecmp(str2 + i, "<script", 7) == 0)
2502 cdata_close_tag = "</script>";
2504 else if (g_ascii_strncasecmp(str2 + i, "<style", 6) == 0)
2506 cdata_close_tag = "</style>";
2508 /* Update the index and continue checking after the tag */
2509 i = (str2[k] == '<' || str2[k] == '\0')? k - 1: k;
2510 continue;
2513 else if (cdata_close_tag)
2515 continue;
2517 else if (!g_ascii_isspace(str2[i]))
2519 visible = TRUE;
2522 if (str2[i] == '&' && (ent = purple_markup_unescape_entity(str2 + i, &entlen)) != NULL)
2524 while (*ent)
2525 str2[j++] = *ent++;
2526 i += entlen - 1;
2527 continue;
2530 if (visible)
2531 str2[j++] = g_ascii_isspace(str2[i])? ' ': str2[i];
2534 g_free(href);
2536 str2[j] = '\0';
2538 return str2;
2541 static gboolean
2542 badchar(char c)
2544 switch (c) {
2545 case ' ':
2546 case ',':
2547 case '\0':
2548 case '\n':
2549 case '\r':
2550 case '<':
2551 case '>':
2552 case '"':
2553 return TRUE;
2554 default:
2555 return FALSE;
2559 static gboolean
2560 badentity(const char *c)
2562 if (!g_ascii_strncasecmp(c, "&lt;", 4) ||
2563 !g_ascii_strncasecmp(c, "&gt;", 4) ||
2564 !g_ascii_strncasecmp(c, "&quot;", 6)) {
2565 return TRUE;
2567 return FALSE;
2570 static const char *
2571 process_link(GString *ret,
2572 const char *start, const char *c,
2573 int matchlen,
2574 const char *urlprefix,
2575 int inside_paren)
2577 char *url_buf, *tmpurlbuf;
2578 const char *t;
2580 for (t = c;; t++) {
2581 if (!badchar(*t) && !badentity(t))
2582 continue;
2584 if (t - c == matchlen)
2585 break;
2587 if (*t == ',' && *(t + 1) != ' ') {
2588 continue;
2591 if (t > start && *(t - 1) == '.')
2592 t--;
2593 if (t > start && *(t - 1) == ')' && inside_paren > 0)
2594 t--;
2596 url_buf = g_strndup(c, t - c);
2597 tmpurlbuf = purple_unescape_html(url_buf);
2598 g_string_append_printf(ret, "<A HREF=\"%s%s\">%s</A>",
2599 urlprefix,
2600 tmpurlbuf, url_buf);
2601 g_free(tmpurlbuf);
2602 g_free(url_buf);
2603 return t;
2606 return c;
2609 char *
2610 purple_markup_linkify(const char *text)
2612 const char *c, *t, *q = NULL;
2613 char *tmpurlbuf, *url_buf;
2614 gunichar g;
2615 gboolean inside_html = FALSE;
2616 int inside_paren = 0;
2617 GString *ret;
2619 if (text == NULL)
2620 return NULL;
2622 ret = g_string_new("");
2624 c = text;
2625 while (*c) {
2627 if(*c == '(' && !inside_html) {
2628 inside_paren++;
2629 ret = g_string_append_c(ret, *c);
2630 c++;
2633 if(inside_html) {
2634 if(*c == '>') {
2635 inside_html = FALSE;
2636 } else if(!q && (*c == '\"' || *c == '\'')) {
2637 q = c;
2638 } else if(q) {
2639 if(*c == *q)
2640 q = NULL;
2642 } else if(*c == '<') {
2643 inside_html = TRUE;
2644 if (!g_ascii_strncasecmp(c, "<A", 2)) {
2645 while (1) {
2646 if (!g_ascii_strncasecmp(c, "/A>", 3)) {
2647 inside_html = FALSE;
2648 break;
2650 ret = g_string_append_c(ret, *c);
2651 c++;
2652 if (!(*c))
2653 break;
2656 } else if (!g_ascii_strncasecmp(c, "http://", 7)) {
2657 c = process_link(ret, text, c, 7, "", inside_paren);
2658 } else if (!g_ascii_strncasecmp(c, "https://", 8)) {
2659 c = process_link(ret, text, c, 8, "", inside_paren);
2660 } else if (!g_ascii_strncasecmp(c, "ftp://", 6)) {
2661 c = process_link(ret, text, c, 6, "", inside_paren);
2662 } else if (!g_ascii_strncasecmp(c, "sftp://", 7)) {
2663 c = process_link(ret, text, c, 7, "", inside_paren);
2664 } else if (!g_ascii_strncasecmp(c, "file://", 7)) {
2665 c = process_link(ret, text, c, 7, "", inside_paren);
2666 } else if (!g_ascii_strncasecmp(c, "www.", 4) && c[4] != '.' && (c == text || badchar(c[-1]) || badentity(c-1))) {
2667 c = process_link(ret, text, c, 4, "http://", inside_paren);
2668 } else if (!g_ascii_strncasecmp(c, "ftp.", 4) && c[4] != '.' && (c == text || badchar(c[-1]) || badentity(c-1))) {
2669 c = process_link(ret, text, c, 4, "ftp://", inside_paren);
2670 } else if (!g_ascii_strncasecmp(c, "xmpp:", 5) && (c == text || badchar(c[-1]) || badentity(c-1))) {
2671 c = process_link(ret, text, c, 5, "", inside_paren);
2672 } else if (!g_ascii_strncasecmp(c, "mailto:", 7)) {
2673 t = c;
2674 while (1) {
2675 if (badchar(*t) || badentity(t)) {
2676 char *d;
2677 if (t - c == 7) {
2678 break;
2680 if (t > text && *(t - 1) == '.')
2681 t--;
2682 if ((d = strstr(c + 7, "?")) != NULL && d < t)
2683 url_buf = g_strndup(c + 7, d - c - 7);
2684 else
2685 url_buf = g_strndup(c + 7, t - c - 7);
2686 if (!purple_email_is_valid(url_buf)) {
2687 g_free(url_buf);
2688 break;
2690 g_free(url_buf);
2691 url_buf = g_strndup(c, t - c);
2692 tmpurlbuf = purple_unescape_html(url_buf);
2693 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>",
2694 tmpurlbuf, url_buf);
2695 g_free(url_buf);
2696 g_free(tmpurlbuf);
2697 c = t;
2698 break;
2700 t++;
2702 } else if (c != text && (*c == '@')) {
2703 int flag;
2704 GString *gurl_buf = NULL;
2705 const char illegal_chars[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0";
2707 if (strchr(illegal_chars,*(c - 1)) || strchr(illegal_chars, *(c + 1)))
2708 flag = 0;
2709 else {
2710 flag = 1;
2711 gurl_buf = g_string_new("");
2714 t = c;
2715 while (flag) {
2716 /* iterate backwards grabbing the local part of an email address */
2717 g = g_utf8_get_char(t);
2718 if (badchar(*t) || (g >= 127) || (*t == '(') ||
2719 ((*t == ';') && ((t > (text+2) && (!g_ascii_strncasecmp(t - 3, "&lt;", 4) ||
2720 !g_ascii_strncasecmp(t - 3, "&gt;", 4))) ||
2721 (t > (text+4) && (!g_ascii_strncasecmp(t - 5, "&quot;", 6)))))) {
2722 /* local part will already be part of ret, strip it out */
2723 ret = g_string_truncate(ret, ret->len - (c - t));
2724 ret = g_string_append_unichar(ret, g);
2725 break;
2726 } else {
2727 g_string_prepend_unichar(gurl_buf, g);
2728 t = g_utf8_find_prev_char(text, t);
2729 if (t < text) {
2730 ret = g_string_assign(ret, "");
2731 break;
2736 t = g_utf8_find_next_char(c, NULL);
2738 while (flag) {
2739 /* iterate forwards grabbing the domain part of an email address */
2740 g = g_utf8_get_char(t);
2741 if (badchar(*t) || (g >= 127) || (*t == ')') || badentity(t)) {
2742 char *d;
2744 url_buf = g_string_free(gurl_buf, FALSE);
2746 /* strip off trailing periods */
2747 if (*url_buf) {
2748 for (d = url_buf + strlen(url_buf) - 1; *d == '.'; d--, t--)
2749 *d = '\0';
2752 tmpurlbuf = purple_unescape_html(url_buf);
2753 if (purple_email_is_valid(tmpurlbuf)) {
2754 g_string_append_printf(ret, "<A HREF=\"mailto:%s\">%s</A>",
2755 tmpurlbuf, url_buf);
2756 } else {
2757 g_string_append(ret, url_buf);
2759 g_free(url_buf);
2760 g_free(tmpurlbuf);
2761 c = t;
2763 break;
2764 } else {
2765 g_string_append_unichar(gurl_buf, g);
2766 t = g_utf8_find_next_char(t, NULL);
2771 if(*c == ')' && !inside_html) {
2772 inside_paren--;
2773 ret = g_string_append_c(ret, *c);
2774 c++;
2777 if (*c == 0)
2778 break;
2780 ret = g_string_append_c(ret, *c);
2781 c++;
2784 return g_string_free(ret, FALSE);
2787 char *purple_unescape_text(const char *in)
2789 GString *ret;
2790 const char *c = in;
2792 if (in == NULL)
2793 return NULL;
2795 ret = g_string_new("");
2796 while (*c) {
2797 int len;
2798 const char *ent;
2800 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2801 g_string_append(ret, ent);
2802 c += len;
2803 } else {
2804 g_string_append_c(ret, *c);
2805 c++;
2809 return g_string_free(ret, FALSE);
2812 char *purple_unescape_html(const char *html)
2814 GString *ret;
2815 const char *c = html;
2817 if (html == NULL)
2818 return NULL;
2820 ret = g_string_new("");
2821 while (*c) {
2822 int len;
2823 const char *ent;
2825 if ((ent = purple_markup_unescape_entity(c, &len)) != NULL) {
2826 g_string_append(ret, ent);
2827 c += len;
2828 } else if (!strncmp(c, "<br>", 4)) {
2829 g_string_append_c(ret, '\n');
2830 c += 4;
2831 } else {
2832 g_string_append_c(ret, *c);
2833 c++;
2837 return g_string_free(ret, FALSE);
2840 char *
2841 purple_markup_slice(const char *str, guint x, guint y)
2843 GString *ret;
2844 GQueue *q;
2845 guint z = 0;
2846 gboolean appended = FALSE;
2847 gunichar c;
2848 char *tag;
2850 g_return_val_if_fail(str != NULL, NULL);
2851 g_return_val_if_fail(x <= y, NULL);
2853 if (x == y)
2854 return g_strdup("");
2856 ret = g_string_new("");
2857 q = g_queue_new();
2859 while (*str && (z < y)) {
2860 c = g_utf8_get_char(str);
2862 if (c == '<') {
2863 char *end = strchr(str, '>');
2865 if (!end) {
2866 g_string_free(ret, TRUE);
2867 while ((tag = g_queue_pop_head(q)))
2868 g_free(tag);
2869 g_queue_free(q);
2870 return NULL;
2873 if (!g_ascii_strncasecmp(str, "<img ", 5)) {
2874 z += strlen("[Image]");
2875 } else if (!g_ascii_strncasecmp(str, "<br", 3)) {
2876 z += 1;
2877 } else if (!g_ascii_strncasecmp(str, "<hr>", 4)) {
2878 z += strlen("\n---\n");
2879 } else if (!g_ascii_strncasecmp(str, "</", 2)) {
2880 /* pop stack */
2881 char *tmp;
2883 tmp = g_queue_pop_head(q);
2884 g_free(tmp);
2885 /* z += 0; */
2886 } else {
2887 /* push it unto the stack */
2888 char *tmp;
2890 tmp = g_strndup(str, end - str + 1);
2891 g_queue_push_head(q, tmp);
2892 /* z += 0; */
2895 if (z >= x) {
2896 g_string_append_len(ret, str, end - str + 1);
2899 str = end;
2900 } else if (c == '&') {
2901 char *end = strchr(str, ';');
2902 if (!end) {
2903 g_string_free(ret, TRUE);
2904 while ((tag = g_queue_pop_head(q)))
2905 g_free(tag);
2906 g_queue_free(q);
2908 return NULL;
2911 if (z >= x)
2912 g_string_append_len(ret, str, end - str + 1);
2914 z++;
2915 str = end;
2916 } else {
2917 if (z == x && z > 0 && !appended) {
2918 GList *l = q->tail;
2920 while (l) {
2921 tag = l->data;
2922 g_string_append(ret, tag);
2923 l = l->prev;
2925 appended = TRUE;
2928 if (z >= x)
2929 g_string_append_unichar(ret, c);
2930 z++;
2933 str = g_utf8_next_char(str);
2936 while ((tag = g_queue_pop_head(q))) {
2937 char *name;
2939 name = purple_markup_get_tag_name(tag);
2940 g_string_append_printf(ret, "</%s>", name);
2941 g_free(name);
2942 g_free(tag);
2945 g_queue_free(q);
2946 return g_string_free(ret, FALSE);
2949 char *
2950 purple_markup_get_tag_name(const char *tag)
2952 int i;
2953 g_return_val_if_fail(tag != NULL, NULL);
2954 g_return_val_if_fail(*tag == '<', NULL);
2956 for (i = 1; tag[i]; i++)
2957 if (tag[i] == '>' || tag[i] == ' ' || tag[i] == '/')
2958 break;
2960 return g_strndup(tag+1, i-1);
2963 /**************************************************************************
2964 * Path/Filename Functions
2965 **************************************************************************/
2966 const char *
2967 purple_home_dir(void)
2969 #ifndef _WIN32
2970 return g_get_home_dir();
2971 #else
2972 return wpurple_home_dir();
2973 #endif
2976 /* Returns the argument passed to -c IFF it was present, or ~/.purple. */
2977 const char *
2978 purple_user_dir(void)
2980 if (custom_user_dir != NULL)
2981 return custom_user_dir;
2982 else if (!user_dir)
2983 user_dir = g_build_filename(purple_home_dir(), ".purple", NULL);
2985 return user_dir;
2988 const char *
2989 purple_cache_dir(void)
2991 if (!cache_dir) {
2992 if (!custom_user_dir) {
2993 cache_dir = g_build_filename(g_get_user_cache_dir(), "purple", NULL);
2994 } else {
2995 cache_dir = g_build_filename(custom_user_dir, "cache", NULL);
2999 return cache_dir;
3002 const char *
3003 purple_config_dir(void)
3005 if (!config_dir) {
3006 if (!custom_user_dir) {
3007 config_dir = g_build_filename(g_get_user_config_dir(), "purple", NULL);
3008 } else {
3009 config_dir = g_build_filename(custom_user_dir, "config", NULL);
3013 return config_dir;
3016 const char *
3017 purple_data_dir(void)
3019 if (!data_dir) {
3020 if (!custom_user_dir) {
3021 data_dir = g_build_filename(g_get_user_data_dir(), "purple", NULL);
3022 } else {
3023 data_dir = g_build_filename(custom_user_dir, "data", NULL);
3027 return data_dir;
3030 void purple_util_set_user_dir(const char *dir)
3032 g_free(custom_user_dir);
3034 if (dir != NULL && *dir)
3035 custom_user_dir = g_strdup(dir);
3036 else
3037 custom_user_dir = NULL;
3040 int purple_build_dir(const char *path, int mode)
3042 return g_mkdir_with_parents(path, mode);
3045 static gboolean
3046 purple_util_write_data_to_file_common(const char *dir, const char *filename, const char *data, gssize size)
3048 gchar *filename_full;
3049 gboolean ret = FALSE;
3051 g_return_val_if_fail(dir != NULL, FALSE);
3053 purple_debug_misc("util", "Writing file %s to directory %s",
3054 filename, dir);
3056 /* Ensure the directory exists */
3057 if (!g_file_test(dir, G_FILE_TEST_IS_DIR))
3059 if (g_mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1)
3061 purple_debug_error("util", "Error creating directory %s: %s\n",
3062 dir, g_strerror(errno));
3063 return FALSE;
3067 filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", dir, filename);
3069 ret = purple_util_write_data_to_file_absolute(filename_full, data, size);
3071 g_free(filename_full);
3072 return ret;
3075 gboolean
3076 purple_util_write_data_to_file(const char *filename, const char *data, gssize size)
3078 const char *user_dir = purple_user_dir();
3079 gboolean ret = purple_util_write_data_to_file_common(user_dir, filename, data, size);
3081 return ret;
3084 gboolean
3085 purple_util_write_data_to_cache_file(const char *filename, const char *data, gssize size)
3087 const char *cache_dir = purple_cache_dir();
3088 gboolean ret = purple_util_write_data_to_file_common(cache_dir, filename, data, size);
3090 return ret;
3093 gboolean
3094 purple_util_write_data_to_config_file(const char *filename, const char *data, gssize size)
3096 const char *config_dir = purple_config_dir();
3097 gboolean ret = purple_util_write_data_to_file_common(config_dir, filename, data, size);
3099 return ret;
3102 gboolean
3103 purple_util_write_data_to_data_file(const char *filename, const char *data, gssize size)
3105 const char *data_dir = purple_data_dir();
3106 gboolean ret = purple_util_write_data_to_file_common(data_dir, filename, data, size);
3108 return ret;
3112 * This function is long and beautiful, like my--um, yeah. Anyway,
3113 * it includes lots of error checking so as we don't overwrite
3114 * people's settings if there is a problem writing the new values.
3116 gboolean
3117 purple_util_write_data_to_file_absolute(const char *filename_full, const char *data, gssize size)
3119 gchar *filename_temp;
3120 FILE *file;
3121 gsize real_size, byteswritten;
3122 GStatBuf st;
3123 #ifndef HAVE_FILENO
3124 int fd;
3125 #endif
3127 purple_debug_misc("util", "Writing file %s",
3128 filename_full);
3130 g_return_val_if_fail((size >= -1), FALSE);
3132 filename_temp = g_strdup_printf("%s.save", filename_full);
3134 /* Remove an old temporary file, if one exists */
3135 if (g_file_test(filename_temp, G_FILE_TEST_EXISTS))
3137 if (g_unlink(filename_temp) == -1)
3139 purple_debug_error("util", "Error removing old file "
3140 "%s: %s\n",
3141 filename_temp, g_strerror(errno));
3145 /* Open file */
3146 file = g_fopen(filename_temp, "wb");
3147 if (file == NULL)
3149 purple_debug_error("util", "Error opening file %s for "
3150 "writing: %s\n",
3151 filename_temp, g_strerror(errno));
3152 g_free(filename_temp);
3153 return FALSE;
3156 /* Write to file */
3157 real_size = (size == -1) ? strlen(data) : (size_t) size;
3158 byteswritten = fwrite(data, 1, real_size, file);
3160 #ifdef HAVE_FILENO
3161 #ifndef _WIN32
3162 /* Set file permissions */
3163 if (fchmod(fileno(file), S_IRUSR | S_IWUSR) == -1) {
3164 purple_debug_error("util", "Error setting permissions of "
3165 "file %s: %s\n", filename_temp, g_strerror(errno));
3167 #endif
3169 /* Apparently XFS (and possibly other filesystems) do not
3170 * guarantee that file data is flushed before file metadata,
3171 * so this procedure is insufficient without some flushage. */
3172 if (fflush(file) < 0) {
3173 purple_debug_error("util", "Error flushing %s: %s\n",
3174 filename_temp, g_strerror(errno));
3175 g_free(filename_temp);
3176 fclose(file);
3177 return FALSE;
3179 if (fsync(fileno(file)) < 0) {
3180 purple_debug_error("util", "Error syncing file contents for %s: %s\n",
3181 filename_temp, g_strerror(errno));
3182 g_free(filename_temp);
3183 fclose(file);
3184 return FALSE;
3186 #endif
3188 /* Close file */
3189 if (fclose(file) != 0)
3191 purple_debug_error("util", "Error closing file %s: %s\n",
3192 filename_temp, g_strerror(errno));
3193 g_free(filename_temp);
3194 return FALSE;
3197 #ifndef HAVE_FILENO
3198 /* This is the same effect (we hope) as the HAVE_FILENO block
3199 * above, but for systems without fileno(). */
3200 if ((fd = open(filename_temp, O_RDWR)) < 0) {
3201 purple_debug_error("util", "Error opening file %s for flush: %s\n",
3202 filename_temp, g_strerror(errno));
3203 g_free(filename_temp);
3204 return FALSE;
3207 #ifndef _WIN32
3208 /* copy-pasta! */
3209 if (fchmod(fd, S_IRUSR | S_IWUSR) == -1) {
3210 purple_debug_error("util", "Error setting permissions of "
3211 "file %s: %s\n", filename_temp, g_strerror(errno));
3213 #endif
3215 if (fsync(fd) < 0) {
3216 purple_debug_error("util", "Error syncing %s: %s\n",
3217 filename_temp, g_strerror(errno));
3218 g_free(filename_temp);
3219 close(fd);
3220 return FALSE;
3222 if (close(fd) < 0) {
3223 purple_debug_error("util", "Error closing %s after sync: %s\n",
3224 filename_temp, g_strerror(errno));
3225 g_free(filename_temp);
3226 return FALSE;
3228 #endif
3230 /* Ensure the file is the correct size */
3231 if (byteswritten != real_size)
3233 purple_debug_error("util", "Error writing to file %s: Wrote %"
3234 G_GSIZE_FORMAT " bytes "
3235 "but should have written %" G_GSIZE_FORMAT
3236 "; is your disk full?\n",
3237 filename_temp, byteswritten, real_size);
3238 g_free(filename_temp);
3239 return FALSE;
3241 #ifndef __COVERITY__
3242 /* Use stat to be absolutely sure.
3243 * It causes TOCTOU coverity warning (against g_rename below),
3244 * but it's not a threat for us.
3246 if ((g_stat(filename_temp, &st) == -1) || ((gsize)st.st_size != real_size)) {
3247 purple_debug_error("util", "Error writing data to file %s: "
3248 "couldn't g_stat file", filename_temp);
3249 g_free(filename_temp);
3250 return FALSE;
3252 #endif /* __COVERITY__ */
3254 /* Rename to the REAL name */
3255 if (g_rename(filename_temp, filename_full) == -1)
3257 purple_debug_error("util", "Error renaming %s to %s: %s\n",
3258 filename_temp, filename_full,
3259 g_strerror(errno));
3262 g_free(filename_temp);
3264 return TRUE;
3267 PurpleXmlNode *
3268 purple_util_read_xml_from_file(const char *filename, const char *description)
3270 return purple_xmlnode_from_file(purple_user_dir(), filename, description, "util");
3274 * Like mkstemp() but returns a file pointer, uses a pre-set template,
3275 * uses the semantics of tempnam() for the directory to use and allocates
3276 * the space for the filepath.
3278 * Caller is responsible for closing the file and removing it when done,
3279 * as well as freeing the space pointed-to by "path" with g_free().
3281 * Returns NULL on failure and cleans up after itself if so.
3283 static const char *purple_mkstemp_templ = {"purpleXXXXXX"};
3285 FILE *
3286 purple_mkstemp(char **fpath, gboolean binary)
3288 const gchar *tmpdir;
3289 int fd;
3290 FILE *fp = NULL;
3292 g_return_val_if_fail(fpath != NULL, NULL);
3294 if((tmpdir = (gchar*)g_get_tmp_dir()) != NULL) {
3295 if((*fpath = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", tmpdir, purple_mkstemp_templ)) != NULL) {
3296 fd = g_mkstemp(*fpath);
3297 if(fd == -1) {
3298 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
3299 "Couldn't make \"%s\", error: %d\n",
3300 *fpath, errno);
3301 } else {
3302 if((fp = fdopen(fd, "r+")) == NULL) {
3303 close(fd);
3304 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
3305 "Couldn't fdopen(), error: %d\n", errno);
3309 if(!fp) {
3310 g_free(*fpath);
3311 *fpath = NULL;
3314 } else {
3315 purple_debug(PURPLE_DEBUG_ERROR, "purple_mkstemp",
3316 "g_get_tmp_dir() failed!\n");
3319 return fp;
3322 gboolean
3323 purple_program_is_valid(const char *program)
3325 GError *error = NULL;
3326 char **argv;
3327 gchar *progname;
3328 gboolean is_valid = FALSE;
3330 g_return_val_if_fail(program != NULL, FALSE);
3331 g_return_val_if_fail(*program != '\0', FALSE);
3333 if (!g_shell_parse_argv(program, NULL, &argv, &error)) {
3334 purple_debug(PURPLE_DEBUG_ERROR, "program_is_valid",
3335 "Could not parse program '%s': %s\n",
3336 program, error->message);
3337 g_error_free(error);
3338 return FALSE;
3341 if (argv == NULL) {
3342 return FALSE;
3345 progname = g_find_program_in_path(argv[0]);
3346 is_valid = (progname != NULL);
3348 if(purple_debug_is_verbose())
3349 purple_debug_info("program_is_valid", "Tested program %s. %s.\n", program,
3350 is_valid ? "Valid" : "Invalid");
3352 g_strfreev(argv);
3353 g_free(progname);
3355 return is_valid;
3359 gboolean
3360 purple_running_gnome(void)
3362 #ifndef _WIN32
3363 gchar *tmp = g_find_program_in_path("gvfs-open");
3365 if (tmp == NULL) {
3366 tmp = g_find_program_in_path("gnome-open");
3368 if (tmp == NULL) {
3369 return FALSE;
3373 g_free(tmp);
3375 tmp = (gchar *)g_getenv("GNOME_DESKTOP_SESSION_ID");
3377 return ((tmp != NULL) && (*tmp != '\0'));
3378 #else
3379 return FALSE;
3380 #endif
3383 gboolean
3384 purple_running_kde(void)
3386 #ifndef _WIN32
3387 gchar *tmp = g_find_program_in_path("kfmclient");
3388 const char *session;
3390 if (tmp == NULL)
3391 return FALSE;
3392 g_free(tmp);
3394 session = g_getenv("KDE_FULL_SESSION");
3395 if (purple_strequal(session, "true"))
3396 return TRUE;
3398 /* If you run Purple from Konsole under !KDE, this will provide a
3399 * a false positive. Since we do the GNOME checks first, this is
3400 * only a problem if you're running something !(KDE || GNOME) and
3401 * you run Purple from Konsole. This really shouldn't be a problem. */
3402 return ((g_getenv("KDEDIR") != NULL) || g_getenv("KDEDIRS") != NULL);
3403 #else
3404 return FALSE;
3405 #endif
3408 gboolean
3409 purple_running_osx(void)
3411 #if defined(__APPLE__)
3412 return TRUE;
3413 #else
3414 return FALSE;
3415 #endif
3418 typedef union purple_sockaddr {
3419 struct sockaddr sa;
3420 struct sockaddr_in sa_in;
3421 #if defined(AF_INET6)
3422 struct sockaddr_in6 sa_in6;
3423 #endif
3424 struct sockaddr_storage sa_stor;
3425 } PurpleSockaddr;
3427 char *
3428 purple_fd_get_ip(int fd)
3430 PurpleSockaddr addr;
3431 socklen_t namelen = sizeof(addr);
3432 int family;
3434 g_return_val_if_fail(fd != 0, NULL);
3436 if (getsockname(fd, &(addr.sa), &namelen))
3437 return NULL;
3439 family = addr.sa.sa_family;
3441 if (family == AF_INET) {
3442 return g_strdup(inet_ntoa(addr.sa_in.sin_addr));
3444 #if defined(AF_INET6) && defined(HAVE_INET_NTOP)
3445 else if (family == AF_INET6) {
3446 char host[INET6_ADDRSTRLEN];
3447 const char *tmp;
3449 tmp = inet_ntop(family, &(addr.sa_in6.sin6_addr), host, sizeof(host));
3450 return g_strdup(tmp);
3452 #endif
3454 return NULL;
3458 purple_socket_get_family(int fd)
3460 PurpleSockaddr addr;
3461 socklen_t len = sizeof(addr);
3463 g_return_val_if_fail(fd >= 0, -1);
3465 if (getsockname(fd, &(addr.sa), &len))
3466 return -1;
3468 return addr.sa.sa_family;
3471 gboolean
3472 purple_socket_speaks_ipv4(int fd)
3474 int family;
3476 g_return_val_if_fail(fd >= 0, FALSE);
3478 family = purple_socket_get_family(fd);
3480 switch (family) {
3481 case AF_INET:
3482 return TRUE;
3483 #if defined(IPV6_V6ONLY)
3484 case AF_INET6:
3486 int val = 0;
3487 socklen_t len = sizeof(val);
3489 if (getsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, &len) != 0)
3490 return FALSE;
3491 return !val;
3493 #endif
3494 default:
3495 return FALSE;
3499 /**************************************************************************
3500 * String Functions
3501 **************************************************************************/
3502 gboolean
3503 purple_strequal(const gchar *left, const gchar *right)
3505 return (g_strcmp0(left, right) == 0);
3508 const char *
3509 purple_normalize(const PurpleAccount *account, const char *str)
3511 const char *ret = NULL;
3512 static char buf[BUF_LEN];
3514 /* This should prevent a crash if purple_normalize gets called with NULL str, see #10115 */
3515 g_return_val_if_fail(str != NULL, "");
3517 if (account != NULL)
3519 PurpleProtocol *protocol =
3520 purple_protocols_find(purple_account_get_protocol_id(account));
3522 if (protocol != NULL)
3523 ret = purple_protocol_client_iface_normalize(protocol, account, str);
3526 if (ret == NULL)
3528 char *tmp;
3530 tmp = g_utf8_normalize(str, -1, G_NORMALIZE_DEFAULT);
3531 g_snprintf(buf, sizeof(buf), "%s", tmp);
3532 g_free(tmp);
3534 ret = buf;
3537 return ret;
3541 * You probably don't want to call this directly, it is
3542 * mainly for use as a protocol callback function. See the
3543 * comments in util.h.
3545 const char *
3546 purple_normalize_nocase(const PurpleAccount *account, const char *str)
3548 static char buf[BUF_LEN];
3549 char *tmp1, *tmp2;
3551 g_return_val_if_fail(str != NULL, NULL);
3553 tmp1 = g_utf8_strdown(str, -1);
3554 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
3555 g_snprintf(buf, sizeof(buf), "%s", tmp2 ? tmp2 : "");
3556 g_free(tmp2);
3557 g_free(tmp1);
3559 return buf;
3562 gboolean
3563 purple_validate(const PurpleProtocol *protocol, const char *str)
3565 const char *normalized;
3567 g_return_val_if_fail(protocol != NULL, FALSE);
3568 g_return_val_if_fail(str != NULL, FALSE);
3570 if (str[0] == '\0')
3571 return FALSE;
3573 if (!PURPLE_PROTOCOL_IMPLEMENTS(protocol, CLIENT_IFACE, normalize))
3574 return TRUE;
3576 normalized = purple_protocol_client_iface_normalize(PURPLE_PROTOCOL(protocol),
3577 NULL, str);
3579 return (NULL != normalized);
3582 gchar *
3583 purple_strdup_withhtml(const gchar *src)
3585 gulong destsize, i, j;
3586 gchar *dest;
3588 g_return_val_if_fail(src != NULL, NULL);
3590 /* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */
3591 destsize = 1;
3592 for (i = 0; src[i] != '\0'; i++)
3594 if (src[i] == '\n')
3595 destsize += 4;
3596 else if (src[i] != '\r')
3597 destsize++;
3600 dest = g_malloc(destsize);
3602 /* Copy stuff, ignoring \r's, because they are dumb */
3603 for (i = 0, j = 0; src[i] != '\0'; i++) {
3604 if (src[i] == '\n') {
3605 strcpy(&dest[j], "<BR>");
3606 j += 4;
3607 } else if (src[i] != '\r')
3608 dest[j++] = src[i];
3611 dest[destsize-1] = '\0';
3613 return dest;
3616 gboolean
3617 purple_str_has_prefix(const char *s, const char *p)
3619 return g_str_has_prefix(s, p);
3622 gboolean
3623 purple_str_has_caseprefix(const gchar *s, const gchar *p)
3625 g_return_val_if_fail(s, FALSE);
3626 g_return_val_if_fail(p, FALSE);
3628 return (g_ascii_strncasecmp(s, p, strlen(p)) == 0);
3631 gboolean
3632 purple_str_has_suffix(const char *s, const char *x)
3634 return g_str_has_suffix(s, x);
3637 char *
3638 purple_str_add_cr(const char *text)
3640 char *ret = NULL;
3641 int count = 0, j;
3642 guint i;
3644 g_return_val_if_fail(text != NULL, NULL);
3646 if (text[0] == '\n')
3647 count++;
3648 for (i = 1; i < strlen(text); i++)
3649 if (text[i] == '\n' && text[i - 1] != '\r')
3650 count++;
3652 if (count == 0)
3653 return g_strdup(text);
3655 ret = g_malloc0(strlen(text) + count + 1);
3657 i = 0; j = 0;
3658 if (text[i] == '\n')
3659 ret[j++] = '\r';
3660 ret[j++] = text[i++];
3661 for (; i < strlen(text); i++) {
3662 if (text[i] == '\n' && text[i - 1] != '\r')
3663 ret[j++] = '\r';
3664 ret[j++] = text[i];
3667 return ret;
3670 void
3671 purple_str_strip_char(char *text, char thechar)
3673 int i, j;
3675 g_return_if_fail(text != NULL);
3677 for (i = 0, j = 0; text[i]; i++)
3678 if (text[i] != thechar)
3679 text[j++] = text[i];
3681 text[j] = '\0';
3684 void
3685 purple_util_chrreplace(char *string, char delimiter,
3686 char replacement)
3688 int i = 0;
3690 g_return_if_fail(string != NULL);
3692 while (string[i] != '\0')
3694 if (string[i] == delimiter)
3695 string[i] = replacement;
3696 i++;
3700 gchar *
3701 purple_strreplace(const char *string, const char *delimiter,
3702 const char *replacement)
3704 gchar **split;
3705 gchar *ret;
3707 g_return_val_if_fail(string != NULL, NULL);
3708 g_return_val_if_fail(delimiter != NULL, NULL);
3709 g_return_val_if_fail(replacement != NULL, NULL);
3711 split = g_strsplit(string, delimiter, 0);
3712 ret = g_strjoinv(replacement, split);
3713 g_strfreev(split);
3715 return ret;
3718 gchar *
3719 purple_strcasereplace(const char *string, const char *delimiter,
3720 const char *replacement)
3722 gchar *ret;
3723 int length_del, length_rep, i, j;
3725 g_return_val_if_fail(string != NULL, NULL);
3726 g_return_val_if_fail(delimiter != NULL, NULL);
3727 g_return_val_if_fail(replacement != NULL, NULL);
3729 length_del = strlen(delimiter);
3730 length_rep = strlen(replacement);
3732 /* Count how many times the delimiter appears */
3733 i = 0; /* position in the source string */
3734 j = 0; /* number of occurrences of "delimiter" */
3735 while (string[i] != '\0') {
3736 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3737 i += length_del;
3738 j += length_rep;
3739 } else {
3740 i++;
3741 j++;
3745 ret = g_malloc(j+1);
3747 i = 0; /* position in the source string */
3748 j = 0; /* position in the destination string */
3749 while (string[i] != '\0') {
3750 if (!g_ascii_strncasecmp(&string[i], delimiter, length_del)) {
3751 strncpy(&ret[j], replacement, length_rep);
3752 i += length_del;
3753 j += length_rep;
3754 } else {
3755 ret[j] = string[i];
3756 i++;
3757 j++;
3761 ret[j] = '\0';
3763 return ret;
3766 /** TODO: Expose this when we can add API */
3767 static const char *
3768 purple_strcasestr_len(const char *haystack, gssize hlen, const char *needle, gssize nlen)
3770 const char *tmp, *ret;
3772 g_return_val_if_fail(haystack != NULL, NULL);
3773 g_return_val_if_fail(needle != NULL, NULL);
3775 if (hlen == -1)
3776 hlen = strlen(haystack);
3777 if (nlen == -1)
3778 nlen = strlen(needle);
3779 tmp = haystack,
3780 ret = NULL;
3782 g_return_val_if_fail(hlen > 0, NULL);
3783 g_return_val_if_fail(nlen > 0, NULL);
3785 while (*tmp && !ret && (hlen - (tmp - haystack)) >= nlen) {
3786 if (!g_ascii_strncasecmp(needle, tmp, nlen))
3787 ret = tmp;
3788 else
3789 tmp++;
3792 return ret;
3795 const char *
3796 purple_strcasestr(const char *haystack, const char *needle)
3798 return purple_strcasestr_len(haystack, -1, needle, -1);
3801 char *
3802 purple_str_size_to_units(goffset size)
3804 static const char * const size_str[] = { "bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
3805 float size_mag;
3806 gsize size_index = 0;
3808 if (size == -1) {
3809 return g_strdup(_("Calculating..."));
3811 else if (size == 0) {
3812 return g_strdup(_("Unknown."));
3814 else {
3815 size_mag = (float)size;
3817 while ((size_index < G_N_ELEMENTS(size_str) - 1) && (size_mag > 1024)) {
3818 size_mag /= 1024;
3819 size_index++;
3822 if (size_index == 0) {
3823 return g_strdup_printf("%" G_GOFFSET_FORMAT " %s", size, _(size_str[size_index]));
3824 } else {
3825 return g_strdup_printf("%.2f %s", size_mag, _(size_str[size_index]));
3830 char *
3831 purple_str_seconds_to_string(guint secs)
3833 char *ret = NULL;
3834 guint days, hrs, mins;
3836 if (secs < 60)
3838 return g_strdup_printf(dngettext(PACKAGE, "%d second", "%d seconds", secs), secs);
3841 days = secs / (60 * 60 * 24);
3842 secs = secs % (60 * 60 * 24);
3843 hrs = secs / (60 * 60);
3844 secs = secs % (60 * 60);
3845 mins = secs / 60;
3846 /* secs = secs % 60; */
3848 if (days > 0)
3850 ret = g_strdup_printf(dngettext(PACKAGE, "%d day", "%d days", days), days);
3853 if (hrs > 0)
3855 if (ret != NULL)
3857 char *tmp = g_strdup_printf(
3858 dngettext(PACKAGE, "%s, %d hour", "%s, %d hours", hrs),
3859 ret, hrs);
3860 g_free(ret);
3861 ret = tmp;
3863 else
3864 ret = g_strdup_printf(dngettext(PACKAGE, "%d hour", "%d hours", hrs), hrs);
3867 if (mins > 0)
3869 if (ret != NULL)
3871 char *tmp = g_strdup_printf(
3872 dngettext(PACKAGE, "%s, %d minute", "%s, %d minutes", mins),
3873 ret, mins);
3874 g_free(ret);
3875 ret = tmp;
3877 else
3878 ret = g_strdup_printf(dngettext(PACKAGE, "%d minute", "%d minutes", mins), mins);
3881 return ret;
3885 char *
3886 purple_str_binary_to_ascii(const unsigned char *binary, guint len)
3888 GString *ret;
3889 guint i;
3891 g_return_val_if_fail(len > 0, NULL);
3893 ret = g_string_sized_new(len);
3895 for (i = 0; i < len; i++)
3896 if (binary[i] < 32 || binary[i] > 126)
3897 g_string_append_printf(ret, "\\x%02x", binary[i] & 0xFF);
3898 else if (binary[i] == '\\')
3899 g_string_append(ret, "\\\\");
3900 else
3901 g_string_append_c(ret, binary[i]);
3903 return g_string_free(ret, FALSE);
3906 size_t
3907 purple_utf16_size(const gunichar2 *str)
3909 /* UTF16 cannot contain two consequent NUL bytes starting at even
3910 * position - see Unicode standards Chapter 3.9 D91 or RFC2781
3911 * Chapter 2.
3914 size_t i = 0;
3916 g_return_val_if_fail(str != NULL, 0);
3918 while (str[i++]);
3920 return i * sizeof(gunichar2);
3923 void
3924 purple_str_wipe(gchar *str)
3926 if (str == NULL)
3927 return;
3928 memset(str, 0, strlen(str));
3929 g_free(str);
3932 void
3933 purple_utf16_wipe(gunichar2 *str)
3935 if (str == NULL)
3936 return;
3937 memset(str, 0, purple_utf16_size(str));
3938 g_free(str);
3941 /**************************************************************************
3942 * URI/URL Functions
3943 **************************************************************************/
3945 void purple_got_protocol_handler_uri(const char *uri)
3947 char proto[11];
3948 char delimiter;
3949 const char *tmp, *param_string;
3950 char *cmd;
3951 GHashTable *params = NULL;
3952 gsize len;
3953 if (!(tmp = strchr(uri, ':')) || tmp == uri) {
3954 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
3955 return;
3958 len = MIN(sizeof(proto) - 1, (gsize)(tmp - uri));
3960 strncpy(proto, uri, len);
3961 proto[len] = '\0';
3963 tmp++;
3965 if (purple_strequal(proto, "xmpp"))
3966 delimiter = ';';
3967 else
3968 delimiter = '&';
3970 purple_debug_info("util", "Processing message '%s' for protocol '%s' using delimiter '%c'.\n", tmp, proto, delimiter);
3972 if ((param_string = strchr(tmp, '?'))) {
3973 const char *keyend = NULL, *pairstart;
3974 char *key, *value = NULL;
3976 cmd = g_strndup(tmp, (param_string - tmp));
3977 param_string++;
3979 params = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
3980 pairstart = tmp = param_string;
3982 while (*tmp || *pairstart) {
3983 if (*tmp == delimiter || !(*tmp)) {
3984 /* If there is no explicit value */
3985 if (keyend == NULL) {
3986 keyend = tmp;
3988 /* without these brackets, clang won't
3989 * recognize tmp as a non-NULL
3992 if (keyend && keyend != pairstart) {
3993 char *p;
3994 key = g_strndup(pairstart, (keyend - pairstart));
3995 /* If there is an explicit value */
3996 if (keyend != tmp && keyend != (tmp - 1))
3997 value = g_strndup(keyend + 1, (tmp - keyend - 1));
3998 for (p = key; *p; ++p)
3999 *p = g_ascii_tolower(*p);
4000 g_hash_table_insert(params, key, value);
4002 keyend = value = NULL;
4003 pairstart = (*tmp) ? tmp + 1 : tmp;
4004 } else if (*tmp == '=')
4005 keyend = tmp;
4007 if (*tmp)
4008 tmp++;
4010 } else
4011 cmd = g_strdup(tmp);
4013 purple_signal_emit_return_1(purple_get_core(), "uri-handler", proto, cmd, params);
4015 g_free(cmd);
4016 if (params)
4017 g_hash_table_destroy(params);
4020 const char *
4021 purple_url_decode(const char *str)
4023 static char buf[BUF_LEN];
4024 guint i, j = 0;
4025 char *bum;
4026 char hex[3];
4028 g_return_val_if_fail(str != NULL, NULL);
4031 * XXX - This check could be removed and buf could be made
4032 * dynamically allocated, but this is easier.
4034 if (strlen(str) >= BUF_LEN)
4035 return NULL;
4037 for (i = 0; i < strlen(str); i++) {
4039 if (str[i] != '%')
4040 buf[j++] = str[i];
4041 else {
4042 strncpy(hex, str + ++i, 2);
4043 hex[2] = '\0';
4045 /* i is pointing to the start of the number */
4046 i++;
4049 * Now it's at the end and at the start of the for loop
4050 * will be at the next character.
4052 buf[j++] = strtol(hex, NULL, 16);
4056 buf[j] = '\0';
4058 if (!g_utf8_validate(buf, -1, (const char **)&bum))
4059 *bum = '\0';
4061 return buf;
4064 const char *
4065 purple_url_encode(const char *str)
4067 const char *iter;
4068 static char buf[BUF_LEN];
4069 char utf_char[6];
4070 guint i, j = 0;
4072 g_return_val_if_fail(str != NULL, NULL);
4073 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4075 iter = str;
4076 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4077 gunichar c = g_utf8_get_char(iter);
4078 /* If the character is an ASCII character and is alphanumeric
4079 * no need to escape */
4080 if (c < 128 && (isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~')) {
4081 buf[j++] = c;
4082 } else {
4083 int bytes = g_unichar_to_utf8(c, utf_char);
4084 for (i = 0; (int)i < bytes; i++) {
4085 if (j > (BUF_LEN - 4))
4086 break;
4087 if (i >= sizeof(utf_char)) {
4088 g_warn_if_reached();
4089 break;
4091 sprintf(buf + j, "%%%02X", utf_char[i] & 0xff);
4092 j += 3;
4097 buf[j] = '\0';
4099 return buf;
4102 /* Originally lifted from
4103 * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html
4104 * ... and slightly modified to be a bit more rfc822 compliant
4105 * ... and modified a bit more to make domain checking rfc1035 compliant
4106 * with the exception permitted in rfc1101 for domains to start with digit
4107 * but not completely checking to avoid conflicts with IP addresses
4109 gboolean
4110 purple_email_is_valid(const char *address)
4112 const char *c, *domain;
4113 static char *rfc822_specials = "()<>@,;:\\\"[]";
4115 g_return_val_if_fail(address != NULL, FALSE);
4117 if (*address == '.') return FALSE;
4119 /* first we validate the name portion (name@domain) (rfc822)*/
4120 for (c = address; *c; c++) {
4121 if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) {
4122 while (*++c) {
4123 if (*c == '\\') {
4124 if (*c++ && *c < 127 && *c != '\n' && *c != '\r') continue;
4125 else return FALSE;
4127 if (*c == '\"') break;
4128 if (*c < ' ' || *c >= 127) return FALSE;
4130 if (!*c++) return FALSE;
4131 if (*c == '@') break;
4132 if (*c != '.') return FALSE;
4133 continue;
4135 if (*c == '@') break;
4136 if (*c <= ' ' || *c >= 127) return FALSE;
4137 if (strchr(rfc822_specials, *c)) return FALSE;
4140 /* It's obviously not an email address if we didn't find an '@' above */
4141 if (*c == '\0') return FALSE;
4143 /* strictly we should return false if (*(c - 1) == '.') too, but I think
4144 * we should permit user.@domain type addresses - they do work :) */
4145 if (c == address) return FALSE;
4147 /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */
4148 if (!*(domain = ++c)) return FALSE;
4149 do {
4150 if (*c == '.' && (c == domain || *(c - 1) == '.' || *(c - 1) == '-'))
4151 return FALSE;
4152 if (*c == '-' && (*(c - 1) == '.' || *(c - 1) == '@')) return FALSE;
4153 if ((*c < '0' && *c != '-' && *c != '.') || (*c > '9' && *c < 'A') ||
4154 (*c > 'Z' && *c < 'a') || (*c > 'z')) return FALSE;
4155 } while (*++c);
4157 if (*(c - 1) == '-') return FALSE;
4159 return ((c - domain) > 3 ? TRUE : FALSE);
4162 gboolean
4163 purple_ipv4_address_is_valid(const char *ip)
4165 int c, o1, o2, o3, o4;
4166 char end;
4168 g_return_val_if_fail(ip != NULL, FALSE);
4170 c = sscanf(ip, "%d.%d.%d.%d%c", &o1, &o2, &o3, &o4, &end);
4171 if (c != 4 || o1 < 0 || o1 > 255 || o2 < 0 || o2 > 255 || o3 < 0 || o3 > 255 || o4 < 0 || o4 > 255)
4172 return FALSE;
4173 return TRUE;
4176 gboolean
4177 purple_ipv6_address_is_valid(const gchar *ip)
4179 const gchar *c;
4180 gboolean double_colon = FALSE;
4181 gint chunks = 1;
4182 gint in = 0;
4184 g_return_val_if_fail(ip != NULL, FALSE);
4186 if (*ip == '\0')
4187 return FALSE;
4189 for (c = ip; *c; ++c) {
4190 if ((*c >= '0' && *c <= '9') ||
4191 (*c >= 'a' && *c <= 'f') ||
4192 (*c >= 'A' && *c <= 'F')) {
4193 if (++in > 4)
4194 /* Only four hex digits per chunk */
4195 return FALSE;
4196 continue;
4197 } else if (*c == ':') {
4198 /* The start of a new chunk */
4199 ++chunks;
4200 in = 0;
4201 if (*(c + 1) == ':') {
4203 * '::' indicates a consecutive series of chunks full
4204 * of zeroes. There can be only one of these per address.
4206 if (double_colon)
4207 return FALSE;
4208 double_colon = TRUE;
4210 } else
4211 return FALSE;
4215 * Either we saw a '::' and there were fewer than 8 chunks -or-
4216 * we didn't see a '::' and saw exactly 8 chunks.
4218 return (double_colon && chunks < 8) || (!double_colon && chunks == 8);
4221 gboolean
4222 purple_ip_address_is_valid(const char *ip)
4224 return (purple_ipv4_address_is_valid(ip) || purple_ipv6_address_is_valid(ip));
4227 /* Stolen from gnome_uri_list_extract_uris */
4228 GList *
4229 purple_uri_list_extract_uris(const gchar *uri_list)
4231 const gchar *p, *q;
4232 gchar *retval;
4233 GList *result = NULL;
4235 g_return_val_if_fail (uri_list != NULL, NULL);
4237 p = uri_list;
4239 /* We don't actually try to validate the URI according to RFC
4240 * 2396, or even check for allowed characters - we just ignore
4241 * comments and trim whitespace off the ends. We also
4242 * allow LF delimination as well as the specified CRLF.
4244 while (p) {
4245 if (*p != '#') {
4246 while (isspace(*p))
4247 p++;
4249 q = p;
4250 while (*q && (*q != '\n') && (*q != '\r'))
4251 q++;
4253 if (q > p) {
4254 q--;
4255 while (q > p && isspace(*q))
4256 q--;
4258 retval = (gchar*)g_malloc (q - p + 2);
4259 strncpy (retval, p, q - p + 1);
4260 retval[q - p + 1] = '\0';
4262 result = g_list_prepend (result, retval);
4265 p = strchr (p, '\n');
4266 if (p)
4267 p++;
4270 return g_list_reverse (result);
4274 /* Stolen from gnome_uri_list_extract_filenames */
4275 GList *
4276 purple_uri_list_extract_filenames(const gchar *uri_list)
4278 GList *tmp_list, *node, *result;
4280 g_return_val_if_fail (uri_list != NULL, NULL);
4282 result = purple_uri_list_extract_uris(uri_list);
4284 tmp_list = result;
4285 while (tmp_list) {
4286 gchar *s = (gchar*)tmp_list->data;
4288 node = tmp_list;
4289 tmp_list = tmp_list->next;
4291 if (!strncmp (s, "file:", 5)) {
4292 node->data = g_filename_from_uri (s, NULL, NULL);
4293 /* not sure if this fallback is useful at all */
4294 if (!node->data) node->data = g_strdup (s+5);
4295 } else {
4296 result = g_list_delete_link(result, node);
4298 g_free (s);
4300 return result;
4303 /**************************************************************************
4304 * UTF8 String Functions
4305 **************************************************************************/
4306 gchar *
4307 purple_utf8_try_convert(const char *str)
4309 gsize converted;
4310 gchar *utf8;
4312 g_return_val_if_fail(str != NULL, NULL);
4314 if (g_utf8_validate(str, -1, NULL)) {
4315 return g_strdup(str);
4318 utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL);
4319 if (utf8 != NULL)
4320 return utf8;
4322 utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL);
4323 if ((utf8 != NULL) && (converted == strlen(str)))
4324 return utf8;
4326 g_free(utf8);
4328 return NULL;
4331 #define utf8_first(x) ((x & 0x80) == 0 || (x & 0xe0) == 0xc0 \
4332 || (x & 0xf0) == 0xe0 || (x & 0xf8) == 0xf0)
4333 gchar *
4334 purple_utf8_salvage(const char *str)
4336 GString *workstr;
4337 const char *end;
4339 g_return_val_if_fail(str != NULL, NULL);
4341 workstr = g_string_sized_new(strlen(str));
4343 do {
4344 (void)g_utf8_validate(str, -1, &end);
4345 workstr = g_string_append_len(workstr, str, end - str);
4346 str = end;
4347 if (*str == '\0')
4348 break;
4349 do {
4350 workstr = g_string_append_c(workstr, '?');
4351 str++;
4352 } while (!utf8_first(*str));
4353 } while (*str != '\0');
4355 return g_string_free(workstr, FALSE);
4358 gchar *
4359 purple_utf8_strip_unprintables(const gchar *str)
4361 gchar *workstr, *iter;
4362 const gchar *bad;
4364 if (str == NULL)
4365 /* Act like g_strdup */
4366 return NULL;
4368 if (!g_utf8_validate(str, -1, &bad)) {
4369 purple_debug_error("util", "purple_utf8_strip_unprintables(%s) failed; "
4370 "first bad character was %02x (%c)\n",
4371 str, *bad, *bad);
4372 g_return_val_if_reached(NULL);
4375 workstr = iter = g_new(gchar, strlen(str) + 1);
4376 while (*str) {
4377 gunichar ch = g_utf8_get_char(str);
4378 gchar *next = g_utf8_next_char(str);
4380 * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
4381 * [#x10000-#x10FFFF]
4383 if ((ch == '\t' || ch == '\n' || ch == '\r') ||
4384 (ch >= 0x20 && ch <= 0xD7FF) ||
4385 (ch >= 0xE000 && ch <= 0xFFFD) ||
4386 (ch >= 0x10000 && ch <= 0x10FFFF)) {
4387 memcpy(iter, str, next - str);
4388 iter += (next - str);
4391 str = next;
4394 /* nul-terminate the new string */
4395 *iter = '\0';
4397 return workstr;
4401 * This function is copied from g_strerror() but changed to use
4402 * gai_strerror().
4404 const gchar *
4405 purple_gai_strerror(gint errnum)
4407 static GPrivate msg_private = G_PRIVATE_INIT(g_free);
4408 char *msg;
4409 int saved_errno = errno;
4411 const char *msg_locale;
4413 msg_locale = gai_strerror(errnum);
4414 if (g_get_charset(NULL))
4416 /* This string is already UTF-8--great! */
4417 errno = saved_errno;
4418 return msg_locale;
4420 else
4422 gchar *msg_utf8 = g_locale_to_utf8(msg_locale, -1, NULL, NULL, NULL);
4423 if (msg_utf8)
4425 /* Stick in the quark table so that we can return a static result */
4426 GQuark msg_quark = g_quark_from_string(msg_utf8);
4427 g_free(msg_utf8);
4429 msg_utf8 = (gchar *)g_quark_to_string(msg_quark);
4430 errno = saved_errno;
4431 return msg_utf8;
4435 msg = g_private_get(&msg_private);
4437 if (!msg)
4439 msg = g_new(gchar, 64);
4440 g_private_set(&msg_private, msg);
4443 sprintf(msg, "unknown error (%d)", errnum);
4445 errno = saved_errno;
4446 return msg;
4449 char *
4450 purple_utf8_ncr_encode(const char *str)
4452 GString *out;
4454 g_return_val_if_fail(str != NULL, NULL);
4455 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4457 out = g_string_new("");
4459 for(; *str; str = g_utf8_next_char(str)) {
4460 gunichar wc = g_utf8_get_char(str);
4462 /* super simple check. hopefully not too wrong. */
4463 if(wc >= 0x80) {
4464 g_string_append_printf(out, "&#%u;", (guint32) wc);
4465 } else {
4466 g_string_append_unichar(out, wc);
4470 return g_string_free(out, FALSE);
4474 char *
4475 purple_utf8_ncr_decode(const char *str)
4477 GString *out;
4478 char *buf, *b;
4480 g_return_val_if_fail(str != NULL, NULL);
4481 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4483 buf = (char *) str;
4484 out = g_string_new("");
4486 while( (b = strstr(buf, "&#")) ) {
4487 gunichar wc;
4488 int base = 0;
4490 /* append everything leading up to the &# */
4491 g_string_append_len(out, buf, b-buf);
4493 b += 2; /* skip past the &# */
4495 /* strtoul will treat 0x prefix as hex, but not just x */
4496 if(*b == 'x' || *b == 'X') {
4497 base = 16;
4498 b++;
4501 /* advances buf to the end of the ncr segment */
4502 wc = (gunichar) strtoul(b, &buf, base);
4504 /* this mimics the previous impl of ncr_decode */
4505 if(*buf == ';') {
4506 g_string_append_unichar(out, wc);
4507 buf++;
4511 /* append whatever's left */
4512 g_string_append(out, buf);
4514 return g_string_free(out, FALSE);
4519 purple_utf8_strcasecmp(const char *a, const char *b)
4521 char *a_norm = NULL;
4522 char *b_norm = NULL;
4523 int ret = -1;
4525 if(!a && b)
4526 return -1;
4527 else if(!b && a)
4528 return 1;
4529 else if(!a && !b)
4530 return 0;
4532 if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL))
4534 purple_debug_error("purple_utf8_strcasecmp",
4535 "One or both parameters are invalid UTF8\n");
4536 return ret;
4539 a_norm = g_utf8_casefold(a, -1);
4540 b_norm = g_utf8_casefold(b, -1);
4541 ret = g_utf8_collate(a_norm, b_norm);
4542 g_free(a_norm);
4543 g_free(b_norm);
4545 return ret;
4548 /* previously conversation::find_nick() */
4549 gboolean
4550 purple_utf8_has_word(const char *haystack, const char *needle)
4552 char *hay, *pin, *p;
4553 const char *start, *prev_char;
4554 gunichar before, after;
4555 int n;
4556 gboolean ret = FALSE;
4558 start = hay = g_utf8_strdown(haystack, -1);
4560 pin = g_utf8_strdown(needle, -1);
4561 n = strlen(pin);
4563 while ((p = strstr(start, pin)) != NULL) {
4564 prev_char = g_utf8_find_prev_char(hay, p);
4565 before = -2;
4566 if (prev_char) {
4567 before = g_utf8_get_char(prev_char);
4569 after = g_utf8_get_char_validated(p + n, - 1);
4571 if ((p == hay ||
4572 /* The character before is a reasonable guess for a word boundary
4573 ("!g_unichar_isalnum()" is not a valid way to determine word
4574 boundaries, but it is the only reasonable thing to do here),
4575 and isn't the '&' from a "&amp;" or some such entity*/
4576 (before != (gunichar)-2 && !g_unichar_isalnum(before) && *(p - 1) != '&'))
4577 && after != (gunichar)-2 && !g_unichar_isalnum(after)) {
4578 ret = TRUE;
4579 break;
4581 start = p + 1;
4584 g_free(pin);
4585 g_free(hay);
4587 return ret;
4590 void
4591 purple_print_utf8_to_console(FILE *filestream, char *message)
4593 gchar *message_conv;
4594 GError *error = NULL;
4596 /* Try to convert 'message' to user's locale */
4597 message_conv = g_locale_from_utf8(message, -1, NULL, NULL, &error);
4598 if (message_conv != NULL) {
4599 fputs(message_conv, filestream);
4600 g_free(message_conv);
4602 else
4604 /* use 'message' as a fallback */
4605 g_warning("%s\n", error->message);
4606 g_error_free(error);
4607 fputs(message, filestream);
4611 gboolean purple_message_meify(char *message, gssize len)
4613 char *c;
4614 gboolean inside_html = FALSE;
4616 g_return_val_if_fail(message != NULL, FALSE);
4618 if(len == -1)
4619 len = strlen(message);
4621 for (c = message; *c; c++, len--) {
4622 if(inside_html) {
4623 if(*c == '>')
4624 inside_html = FALSE;
4625 } else {
4626 if(*c == '<')
4627 inside_html = TRUE;
4628 else
4629 break;
4633 if(*c && !g_ascii_strncasecmp(c, "/me ", 4)) {
4634 memmove(c, c+4, len-3);
4635 return TRUE;
4638 return FALSE;
4641 char *purple_text_strip_mnemonic(const char *in)
4643 char *out;
4644 char *a;
4645 char *a0;
4646 const char *b;
4648 g_return_val_if_fail(in != NULL, NULL);
4650 out = g_malloc(strlen(in)+1);
4651 a = out;
4652 b = in;
4654 a0 = a; /* The last non-space char seen so far, or the first char */
4656 while(*b) {
4657 if(*b == '_') {
4658 if(a > out && b > in && *(b-1) == '(' && *(b+1) && !(*(b+1) & 0x80) && *(b+2) == ')') {
4659 /* Detected CJK style shortcut (Bug 875311) */
4660 a = a0; /* undo the left parenthesis */
4661 b += 3; /* and skip the whole mess */
4662 } else if(*(b+1) == '_') {
4663 *(a++) = '_';
4664 b += 2;
4665 a0 = a;
4666 } else {
4667 b++;
4669 /* We don't want to corrupt the middle of UTF-8 characters */
4670 } else if (!(*b & 0x80)) { /* other 1-byte char */
4671 if (*b != ' ')
4672 a0 = a;
4673 *(a++) = *(b++);
4674 } else {
4675 /* Multibyte utf8 char, don't look for _ inside these */
4676 int n = 0;
4677 int i;
4678 if ((*b & 0xe0) == 0xc0) {
4679 n = 2;
4680 } else if ((*b & 0xf0) == 0xe0) {
4681 n = 3;
4682 } else if ((*b & 0xf8) == 0xf0) {
4683 n = 4;
4684 } else if ((*b & 0xfc) == 0xf8) {
4685 n = 5;
4686 } else if ((*b & 0xfe) == 0xfc) {
4687 n = 6;
4688 } else { /* Illegal utf8 */
4689 n = 1;
4691 a0 = a; /* unless we want to delete CJK spaces too */
4692 for (i = 0; i < n && *b; i += 1) {
4693 *(a++) = *(b++);
4697 *a = '\0';
4699 return out;
4702 const char* purple_unescape_filename(const char *escaped) {
4703 return purple_url_decode(escaped);
4707 /* this is almost identical to purple_url_encode (hence purple_url_decode
4708 * being used above), but we want to keep certain characters unescaped
4709 * for compat reasons */
4710 const char *
4711 purple_escape_filename(const char *str)
4713 const char *iter;
4714 static char buf[BUF_LEN];
4715 char utf_char[6];
4716 guint i, j = 0;
4718 g_return_val_if_fail(str != NULL, NULL);
4719 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL);
4721 iter = str;
4722 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) {
4723 gunichar c = g_utf8_get_char(iter);
4724 /* If the character is an ASCII character and is alphanumeric,
4725 * or one of the specified values, no need to escape */
4726 if (c < 128 && (g_ascii_isalnum(c) || c == '@' || c == '-' ||
4727 c == '_' || c == '.' || c == '#')) {
4728 buf[j++] = c;
4729 } else {
4730 int bytes = g_unichar_to_utf8(c, utf_char);
4731 for (i = 0; (int)i < bytes; i++) {
4732 if (j > (BUF_LEN - 4))
4733 break;
4734 if (i >= sizeof(utf_char)) {
4735 g_warn_if_reached();
4736 break;
4738 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff);
4739 j += 3;
4743 #ifdef _WIN32
4744 /* File/Directory names in windows cannot end in periods/spaces.
4745 * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
4747 while (j > 0 && (buf[j - 1] == '.' || buf[j - 1] == ' '))
4748 j--;
4749 #endif
4750 buf[j] = '\0';
4752 return buf;
4755 gchar * purple_escape_js(const gchar *str)
4757 gchar *escaped;
4759 json_node_set_string(escape_js_node, str);
4760 json_generator_set_root(escape_js_gen, escape_js_node);
4761 escaped = json_generator_to_data(escape_js_gen, NULL);
4762 json_node_set_boolean(escape_js_node, FALSE);
4764 return escaped;
4767 void purple_restore_default_signal_handlers(void)
4769 #ifndef _WIN32
4770 signal(SIGHUP, SIG_DFL); /* 1: terminal line hangup */
4771 signal(SIGINT, SIG_DFL); /* 2: interrupt program */
4772 signal(SIGQUIT, SIG_DFL); /* 3: quit program */
4773 signal(SIGILL, SIG_DFL); /* 4: illegal instruction (not reset when caught) */
4774 signal(SIGTRAP, SIG_DFL); /* 5: trace trap (not reset when caught) */
4775 signal(SIGABRT, SIG_DFL); /* 6: abort program */
4777 #ifdef SIGPOLL
4778 signal(SIGPOLL, SIG_DFL); /* 7: pollable event (POSIX) */
4779 #endif /* SIGPOLL */
4781 #ifdef SIGEMT
4782 signal(SIGEMT, SIG_DFL); /* 7: EMT instruction (Non-POSIX) */
4783 #endif /* SIGEMT */
4785 signal(SIGFPE, SIG_DFL); /* 8: floating point exception */
4786 signal(SIGBUS, SIG_DFL); /* 10: bus error */
4787 signal(SIGSEGV, SIG_DFL); /* 11: segmentation violation */
4788 signal(SIGSYS, SIG_DFL); /* 12: bad argument to system call */
4789 signal(SIGPIPE, SIG_DFL); /* 13: write on a pipe with no reader */
4790 signal(SIGALRM, SIG_DFL); /* 14: real-time timer expired */
4791 signal(SIGTERM, SIG_DFL); /* 15: software termination signal */
4792 signal(SIGCHLD, SIG_DFL); /* 20: child status has changed */
4793 signal(SIGXCPU, SIG_DFL); /* 24: exceeded CPU time limit */
4794 signal(SIGXFSZ, SIG_DFL); /* 25: exceeded file size limit */
4795 #endif /* !_WIN32 */
4798 static void
4799 set_status_with_attrs(PurpleStatus *status, ...)
4801 va_list args;
4802 va_start(args, status);
4803 purple_status_set_active_with_attrs(status, TRUE, args);
4804 va_end(args);
4807 void purple_util_set_current_song(const char *title, const char *artist, const char *album)
4809 GList *list = purple_accounts_get_all();
4810 for (; list; list = list->next) {
4811 PurplePresence *presence;
4812 PurpleStatus *tune;
4813 PurpleAccount *account = list->data;
4814 if (!purple_account_get_enabled(account, purple_core_get_ui()))
4815 continue;
4817 presence = purple_account_get_presence(account);
4818 tune = purple_presence_get_status(presence, "tune");
4819 if (!tune)
4820 continue;
4821 if (title) {
4822 set_status_with_attrs(tune,
4823 PURPLE_TUNE_TITLE, title,
4824 PURPLE_TUNE_ARTIST, artist,
4825 PURPLE_TUNE_ALBUM, album,
4826 NULL);
4827 } else {
4828 purple_status_set_active(tune, FALSE);
4833 char * purple_util_format_song_info(const char *title, const char *artist, const char *album, gpointer unused)
4835 GString *string;
4836 char *esc;
4838 if (!title || !*title)
4839 return NULL;
4841 esc = g_markup_escape_text(title, -1);
4842 string = g_string_new("");
4843 g_string_append_printf(string, "%s", esc);
4844 g_free(esc);
4846 if (artist && *artist) {
4847 esc = g_markup_escape_text(artist, -1);
4848 g_string_append_printf(string, _(" - %s"), esc);
4849 g_free(esc);
4852 if (album && *album) {
4853 esc = g_markup_escape_text(album, -1);
4854 g_string_append_printf(string, _(" (%s)"), esc);
4855 g_free(esc);
4858 return g_string_free(string, FALSE);
4861 const gchar *
4862 purple_get_host_name(void)
4864 return g_get_host_name();
4867 gchar *
4868 purple_uuid_random(void)
4870 guint32 tmp, a, b;
4872 tmp = g_random_int();
4873 a = 0x4000 | (tmp & 0xFFF); /* 0x4000 to 0x4FFF */
4874 tmp >>= 12;
4875 b = ((1 << 3) << 12) | (tmp & 0x3FFF); /* 0x8000 to 0xBFFF */
4877 tmp = g_random_int();
4879 return g_strdup_printf("%08x-%04x-%04x-%04x-%04x%08x",
4880 g_random_int(),
4881 tmp & 0xFFFF,
4884 (tmp >> 16) & 0xFFFF, g_random_int());
4887 void purple_callback_set_zero(gpointer data)
4889 gpointer *ptr = data;
4891 g_return_if_fail(ptr != NULL);
4893 *ptr = NULL;
4896 GValue *
4897 purple_value_new(GType type)
4899 GValue *ret;
4901 g_return_val_if_fail(type != G_TYPE_NONE, NULL);
4903 ret = g_new0(GValue, 1);
4904 g_value_init(ret, type);
4906 return ret;
4909 GValue *
4910 purple_value_dup(GValue *value)
4912 GValue *ret;
4914 g_return_val_if_fail(value != NULL, NULL);
4916 ret = g_new0(GValue, 1);
4917 g_value_init(ret, G_VALUE_TYPE(value));
4918 g_value_copy(value, ret);
4920 return ret;
4923 void
4924 purple_value_free(GValue *value)
4926 g_return_if_fail(value != NULL);
4928 g_value_unset(value);
4929 g_free(value);
4932 gchar *purple_http_digest_calculate_session_key(
4933 const gchar *algorithm,
4934 const gchar *username,
4935 const gchar *realm,
4936 const gchar *password,
4937 const gchar *nonce,
4938 const gchar *client_nonce)
4940 GChecksum *hasher;
4941 gchar *hash;
4943 g_return_val_if_fail(username != NULL, NULL);
4944 g_return_val_if_fail(realm != NULL, NULL);
4945 g_return_val_if_fail(password != NULL, NULL);
4946 g_return_val_if_fail(nonce != NULL, NULL);
4948 /* Check for a supported algorithm. */
4949 g_return_val_if_fail(algorithm == NULL ||
4950 *algorithm == '\0' ||
4951 g_ascii_strcasecmp(algorithm, "MD5") ||
4952 g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);
4954 hasher = g_checksum_new(G_CHECKSUM_MD5);
4955 g_return_val_if_fail(hasher != NULL, NULL);
4957 g_checksum_update(hasher, (guchar *)username, -1);
4958 g_checksum_update(hasher, (guchar *)":", -1);
4959 g_checksum_update(hasher, (guchar *)realm, -1);
4960 g_checksum_update(hasher, (guchar *)":", -1);
4961 g_checksum_update(hasher, (guchar *)password, -1);
4963 if (algorithm != NULL && !g_ascii_strcasecmp(algorithm, "MD5-sess"))
4965 guchar digest[16];
4966 gsize digest_len = 16;
4968 if (client_nonce == NULL)
4970 g_object_unref(hasher);
4971 purple_debug_error("hash", "Required client_nonce missing for MD5-sess digest calculation.\n");
4972 return NULL;
4975 g_checksum_get_digest(hasher, digest, &digest_len);
4977 g_checksum_reset(hasher);
4978 g_checksum_update(hasher, digest, sizeof(digest));
4979 g_checksum_update(hasher, (guchar *)":", -1);
4980 g_checksum_update(hasher, (guchar *)nonce, -1);
4981 g_checksum_update(hasher, (guchar *)":", -1);
4982 g_checksum_update(hasher, (guchar *)client_nonce, -1);
4985 hash = g_strdup(g_checksum_get_string(hasher));
4986 g_checksum_free(hasher);
4988 return hash;
4991 gchar *purple_http_digest_calculate_response(
4992 const gchar *algorithm,
4993 const gchar *method,
4994 const gchar *digest_uri,
4995 const gchar *qop,
4996 const gchar *entity,
4997 const gchar *nonce,
4998 const gchar *nonce_count,
4999 const gchar *client_nonce,
5000 const gchar *session_key)
5002 GChecksum *hash;
5003 gchar *hash2;
5005 g_return_val_if_fail(method != NULL, NULL);
5006 g_return_val_if_fail(digest_uri != NULL, NULL);
5007 g_return_val_if_fail(nonce != NULL, NULL);
5008 g_return_val_if_fail(session_key != NULL, NULL);
5010 /* Check for a supported algorithm. */
5011 g_return_val_if_fail(algorithm == NULL ||
5012 *algorithm == '\0' ||
5013 g_ascii_strcasecmp(algorithm, "MD5") ||
5014 g_ascii_strcasecmp(algorithm, "MD5-sess"), NULL);
5016 /* Check for a supported "quality of protection". */
5017 g_return_val_if_fail(qop == NULL ||
5018 *qop == '\0' ||
5019 g_ascii_strcasecmp(qop, "auth") ||
5020 g_ascii_strcasecmp(qop, "auth-int"), NULL);
5022 hash = g_checksum_new(G_CHECKSUM_MD5);
5023 g_return_val_if_fail(hash != NULL, NULL);
5025 g_checksum_update(hash, (guchar *)method, -1);
5026 g_checksum_update(hash, (guchar *)":", -1);
5027 g_checksum_update(hash, (guchar *)digest_uri, -1);
5029 if (qop != NULL && !g_ascii_strcasecmp(qop, "auth-int"))
5031 gchar *entity_hash;
5033 if (entity == NULL)
5035 g_checksum_free(hash);
5036 purple_debug_error("hash", "Required entity missing for auth-int digest calculation.\n");
5037 return NULL;
5040 entity_hash = g_compute_checksum_for_string(G_CHECKSUM_MD5,
5041 entity, -1);
5043 if (entity_hash == NULL) {
5044 g_checksum_free(hash);
5045 g_return_val_if_reached(NULL);
5048 g_checksum_update(hash, (guchar *)":", -1);
5049 g_checksum_update(hash, (guchar *)entity_hash, -1);
5050 g_free(entity_hash);
5053 hash2 = g_strdup(g_checksum_get_string(hash));
5054 g_checksum_reset(hash);
5056 if (hash2 == NULL) {
5057 g_checksum_free(hash);
5058 g_return_val_if_reached(NULL);
5061 g_checksum_update(hash, (guchar *)session_key, -1);
5062 g_checksum_update(hash, (guchar *)":", -1);
5063 g_checksum_update(hash, (guchar *)nonce, -1);
5064 g_checksum_update(hash, (guchar *)":", -1);
5066 if (qop != NULL && *qop != '\0')
5068 if (nonce_count == NULL)
5070 g_checksum_free(hash);
5071 purple_debug_error("hash", "Required nonce_count missing for digest calculation.\n");
5072 return NULL;
5075 if (client_nonce == NULL)
5077 g_checksum_free(hash);
5078 purple_debug_error("hash", "Required client_nonce missing for digest calculation.\n");
5079 return NULL;
5082 g_checksum_update(hash, (guchar *)nonce_count, -1);
5083 g_checksum_update(hash, (guchar *)":", -1);
5084 g_checksum_update(hash, (guchar *)client_nonce, -1);
5085 g_checksum_update(hash, (guchar *)":", -1);
5087 g_checksum_update(hash, (guchar *)qop, -1);
5089 g_checksum_update(hash, (guchar *)":", -1);
5092 g_checksum_update(hash, (guchar *)hash2, -1);
5093 g_free(hash2);
5095 hash2 = g_strdup(g_checksum_get_string(hash));
5096 g_checksum_free(hash);
5098 return hash2;
5102 _purple_fstat(int fd, GStatBuf *st)
5104 int ret;
5106 g_return_val_if_fail(st != NULL, -1);
5108 #ifdef _WIN32
5109 ret = _fstat(fd, st);
5110 #else
5111 ret = fstat(fd, st);
5112 #endif
5114 return ret;
5117 #if 0
5119 /* Temporarily removed - re-add this when you need ini file support. */
5121 #define PURPLE_KEY_FILE_DEFAULT_MAX_SIZE 102400
5122 #define PURPLE_KEY_FILE_HARD_LIMIT 10485760
5124 gboolean
5125 purple_key_file_load_from_ini(GKeyFile *key_file, const gchar *file,
5126 gsize max_size)
5128 const gchar *header = "[default]\n\n";
5129 int header_len = strlen(header);
5130 int fd;
5131 GStatBuf st;
5132 gsize file_size, buff_size;
5133 gchar *buff;
5134 GError *error = NULL;
5136 g_return_val_if_fail(key_file != NULL, FALSE);
5137 g_return_val_if_fail(file != NULL, FALSE);
5138 g_return_val_if_fail(max_size < PURPLE_KEY_FILE_HARD_LIMIT, FALSE);
5140 if (max_size == 0)
5141 max_size = PURPLE_KEY_FILE_DEFAULT_MAX_SIZE;
5143 fd = g_open(file, O_RDONLY, S_IREAD);
5144 if (fd == -1) {
5145 purple_debug_error("util", "Failed to read ini file %s", file);
5146 return FALSE;
5149 if (_purple_fstat(fd, &st) != 0) {
5150 purple_debug_error("util", "Failed to fstat ini file %s", file);
5151 return FALSE;
5154 file_size = (st.st_size > max_size) ? max_size : st.st_size;
5156 buff_size = file_size + header_len;
5157 buff = g_new(gchar, buff_size);
5158 memcpy(buff, header, header_len);
5159 if (read(fd, buff + header_len, file_size) != (gssize)file_size) {
5160 purple_debug_error("util",
5161 "Failed to read whole ini file %s", file);
5162 g_close(fd, NULL);
5163 free(buff);
5164 return FALSE;
5166 g_close(fd, NULL);
5168 g_key_file_load_from_data(key_file, buff, buff_size,
5169 G_KEY_FILE_NONE, &error);
5171 free(buff);
5173 if (error) {
5174 purple_debug_error("util", "Failed parsing ini file %s: %s",
5175 file, error->message);
5176 return FALSE;
5179 return TRUE;
5181 #endif