2 * @file util.h Utility Functions
6 /* Purple is the legal property of its developers, whose names are too numerous
7 * to list here. Please refer to the COPYRIGHT file distributed with this
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
27 #include "conversation.h"
36 struct _PurpleUtilFetchUrlData
38 PurpleUtilFetchUrlCallback callback
;
52 int num_times_redirected
;
57 gsize request_written
;
58 gboolean include_headers
;
61 PurpleSslConnection
*ssl_connection
;
62 PurpleProxyConnectData
*connect_data
;
67 gboolean has_explicit_data_len
;
70 unsigned long data_len
;
73 PurpleAccount
*account
;
76 static char *custom_user_dir
= NULL
;
77 static char *user_dir
= NULL
;
81 purple_menu_action_new(const char *label
, PurpleCallback callback
, gpointer data
,
84 PurpleMenuAction
*act
= g_new0(PurpleMenuAction
, 1);
85 act
->label
= g_strdup(label
);
86 act
->callback
= callback
;
88 act
->children
= children
;
93 purple_menu_action_free(PurpleMenuAction
*act
)
95 g_return_if_fail(act
!= NULL
);
102 purple_util_init(void)
104 /* This does nothing right now. It exists for symmetry with
105 * purple_util_uninit() and forwards compatibility. */
109 purple_util_uninit(void)
111 /* Free these so we don't have leaks at shutdown. */
113 g_free(custom_user_dir
);
114 custom_user_dir
= NULL
;
120 /**************************************************************************
122 **************************************************************************/
124 purple_base16_encode(const guchar
*data
, gsize len
)
129 g_return_val_if_fail(data
!= NULL
, NULL
);
130 g_return_val_if_fail(len
> 0, NULL
);
132 ascii
= g_malloc(len
* 2 + 1);
134 for (i
= 0; i
< len
; i
++)
135 g_snprintf(&ascii
[i
* 2], 3, "%02hhx", data
[i
]);
141 purple_base16_decode(const char *str
, gsize
*ret_len
)
143 int len
, i
, accumulator
= 0;
146 g_return_val_if_fail(str
!= NULL
, NULL
);
150 g_return_val_if_fail(strlen(str
) > 0, 0);
151 g_return_val_if_fail(len
% 2 == 0, 0);
153 data
= g_malloc(len
/ 2);
155 for (i
= 0; i
< len
; i
++)
163 accumulator
|= str
[i
] - 48;
166 switch(tolower(str
[i
]))
168 case 'a': accumulator
|= 10; break;
169 case 'b': accumulator
|= 11; break;
170 case 'c': accumulator
|= 12; break;
171 case 'd': accumulator
|= 13; break;
172 case 'e': accumulator
|= 14; break;
173 case 'f': accumulator
|= 15; break;
178 data
[(i
- 1) / 2] = accumulator
;
188 purple_base16_encode_chunked(const guchar
*data
, gsize len
)
193 g_return_val_if_fail(data
!= NULL
, NULL
);
194 g_return_val_if_fail(len
> 0, NULL
);
196 /* For each byte of input, we need 2 bytes for the hex representation
197 * and 1 for the colon.
198 * The final colon will be replaced by a terminating NULL
200 ascii
= g_malloc(len
* 3 + 1);
202 for (i
= 0; i
< len
; i
++)
203 g_snprintf(&ascii
[i
* 3], 4, "%02hhx:", data
[i
]);
205 /* Replace the final colon with NULL */
206 ascii
[len
* 3 - 1] = 0;
212 /**************************************************************************
214 **************************************************************************/
215 static const char alphabet
[] =
216 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
219 static const char xdigits
[] =
223 purple_base64_encode(const guchar
*data
, gsize len
)
225 return g_base64_encode(data
, len
);
229 purple_base64_decode(const char *str
, gsize
*ret_len
)
232 * We want to allow ret_len to be NULL for backward compatibility,
233 * but g_base64_decode() requires a valid length variable. So if
234 * ret_len is NULL then pass in a dummy variable.
237 return g_base64_decode(str
, ret_len
!= NULL
? ret_len
: &unused
);
240 /**************************************************************************
241 * Quoted Printable Functions (see RFC 2045).
242 **************************************************************************/
244 purple_quotedp_decode(const char *str
, gsize
*ret_len
)
249 n
= new = g_malloc(strlen (str
) + 1);
250 end
= str
+ strlen(str
);
252 for (p
= str
; p
< end
; p
++, n
++) {
254 if (p
[1] == '\r' && p
[2] == '\n') { /* 5.1 #5 */
257 } else if (p
[1] == '\n') { /* fuzzy case for 5.1 #5 */
260 } else if (p
[1] && p
[2]) {
261 char *nibble1
= strchr(xdigits
, tolower(p
[1]));
262 char *nibble2
= strchr(xdigits
, tolower(p
[2]));
263 if (nibble1
&& nibble2
) { /* 5.1 #1 */
264 *n
= ((nibble1
- xdigits
) << 4) | (nibble2
- xdigits
);
266 } else { /* This should never happen */
269 } else { /* This should never happen */
284 /* Resize to take less space */
285 /* new = realloc(new, n - new); */
287 return (guchar
*)new;
290 /**************************************************************************
292 **************************************************************************/
294 purple_mime_decode_field(const char *str
)
297 * This is wing's version, partially based on revo/shx's version
298 * See RFC2047 [which apparently obsoletes RFC1342]
301 state_start
, state_equal1
, state_question1
,
302 state_charset
, state_question2
,
303 state_encoding
, state_question3
,
304 state_encoded_text
, state_question4
, state_equal2
= state_start
305 } encoded_word_state_t
;
306 encoded_word_state_t state
= state_start
;
307 const char *cur
, *mark
;
308 const char *charset0
= NULL
, *encoding0
= NULL
, *encoded_text0
= NULL
;
311 /* token can be any CHAR (supposedly ISO8859-1/ISO2022), not just ASCII */
312 #define token_char_p(c) \
313 (c != ' ' && !iscntrl(c) && !strchr("()<>@,;:\"/[]?.=", c))
315 /* But encoded-text must be ASCII; alas, isascii() may not exist */
316 #define encoded_text_char_p(c) \
317 ((c & 0x80) == 0 && c != '?' && c != ' ' && isgraph(c))
319 g_return_val_if_fail(str
!= NULL
, NULL
);
321 new = g_string_new(NULL
);
323 /* Here we will be looking for encoded words and if they seem to be
324 * valid then decode them.
325 * They are of this form: =?charset?encoding?text?=
328 for (cur
= str
, mark
= NULL
; *cur
; cur
+= 1) {
332 state
= state_question1
;
334 g_string_append_len(new, mark
, cur
- mark
+ 1);
338 case state_question1
:
339 if (token_char_p(*cur
)) {
341 state
= state_charset
;
342 } else { /* This should never happen */
343 g_string_append_len(new, mark
, cur
- mark
+ 1);
349 state
= state_question2
;
350 } else if (!token_char_p(*cur
)) { /* This should never happen */
351 g_string_append_len(new, mark
, cur
- mark
+ 1);
355 case state_question2
:
356 if (token_char_p(*cur
)) {
358 state
= state_encoding
;
359 } else { /* This should never happen */
360 g_string_append_len(new, mark
, cur
- mark
+ 1);
366 state
= state_question3
;
367 } else if (!token_char_p(*cur
)) { /* This should never happen */
368 g_string_append_len(new, mark
, cur
- mark
+ 1);
372 case state_question3
:
373 if (encoded_text_char_p(*cur
)) {
375 state
= state_encoded_text
;
376 } else if (*cur
== '?') { /* empty string */
378 state
= state_question4
;
379 } else { /* This should never happen */
380 g_string_append_len(new, mark
, cur
- mark
+ 1);
384 case state_encoded_text
:
386 state
= state_question4
;
387 } else if (!encoded_text_char_p(*cur
)) {
388 g_string_append_len(new, mark
, cur
- mark
+ 1);
392 case state_question4
:
393 if (*cur
== '=') { /* Got the whole encoded-word */
394 char *charset
= g_strndup(charset0
, encoding0
- charset0
- 1);
395 char *encoding
= g_strndup(encoding0
, encoded_text0
- encoding0
- 1);
396 char *encoded_text
= g_strndup(encoded_text0
, cur
- encoded_text0
- 1);
397 guchar
*decoded
= NULL
;
399 if (g_ascii_strcasecmp(encoding
, "Q") == 0)
400 decoded
= purple_quotedp_decode(encoded_text
, &dec_len
);
401 else if (g_ascii_strcasecmp(encoding
, "B") == 0)
402 decoded
= purple_base64_decode(encoded_text
, &dec_len
);
407 char *converted
= g_convert((const gchar
*)decoded
, dec_len
, "utf-8", charset
, NULL
, &len
, NULL
);
410 g_string_append_len(new, converted
, len
);
417 g_free(encoded_text
);
418 state
= state_equal2
; /* Restart the FSM */
419 } else { /* This should never happen */
420 g_string_append_len(new, mark
, cur
- mark
+ 1);
427 state
= state_equal1
;
429 /* Some unencoded text. */
430 g_string_append_c(new, *cur
);
436 if (state
!= state_start
)
437 g_string_append_len(new, mark
, cur
- mark
+ 1);
439 return g_string_free(new, FALSE
);;
443 /**************************************************************************
444 * Date/Time Functions
445 **************************************************************************/
447 const char *purple_get_tzoff_str(const struct tm
*tm
, gboolean iso
)
453 struct tm new_tm
= *tm
;
457 if (new_tm
.tm_isdst
< 0)
458 g_return_val_if_reached("");
461 if ((off
= wpurple_get_tz_offset()) == -1)
464 # ifdef HAVE_TM_GMTOFF
465 off
= new_tm
.tm_gmtoff
;
467 # ifdef HAVE_TIMEZONE
470 # endif /* HAVE_TIMEZONE */
471 # endif /* !HAVE_TM_GMTOFF */
474 min
= (off
/ 60) % 60;
475 hrs
= ((off
/ 60) - min
) / 60;
481 /* please leave the colons...they're optional for iso, but jabber
483 if(g_snprintf(buf
, sizeof(buf
), "%+03d:%02d", hrs
, ABS(min
)) > 6)
484 g_return_val_if_reached("");
487 if (g_snprintf(buf
, sizeof(buf
), "%+03d%02d", hrs
, ABS(min
)) > 5)
488 g_return_val_if_reached("");
494 /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */
495 #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32)
496 static size_t purple_internal_strftime(char *s
, size_t max
, const char *format
, const struct tm
*tm
)
502 /* Yes, this is checked in purple_utf8_strftime(),
503 * but better safe than sorry. -- rlaager */
504 g_return_val_if_fail(format
!= NULL
, 0);
506 /* This is fairly efficient, and it only gets
507 * executed on Windows or if the underlying
508 * system doesn't support the %z format string,
509 * for strftime() so I think it's good enough.
511 for (c
= start
= format
; *c
; c
++)
518 #ifndef HAVE_STRFTIME_Z_FORMAT
521 char *tmp
= g_strdup_printf("%s%.*s%s",
525 purple_get_tzoff_str(tm
, FALSE
));
534 char *tmp
= g_strdup_printf("%s%.*s%s",
538 wpurple_get_timezone_abbreviation(tm
));
552 char *tmp
= g_strconcat(fmt
, start
, NULL
);
557 ret
= strftime(s
, max
, fmt
, tm
);
563 return strftime(s
, max
, format
, tm
);
565 #else /* HAVE_STRFTIME_Z_FORMAT && !_WIN32 */
566 #define purple_internal_strftime strftime
570 purple_utf8_strftime(const char *format
, const struct tm
*tm
)
572 static char buf
[128];
578 g_return_val_if_fail(format
!= NULL
, NULL
);
582 time_t now
= time(NULL
);
583 tm
= localtime(&now
);
586 locale
= g_locale_from_utf8(format
, -1, NULL
, NULL
, &err
);
589 purple_debug_error("util", "Format conversion failed in purple_utf8_strftime(): %s\n", err
->message
);
592 locale
= g_strdup(format
);
595 /* A return value of 0 is either an error (in
596 * which case, the contents of the buffer are
597 * undefined) or the empty string (in which
598 * case, no harm is done here). */
599 if ((len
= purple_internal_strftime(buf
, sizeof(buf
), locale
, tm
)) == 0)
607 utf8
= g_locale_to_utf8(buf
, len
, NULL
, NULL
, &err
);
610 purple_debug_error("util", "Result conversion failed in purple_utf8_strftime(): %s\n", err
->message
);
615 purple_strlcpy(buf
, utf8
);
623 purple_date_format_short(const struct tm
*tm
)
625 return purple_utf8_strftime("%x", tm
);
629 purple_date_format_long(const struct tm
*tm
)
632 * This string determines how some dates are displayed. The default
633 * string "%x %X" shows the date then the time. Translators can
634 * change this to "%X %x" if they want the time to be shown first,
635 * followed by the date.
637 return purple_utf8_strftime(_("%x %X"), tm
);
641 purple_date_format_full(const struct tm
*tm
)
643 return purple_utf8_strftime("%c", tm
);
647 purple_time_format(const struct tm
*tm
)
649 return purple_utf8_strftime("%X", tm
);
653 purple_time_build(int year
, int month
, int day
, int hour
, int min
, int sec
)
657 tm
.tm_year
= year
- 1900;
658 tm
.tm_mon
= month
- 1;
662 tm
.tm_sec
= sec
>= 0 ? sec
: time(NULL
) % 60;
667 /* originally taken from GLib trunk 1-6-11 */
668 /* originally licensed as LGPL 2+ */
670 mktime_utc(struct tm
*tm
)
675 static const gint days_before
[] =
677 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
682 if (tm
->tm_mon
< 0 || tm
->tm_mon
> 11)
685 retval
= (tm
->tm_year
- 70) * 365;
686 retval
+= (tm
->tm_year
- 68) / 4;
687 retval
+= days_before
[tm
->tm_mon
] + tm
->tm_mday
- 1;
689 if (tm
->tm_year
% 4 == 0 && tm
->tm_mon
< 2)
692 retval
= ((((retval
* 24) + tm
->tm_hour
) * 60) + tm
->tm_min
) * 60 + tm
->tm_sec
;
694 retval
= timegm (tm
);
695 #endif /* !HAVE_TIMEGM */
701 purple_str_to_time(const char *timestamp
, gboolean utc
,
702 struct tm
*tm
, long *tz_off
, const char **rest
)
707 long tzoff
= PURPLE_NO_TZ_OFF
;
709 gboolean mktime_with_utc
= TRUE
;
714 g_return_val_if_fail(timestamp
!= NULL
, 0);
716 memset(&t
, 0, sizeof(struct tm
));
720 /* Strip leading whitespace */
721 while (g_ascii_isspace(*str
))
725 if (rest
!= NULL
&& *str
!= '\0')
731 if (!g_ascii_isdigit(*str
) && *str
!= '-' && *str
!= '+') {
732 if (rest
!= NULL
&& *str
!= '\0')
739 if (sscanf(str
, "%04d", &year
) && year
>= 1900) {
742 if (*str
== '-' || *str
== '/')
745 t
.tm_year
= year
- 1900;
749 if (!sscanf(str
, "%02d", &t
.tm_mon
)) {
750 if (rest
!= NULL
&& *str
!= '\0')
759 if (*str
== '-' || *str
== '/')
763 if (!sscanf(str
, "%02d", &t
.tm_mday
)) {
764 if (rest
!= NULL
&& *str
!= '\0')
772 /* Grab the year off the end if there's still stuff */
773 if (*str
== '/' || *str
== '-') {
774 /* But make sure we don't read the year twice */
776 if (rest
!= NULL
&& *str
!= '\0')
784 if (!sscanf(str
, "%04d", &t
.tm_year
)) {
785 if (rest
!= NULL
&& *str
!= '\0')
792 } else if (*str
== 'T' || *str
== '.') {
795 /* Continue grabbing the hours/minutes/seconds */
796 if ((sscanf(str
, "%02d:%02d:%02d", &t
.tm_hour
, &t
.tm_min
, &t
.tm_sec
) == 3 &&
798 (sscanf(str
, "%02d%02d%02d", &t
.tm_hour
, &t
.tm_min
, &t
.tm_sec
) == 3 &&
801 gint sign
, tzhrs
, tzmins
;
804 /* Cut off those pesky micro-seconds */
807 } while (*str
>= '0' && *str
<= '9');
810 sign
= (*str
== '+') ? -1 : 1;
812 /* Process the timezone */
813 if (*str
== '+' || *str
== '-') {
816 if (((sscanf(str
, "%02d:%02d", &tzhrs
, &tzmins
) == 2 && (str
+= 5)) ||
817 (sscanf(str
, "%02d%02d", &tzhrs
, &tzmins
) == 2 && (str
+= 4))))
819 tzoff
= tzhrs
* 60 * 60 + tzmins
* 60;
822 if (rest
!= NULL
&& *str
!= '\0')
827 } else if (*str
== 'Z') {
828 /* 'Z' = Zulu = UTC */
834 mktime_with_utc
= FALSE
;
842 if (rest
!= NULL
&& *str
!= '\0') {
843 /* Strip trailing whitespace */
844 while (g_ascii_isspace(*str
))
852 retval
= mktime_utc(&t
);
859 if (tzoff
!= PURPLE_NO_TZ_OFF
)
868 /**************************************************************************
870 **************************************************************************/
873 * This function is stolen from glib's gmarkup.c and modified to not
874 * replace ' with '
876 static void append_escaped_text(GString
*str
,
877 const gchar
*text
, gssize length
)
889 next
= g_utf8_next_char (p
);
894 g_string_append (str
, "&");
898 g_string_append (str
, "<");
902 g_string_append (str
, ">");
906 g_string_append (str
, """);
910 c
= g_utf8_get_char (p
);
911 if ((0x1 <= c
&& c
<= 0x8) ||
912 (0xb <= c
&& c
<= 0xc) ||
913 (0xe <= c
&& c
<= 0x1f) ||
914 (0x7f <= c
&& c
<= 0x84) ||
915 (0x86 <= c
&& c
<= 0x9f))
916 g_string_append_printf (str
, "&#x%x;", c
);
918 g_string_append_len (str
, p
, next
- p
);
926 /* This function is stolen from glib's gmarkup.c */
927 gchar
*purple_markup_escape_text(const gchar
*text
, gssize length
)
931 g_return_val_if_fail(text
!= NULL
, NULL
);
934 length
= strlen(text
);
936 /* prealloc at least as long as original text */
937 str
= g_string_sized_new(length
);
938 append_escaped_text(str
, text
, length
);
940 return g_string_free(str
, FALSE
);
944 purple_markup_unescape_entity(const char *text
, int *length
)
950 if (!text
|| *text
!= '&')
953 #define IS_ENTITY(s) (!g_ascii_strncasecmp(text, s, (len = sizeof(s) - 1)))
955 if(IS_ENTITY("&"))
957 else if(IS_ENTITY("<"))
959 else if(IS_ENTITY(">"))
961 else if(IS_ENTITY(" "))
963 else if(IS_ENTITY("©"))
964 pln
= "\302\251"; /* or use g_unichar_to_utf8(0xa9); */
965 else if(IS_ENTITY("""))
967 else if(IS_ENTITY("®"))
968 pln
= "\302\256"; /* or use g_unichar_to_utf8(0xae); */
969 else if(IS_ENTITY("'"))
971 else if(*(text
+1) == '#' &&
972 (sscanf(text
, "&#%u%1[;]", £
, temp
) == 2 ||
973 sscanf(text
, "&#x%x%1[;]", £
, temp
) == 2) &&
976 int buflen
= g_unichar_to_utf8((gunichar
)pound
, buf
);
980 len
= (*(text
+2) == 'x' ? 3 : 2);
981 while(isxdigit((gint
) text
[len
])) len
++;
982 if(text
[len
] == ';') len
++;
993 purple_markup_get_css_property(const gchar
*style
,
996 const gchar
*css_str
= style
;
997 const gchar
*css_value_start
;
998 const gchar
*css_value_end
;
1002 g_return_val_if_fail(opt
!= NULL
, NULL
);
1007 /* find the CSS property */
1010 /* skip whitespace characters */
1011 while (*css_str
&& g_ascii_isspace(*css_str
))
1013 if (!g_ascii_isalpha(*css_str
))
1015 if (g_ascii_strncasecmp(css_str
, opt
, strlen(opt
)))
1017 /* go to next css property positioned after the next ';' */
1018 while (*css_str
&& *css_str
!= '"' && *css_str
!= ';')
1028 /* find the CSS value position in the string */
1029 css_str
+= strlen(opt
);
1030 while (*css_str
&& g_ascii_isspace(*css_str
))
1032 if (*css_str
!= ':')
1035 while (*css_str
&& g_ascii_isspace(*css_str
))
1037 if (*css_str
== '\0' || *css_str
== '"' || *css_str
== ';')
1040 /* mark the CSS value */
1041 css_value_start
= css_str
;
1042 while (*css_str
&& *css_str
!= '"' && *css_str
!= ';')
1044 css_value_end
= css_str
- 1;
1046 /* Removes trailing whitespace */
1047 while (css_value_end
> css_value_start
&& g_ascii_isspace(*css_value_end
))
1050 tmp
= g_strndup(css_value_start
, css_value_end
- css_value_start
+ 1);
1051 ret
= purple_unescape_html(tmp
);
1057 gboolean
purple_markup_is_rtl(const char *html
)
1060 const gchar
*start
, *end
;
1061 gboolean res
= FALSE
;
1063 if (purple_markup_find_tag("span", html
, &start
, &end
, &attributes
))
1065 /* tmp is a member of attributes and is free with g_datalist_clear call */
1066 const char *tmp
= g_datalist_get_data(&attributes
, "dir");
1067 if (tmp
&& !g_ascii_strcasecmp(tmp
, "RTL"))
1071 tmp
= g_datalist_get_data(&attributes
, "style");
1074 char *tmp2
= purple_markup_get_css_property(tmp
, "direction");
1075 if (tmp2
&& !g_ascii_strcasecmp(tmp2
, "RTL"))
1081 g_datalist_clear(&attributes
);
1087 purple_markup_find_tag(const char *needle
, const char *haystack
,
1088 const char **start
, const char **end
, GData
**attributes
)
1091 const char *cur
= haystack
;
1093 gboolean found
= FALSE
;
1094 gboolean in_tag
= FALSE
;
1095 gboolean in_attr
= FALSE
;
1096 const char *in_quotes
= NULL
;
1099 g_return_val_if_fail( needle
!= NULL
, FALSE
);
1100 g_return_val_if_fail( *needle
!= '\0', FALSE
);
1101 g_return_val_if_fail( haystack
!= NULL
, FALSE
);
1102 g_return_val_if_fail( start
!= NULL
, FALSE
);
1103 g_return_val_if_fail( end
!= NULL
, FALSE
);
1104 g_return_val_if_fail(attributes
!= NULL
, FALSE
);
1106 needlelen
= strlen(needle
);
1107 g_datalist_init(&attribs
);
1109 while (*cur
&& !found
) {
1112 const char *close
= cur
;
1114 while (*close
&& *close
!= *in_quotes
)
1117 /* if we got the close quote, store the value and carry on from *
1118 * after it. if we ran to the end of the string, point to the NULL *
1119 * and we're outta here */
1121 /* only store a value if we have an attribute name */
1123 size_t len
= close
- cur
;
1124 char *val
= g_strndup(cur
, len
);
1126 g_datalist_set_data_full(&attribs
, name
, val
, g_free
);
1136 } else if (in_attr
) {
1137 const char *close
= cur
;
1139 while (*close
&& *close
!= '>' && *close
!= '"' &&
1140 *close
!= '\'' && *close
!= ' ' && *close
!= '=')
1143 /* if we got the equals, store the name of the attribute. if we got
1144 * the quote, save the attribute and go straight to quote mode.
1145 * otherwise the tag closed or we reached the end of the string,
1146 * so we can get outta here */
1153 size_t len
= close
- cur
;
1155 /* don't store a blank attribute name */
1158 name
= g_ascii_strdown(cur
, len
);
1175 /* swallow extra spaces inside tag */
1176 while (*cur
&& *cur
== ' ') cur
++;
1192 /* if we hit a < followed by the name of our tag... */
1193 if (*cur
== '<' && !g_ascii_strncasecmp(cur
+ 1, needle
, needlelen
)) {
1195 cur
= cur
+ needlelen
+ 1;
1197 /* if we're pointing at a space or a >, we found the right tag. if *
1198 * we're not, we've found a longer tag, so we need to skip to the *
1199 * >, but not being distracted by >s inside quotes. */
1200 if (*cur
== ' ' || *cur
== '>') {
1203 while (*cur
&& *cur
!= '"' && *cur
!= '\'' && *cur
!= '>') {
1206 while (*cur
&& *cur
!= '"')
1208 } else if (*cur
== '\'') {
1210 while (*cur
&& *cur
!= '\'')
1223 /* clean up any attribute name from a premature termination */
1227 *attributes
= attribs
;
1238 purple_markup_extract_info_field(const char *str
, int len
, PurpleNotifyUserInfo
*user_info
,
1239 const char *start_token
, int skip
,
1240 const char *end_token
, char check_value
,
1241 const char *no_value_token
,
1242 const char *display_name
, gboolean is_link
,
1243 const char *link_prefix
,
1244 PurpleInfoFieldFormatCallback format_cb
)
1248 g_return_val_if_fail(str
!= NULL
, FALSE
);
1249 g_return_val_if_fail(user_info
!= NULL
, FALSE
);
1250 g_return_val_if_fail(start_token
!= NULL
, FALSE
);
1251 g_return_val_if_fail(end_token
!= NULL
, FALSE
);
1252 g_return_val_if_fail(display_name
!= NULL
, FALSE
);
1254 p
= strstr(str
, start_token
);
1259 p
+= strlen(start_token
) + skip
;
1264 if (check_value
!= '\0' && *p
== check_value
)
1267 q
= strstr(p
, end_token
);
1269 /* Trim leading blanks */
1270 while (*p
!= '\n' && g_ascii_isspace(*p
)) {
1274 /* Trim trailing blanks */
1275 while (q
> p
&& g_ascii_isspace(*(q
- 1))) {
1279 /* Don't bother with null strings */
1283 if (q
!= NULL
&& (!no_value_token
||
1284 (no_value_token
&& strncmp(p
, no_value_token
,
1285 strlen(no_value_token
)))))
1287 GString
*dest
= g_string_new("");
1291 g_string_append(dest
, "<a href=\"");
1294 g_string_append(dest
, link_prefix
);
1296 if (format_cb
!= NULL
)
1298 char *reformatted
= format_cb(p
, q
- p
);
1299 g_string_append(dest
, reformatted
);
1300 g_free(reformatted
);
1303 g_string_append_len(dest
, p
, q
- p
);
1304 g_string_append(dest
, "\">");
1307 g_string_append(dest
, link_prefix
);
1309 g_string_append_len(dest
, p
, q
- p
);
1310 g_string_append(dest
, "</a>");
1314 if (format_cb
!= NULL
)
1316 char *reformatted
= format_cb(p
, q
- p
);
1317 g_string_append(dest
, reformatted
);
1318 g_free(reformatted
);
1321 g_string_append_len(dest
, p
, q
- p
);
1324 purple_notify_user_info_add_pair(user_info
, display_name
, dest
->str
);
1325 g_string_free(dest
, TRUE
);
1333 struct purple_parse_tag
{
1339 /* NOTE: Do not put `do {} while(0)` around this macro (as this is the method
1340 recommended in the GCC docs). It contains 'continue's that should
1341 affect the while-loop in purple_markup_html_to_xhtml and doing the
1342 above would break that.
1343 Also, remember to put braces in constructs that require them for
1344 multiple statements when using this macro. */
1345 #define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \
1346 const char *o = c + strlen("<" x); \
1347 const char *p = NULL, *q = NULL, *r = NULL; \
1348 /* o = iterating over full tag \
1349 * p = > (end of tag) \
1350 * q = start of quoted bit \
1351 * r = < inside tag \
1353 GString *innards = g_string_new(""); \
1355 if(!q && (*o == '\"' || *o == '\'') ) { \
1358 if(*o == *q) { /* end of quoted bit */ \
1359 char *unescaped = g_strndup(q+1, o-q-1); \
1360 char *escaped = g_markup_escape_text(unescaped, -1); \
1361 g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \
1362 g_free(unescaped); \
1365 } else if(*c == '\\') { \
1368 } else if(*o == '<') { \
1370 } else if(*o == '>') { \
1374 innards = g_string_append_c(innards, *o); \
1378 if(p && !r) { /* got an end of tag and no other < earlier */\
1379 if(*(p-1) != '/') { \
1380 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1383 tags = g_list_prepend(tags, pt); \
1386 xhtml = g_string_append(xhtml, "<" y); \
1387 xhtml = g_string_append(xhtml, innards->str); \
1388 xhtml = g_string_append_c(xhtml, '>'); \
1391 } else { /* got end of tag with earlier < *or* didn't get anything */ \
1393 xhtml = g_string_append(xhtml, "<"); \
1395 plain = g_string_append_c(plain, '<'); \
1398 g_string_free(innards, TRUE); \
1401 if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \
1402 (*(c+strlen("<" x)) == '>' || \
1403 !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \
1405 xhtml = g_string_append(xhtml, "<" y); \
1406 c += strlen("<" x); \
1408 struct purple_parse_tag *pt = g_new0(struct purple_parse_tag, 1); \
1411 tags = g_list_prepend(tags, pt); \
1413 xhtml = g_string_append_c(xhtml, '>'); \
1416 xhtml = g_string_append(xhtml, "/>");\
1418 c = strchr(c, '>') + 1; \
1421 /* Don't forget to check the note above for ALLOW_TAG_ALT. */
1422 #define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x)
1424 purple_markup_html_to_xhtml(const char *html
, char **xhtml_out
,
1427 GString
*xhtml
= NULL
;
1428 GString
*plain
= NULL
;
1429 GString
*url
= NULL
;
1430 GString
*cdata
= NULL
;
1431 GList
*tags
= NULL
, *tag
;
1432 const char *c
= html
;
1435 #define CHECK_QUOTE(ptr) if (*(ptr) == '\'' || *(ptr) == '\"') \
1440 #define VALID_CHAR(ptr) (*(ptr) && *(ptr) != quote && (quote || (*(ptr) != ' ' && *(ptr) != '>')))
1442 g_return_if_fail(xhtml_out
!= NULL
|| plain_out
!= NULL
);
1445 xhtml
= g_string_new("");
1447 plain
= g_string_new("");
1451 if(*(c
+1) == '/') { /* closing tag */
1454 struct purple_parse_tag
*pt
= tag
->data
;
1455 if(!g_ascii_strncasecmp((c
+2), pt
->src_tag
, strlen(pt
->src_tag
)) && *(c
+strlen(pt
->src_tag
)+2) == '>') {
1456 c
+= strlen(pt
->src_tag
) + 3;
1463 struct purple_parse_tag
*pt
= tags
->data
;
1464 if(xhtml
&& !pt
->ignore
)
1465 g_string_append_printf(xhtml
, "</%s>", pt
->dest_tag
);
1466 if(plain
&& purple_strequal(pt
->src_tag
, "a")) {
1467 /* if this is a link, we have to add the url to the plaintext, too */
1469 (!g_string_equal(cdata
, url
) && (g_ascii_strncasecmp(url
->str
, "mailto:", 7) != 0 ||
1470 g_utf8_collate(url
->str
+ 7, cdata
->str
) != 0)))
1471 g_string_append_printf(plain
, " <%s>", g_strstrip(url
->str
));
1473 g_string_free(cdata
, TRUE
);
1480 tags
= g_list_remove(tags
, pt
);
1484 tags
= g_list_remove(tags
, tag
->data
);
1486 /* a closing tag we weren't expecting...
1487 * we'll let it slide, if it's really a tag...if it's
1488 * just a </ we'll escape it properly */
1489 const char *end
= c
+2;
1490 while(*end
&& g_ascii_isalpha(*end
))
1496 xhtml
= g_string_append(xhtml
, "<");
1498 plain
= g_string_append_c(plain
, '<');
1502 } else { /* opening tag */
1503 ALLOW_TAG("blockquote");
1513 /* we only allow html to start the message */
1517 ALLOW_TAG_ALT("i", "em");
1518 ALLOW_TAG_ALT("italic", "em");
1528 /* we skip <HR> because it's not legal in XHTML-IM. However,
1529 * we still want to send something sensible, so we put a
1530 * linebreak in its place. <BR> also needs special handling
1531 * because putting a </BR> to close it would just be dumb. */
1532 if((!g_ascii_strncasecmp(c
, "<br", 3)
1533 || !g_ascii_strncasecmp(c
, "<hr", 3))
1534 && (*(c
+3) == '>' ||
1535 !g_ascii_strncasecmp(c
+3, "/>", 2) ||
1536 !g_ascii_strncasecmp(c
+3, " />", 3))) {
1537 c
= strchr(c
, '>') + 1;
1539 xhtml
= g_string_append(xhtml
, "<br/>");
1540 if(plain
&& *c
!= '\n')
1541 plain
= g_string_append_c(plain
, '\n');
1544 if(!g_ascii_strncasecmp(c
, "<b>", 3) || !g_ascii_strncasecmp(c
, "<bold>", strlen("<bold>")) || !g_ascii_strncasecmp(c
, "<strong>", strlen("<strong>"))) {
1545 struct purple_parse_tag
*pt
= g_new0(struct purple_parse_tag
, 1);
1548 else if (*(c
+2) == 'o')
1549 pt
->src_tag
= "bold";
1551 pt
->src_tag
= "strong";
1552 pt
->dest_tag
= "span";
1553 tags
= g_list_prepend(tags
, pt
);
1554 c
= strchr(c
, '>') + 1;
1556 xhtml
= g_string_append(xhtml
, "<span style='font-weight: bold;'>");
1559 if(!g_ascii_strncasecmp(c
, "<u>", 3) || !g_ascii_strncasecmp(c
, "<underline>", strlen("<underline>"))) {
1560 struct purple_parse_tag
*pt
= g_new0(struct purple_parse_tag
, 1);
1561 pt
->src_tag
= *(c
+2) == '>' ? "u" : "underline";
1562 pt
->dest_tag
= "span";
1563 tags
= g_list_prepend(tags
, pt
);
1564 c
= strchr(c
, '>') + 1;
1566 xhtml
= g_string_append(xhtml
, "<span style='text-decoration: underline;'>");
1569 if(!g_ascii_strncasecmp(c
, "<s>", 3) || !g_ascii_strncasecmp(c
, "<strike>", strlen("<strike>"))) {
1570 struct purple_parse_tag
*pt
= g_new0(struct purple_parse_tag
, 1);
1571 pt
->src_tag
= *(c
+2) == '>' ? "s" : "strike";
1572 pt
->dest_tag
= "span";
1573 tags
= g_list_prepend(tags
, pt
);
1574 c
= strchr(c
, '>') + 1;
1576 xhtml
= g_string_append(xhtml
, "<span style='text-decoration: line-through;'>");
1579 if(!g_ascii_strncasecmp(c
, "<sub>", 5)) {
1580 struct purple_parse_tag
*pt
= g_new0(struct purple_parse_tag
, 1);
1581 pt
->src_tag
= "sub";
1582 pt
->dest_tag
= "span";
1583 tags
= g_list_prepend(tags
, pt
);
1584 c
= strchr(c
, '>') + 1;
1586 xhtml
= g_string_append(xhtml
, "<span style='vertical-align:sub;'>");
1589 if(!g_ascii_strncasecmp(c
, "<sup>", 5)) {
1590 struct purple_parse_tag
*pt
= g_new0(struct purple_parse_tag
, 1);
1591 pt
->src_tag
= "sup";
1592 pt
->dest_tag
= "span";
1593 tags
= g_list_prepend(tags
, pt
);
1594 c
= strchr(c
, '>') + 1;
1596 xhtml
= g_string_append(xhtml
, "<span style='vertical-align:super;'>");
1599 if (!g_ascii_strncasecmp(c
, "<img", 4) && (*(c
+4) == '>' || *(c
+4) == ' ')) {
1600 const char *p
= c
+ 4;
1601 GString
*src
= NULL
, *alt
= NULL
;
1602 while (*p
&& *p
!= '>') {
1603 if (!g_ascii_strncasecmp(p
, "src=", 4)) {
1604 const char *q
= p
+ 4;
1606 g_string_free(src
, TRUE
);
1607 src
= g_string_new("");
1609 while (VALID_CHAR(q
)) {
1610 src
= g_string_append_c(src
, *q
);
1614 } else if (!g_ascii_strncasecmp(p
, "alt=", 4)) {
1615 const char *q
= p
+ 4;
1617 g_string_free(alt
, TRUE
);
1618 alt
= g_string_new("");
1620 while (VALID_CHAR(q
)) {
1621 alt
= g_string_append_c(alt
, *q
);
1629 if ((c
= strchr(p
, '>')) != NULL
)
1633 /* src and alt are required! */
1635 g_string_append_printf(xhtml
, "<img src='%s' alt='%s' />", g_strstrip(src
->str
), alt
? alt
->str
: "");
1638 plain
= g_string_append(plain
, alt
->str
);
1640 xhtml
= g_string_append(xhtml
, alt
->str
);
1641 g_string_free(alt
, TRUE
);
1643 g_string_free(src
, TRUE
);
1646 if (!g_ascii_strncasecmp(c
, "<a", 2) && (*(c
+2) == '>' || *(c
+2) == ' ')) {
1647 const char *p
= c
+ 2;
1648 struct purple_parse_tag
*pt
;
1649 while (*p
&& *p
!= '>') {
1650 if (!g_ascii_strncasecmp(p
, "href=", 5)) {
1651 const char *q
= p
+ 5;
1653 g_string_free(url
, TRUE
);
1654 url
= g_string_new("");
1656 g_string_free(cdata
, TRUE
);
1657 cdata
= g_string_new("");
1659 while (VALID_CHAR(q
)) {
1661 if ((*q
== '&') && (purple_markup_unescape_entity(q
, &len
) == NULL
))
1662 url
= g_string_append(url
, "&");
1664 url
= g_string_append_c(url
, *q
);
1672 if ((c
= strchr(p
, '>')) != NULL
)
1676 pt
= g_new0(struct purple_parse_tag
, 1);
1679 tags
= g_list_prepend(tags
, pt
);
1681 g_string_append_printf(xhtml
, "<a href=\"%s\">", url
? g_strstrip(url
->str
) : "");
1684 if(!g_ascii_strncasecmp(c
, "<font", 5) && (*(c
+5) == '>' || *(c
+5) == ' ')) {
1685 const char *p
= c
+ 5;
1686 GString
*style
= g_string_new("");
1687 struct purple_parse_tag
*pt
;
1688 while (*p
&& *p
!= '>') {
1689 if (!g_ascii_strncasecmp(p
, "back=", 5)) {
1690 const char *q
= p
+ 5;
1691 GString
*color
= g_string_new("");
1693 while (VALID_CHAR(q
)) {
1694 color
= g_string_append_c(color
, *q
);
1697 g_string_append_printf(style
, "background: %s; ", color
->str
);
1698 g_string_free(color
, TRUE
);
1700 } else if (!g_ascii_strncasecmp(p
, "color=", 6)) {
1701 const char *q
= p
+ 6;
1702 GString
*color
= g_string_new("");
1704 while (VALID_CHAR(q
)) {
1705 color
= g_string_append_c(color
, *q
);
1708 g_string_append_printf(style
, "color: %s; ", color
->str
);
1709 g_string_free(color
, TRUE
);
1711 } else if (!g_ascii_strncasecmp(p
, "face=", 5)) {
1712 const char *q
= p
+ 5;
1713 GString
*face
= g_string_new("");
1715 while (VALID_CHAR(q
)) {
1716 face
= g_string_append_c(face
, *q
);
1719 g_string_append_printf(style
, "font-family: %s; ", g_strstrip(face
->str
));
1720 g_string_free(face
, TRUE
);
1722 } else if (!g_ascii_strncasecmp(p
, "size=", 5)) {
1723 const char *q
= p
+ 5;
1725 const char *size
= "medium";
1752 g_string_append_printf(style
, "font-size: %s; ", size
);
1758 if ((c
= strchr(p
, '>')) != NULL
)
1762 pt
= g_new0(struct purple_parse_tag
, 1);
1763 pt
->src_tag
= "font";
1764 pt
->dest_tag
= "span";
1765 tags
= g_list_prepend(tags
, pt
);
1766 if(style
->len
&& xhtml
)
1767 g_string_append_printf(xhtml
, "<span style='%s'>", g_strstrip(style
->str
));
1770 g_string_free(style
, TRUE
);
1773 if (!g_ascii_strncasecmp(c
, "<body ", 6)) {
1774 const char *p
= c
+ 6;
1775 gboolean did_something
= FALSE
;
1776 while (*p
&& *p
!= '>') {
1777 if (!g_ascii_strncasecmp(p
, "bgcolor=", 8)) {
1778 const char *q
= p
+ 8;
1779 struct purple_parse_tag
*pt
= g_new0(struct purple_parse_tag
, 1);
1780 GString
*color
= g_string_new("");
1782 while (VALID_CHAR(q
)) {
1783 color
= g_string_append_c(color
, *q
);
1787 g_string_append_printf(xhtml
, "<span style='background: %s;'>", g_strstrip(color
->str
));
1788 g_string_free(color
, TRUE
);
1789 if ((c
= strchr(p
, '>')) != NULL
)
1793 pt
->src_tag
= "body";
1794 pt
->dest_tag
= "span";
1795 tags
= g_list_prepend(tags
, pt
);
1796 did_something
= TRUE
;
1801 if (did_something
) continue;
1803 /* this has to come after the special case for bgcolor */
1805 if(!g_ascii_strncasecmp(c
, "<!--", strlen("<!--"))) {
1806 char *p
= strstr(c
+ strlen("<!--"), "-->");
1809 xhtml
= g_string_append(xhtml
, "<!--");
1810 c
+= strlen("<!--");
1816 xhtml
= g_string_append(xhtml
, "<");
1818 plain
= g_string_append_c(plain
, '<');
1821 } else if(*c
== '&') {
1826 if ((pln
= purple_markup_unescape_entity(c
, &len
)) == NULL
) {
1828 g_snprintf(buf
, sizeof(buf
), "%c", *c
);
1832 xhtml
= g_string_append_len(xhtml
, c
, len
);
1834 plain
= g_string_append(plain
, pln
);
1836 cdata
= g_string_append_len(cdata
, c
, len
);
1840 xhtml
= g_string_append_c(xhtml
, *c
);
1842 plain
= g_string_append_c(plain
, *c
);
1844 cdata
= g_string_append_c(cdata
, *c
);
1849 for (tag
= tags
; tag
; tag
= tag
->next
) {
1850 struct purple_parse_tag
*pt
= tag
->data
;
1852 g_string_append_printf(xhtml
, "</%s>", pt
->dest_tag
);
1857 *xhtml_out
= g_string_free(xhtml
, FALSE
);
1859 *plain_out
= g_string_free(plain
, FALSE
);
1861 g_string_free(url
, TRUE
);
1863 g_string_free(cdata
, TRUE
);
1868 /* The following are probably reasonable changes:
1869 * - \n should be converted to a normal space
1870 * - in addition to <br>, <p> and <div> etc. should also be converted into \n
1871 * - We want to turn </td>#whitespace<td> sequences into a single tab
1872 * - We want to turn <td> into a single tab (for msn profile "parsing")
1873 * - We want to turn </tr>#whitespace<tr> sequences into a single \n
1874 * - <script>...</script> and <style>...</style> should be completely removed
1878 purple_markup_strip_html(const char *str
)
1880 int i
, j
, k
, entlen
;
1881 gboolean visible
= TRUE
;
1882 gboolean closing_td_p
= FALSE
;
1884 const gchar
*cdata_close_tag
= NULL
, *ent
;
1891 str2
= g_strdup(str
);
1893 for (i
= 0, j
= 0; str2
[i
]; i
++)
1897 if (cdata_close_tag
)
1899 /* Note: Don't even assume any other tag is a tag in CDATA */
1900 if (g_ascii_strncasecmp(str2
+ i
, cdata_close_tag
,
1901 strlen(cdata_close_tag
)) == 0)
1903 i
+= strlen(cdata_close_tag
) - 1;
1904 cdata_close_tag
= NULL
;
1908 else if (g_ascii_strncasecmp(str2
+ i
, "<td", 3) == 0 && closing_td_p
)
1913 else if (g_ascii_strncasecmp(str2
+ i
, "</td>", 5) == 0)
1915 closing_td_p
= TRUE
;
1920 closing_td_p
= FALSE
;
1926 if(g_ascii_isspace(str2
[k
]))
1930 /* Scan until we end the tag either implicitly (closed start
1931 * tag) or explicitly, using a sloppy method (i.e., < or >
1932 * inside quoted attributes will screw us up)
1934 while (str2
[k
] && str2
[k
] != '<' && str2
[k
] != '>')
1939 /* If we've got an <a> tag with an href, save the address
1940 * to print later. */
1941 if (g_ascii_strncasecmp(str2
+ i
, "<a", 2) == 0 &&
1942 g_ascii_isspace(str2
[i
+2]))
1944 int st
; /* start of href, inclusive [ */
1945 int end
; /* end of href, exclusive ) */
1947 /* Find start of href */
1948 for (st
= i
+ 3; st
< k
; st
++)
1950 if (g_ascii_strncasecmp(str2
+st
, "href=", 5) == 0)
1953 if (str2
[st
] == '"' || str2
[st
] == '\'')
1961 /* find end of address */
1962 for (end
= st
; end
< k
&& str2
[end
] != delim
; end
++)
1964 /* All the work is done in the loop construct above. */
1967 /* If there's an address, save it. If there was
1968 * already one saved, kill it. */
1973 tmp
= g_strndup(str2
+ st
, end
- st
);
1974 href
= purple_unescape_html(tmp
);
1980 /* Replace </a> with an ascii representation of the
1981 * address the link was pointing to. */
1982 else if (href
!= NULL
&& g_ascii_strncasecmp(str2
+ i
, "</a>", 4) == 0)
1985 size_t hrlen
= strlen(href
);
1987 /* Only insert the href if it's different from the CDATA. */
1988 if ((hrlen
!= j
- href_st
||
1989 strncmp(str2
+ href_st
, href
, hrlen
)) &&
1990 (hrlen
!= j
- href_st
+ 7 || /* 7 == strlen("http://") */
1991 strncmp(str2
+ href_st
, href
+ 7, hrlen
- 7)))
1995 g_memmove(str2
+ j
, href
, hrlen
);
2003 /* Check for tags which should be mapped to newline (but ignore some of
2004 * the tags at the beginning of the text) */
2005 else if ((j
&& (g_ascii_strncasecmp(str2
+ i
, "<p>", 3) == 0
2006 || g_ascii_strncasecmp(str2
+ i
, "<tr", 3) == 0
2007 || g_ascii_strncasecmp(str2
+ i
, "<hr", 3) == 0
2008 || g_ascii_strncasecmp(str2
+ i
, "<li", 3) == 0
2009 || g_ascii_strncasecmp(str2
+ i
, "<div", 4) == 0))
2010 || g_ascii_strncasecmp(str2
+ i
, "<br", 3) == 0
2011 || g_ascii_strncasecmp(str2
+ i
, "</table>", 8) == 0)
2015 /* Check for tags which begin CDATA and need to be closed */
2016 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */
2017 else if (g_ascii_strncasecmp(str2
+ i
, "<option", 7) == 0)
2019 /* FIXME: We should not do this if the OPTION is SELECT'd */
2020 cdata_close_tag
= "</option>";
2023 else if (g_ascii_strncasecmp(str2
+ i
, "<script", 7) == 0)
2025 cdata_close_tag
= "</script>";
2027 else if (g_ascii_strncasecmp(str2
+ i
, "<style", 6) == 0)
2029 cdata_close_tag
= "</style>";
2031 /* Update the index and continue checking after the tag */
2032 i
= (str2
[k
] == '<' || str2
[k
] == '\0')? k
- 1: k
;
2036 else if (cdata_close_tag
)
2040 else if (!g_ascii_isspace(str2
[i
]))
2045 if (str2
[i
] == '&' && (ent
= purple_markup_unescape_entity(str2
+ i
, &entlen
)) != NULL
)
2054 str2
[j
++] = g_ascii_isspace(str2
[i
])? ' ': str2
[i
];
2083 badentity(const char *c
)
2085 if (!g_ascii_strncasecmp(c
, "<", 4) ||
2086 !g_ascii_strncasecmp(c
, ">", 4) ||
2087 !g_ascii_strncasecmp(c
, """, 6)) {
2094 process_link(GString
*ret
,
2095 const char *start
, const char *c
,
2097 const char *urlprefix
,
2100 char *url_buf
, *tmpurlbuf
;
2104 if (!badchar(*t
) && !badentity(t
))
2107 if (t
- c
== matchlen
)
2110 if (*t
== ',' && *(t
+ 1) != ' ') {
2114 if (t
> start
&& *(t
- 1) == '.')
2116 if (t
> start
&& *(t
- 1) == ')' && inside_paren
> 0)
2119 url_buf
= g_strndup(c
, t
- c
);
2120 tmpurlbuf
= purple_unescape_html(url_buf
);
2121 g_string_append_printf(ret
, "<A HREF=\"%s%s\">%s</A>",
2123 tmpurlbuf
, url_buf
);
2133 purple_markup_linkify(const char *text
)
2135 const char *c
, *t
, *q
= NULL
;
2136 char *tmpurlbuf
, *url_buf
;
2138 gboolean inside_html
= FALSE
;
2139 int inside_paren
= 0;
2145 ret
= g_string_new("");
2150 if(*c
== '(' && !inside_html
) {
2152 ret
= g_string_append_c(ret
, *c
);
2158 inside_html
= FALSE
;
2159 } else if(!q
&& (*c
== '\"' || *c
== '\'')) {
2165 } else if(*c
== '<') {
2167 if (!g_ascii_strncasecmp(c
, "<A", 2)) {
2169 if (!g_ascii_strncasecmp(c
, "/A>", 3)) {
2170 inside_html
= FALSE
;
2173 ret
= g_string_append_c(ret
, *c
);
2179 } else if (!g_ascii_strncasecmp(c
, "http://", 7)) {
2180 c
= process_link(ret
, text
, c
, 7, "", inside_paren
);
2181 } else if (!g_ascii_strncasecmp(c
, "https://", 8)) {
2182 c
= process_link(ret
, text
, c
, 8, "", inside_paren
);
2183 } else if (!g_ascii_strncasecmp(c
, "ftp://", 6)) {
2184 c
= process_link(ret
, text
, c
, 6, "", inside_paren
);
2185 } else if (!g_ascii_strncasecmp(c
, "sftp://", 7)) {
2186 c
= process_link(ret
, text
, c
, 7, "", inside_paren
);
2187 } else if (!g_ascii_strncasecmp(c
, "file://", 7)) {
2188 c
= process_link(ret
, text
, c
, 7, "", inside_paren
);
2189 } else if (!g_ascii_strncasecmp(c
, "www.", 4) && c
[4] != '.' && (c
== text
|| badchar(c
[-1]) || badentity(c
-1))) {
2190 c
= process_link(ret
, text
, c
, 4, "http://", inside_paren
);
2191 } else if (!g_ascii_strncasecmp(c
, "ftp.", 4) && c
[4] != '.' && (c
== text
|| badchar(c
[-1]) || badentity(c
-1))) {
2192 c
= process_link(ret
, text
, c
, 4, "ftp://", inside_paren
);
2193 } else if (!g_ascii_strncasecmp(c
, "xmpp:", 5) && (c
== text
|| badchar(c
[-1]) || badentity(c
-1))) {
2194 c
= process_link(ret
, text
, c
, 5, "", inside_paren
);
2195 } else if (!g_ascii_strncasecmp(c
, "mailto:", 7)) {
2198 if (badchar(*t
) || badentity(t
)) {
2203 if (t
> text
&& *(t
- 1) == '.')
2205 if ((d
= strstr(c
+ 7, "?")) != NULL
&& d
< t
)
2206 url_buf
= g_strndup(c
+ 7, d
- c
- 7);
2208 url_buf
= g_strndup(c
+ 7, t
- c
- 7);
2209 if (!purple_email_is_valid(url_buf
)) {
2214 url_buf
= g_strndup(c
, t
- c
);
2215 tmpurlbuf
= purple_unescape_html(url_buf
);
2216 g_string_append_printf(ret
, "<A HREF=\"%s\">%s</A>",
2217 tmpurlbuf
, url_buf
);
2225 } else if (c
!= text
&& (*c
== '@')) {
2227 GString
*gurl_buf
= NULL
;
2228 const char illegal_chars
[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0";
2230 if (strchr(illegal_chars
,*(c
- 1)) || strchr(illegal_chars
, *(c
+ 1)))
2234 gurl_buf
= g_string_new("");
2239 /* iterate backwards grabbing the local part of an email address */
2240 g
= g_utf8_get_char(t
);
2241 if (badchar(*t
) || (g
>= 127) || (*t
== '(') ||
2242 ((*t
== ';') && ((t
> (text
+2) && (!g_ascii_strncasecmp(t
- 3, "<", 4) ||
2243 !g_ascii_strncasecmp(t
- 3, ">", 4))) ||
2244 (t
> (text
+4) && (!g_ascii_strncasecmp(t
- 5, """, 6)))))) {
2245 /* local part will already be part of ret, strip it out */
2246 ret
= g_string_truncate(ret
, ret
->len
- (c
- t
));
2247 ret
= g_string_append_unichar(ret
, g
);
2250 g_string_prepend_unichar(gurl_buf
, g
);
2251 t
= g_utf8_find_prev_char(text
, t
);
2253 ret
= g_string_assign(ret
, "");
2259 t
= g_utf8_find_next_char(c
, NULL
);
2262 /* iterate forwards grabbing the domain part of an email address */
2263 g
= g_utf8_get_char(t
);
2264 if (badchar(*t
) || (g
>= 127) || (*t
== ')') || badentity(t
)) {
2267 url_buf
= g_string_free(gurl_buf
, FALSE
);
2269 /* strip off trailing periods */
2270 if (strlen(url_buf
) > 0) {
2271 for (d
= url_buf
+ strlen(url_buf
) - 1; *d
== '.'; d
--, t
--)
2275 tmpurlbuf
= purple_unescape_html(url_buf
);
2276 if (purple_email_is_valid(tmpurlbuf
)) {
2277 g_string_append_printf(ret
, "<A HREF=\"mailto:%s\">%s</A>",
2278 tmpurlbuf
, url_buf
);
2280 g_string_append(ret
, url_buf
);
2288 g_string_append_unichar(gurl_buf
, g
);
2289 t
= g_utf8_find_next_char(t
, NULL
);
2294 if(*c
== ')' && !inside_html
) {
2296 ret
= g_string_append_c(ret
, *c
);
2303 ret
= g_string_append_c(ret
, *c
);
2307 return g_string_free(ret
, FALSE
);
2310 char *purple_unescape_text(const char *in
)
2318 ret
= g_string_new("");
2323 if ((ent
= purple_markup_unescape_entity(c
, &len
)) != NULL
) {
2324 g_string_append(ret
, ent
);
2327 g_string_append_c(ret
, *c
);
2332 return g_string_free(ret
, FALSE
);
2335 char *purple_unescape_html(const char *html
)
2338 const char *c
= html
;
2343 ret
= g_string_new("");
2348 if ((ent
= purple_markup_unescape_entity(c
, &len
)) != NULL
) {
2349 g_string_append(ret
, ent
);
2351 } else if (!strncmp(c
, "<br>", 4)) {
2352 g_string_append_c(ret
, '\n');
2355 g_string_append_c(ret
, *c
);
2360 return g_string_free(ret
, FALSE
);
2364 purple_markup_slice(const char *str
, guint x
, guint y
)
2369 gboolean appended
= FALSE
;
2373 g_return_val_if_fail(str
!= NULL
, NULL
);
2374 g_return_val_if_fail(x
<= y
, NULL
);
2377 return g_strdup("");
2379 ret
= g_string_new("");
2382 while (*str
&& (z
< y
)) {
2383 c
= g_utf8_get_char(str
);
2386 char *end
= strchr(str
, '>');
2389 g_string_free(ret
, TRUE
);
2390 while ((tag
= g_queue_pop_head(q
)))
2396 if (!g_ascii_strncasecmp(str
, "<img ", 5)) {
2397 z
+= strlen("[Image]");
2398 } else if (!g_ascii_strncasecmp(str
, "<br", 3)) {
2400 } else if (!g_ascii_strncasecmp(str
, "<hr>", 4)) {
2401 z
+= strlen("\n---\n");
2402 } else if (!g_ascii_strncasecmp(str
, "</", 2)) {
2406 tmp
= g_queue_pop_head(q
);
2410 /* push it unto the stack */
2413 tmp
= g_strndup(str
, end
- str
+ 1);
2414 g_queue_push_head(q
, tmp
);
2419 g_string_append_len(ret
, str
, end
- str
+ 1);
2423 } else if (c
== '&') {
2424 char *end
= strchr(str
, ';');
2426 g_string_free(ret
, TRUE
);
2427 while ((tag
= g_queue_pop_head(q
)))
2435 g_string_append_len(ret
, str
, end
- str
+ 1);
2440 if (z
== x
&& z
> 0 && !appended
) {
2445 g_string_append(ret
, tag
);
2452 g_string_append_unichar(ret
, c
);
2456 str
= g_utf8_next_char(str
);
2459 while ((tag
= g_queue_pop_head(q
))) {
2462 name
= purple_markup_get_tag_name(tag
);
2463 g_string_append_printf(ret
, "</%s>", name
);
2469 return g_string_free(ret
, FALSE
);
2473 purple_markup_get_tag_name(const char *tag
)
2476 g_return_val_if_fail(tag
!= NULL
, NULL
);
2477 g_return_val_if_fail(*tag
== '<', NULL
);
2479 for (i
= 1; tag
[i
]; i
++)
2480 if (tag
[i
] == '>' || tag
[i
] == ' ' || tag
[i
] == '/')
2483 return g_strndup(tag
+1, i
-1);
2486 /**************************************************************************
2487 * Path/Filename Functions
2488 **************************************************************************/
2490 purple_home_dir(void)
2493 return g_get_home_dir();
2495 return wpurple_data_dir();
2499 /* Returns the argument passed to -c IFF it was present, or ~/.purple. */
2501 purple_user_dir(void)
2503 if (custom_user_dir
!= NULL
)
2504 return custom_user_dir
;
2506 user_dir
= g_build_filename(purple_home_dir(), ".purple", NULL
);
2511 void purple_util_set_user_dir(const char *dir
)
2513 g_free(custom_user_dir
);
2515 if (dir
!= NULL
&& *dir
)
2516 custom_user_dir
= g_strdup(dir
);
2518 custom_user_dir
= NULL
;
2521 int purple_build_dir (const char *path
, int mode
)
2523 return g_mkdir_with_parents(path
, mode
);
2527 * This function is long and beautiful, like my--um, yeah. Anyway,
2528 * it includes lots of error checking so as we don't overwrite
2529 * people's settings if there is a problem writing the new values.
2532 purple_util_write_data_to_file(const char *filename
, const char *data
, gssize size
)
2534 const char *user_dir
= purple_user_dir();
2535 gchar
*filename_full
;
2536 gboolean ret
= FALSE
;
2538 g_return_val_if_fail(user_dir
!= NULL
, FALSE
);
2540 purple_debug_info("util", "Writing file %s to directory %s\n",
2541 filename
, user_dir
);
2543 /* Ensure the user directory exists */
2544 if (!g_file_test(user_dir
, G_FILE_TEST_IS_DIR
))
2546 if (g_mkdir(user_dir
, S_IRUSR
| S_IWUSR
| S_IXUSR
) == -1)
2548 purple_debug_error("util", "Error creating directory %s: %s\n",
2549 user_dir
, g_strerror(errno
));
2554 filename_full
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s", user_dir
, filename
);
2556 ret
= purple_util_write_data_to_file_absolute(filename_full
, data
, size
);
2558 g_free(filename_full
);
2563 purple_util_write_data_to_file_absolute(const char *filename_full
, const char *data
, gssize size
)
2565 gchar
*filename_temp
;
2567 size_t real_size
, byteswritten
;
2573 purple_debug_info("util", "Writing file %s\n",
2576 g_return_val_if_fail((size
>= -1), FALSE
);
2578 filename_temp
= g_strdup_printf("%s.save", filename_full
);
2580 /* Remove an old temporary file, if one exists */
2581 if (g_file_test(filename_temp
, G_FILE_TEST_EXISTS
))
2583 if (g_unlink(filename_temp
) == -1)
2585 purple_debug_error("util", "Error removing old file "
2587 filename_temp
, g_strerror(errno
));
2592 file
= g_fopen(filename_temp
, "wb");
2595 purple_debug_error("util", "Error opening file %s for "
2597 filename_temp
, g_strerror(errno
));
2598 g_free(filename_temp
);
2603 real_size
= (size
== -1) ? strlen(data
) : (size_t) size
;
2604 byteswritten
= fwrite(data
, 1, real_size
, file
);
2607 /* Apparently XFS (and possibly other filesystems) do not
2608 * guarantee that file data is flushed before file metadata,
2609 * so this procedure is insufficient without some flushage. */
2610 if (fflush(file
) < 0) {
2611 purple_debug_error("util", "Error flushing %s: %s\n",
2612 filename_temp
, g_strerror(errno
));
2613 g_free(filename_temp
);
2617 if (fsync(fileno(file
)) < 0) {
2618 purple_debug_error("util", "Error syncing file contents for %s: %s\n",
2619 filename_temp
, g_strerror(errno
));
2620 g_free(filename_temp
);
2627 if (fclose(file
) != 0)
2629 purple_debug_error("util", "Error closing file %s: %s\n",
2630 filename_temp
, g_strerror(errno
));
2631 g_free(filename_temp
);
2636 /* This is the same effect (we hope) as the HAVE_FILENO block
2637 * above, but for systems without fileno(). */
2638 if ((fd
= open(filename_temp
, O_RDWR
)) < 0) {
2639 purple_debug_error("util", "Error opening file %s for flush: %s\n",
2640 filename_temp
, g_strerror(errno
));
2641 g_free(filename_temp
);
2644 if (fsync(fd
) < 0) {
2645 purple_debug_error("util", "Error syncing %s: %s\n",
2646 filename_temp
, g_strerror(errno
));
2647 g_free(filename_temp
);
2651 if (close(fd
) < 0) {
2652 purple_debug_error("util", "Error closing %s after sync: %s\n",
2653 filename_temp
, g_strerror(errno
));
2654 g_free(filename_temp
);
2659 /* Ensure the file is the correct size */
2660 if (byteswritten
!= real_size
)
2662 purple_debug_error("util", "Error writing to file %s: Wrote %"
2663 G_GSIZE_FORMAT
" bytes "
2664 "but should have written %" G_GSIZE_FORMAT
2665 "; is your disk full?\n",
2666 filename_temp
, byteswritten
, real_size
);
2667 g_free(filename_temp
);
2670 /* Use stat to be absolutely sure. */
2671 if ((g_stat(filename_temp
, &st
) == -1) || (st
.st_size
!= real_size
))
2673 purple_debug_error("util", "Error writing data to file %s: "
2674 "Incomplete file written; is your disk "
2677 g_free(filename_temp
);
2682 /* Set file permissions */
2683 if (chmod(filename_temp
, S_IRUSR
| S_IWUSR
) == -1)
2685 purple_debug_error("util", "Error setting permissions of file %s: %s\n",
2686 filename_temp
, g_strerror(errno
));
2690 /* Rename to the REAL name */
2691 if (g_rename(filename_temp
, filename_full
) == -1)
2693 purple_debug_error("util", "Error renaming %s to %s: %s\n",
2694 filename_temp
, filename_full
,
2698 g_free(filename_temp
);
2704 purple_util_read_xml_from_file(const char *filename
, const char *description
)
2706 return xmlnode_from_file(purple_user_dir(), filename
, description
, "util");
2710 * Like mkstemp() but returns a file pointer, uses a pre-set template,
2711 * uses the semantics of tempnam() for the directory to use and allocates
2712 * the space for the filepath.
2714 * Caller is responsible for closing the file and removing it when done,
2715 * as well as freeing the space pointed-to by "path" with g_free().
2717 * Returns NULL on failure and cleans up after itself if so.
2719 static const char *purple_mkstemp_templ
= {"purpleXXXXXX"};
2722 purple_mkstemp(char **fpath
, gboolean binary
)
2724 const gchar
*tmpdir
;
2728 g_return_val_if_fail(fpath
!= NULL
, NULL
);
2730 if((tmpdir
= (gchar
*)g_get_tmp_dir()) != NULL
) {
2731 if((*fpath
= g_strdup_printf("%s" G_DIR_SEPARATOR_S
"%s", tmpdir
, purple_mkstemp_templ
)) != NULL
) {
2732 fd
= g_mkstemp(*fpath
);
2734 purple_debug(PURPLE_DEBUG_ERROR
, "purple_mkstemp",
2735 "Couldn't make \"%s\", error: %d\n",
2738 if((fp
= fdopen(fd
, "r+")) == NULL
) {
2740 purple_debug(PURPLE_DEBUG_ERROR
, "purple_mkstemp",
2741 "Couldn't fdopen(), error: %d\n", errno
);
2751 purple_debug(PURPLE_DEBUG_ERROR
, "purple_mkstemp",
2752 "g_get_tmp_dir() failed!\n");
2759 purple_util_get_image_extension(gconstpointer data
, size_t len
)
2761 g_return_val_if_fail(data
!= NULL
, NULL
);
2762 g_return_val_if_fail(len
> 0, NULL
);
2766 if (!strncmp((char *)data
, "GIF8", 4))
2768 else if (!strncmp((char *)data
, "\xff\xd8\xff", 3)) /* 4th may be e0 through ef */
2770 else if (!strncmp((char *)data
, "\x89PNG", 4))
2772 else if (!strncmp((char *)data
, "MM", 2) ||
2773 !strncmp((char *)data
, "II", 2))
2775 else if (!strncmp((char *)data
, "BM", 2))
2783 * We thought about using non-cryptographic hashes like CRC32 here.
2784 * They would be faster, but we think using something more secure is
2785 * important, so that it is more difficult for someone to maliciously
2786 * replace one buddy's icon with something else.
2789 purple_util_get_image_checksum(gconstpointer image_data
, size_t image_len
)
2791 PurpleCipherContext
*context
;
2794 context
= purple_cipher_context_new_by_name("sha1", NULL
);
2795 if (context
== NULL
)
2797 purple_debug_error("util", "Could not find sha1 cipher\n");
2798 g_return_val_if_reached(NULL
);
2801 /* Hash the image data */
2802 purple_cipher_context_append(context
, image_data
, image_len
);
2803 if (!purple_cipher_context_digest_to_str(context
, sizeof(digest
), digest
, NULL
))
2805 purple_debug_error("util", "Failed to get SHA-1 digest.\n");
2806 g_return_val_if_reached(NULL
);
2808 purple_cipher_context_destroy(context
);
2810 return g_strdup(digest
);
2814 purple_util_get_image_filename(gconstpointer image_data
, size_t image_len
)
2816 /* Return the filename */
2817 char *checksum
= purple_util_get_image_checksum(image_data
, image_len
);
2818 char *filename
= g_strdup_printf("%s.%s", checksum
,
2819 purple_util_get_image_extension(image_data
, image_len
));
2825 purple_program_is_valid(const char *program
)
2827 GError
*error
= NULL
;
2830 gboolean is_valid
= FALSE
;
2832 g_return_val_if_fail(program
!= NULL
, FALSE
);
2833 g_return_val_if_fail(*program
!= '\0', FALSE
);
2835 if (!g_shell_parse_argv(program
, NULL
, &argv
, &error
)) {
2836 purple_debug(PURPLE_DEBUG_ERROR
, "program_is_valid",
2837 "Could not parse program '%s': %s\n",
2838 program
, error
->message
);
2839 g_error_free(error
);
2847 progname
= g_find_program_in_path(argv
[0]);
2848 is_valid
= (progname
!= NULL
);
2850 if(purple_debug_is_verbose())
2851 purple_debug_info("program_is_valid", "Tested program %s. %s.\n", program
,
2852 is_valid
? "Valid" : "Invalid");
2862 purple_running_gnome(void)
2865 gchar
*tmp
= g_find_program_in_path("gnome-open");
2871 tmp
= (gchar
*)g_getenv("GNOME_DESKTOP_SESSION_ID");
2873 return ((tmp
!= NULL
) && (*tmp
!= '\0'));
2880 purple_running_kde(void)
2883 gchar
*tmp
= g_find_program_in_path("kfmclient");
2884 const char *session
;
2890 session
= g_getenv("KDE_FULL_SESSION");
2891 if (purple_strequal(session
, "true"))
2894 /* If you run Purple from Konsole under !KDE, this will provide a
2895 * a false positive. Since we do the GNOME checks first, this is
2896 * only a problem if you're running something !(KDE || GNOME) and
2897 * you run Purple from Konsole. This really shouldn't be a problem. */
2898 return ((g_getenv("KDEDIR") != NULL
) || g_getenv("KDEDIRS") != NULL
);
2905 purple_running_osx(void)
2907 #if defined(__APPLE__)
2914 typedef union purple_sockaddr
{
2916 struct sockaddr_in sa_in
;
2917 #if defined(AF_INET6)
2918 struct sockaddr_in6 sa_in6
;
2920 struct sockaddr_storage sa_stor
;
2924 purple_fd_get_ip(int fd
)
2926 PurpleSockaddr addr
;
2927 socklen_t namelen
= sizeof(addr
);
2930 g_return_val_if_fail(fd
!= 0, NULL
);
2932 if (getsockname(fd
, &(addr
.sa
), &namelen
))
2935 family
= addr
.sa
.sa_family
;
2937 if (family
== AF_INET
) {
2938 return g_strdup(inet_ntoa(addr
.sa_in
.sin_addr
));
2940 #if defined(AF_INET6) && defined(HAVE_INET_NTOP)
2941 else if (family
== AF_INET6
) {
2942 char host
[INET6_ADDRSTRLEN
];
2945 tmp
= inet_ntop(family
, &(addr
.sa_in6
.sin6_addr
), host
, sizeof(host
));
2946 return g_strdup(tmp
);
2954 purple_socket_get_family(int fd
)
2956 PurpleSockaddr addr
;
2957 socklen_t len
= sizeof(addr
);
2959 g_return_val_if_fail(fd
>= 0, -1);
2961 if (getsockname(fd
, &(addr
.sa
), &len
))
2964 return addr
.sa
.sa_family
;
2968 purple_socket_speaks_ipv4(int fd
)
2972 g_return_val_if_fail(fd
>= 0, FALSE
);
2974 family
= purple_socket_get_family(fd
);
2979 #if defined(IPV6_V6ONLY)
2983 guint len
= sizeof(val
);
2985 if (getsockopt(fd
, IPPROTO_IPV6
, IPV6_V6ONLY
, &val
, &len
) != 0)
2995 /**************************************************************************
2997 **************************************************************************/
2999 purple_strequal(const gchar
*left
, const gchar
*right
)
3001 #if GLIB_CHECK_VERSION(2,16,0)
3002 return (g_strcmp0(left
, right
) == 0);
3004 return ((left
== NULL
&& right
== NULL
) ||
3005 (left
!= NULL
&& right
!= NULL
&& strcmp(left
, right
) == 0));
3010 purple_normalize(const PurpleAccount
*account
, const char *str
)
3012 const char *ret
= NULL
;
3013 static char buf
[BUF_LEN
];
3015 /* This should prevent a crash if purple_normalize gets called with NULL str, see #10115 */
3016 g_return_val_if_fail(str
!= NULL
, "");
3018 if (account
!= NULL
)
3020 PurplePlugin
*prpl
= purple_find_prpl(purple_account_get_protocol_id(account
));
3024 PurplePluginProtocolInfo
*prpl_info
= PURPLE_PLUGIN_PROTOCOL_INFO(prpl
);
3026 if (prpl_info
->normalize
)
3027 ret
= prpl_info
->normalize(account
, str
);
3035 tmp
= g_utf8_normalize(str
, -1, G_NORMALIZE_DEFAULT
);
3036 g_snprintf(buf
, sizeof(buf
), "%s", tmp
);
3046 * You probably don't want to call this directly, it is
3047 * mainly for use as a PRPL callback function. See the
3048 * comments in util.h.
3051 purple_normalize_nocase(const PurpleAccount
*account
, const char *str
)
3053 static char buf
[BUF_LEN
];
3056 g_return_val_if_fail(str
!= NULL
, NULL
);
3058 tmp1
= g_utf8_strdown(str
, -1);
3059 tmp2
= g_utf8_normalize(tmp1
, -1, G_NORMALIZE_DEFAULT
);
3060 g_snprintf(buf
, sizeof(buf
), "%s", tmp2
? tmp2
: "");
3068 purple_strdup_withhtml(const gchar
*src
)
3070 gulong destsize
, i
, j
;
3073 g_return_val_if_fail(src
!= NULL
, NULL
);
3075 /* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */
3077 for (i
= 0; src
[i
] != '\0'; i
++)
3081 else if (src
[i
] != '\r')
3085 dest
= g_malloc(destsize
);
3087 /* Copy stuff, ignoring \r's, because they are dumb */
3088 for (i
= 0, j
= 0; src
[i
] != '\0'; i
++) {
3089 if (src
[i
] == '\n') {
3090 strcpy(&dest
[j
], "<BR>");
3092 } else if (src
[i
] != '\r')
3096 dest
[destsize
-1] = '\0';
3102 purple_str_has_prefix(const char *s
, const char *p
)
3104 return g_str_has_prefix(s
, p
);
3108 purple_str_has_suffix(const char *s
, const char *x
)
3110 return g_str_has_suffix(s
, x
);
3114 purple_str_add_cr(const char *text
)
3120 g_return_val_if_fail(text
!= NULL
, NULL
);
3122 if (text
[0] == '\n')
3124 for (i
= 1; i
< strlen(text
); i
++)
3125 if (text
[i
] == '\n' && text
[i
- 1] != '\r')
3129 return g_strdup(text
);
3131 ret
= g_malloc0(strlen(text
) + count
+ 1);
3134 if (text
[i
] == '\n')
3136 ret
[j
++] = text
[i
++];
3137 for (; i
< strlen(text
); i
++) {
3138 if (text
[i
] == '\n' && text
[i
- 1] != '\r')
3147 purple_str_strip_char(char *text
, char thechar
)
3151 g_return_if_fail(text
!= NULL
);
3153 for (i
= 0, j
= 0; text
[i
]; i
++)
3154 if (text
[i
] != thechar
)
3155 text
[j
++] = text
[i
];
3161 purple_util_chrreplace(char *string
, char delimiter
,
3166 g_return_if_fail(string
!= NULL
);
3168 while (string
[i
] != '\0')
3170 if (string
[i
] == delimiter
)
3171 string
[i
] = replacement
;
3177 purple_strreplace(const char *string
, const char *delimiter
,
3178 const char *replacement
)
3183 g_return_val_if_fail(string
!= NULL
, NULL
);
3184 g_return_val_if_fail(delimiter
!= NULL
, NULL
);
3185 g_return_val_if_fail(replacement
!= NULL
, NULL
);
3187 split
= g_strsplit(string
, delimiter
, 0);
3188 ret
= g_strjoinv(replacement
, split
);
3195 purple_strcasereplace(const char *string
, const char *delimiter
,
3196 const char *replacement
)
3199 int length_del
, length_rep
, i
, j
;
3201 g_return_val_if_fail(string
!= NULL
, NULL
);
3202 g_return_val_if_fail(delimiter
!= NULL
, NULL
);
3203 g_return_val_if_fail(replacement
!= NULL
, NULL
);
3205 length_del
= strlen(delimiter
);
3206 length_rep
= strlen(replacement
);
3208 /* Count how many times the delimiter appears */
3209 i
= 0; /* position in the source string */
3210 j
= 0; /* number of occurrences of "delimiter" */
3211 while (string
[i
] != '\0') {
3212 if (!g_ascii_strncasecmp(&string
[i
], delimiter
, length_del
)) {
3221 ret
= g_malloc(j
+1);
3223 i
= 0; /* position in the source string */
3224 j
= 0; /* position in the destination string */
3225 while (string
[i
] != '\0') {
3226 if (!g_ascii_strncasecmp(&string
[i
], delimiter
, length_del
)) {
3227 strncpy(&ret
[j
], replacement
, length_rep
);
3243 purple_strcasestr(const char *haystack
, const char *needle
)
3246 const char *tmp
, *ret
;
3248 g_return_val_if_fail(haystack
!= NULL
, NULL
);
3249 g_return_val_if_fail(needle
!= NULL
, NULL
);
3251 hlen
= strlen(haystack
);
3252 nlen
= strlen(needle
);
3256 g_return_val_if_fail(hlen
> 0, NULL
);
3257 g_return_val_if_fail(nlen
> 0, NULL
);
3259 while (*tmp
&& !ret
) {
3260 if (!g_ascii_strncasecmp(needle
, tmp
, nlen
))
3270 purple_str_size_to_units(size_t size
)
3272 static const char * const size_str
[] = { "bytes", "KiB", "MiB", "GiB" };
3277 return g_strdup(_("Calculating..."));
3279 else if (size
== 0) {
3280 return g_strdup(_("Unknown."));
3283 size_mag
= (float)size
;
3285 while ((size_index
< 3) && (size_mag
> 1024)) {
3290 if (size_index
== 0) {
3291 return g_strdup_printf("%" G_GSIZE_FORMAT
" %s", size
, size_str
[size_index
]);
3293 return g_strdup_printf("%.2f %s", size_mag
, size_str
[size_index
]);
3299 purple_str_seconds_to_string(guint secs
)
3302 guint days
, hrs
, mins
;
3306 return g_strdup_printf(dngettext(PACKAGE
, "%d second", "%d seconds", secs
), secs
);
3309 days
= secs
/ (60 * 60 * 24);
3310 secs
= secs
% (60 * 60 * 24);
3311 hrs
= secs
/ (60 * 60);
3312 secs
= secs
% (60 * 60);
3318 ret
= g_strdup_printf(dngettext(PACKAGE
, "%d day", "%d days", days
), days
);
3325 char *tmp
= g_strdup_printf(
3326 dngettext(PACKAGE
, "%s, %d hour", "%s, %d hours", hrs
),
3332 ret
= g_strdup_printf(dngettext(PACKAGE
, "%d hour", "%d hours", hrs
), hrs
);
3339 char *tmp
= g_strdup_printf(
3340 dngettext(PACKAGE
, "%s, %d minute", "%s, %d minutes", mins
),
3346 ret
= g_strdup_printf(dngettext(PACKAGE
, "%d minute", "%d minutes", mins
), mins
);
3354 purple_str_binary_to_ascii(const unsigned char *binary
, guint len
)
3359 g_return_val_if_fail(len
> 0, NULL
);
3361 ret
= g_string_sized_new(len
);
3363 for (i
= 0; i
< len
; i
++)
3364 if (binary
[i
] < 32 || binary
[i
] > 126)
3365 g_string_append_printf(ret
, "\\x%02hhx", binary
[i
]);
3366 else if (binary
[i
] == '\\')
3367 g_string_append(ret
, "\\\\");
3369 g_string_append_c(ret
, binary
[i
]);
3371 return g_string_free(ret
, FALSE
);
3374 /**************************************************************************
3376 **************************************************************************/
3378 void purple_got_protocol_handler_uri(const char *uri
)
3382 const char *tmp
, *param_string
;
3384 GHashTable
*params
= NULL
;
3386 if (!(tmp
= strchr(uri
, ':')) || tmp
== uri
) {
3387 purple_debug_error("util", "Malformed protocol handler message - missing protocol.\n");
3391 len
= MIN(sizeof(proto
) - 1, (tmp
- uri
));
3393 strncpy(proto
, uri
, len
);
3398 if (g_str_equal(proto
, "xmpp"))
3403 purple_debug_info("util", "Processing message '%s' for protocol '%s' using delimiter '%c'.\n", tmp
, proto
, delimiter
);
3405 if ((param_string
= strchr(tmp
, '?'))) {
3406 const char *keyend
= NULL
, *pairstart
;
3407 char *key
, *value
= NULL
;
3409 cmd
= g_strndup(tmp
, (param_string
- tmp
));
3412 params
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
3413 pairstart
= tmp
= param_string
;
3415 while (*tmp
|| *pairstart
) {
3416 if (*tmp
== delimiter
|| !(*tmp
)) {
3417 /* If there is no explicit value */
3421 if (keyend
&& keyend
!= pairstart
) {
3423 key
= g_strndup(pairstart
, (keyend
- pairstart
));
3424 /* If there is an explicit value */
3425 if (keyend
!= tmp
&& keyend
!= (tmp
- 1))
3426 value
= g_strndup(keyend
+ 1, (tmp
- keyend
- 1));
3427 for (p
= key
; *p
; ++p
)
3428 *p
= g_ascii_tolower(*p
);
3429 g_hash_table_insert(params
, key
, value
);
3431 keyend
= value
= NULL
;
3432 pairstart
= (*tmp
) ? tmp
+ 1 : tmp
;
3433 } else if (*tmp
== '=')
3440 cmd
= g_strdup(tmp
);
3442 purple_signal_emit_return_1(purple_get_core(), "uri-handler", proto
, cmd
, params
);
3446 g_hash_table_destroy(params
);
3450 * TODO: Should probably add a "gboolean *ret_ishttps" parameter that
3451 * is set to TRUE if this URL is https, otherwise it is set to
3452 * FALSE. But that change will break the API.
3454 * This is important for Yahoo! web messenger login. They now
3455 * force https login, and if you access the web messenger login
3456 * page via http then it redirects you to the https version, but
3457 * purple_util_fetch_url() ignores the "https" and attempts to
3458 * fetch the URL via http again, which gets redirected again.
3461 purple_url_parse(const char *url
, char **ret_host
, int *ret_port
,
3462 char **ret_path
, char **ret_user
, char **ret_passwd
)
3464 gboolean is_https
= FALSE
;
3465 const char * scan_info
;
3468 const char *at
, *slash
;
3470 char host
[256], path
[256], user
[256], passwd
[256];
3472 /* hyphen at end includes it in control set */
3474 #define ADDR_CTRL "A-Za-z0-9.-"
3475 #define PORT_CTRL "0-9"
3476 #define PAGE_CTRL "A-Za-z0-9.~_/:*!@&%%?=+^-"
3477 #define USER_CTRL "A-Za-z0-9.~_/*!&%%?=+^-"
3478 #define PASSWD_CTRL "A-Za-z0-9.~_/*!&%%?=+^-"
3480 g_return_val_if_fail(url
!= NULL
, FALSE
);
3482 if ((turl
= purple_strcasestr(url
, "http://")) != NULL
)
3487 else if ((turl
= purple_strcasestr(url
, "https://")) != NULL
)
3494 /* parse out authentication information if supplied */
3495 /* Only care about @ char BEFORE the first / */
3496 at
= strchr(url
, '@');
3497 slash
= strchr(url
, '/');
3499 if (at
&& (!slash
|| at
< slash
)) {
3500 scan_info
= "%255[" USER_CTRL
"]:%255[" PASSWD_CTRL
"]^@";
3501 f
= sscanf(url
, scan_info
, user
, passwd
);
3504 /* No passwd, possibly just username supplied */
3505 scan_info
= "%255[" USER_CTRL
"]^@";
3506 f
= sscanf(url
, scan_info
, user
);
3509 url
= at
+1; /* move pointer after the @ char */
3518 scan_info
= "%255[" ADDR_CTRL
"]:%5[" PORT_CTRL
"]/%255[" PAGE_CTRL
"]";
3519 f
= sscanf(url
, scan_info
, host
, port_str
, path
);
3523 scan_info
= "%255[" ADDR_CTRL
"]/%255[" PAGE_CTRL
"]";
3524 f
= sscanf(url
, scan_info
, host
, path
);
3525 /* Use the default port */
3527 g_snprintf(port_str
, sizeof(port_str
), "443");
3529 g_snprintf(port_str
, sizeof(port_str
), "80");
3538 sscanf(port_str
, "%d", &port
);
3540 if (ret_host
!= NULL
) *ret_host
= g_strdup(host
);
3541 if (ret_port
!= NULL
) *ret_port
= port
;
3542 if (ret_path
!= NULL
) *ret_path
= g_strdup(path
);
3543 if (ret_user
!= NULL
) *ret_user
= g_strdup(user
);
3544 if (ret_passwd
!= NULL
) *ret_passwd
= g_strdup(passwd
);
3546 return ((*host
!= '\0') ? TRUE
: FALSE
);
3556 * The arguments to this function are similar to printf.
3559 purple_util_fetch_url_error(PurpleUtilFetchUrlData
*gfud
, const char *format
, ...)
3561 gchar
*error_message
;
3564 va_start(args
, format
);
3565 error_message
= g_strdup_vprintf(format
, args
);
3568 gfud
->callback(gfud
, gfud
->user_data
, NULL
, 0, error_message
);
3569 g_free(error_message
);
3570 purple_util_fetch_url_cancel(gfud
);
3573 static void url_fetch_connect_cb(gpointer url_data
, gint source
, const gchar
*error_message
);
3574 static void ssl_url_fetch_connect_cb(gpointer data
, PurpleSslConnection
*ssl_connection
, PurpleInputCondition cond
);
3575 static void ssl_url_fetch_error_cb(PurpleSslConnection
*ssl_connection
, PurpleSslErrorType error
, gpointer data
);
3578 parse_redirect(const char *data
, size_t data_len
,
3579 PurpleUtilFetchUrlData
*gfud
)
3582 gchar
*new_url
, *temp_url
, *end
;
3586 if ((s
= g_strstr_len(data
, data_len
, "\nLocation: ")) == NULL
)
3587 /* We're not being redirected */
3590 s
+= strlen("Location: ");
3591 end
= strchr(s
, '\r');
3593 /* Just in case :) */
3595 end
= strchr(s
, '\n');
3602 new_url
= g_malloc(len
+ 1);
3603 strncpy(new_url
, s
, len
);
3604 new_url
[len
] = '\0';
3608 if (*new_url
== '/' || g_strstr_len(new_url
, len
, "://") == NULL
)
3612 new_url
= g_strdup_printf("%s:%d%s", gfud
->website
.address
,
3613 gfud
->website
.port
, temp_url
);
3620 purple_debug_info("util", "Redirecting to %s\n", new_url
);
3622 gfud
->num_times_redirected
++;
3623 if (gfud
->num_times_redirected
>= 5)
3625 purple_util_fetch_url_error(gfud
,
3626 _("Could not open %s: Redirected too many times"),
3632 * Try again, with this new location. This code is somewhat
3633 * ugly, but we need to reuse the gfud because whoever called
3634 * us is holding a reference to it.
3637 gfud
->url
= new_url
;
3639 g_free(gfud
->request
);
3640 gfud
->request
= NULL
;
3643 gfud
->is_ssl
= FALSE
;
3644 purple_ssl_close(gfud
->ssl_connection
);
3645 gfud
->ssl_connection
= NULL
;
3647 purple_input_remove(gfud
->inpa
);
3652 gfud
->request_written
= 0;
3656 g_free(gfud
->website
.user
);
3657 g_free(gfud
->website
.passwd
);
3658 g_free(gfud
->website
.address
);
3659 g_free(gfud
->website
.page
);
3660 purple_url_parse(new_url
, &gfud
->website
.address
, &gfud
->website
.port
,
3661 &gfud
->website
.page
, &gfud
->website
.user
, &gfud
->website
.passwd
);
3663 if (purple_strcasestr(new_url
, "https://") != NULL
) {
3664 gfud
->is_ssl
= TRUE
;
3665 gfud
->ssl_connection
= purple_ssl_connect(gfud
->account
,
3666 gfud
->website
.address
, gfud
->website
.port
,
3667 ssl_url_fetch_connect_cb
, ssl_url_fetch_error_cb
, gfud
);
3669 gfud
->connect_data
= purple_proxy_connect(NULL
, gfud
->account
,
3670 gfud
->website
.address
, gfud
->website
.port
,
3671 url_fetch_connect_cb
, gfud
);
3674 if (gfud
->ssl_connection
== NULL
&& gfud
->connect_data
== NULL
)
3676 purple_util_fetch_url_error(gfud
, _("Unable to connect to %s"),
3677 gfud
->website
.address
);
3684 find_header_content(const char *data
, size_t data_len
, const char *header
, size_t header_len
)
3686 const char *p
= NULL
;
3688 if (header_len
<= 0)
3689 header_len
= strlen(header
);
3691 /* Note: data is _not_ nul-terminated. */
3692 if (data_len
> header_len
) {
3693 if (header
[0] == '\n')
3694 p
= (g_ascii_strncasecmp(data
, header
+ 1, header_len
- 1) == 0) ? data
: NULL
;
3696 p
= purple_strcasestr(data
, header
);
3701 /* If we can find the header at all, try to sscanf it.
3702 * Response headers should end with at least \r\n, so sscanf is safe,
3703 * if we make sure that there is indeed a \n in our header.
3705 if (p
&& g_strstr_len(p
, data_len
- (p
- data
), "\n")) {
3713 parse_content_len(const char *data
, size_t data_len
)
3715 size_t content_len
= 0;
3716 const char *p
= NULL
;
3718 p
= find_header_content(data
, data_len
, "\nContent-Length: ", sizeof("\nContent-Length: ") - 1);
3720 sscanf(p
, "%" G_GSIZE_FORMAT
, &content_len
);
3721 purple_debug_misc("util", "parsed %" G_GSIZE_FORMAT
"\n", content_len
);
3728 content_is_chunked(const char *data
, size_t data_len
)
3730 const char *p
= find_header_content(data
, data_len
, "\nTransfer-Encoding: ", sizeof("\nTransfer-Encoding: ") - 1);
3731 if (p
&& g_ascii_strncasecmp(p
, "chunked", 7) == 0)
3737 /* Process in-place */
3739 process_chunked_data(char *data
, gsize
*len
)
3747 /* Read the size of this chunk */
3748 if (sscanf(s
, "%" G_GSIZE_MODIFIER
"x", &sz
) != 1)
3750 purple_debug_error("util", "Error processing chunked data: "
3751 "Expected data length, found: %s\n", s
);
3755 /* We've reached the last chunk */
3757 * TODO: The spec allows "footers" to follow the last chunk.
3758 * If there is more data after this line then we should
3759 * treat it like a header.
3764 /* Advance to the start of the data */
3765 s
= strstr(s
, "\r\n");
3770 if (s
+ sz
> data
+ *len
) {
3771 purple_debug_error("util", "Error processing chunked data: "
3772 "Chunk size %" G_GSIZE_FORMAT
" bytes was longer "
3773 "than the data remaining in the buffer (%"
3774 G_GSIZE_FORMAT
" bytes)\n", sz
, data
+ *len
- s
);
3777 /* Move all data overtop of the chunk length that we read in earlier */
3778 g_memmove(p
, s
, sz
);
3782 if (*s
!= '\r' && *(s
+ 1) != '\n') {
3783 purple_debug_error("util", "Error processing chunked data: "
3784 "Expected \\r\\n, found: %s\n", s
);
3790 /* NULL terminate the data */
3797 url_fetch_recv_cb(gpointer url_data
, gint source
, PurpleInputCondition cond
)
3799 PurpleUtilFetchUrlData
*gfud
= url_data
;
3803 gboolean got_eof
= FALSE
;
3806 * Read data in a loop until we can't read any more! This is a
3807 * little confusing because we read using a different function
3808 * depending on whether the socket is ssl or cleartext.
3810 while ((gfud
->is_ssl
&& ((len
= purple_ssl_read(gfud
->ssl_connection
, buf
, sizeof(buf
))) > 0)) ||
3811 (!gfud
->is_ssl
&& (len
= read(source
, buf
, sizeof(buf
))) > 0))
3813 if(gfud
->max_len
!= -1 && (gfud
->len
+ len
) > gfud
->max_len
) {
3814 purple_util_fetch_url_error(gfud
, _("Error reading from %s: response too long (%d bytes limit)"),
3815 gfud
->website
.address
, gfud
->max_len
);
3819 /* If we've filled up our buffer, make it bigger */
3820 if((gfud
->len
+ len
) >= gfud
->data_len
) {
3821 while((gfud
->len
+ len
) >= gfud
->data_len
)
3822 gfud
->data_len
+= sizeof(buf
);
3824 gfud
->webdata
= g_realloc(gfud
->webdata
, gfud
->data_len
);
3827 data_cursor
= gfud
->webdata
+ gfud
->len
;
3831 memcpy(data_cursor
, buf
, len
);
3833 gfud
->webdata
[gfud
->len
] = '\0';
3835 if(!gfud
->got_headers
) {
3836 char *end_of_headers
;
3838 /* See if we've reached the end of the headers yet */
3839 end_of_headers
= strstr(gfud
->webdata
, "\r\n\r\n");
3840 if (end_of_headers
) {
3842 guint header_len
= (end_of_headers
+ 4 - gfud
->webdata
);
3845 purple_debug_misc("util", "Response headers: '%.*s'\n",
3846 header_len
, gfud
->webdata
);
3848 /* See if we can find a redirect. */
3849 if(parse_redirect(gfud
->webdata
, header_len
, gfud
))
3852 gfud
->got_headers
= TRUE
;
3854 /* No redirect. See if we can find a content length. */
3855 content_len
= parse_content_len(gfud
->webdata
, header_len
);
3856 gfud
->chunked
= content_is_chunked(gfud
->webdata
, header_len
);
3858 if (content_len
== 0) {
3859 /* We'll stick with an initial 8192 */
3862 gfud
->has_explicit_data_len
= TRUE
;
3866 /* If we're returning the headers too, we don't need to clean them out */
3867 if (gfud
->include_headers
) {
3868 gfud
->data_len
= content_len
+ header_len
;
3869 gfud
->webdata
= g_realloc(gfud
->webdata
, gfud
->data_len
);
3871 size_t body_len
= gfud
->len
- header_len
;
3873 content_len
= MAX(content_len
, body_len
);
3875 new_data
= g_try_malloc(content_len
);
3876 if (new_data
== NULL
) {
3877 purple_debug_error("util",
3878 "Failed to allocate %" G_GSIZE_FORMAT
" bytes: %s\n",
3879 content_len
, g_strerror(errno
));
3880 purple_util_fetch_url_error(gfud
,
3881 _("Unable to allocate enough memory to hold "
3882 "the contents from %s. The web server may "
3883 "be trying something malicious."),
3884 gfud
->website
.address
);
3889 /* We may have read part of the body when reading the headers, don't lose it */
3891 memcpy(new_data
, end_of_headers
+ 4, body_len
);
3894 /* Out with the old... */
3895 g_free(gfud
->webdata
);
3897 /* In with the new. */
3898 gfud
->len
= body_len
;
3899 gfud
->data_len
= content_len
;
3900 gfud
->webdata
= new_data
;
3905 if(gfud
->has_explicit_data_len
&& gfud
->len
>= gfud
->data_len
) {
3912 if(errno
== EAGAIN
) {
3915 purple_util_fetch_url_error(gfud
, _("Error reading from %s: %s"),
3916 gfud
->website
.address
, g_strerror(errno
));
3921 if((len
== 0) || got_eof
) {
3922 gfud
->webdata
= g_realloc(gfud
->webdata
, gfud
->len
+ 1);
3923 gfud
->webdata
[gfud
->len
] = '\0';
3925 if (!gfud
->include_headers
&& gfud
->chunked
) {
3926 /* Process only if we don't want the headers. */
3927 process_chunked_data(gfud
->webdata
, &gfud
->len
);
3930 gfud
->callback(gfud
, gfud
->user_data
, gfud
->webdata
, gfud
->len
, NULL
);
3931 purple_util_fetch_url_cancel(gfud
);
3935 static void ssl_url_fetch_recv_cb(gpointer data
, PurpleSslConnection
*ssl_connection
, PurpleInputCondition cond
)
3937 url_fetch_recv_cb(data
, -1, cond
);
3941 * This function is called when the socket is available to be written
3944 * @param source The file descriptor that can be written to. This can
3945 * be an http connection or it can be the SSL connection of an
3946 * https request. So be careful what you use it for! If it's
3947 * an https request then use purple_ssl_write() instead of
3948 * writing to it directly.
3951 url_fetch_send_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
3953 PurpleUtilFetchUrlData
*gfud
;
3958 if (gfud
->request
== NULL
) {
3960 PurpleProxyInfo
*gpi
= purple_proxy_get_setup(gfud
->account
);
3961 GString
*request_str
= g_string_new(NULL
);
3963 g_string_append_printf(request_str
, "GET %s%s HTTP/%s\r\n"
3964 "Connection: close\r\n",
3965 (gfud
->full
? "" : "/"),
3966 (gfud
->full
? (gfud
->url
? gfud
->url
: "") : (gfud
->website
.page
? gfud
->website
.page
: "")),
3967 (gfud
->http11
? "1.1" : "1.0"));
3969 if (gfud
->user_agent
)
3970 g_string_append_printf(request_str
, "User-Agent: %s\r\n", gfud
->user_agent
);
3972 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1
3973 * clients must know how to handle the "chunked" transfer encoding.
3974 * Purple doesn't know how to handle "chunked", so should always send
3975 * the Host header regardless, to get around some observed problems
3977 g_string_append_printf(request_str
, "Accept: */*\r\n"
3979 (gfud
->website
.address
? gfud
->website
.address
: ""));
3981 if (purple_proxy_info_get_username(gpi
) != NULL
3982 && (purple_proxy_info_get_type(gpi
) == PURPLE_PROXY_USE_ENVVAR
3983 || purple_proxy_info_get_type(gpi
) == PURPLE_PROXY_HTTP
)) {
3984 /* This chunk of code was copied from proxy.c http_start_connect_tunneling()
3985 * This is really a temporary hack - we need a more complete proxy handling solution,
3986 * so I didn't think it was worthwhile to refactor for reuse
3988 char *t1
, *t2
, *ntlm_type1
;
3992 ret
= gethostname(hostname
, sizeof(hostname
));
3993 hostname
[sizeof(hostname
) - 1] = '\0';
3994 if (ret
< 0 || hostname
[0] == '\0') {
3995 purple_debug_warning("util", "proxy - gethostname() failed -- is your hostname set?");
3996 strcpy(hostname
, "localhost");
3999 t1
= g_strdup_printf("%s:%s",
4000 purple_proxy_info_get_username(gpi
),
4001 purple_proxy_info_get_password(gpi
) ?
4002 purple_proxy_info_get_password(gpi
) : "");
4003 t2
= purple_base64_encode((const guchar
*)t1
, strlen(t1
));
4006 ntlm_type1
= purple_ntlm_gen_type1(hostname
, "");
4008 g_string_append_printf(request_str
,
4009 "Proxy-Authorization: Basic %s\r\n"
4010 "Proxy-Authorization: NTLM %s\r\n"
4011 "Proxy-Connection: Keep-Alive\r\n",
4017 g_string_append(request_str
, "\r\n");
4019 gfud
->request
= g_string_free(request_str
, FALSE
);
4022 if(purple_debug_is_unsafe())
4023 purple_debug_misc("util", "Request: '%s'\n", gfud
->request
);
4025 purple_debug_misc("util", "request constructed\n");
4027 total_len
= strlen(gfud
->request
);
4030 len
= purple_ssl_write(gfud
->ssl_connection
, gfud
->request
+ gfud
->request_written
,
4031 total_len
- gfud
->request_written
);
4033 len
= write(gfud
->fd
, gfud
->request
+ gfud
->request_written
,
4034 total_len
- gfud
->request_written
);
4036 if (len
< 0 && errno
== EAGAIN
)
4039 purple_util_fetch_url_error(gfud
, _("Error writing to %s: %s"),
4040 gfud
->website
.address
, g_strerror(errno
));
4043 gfud
->request_written
+= len
;
4045 if (gfud
->request_written
< total_len
)
4048 /* We're done writing our request, now start reading the response */
4050 purple_input_remove(gfud
->inpa
);
4052 purple_ssl_input_add(gfud
->ssl_connection
, ssl_url_fetch_recv_cb
, gfud
);
4054 purple_input_remove(gfud
->inpa
);
4055 gfud
->inpa
= purple_input_add(gfud
->fd
, PURPLE_INPUT_READ
, url_fetch_recv_cb
,
4061 url_fetch_connect_cb(gpointer url_data
, gint source
, const gchar
*error_message
)
4063 PurpleUtilFetchUrlData
*gfud
;
4066 gfud
->connect_data
= NULL
;
4070 purple_util_fetch_url_error(gfud
, _("Unable to connect to %s: %s"),
4071 (gfud
->website
.address
? gfud
->website
.address
: ""), error_message
);
4077 gfud
->inpa
= purple_input_add(source
, PURPLE_INPUT_WRITE
,
4078 url_fetch_send_cb
, gfud
);
4079 url_fetch_send_cb(gfud
, source
, PURPLE_INPUT_WRITE
);
4082 static void ssl_url_fetch_connect_cb(gpointer data
, PurpleSslConnection
*ssl_connection
, PurpleInputCondition cond
)
4084 PurpleUtilFetchUrlData
*gfud
;
4088 gfud
->inpa
= purple_input_add(ssl_connection
->fd
, PURPLE_INPUT_WRITE
,
4089 url_fetch_send_cb
, gfud
);
4090 url_fetch_send_cb(gfud
, ssl_connection
->fd
, PURPLE_INPUT_WRITE
);
4093 static void ssl_url_fetch_error_cb(PurpleSslConnection
*ssl_connection
, PurpleSslErrorType error
, gpointer data
)
4095 PurpleUtilFetchUrlData
*gfud
;
4098 gfud
->ssl_connection
= NULL
;
4100 purple_util_fetch_url_error(gfud
, _("Unable to connect to %s: %s"),
4101 (gfud
->website
.address
? gfud
->website
.address
: ""),
4102 purple_ssl_strerror(error
));
4105 PurpleUtilFetchUrlData
*
4106 purple_util_fetch_url_request(const char *url
, gboolean full
,
4107 const char *user_agent
, gboolean http11
,
4108 const char *request
, gboolean include_headers
,
4109 PurpleUtilFetchUrlCallback callback
, void *user_data
)
4111 return purple_util_fetch_url_request_len_with_account(NULL
, url
, full
,
4113 request
, include_headers
, -1,
4114 callback
, user_data
);
4117 PurpleUtilFetchUrlData
*
4118 purple_util_fetch_url_request_len(const char *url
, gboolean full
,
4119 const char *user_agent
, gboolean http11
,
4120 const char *request
, gboolean include_headers
, gssize max_len
,
4121 PurpleUtilFetchUrlCallback callback
, void *user_data
)
4123 return purple_util_fetch_url_request_len_with_account(NULL
, url
, full
,
4124 user_agent
, http11
, request
, include_headers
, max_len
, callback
,
4128 PurpleUtilFetchUrlData
*
4129 purple_util_fetch_url_request_len_with_account(PurpleAccount
*account
,
4130 const char *url
, gboolean full
, const char *user_agent
, gboolean http11
,
4131 const char *request
, gboolean include_headers
, gssize max_len
,
4132 PurpleUtilFetchUrlCallback callback
, void *user_data
)
4134 PurpleUtilFetchUrlData
*gfud
;
4136 g_return_val_if_fail(url
!= NULL
, NULL
);
4137 g_return_val_if_fail(callback
!= NULL
, NULL
);
4139 if(purple_debug_is_unsafe())
4140 purple_debug_info("util",
4141 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n",
4142 url
, full
, user_agent
?user_agent
:"(null)", http11
);
4144 purple_debug_info("util", "requesting to fetch a URL\n");
4146 gfud
= g_new0(PurpleUtilFetchUrlData
, 1);
4148 gfud
->callback
= callback
;
4149 gfud
->user_data
= user_data
;
4150 gfud
->url
= g_strdup(url
);
4151 gfud
->user_agent
= g_strdup(user_agent
);
4152 gfud
->http11
= http11
;
4154 gfud
->request
= g_strdup(request
);
4155 gfud
->include_headers
= include_headers
;
4157 gfud
->max_len
= max_len
;
4158 gfud
->account
= account
;
4160 purple_url_parse(url
, &gfud
->website
.address
, &gfud
->website
.port
,
4161 &gfud
->website
.page
, &gfud
->website
.user
, &gfud
->website
.passwd
);
4163 if (purple_strcasestr(url
, "https://") != NULL
) {
4164 if (!purple_ssl_is_supported()) {
4165 purple_util_fetch_url_error(gfud
,
4166 _("Unable to connect to %s: %s"),
4167 gfud
->website
.address
,
4168 _("Server requires TLS/SSL, but no TLS/SSL support was found."));
4172 gfud
->is_ssl
= TRUE
;
4173 gfud
->ssl_connection
= purple_ssl_connect(account
,
4174 gfud
->website
.address
, gfud
->website
.port
,
4175 ssl_url_fetch_connect_cb
, ssl_url_fetch_error_cb
, gfud
);
4177 gfud
->connect_data
= purple_proxy_connect(NULL
, account
,
4178 gfud
->website
.address
, gfud
->website
.port
,
4179 url_fetch_connect_cb
, gfud
);
4182 if (gfud
->ssl_connection
== NULL
&& gfud
->connect_data
== NULL
)
4184 purple_util_fetch_url_error(gfud
, _("Unable to connect to %s"),
4185 gfud
->website
.address
);
4193 purple_util_fetch_url_cancel(PurpleUtilFetchUrlData
*gfud
)
4195 if (gfud
->ssl_connection
!= NULL
)
4196 purple_ssl_close(gfud
->ssl_connection
);
4198 if (gfud
->connect_data
!= NULL
)
4199 purple_proxy_connect_cancel(gfud
->connect_data
);
4202 purple_input_remove(gfud
->inpa
);
4207 g_free(gfud
->website
.user
);
4208 g_free(gfud
->website
.passwd
);
4209 g_free(gfud
->website
.address
);
4210 g_free(gfud
->website
.page
);
4212 g_free(gfud
->user_agent
);
4213 g_free(gfud
->request
);
4214 g_free(gfud
->webdata
);
4220 purple_url_decode(const char *str
)
4222 static char buf
[BUF_LEN
];
4227 g_return_val_if_fail(str
!= NULL
, NULL
);
4230 * XXX - This check could be removed and buf could be made
4231 * dynamically allocated, but this is easier.
4233 if (strlen(str
) >= BUF_LEN
)
4236 for (i
= 0; i
< strlen(str
); i
++) {
4241 strncpy(hex
, str
+ ++i
, 2);
4244 /* i is pointing to the start of the number */
4248 * Now it's at the end and at the start of the for loop
4249 * will be at the next character.
4251 buf
[j
++] = strtol(hex
, NULL
, 16);
4257 if (!g_utf8_validate(buf
, -1, (const char **)&bum
))
4264 purple_url_encode(const char *str
)
4267 static char buf
[BUF_LEN
];
4271 g_return_val_if_fail(str
!= NULL
, NULL
);
4272 g_return_val_if_fail(g_utf8_validate(str
, -1, NULL
), NULL
);
4275 for (; *iter
&& j
< (BUF_LEN
- 1) ; iter
= g_utf8_next_char(iter
)) {
4276 gunichar c
= g_utf8_get_char(iter
);
4277 /* If the character is an ASCII character and is alphanumeric
4278 * no need to escape */
4279 if (c
< 128 && (isalnum(c
) || c
== '-' || c
== '.' || c
== '_' || c
== '~')) {
4282 int bytes
= g_unichar_to_utf8(c
, utf_char
);
4283 for (i
= 0; i
< bytes
; i
++) {
4284 if (j
> (BUF_LEN
- 4))
4286 sprintf(buf
+ j
, "%%%02X", utf_char
[i
] & 0xff);
4297 /* Originally lifted from
4298 * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html
4299 * ... and slightly modified to be a bit more rfc822 compliant
4300 * ... and modified a bit more to make domain checking rfc1035 compliant
4301 * with the exception permitted in rfc1101 for domains to start with digit
4302 * but not completely checking to avoid conflicts with IP addresses
4305 purple_email_is_valid(const char *address
)
4307 const char *c
, *domain
;
4308 static char *rfc822_specials
= "()<>@,;:\\\"[]";
4310 g_return_val_if_fail(address
!= NULL
, FALSE
);
4312 if (*address
== '.') return FALSE
;
4314 /* first we validate the name portion (name@domain) (rfc822)*/
4315 for (c
= address
; *c
; c
++) {
4316 if (*c
== '\"' && (c
== address
|| *(c
- 1) == '.' || *(c
- 1) == '\"')) {
4319 if (*c
++ && *c
< 127 && *c
!= '\n' && *c
!= '\r') continue;
4322 if (*c
== '\"') break;
4323 if (*c
< ' ' || *c
>= 127) return FALSE
;
4325 if (!*c
++) return FALSE
;
4326 if (*c
== '@') break;
4327 if (*c
!= '.') return FALSE
;
4330 if (*c
== '@') break;
4331 if (*c
<= ' ' || *c
>= 127) return FALSE
;
4332 if (strchr(rfc822_specials
, *c
)) return FALSE
;
4335 /* It's obviously not an email address if we didn't find an '@' above */
4336 if (*c
== '\0') return FALSE
;
4338 /* strictly we should return false if (*(c - 1) == '.') too, but I think
4339 * we should permit user.@domain type addresses - they do work :) */
4340 if (c
== address
) return FALSE
;
4342 /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */
4343 if (!*(domain
= ++c
)) return FALSE
;
4345 if (*c
== '.' && (c
== domain
|| *(c
- 1) == '.' || *(c
- 1) == '-'))
4347 if (*c
== '-' && (*(c
- 1) == '.' || *(c
- 1) == '@')) return FALSE
;
4348 if ((*c
< '0' && *c
!= '-' && *c
!= '.') || (*c
> '9' && *c
< 'A') ||
4349 (*c
> 'Z' && *c
< 'a') || (*c
> 'z')) return FALSE
;
4352 if (*(c
- 1) == '-') return FALSE
;
4354 return ((c
- domain
) > 3 ? TRUE
: FALSE
);
4358 purple_ipv4_address_is_valid(const char *ip
)
4360 int c
, o1
, o2
, o3
, o4
;
4363 g_return_val_if_fail(ip
!= NULL
, FALSE
);
4365 c
= sscanf(ip
, "%d.%d.%d.%d%c", &o1
, &o2
, &o3
, &o4
, &end
);
4366 if (c
!= 4 || o1
< 0 || o1
> 255 || o2
< 0 || o2
> 255 || o3
< 0 || o3
> 255 || o4
< 0 || o4
> 255)
4372 purple_ipv6_address_is_valid(const gchar
*ip
)
4375 gboolean double_colon
= FALSE
;
4379 g_return_val_if_fail(ip
!= NULL
, FALSE
);
4384 for (c
= ip
; *c
; ++c
) {
4385 if ((*c
>= '0' && *c
<= '9') ||
4386 (*c
>= 'a' && *c
<= 'f') ||
4387 (*c
>= 'A' && *c
<= 'F')) {
4389 /* Only four hex digits per chunk */
4392 } else if (*c
== ':') {
4393 /* The start of a new chunk */
4396 if (*(c
+ 1) == ':') {
4398 * '::' indicates a consecutive series of chunks full
4399 * of zeroes. There can be only one of these per address.
4403 double_colon
= TRUE
;
4410 * Either we saw a '::' and there were fewer than 8 chunks -or-
4411 * we didn't see a '::' and saw exactly 8 chunks.
4413 return (double_colon
&& chunks
< 8) || (!double_colon
&& chunks
== 8);
4416 /* TODO 3.0.0: Add ipv6 check, too */
4418 purple_ip_address_is_valid(const char *ip
)
4420 return purple_ipv4_address_is_valid(ip
);
4423 /* Stolen from gnome_uri_list_extract_uris */
4425 purple_uri_list_extract_uris(const gchar
*uri_list
)
4429 GList
*result
= NULL
;
4431 g_return_val_if_fail (uri_list
!= NULL
, NULL
);
4435 /* We don't actually try to validate the URI according to RFC
4436 * 2396, or even check for allowed characters - we just ignore
4437 * comments and trim whitespace off the ends. We also
4438 * allow LF delimination as well as the specified CRLF.
4446 while (*q
&& (*q
!= '\n') && (*q
!= '\r'))
4451 while (q
> p
&& isspace(*q
))
4454 retval
= (gchar
*)g_malloc (q
- p
+ 2);
4455 strncpy (retval
, p
, q
- p
+ 1);
4456 retval
[q
- p
+ 1] = '\0';
4458 result
= g_list_prepend (result
, retval
);
4461 p
= strchr (p
, '\n');
4466 return g_list_reverse (result
);
4470 /* Stolen from gnome_uri_list_extract_filenames */
4472 purple_uri_list_extract_filenames(const gchar
*uri_list
)
4474 GList
*tmp_list
, *node
, *result
;
4476 g_return_val_if_fail (uri_list
!= NULL
, NULL
);
4478 result
= purple_uri_list_extract_uris(uri_list
);
4482 gchar
*s
= (gchar
*)tmp_list
->data
;
4485 tmp_list
= tmp_list
->next
;
4487 if (!strncmp (s
, "file:", 5)) {
4488 node
->data
= g_filename_from_uri (s
, NULL
, NULL
);
4489 /* not sure if this fallback is useful at all */
4490 if (!node
->data
) node
->data
= g_strdup (s
+5);
4492 result
= g_list_delete_link(result
, node
);
4499 /**************************************************************************
4500 * UTF8 String Functions
4501 **************************************************************************/
4503 purple_utf8_try_convert(const char *str
)
4508 g_return_val_if_fail(str
!= NULL
, NULL
);
4510 if (g_utf8_validate(str
, -1, NULL
)) {
4511 return g_strdup(str
);
4514 utf8
= g_locale_to_utf8(str
, -1, &converted
, NULL
, NULL
);
4518 utf8
= g_convert(str
, -1, "UTF-8", "ISO-8859-15", &converted
, NULL
, NULL
);
4519 if ((utf8
!= NULL
) && (converted
== strlen(str
)))
4527 #define utf8_first(x) ((x & 0x80) == 0 || (x & 0xe0) == 0xc0 \
4528 || (x & 0xf0) == 0xe0 || (x & 0xf8) == 0xf)
4530 purple_utf8_salvage(const char *str
)
4535 g_return_val_if_fail(str
!= NULL
, NULL
);
4537 workstr
= g_string_sized_new(strlen(str
));
4540 g_utf8_validate(str
, -1, &end
);
4541 workstr
= g_string_append_len(workstr
, str
, end
- str
);
4546 workstr
= g_string_append_c(workstr
, '?');
4548 } while (!utf8_first(*str
));
4549 } while (*str
!= '\0');
4551 return g_string_free(workstr
, FALSE
);
4555 purple_utf8_strip_unprintables(const gchar
*str
)
4557 gchar
*workstr
, *iter
;
4561 /* Act like g_strdup */
4564 if (!g_utf8_validate(str
, -1, &bad
)) {
4565 purple_debug_error("util", "purple_utf8_strip_unprintables(%s) failed; "
4566 "first bad character was %02x (%c)\n",
4568 g_return_val_if_reached(NULL
);
4571 workstr
= iter
= g_new(gchar
, strlen(str
) + 1);
4573 gunichar ch
= g_utf8_get_char(str
);
4574 gchar
*next
= g_utf8_next_char(str
);
4576 * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] |
4577 * [#x10000-#x10FFFF]
4579 if ((ch
== '\t' || ch
== '\n' || ch
== '\r') ||
4580 (ch
>= 0x20 && ch
<= 0xD7FF) ||
4581 (ch
>= 0xE000 && ch
<= 0xFFFD) ||
4582 (ch
>= 0x10000 && ch
<= 0x10FFFF)) {
4583 memcpy(iter
, str
, next
- str
);
4584 iter
+= (next
- str
);
4590 /* nul-terminate the new string */
4597 * This function is copied from g_strerror() but changed to use
4600 G_CONST_RETURN gchar
*
4601 purple_gai_strerror(gint errnum
)
4603 static GStaticPrivate msg_private
= G_STATIC_PRIVATE_INIT
;
4605 int saved_errno
= errno
;
4607 const char *msg_locale
;
4609 msg_locale
= gai_strerror(errnum
);
4610 if (g_get_charset(NULL
))
4612 /* This string is already UTF-8--great! */
4613 errno
= saved_errno
;
4618 gchar
*msg_utf8
= g_locale_to_utf8(msg_locale
, -1, NULL
, NULL
, NULL
);
4621 /* Stick in the quark table so that we can return a static result */
4622 GQuark msg_quark
= g_quark_from_string(msg_utf8
);
4625 msg_utf8
= (gchar
*)g_quark_to_string(msg_quark
);
4626 errno
= saved_errno
;
4631 msg
= g_static_private_get(&msg_private
);
4634 msg
= g_new(gchar
, 64);
4635 g_static_private_set(&msg_private
, msg
, g_free
);
4638 sprintf(msg
, "unknown error (%d)", errnum
);
4640 errno
= saved_errno
;
4645 purple_utf8_ncr_encode(const char *str
)
4649 g_return_val_if_fail(str
!= NULL
, NULL
);
4650 g_return_val_if_fail(g_utf8_validate(str
, -1, NULL
), NULL
);
4652 out
= g_string_new("");
4654 for(; *str
; str
= g_utf8_next_char(str
)) {
4655 gunichar wc
= g_utf8_get_char(str
);
4657 /* super simple check. hopefully not too wrong. */
4659 g_string_append_printf(out
, "&#%u;", (guint32
) wc
);
4661 g_string_append_unichar(out
, wc
);
4665 return g_string_free(out
, FALSE
);
4670 purple_utf8_ncr_decode(const char *str
)
4675 g_return_val_if_fail(str
!= NULL
, NULL
);
4676 g_return_val_if_fail(g_utf8_validate(str
, -1, NULL
), NULL
);
4679 out
= g_string_new("");
4681 while( (b
= strstr(buf
, "&#")) ) {
4685 /* append everything leading up to the &# */
4686 g_string_append_len(out
, buf
, b
-buf
);
4688 b
+= 2; /* skip past the &# */
4690 /* strtoul will treat 0x prefix as hex, but not just x */
4691 if(*b
== 'x' || *b
== 'X') {
4696 /* advances buf to the end of the ncr segment */
4697 wc
= (gunichar
) strtoul(b
, &buf
, base
);
4699 /* this mimics the previous impl of ncr_decode */
4701 g_string_append_unichar(out
, wc
);
4706 /* append whatever's left */
4707 g_string_append(out
, buf
);
4709 return g_string_free(out
, FALSE
);
4714 purple_utf8_strcasecmp(const char *a
, const char *b
)
4716 char *a_norm
= NULL
;
4717 char *b_norm
= NULL
;
4727 if(!g_utf8_validate(a
, -1, NULL
) || !g_utf8_validate(b
, -1, NULL
))
4729 purple_debug_error("purple_utf8_strcasecmp",
4730 "One or both parameters are invalid UTF8\n");
4734 a_norm
= g_utf8_casefold(a
, -1);
4735 b_norm
= g_utf8_casefold(b
, -1);
4736 ret
= g_utf8_collate(a_norm
, b_norm
);
4743 /* previously conversation::find_nick() */
4745 purple_utf8_has_word(const char *haystack
, const char *needle
)
4747 char *hay
, *pin
, *p
;
4748 const char *start
, *prev_char
;
4749 gunichar before
, after
;
4751 gboolean ret
= FALSE
;
4753 start
= hay
= g_utf8_strdown(haystack
, -1);
4755 pin
= g_utf8_strdown(needle
, -1);
4758 while ((p
= strstr(start
, pin
)) != NULL
) {
4759 prev_char
= g_utf8_find_prev_char(hay
, p
);
4762 before
= g_utf8_get_char(prev_char
);
4764 after
= g_utf8_get_char_validated(p
+ n
, - 1);
4767 /* The character before is a reasonable guess for a word boundary
4768 ("!g_unichar_isalnum()" is not a valid way to determine word
4769 boundaries, but it is the only reasonable thing to do here),
4770 and isn't the '&' from a "&" or some such entity*/
4771 (before
!= -2 && !g_unichar_isalnum(before
) && *(p
- 1) != '&'))
4772 && after
!= -2 && !g_unichar_isalnum(after
)) {
4786 purple_print_utf8_to_console(FILE *filestream
, char *message
)
4788 gchar
*message_conv
;
4789 GError
*error
= NULL
;
4791 /* Try to convert 'message' to user's locale */
4792 message_conv
= g_locale_from_utf8(message
, -1, NULL
, NULL
, &error
);
4793 if (message_conv
!= NULL
) {
4794 fputs(message_conv
, filestream
);
4795 g_free(message_conv
);
4799 /* use 'message' as a fallback */
4800 g_warning("%s\n", error
->message
);
4801 g_error_free(error
);
4802 fputs(message
, filestream
);
4806 gboolean
purple_message_meify(char *message
, gssize len
)
4809 gboolean inside_html
= FALSE
;
4811 g_return_val_if_fail(message
!= NULL
, FALSE
);
4814 len
= strlen(message
);
4816 for (c
= message
; *c
; c
++, len
--) {
4819 inside_html
= FALSE
;
4828 if(*c
&& !g_ascii_strncasecmp(c
, "/me ", 4)) {
4829 memmove(c
, c
+4, len
-3);
4836 char *purple_text_strip_mnemonic(const char *in
)
4843 g_return_val_if_fail(in
!= NULL
, NULL
);
4845 out
= g_malloc(strlen(in
)+1);
4849 a0
= a
; /* The last non-space char seen so far, or the first char */
4853 if(a
> out
&& b
> in
&& *(b
-1) == '(' && *(b
+1) && !(*(b
+1) & 0x80) && *(b
+2) == ')') {
4854 /* Detected CJK style shortcut (Bug 875311) */
4855 a
= a0
; /* undo the left parenthesis */
4856 b
+= 3; /* and skip the whole mess */
4857 } else if(*(b
+1) == '_') {
4864 /* We don't want to corrupt the middle of UTF-8 characters */
4865 } else if (!(*b
& 0x80)) { /* other 1-byte char */
4870 /* Multibyte utf8 char, don't look for _ inside these */
4873 if ((*b
& 0xe0) == 0xc0) {
4875 } else if ((*b
& 0xf0) == 0xe0) {
4877 } else if ((*b
& 0xf8) == 0xf0) {
4879 } else if ((*b
& 0xfc) == 0xf8) {
4881 } else if ((*b
& 0xfe) == 0xfc) {
4883 } else { /* Illegal utf8 */
4886 a0
= a
; /* unless we want to delete CJK spaces too */
4887 for (i
= 0; i
< n
&& *b
; i
+= 1) {
4897 const char* purple_unescape_filename(const char *escaped
) {
4898 return purple_url_decode(escaped
);
4902 /* this is almost identical to purple_url_encode (hence purple_url_decode
4903 * being used above), but we want to keep certain characters unescaped
4904 * for compat reasons */
4906 purple_escape_filename(const char *str
)
4909 static char buf
[BUF_LEN
];
4913 g_return_val_if_fail(str
!= NULL
, NULL
);
4914 g_return_val_if_fail(g_utf8_validate(str
, -1, NULL
), NULL
);
4917 for (; *iter
&& j
< (BUF_LEN
- 1) ; iter
= g_utf8_next_char(iter
)) {
4918 gunichar c
= g_utf8_get_char(iter
);
4919 /* If the character is an ASCII character and is alphanumeric,
4920 * or one of the specified values, no need to escape */
4921 if (c
< 128 && (g_ascii_isalnum(c
) || c
== '@' || c
== '-' ||
4922 c
== '_' || c
== '.' || c
== '#')) {
4925 int bytes
= g_unichar_to_utf8(c
, utf_char
);
4926 for (i
= 0; i
< bytes
; i
++) {
4927 if (j
> (BUF_LEN
- 4))
4929 sprintf(buf
+ j
, "%%%02x", utf_char
[i
] & 0xff);
4935 /* File/Directory names in windows cannot end in periods/spaces.
4936 * http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx
4938 while (j
> 0 && (buf
[j
- 1] == '.' || buf
[j
- 1] == ' '))
4946 const char *_purple_oscar_convert(const char *act
, const char *protocol
)
4948 if (act
&& purple_strequal(protocol
, "prpl-oscar")) {
4950 for (i
= 0; act
[i
] != '\0'; i
++)
4951 if (!isdigit(act
[i
]))
4958 void purple_restore_default_signal_handlers(void)
4961 #ifdef HAVE_SIGNAL_H
4962 signal(SIGHUP
, SIG_DFL
); /* 1: terminal line hangup */
4963 signal(SIGINT
, SIG_DFL
); /* 2: interrupt program */
4964 signal(SIGQUIT
, SIG_DFL
); /* 3: quit program */
4965 signal(SIGILL
, SIG_DFL
); /* 4: illegal instruction (not reset when caught) */
4966 signal(SIGTRAP
, SIG_DFL
); /* 5: trace trap (not reset when caught) */
4967 signal(SIGABRT
, SIG_DFL
); /* 6: abort program */
4970 signal(SIGPOLL
, SIG_DFL
); /* 7: pollable event (POSIX) */
4971 #endif /* SIGPOLL */
4974 signal(SIGEMT
, SIG_DFL
); /* 7: EMT instruction (Non-POSIX) */
4977 signal(SIGFPE
, SIG_DFL
); /* 8: floating point exception */
4978 signal(SIGBUS
, SIG_DFL
); /* 10: bus error */
4979 signal(SIGSEGV
, SIG_DFL
); /* 11: segmentation violation */
4980 signal(SIGSYS
, SIG_DFL
); /* 12: bad argument to system call */
4981 signal(SIGPIPE
, SIG_DFL
); /* 13: write on a pipe with no reader */
4982 signal(SIGALRM
, SIG_DFL
); /* 14: real-time timer expired */
4983 signal(SIGTERM
, SIG_DFL
); /* 15: software termination signal */
4984 signal(SIGCHLD
, SIG_DFL
); /* 20: child status has changed */
4985 signal(SIGXCPU
, SIG_DFL
); /* 24: exceeded CPU time limit */
4986 signal(SIGXFSZ
, SIG_DFL
); /* 25: exceeded file size limit */
4987 #endif /* HAVE_SIGNAL_H */
4988 #endif /* !_WIN32 */
4992 set_status_with_attrs(PurpleStatus
*status
, ...)
4995 va_start(args
, status
);
4996 purple_status_set_active_with_attrs(status
, TRUE
, args
);
5000 void purple_util_set_current_song(const char *title
, const char *artist
, const char *album
)
5002 GList
*list
= purple_accounts_get_all();
5003 for (; list
; list
= list
->next
) {
5004 PurplePresence
*presence
;
5006 PurpleAccount
*account
= list
->data
;
5007 if (!purple_account_get_enabled(account
, purple_core_get_ui()))
5010 presence
= purple_account_get_presence(account
);
5011 tune
= purple_presence_get_status(presence
, "tune");
5015 set_status_with_attrs(tune
,
5016 PURPLE_TUNE_TITLE
, title
,
5017 PURPLE_TUNE_ARTIST
, artist
,
5018 PURPLE_TUNE_ALBUM
, album
,
5021 purple_status_set_active(tune
, FALSE
);
5026 char * purple_util_format_song_info(const char *title
, const char *artist
, const char *album
, gpointer unused
)
5031 if (!title
|| !*title
)
5034 esc
= g_markup_escape_text(title
, -1);
5035 string
= g_string_new("");
5036 g_string_append_printf(string
, "%s", esc
);
5039 if (artist
&& *artist
) {
5040 esc
= g_markup_escape_text(artist
, -1);
5041 g_string_append_printf(string
, _(" - %s"), esc
);
5045 if (album
&& *album
) {
5046 esc
= g_markup_escape_text(album
, -1);
5047 g_string_append_printf(string
, _(" (%s)"), esc
);
5051 return g_string_free(string
, FALSE
);
5055 purple_get_host_name(void)
5057 return g_get_host_name();
5061 purple_uuid_random(void)
5065 tmp
= g_random_int();
5066 a
= 0x4000 | (tmp
& 0xFFF); /* 0x4000 to 0x4FFF */
5068 b
= ((1 << 3) << 12) | (tmp
& 0x3FFF); /* 0x8000 to 0xBFFF */
5070 tmp
= g_random_int();
5072 return g_strdup_printf("%08x-%04x-%04x-%04x-%04x%08x",
5077 (tmp
>> 16) & 0xFFFF, g_random_int());