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