Mercurial > pidgin
annotate libgaim/util.c @ 14837:118fd0dc5b6e
[gaim-migrate @ 17606]
Add a "handle" parameter to gaim_proxy_connect(). It seemed like
people thought this was a good idea. You can still cancel
each gaim_proxy_connect() individually, if needed. I passed in
NULL for the handle in most places. It might be better to pass
in the gc in more places, but these changes do no harm, and they
should help some Yahoo! things, and I wanted to get the API change in.
committer: Tailor Script <tailor@pidgin.im>
author | Mark Doliner <mark@kingant.net> |
---|---|
date | Sat, 28 Oct 2006 20:04:03 +0000 |
parents | 2c1781ea074c |
children | 14b132352449 |
rev | line source |
---|---|
14192 | 1 /* |
2 * @file util.h Utility Functions | |
3 * @ingroup core | |
4 * | |
5 * Gaim is the legal property of its developers, whose names are too numerous | |
6 * to list here. Please refer to the COPYRIGHT file distributed with this | |
7 * source distribution. | |
8 * | |
9 * This program is free software; you can redistribute it and/or modify | |
10 * it under the terms of the GNU General Public License as published by | |
11 * the Free Software Foundation; either version 2 of the License, or | |
12 * (at your option) any later version. | |
13 * | |
14 * This program is distributed in the hope that it will be useful, | |
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 * GNU General Public License for more details. | |
18 * | |
19 * You should have received a copy of the GNU General Public License | |
20 * along with this program; if not, write to the Free Software | |
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
22 */ | |
23 #include "internal.h" | |
24 | |
25 #include "conversation.h" | |
26 #include "debug.h" | |
27 #include "notify.h" | |
28 #include "prpl.h" | |
29 #include "prefs.h" | |
30 #include "util.h" | |
31 | |
14354 | 32 struct _GaimUtilFetchUrlData |
14192 | 33 { |
14354 | 34 GaimUtilFetchUrlCallback callback; |
14192 | 35 void *user_data; |
36 | |
37 struct | |
38 { | |
39 char *user; | |
40 char *passwd; | |
41 char *address; | |
42 int port; | |
43 char *page; | |
44 | |
45 } website; | |
46 | |
47 char *url; | |
48 gboolean full; | |
49 char *user_agent; | |
50 gboolean http11; | |
51 char *request; | |
52 gsize request_written; | |
53 gboolean include_headers; | |
54 | |
14354 | 55 GaimProxyConnectData *connect_data; |
56 int fd; | |
57 guint inpa; | |
14192 | 58 |
59 gboolean got_headers; | |
60 gboolean has_explicit_data_len; | |
61 char *webdata; | |
62 unsigned long len; | |
63 unsigned long data_len; | |
14354 | 64 }; |
14192 | 65 |
66 static char custom_home_dir[MAXPATHLEN]; | |
67 static char home_dir[MAXPATHLEN]; | |
68 | |
69 GaimMenuAction * | |
70 gaim_menu_action_new(const char *label, GaimCallback callback, gpointer data, | |
71 GList *children) | |
72 { | |
73 GaimMenuAction *act = g_new0(GaimMenuAction, 1); | |
74 act->label = g_strdup(label); | |
75 act->callback = callback; | |
76 act->data = data; | |
77 act->children = children; | |
78 return act; | |
79 } | |
80 | |
81 void | |
82 gaim_menu_action_free(GaimMenuAction *act) | |
83 { | |
84 g_return_if_fail(act != NULL); | |
85 | |
86 g_free(act->label); | |
87 g_free(act); | |
88 } | |
89 | |
90 /************************************************************************** | |
91 * Base16 Functions | |
92 **************************************************************************/ | |
93 gchar * | |
94 gaim_base16_encode(const guchar *data, gsize len) | |
95 { | |
96 int i; | |
97 gchar *ascii = NULL; | |
98 | |
99 g_return_val_if_fail(data != NULL, NULL); | |
100 g_return_val_if_fail(len > 0, NULL); | |
101 | |
102 ascii = g_malloc(len * 2 + 1); | |
103 | |
104 for (i = 0; i < len; i++) | |
105 snprintf(&ascii[i * 2], 3, "%02hhx", data[i]); | |
106 | |
107 return ascii; | |
108 } | |
109 | |
110 guchar * | |
111 gaim_base16_decode(const char *str, gsize *ret_len) | |
112 { | |
113 int len, i, accumulator = 0; | |
114 guchar *data; | |
115 | |
116 g_return_val_if_fail(str != NULL, NULL); | |
117 | |
118 len = strlen(str); | |
119 | |
120 g_return_val_if_fail(strlen(str) > 0, 0); | |
121 g_return_val_if_fail(len % 2 > 0, 0); | |
122 | |
123 data = g_malloc(len / 2); | |
124 | |
125 for (i = 0; i < len; i++) | |
126 { | |
127 if ((i % 2) == 0) | |
128 accumulator = 0; | |
129 else | |
130 accumulator <<= 4; | |
131 | |
132 if (isdigit(str[i])) | |
133 accumulator |= str[i] - 48; | |
134 else | |
135 { | |
136 switch(str[i]) | |
137 { | |
138 case 'a': case 'A': accumulator |= 10; break; | |
139 case 'b': case 'B': accumulator |= 11; break; | |
140 case 'c': case 'C': accumulator |= 12; break; | |
141 case 'd': case 'D': accumulator |= 13; break; | |
142 case 'e': case 'E': accumulator |= 14; break; | |
143 case 'f': case 'F': accumulator |= 15; break; | |
144 } | |
145 } | |
146 | |
147 if (i % 2) | |
148 data[(i - 1) / 2] = accumulator; | |
149 } | |
150 | |
151 if (ret_len != NULL) | |
152 *ret_len = len / 2; | |
153 | |
154 return data; | |
155 } | |
156 | |
157 /************************************************************************** | |
158 * Base64 Functions | |
159 **************************************************************************/ | |
160 static const char alphabet[] = | |
161 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | |
162 "0123456789+/"; | |
163 | |
164 static const char xdigits[] = | |
165 "0123456789abcdef"; | |
166 | |
167 gchar * | |
168 gaim_base64_encode(const guchar *data, gsize len) | |
169 { | |
170 char *out, *rv; | |
171 | |
172 g_return_val_if_fail(data != NULL, NULL); | |
173 g_return_val_if_fail(len > 0, NULL); | |
174 | |
175 rv = out = g_malloc(((len/3)+1)*4 + 1); | |
176 | |
177 for (; len >= 3; len -= 3) | |
178 { | |
179 *out++ = alphabet[data[0] >> 2]; | |
180 *out++ = alphabet[((data[0] << 4) & 0x30) | (data[1] >> 4)]; | |
181 *out++ = alphabet[((data[1] << 2) & 0x3c) | (data[2] >> 6)]; | |
182 *out++ = alphabet[data[2] & 0x3f]; | |
183 data += 3; | |
184 } | |
185 | |
186 if (len > 0) | |
187 { | |
188 unsigned char fragment; | |
189 | |
190 *out++ = alphabet[data[0] >> 2]; | |
191 fragment = (data[0] << 4) & 0x30; | |
192 | |
193 if (len > 1) | |
194 fragment |= data[1] >> 4; | |
195 | |
196 *out++ = alphabet[fragment]; | |
197 *out++ = (len < 2) ? '=' : alphabet[(data[1] << 2) & 0x3c]; | |
198 *out++ = '='; | |
199 } | |
200 | |
201 *out = '\0'; | |
202 | |
203 return rv; | |
204 } | |
205 | |
206 guchar * | |
207 gaim_base64_decode(const char *str, gsize *ret_len) | |
208 { | |
209 guchar *out = NULL; | |
210 char tmp = 0; | |
211 const char *c; | |
212 gint32 tmp2 = 0; | |
213 int len = 0, n = 0; | |
214 | |
215 g_return_val_if_fail(str != NULL, NULL); | |
216 | |
217 c = str; | |
218 | |
219 while (*c) { | |
220 if (*c >= 'A' && *c <= 'Z') { | |
221 tmp = *c - 'A'; | |
222 } else if (*c >= 'a' && *c <= 'z') { | |
223 tmp = 26 + (*c - 'a'); | |
224 } else if (*c >= '0' && *c <= 57) { | |
225 tmp = 52 + (*c - '0'); | |
226 } else if (*c == '+') { | |
227 tmp = 62; | |
228 } else if (*c == '/') { | |
229 tmp = 63; | |
230 } else if (*c == '\r' || *c == '\n') { | |
231 c++; | |
232 continue; | |
233 } else if (*c == '=') { | |
234 if (n == 3) { | |
235 out = g_realloc(out, len + 2); | |
236 out[len] = (guchar)(tmp2 >> 10) & 0xff; | |
237 len++; | |
238 out[len] = (guchar)(tmp2 >> 2) & 0xff; | |
239 len++; | |
240 } else if (n == 2) { | |
241 out = g_realloc(out, len + 1); | |
242 out[len] = (guchar)(tmp2 >> 4) & 0xff; | |
243 len++; | |
244 } | |
245 break; | |
246 } | |
247 tmp2 = ((tmp2 << 6) | (tmp & 0xff)); | |
248 n++; | |
249 if (n == 4) { | |
250 out = g_realloc(out, len + 3); | |
251 out[len] = (guchar)((tmp2 >> 16) & 0xff); | |
252 len++; | |
253 out[len] = (guchar)((tmp2 >> 8) & 0xff); | |
254 len++; | |
255 out[len] = (guchar)(tmp2 & 0xff); | |
256 len++; | |
257 tmp2 = 0; | |
258 n = 0; | |
259 } | |
260 c++; | |
261 } | |
262 | |
263 out = g_realloc(out, len + 1); | |
264 out[len] = 0; | |
265 | |
266 if (ret_len != NULL) | |
267 *ret_len = len; | |
268 | |
269 return out; | |
270 } | |
271 | |
272 /************************************************************************** | |
273 * Quoted Printable Functions (see RFC 2045). | |
274 **************************************************************************/ | |
275 guchar * | |
276 gaim_quotedp_decode(const char *str, gsize *ret_len) | |
277 { | |
278 char *n, *new; | |
279 const char *end, *p; | |
280 | |
281 n = new = g_malloc(strlen (str) + 1); | |
282 end = str + strlen(str); | |
283 | |
284 for (p = str; p < end; p++, n++) { | |
285 if (*p == '=') { | |
286 if (p[1] == '\r' && p[2] == '\n') { /* 5.1 #5 */ | |
287 n -= 1; | |
288 p += 2; | |
289 } else if (p[1] == '\n') { /* fuzzy case for 5.1 #5 */ | |
290 n -= 1; | |
291 p += 1; | |
292 } else if (p[1] && p[2]) { | |
293 char *nibble1 = strchr(xdigits, tolower(p[1])); | |
294 char *nibble2 = strchr(xdigits, tolower(p[2])); | |
295 if (nibble1 && nibble2) { /* 5.1 #1 */ | |
296 *n = ((nibble1 - xdigits) << 4) | (nibble2 - xdigits); | |
297 p += 2; | |
298 } else { /* This should never happen */ | |
299 *n = *p; | |
300 } | |
301 } else { /* This should never happen */ | |
302 *n = *p; | |
303 } | |
304 } | |
305 else if (*p == '_') | |
306 *n = ' '; | |
307 else | |
308 *n = *p; | |
309 } | |
310 | |
311 *n = '\0'; | |
312 | |
313 if (ret_len != NULL) | |
314 *ret_len = n - new; | |
315 | |
316 /* Resize to take less space */ | |
317 /* new = realloc(new, n - new); */ | |
318 | |
319 return (guchar *)new; | |
320 } | |
321 | |
322 /************************************************************************** | |
323 * MIME Functions | |
324 **************************************************************************/ | |
325 char * | |
326 gaim_mime_decode_field(const char *str) | |
327 { | |
328 /* | |
329 * This is wing's version, partially based on revo/shx's version | |
330 * See RFC2047 [which apparently obsoletes RFC1342] | |
331 */ | |
332 typedef enum { | |
333 state_start, state_equal1, state_question1, | |
334 state_charset, state_question2, | |
335 state_encoding, state_question3, | |
336 state_encoded_text, state_question4, state_equal2 = state_start | |
337 } encoded_word_state_t; | |
338 encoded_word_state_t state = state_start; | |
339 const char *cur, *mark; | |
340 const char *charset0 = NULL, *encoding0 = NULL, *encoded_text0 = NULL; | |
341 char *n, *new; | |
342 | |
343 /* token can be any CHAR (supposedly ISO8859-1/ISO2022), not just ASCII */ | |
344 #define token_char_p(c) \ | |
345 (c != ' ' && !iscntrl(c) && !strchr("()<>@,;:\"/[]?.=", c)) | |
346 | |
347 /* But encoded-text must be ASCII; alas, isascii() may not exist */ | |
348 #define encoded_text_char_p(c) \ | |
349 ((c & 0x80) == 0 && c != '?' && c != ' ' && isgraph(c)) | |
350 | |
351 #define RECOVER_MARKED_TEXT strncpy(n, mark, cur - mark + 1); \ | |
352 n += cur - mark + 1 | |
353 | |
354 g_return_val_if_fail(str != NULL, NULL); | |
355 | |
356 /* NOTE: Assuming that we need just strlen(str)+1 *may* be wrong. | |
357 * It would be wrong if one byte (in some unknown encoding) could | |
358 * expand to >=4 bytes of UTF-8; I don't know if there are such things. | |
359 */ | |
360 n = new = g_malloc(strlen(str) + 1); | |
361 | |
362 /* Here we will be looking for encoded words and if they seem to be | |
363 * valid then decode them. | |
364 * They are of this form: =?charset?encoding?text?= | |
365 */ | |
366 | |
367 for (cur = str, mark = NULL; *cur; cur += 1) { | |
368 switch (state) { | |
369 case state_equal1: | |
370 if (*cur == '?') { | |
371 state = state_question1; | |
372 } else { | |
373 RECOVER_MARKED_TEXT; | |
374 state = state_start; | |
375 } | |
376 break; | |
377 case state_question1: | |
378 if (token_char_p(*cur)) { | |
379 charset0 = cur; | |
380 state = state_charset; | |
381 } else { /* This should never happen */ | |
382 RECOVER_MARKED_TEXT; | |
383 state = state_start; | |
384 } | |
385 break; | |
386 case state_charset: | |
387 if (*cur == '?') { | |
388 state = state_question2; | |
389 } else if (!token_char_p(*cur)) { /* This should never happen */ | |
390 RECOVER_MARKED_TEXT; | |
391 state = state_start; | |
392 } | |
393 break; | |
394 case state_question2: | |
395 if (token_char_p(*cur)) { | |
396 encoding0 = cur; | |
397 state = state_encoding; | |
398 } else { /* This should never happen */ | |
399 RECOVER_MARKED_TEXT; | |
400 state = state_start; | |
401 } | |
402 break; | |
403 case state_encoding: | |
404 if (*cur == '?') { | |
405 state = state_question3; | |
406 } else if (!token_char_p(*cur)) { /* This should never happen */ | |
407 RECOVER_MARKED_TEXT; | |
408 state = state_start; | |
409 } | |
410 break; | |
411 case state_question3: | |
412 if (encoded_text_char_p(*cur)) { | |
413 encoded_text0 = cur; | |
414 state = state_encoded_text; | |
415 } else if (*cur == '?') { /* empty string */ | |
416 encoded_text0 = cur; | |
417 state = state_question4; | |
418 } else { /* This should never happen */ | |
419 RECOVER_MARKED_TEXT; | |
420 state = state_start; | |
421 } | |
422 break; | |
423 case state_encoded_text: | |
424 if (*cur == '?') { | |
425 state = state_question4; | |
426 } else if (!encoded_text_char_p(*cur)) { | |
427 RECOVER_MARKED_TEXT; | |
428 state = state_start; | |
429 } | |
430 break; | |
431 case state_question4: | |
432 if (*cur == '=') { /* Got the whole encoded-word */ | |
433 char *charset = g_strndup(charset0, encoding0 - charset0 - 1); | |
434 char *encoding = g_strndup(encoding0, encoded_text0 - encoding0 - 1); | |
435 char *encoded_text = g_strndup(encoded_text0, cur - encoded_text0 - 1); | |
436 guchar *decoded = NULL; | |
437 gsize dec_len; | |
438 if (g_ascii_strcasecmp(encoding, "Q") == 0) | |
439 decoded = gaim_quotedp_decode(encoded_text, &dec_len); | |
440 else if (g_ascii_strcasecmp(encoding, "B") == 0) | |
441 decoded = gaim_base64_decode(encoded_text, &dec_len); | |
442 else | |
443 decoded = NULL; | |
444 if (decoded) { | |
445 gsize len; | |
446 char *converted = g_convert((const gchar *)decoded, dec_len, "utf-8", charset, NULL, &len, NULL); | |
447 | |
448 if (converted) { | |
449 n = strncpy(n, converted, len) + len; | |
450 g_free(converted); | |
451 } | |
452 g_free(decoded); | |
453 } | |
454 g_free(charset); | |
455 g_free(encoding); | |
456 g_free(encoded_text); | |
457 state = state_equal2; /* Restart the FSM */ | |
458 } else { /* This should never happen */ | |
459 RECOVER_MARKED_TEXT; | |
460 state = state_start; | |
461 } | |
462 break; | |
463 default: | |
464 if (*cur == '=') { | |
465 mark = cur; | |
466 state = state_equal1; | |
467 } else { | |
468 /* Some unencoded text. */ | |
469 *n = *cur; | |
470 n += 1; | |
471 } | |
472 break; | |
473 } /* switch */ | |
474 } /* for */ | |
475 | |
476 if (state != state_start) { | |
477 RECOVER_MARKED_TEXT; | |
478 } | |
479 *n = '\0'; | |
480 | |
481 return new; | |
482 } | |
483 | |
484 | |
485 /************************************************************************** | |
486 * Date/Time Functions | |
487 **************************************************************************/ | |
488 | |
489 #ifdef _WIN32 | |
490 static long win32_get_tz_offset() { | |
491 TIME_ZONE_INFORMATION tzi; | |
492 DWORD ret; | |
493 long off = -1; | |
494 | |
495 if ((ret = GetTimeZoneInformation(&tzi)) != TIME_ZONE_ID_INVALID) | |
496 { | |
497 off = -(tzi.Bias * 60); | |
498 if (ret == TIME_ZONE_ID_DAYLIGHT) | |
499 off -= tzi.DaylightBias * 60; | |
500 } | |
501 | |
502 return off; | |
503 } | |
504 #endif | |
505 | |
506 #ifndef HAVE_STRFTIME_Z_FORMAT | |
507 static const char *get_tmoff(const struct tm *tm) | |
508 { | |
509 static char buf[6]; | |
510 long off; | |
511 gint8 min; | |
512 gint8 hrs; | |
513 struct tm new_tm = *tm; | |
514 | |
515 mktime(&new_tm); | |
516 | |
517 if (new_tm.tm_isdst < 0) | |
518 g_return_val_if_reached(""); | |
519 | |
520 #ifdef _WIN32 | |
521 if ((off = win32_get_tz_offset()) == -1) | |
522 return ""; | |
523 #else | |
524 # ifdef HAVE_TM_GMTOFF | |
525 off = new_tm.tm_gmtoff; | |
526 # else | |
527 # ifdef HAVE_TIMEZONE | |
528 tzset(); | |
529 off = -timezone; | |
530 # endif /* HAVE_TIMEZONE */ | |
531 # endif /* !HAVE_TM_GMTOFF */ | |
532 #endif /* _WIN32 */ | |
533 | |
534 min = (off / 60) % 60; | |
535 hrs = ((off / 60) - min) / 60; | |
536 | |
537 if (g_snprintf(buf, sizeof(buf), "%+03d%02d", hrs, ABS(min)) > 5) | |
538 g_return_val_if_reached(""); | |
539 | |
540 return buf; | |
541 } | |
542 #endif | |
543 | |
544 /* Windows doesn't HAVE_STRFTIME_Z_FORMAT, but this seems clearer. -- rlaager */ | |
545 #if !defined(HAVE_STRFTIME_Z_FORMAT) || defined(_WIN32) | |
546 static size_t gaim_internal_strftime(char *s, size_t max, const char *format, const struct tm *tm) | |
547 { | |
548 const char *start; | |
549 const char *c; | |
550 char *fmt = NULL; | |
551 | |
552 /* Yes, this is checked in gaim_utf8_strftime(), | |
553 * but better safe than sorry. -- rlaager */ | |
554 g_return_val_if_fail(format != NULL, 0); | |
555 | |
556 /* This is fairly efficient, and it only gets | |
557 * executed on Windows or if the underlying | |
558 * system doesn't support the %z format string, | |
559 * for strftime() so I think it's good enough. | |
560 * -- rlaager */ | |
561 for (c = start = format; *c ; c++) | |
562 { | |
563 if (*c != '%') | |
564 continue; | |
565 | |
566 c++; | |
567 | |
568 #ifndef HAVE_STRFTIME_Z_FORMAT | |
569 if (*c == 'z') | |
570 { | |
571 char *tmp = g_strdup_printf("%s%.*s%s", | |
572 fmt ? fmt : "", | |
573 c - start - 1, | |
574 start, | |
575 get_tmoff(tm)); | |
576 g_free(fmt); | |
577 fmt = tmp; | |
578 start = c + 1; | |
579 } | |
580 #endif | |
581 #ifdef _WIN32 | |
582 if (*c == 'Z') | |
583 { | |
584 char *tmp = g_strdup_printf("%s%.*s%s", | |
585 fmt ? fmt : "", | |
586 c - start - 1, | |
587 start, | |
588 wgaim_get_timezone_abbreviation(tm)); | |
589 g_free(fmt); | |
590 fmt = tmp; | |
591 start = c + 1; | |
592 } | |
593 #endif | |
594 } | |
595 | |
596 if (fmt != NULL) | |
597 { | |
598 size_t ret; | |
599 | |
600 if (*start) | |
601 { | |
602 char *tmp = g_strconcat(fmt, start, NULL); | |
603 g_free(fmt); | |
604 fmt = tmp; | |
605 } | |
606 | |
607 ret = strftime(s, max, fmt, tm); | |
608 g_free(fmt); | |
609 | |
610 return ret; | |
611 } | |
612 | |
613 return strftime(s, max, format, tm); | |
614 } | |
615 #else /* HAVE_STRFTIME_Z_FORMAT && !_WIN32 */ | |
616 #define gaim_internal_strftime strftime | |
617 #endif | |
618 | |
619 const char * | |
620 gaim_utf8_strftime(const char *format, const struct tm *tm) | |
621 { | |
622 static char buf[128]; | |
623 char *locale; | |
624 GError *err = NULL; | |
625 int len; | |
626 char *utf8; | |
627 | |
628 g_return_val_if_fail(format != NULL, NULL); | |
629 | |
630 if (tm == NULL) | |
631 { | |
632 time_t now = time(NULL); | |
633 tm = localtime(&now); | |
634 } | |
635 | |
636 locale = g_locale_from_utf8(format, -1, NULL, NULL, &err); | |
637 if (err != NULL) | |
638 { | |
639 gaim_debug_error("util", "Format conversion failed in gaim_utf8_strftime(): %s", err->message); | |
640 g_error_free(err); | |
641 locale = g_strdup(format); | |
642 } | |
643 | |
644 /* A return value of 0 is either an error (in | |
645 * which case, the contents of the buffer are | |
646 * undefined) or the empty string (in which | |
647 * case, no harm is done here). */ | |
648 if ((len = gaim_internal_strftime(buf, sizeof(buf), locale, tm)) == 0) | |
649 { | |
650 g_free(locale); | |
651 return ""; | |
652 } | |
653 | |
654 g_free(locale); | |
655 | |
656 utf8 = g_locale_to_utf8(buf, len, NULL, NULL, &err); | |
657 if (err != NULL) | |
658 { | |
659 gaim_debug_error("util", "Result conversion failed in gaim_utf8_strftime(): %s", err->message); | |
660 g_error_free(err); | |
661 } | |
662 else | |
663 { | |
664 gaim_strlcpy(buf, utf8); | |
665 g_free(utf8); | |
666 } | |
667 | |
668 return buf; | |
669 } | |
670 | |
671 const char * | |
672 gaim_date_format_short(const struct tm *tm) | |
673 { | |
674 return gaim_utf8_strftime("%x", tm); | |
675 } | |
676 | |
677 const char * | |
678 gaim_date_format_long(const struct tm *tm) | |
679 { | |
680 return gaim_utf8_strftime(_("%x %X"), tm); | |
681 } | |
682 | |
683 const char * | |
684 gaim_date_format_full(const struct tm *tm) | |
685 { | |
686 return gaim_utf8_strftime("%c", tm); | |
687 } | |
688 | |
689 const char * | |
690 gaim_time_format(const struct tm *tm) | |
691 { | |
692 return gaim_utf8_strftime("%X", tm); | |
693 } | |
694 | |
695 time_t | |
696 gaim_time_build(int year, int month, int day, int hour, int min, int sec) | |
697 { | |
698 struct tm tm; | |
699 | |
700 tm.tm_year = year - 1900; | |
701 tm.tm_mon = month - 1; | |
702 tm.tm_mday = day; | |
703 tm.tm_hour = hour; | |
704 tm.tm_min = min; | |
705 tm.tm_sec = sec >= 0 ? sec : time(NULL) % 60; | |
706 | |
707 return mktime(&tm); | |
708 } | |
709 | |
710 time_t | |
711 gaim_str_to_time(const char *timestamp, gboolean utc, | |
712 struct tm *tm, long *tz_off, const char **rest) | |
713 { | |
714 time_t retval = 0; | |
715 struct tm *t; | |
716 const char *c = timestamp; | |
717 int year = 0; | |
718 long tzoff = GAIM_NO_TZ_OFF; | |
719 | |
720 time(&retval); | |
721 t = localtime(&retval); | |
722 | |
723 /* 4 digit year */ | |
724 if (sscanf(c, "%04d", &year) && year > 1900) | |
725 { | |
726 c += 4; | |
727 if (*c == '-') | |
728 c++; | |
729 t->tm_year = year - 1900; | |
730 } | |
731 | |
732 /* 2 digit month */ | |
733 if (!sscanf(c, "%02d", &t->tm_mon)) | |
734 { | |
735 if (rest != NULL && *c != '\0') | |
736 *rest = c; | |
737 return 0; | |
738 } | |
739 c += 2; | |
740 if (*c == '-' || *c == '/') | |
741 c++; | |
742 t->tm_mon -= 1; | |
743 | |
744 /* 2 digit day */ | |
745 if (!sscanf(c, "%02d", &t->tm_mday)) | |
746 { | |
747 if (rest != NULL && *c != '\0') | |
748 *rest = c; | |
749 return 0; | |
750 } | |
751 c += 2; | |
752 if (*c == '/') | |
753 { | |
754 c++; | |
755 | |
756 if (!sscanf(c, "%04d", &t->tm_year)) | |
757 { | |
758 if (rest != NULL && *c != '\0') | |
759 *rest = c; | |
760 return 0; | |
761 } | |
762 t->tm_year -= 1900; | |
763 } | |
764 else if (*c == 'T' || *c == '.') | |
765 { | |
766 c++; | |
767 /* we have more than a date, keep going */ | |
768 | |
769 /* 2 digit hour */ | |
770 if ((sscanf(c, "%02d:%02d:%02d", &t->tm_hour, &t->tm_min, &t->tm_sec) == 3 && (c = c + 8)) || | |
771 (sscanf(c, "%02d%02d%02d", &t->tm_hour, &t->tm_min, &t->tm_sec) == 3 && (c = c + 6))) | |
772 { | |
773 gboolean offset_positive = FALSE; | |
774 int tzhrs; | |
775 int tzmins; | |
776 | |
777 t->tm_isdst = -1; | |
778 | |
779 if (*c == '.' && *(c+1) >= '0' && *(c+1) <= '9') /* dealing with precision we don't care about */ | |
780 c += 4; | |
781 if (*c == '+') | |
782 offset_positive = TRUE; | |
783 if (((*c == '+' || *c == '-') && (c = c + 1)) && | |
784 ((sscanf(c, "%02d:%02d", &tzhrs, &tzmins) == 2 && (c = c + 5)) || | |
785 (sscanf(c, "%02d%02d", &tzhrs, &tzmins) == 2 && (c = c + 4)))) | |
786 { | |
787 tzoff = tzhrs*60*60 + tzmins*60; | |
788 if (offset_positive) | |
789 tzoff *= -1; | |
790 /* We don't want the C library doing DST calculations | |
791 * if we know the UTC offset already. */ | |
792 t->tm_isdst = 0; | |
793 } | |
794 | |
795 if (rest != NULL && *c != '\0') | |
796 { | |
797 if (*c == ' ') | |
798 c++; | |
799 if (*c != '\0') | |
800 *rest = c; | |
801 } | |
802 | |
803 if (tzoff != GAIM_NO_TZ_OFF || utc) | |
804 { | |
805 #if defined(_WIN32) | |
806 long sys_tzoff; | |
807 #endif | |
808 | |
809 #if defined(_WIN32) || defined(HAVE_TM_GMTOFF) || defined (HAVE_TIMEZONE) | |
810 if (tzoff == GAIM_NO_TZ_OFF) | |
811 tzoff = 0; | |
812 #endif | |
813 | |
814 #ifdef _WIN32 | |
815 if ((sys_tzoff = win32_get_tz_offset()) == -1) | |
816 tzoff = GAIM_NO_TZ_OFF; | |
817 else | |
818 tzoff += sys_tzoff; | |
819 #else | |
820 #ifdef HAVE_TM_GMTOFF | |
821 tzoff += t->tm_gmtoff; | |
822 #else | |
823 # ifdef HAVE_TIMEZONE | |
824 tzset(); /* making sure */ | |
825 tzoff -= timezone; | |
826 # endif | |
827 #endif | |
828 #endif /* _WIN32 */ | |
829 } | |
830 } | |
831 else | |
832 { | |
833 if (rest != NULL && *c != '\0') | |
834 *rest = c; | |
835 } | |
836 } | |
837 | |
838 if (tm != NULL) | |
839 { | |
840 *tm = *t; | |
841 tm->tm_isdst = -1; | |
842 mktime(tm); | |
843 } | |
844 | |
845 retval = mktime(t); | |
846 if (tzoff != GAIM_NO_TZ_OFF) | |
847 retval += tzoff; | |
848 | |
849 if (tz_off != NULL) | |
850 *tz_off = tzoff; | |
851 | |
852 return retval; | |
853 } | |
854 | |
855 /************************************************************************** | |
856 * Markup Functions | |
857 **************************************************************************/ | |
858 | |
859 /* Returns a NULL-terminated string after unescaping an entity | |
860 * (eg. &, < & etc.) starting at s. Returns NULL on failure.*/ | |
861 static const char * | |
862 detect_entity(const char *text, int *length) | |
863 { | |
864 const char *pln; | |
865 int len, pound; | |
866 | |
867 if (!text || *text != '&') | |
868 return NULL; | |
869 | |
870 #define IS_ENTITY(s) (!g_ascii_strncasecmp(text, s, (len = sizeof(s) - 1))) | |
871 | |
872 if(IS_ENTITY("&")) | |
873 pln = "&"; | |
874 else if(IS_ENTITY("<")) | |
875 pln = "<"; | |
876 else if(IS_ENTITY(">")) | |
877 pln = ">"; | |
878 else if(IS_ENTITY(" ")) | |
879 pln = " "; | |
880 else if(IS_ENTITY("©")) | |
881 pln = "\302\251"; /* or use g_unichar_to_utf8(0xa9); */ | |
882 else if(IS_ENTITY(""")) | |
883 pln = "\""; | |
884 else if(IS_ENTITY("®")) | |
885 pln = "\302\256"; /* or use g_unichar_to_utf8(0xae); */ | |
886 else if(IS_ENTITY("'")) | |
887 pln = "\'"; | |
888 else if(*(text+1) == '#' && (sscanf(text, "&#%u;", £) == 1) && | |
889 pound != 0 && *(text+3+(gint)log10(pound)) == ';') { | |
890 static char buf[7]; | |
891 int buflen = g_unichar_to_utf8((gunichar)pound, buf); | |
892 buf[buflen] = '\0'; | |
893 pln = buf; | |
894 | |
895 len = 2; | |
896 while(isdigit((gint) text[len])) len++; | |
897 if(text[len] == ';') len++; | |
898 } | |
899 else | |
900 return NULL; | |
901 | |
902 if (length) | |
903 *length = len; | |
904 return pln; | |
905 } | |
906 | |
907 gboolean | |
908 gaim_markup_find_tag(const char *needle, const char *haystack, | |
909 const char **start, const char **end, GData **attributes) | |
910 { | |
911 GData *attribs; | |
912 const char *cur = haystack; | |
913 char *name = NULL; | |
914 gboolean found = FALSE; | |
915 gboolean in_tag = FALSE; | |
916 gboolean in_attr = FALSE; | |
917 const char *in_quotes = NULL; | |
918 size_t needlelen; | |
919 | |
920 g_return_val_if_fail( needle != NULL, FALSE); | |
921 g_return_val_if_fail( *needle != '\0', FALSE); | |
922 g_return_val_if_fail( haystack != NULL, FALSE); | |
923 g_return_val_if_fail( *haystack != '\0', FALSE); | |
924 g_return_val_if_fail( start != NULL, FALSE); | |
925 g_return_val_if_fail( end != NULL, FALSE); | |
926 g_return_val_if_fail(attributes != NULL, FALSE); | |
927 | |
928 needlelen = strlen(needle); | |
929 g_datalist_init(&attribs); | |
930 | |
931 while (*cur && !found) { | |
932 if (in_tag) { | |
933 if (in_quotes) { | |
934 const char *close = cur; | |
935 | |
936 while (*close && *close != *in_quotes) | |
937 close++; | |
938 | |
939 /* if we got the close quote, store the value and carry on from * | |
940 * after it. if we ran to the end of the string, point to the NULL * | |
941 * and we're outta here */ | |
942 if (*close) { | |
943 /* only store a value if we have an attribute name */ | |
944 if (name) { | |
945 size_t len = close - cur; | |
946 char *val = g_strndup(cur, len); | |
947 | |
948 g_datalist_set_data_full(&attribs, name, val, g_free); | |
949 g_free(name); | |
950 name = NULL; | |
951 } | |
952 | |
953 in_quotes = NULL; | |
954 cur = close + 1; | |
955 } else { | |
956 cur = close; | |
957 } | |
958 } else if (in_attr) { | |
959 const char *close = cur; | |
960 | |
961 while (*close && *close != '>' && *close != '"' && | |
962 *close != '\'' && *close != ' ' && *close != '=') | |
963 close++; | |
964 | |
965 /* if we got the equals, store the name of the attribute. if we got | |
966 * the quote, save the attribute and go straight to quote mode. | |
967 * otherwise the tag closed or we reached the end of the string, | |
968 * so we can get outta here */ | |
969 switch (*close) { | |
970 case '"': | |
971 case '\'': | |
972 in_quotes = close; | |
973 case '=': | |
974 { | |
975 size_t len = close - cur; | |
976 | |
977 /* don't store a blank attribute name */ | |
978 if (len) { | |
979 g_free(name); | |
980 name = g_ascii_strdown(cur, len); | |
981 } | |
982 | |
983 in_attr = FALSE; | |
984 cur = close + 1; | |
985 break; | |
986 } | |
987 case ' ': | |
988 case '>': | |
989 in_attr = FALSE; | |
990 default: | |
991 cur = close; | |
992 break; | |
993 } | |
994 } else { | |
995 switch (*cur) { | |
996 case ' ': | |
997 /* swallow extra spaces inside tag */ | |
998 while (*cur && *cur == ' ') cur++; | |
999 in_attr = TRUE; | |
1000 break; | |
1001 case '>': | |
1002 found = TRUE; | |
1003 *end = cur; | |
1004 break; | |
1005 case '"': | |
1006 case '\'': | |
1007 in_quotes = cur; | |
1008 default: | |
1009 cur++; | |
1010 break; | |
1011 } | |
1012 } | |
1013 } else { | |
1014 /* if we hit a < followed by the name of our tag... */ | |
1015 if (*cur == '<' && !g_ascii_strncasecmp(cur + 1, needle, needlelen)) { | |
1016 *start = cur; | |
1017 cur = cur + needlelen + 1; | |
1018 | |
1019 /* if we're pointing at a space or a >, we found the right tag. if * | |
1020 * we're not, we've found a longer tag, so we need to skip to the * | |
1021 * >, but not being distracted by >s inside quotes. */ | |
1022 if (*cur == ' ' || *cur == '>') { | |
1023 in_tag = TRUE; | |
1024 } else { | |
1025 while (*cur && *cur != '"' && *cur != '\'' && *cur != '>') { | |
1026 if (*cur == '"') { | |
1027 cur++; | |
1028 while (*cur && *cur != '"') | |
1029 cur++; | |
1030 } else if (*cur == '\'') { | |
1031 cur++; | |
1032 while (*cur && *cur != '\'') | |
1033 cur++; | |
1034 } else { | |
1035 cur++; | |
1036 } | |
1037 } | |
1038 } | |
1039 } else { | |
1040 cur++; | |
1041 } | |
1042 } | |
1043 } | |
1044 | |
1045 /* clean up any attribute name from a premature termination */ | |
1046 g_free(name); | |
1047 | |
1048 if (found) { | |
1049 *attributes = attribs; | |
1050 } else { | |
1051 *start = NULL; | |
1052 *end = NULL; | |
1053 *attributes = NULL; | |
1054 } | |
1055 | |
1056 return found; | |
1057 } | |
1058 | |
1059 gboolean | |
1060 gaim_markup_extract_info_field(const char *str, int len, GString *dest, | |
1061 const char *start_token, int skip, | |
1062 const char *end_token, char check_value, | |
1063 const char *no_value_token, | |
1064 const char *display_name, gboolean is_link, | |
1065 const char *link_prefix, | |
1066 GaimInfoFieldFormatCallback format_cb) | |
1067 { | |
1068 const char *p, *q; | |
1069 | |
1070 g_return_val_if_fail(str != NULL, FALSE); | |
1071 g_return_val_if_fail(dest != NULL, FALSE); | |
1072 g_return_val_if_fail(start_token != NULL, FALSE); | |
1073 g_return_val_if_fail(end_token != NULL, FALSE); | |
1074 g_return_val_if_fail(display_name != NULL, FALSE); | |
1075 | |
1076 p = strstr(str, start_token); | |
1077 | |
1078 if (p == NULL) | |
1079 return FALSE; | |
1080 | |
1081 p += strlen(start_token) + skip; | |
1082 | |
1083 if (p >= str + len) | |
1084 return FALSE; | |
1085 | |
1086 if (check_value != '\0' && *p == check_value) | |
1087 return FALSE; | |
1088 | |
1089 q = strstr(p, end_token); | |
1090 | |
1091 /* Trim leading blanks */ | |
1092 while (*p != '\n' && g_ascii_isspace(*p)) { | |
1093 p += 1; | |
1094 } | |
1095 | |
1096 /* Trim trailing blanks */ | |
1097 while (q > p && g_ascii_isspace(*(q - 1))) { | |
1098 q -= 1; | |
1099 } | |
1100 | |
1101 /* Don't bother with null strings */ | |
1102 if (p == q) | |
1103 return FALSE; | |
1104 | |
1105 if (q != NULL && (!no_value_token || | |
1106 (no_value_token && strncmp(p, no_value_token, | |
1107 strlen(no_value_token))))) | |
1108 { | |
1109 g_string_append_printf(dest, _("<b>%s:</b> "), display_name); | |
1110 | |
1111 if (is_link) | |
1112 { | |
1113 g_string_append(dest, "<br><a href=\""); | |
1114 | |
1115 if (link_prefix) | |
1116 g_string_append(dest, link_prefix); | |
1117 | |
1118 if (format_cb != NULL) | |
1119 { | |
1120 char *reformatted = format_cb(p, q - p); | |
1121 g_string_append(dest, reformatted); | |
1122 g_free(reformatted); | |
1123 } | |
1124 else | |
1125 g_string_append_len(dest, p, q - p); | |
1126 g_string_append(dest, "\">"); | |
1127 | |
1128 if (link_prefix) | |
1129 g_string_append(dest, link_prefix); | |
1130 | |
1131 g_string_append_len(dest, p, q - p); | |
1132 g_string_append(dest, "</a>"); | |
1133 } | |
1134 else | |
1135 { | |
1136 if (format_cb != NULL) | |
1137 { | |
1138 char *reformatted = format_cb(p, q - p); | |
1139 g_string_append(dest, reformatted); | |
1140 g_free(reformatted); | |
1141 } | |
1142 else | |
1143 g_string_append_len(dest, p, q - p); | |
1144 } | |
1145 | |
1146 g_string_append(dest, "<br>\n"); | |
1147 | |
1148 return TRUE; | |
1149 } | |
1150 | |
1151 return FALSE; | |
1152 } | |
1153 | |
1154 struct gaim_parse_tag { | |
1155 char *src_tag; | |
1156 char *dest_tag; | |
1157 gboolean ignore; | |
1158 }; | |
1159 | |
1160 #define ALLOW_TAG_ALT(x, y) if(!g_ascii_strncasecmp(c, "<" x " ", strlen("<" x " "))) { \ | |
1161 const char *o = c + strlen("<" x); \ | |
1162 const char *p = NULL, *q = NULL, *r = NULL; \ | |
1163 GString *innards = g_string_new(""); \ | |
1164 while(o && *o) { \ | |
1165 if(!q && (*o == '\"' || *o == '\'') ) { \ | |
1166 q = o; \ | |
1167 } else if(q) { \ | |
1168 if(*o == *q) { \ | |
1169 char *unescaped = g_strndup(q+1, o-q-1); \ | |
1170 char *escaped = g_markup_escape_text(unescaped, -1); \ | |
1171 g_string_append_printf(innards, "%c%s%c", *q, escaped, *q); \ | |
1172 g_free(unescaped); \ | |
1173 g_free(escaped); \ | |
1174 q = NULL; \ | |
1175 } else if(*c == '\\') { \ | |
1176 o++; \ | |
1177 } \ | |
1178 } else if(*o == '<') { \ | |
1179 r = o; \ | |
1180 } else if(*o == '>') { \ | |
1181 p = o; \ | |
1182 break; \ | |
1183 } else { \ | |
1184 innards = g_string_append_c(innards, *o); \ | |
1185 } \ | |
1186 o++; \ | |
1187 } \ | |
1188 if(p && !r) { \ | |
1189 if(*(p-1) != '/') { \ | |
1190 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); \ | |
1191 pt->src_tag = x; \ | |
1192 pt->dest_tag = y; \ | |
1193 tags = g_list_prepend(tags, pt); \ | |
1194 } \ | |
1195 xhtml = g_string_append(xhtml, "<" y); \ | |
1196 c += strlen("<" x ); \ | |
1197 xhtml = g_string_append(xhtml, innards->str); \ | |
1198 xhtml = g_string_append_c(xhtml, '>'); \ | |
1199 c = p + 1; \ | |
1200 } else { \ | |
1201 xhtml = g_string_append(xhtml, "<"); \ | |
1202 plain = g_string_append_c(plain, '<'); \ | |
1203 c++; \ | |
1204 } \ | |
1205 g_string_free(innards, TRUE); \ | |
1206 continue; \ | |
1207 } \ | |
1208 if(!g_ascii_strncasecmp(c, "<" x, strlen("<" x)) && \ | |
1209 (*(c+strlen("<" x)) == '>' || \ | |
1210 !g_ascii_strncasecmp(c+strlen("<" x), "/>", 2))) { \ | |
1211 xhtml = g_string_append(xhtml, "<" y); \ | |
1212 c += strlen("<" x); \ | |
1213 if(*c != '/') { \ | |
1214 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); \ | |
1215 pt->src_tag = x; \ | |
1216 pt->dest_tag = y; \ | |
1217 tags = g_list_prepend(tags, pt); \ | |
1218 xhtml = g_string_append_c(xhtml, '>'); \ | |
1219 } else { \ | |
1220 xhtml = g_string_append(xhtml, "/>");\ | |
1221 } \ | |
1222 c = strchr(c, '>') + 1; \ | |
1223 continue; \ | |
1224 } | |
1225 #define ALLOW_TAG(x) ALLOW_TAG_ALT(x, x) | |
1226 void | |
1227 gaim_markup_html_to_xhtml(const char *html, char **xhtml_out, | |
1228 char **plain_out) | |
1229 { | |
1230 GString *xhtml = g_string_new(""); | |
1231 GString *plain = g_string_new(""); | |
1232 GList *tags = NULL, *tag; | |
1233 const char *c = html; | |
1234 | |
1235 while(c && *c) { | |
1236 if(*c == '<') { | |
1237 if(*(c+1) == '/') { /* closing tag */ | |
1238 tag = tags; | |
1239 while(tag) { | |
1240 struct gaim_parse_tag *pt = tag->data; | |
1241 if(!g_ascii_strncasecmp((c+2), pt->src_tag, strlen(pt->src_tag)) && *(c+strlen(pt->src_tag)+2) == '>') { | |
1242 c += strlen(pt->src_tag) + 3; | |
1243 break; | |
1244 } | |
1245 tag = tag->next; | |
1246 } | |
1247 if(tag) { | |
1248 while(tags) { | |
1249 struct gaim_parse_tag *pt = tags->data; | |
1250 g_string_append_printf(xhtml, "</%s>", pt->dest_tag); | |
1251 if(tags == tag) | |
1252 break; | |
1253 tags = g_list_remove(tags, pt); | |
1254 g_free(pt); | |
1255 } | |
1256 g_free(tag->data); | |
1257 tags = g_list_remove(tags, tag->data); | |
1258 } else { | |
1259 /* a closing tag we weren't expecting... | |
1260 * we'll let it slide, if it's really a tag...if it's | |
1261 * just a </ we'll escape it properly */ | |
1262 const char *end = c+2; | |
1263 while(*end && g_ascii_isalpha(*end)) | |
1264 end++; | |
1265 if(*end == '>') { | |
1266 c = end+1; | |
1267 } else { | |
1268 xhtml = g_string_append(xhtml, "<"); | |
1269 plain = g_string_append_c(plain, '<'); | |
1270 c++; | |
1271 } | |
1272 } | |
1273 } else { /* opening tag */ | |
1274 ALLOW_TAG("a"); | |
1275 ALLOW_TAG("blockquote"); | |
1276 ALLOW_TAG("cite"); | |
1277 ALLOW_TAG("div"); | |
1278 ALLOW_TAG("em"); | |
1279 ALLOW_TAG("h1"); | |
1280 ALLOW_TAG("h2"); | |
1281 ALLOW_TAG("h3"); | |
1282 ALLOW_TAG("h4"); | |
1283 ALLOW_TAG("h5"); | |
1284 ALLOW_TAG("h6"); | |
1285 /* we only allow html to start the message */ | |
1286 if(c == html) | |
1287 ALLOW_TAG("html"); | |
1288 ALLOW_TAG_ALT("i", "em"); | |
1289 ALLOW_TAG_ALT("italic", "em"); | |
1290 ALLOW_TAG("li"); | |
1291 ALLOW_TAG("ol"); | |
1292 ALLOW_TAG("p"); | |
1293 ALLOW_TAG("pre"); | |
1294 ALLOW_TAG("q"); | |
1295 ALLOW_TAG("span"); | |
1296 ALLOW_TAG("strong"); | |
1297 ALLOW_TAG("ul"); | |
1298 | |
1299 /* we skip <HR> because it's not legal in XHTML-IM. However, | |
1300 * we still want to send something sensible, so we put a | |
1301 * linebreak in its place. <BR> also needs special handling | |
1302 * because putting a </BR> to close it would just be dumb. */ | |
1303 if((!g_ascii_strncasecmp(c, "<br", 3) | |
1304 || !g_ascii_strncasecmp(c, "<hr", 3)) | |
1305 && (*(c+3) == '>' || | |
1306 !g_ascii_strncasecmp(c+3, "/>", 2) || | |
1307 !g_ascii_strncasecmp(c+3, " />", 3))) { | |
1308 c = strchr(c, '>') + 1; | |
1309 xhtml = g_string_append(xhtml, "<br/>"); | |
1310 if(*c != '\n') | |
1311 plain = g_string_append_c(plain, '\n'); | |
1312 continue; | |
1313 } | |
14333 | 1314 if(!g_ascii_strncasecmp(c, "<b>", 3) || !g_ascii_strncasecmp(c, "<bold>", strlen("<bold>"))) { |
1315 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); | |
1316 pt->src_tag = *(c+2) == '>' ? "b" : "bold"; | |
1317 pt->dest_tag = "span"; | |
1318 tags = g_list_prepend(tags, pt); | |
1319 c = strchr(c, '>') + 1; | |
1320 xhtml = g_string_append(xhtml, "<span style='font-weight: bold;'>"); | |
1321 continue; | |
1322 } | |
14192 | 1323 if(!g_ascii_strncasecmp(c, "<u>", 3) || !g_ascii_strncasecmp(c, "<underline>", strlen("<underline>"))) { |
1324 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); | |
1325 pt->src_tag = *(c+2) == '>' ? "u" : "underline"; | |
1326 pt->dest_tag = "span"; | |
1327 tags = g_list_prepend(tags, pt); | |
1328 c = strchr(c, '>') + 1; | |
1329 xhtml = g_string_append(xhtml, "<span style='text-decoration: underline;'>"); | |
1330 continue; | |
1331 } | |
1332 if(!g_ascii_strncasecmp(c, "<s>", 3) || !g_ascii_strncasecmp(c, "<strike>", strlen("<strike>"))) { | |
1333 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); | |
1334 pt->src_tag = *(c+2) == '>' ? "s" : "strike"; | |
1335 pt->dest_tag = "span"; | |
1336 tags = g_list_prepend(tags, pt); | |
1337 c = strchr(c, '>') + 1; | |
1338 xhtml = g_string_append(xhtml, "<span style='text-decoration: line-through;'>"); | |
1339 continue; | |
1340 } | |
1341 if(!g_ascii_strncasecmp(c, "<sub>", 5)) { | |
1342 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); | |
1343 pt->src_tag = "sub"; | |
1344 pt->dest_tag = "span"; | |
1345 tags = g_list_prepend(tags, pt); | |
1346 c = strchr(c, '>') + 1; | |
1347 xhtml = g_string_append(xhtml, "<span style='vertical-align:sub;'>"); | |
1348 continue; | |
1349 } | |
1350 if(!g_ascii_strncasecmp(c, "<sup>", 5)) { | |
1351 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); | |
1352 pt->src_tag = "sup"; | |
1353 pt->dest_tag = "span"; | |
1354 tags = g_list_prepend(tags, pt); | |
1355 c = strchr(c, '>') + 1; | |
1356 xhtml = g_string_append(xhtml, "<span style='vertical-align:super;'>"); | |
1357 continue; | |
1358 } | |
1359 if(!g_ascii_strncasecmp(c, "<font", 5) && (*(c+5) == '>' || *(c+5) == ' ')) { | |
1360 const char *p = c; | |
1361 GString *style = g_string_new(""); | |
1362 struct gaim_parse_tag *pt; | |
1363 while(*p && *p != '>') { | |
1364 if(!g_ascii_strncasecmp(p, "back=", strlen("back="))) { | |
1365 const char *q = p + strlen("back="); | |
1366 GString *color = g_string_new(""); | |
1367 if(*q == '\'' || *q == '\"') | |
1368 q++; | |
1369 while(*q && *q != '\"' && *q != '\'' && *q != ' ') { | |
1370 color = g_string_append_c(color, *q); | |
1371 q++; | |
1372 } | |
1373 g_string_append_printf(style, "background: %s; ", color->str); | |
1374 g_string_free(color, TRUE); | |
1375 p = q; | |
1376 } else if(!g_ascii_strncasecmp(p, "color=", strlen("color="))) { | |
1377 const char *q = p + strlen("color="); | |
1378 GString *color = g_string_new(""); | |
1379 if(*q == '\'' || *q == '\"') | |
1380 q++; | |
1381 while(*q && *q != '\"' && *q != '\'' && *q != ' ') { | |
1382 color = g_string_append_c(color, *q); | |
1383 q++; | |
1384 } | |
1385 g_string_append_printf(style, "color: %s; ", color->str); | |
1386 g_string_free(color, TRUE); | |
1387 p = q; | |
1388 } else if(!g_ascii_strncasecmp(p, "face=", strlen("face="))) { | |
1389 const char *q = p + strlen("face="); | |
1390 gboolean space_allowed = FALSE; | |
1391 GString *face = g_string_new(""); | |
1392 if(*q == '\'' || *q == '\"') { | |
1393 space_allowed = TRUE; | |
1394 q++; | |
1395 } | |
1396 while(*q && *q != '\"' && *q != '\'' && (space_allowed || *q != ' ')) { | |
1397 face = g_string_append_c(face, *q); | |
1398 q++; | |
1399 } | |
1400 g_string_append_printf(style, "font-family: %s; ", g_strstrip(face->str)); | |
1401 g_string_free(face, TRUE); | |
1402 p = q; | |
1403 } else if(!g_ascii_strncasecmp(p, "size=", strlen("size="))) { | |
1404 const char *q = p + strlen("size="); | |
1405 int sz; | |
1406 const char *size = "medium"; | |
1407 if(*q == '\'' || *q == '\"') | |
1408 q++; | |
1409 sz = atoi(q); | |
1410 switch (sz) | |
1411 { | |
1412 case 1: | |
1413 size = "xx-small"; | |
1414 break; | |
1415 case 2: | |
1416 size = "x-small"; | |
1417 break; | |
1418 case 3: | |
1419 size = "small"; | |
1420 break; | |
1421 case 4: | |
1422 size = "medium"; | |
1423 break; | |
1424 case 5: | |
1425 size = "large"; | |
1426 break; | |
1427 case 6: | |
1428 size = "x-large"; | |
1429 break; | |
1430 case 7: | |
1431 size = "xx-large"; | |
1432 break; | |
1433 default: | |
1434 break; | |
1435 } | |
1436 g_string_append_printf(style, "font-size: %s; ", size); | |
1437 p = q; | |
1438 } | |
1439 p++; | |
1440 } | |
1441 if ((c = strchr(c, '>')) != NULL) | |
1442 c++; | |
1443 else | |
1444 c = p; | |
1445 pt = g_new0(struct gaim_parse_tag, 1); | |
1446 pt->src_tag = "font"; | |
1447 pt->dest_tag = "span"; | |
1448 tags = g_list_prepend(tags, pt); | |
1449 if(style->len) | |
1450 g_string_append_printf(xhtml, "<span style='%s'>", g_strstrip(style->str)); | |
1451 else | |
1452 pt->ignore = TRUE; | |
1453 g_string_free(style, TRUE); | |
1454 continue; | |
1455 } | |
1456 if(!g_ascii_strncasecmp(c, "<body ", 6)) { | |
1457 const char *p = c; | |
1458 gboolean did_something = FALSE; | |
1459 while(*p && *p != '>') { | |
1460 if(!g_ascii_strncasecmp(p, "bgcolor=", strlen("bgcolor="))) { | |
1461 const char *q = p + strlen("bgcolor="); | |
1462 struct gaim_parse_tag *pt = g_new0(struct gaim_parse_tag, 1); | |
1463 GString *color = g_string_new(""); | |
1464 if(*q == '\'' || *q == '\"') | |
1465 q++; | |
1466 while(*q && *q != '\"' && *q != '\'' && *q != ' ') { | |
1467 color = g_string_append_c(color, *q); | |
1468 q++; | |
1469 } | |
1470 g_string_append_printf(xhtml, "<span style='background: %s;'>", g_strstrip(color->str)); | |
1471 g_string_free(color, TRUE); | |
1472 if ((c = strchr(c, '>')) != NULL) | |
1473 c++; | |
1474 else | |
1475 c = p; | |
1476 pt->src_tag = "body"; | |
1477 pt->dest_tag = "span"; | |
1478 tags = g_list_prepend(tags, pt); | |
1479 did_something = TRUE; | |
1480 break; | |
1481 } | |
1482 p++; | |
1483 } | |
1484 if(did_something) continue; | |
1485 } | |
1486 /* this has to come after the special case for bgcolor */ | |
1487 ALLOW_TAG("body"); | |
1488 if(!g_ascii_strncasecmp(c, "<!--", strlen("<!--"))) { | |
1489 char *p = strstr(c + strlen("<!--"), "-->"); | |
1490 if(p) { | |
1491 xhtml = g_string_append(xhtml, "<!--"); | |
1492 c += strlen("<!--"); | |
1493 continue; | |
1494 } | |
1495 } | |
1496 | |
1497 xhtml = g_string_append(xhtml, "<"); | |
1498 plain = g_string_append_c(plain, '<'); | |
1499 c++; | |
1500 } | |
1501 } else if(*c == '&') { | |
1502 char buf[7]; | |
1503 const char *pln; | |
1504 int len; | |
1505 | |
1506 if ((pln = detect_entity(c, &len)) == NULL) { | |
1507 len = 1; | |
1508 g_snprintf(buf, sizeof(buf), "%c", *c); | |
1509 pln = buf; | |
1510 } | |
1511 xhtml = g_string_append_len(xhtml, c, len); | |
1512 plain = g_string_append(plain, pln); | |
1513 c += len; | |
1514 } else { | |
1515 xhtml = g_string_append_c(xhtml, *c); | |
1516 plain = g_string_append_c(plain, *c); | |
1517 c++; | |
1518 } | |
1519 } | |
1520 tag = tags; | |
1521 while(tag) { | |
1522 struct gaim_parse_tag *pt = tag->data; | |
1523 if(!pt->ignore) | |
1524 g_string_append_printf(xhtml, "</%s>", pt->dest_tag); | |
1525 tag = tag->next; | |
1526 } | |
1527 g_list_free(tags); | |
1528 if(xhtml_out) | |
1529 *xhtml_out = g_strdup(xhtml->str); | |
1530 if(plain_out) | |
1531 *plain_out = g_strdup(plain->str); | |
1532 g_string_free(xhtml, TRUE); | |
1533 g_string_free(plain, TRUE); | |
1534 } | |
1535 | |
1536 /* The following are probably reasonable changes: | |
1537 * - \n should be converted to a normal space | |
1538 * - in addition to <br>, <p> and <div> etc. should also be converted into \n | |
1539 * - We want to turn </td>#whitespace<td> sequences into a single tab | |
1540 * - We want to turn <td> into a single tab (for msn profile "parsing") | |
1541 * - We want to turn </tr>#whitespace<tr> sequences into a single \n | |
1542 * - <script>...</script> and <style>...</style> should be completely removed | |
1543 */ | |
1544 | |
1545 char * | |
1546 gaim_markup_strip_html(const char *str) | |
1547 { | |
1548 int i, j, k, entlen; | |
1549 gboolean visible = TRUE; | |
1550 gboolean closing_td_p = FALSE; | |
1551 gchar *str2; | |
1552 const gchar *cdata_close_tag = NULL, *ent; | |
1553 gchar *href = NULL; | |
1554 int href_st = 0; | |
1555 | |
1556 if(!str) | |
1557 return NULL; | |
1558 | |
1559 str2 = g_strdup(str); | |
1560 | |
1561 for (i = 0, j = 0; str2[i]; i++) | |
1562 { | |
1563 if (str2[i] == '<') | |
1564 { | |
1565 if (cdata_close_tag) | |
1566 { | |
1567 /* Note: Don't even assume any other tag is a tag in CDATA */ | |
1568 if (strncasecmp(str2 + i, cdata_close_tag, | |
1569 strlen(cdata_close_tag)) == 0) | |
1570 { | |
1571 i += strlen(cdata_close_tag) - 1; | |
1572 cdata_close_tag = NULL; | |
1573 } | |
1574 continue; | |
1575 } | |
1576 else if (strncasecmp(str2 + i, "<td", 3) == 0 && closing_td_p) | |
1577 { | |
1578 str2[j++] = '\t'; | |
1579 visible = TRUE; | |
1580 } | |
1581 else if (strncasecmp(str2 + i, "</td>", 5) == 0) | |
1582 { | |
1583 closing_td_p = TRUE; | |
1584 visible = FALSE; | |
1585 } | |
1586 else | |
1587 { | |
1588 closing_td_p = FALSE; | |
1589 visible = TRUE; | |
1590 } | |
1591 | |
1592 k = i + 1; | |
1593 | |
1594 if(g_ascii_isspace(str2[k])) | |
1595 visible = TRUE; | |
1596 else if (str2[k]) | |
1597 { | |
1598 /* Scan until we end the tag either implicitly (closed start | |
1599 * tag) or explicitly, using a sloppy method (i.e., < or > | |
1600 * inside quoted attributes will screw us up) | |
1601 */ | |
1602 while (str2[k] && str2[k] != '<' && str2[k] != '>') | |
1603 { | |
1604 k++; | |
1605 } | |
1606 | |
1607 /* If we've got an <a> tag with an href, save the address | |
1608 * to print later. */ | |
1609 if (strncasecmp(str2 + i, "<a", 2) == 0 && | |
1610 g_ascii_isspace(str2[i+2])) | |
1611 { | |
1612 int st; /* start of href, inclusive [ */ | |
1613 int end; /* end of href, exclusive ) */ | |
1614 char delim = ' '; | |
1615 /* Find start of href */ | |
1616 for (st = i + 3; st < k; st++) | |
1617 { | |
1618 if (strncasecmp(str2+st, "href=", 5) == 0) | |
1619 { | |
1620 st += 5; | |
1621 if (str2[st] == '"') | |
1622 { | |
1623 delim = '"'; | |
1624 st++; | |
1625 } | |
1626 break; | |
1627 } | |
1628 } | |
1629 /* find end of address */ | |
1630 for (end = st; end < k && str2[end] != delim; end++) | |
1631 { | |
1632 /* All the work is done in the loop construct above. */ | |
1633 } | |
1634 | |
1635 /* If there's an address, save it. If there was | |
1636 * already one saved, kill it. */ | |
1637 if (st < k) | |
1638 { | |
1639 char *tmp; | |
1640 g_free(href); | |
1641 tmp = g_strndup(str2 + st, end - st); | |
1642 href = gaim_unescape_html(tmp); | |
1643 g_free(tmp); | |
1644 href_st = j; | |
1645 } | |
1646 } | |
1647 | |
1648 /* Replace </a> with an ascii representation of the | |
1649 * address the link was pointing to. */ | |
1650 else if (href != NULL && strncasecmp(str2 + i, "</a>", 4) == 0) | |
1651 { | |
1652 | |
1653 size_t hrlen = strlen(href); | |
1654 | |
1655 /* Only insert the href if it's different from the CDATA. */ | |
1656 if ((hrlen != j - href_st || | |
1657 strncmp(str2 + href_st, href, hrlen)) && | |
1658 (hrlen != j - href_st + 7 || /* 7 == strlen("http://") */ | |
1659 strncmp(str2 + href_st, href + 7, hrlen - 7))) | |
1660 { | |
1661 str2[j++] = ' '; | |
1662 str2[j++] = '('; | |
1663 g_memmove(str2 + j, href, hrlen); | |
1664 j += hrlen; | |
1665 str2[j++] = ')'; | |
1666 g_free(href); | |
1667 href = NULL; | |
1668 } | |
1669 } | |
1670 | |
1671 /* Check for tags which should be mapped to newline */ | |
1672 else if (strncasecmp(str2 + i, "<p>", 3) == 0 | |
1673 || strncasecmp(str2 + i, "<tr", 3) == 0 | |
1674 || strncasecmp(str2 + i, "<br", 3) == 0 | |
1675 || strncasecmp(str2 + i, "<li", 3) == 0 | |
1676 || strncasecmp(str2 + i, "<div", 4) == 0 | |
1677 || strncasecmp(str2 + i, "</table>", 8) == 0) | |
1678 { | |
1679 str2[j++] = '\n'; | |
1680 } | |
1681 /* Check for tags which begin CDATA and need to be closed */ | |
1682 #if 0 /* FIXME.. option is end tag optional, we can't handle this right now */ | |
1683 else if (strncasecmp(str2 + i, "<option", 7) == 0) | |
1684 { | |
1685 /* FIXME: We should not do this if the OPTION is SELECT'd */ | |
1686 cdata_close_tag = "</option>"; | |
1687 } | |
1688 #endif | |
1689 else if (strncasecmp(str2 + i, "<script", 7) == 0) | |
1690 { | |
1691 cdata_close_tag = "</script>"; | |
1692 } | |
1693 else if (strncasecmp(str2 + i, "<style", 6) == 0) | |
1694 { | |
1695 cdata_close_tag = "</style>"; | |
1696 } | |
1697 /* Update the index and continue checking after the tag */ | |
1698 i = (str2[k] == '<' || str2[k] == '\0')? k - 1: k; | |
1699 continue; | |
1700 } | |
1701 } | |
1702 else if (cdata_close_tag) | |
1703 { | |
1704 continue; | |
1705 } | |
1706 else if (!g_ascii_isspace(str2[i])) | |
1707 { | |
1708 visible = TRUE; | |
1709 } | |
1710 | |
1711 if (str2[i] == '&' && (ent = detect_entity(str2 + i, &entlen)) != NULL) | |
1712 { | |
1713 while (*ent) | |
1714 str2[j++] = *ent++; | |
1715 i += entlen - 1; | |
1716 continue; | |
1717 } | |
1718 | |
1719 if (visible) | |
1720 str2[j++] = g_ascii_isspace(str2[i])? ' ': str2[i]; | |
1721 } | |
1722 | |
1723 g_free(href); | |
1724 | |
1725 str2[j] = '\0'; | |
1726 | |
1727 return str2; | |
1728 } | |
1729 | |
1730 static gboolean | |
1731 badchar(char c) | |
1732 { | |
1733 switch (c) { | |
1734 case ' ': | |
1735 case ',': | |
1736 case '\0': | |
1737 case '\n': | |
1738 case '\r': | |
1739 case '<': | |
1740 case '>': | |
1741 case '"': | |
1742 case '\'': | |
1743 return TRUE; | |
1744 default: | |
1745 return FALSE; | |
1746 } | |
1747 } | |
1748 | |
1749 static gboolean | |
1750 badentity(const char *c) | |
1751 { | |
1752 if (!g_ascii_strncasecmp(c, "<", 4) || | |
1753 !g_ascii_strncasecmp(c, ">", 4) || | |
1754 !g_ascii_strncasecmp(c, """, 6)) { | |
1755 return TRUE; | |
1756 } | |
1757 return FALSE; | |
1758 } | |
1759 | |
1760 char * | |
1761 gaim_markup_linkify(const char *text) | |
1762 { | |
1763 const char *c, *t, *q = NULL; | |
1764 char *tmpurlbuf, *url_buf; | |
1765 gunichar g; | |
1766 gboolean inside_html = FALSE; | |
1767 int inside_paren = 0; | |
1768 GString *ret = g_string_new(""); | |
1769 /* Assumes you have a buffer able to carry at least BUF_LEN * 2 bytes */ | |
1770 | |
1771 c = text; | |
1772 while (*c) { | |
1773 | |
1774 if(*c == '(' && !inside_html) { | |
1775 inside_paren++; | |
1776 ret = g_string_append_c(ret, *c); | |
1777 c++; | |
1778 } | |
1779 | |
1780 if(inside_html) { | |
1781 if(*c == '>') { | |
1782 inside_html = FALSE; | |
1783 } else if(!q && (*c == '\"' || *c == '\'')) { | |
1784 q = c; | |
1785 } else if(q) { | |
1786 if(*c == *q) | |
1787 q = NULL; | |
1788 } | |
1789 } else if(*c == '<') { | |
1790 inside_html = TRUE; | |
1791 if (!g_ascii_strncasecmp(c, "<A", 2)) { | |
1792 while (1) { | |
1793 if (!g_ascii_strncasecmp(c, "/A>", 3)) { | |
1794 inside_html = FALSE; | |
1795 break; | |
1796 } | |
1797 ret = g_string_append_c(ret, *c); | |
1798 c++; | |
1799 if (!(*c)) | |
1800 break; | |
1801 } | |
1802 } | |
1803 } else if ((*c=='h') && (!g_ascii_strncasecmp(c, "http://", 7) || | |
1804 (!g_ascii_strncasecmp(c, "https://", 8)))) { | |
1805 t = c; | |
1806 while (1) { | |
1807 if (badchar(*t) || badentity(t)) { | |
1808 | |
1809 if (*(t) == ',' && (*(t + 1) != ' ')) { | |
1810 t++; | |
1811 continue; | |
1812 } | |
1813 | |
1814 if (*(t - 1) == '.') | |
1815 t--; | |
1816 if ((*(t - 1) == ')' && (inside_paren > 0))) { | |
1817 t--; | |
1818 } | |
1819 | |
1820 url_buf = g_strndup(c, t - c); | |
1821 tmpurlbuf = gaim_unescape_html(url_buf); | |
1822 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>", | |
1823 tmpurlbuf, url_buf); | |
1824 g_free(url_buf); | |
1825 g_free(tmpurlbuf); | |
1826 c = t; | |
1827 break; | |
1828 } | |
1829 t++; | |
1830 | |
1831 } | |
1832 } else if (!g_ascii_strncasecmp(c, "www.", 4) && (c == text || badchar(c[-1]) || badentity(c-1))) { | |
1833 if (c[4] != '.') { | |
1834 t = c; | |
1835 while (1) { | |
1836 if (badchar(*t) || badentity(t)) { | |
1837 if (t - c == 4) { | |
1838 break; | |
1839 } | |
1840 | |
1841 if (*(t) == ',' && (*(t + 1) != ' ')) { | |
1842 t++; | |
1843 continue; | |
1844 } | |
1845 | |
1846 if (*(t - 1) == '.') | |
1847 t--; | |
1848 if ((*(t - 1) == ')' && (inside_paren > 0))) { | |
1849 t--; | |
1850 } | |
1851 url_buf = g_strndup(c, t - c); | |
1852 tmpurlbuf = gaim_unescape_html(url_buf); | |
1853 g_string_append_printf(ret, | |
1854 "<A HREF=\"http://%s\">%s</A>", tmpurlbuf, | |
1855 url_buf); | |
1856 g_free(url_buf); | |
1857 g_free(tmpurlbuf); | |
1858 c = t; | |
1859 break; | |
1860 } | |
1861 t++; | |
1862 } | |
1863 } | |
14321 | 1864 } else if (!g_ascii_strncasecmp(c, "ftp://", 6) || !g_ascii_strncasecmp(c, "sftp://", 7)) { |
14192 | 1865 t = c; |
1866 while (1) { | |
1867 if (badchar(*t) || badentity(t)) { | |
1868 if (*(t - 1) == '.') | |
1869 t--; | |
1870 if ((*(t - 1) == ')' && (inside_paren > 0))) { | |
1871 t--; | |
1872 } | |
1873 url_buf = g_strndup(c, t - c); | |
1874 tmpurlbuf = gaim_unescape_html(url_buf); | |
1875 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>", | |
1876 tmpurlbuf, url_buf); | |
1877 g_free(url_buf); | |
1878 g_free(tmpurlbuf); | |
1879 c = t; | |
1880 break; | |
1881 } | |
1882 if (!t) | |
1883 break; | |
1884 t++; | |
1885 | |
1886 } | |
1887 } else if (!g_ascii_strncasecmp(c, "ftp.", 4) && (c == text || badchar(c[-1]) || badentity(c-1))) { | |
1888 if (c[4] != '.') { | |
1889 t = c; | |
1890 while (1) { | |
1891 if (badchar(*t) || badentity(t)) { | |
1892 if (t - c == 4) { | |
1893 break; | |
1894 } | |
1895 if (*(t - 1) == '.') | |
1896 t--; | |
1897 if ((*(t - 1) == ')' && (inside_paren > 0))) { | |
1898 t--; | |
1899 } | |
1900 url_buf = g_strndup(c, t - c); | |
1901 tmpurlbuf = gaim_unescape_html(url_buf); | |
1902 g_string_append_printf(ret, | |
1903 "<A HREF=\"ftp://%s\">%s</A>", tmpurlbuf, | |
1904 url_buf); | |
1905 g_free(url_buf); | |
1906 g_free(tmpurlbuf); | |
1907 c = t; | |
1908 break; | |
1909 } | |
1910 if (!t) | |
1911 break; | |
1912 t++; | |
1913 } | |
1914 } | |
1915 } else if (!g_ascii_strncasecmp(c, "mailto:", 7)) { | |
1916 t = c; | |
1917 while (1) { | |
1918 if (badchar(*t) || badentity(t)) { | |
1919 if (*(t - 1) == '.') | |
1920 t--; | |
1921 url_buf = g_strndup(c, t - c); | |
1922 tmpurlbuf = gaim_unescape_html(url_buf); | |
1923 g_string_append_printf(ret, "<A HREF=\"%s\">%s</A>", | |
1924 tmpurlbuf, url_buf); | |
1925 g_free(url_buf); | |
1926 g_free(tmpurlbuf); | |
1927 c = t; | |
1928 break; | |
1929 } | |
1930 if (!t) | |
1931 break; | |
1932 t++; | |
1933 | |
1934 } | |
1935 } else if (c != text && (*c == '@')) { | |
1936 int flag; | |
1937 GString *gurl_buf = NULL; | |
1938 const char illegal_chars[] = "!@#$%^&*()[]{}/|\\<>\":;\r\n \0"; | |
1939 | |
1940 if (strchr(illegal_chars,*(c - 1)) || strchr(illegal_chars, *(c + 1))) | |
1941 flag = 0; | |
1942 else { | |
1943 flag = 1; | |
1944 gurl_buf = g_string_new(""); | |
1945 } | |
1946 | |
1947 t = c; | |
1948 while (flag) { | |
1949 /* iterate backwards grabbing the local part of an email address */ | |
1950 g = g_utf8_get_char(t); | |
1951 if (badchar(*t) || (g >= 127) || (*t == '(') || | |
1952 ((*t == ';') && ((t > (text+2) && (!g_ascii_strncasecmp(t - 3, "<", 4) || | |
1953 !g_ascii_strncasecmp(t - 3, ">", 4))) || | |
1954 (t > (text+4) && (!g_ascii_strncasecmp(t - 5, """, 6)))))) { | |
1955 /* local part will already be part of ret, strip it out */ | |
1956 ret = g_string_truncate(ret, ret->len - (c - t)); | |
1957 ret = g_string_append_unichar(ret, g); | |
1958 break; | |
1959 } else { | |
1960 g_string_prepend_unichar(gurl_buf, g); | |
1961 t = g_utf8_find_prev_char(text, t); | |
1962 if (t < text) { | |
1963 ret = g_string_assign(ret, ""); | |
1964 break; | |
1965 } | |
1966 } | |
1967 } | |
1968 | |
1969 t = g_utf8_find_next_char(c, NULL); | |
1970 | |
1971 while (flag) { | |
1972 /* iterate forwards grabbing the domain part of an email address */ | |
1973 g = g_utf8_get_char(t); | |
1974 if (badchar(*t) || (g >= 127) || (*t == ')') || badentity(t)) { | |
1975 char *d; | |
1976 | |
1977 url_buf = g_string_free(gurl_buf, FALSE); | |
1978 | |
1979 /* strip off trailing periods */ | |
1980 if (strlen(url_buf) > 0) { | |
1981 for (d = url_buf + strlen(url_buf) - 1; *d == '.'; d--, t--) | |
1982 *d = '\0'; | |
1983 } | |
1984 | |
1985 tmpurlbuf = gaim_unescape_html(url_buf); | |
1986 if (gaim_email_is_valid(tmpurlbuf)) { | |
1987 g_string_append_printf(ret, "<A HREF=\"mailto:%s\">%s</A>", | |
1988 tmpurlbuf, url_buf); | |
1989 } else { | |
1990 g_string_append(ret, url_buf); | |
1991 } | |
1992 g_free(url_buf); | |
1993 g_free(tmpurlbuf); | |
1994 c = t; | |
1995 | |
1996 break; | |
1997 } else { | |
1998 g_string_append_unichar(gurl_buf, g); | |
1999 t = g_utf8_find_next_char(t, NULL); | |
2000 } | |
2001 } | |
2002 } | |
2003 | |
2004 if(*c == ')' && !inside_html) { | |
2005 inside_paren--; | |
2006 ret = g_string_append_c(ret, *c); | |
2007 c++; | |
2008 } | |
2009 | |
2010 if (*c == 0) | |
2011 break; | |
2012 | |
2013 ret = g_string_append_c(ret, *c); | |
2014 c++; | |
2015 | |
2016 } | |
2017 return g_string_free(ret, FALSE); | |
2018 } | |
2019 | |
2020 char * | |
2021 gaim_unescape_html(const char *html) { | |
2022 if (html != NULL) { | |
2023 const char *c = html; | |
2024 GString *ret = g_string_new(""); | |
2025 while (*c) { | |
2026 int len; | |
2027 const char *ent; | |
2028 | |
2029 if ((ent = detect_entity(c, &len)) != NULL) { | |
2030 ret = g_string_append(ret, ent); | |
2031 c += len; | |
2032 } else if (!strncmp(c, "<br>", 4)) { | |
2033 ret = g_string_append_c(ret, '\n'); | |
2034 c += 4; | |
2035 } else { | |
2036 ret = g_string_append_c(ret, *c); | |
2037 c++; | |
2038 } | |
2039 } | |
2040 return g_string_free(ret, FALSE); | |
2041 } | |
2042 | |
2043 return NULL; | |
2044 } | |
2045 | |
2046 char * | |
2047 gaim_markup_slice(const char *str, guint x, guint y) | |
2048 { | |
2049 GString *ret; | |
2050 GQueue *q; | |
2051 guint z = 0; | |
2052 gboolean appended = FALSE; | |
2053 gunichar c; | |
2054 char *tag; | |
2055 | |
2056 g_return_val_if_fail(x <= y, NULL); | |
2057 | |
2058 if (x == y) | |
2059 return g_strdup(""); | |
2060 | |
2061 ret = g_string_new(""); | |
2062 q = g_queue_new(); | |
2063 | |
2064 while (*str && (z < y)) { | |
2065 c = g_utf8_get_char(str); | |
2066 | |
2067 if (c == '<') { | |
2068 char *end = strchr(str, '>'); | |
2069 | |
2070 if (!end) { | |
2071 g_string_free(ret, TRUE); | |
2072 while ((tag = g_queue_pop_head(q))) | |
2073 g_free(tag); | |
2074 g_queue_free(q); | |
2075 return NULL; | |
2076 } | |
2077 | |
2078 if (!g_ascii_strncasecmp(str, "<img ", 5)) { | |
2079 z += strlen("[Image]"); | |
2080 } else if (!g_ascii_strncasecmp(str, "<br", 3)) { | |
2081 z += 1; | |
2082 } else if (!g_ascii_strncasecmp(str, "<hr>", 4)) { | |
2083 z += strlen("\n---\n"); | |
2084 } else if (!g_ascii_strncasecmp(str, "</", 2)) { | |
2085 /* pop stack */ | |
2086 char *tmp; | |
2087 | |
2088 tmp = g_queue_pop_head(q); | |
2089 g_free(tmp); | |
2090 /* z += 0; */ | |
2091 } else { | |
2092 /* push it unto the stack */ | |
2093 char *tmp; | |
2094 | |
2095 tmp = g_strndup(str, end - str + 1); | |
2096 g_queue_push_head(q, tmp); | |
2097 /* z += 0; */ | |
2098 } | |
2099 | |
2100 if (z >= x) { | |
2101 g_string_append_len(ret, str, end - str + 1); | |
2102 } | |
2103 | |
2104 str = end; | |
2105 } else if (c == '&') { | |
2106 char *end = strchr(str, ';'); | |
2107 if (!end) { | |
2108 g_string_free(ret, TRUE); | |
2109 while ((tag = g_queue_pop_head(q))) | |
2110 g_free(tag); | |
2111 g_queue_free(q); | |
2112 | |
2113 return NULL; | |
2114 } | |
2115 | |
2116 if (z >= x) | |
2117 g_string_append_len(ret, str, end - str + 1); | |
2118 | |
2119 z++; | |
2120 str = end; | |
2121 } else { | |
2122 if (z == x && z > 0 && !appended) { | |
2123 GList *l = q->tail; | |
2124 | |
2125 while (l) { | |
2126 tag = l->data; | |
2127 g_string_append(ret, tag); | |
2128 l = l->prev; | |
2129 } | |
2130 appended = TRUE; | |
2131 } | |
2132 | |
2133 if (z >= x) | |
2134 g_string_append_unichar(ret, c); | |
2135 z++; | |
2136 } | |
2137 | |
2138 str = g_utf8_next_char(str); | |
2139 } | |
2140 | |
2141 while ((tag = g_queue_pop_head(q))) { | |
2142 char *name; | |
2143 | |
2144 name = gaim_markup_get_tag_name(tag); | |
2145 g_string_append_printf(ret, "</%s>", name); | |
2146 g_free(name); | |
2147 g_free(tag); | |
2148 } | |
2149 | |
2150 g_queue_free(q); | |
2151 return g_string_free(ret, FALSE); | |
2152 } | |
2153 | |
2154 char * | |
2155 gaim_markup_get_tag_name(const char *tag) | |
2156 { | |
2157 int i; | |
2158 g_return_val_if_fail(tag != NULL, NULL); | |
2159 g_return_val_if_fail(*tag == '<', NULL); | |
2160 | |
2161 for (i = 1; tag[i]; i++) | |
2162 if (tag[i] == '>' || tag[i] == ' ' || tag[i] == '/') | |
2163 break; | |
2164 | |
2165 return g_strndup(tag+1, i-1); | |
2166 } | |
2167 | |
2168 /************************************************************************** | |
2169 * Path/Filename Functions | |
2170 **************************************************************************/ | |
2171 const char * | |
2172 gaim_home_dir(void) | |
2173 { | |
2174 #ifndef _WIN32 | |
2175 return g_get_home_dir(); | |
2176 #else | |
2177 return wgaim_data_dir(); | |
2178 #endif | |
2179 } | |
2180 | |
2181 /* returns a string of the form ~/.gaim, where ~ is replaced by the user's home | |
2182 * dir. Note that there is no trailing slash after .gaim. */ | |
2183 const char * | |
2184 gaim_user_dir(void) | |
2185 { | |
2186 if (custom_home_dir != NULL && strlen(custom_home_dir) > 0) { | |
2187 strcpy ((char*) &home_dir, (char*) &custom_home_dir); | |
2188 } else { | |
2189 const gchar *hd = gaim_home_dir(); | |
2190 | |
2191 if (hd) { | |
2192 g_strlcpy((char*) &home_dir, hd, sizeof(home_dir)); | |
2193 g_strlcat((char*) &home_dir, G_DIR_SEPARATOR_S ".gaim", | |
2194 sizeof(home_dir)); | |
2195 } | |
2196 } | |
2197 | |
2198 return home_dir; | |
2199 } | |
2200 | |
2201 void gaim_util_set_user_dir(const char *dir) | |
2202 { | |
2203 if (dir != NULL && strlen(dir) > 0) { | |
2204 g_strlcpy((char*) &custom_home_dir, dir, | |
2205 sizeof(custom_home_dir)); | |
2206 } | |
2207 } | |
2208 | |
2209 int gaim_build_dir (const char *path, int mode) | |
2210 { | |
2211 #if GLIB_CHECK_VERSION(2,8,0) | |
2212 return g_mkdir_with_parents(path, mode); | |
2213 #else | |
2214 char *dir, **components, delim[] = { G_DIR_SEPARATOR, '\0' }; | |
2215 int cur, len; | |
2216 | |
2217 g_return_val_if_fail(path != NULL, -1); | |
2218 | |
2219 dir = g_new0(char, strlen(path) + 1); | |
2220 components = g_strsplit(path, delim, -1); | |
2221 len = 0; | |
2222 for (cur = 0; components[cur] != NULL; cur++) { | |
2223 /* If you don't know what you're doing on both | |
2224 * win32 and *NIX, stay the hell away from this code */ | |
2225 if(cur > 1) | |
2226 dir[len++] = G_DIR_SEPARATOR; | |
2227 strcpy(dir + len, components[cur]); | |
2228 len += strlen(components[cur]); | |
2229 if(cur == 0) | |
2230 dir[len++] = G_DIR_SEPARATOR; | |
2231 | |
2232 if(g_file_test(dir, G_FILE_TEST_IS_DIR)) { | |
2233 continue; | |
2234 #ifdef _WIN32 | |
2235 /* allow us to create subdirs on UNC paths | |
2236 * (\\machinename\path\to\blah) | |
2237 * g_file_test() doesn't work on "\\machinename" */ | |
2238 } else if (cur == 2 && dir[0] == '\\' && dir[1] == '\\' | |
2239 && components[cur + 1] != NULL) { | |
2240 continue; | |
2241 #endif | |
2242 } else if(g_file_test(dir, G_FILE_TEST_EXISTS)) { | |
2243 gaim_debug_warning("build_dir", "bad path: %s\n", path); | |
2244 g_strfreev(components); | |
2245 g_free(dir); | |
2246 return -1; | |
2247 } | |
2248 | |
2249 if (g_mkdir(dir, mode) < 0) { | |
2250 gaim_debug_warning("build_dir", "mkdir: %s\n", strerror(errno)); | |
2251 g_strfreev(components); | |
2252 g_free(dir); | |
2253 return -1; | |
2254 } | |
2255 } | |
2256 | |
2257 g_strfreev(components); | |
2258 g_free(dir); | |
2259 return 0; | |
2260 #endif | |
2261 } | |
2262 | |
2263 /* | |
2264 * This function is long and beautiful, like my--um, yeah. Anyway, | |
2265 * it includes lots of error checking so as we don't overwrite | |
2266 * people's settings if there is a problem writing the new values. | |
2267 */ | |
2268 gboolean | |
2269 gaim_util_write_data_to_file(const char *filename, const char *data, size_t size) | |
2270 { | |
2271 const char *user_dir = gaim_user_dir(); | |
2272 gchar *filename_temp, *filename_full; | |
2273 FILE *file; | |
2274 size_t real_size, byteswritten; | |
2275 struct stat st; | |
2276 | |
2277 g_return_val_if_fail(user_dir != NULL, FALSE); | |
2278 | |
2279 gaim_debug_info("util", "Writing file %s to directory %s\n", | |
2280 filename, user_dir); | |
2281 | |
2282 /* Ensure the user directory exists */ | |
2283 if (!g_file_test(user_dir, G_FILE_TEST_IS_DIR)) | |
2284 { | |
2285 if (g_mkdir(user_dir, S_IRUSR | S_IWUSR | S_IXUSR) == -1) | |
2286 { | |
2287 gaim_debug_error("util", "Error creating directory %s: %s\n", | |
2288 user_dir, strerror(errno)); | |
2289 return FALSE; | |
2290 } | |
2291 } | |
2292 | |
2293 filename_full = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", user_dir, filename); | |
2294 filename_temp = g_strdup_printf("%s.save", filename_full); | |
2295 | |
2296 /* Remove an old temporary file, if one exists */ | |
2297 if (g_file_test(filename_temp, G_FILE_TEST_EXISTS)) | |
2298 { | |
2299 if (g_unlink(filename_temp) == -1) | |
2300 { | |
2301 gaim_debug_error("util", "Error removing old file %s: %s\n", | |
2302 filename_temp, strerror(errno)); | |
2303 } | |
2304 } | |
2305 | |
2306 /* Open file */ | |
2307 file = g_fopen(filename_temp, "wb"); | |
2308 if (file == NULL) | |
2309 { | |
2310 gaim_debug_error("util", "Error opening file %s for writing: %s\n", | |
2311 filename_temp, strerror(errno)); | |
2312 g_free(filename_full); | |
2313 g_free(filename_temp); | |
2314 return FALSE; | |
2315 } | |
2316 | |
2317 /* Write to file */ | |
2318 real_size = (size == -1) ? strlen(data) : size; | |
2319 byteswritten = fwrite(data, 1, real_size, file); | |
2320 | |
2321 /* Close file */ | |
2322 if (fclose(file) != 0) | |
2323 { | |
2324 gaim_debug_error("util", "Error closing file %s: %s\n", | |
2325 filename_temp, strerror(errno)); | |
2326 g_free(filename_full); | |
2327 g_free(filename_temp); | |
2328 return FALSE; | |
2329 } | |
2330 | |
2331 /* Ensure the file is the correct size */ | |
2332 if (byteswritten != real_size) | |
2333 { | |
2334 gaim_debug_error("util", "Error writing to file %s: Wrote %" G_GSIZE_FORMAT " bytes " | |
2335 "but should have written %" G_GSIZE_FORMAT "; is your disk full?\n", | |
2336 filename_temp, byteswritten, real_size); | |
2337 g_free(filename_full); | |
2338 g_free(filename_temp); | |
2339 return FALSE; | |
2340 } | |
2341 /* Use stat to be absolutely sure. */ | |
2342 if ((g_stat(filename_temp, &st) == -1) || (st.st_size != real_size)) | |
2343 { | |
2344 gaim_debug_error("util", "Error writing data to file %s: " | |
2345 "Incomplete file written; is your disk full?\n", | |
2346 filename_temp); | |
2347 g_free(filename_full); | |
2348 g_free(filename_temp); | |
2349 return FALSE; | |
2350 } | |
2351 | |
2352 #ifndef _WIN32 | |
2353 /* Set file permissions */ | |
2354 if (chmod(filename_temp, S_IRUSR | S_IWUSR) == -1) | |
2355 { | |
2356 gaim_debug_error("util", "Error setting permissions of file %s: %s\n", | |
2357 filename_temp, strerror(errno)); | |
2358 } | |
2359 #endif | |
2360 | |
2361 /* Rename to the REAL name */ | |
2362 if (g_rename(filename_temp, filename_full) == -1) | |
2363 { | |
2364 gaim_debug_error("util", "Error renaming %s to %s: %s\n", | |
2365 filename_temp, filename_full, strerror(errno)); | |
2366 } | |
2367 | |
2368 g_free(filename_full); | |
2369 g_free(filename_temp); | |
2370 | |
2371 return TRUE; | |
2372 } | |
2373 | |
2374 xmlnode * | |
2375 gaim_util_read_xml_from_file(const char *filename, const char *description) | |
2376 { | |
2377 const char *user_dir = gaim_user_dir(); | |
2378 gchar *filename_full; | |
2379 GError *error; | |
2380 gchar *contents = NULL; | |
2381 gsize length; | |
2382 xmlnode *node = NULL; | |
2383 | |
2384 g_return_val_if_fail(user_dir != NULL, NULL); | |
2385 | |
2386 gaim_debug_info("util", "Reading file %s from directory %s\n", | |
2387 filename, user_dir); | |
2388 | |
2389 filename_full = g_build_filename(user_dir, filename, NULL); | |
2390 | |
2391 if (!g_file_test(filename_full, G_FILE_TEST_EXISTS)) | |
2392 { | |
2393 gaim_debug_info("util", "File %s does not exist (this is not " | |
2394 "necessarily an error)\n", filename_full); | |
2395 g_free(filename_full); | |
2396 return NULL; | |
2397 } | |
2398 | |
2399 if (!g_file_get_contents(filename_full, &contents, &length, &error)) | |
2400 { | |
2401 gaim_debug_error("util", "Error reading file %s: %s\n", | |
2402 filename_full, error->message); | |
2403 g_error_free(error); | |
2404 } | |
2405 | |
2406 if ((contents != NULL) && (length > 0)) | |
2407 { | |
2408 node = xmlnode_from_str(contents, length); | |
2409 | |
2410 /* If we were unable to parse the file then save its contents to a backup file */ | |
2411 if (node == NULL) | |
2412 { | |
2413 gchar *filename_temp; | |
2414 | |
2415 filename_temp = g_strdup_printf("%s~", filename); | |
14586
089e65ea9813
[gaim-migrate @ 17310]
Daniel Atallah <daniel.atallah@gmail.com>
parents:
14403
diff
changeset
|
2416 gaim_debug_error("util", "Error parsing file %s. Renaming old " |
14192 | 2417 "file to %s\n", filename_full, filename_temp); |
2418 gaim_util_write_data_to_file(filename_temp, contents, length); | |
2419 g_free(filename_temp); | |
2420 } | |
2421 | |
2422 g_free(contents); | |
2423 } | |
2424 | |
2425 /* If we could not parse the file then show the user an error message */ | |
2426 if (node == NULL) | |
2427 { | |
2428 gchar *title, *msg; | |
2429 title = g_strdup_printf(_("Error Reading %s"), filename); | |
2430 msg = g_strdup_printf(_("An error was encountered reading your " | |
2431 "%s. They have not been loaded, and the old file " | |
2432 "has been renamed to %s~."), description, filename_full); | |
2433 gaim_notify_error(NULL, NULL, title, msg); | |
2434 g_free(title); | |
2435 g_free(msg); | |
2436 } | |
2437 | |
2438 g_free(filename_full); | |
2439 | |
2440 return node; | |
2441 } | |
2442 | |
2443 /* | |
2444 * Like mkstemp() but returns a file pointer, uses a pre-set template, | |
2445 * uses the semantics of tempnam() for the directory to use and allocates | |
2446 * the space for the filepath. | |
2447 * | |
2448 * Caller is responsible for closing the file and removing it when done, | |
2449 * as well as freeing the space pointed-to by "path" with g_free(). | |
2450 * | |
2451 * Returns NULL on failure and cleans up after itself if so. | |
2452 */ | |
2453 static const char *gaim_mkstemp_templ = {"gaimXXXXXX"}; | |
2454 | |
2455 FILE * | |
2456 gaim_mkstemp(char **fpath, gboolean binary) | |
2457 { | |
2458 const gchar *tmpdir; | |
2459 #ifndef _WIN32 | |
2460 int fd; | |
2461 #endif | |
2462 FILE *fp = NULL; | |
2463 | |
2464 g_return_val_if_fail(fpath != NULL, NULL); | |
2465 | |
2466 if((tmpdir = (gchar*)g_get_tmp_dir()) != NULL) { | |
2467 if((*fpath = g_strdup_printf("%s" G_DIR_SEPARATOR_S "%s", tmpdir, gaim_mkstemp_templ)) != NULL) { | |
2468 #ifdef _WIN32 | |
2469 char* result = _mktemp( *fpath ); | |
2470 if( result == NULL ) | |
2471 gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp", | |
2472 "Problem creating the template\n"); | |
2473 else | |
2474 { | |
2475 if( (fp = g_fopen( result, binary?"wb+":"w+")) == NULL ) { | |
2476 gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp", | |
2477 "Couldn't fopen() %s\n", result); | |
2478 } | |
2479 } | |
2480 #else | |
2481 if((fd = mkstemp(*fpath)) == -1) { | |
2482 gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp", | |
2483 "Couldn't make \"%s\", error: %d\n", | |
2484 *fpath, errno); | |
2485 } else { | |
2486 if((fp = fdopen(fd, "r+")) == NULL) { | |
2487 close(fd); | |
2488 gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp", | |
2489 "Couldn't fdopen(), error: %d\n", errno); | |
2490 } | |
2491 } | |
2492 #endif | |
2493 if(!fp) { | |
2494 g_free(*fpath); | |
2495 *fpath = NULL; | |
2496 } | |
2497 } | |
2498 } else { | |
2499 gaim_debug(GAIM_DEBUG_ERROR, "gaim_mkstemp", | |
2500 "g_get_tmp_dir() failed!\n"); | |
2501 } | |
2502 | |
2503 return fp; | |
2504 } | |
2505 | |
2506 gboolean | |
2507 gaim_program_is_valid(const char *program) | |
2508 { | |
2509 GError *error = NULL; | |
2510 char **argv; | |
2511 gchar *progname; | |
2512 gboolean is_valid = FALSE; | |
2513 | |
2514 g_return_val_if_fail(program != NULL, FALSE); | |
2515 g_return_val_if_fail(*program != '\0', FALSE); | |
2516 | |
2517 if (!g_shell_parse_argv(program, NULL, &argv, &error)) { | |
2518 gaim_debug(GAIM_DEBUG_ERROR, "program_is_valid", | |
2519 "Could not parse program '%s': %s\n", | |
2520 program, error->message); | |
2521 g_error_free(error); | |
2522 return FALSE; | |
2523 } | |
2524 | |
2525 if (argv == NULL) { | |
2526 return FALSE; | |
2527 } | |
2528 | |
2529 progname = g_find_program_in_path(argv[0]); | |
2530 is_valid = (progname != NULL); | |
2531 | |
2532 g_strfreev(argv); | |
2533 g_free(progname); | |
2534 | |
2535 return is_valid; | |
2536 } | |
2537 | |
2538 | |
2539 gboolean | |
2540 gaim_running_gnome(void) | |
2541 { | |
2542 #ifndef _WIN32 | |
2543 gchar *tmp = g_find_program_in_path("gnome-open"); | |
2544 | |
2545 if (tmp == NULL) | |
2546 return FALSE; | |
2547 g_free(tmp); | |
2548 | |
2549 return (g_getenv("GNOME_DESKTOP_SESSION_ID") != NULL); | |
2550 #else | |
2551 return FALSE; | |
2552 #endif | |
2553 } | |
2554 | |
2555 gboolean | |
2556 gaim_running_kde(void) | |
2557 { | |
2558 #ifndef _WIN32 | |
2559 gchar *tmp = g_find_program_in_path("kfmclient"); | |
2560 const char *session; | |
2561 | |
2562 if (tmp == NULL) | |
2563 return FALSE; | |
2564 g_free(tmp); | |
2565 | |
2566 session = g_getenv("KDE_FULL_SESSION"); | |
2567 if (session != NULL && !strcmp(session, "true")) | |
2568 return TRUE; | |
2569 | |
2570 /* If you run Gaim from Konsole under !KDE, this will provide a | |
2571 * a false positive. Since we do the GNOME checks first, this is | |
2572 * only a problem if you're running something !(KDE || GNOME) and | |
2573 * you run Gaim from Konsole. This really shouldn't be a problem. */ | |
2574 return ((g_getenv("KDEDIR") != NULL) || g_getenv("KDEDIRS") != NULL); | |
2575 #else | |
2576 return FALSE; | |
2577 #endif | |
2578 } | |
2579 | |
14681 | 2580 inline gboolean |
14646
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2581 gaim_running_osx(void) |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2582 { |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2583 #if defined(__APPLE__) |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2584 return TRUE; |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2585 #else |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2586 return FALSE; |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2587 #endif |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2588 } |
e0a93e6fa98b
[gaim-migrate @ 17392]
Evan Schoenberg <evan.s@dreskin.net>
parents:
14586
diff
changeset
|
2589 |
14192 | 2590 char * |
2591 gaim_fd_get_ip(int fd) | |
2592 { | |
2593 struct sockaddr addr; | |
2594 socklen_t namelen = sizeof(addr); | |
2595 | |
2596 g_return_val_if_fail(fd != 0, NULL); | |
2597 | |
2598 if (getsockname(fd, &addr, &namelen)) | |
2599 return NULL; | |
2600 | |
2601 return g_strdup(inet_ntoa(((struct sockaddr_in *)&addr)->sin_addr)); | |
2602 } | |
2603 | |
2604 | |
2605 /************************************************************************** | |
2606 * String Functions | |
2607 **************************************************************************/ | |
2608 const char * | |
2609 gaim_normalize(const GaimAccount *account, const char *str) | |
2610 { | |
2611 const char *ret = NULL; | |
2612 | |
2613 if (account != NULL) | |
2614 { | |
2615 GaimPlugin *prpl = gaim_find_prpl(gaim_account_get_protocol_id(account)); | |
2616 | |
2617 if (prpl != NULL) | |
2618 { | |
2619 GaimPluginProtocolInfo *prpl_info = GAIM_PLUGIN_PROTOCOL_INFO(prpl); | |
2620 | |
2621 if(prpl_info && prpl_info->normalize) | |
2622 ret = prpl_info->normalize(account, str); | |
2623 } | |
2624 } | |
2625 | |
2626 if (ret == NULL) | |
2627 { | |
2628 static char buf[BUF_LEN]; | |
2629 char *tmp; | |
2630 | |
2631 tmp = g_utf8_normalize(str, -1, G_NORMALIZE_DEFAULT); | |
2632 g_snprintf(buf, sizeof(buf), "%s", tmp); | |
2633 g_free(tmp); | |
2634 | |
2635 ret = buf; | |
2636 } | |
2637 | |
2638 return ret; | |
2639 } | |
2640 | |
2641 /* | |
2642 * You probably don't want to call this directly, it is | |
2643 * mainly for use as a PRPL callback function. See the | |
2644 * comments in util.h. | |
2645 */ | |
2646 const char * | |
2647 gaim_normalize_nocase(const GaimAccount *account, const char *str) | |
2648 { | |
2649 static char buf[BUF_LEN]; | |
2650 char *tmp1, *tmp2; | |
2651 | |
2652 g_return_val_if_fail(str != NULL, NULL); | |
2653 | |
2654 tmp1 = g_utf8_strdown(str, -1); | |
2655 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT); | |
2656 g_snprintf(buf, sizeof(buf), "%s", tmp2 ? tmp2 : ""); | |
2657 g_free(tmp2); | |
2658 g_free(tmp1); | |
2659 | |
2660 return buf; | |
2661 } | |
2662 | |
2663 gchar * | |
2664 gaim_strdup_withhtml(const gchar *src) | |
2665 { | |
2666 gulong destsize, i, j; | |
2667 gchar *dest; | |
2668 | |
2669 g_return_val_if_fail(src != NULL, NULL); | |
2670 | |
2671 /* New length is (length of src) + (number of \n's * 3) - (number of \r's) + 1 */ | |
2672 destsize = 1; | |
2673 for (i = 0; src[i] != '\0'; i++) | |
2674 { | |
2675 if (src[i] == '\n') | |
2676 destsize += 4; | |
2677 else if (src[i] != '\r') | |
2678 destsize++; | |
2679 } | |
2680 | |
2681 dest = g_malloc(destsize); | |
2682 | |
2683 /* Copy stuff, ignoring \r's, because they are dumb */ | |
2684 for (i = 0, j = 0; src[i] != '\0'; i++) { | |
2685 if (src[i] == '\n') { | |
2686 strcpy(&dest[j], "<BR>"); | |
2687 j += 4; | |
2688 } else if (src[i] != '\r') | |
2689 dest[j++] = src[i]; | |
2690 } | |
2691 | |
2692 dest[destsize-1] = '\0'; | |
2693 | |
2694 return dest; | |
2695 } | |
2696 | |
2697 gboolean | |
2698 gaim_str_has_prefix(const char *s, const char *p) | |
2699 { | |
2700 #if GLIB_CHECK_VERSION(2,2,0) | |
2701 return g_str_has_prefix(s, p); | |
2702 #else | |
2703 g_return_val_if_fail(s != NULL, FALSE); | |
2704 g_return_val_if_fail(p != NULL, FALSE); | |
2705 | |
2706 return (!strncmp(s, p, strlen(p))); | |
2707 #endif | |
2708 } | |
2709 | |
2710 gboolean | |
2711 gaim_str_has_suffix(const char *s, const char *x) | |
2712 { | |
2713 #if GLIB_CHECK_VERSION(2,2,0) | |
2714 return g_str_has_suffix(s, x); | |
2715 #else | |
2716 int off; | |
2717 | |
2718 g_return_val_if_fail(s != NULL, FALSE); | |
2719 g_return_val_if_fail(x != NULL, FALSE); | |
2720 | |
2721 off = strlen(s) - strlen(x); | |
2722 return (off >= 0 && !strcmp(s + off, x)); | |
2723 #endif | |
2724 } | |
2725 | |
2726 char * | |
2727 gaim_str_add_cr(const char *text) | |
2728 { | |
2729 char *ret = NULL; | |
2730 int count = 0, j; | |
2731 guint i; | |
2732 | |
2733 g_return_val_if_fail(text != NULL, NULL); | |
2734 | |
2735 if (text[0] == '\n') | |
2736 count++; | |
2737 for (i = 1; i < strlen(text); i++) | |
2738 if (text[i] == '\n' && text[i - 1] != '\r') | |
2739 count++; | |
2740 | |
2741 if (count == 0) | |
2742 return g_strdup(text); | |
2743 | |
2744 ret = g_malloc0(strlen(text) + count + 1); | |
2745 | |
2746 i = 0; j = 0; | |
2747 if (text[i] == '\n') | |
2748 ret[j++] = '\r'; | |
2749 ret[j++] = text[i++]; | |
2750 for (; i < strlen(text); i++) { | |
2751 if (text[i] == '\n' && text[i - 1] != '\r') | |
2752 ret[j++] = '\r'; | |
2753 ret[j++] = text[i]; | |
2754 } | |
2755 | |
2756 gaim_debug_misc("gaim_str_add_cr", "got: %s, leaving with %s\n", | |
2757 text, ret); | |
2758 | |
2759 return ret; | |
2760 } | |
2761 | |
2762 void | |
2763 gaim_str_strip_char(char *text, char thechar) | |
2764 { | |
2765 int i, j; | |
2766 | |
2767 g_return_if_fail(text != NULL); | |
2768 | |
2769 for (i = 0, j = 0; text[i]; i++) | |
2770 if (text[i] != thechar) | |
2771 text[j++] = text[i]; | |
2772 | |
2773 text[j++] = '\0'; | |
2774 } | |
2775 | |
2776 void | |
2777 gaim_util_chrreplace(char *string, char delimiter, | |
2778 char replacement) | |
2779 { | |
2780 int i = 0; | |
2781 | |
2782 g_return_if_fail(string != NULL); | |
2783 | |
2784 while (string[i] != '\0') | |
2785 { | |
2786 if (string[i] == delimiter) | |
2787 string[i] = replacement; | |
2788 i++; | |
2789 } | |
2790 } | |
2791 | |
2792 gchar * | |
2793 gaim_strreplace(const char *string, const char *delimiter, | |
2794 const char *replacement) | |
2795 { | |
2796 gchar **split; | |
2797 gchar *ret; | |
2798 | |
2799 g_return_val_if_fail(string != NULL, NULL); | |
2800 g_return_val_if_fail(delimiter != NULL, NULL); | |
2801 g_return_val_if_fail(replacement != NULL, NULL); | |
2802 | |
2803 split = g_strsplit(string, delimiter, 0); | |
2804 ret = g_strjoinv(replacement, split); | |
2805 g_strfreev(split); | |
2806 | |
2807 return ret; | |
2808 } | |
2809 | |
2810 gchar * | |
2811 gaim_strcasereplace(const char *string, const char *delimiter, | |
2812 const char *replacement) | |
2813 { | |
2814 gchar *ret; | |
2815 int length_del, length_rep, i, j; | |
2816 | |
2817 g_return_val_if_fail(string != NULL, NULL); | |
2818 g_return_val_if_fail(delimiter != NULL, NULL); | |
2819 g_return_val_if_fail(replacement != NULL, NULL); | |
2820 | |
2821 length_del = strlen(delimiter); | |
2822 length_rep = strlen(replacement); | |
2823 | |
2824 /* Count how many times the delimiter appears */ | |
2825 i = 0; /* position in the source string */ | |
2826 j = 0; /* number of occurrences of "delimiter" */ | |
2827 while (string[i] != '\0') { | |
2828 if (!strncasecmp(&string[i], delimiter, length_del)) { | |
2829 i += length_del; | |
2830 j += length_rep; | |
2831 } else { | |
2832 i++; | |
2833 j++; | |
2834 } | |
2835 } | |
2836 | |
2837 ret = g_malloc(j+1); | |
2838 | |
2839 i = 0; /* position in the source string */ | |
2840 j = 0; /* position in the destination string */ | |
2841 while (string[i] != '\0') { | |
2842 if (!strncasecmp(&string[i], delimiter, length_del)) { | |
2843 strncpy(&ret[j], replacement, length_rep); | |
2844 i += length_del; | |
2845 j += length_rep; | |
2846 } else { | |
2847 ret[j] = string[i]; | |
2848 i++; | |
2849 j++; | |
2850 } | |
2851 } | |
2852 | |
2853 ret[j] = '\0'; | |
2854 | |
2855 return ret; | |
2856 } | |
2857 | |
2858 const char * | |
2859 gaim_strcasestr(const char *haystack, const char *needle) | |
2860 { | |
2861 size_t hlen, nlen; | |
2862 const char *tmp, *ret; | |
2863 | |
2864 g_return_val_if_fail(haystack != NULL, NULL); | |
2865 g_return_val_if_fail(needle != NULL, NULL); | |
2866 | |
2867 hlen = strlen(haystack); | |
2868 nlen = strlen(needle); | |
2869 tmp = haystack, | |
2870 ret = NULL; | |
2871 | |
2872 g_return_val_if_fail(hlen > 0, NULL); | |
2873 g_return_val_if_fail(nlen > 0, NULL); | |
2874 | |
2875 while (*tmp && !ret) { | |
2876 if (!g_ascii_strncasecmp(needle, tmp, nlen)) | |
2877 ret = tmp; | |
2878 else | |
2879 tmp++; | |
2880 } | |
2881 | |
2882 return ret; | |
2883 } | |
2884 | |
2885 char * | |
2886 gaim_str_size_to_units(size_t size) | |
2887 { | |
2888 static const char *size_str[4] = { "bytes", "KB", "MB", "GB" }; | |
2889 float size_mag; | |
2890 int size_index = 0; | |
2891 | |
2892 if (size == -1) { | |
2893 return g_strdup(_("Calculating...")); | |
2894 } | |
2895 else if (size == 0) { | |
2896 return g_strdup(_("Unknown.")); | |
2897 } | |
2898 else { | |
2899 size_mag = (float)size; | |
2900 | |
2901 while ((size_index < 3) && (size_mag > 1024)) { | |
2902 size_mag /= 1024; | |
2903 size_index++; | |
2904 } | |
2905 | |
2906 if (size_index == 0) { | |
2907 return g_strdup_printf("%" G_GSIZE_FORMAT " %s", size, size_str[size_index]); | |
2908 } else { | |
2909 return g_strdup_printf("%.2f %s", size_mag, size_str[size_index]); | |
2910 } | |
2911 } | |
2912 } | |
2913 | |
2914 char * | |
2915 gaim_str_seconds_to_string(guint secs) | |
2916 { | |
2917 char *ret = NULL; | |
2918 guint days, hrs, mins; | |
2919 | |
2920 if (secs < 60) | |
2921 { | |
2922 return g_strdup_printf(ngettext("%d second", "%d seconds", secs), secs); | |
2923 } | |
2924 | |
2925 days = secs / (60 * 60 * 24); | |
2926 secs = secs % (60 * 60 * 24); | |
2927 hrs = secs / (60 * 60); | |
2928 secs = secs % (60 * 60); | |
2929 mins = secs / 60; | |
2930 secs = secs % 60; | |
2931 | |
2932 if (days > 0) | |
2933 { | |
2934 ret = g_strdup_printf(ngettext("%d day", "%d days", days), days); | |
2935 } | |
2936 | |
2937 if (hrs > 0) | |
2938 { | |
2939 if (ret != NULL) | |
2940 { | |
2941 char *tmp = g_strdup_printf( | |
2942 ngettext("%s, %d hour", "%s, %d hours", hrs), | |
2943 ret, hrs); | |
2944 g_free(ret); | |
2945 ret = tmp; | |
2946 } | |
2947 else | |
2948 ret = g_strdup_printf(ngettext("%d hour", "%d hours", hrs), hrs); | |
2949 } | |
2950 | |
2951 if (mins > 0) | |
2952 { | |
2953 if (ret != NULL) | |
2954 { | |
2955 char *tmp = g_strdup_printf( | |
2956 ngettext("%s, %d minute", "%s, %d minutes", mins), | |
2957 ret, mins); | |
2958 g_free(ret); | |
2959 ret = tmp; | |
2960 } | |
2961 else | |
2962 ret = g_strdup_printf(ngettext("%d minute", "%d minutes", mins), mins); | |
2963 } | |
2964 | |
2965 return ret; | |
2966 } | |
2967 | |
2968 | |
2969 char * | |
2970 gaim_str_binary_to_ascii(const unsigned char *binary, guint len) | |
2971 { | |
2972 GString *ret; | |
2973 guint i; | |
2974 | |
2975 g_return_val_if_fail(len > 0, NULL); | |
2976 | |
2977 ret = g_string_sized_new(len); | |
2978 | |
2979 for (i = 0; i < len; i++) | |
2980 if (binary[i] < 32 || binary[i] > 126) | |
2981 g_string_append_printf(ret, "\\x%02hhx", binary[i]); | |
2982 else if (binary[i] == '\\') | |
2983 g_string_append(ret, "\\\\"); | |
2984 else | |
2985 g_string_append_c(ret, binary[i]); | |
2986 | |
2987 return g_string_free(ret, FALSE); | |
2988 } | |
2989 | |
2990 /************************************************************************** | |
2991 * URI/URL Functions | |
2992 **************************************************************************/ | |
2993 gboolean | |
2994 gaim_url_parse(const char *url, char **ret_host, int *ret_port, | |
2995 char **ret_path, char **ret_user, char **ret_passwd) | |
2996 { | |
2997 char scan_info[255]; | |
2998 char port_str[6]; | |
2999 int f; | |
3000 const char *at, *slash; | |
3001 const char *turl; | |
3002 char host[256], path[256], user[256], passwd[256]; | |
3003 int port = 0; | |
3004 /* hyphen at end includes it in control set */ | |
3005 static char addr_ctrl[] = "A-Za-z0-9.-"; | |
3006 static char port_ctrl[] = "0-9"; | |
3007 static char page_ctrl[] = "A-Za-z0-9.~_/:*!@&%%?=+^-"; | |
3008 static char user_ctrl[] = "A-Za-z0-9.~_/*!&%%?=+^-"; | |
3009 static char passwd_ctrl[] = "A-Za-z0-9.~_/*!&%%?=+^-"; | |
3010 | |
3011 g_return_val_if_fail(url != NULL, FALSE); | |
3012 | |
3013 if ((turl = strstr(url, "http://")) != NULL || | |
3014 (turl = strstr(url, "HTTP://")) != NULL) | |
3015 { | |
3016 turl += 7; | |
3017 url = turl; | |
3018 } | |
3019 | |
3020 /* parse out authentication information if supplied */ | |
3021 /* Only care about @ char BEFORE the first / */ | |
3022 at = strchr(url, '@'); | |
3023 slash = strchr(url, '/'); | |
3024 if ((at != NULL) && | |
3025 (((slash != NULL) && (strlen(at) > strlen(slash))) || | |
3026 (slash == NULL))) { | |
3027 g_snprintf(scan_info, sizeof(scan_info), | |
3028 "%%255[%s]:%%255[%s]^@", user_ctrl, passwd_ctrl); | |
3029 f = sscanf(url, scan_info, user, passwd); | |
3030 | |
3031 if (f ==1 ) { | |
3032 /* No passwd, possibly just username supplied */ | |
3033 g_snprintf(scan_info, sizeof(scan_info), | |
3034 "%%255[%s]^@", user_ctrl); | |
3035 f = sscanf(url, scan_info, user); | |
3036 *passwd = '\0'; | |
3037 } | |
3038 | |
3039 url = at+1; /* move pointer after the @ char */ | |
3040 } else { | |
3041 *user = '\0'; | |
3042 *passwd = '\0'; | |
3043 } | |
3044 | |
3045 g_snprintf(scan_info, sizeof(scan_info), | |
3046 "%%255[%s]:%%5[%s]/%%255[%s]", addr_ctrl, port_ctrl, page_ctrl); | |
3047 | |
3048 f = sscanf(url, scan_info, host, port_str, path); | |
3049 | |
3050 if (f == 1) | |
3051 { | |
3052 g_snprintf(scan_info, sizeof(scan_info), | |
3053 "%%255[%s]/%%255[%s]", | |
3054 addr_ctrl, page_ctrl); | |
3055 f = sscanf(url, scan_info, host, path); | |
3056 g_snprintf(port_str, sizeof(port_str), "80"); | |
3057 } | |
3058 | |
3059 if (f == 1) | |
3060 *path = '\0'; | |
3061 | |
3062 sscanf(port_str, "%d", &port); | |
3063 | |
3064 if (ret_host != NULL) *ret_host = g_strdup(host); | |
3065 if (ret_port != NULL) *ret_port = port; | |
3066 if (ret_path != NULL) *ret_path = g_strdup(path); | |
3067 if (ret_user != NULL) *ret_user = g_strdup(user); | |
3068 if (ret_passwd != NULL) *ret_passwd = g_strdup(passwd); | |
3069 | |
3070 return TRUE; | |
3071 } | |
3072 | |
14354 | 3073 /** |
3074 * The arguments to this function are similar to printf. | |
3075 */ | |
14192 | 3076 static void |
14354 | 3077 gaim_util_fetch_url_error(GaimUtilFetchUrlData *gfud, const char *format, ...) |
14192 | 3078 { |
14354 | 3079 gchar *error_message; |
3080 va_list args; | |
3081 | |
3082 va_start(args, format); | |
3083 error_message = g_strdup_vprintf(format, args); | |
3084 va_end(args); | |
3085 | |
3086 gfud->callback(gfud, gfud->user_data, NULL, 0, error_message); | |
3087 g_free(error_message); | |
3088 gaim_util_fetch_url_cancel(gfud); | |
14192 | 3089 } |
3090 | |
14403 | 3091 static void url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message); |
3092 | |
14192 | 3093 static gboolean |
3094 parse_redirect(const char *data, size_t data_len, gint sock, | |
14354 | 3095 GaimUtilFetchUrlData *gfud) |
14192 | 3096 { |
3097 gchar *s; | |
3098 | |
3099 if ((s = g_strstr_len(data, data_len, "Location: ")) != NULL) | |
3100 { | |
3101 gchar *new_url, *temp_url, *end; | |
3102 gboolean full; | |
3103 int len; | |
3104 | |
3105 s += strlen("Location: "); | |
3106 end = strchr(s, '\r'); | |
3107 | |
3108 /* Just in case :) */ | |
3109 if (end == NULL) | |
3110 end = strchr(s, '\n'); | |
3111 | |
3112 if (end == NULL) | |
3113 return FALSE; | |
3114 | |
3115 len = end - s; | |
3116 | |
3117 new_url = g_malloc(len + 1); | |
3118 strncpy(new_url, s, len); | |
3119 new_url[len] = '\0'; | |
3120 | |
3121 full = gfud->full; | |
3122 | |
3123 if (*new_url == '/' || g_strstr_len(new_url, len, "://") == NULL) | |
3124 { | |
3125 temp_url = new_url; | |
3126 | |
3127 new_url = g_strdup_printf("%s:%d%s", gfud->website.address, | |
3128 gfud->website.port, temp_url); | |
3129 | |
3130 g_free(temp_url); | |
3131 | |
3132 full = FALSE; | |
3133 } | |
3134 | |
14354 | 3135 gaim_debug_info("util", "Redirecting to %s\n", new_url); |
14192 | 3136 |
14403 | 3137 /* |
3138 * Try again, with this new location. This code is somewhat | |
3139 * ugly, but we need to reuse the gfud because whoever called | |
3140 * us is holding a reference to it. | |
3141 */ | |
3142 g_free(gfud->url); | |
3143 gfud->url = new_url; | |
3144 gfud->full = full; | |
3145 g_free(gfud->request); | |
3146 gfud->request = NULL; | |
3147 | |
3148 g_free(gfud->website.user); | |
3149 g_free(gfud->website.passwd); | |
3150 g_free(gfud->website.address); | |
3151 g_free(gfud->website.page); | |
3152 gaim_url_parse(new_url, &gfud->website.address, &gfud->website.port, | |
3153 &gfud->website.page, &gfud->website.user, &gfud->website.passwd); | |
3154 | |
14837 | 3155 gfud->connect_data = gaim_proxy_connect(NULL, NULL, |
14403 | 3156 gfud->website.address, gfud->website.port, |
3157 url_fetch_connect_cb, gfud); | |
3158 | |
3159 if (gfud->connect_data == NULL) | |
3160 { | |
3161 gaim_util_fetch_url_error(gfud, _("Unable to connect to %s"), | |
3162 gfud->website.address); | |
3163 } | |
14192 | 3164 |
3165 return TRUE; | |
3166 } | |
3167 | |
3168 return FALSE; | |
3169 } | |
3170 | |
3171 static size_t | |
3172 parse_content_len(const char *data, size_t data_len) | |
3173 { | |
3174 size_t content_len = 0; | |
3175 const char *p = NULL; | |
3176 | |
3177 /* This is still technically wrong, since headers are case-insensitive | |
3178 * [RFC 2616, section 4.2], though this ought to catch the normal case. | |
3179 * Note: data is _not_ nul-terminated. | |
3180 */ | |
3181 if(data_len > 16) { | |
3182 p = (strncmp(data, "Content-Length: ", 16) == 0) ? data : NULL; | |
3183 if(!p) | |
3184 p = (strncmp(data, "CONTENT-LENGTH: ", 16) == 0) | |
3185 ? data : NULL; | |
3186 if(!p) { | |
3187 p = g_strstr_len(data, data_len, "\nContent-Length: "); | |
3188 if (p) | |
3189 p++; | |
3190 } | |
3191 if(!p) { | |
3192 p = g_strstr_len(data, data_len, "\nCONTENT-LENGTH: "); | |
3193 if (p) | |
3194 p++; | |
3195 } | |
3196 | |
3197 if(p) | |
3198 p += 16; | |
3199 } | |
3200 | |
3201 /* If we can find a Content-Length header at all, try to sscanf it. | |
3202 * Response headers should end with at least \r\n, so sscanf is safe, | |
3203 * if we make sure that there is indeed a \n in our header. | |
3204 */ | |
3205 if (p && g_strstr_len(p, data_len - (p - data), "\n")) { | |
3206 sscanf(p, "%" G_GSIZE_FORMAT, &content_len); | |
14354 | 3207 gaim_debug_misc("util", "parsed %u\n", content_len); |
14192 | 3208 } |
3209 | |
3210 return content_len; | |
3211 } | |
3212 | |
3213 | |
3214 static void | |
3215 url_fetch_recv_cb(gpointer url_data, gint source, GaimInputCondition cond) | |
3216 { | |
14354 | 3217 GaimUtilFetchUrlData *gfud = url_data; |
14192 | 3218 int len; |
3219 char buf[4096]; | |
3220 char *data_cursor; | |
3221 gboolean got_eof = FALSE; | |
3222 | |
3223 while((len = read(source, buf, sizeof(buf))) > 0) { | |
14354 | 3224 /* If we've filled up our buffer, make it bigger */ |
14192 | 3225 if((gfud->len + len) >= gfud->data_len) { |
3226 while((gfud->len + len) >= gfud->data_len) | |
3227 gfud->data_len += sizeof(buf); | |
3228 | |
3229 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len); | |
3230 } | |
3231 | |
3232 data_cursor = gfud->webdata + gfud->len; | |
3233 | |
3234 gfud->len += len; | |
3235 | |
3236 memcpy(data_cursor, buf, len); | |
3237 | |
3238 gfud->webdata[gfud->len] = '\0'; | |
3239 | |
3240 if(!gfud->got_headers) { | |
3241 char *tmp; | |
3242 | |
14354 | 3243 /* See if we've reached the end of the headers yet */ |
14192 | 3244 if((tmp = strstr(gfud->webdata, "\r\n\r\n"))) { |
3245 char * new_data; | |
3246 guint header_len = (tmp + 4 - gfud->webdata); | |
3247 size_t content_len; | |
3248 | |
14354 | 3249 gaim_debug_misc("util", "Response headers: '%.*s'\n", |
14192 | 3250 header_len, gfud->webdata); |
3251 | |
3252 /* See if we can find a redirect. */ | |
3253 if(parse_redirect(gfud->webdata, header_len, source, gfud)) | |
3254 return; | |
3255 | |
3256 gfud->got_headers = TRUE; | |
3257 | |
3258 /* No redirect. See if we can find a content length. */ | |
3259 content_len = parse_content_len(gfud->webdata, header_len); | |
3260 | |
3261 if(content_len == 0) { | |
3262 /* We'll stick with an initial 8192 */ | |
3263 content_len = 8192; | |
3264 } else { | |
3265 gfud->has_explicit_data_len = TRUE; | |
3266 } | |
3267 | |
3268 | |
3269 /* If we're returning the headers too, we don't need to clean them out */ | |
3270 if(gfud->include_headers) { | |
3271 gfud->data_len = content_len + header_len; | |
3272 gfud->webdata = g_realloc(gfud->webdata, gfud->data_len); | |
3273 } else { | |
3274 size_t body_len = 0; | |
3275 | |
3276 if(gfud->len > (header_len + 1)) | |
3277 body_len = (gfud->len - header_len); | |
3278 | |
3279 content_len = MAX(content_len, body_len); | |
3280 | |
3281 new_data = g_try_malloc(content_len); | |
3282 if(new_data == NULL) { | |
14354 | 3283 gaim_debug_error("util", |
3284 "Failed to allocate %u bytes: %s\n", | |
3285 content_len, strerror(errno)); | |
3286 gaim_util_fetch_url_error(gfud, | |
3287 _("Unable to allocate enough memory to hold " | |
3288 "the contents from %s. The web server may " | |
3289 "be trying something malicious."), | |
3290 gfud->website.address); | |
14192 | 3291 |
3292 return; | |
3293 } | |
3294 | |
3295 /* We may have read part of the body when reading the headers, don't lose it */ | |
3296 if(body_len > 0) { | |
3297 tmp += 4; | |
3298 memcpy(new_data, tmp, body_len); | |
3299 } | |
3300 | |
3301 /* Out with the old... */ | |
3302 g_free(gfud->webdata); | |
3303 | |
3304 /* In with the new. */ | |
3305 gfud->len = body_len; | |
3306 gfud->data_len = content_len; | |
3307 gfud->webdata = new_data; | |
3308 } | |
3309 } | |
3310 } | |
3311 | |
3312 if(gfud->has_explicit_data_len && gfud->len >= gfud->data_len) { | |
3313 got_eof = TRUE; | |
3314 break; | |
3315 } | |
3316 } | |
3317 | |
3318 if(len <= 0) { | |
3319 if(errno == EAGAIN) { | |
3320 return; | |
3321 } else if(errno != ETIMEDOUT) { | |
3322 got_eof = TRUE; | |
3323 } else { | |
14354 | 3324 gaim_util_fetch_url_error(gfud, _("Error reading from %s: %s"), |
3325 gfud->website.address, strerror(errno)); | |
14192 | 3326 return; |
3327 } | |
3328 } | |
3329 | |
3330 if(got_eof) { | |
3331 gfud->webdata = g_realloc(gfud->webdata, gfud->len + 1); | |
3332 gfud->webdata[gfud->len] = '\0'; | |
3333 | |
14354 | 3334 gfud->callback(gfud, gfud->user_data, gfud->webdata, gfud->len, NULL); |
3335 gaim_util_fetch_url_cancel(gfud); | |
14192 | 3336 } |
3337 } | |
3338 | |
3339 static void | |
3340 url_fetch_send_cb(gpointer data, gint source, GaimInputCondition cond) | |
3341 { | |
14354 | 3342 GaimUtilFetchUrlData *gfud; |
14192 | 3343 int len, total_len; |
3344 | |
3345 gfud = data; | |
3346 | |
3347 total_len = strlen(gfud->request); | |
3348 | |
14354 | 3349 len = write(gfud->fd, gfud->request + gfud->request_written, |
14192 | 3350 total_len - gfud->request_written); |
3351 | |
14354 | 3352 if (len < 0 && errno == EAGAIN) |
14192 | 3353 return; |
14354 | 3354 else if (len < 0) { |
3355 gaim_util_fetch_url_error(gfud, _("Error writing to %s: %s"), | |
3356 gfud->website.address, strerror(errno)); | |
14192 | 3357 return; |
3358 } | |
3359 gfud->request_written += len; | |
3360 | |
14354 | 3361 if (gfud->request_written != total_len) |
14192 | 3362 return; |
3363 | |
14354 | 3364 /* We're done writing our request, now start reading the response */ |
14192 | 3365 gaim_input_remove(gfud->inpa); |
14354 | 3366 gfud->inpa = gaim_input_add(gfud->fd, GAIM_INPUT_READ, url_fetch_recv_cb, |
14192 | 3367 gfud); |
3368 } | |
3369 | |
3370 static void | |
3371 url_fetch_connect_cb(gpointer url_data, gint source, const gchar *error_message) | |
3372 { | |
14354 | 3373 GaimUtilFetchUrlData *gfud; |
14192 | 3374 |
3375 gfud = url_data; | |
14354 | 3376 gfud->connect_data = NULL; |
14192 | 3377 |
3378 if (source == -1) | |
3379 { | |
14354 | 3380 gaim_util_fetch_url_error(gfud, _("Unable to connect to %s: %s"), |
3381 gfud->website.address, error_message); | |
14192 | 3382 return; |
3383 } | |
3384 | |
14354 | 3385 gfud->fd = source; |
3386 | |
14192 | 3387 if (!gfud->request) |
3388 { | |
3389 if (gfud->user_agent) { | |
3390 /* Host header is not forbidden in HTTP/1.0 requests, and HTTP/1.1 | |
3391 * clients must know how to handle the "chunked" transfer encoding. | |
3392 * Gaim doesn't know how to handle "chunked", so should always send | |
3393 * the Host header regardless, to get around some observed problems | |
3394 */ | |
3395 gfud->request = g_strdup_printf( | |
3396 "GET %s%s HTTP/%s\r\n" | |
3397 "Connection: close\r\n" | |
3398 "User-Agent: %s\r\n" | |
3399 "Accept: */*\r\n" | |
3400 "Host: %s\r\n\r\n", | |
3401 (gfud->full ? "" : "/"), | |
3402 (gfud->full ? gfud->url : gfud->website.page), | |
3403 (gfud->http11 ? "1.1" : "1.0"), | |
3404 gfud->user_agent, gfud->website.address); | |
3405 } else { | |
3406 gfud->request = g_strdup_printf( | |
3407 "GET %s%s HTTP/%s\r\n" | |
3408 "Connection: close\r\n" | |
3409 "Accept: */*\r\n" | |
3410 "Host: %s\r\n\r\n", | |
3411 (gfud->full ? "" : "/"), | |
3412 (gfud->full ? gfud->url : gfud->website.page), | |
3413 (gfud->http11 ? "1.1" : "1.0"), | |
3414 gfud->website.address); | |
3415 } | |
3416 } | |
3417 | |
14354 | 3418 gaim_debug_misc("util", "Request: '%s'\n", gfud->request); |
14192 | 3419 |
3420 gfud->inpa = gaim_input_add(source, GAIM_INPUT_WRITE, | |
3421 url_fetch_send_cb, gfud); | |
3422 url_fetch_send_cb(gfud, source, GAIM_INPUT_WRITE); | |
3423 } | |
3424 | |
14354 | 3425 GaimUtilFetchUrlData * |
3426 gaim_util_fetch_url_request(const char *url, gboolean full, | |
14192 | 3427 const char *user_agent, gboolean http11, |
3428 const char *request, gboolean include_headers, | |
14354 | 3429 GaimUtilFetchUrlCallback callback, void *user_data) |
14192 | 3430 { |
14354 | 3431 GaimUtilFetchUrlData *gfud; |
3432 | |
3433 g_return_val_if_fail(url != NULL, NULL); | |
3434 g_return_val_if_fail(callback != NULL, NULL); | |
3435 | |
3436 gaim_debug_info("util", | |
14192 | 3437 "requested to fetch (%s), full=%d, user_agent=(%s), http11=%d\n", |
3438 url, full, user_agent?user_agent:"(null)", http11); | |
3439 | |
14354 | 3440 gfud = g_new0(GaimUtilFetchUrlData, 1); |
3441 | |
3442 gfud->callback = callback; | |
14192 | 3443 gfud->user_data = user_data; |
3444 gfud->url = g_strdup(url); | |
3445 gfud->user_agent = g_strdup(user_agent); | |
3446 gfud->http11 = http11; | |
3447 gfud->full = full; | |
3448 gfud->request = g_strdup(request); | |
3449 gfud->include_headers = include_headers; | |
3450 | |
3451 gaim_url_parse(url, &gfud->website.address, &gfud->website.port, | |
3452 &gfud->website.page, &gfud->website.user, &gfud->website.passwd); | |
3453 | |
14837 | 3454 gfud->connect_data = gaim_proxy_connect(NULL, NULL, |
14354 | 3455 gfud->website.address, gfud->website.port, |
3456 url_fetch_connect_cb, gfud); | |
3457 | |
3458 if (gfud->connect_data == NULL) | |
14192 | 3459 { |
14354 | 3460 gaim_util_fetch_url_error(gfud, _("Unable to connect to %s"), |
3461 gfud->website.address); | |
3462 return NULL; | |
14192 | 3463 } |
14354 | 3464 |
3465 return gfud; | |
3466 } | |
3467 | |
3468 void | |
3469 gaim_util_fetch_url_cancel(GaimUtilFetchUrlData *gfud) | |
3470 { | |
3471 if (gfud->connect_data != NULL) | |
3472 gaim_proxy_connect_cancel(gfud->connect_data); | |
3473 | |
3474 if (gfud->inpa > 0) | |
3475 gaim_input_remove(gfud->inpa); | |
3476 | |
3477 if (gfud->fd >= 0) | |
3478 close(gfud->fd); | |
3479 | |
3480 g_free(gfud->website.user); | |
3481 g_free(gfud->website.passwd); | |
3482 g_free(gfud->website.address); | |
3483 g_free(gfud->website.page); | |
3484 g_free(gfud->url); | |
3485 g_free(gfud->user_agent); | |
3486 g_free(gfud->request); | |
3487 g_free(gfud->webdata); | |
3488 | |
3489 g_free(gfud); | |
14192 | 3490 } |
3491 | |
3492 const char * | |
3493 gaim_url_decode(const char *str) | |
3494 { | |
3495 static char buf[BUF_LEN]; | |
3496 guint i, j = 0; | |
3497 char *bum; | |
3498 char hex[3]; | |
3499 | |
3500 g_return_val_if_fail(str != NULL, NULL); | |
3501 | |
3502 /* | |
3503 * XXX - This check could be removed and buf could be made | |
3504 * dynamically allocated, but this is easier. | |
3505 */ | |
3506 if (strlen(str) >= BUF_LEN) | |
3507 return NULL; | |
3508 | |
3509 for (i = 0; i < strlen(str); i++) { | |
3510 | |
3511 if (str[i] != '%') | |
3512 buf[j++] = str[i]; | |
3513 else { | |
3514 strncpy(hex, str + ++i, 2); | |
3515 hex[2] = '\0'; | |
3516 | |
3517 /* i is pointing to the start of the number */ | |
3518 i++; | |
3519 | |
3520 /* | |
3521 * Now it's at the end and at the start of the for loop | |
3522 * will be at the next character. | |
3523 */ | |
3524 buf[j++] = strtol(hex, NULL, 16); | |
3525 } | |
3526 } | |
3527 | |
3528 buf[j] = '\0'; | |
3529 | |
3530 if (!g_utf8_validate(buf, -1, (const char **)&bum)) | |
3531 *bum = '\0'; | |
3532 | |
3533 return buf; | |
3534 } | |
3535 | |
3536 const char * | |
3537 gaim_url_encode(const char *str) | |
3538 { | |
3539 const char *iter; | |
3540 static char buf[BUF_LEN]; | |
3541 char utf_char[6]; | |
3542 guint i, j = 0; | |
3543 | |
3544 g_return_val_if_fail(str != NULL, NULL); | |
3545 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL); | |
3546 | |
3547 iter = str; | |
3548 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) { | |
3549 gunichar c = g_utf8_get_char(iter); | |
3550 /* If the character is an ASCII character and is alphanumeric | |
3551 * no need to escape */ | |
3552 if (c < 128 && isalnum(c)) { | |
3553 buf[j++] = c; | |
3554 } else { | |
3555 int bytes = g_unichar_to_utf8(c, utf_char); | |
3556 for (i = 0; i < bytes; i++) { | |
3557 if (j > (BUF_LEN - 4)) | |
3558 break; | |
3559 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff); | |
3560 j += 3; | |
3561 } | |
3562 } | |
3563 } | |
3564 | |
3565 buf[j] = '\0'; | |
3566 | |
3567 return buf; | |
3568 } | |
3569 | |
3570 /* Originally lifted from | |
3571 * http://www.oreillynet.com/pub/a/network/excerpt/spcookbook_chap03/index3.html | |
3572 * ... and slightly modified to be a bit more rfc822 compliant | |
3573 * ... and modified a bit more to make domain checking rfc1035 compliant | |
3574 * with the exception permitted in rfc1101 for domains to start with digit | |
3575 * but not completely checking to avoid conflicts with IP addresses | |
3576 */ | |
3577 gboolean | |
3578 gaim_email_is_valid(const char *address) | |
3579 { | |
3580 const char *c, *domain; | |
3581 static char *rfc822_specials = "()<>@,;:\\\"[]"; | |
3582 | |
3583 /* first we validate the name portion (name@domain) (rfc822)*/ | |
3584 for (c = address; *c; c++) { | |
3585 if (*c == '\"' && (c == address || *(c - 1) == '.' || *(c - 1) == '\"')) { | |
3586 while (*++c) { | |
3587 if (*c == '\\') { | |
3588 if (*c++ && *c < 127 && *c != '\n' && *c != '\r') continue; | |
3589 else return FALSE; | |
3590 } | |
3591 if (*c == '\"') break; | |
3592 if (*c < ' ' || *c >= 127) return FALSE; | |
3593 } | |
3594 if (!*c++) return FALSE; | |
3595 if (*c == '@') break; | |
3596 if (*c != '.') return FALSE; | |
3597 continue; | |
3598 } | |
3599 if (*c == '@') break; | |
3600 if (*c <= ' ' || *c >= 127) return FALSE; | |
3601 if (strchr(rfc822_specials, *c)) return FALSE; | |
3602 } | |
3603 /* strictly we should return false if (*(c - 1) == '.') too, but I think | |
3604 * we should permit user.@domain type addresses - they do work :) */ | |
3605 if (c == address) return FALSE; | |
3606 | |
3607 /* next we validate the domain portion (name@domain) (rfc1035 & rfc1011) */ | |
3608 if (!*(domain = ++c)) return FALSE; | |
3609 do { | |
3610 if (*c == '.' && (c == domain || *(c - 1) == '.' || *(c - 1) == '-')) | |
3611 return FALSE; | |
3612 if (*c == '-' && *(c - 1) == '.') return FALSE; | |
3613 if ((*c < '0' && *c != '-' && *c != '.') || (*c > '9' && *c < 'A') || | |
3614 (*c > 'Z' && *c < 'a') || (*c > 'z')) return FALSE; | |
3615 } while (*++c); | |
3616 | |
3617 if (*(c - 1) == '-') return FALSE; | |
3618 | |
3619 return ((c - domain) > 3 ? TRUE : FALSE); | |
3620 } | |
3621 | |
3622 /* Stolen from gnome_uri_list_extract_uris */ | |
3623 GList * | |
3624 gaim_uri_list_extract_uris(const gchar *uri_list) | |
3625 { | |
3626 const gchar *p, *q; | |
3627 gchar *retval; | |
3628 GList *result = NULL; | |
3629 | |
3630 g_return_val_if_fail (uri_list != NULL, NULL); | |
3631 | |
3632 p = uri_list; | |
3633 | |
3634 /* We don't actually try to validate the URI according to RFC | |
3635 * 2396, or even check for allowed characters - we just ignore | |
3636 * comments and trim whitespace off the ends. We also | |
3637 * allow LF delimination as well as the specified CRLF. | |
3638 */ | |
3639 while (p) { | |
3640 if (*p != '#') { | |
3641 while (isspace(*p)) | |
3642 p++; | |
3643 | |
3644 q = p; | |
3645 while (*q && (*q != '\n') && (*q != '\r')) | |
3646 q++; | |
3647 | |
3648 if (q > p) { | |
3649 q--; | |
3650 while (q > p && isspace(*q)) | |
3651 q--; | |
3652 | |
3653 retval = (gchar*)g_malloc (q - p + 2); | |
3654 strncpy (retval, p, q - p + 1); | |
3655 retval[q - p + 1] = '\0'; | |
3656 | |
3657 result = g_list_prepend (result, retval); | |
3658 } | |
3659 } | |
3660 p = strchr (p, '\n'); | |
3661 if (p) | |
3662 p++; | |
3663 } | |
3664 | |
3665 return g_list_reverse (result); | |
3666 } | |
3667 | |
3668 | |
3669 /* Stolen from gnome_uri_list_extract_filenames */ | |
3670 GList * | |
3671 gaim_uri_list_extract_filenames(const gchar *uri_list) | |
3672 { | |
3673 GList *tmp_list, *node, *result; | |
3674 | |
3675 g_return_val_if_fail (uri_list != NULL, NULL); | |
3676 | |
3677 result = gaim_uri_list_extract_uris(uri_list); | |
3678 | |
3679 tmp_list = result; | |
3680 while (tmp_list) { | |
3681 gchar *s = (gchar*)tmp_list->data; | |
3682 | |
3683 node = tmp_list; | |
3684 tmp_list = tmp_list->next; | |
3685 | |
3686 if (!strncmp (s, "file:", 5)) { | |
3687 node->data = g_filename_from_uri (s, NULL, NULL); | |
3688 /* not sure if this fallback is useful at all */ | |
3689 if (!node->data) node->data = g_strdup (s+5); | |
3690 } else { | |
3691 result = g_list_remove_link(result, node); | |
3692 g_list_free_1 (node); | |
3693 } | |
3694 g_free (s); | |
3695 } | |
3696 return result; | |
3697 } | |
3698 | |
3699 /************************************************************************** | |
3700 * UTF8 String Functions | |
3701 **************************************************************************/ | |
3702 gchar * | |
3703 gaim_utf8_try_convert(const char *str) | |
3704 { | |
3705 gsize converted; | |
3706 gchar *utf8; | |
3707 | |
3708 g_return_val_if_fail(str != NULL, NULL); | |
3709 | |
3710 if (g_utf8_validate(str, -1, NULL)) { | |
3711 return g_strdup(str); | |
3712 } | |
3713 | |
3714 utf8 = g_locale_to_utf8(str, -1, &converted, NULL, NULL); | |
3715 if (utf8 != NULL) | |
3716 return utf8; | |
3717 | |
3718 utf8 = g_convert(str, -1, "UTF-8", "ISO-8859-15", &converted, NULL, NULL); | |
3719 if ((utf8 != NULL) && (converted == strlen(str))) | |
3720 return utf8; | |
3721 | |
3722 g_free(utf8); | |
3723 | |
3724 return NULL; | |
3725 } | |
3726 | |
3727 #define utf8_first(x) ((x & 0x80) == 0 || (x & 0xe0) == 0xc0 \ | |
3728 || (x & 0xf0) == 0xe0 || (x & 0xf8) == 0xf) | |
3729 gchar * | |
3730 gaim_utf8_salvage(const char *str) | |
3731 { | |
3732 GString *workstr; | |
3733 const char *end; | |
3734 | |
3735 g_return_val_if_fail(str != NULL, NULL); | |
3736 | |
3737 workstr = g_string_sized_new(strlen(str)); | |
3738 | |
3739 do { | |
3740 g_utf8_validate(str, -1, &end); | |
3741 workstr = g_string_append_len(workstr, str, end - str); | |
3742 str = end; | |
3743 if (*str == '\0') | |
3744 break; | |
3745 do { | |
3746 workstr = g_string_append_c(workstr, '?'); | |
3747 str++; | |
3748 } while (!utf8_first(*str)); | |
3749 } while (*str != '\0'); | |
3750 | |
3751 return g_string_free(workstr, FALSE); | |
3752 } | |
3753 | |
3754 | |
3755 char * | |
3756 gaim_utf8_ncr_encode(const char *str) | |
3757 { | |
3758 GString *out; | |
3759 | |
3760 g_return_val_if_fail(str != NULL, NULL); | |
3761 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL); | |
3762 | |
3763 out = g_string_new(""); | |
3764 | |
3765 for(; *str; str = g_utf8_next_char(str)) { | |
3766 gunichar wc = g_utf8_get_char(str); | |
3767 | |
3768 /* super simple check. hopefully not too wrong. */ | |
3769 if(wc >= 0x80) { | |
3770 g_string_append_printf(out, "&#%u;", (guint32) wc); | |
3771 } else { | |
3772 g_string_append_unichar(out, wc); | |
3773 } | |
3774 } | |
3775 | |
3776 return g_string_free(out, FALSE); | |
3777 } | |
3778 | |
3779 | |
3780 char * | |
3781 gaim_utf8_ncr_decode(const char *str) | |
3782 { | |
3783 GString *out; | |
3784 char *buf, *b; | |
3785 | |
3786 g_return_val_if_fail(str != NULL, NULL); | |
3787 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL); | |
3788 | |
3789 buf = (char *) str; | |
3790 out = g_string_new(""); | |
3791 | |
3792 while( (b = strstr(buf, "&#")) ) { | |
3793 gunichar wc; | |
3794 int base = 0; | |
3795 | |
3796 /* append everything leading up to the &# */ | |
3797 g_string_append_len(out, buf, b-buf); | |
3798 | |
3799 b += 2; /* skip past the &# */ | |
3800 | |
3801 /* strtoul will treat 0x prefix as hex, but not just x */ | |
3802 if(*b == 'x' || *b == 'X') { | |
3803 base = 16; | |
3804 b++; | |
3805 } | |
3806 | |
3807 /* advances buf to the end of the ncr segment */ | |
3808 wc = (gunichar) strtoul(b, &buf, base); | |
3809 | |
3810 /* this mimics the previous impl of ncr_decode */ | |
3811 if(*buf == ';') { | |
3812 g_string_append_unichar(out, wc); | |
3813 buf++; | |
3814 } | |
3815 } | |
3816 | |
3817 /* append whatever's left */ | |
3818 g_string_append(out, buf); | |
3819 | |
3820 return g_string_free(out, FALSE); | |
3821 } | |
3822 | |
3823 | |
3824 int | |
3825 gaim_utf8_strcasecmp(const char *a, const char *b) | |
3826 { | |
3827 char *a_norm = NULL; | |
3828 char *b_norm = NULL; | |
3829 int ret = -1; | |
3830 | |
3831 if(!a && b) | |
3832 return -1; | |
3833 else if(!b && a) | |
3834 return 1; | |
3835 else if(!a && !b) | |
3836 return 0; | |
3837 | |
3838 if(!g_utf8_validate(a, -1, NULL) || !g_utf8_validate(b, -1, NULL)) | |
3839 { | |
3840 gaim_debug_error("gaim_utf8_strcasecmp", | |
3841 "One or both parameters are invalid UTF8\n"); | |
3842 return ret; | |
3843 } | |
3844 | |
3845 a_norm = g_utf8_casefold(a, -1); | |
3846 b_norm = g_utf8_casefold(b, -1); | |
3847 ret = g_utf8_collate(a_norm, b_norm); | |
3848 g_free(a_norm); | |
3849 g_free(b_norm); | |
3850 | |
3851 return ret; | |
3852 } | |
3853 | |
3854 /* previously conversation::find_nick() */ | |
3855 gboolean | |
3856 gaim_utf8_has_word(const char *haystack, const char *needle) | |
3857 { | |
3858 char *hay, *pin, *p; | |
3859 int n; | |
3860 gboolean ret = FALSE; | |
3861 | |
3862 hay = g_utf8_strdown(haystack, -1); | |
3863 | |
3864 pin = g_utf8_strdown(needle, -1); | |
3865 n = strlen(pin); | |
3866 | |
3867 if ((p = strstr(hay, pin)) != NULL) { | |
3868 if ((p == hay || !isalnum(*(p - 1))) && !isalnum(*(p + n))) { | |
3869 ret = TRUE; | |
3870 } | |
3871 } | |
3872 | |
3873 g_free(pin); | |
3874 g_free(hay); | |
3875 | |
3876 return ret; | |
3877 } | |
3878 | |
3879 void | |
3880 gaim_print_utf8_to_console(FILE *filestream, char *message) | |
3881 { | |
3882 gchar *message_conv; | |
3883 GError *error = NULL; | |
3884 | |
3885 /* Try to convert 'message' to user's locale */ | |
3886 message_conv = g_locale_from_utf8(message, -1, NULL, NULL, &error); | |
3887 if (message_conv != NULL) { | |
3888 fputs(message_conv, filestream); | |
3889 g_free(message_conv); | |
3890 } | |
3891 else | |
3892 { | |
3893 /* use 'message' as a fallback */ | |
3894 g_warning("%s\n", error->message); | |
3895 g_error_free(error); | |
3896 fputs(message, filestream); | |
3897 } | |
3898 } | |
3899 | |
3900 gboolean gaim_message_meify(char *message, size_t len) | |
3901 { | |
3902 char *c; | |
3903 gboolean inside_html = FALSE; | |
3904 | |
3905 g_return_val_if_fail(message != NULL, FALSE); | |
3906 | |
3907 if(len == -1) | |
3908 len = strlen(message); | |
3909 | |
3910 for (c = message; *c; c++, len--) { | |
3911 if(inside_html) { | |
3912 if(*c == '>') | |
3913 inside_html = FALSE; | |
3914 } else { | |
3915 if(*c == '<') | |
3916 inside_html = TRUE; | |
3917 else | |
3918 break; | |
3919 } | |
3920 } | |
3921 | |
3922 if(*c && !g_ascii_strncasecmp(c, "/me ", 4)) { | |
3923 memmove(c, c+4, len-3); | |
3924 return TRUE; | |
3925 } | |
3926 | |
3927 return FALSE; | |
3928 } | |
3929 | |
3930 char *gaim_text_strip_mnemonic(const char *in) | |
3931 { | |
3932 char *out; | |
3933 char *a; | |
3934 char *a0; | |
3935 const char *b; | |
3936 | |
3937 g_return_val_if_fail(in != NULL, NULL); | |
3938 | |
3939 out = g_malloc(strlen(in)+1); | |
3940 a = out; | |
3941 b = in; | |
3942 | |
3943 a0 = a; /* The last non-space char seen so far, or the first char */ | |
3944 | |
3945 while(*b) { | |
3946 if(*b == '_') { | |
3947 if(a > out && b > in && *(b-1) == '(' && *(b+1) && !(*(b+1) & 0x80) && *(b+2) == ')') { | |
3948 /* Detected CJK style shortcut (Bug 875311) */ | |
3949 a = a0; /* undo the left parenthesis */ | |
3950 b += 3; /* and skip the whole mess */ | |
3951 } else if(*(b+1) == '_') { | |
3952 *(a++) = '_'; | |
3953 b += 2; | |
3954 a0 = a; | |
3955 } else { | |
3956 b++; | |
3957 } | |
3958 /* We don't want to corrupt the middle of UTF-8 characters */ | |
3959 } else if (!(*b & 0x80)) { /* other 1-byte char */ | |
3960 if (*b != ' ') | |
3961 a0 = a; | |
3962 *(a++) = *(b++); | |
3963 } else { | |
3964 /* Multibyte utf8 char, don't look for _ inside these */ | |
3965 int n = 0; | |
3966 int i; | |
3967 if ((*b & 0xe0) == 0xc0) { | |
3968 n = 2; | |
3969 } else if ((*b & 0xf0) == 0xe0) { | |
3970 n = 3; | |
3971 } else if ((*b & 0xf8) == 0xf0) { | |
3972 n = 4; | |
3973 } else if ((*b & 0xfc) == 0xf8) { | |
3974 n = 5; | |
3975 } else if ((*b & 0xfe) == 0xfc) { | |
3976 n = 6; | |
3977 } else { /* Illegal utf8 */ | |
3978 n = 1; | |
3979 } | |
3980 a0 = a; /* unless we want to delete CJK spaces too */ | |
3981 for (i = 0; i < n && *b; i += 1) { | |
3982 *(a++) = *(b++); | |
3983 } | |
3984 } | |
3985 } | |
3986 *a = '\0'; | |
3987 | |
3988 return out; | |
3989 } | |
3990 | |
3991 const char* gaim_unescape_filename(const char *escaped) { | |
3992 return gaim_url_decode(escaped); | |
3993 } | |
3994 | |
3995 | |
3996 /* this is almost identical to gaim_url_encode (hence gaim_url_decode | |
3997 * being used above), but we want to keep certain characters unescaped | |
3998 * for compat reasons */ | |
3999 const char * | |
4000 gaim_escape_filename(const char *str) | |
4001 { | |
4002 const char *iter; | |
4003 static char buf[BUF_LEN]; | |
4004 char utf_char[6]; | |
4005 guint i, j = 0; | |
4006 | |
4007 g_return_val_if_fail(str != NULL, NULL); | |
4008 g_return_val_if_fail(g_utf8_validate(str, -1, NULL), NULL); | |
4009 | |
4010 iter = str; | |
4011 for (; *iter && j < (BUF_LEN - 1) ; iter = g_utf8_next_char(iter)) { | |
4012 gunichar c = g_utf8_get_char(iter); | |
4013 /* If the character is an ASCII character and is alphanumeric, | |
4014 * or one of the specified values, no need to escape */ | |
4015 if (c < 128 && (isalnum(c) || c == '@' || c == '-' || | |
4016 c == '_' || c == '.' || c == '#')) { | |
4017 buf[j++] = c; | |
4018 } else { | |
4019 int bytes = g_unichar_to_utf8(c, utf_char); | |
4020 for (i = 0; i < bytes; i++) { | |
4021 if (j > (BUF_LEN - 4)) | |
4022 break; | |
4023 sprintf(buf + j, "%%%02x", utf_char[i] & 0xff); | |
4024 j += 3; | |
4025 } | |
4026 } | |
4027 } | |
4028 | |
4029 buf[j] = '\0'; | |
4030 | |
4031 return buf; | |
4032 } | |
4033 |