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