utils.c (93918B)
1 /* 2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client 3 * Copyright (C) 1999-2025 The Claws Mail Team and Hiroyuki Yamamoto 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 3 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 * The code of the g_utf8_substring function below is owned by 19 * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com> 20 * and is got from GLIB 2.30: https://git.gnome.org/browse/glib/commit/ 21 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511 22 * 23 * GLib 2.30 is licensed under GPL v2 or later and: 24 * Copyright (C) 1999 Tom Tromey 25 * Copyright (C) 2000 Red Hat, Inc. 26 * 27 * https://git.gnome.org/browse/glib/tree/glib/gutf8.c 28 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511 29 */ 30 31 #include "defs.h" 32 33 #include <glib.h> 34 #include <gio/gio.h> 35 36 #include <glib/gi18n.h> 37 38 #include <stdio.h> 39 #include <string.h> 40 #include <ctype.h> 41 #include <errno.h> 42 #include <sys/param.h> 43 #include <sys/socket.h> 44 45 #include <wchar.h> 46 #include <wctype.h> 47 #include <stdlib.h> 48 #include <stdint.h> 49 #include <sys/stat.h> 50 #include <unistd.h> 51 #include <stdarg.h> 52 #include <sys/types.h> 53 #include <sys/wait.h> 54 #include <time.h> 55 #include <regex.h> 56 57 #include <fcntl.h> 58 59 #include "utils.h" 60 #include "socket.h" 61 #include "codeconv.h" 62 #include "file-utils.h" 63 #include "fence.h" 64 65 #define BUFFSIZE 8192 66 67 static gboolean debug_mode = FALSE; 68 69 void list_free_strings_full(GList *list) 70 { 71 g_list_free_full(list, (GDestroyNotify)g_free); 72 } 73 74 void slist_free_strings_full(GSList *list) 75 { 76 g_slist_free_full(list, (GDestroyNotify)g_free); 77 } 78 79 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data) 80 { 81 g_free(key); 82 } 83 84 void hash_free_strings(GHashTable *table) 85 { 86 g_hash_table_foreach(table, hash_free_strings_func, NULL); 87 } 88 89 gint str_case_equal(gconstpointer v, gconstpointer v2) 90 { 91 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0; 92 } 93 94 guint str_case_hash(gconstpointer key) 95 { 96 const gchar *p = key; 97 guint h = *p; 98 99 if (h) { 100 h = g_ascii_tolower(h); 101 for (p += 1; *p != '\0'; p++) 102 h = (h << 5) - h + g_ascii_tolower(*p); 103 } 104 105 return h; 106 } 107 108 /* convert integer into string, 109 nstr must be not lower than 11 characters length */ 110 gchar *itos_buf(gchar *nstr, gint n) 111 { 112 g_snprintf(nstr, 11, "%d", n); 113 return nstr; 114 } 115 116 /* convert integer into string */ 117 gchar *itos(gint n) 118 { 119 static gchar nstr[11]; 120 121 return itos_buf(nstr, n); 122 } 123 124 #define divide(num,divisor,i,d) \ 125 { \ 126 i = num >> divisor; \ 127 d = num & ((1<<divisor)-1); \ 128 d = (d*100) >> divisor; \ 129 } 130 131 132 /*! 133 * \brief Convert a given size in bytes in a human-readable string 134 * 135 * \param size The size expressed in bytes to convert in string 136 * \return The string that respresents the size in an human-readable way 137 */ 138 gchar *to_human_readable(goffset size) 139 { 140 static gchar str[14]; 141 static gchar *b_format = NULL, *kb_format = NULL, 142 *mb_format = NULL, *gb_format = NULL; 143 register int t = 0, r = 0; 144 if (b_format == NULL) { 145 b_format = _("%dB"); 146 kb_format = _("%d.%02dKiB"); 147 mb_format = _("%d.%02dMiB"); 148 gb_format = _("%.2fGiB"); 149 } 150 151 if (size < (goffset)1024) { 152 g_snprintf(str, sizeof(str), b_format, (gint)size); 153 return str; 154 } else if (size >> 10 < (goffset)1024) { 155 divide(size, 10, t, r); 156 g_snprintf(str, sizeof(str), kb_format, t, r); 157 return str; 158 } else if (size >> 20 < (goffset)1024) { 159 divide(size, 20, t, r); 160 g_snprintf(str, sizeof(str), mb_format, t, r); 161 return str; 162 } else { 163 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30)); 164 return str; 165 } 166 } 167 168 gint path_cmp(const gchar *s1, const gchar *s2) 169 { 170 gint len1, len2; 171 172 if (s1 == NULL || s2 == NULL) return -1; 173 if (*s1 == '\0' || *s2 == '\0') return -1; 174 175 len1 = strlen(s1); 176 len2 = strlen(s2); 177 178 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--; 179 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--; 180 181 return strncmp(s1, s2, MAX(len1, len2)); 182 } 183 184 /* remove trailing return code */ 185 gchar *strretchomp(gchar *str) 186 { 187 register gchar *s; 188 189 if (!*str) return str; 190 191 for (s = str + strlen(str) - 1; 192 s >= str && (*s == '\n' || *s == '\r'); 193 s--) 194 *s = '\0'; 195 196 return str; 197 } 198 199 /* remove trailing character */ 200 gchar *strtailchomp(gchar *str, gchar tail_char) 201 { 202 register gchar *s; 203 204 if (!*str) return str; 205 if (tail_char == '\0') return str; 206 207 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--) 208 *s = '\0'; 209 210 return str; 211 } 212 213 /* remove CR (carriage return) */ 214 gchar *strcrchomp(gchar *str) 215 { 216 register gchar *s; 217 218 if (!*str) return str; 219 220 s = str + strlen(str) - 1; 221 if (*s == '\n' && s > str && *(s - 1) == '\r') { 222 *(s - 1) = '\n'; 223 *s = '\0'; 224 } 225 226 return str; 227 } 228 229 /* truncates string at first CR (carriage return) or LF (line feed) */ 230 gchar *strcrlftrunc(gchar *str) 231 { 232 gchar *p = NULL; 233 234 if ((str == NULL) || (!*str)) return str; 235 236 if ((p = strstr(str, "\r")) != NULL) 237 *p = '\0'; 238 if ((p = strstr(str, "\n")) != NULL) 239 *p = '\0'; 240 241 return str; 242 } 243 244 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle) 245 { 246 register size_t needle_len; 247 248 needle_len = strlen(needle); 249 250 if (haystack_len < needle_len || needle_len == 0) 251 return NULL; 252 253 while (haystack_len >= needle_len) { 254 if (!g_ascii_strncasecmp(haystack, needle, needle_len)) 255 return (gchar *)haystack; 256 else { 257 haystack++; 258 haystack_len--; 259 } 260 } 261 262 return NULL; 263 } 264 265 gpointer my_memmem(gconstpointer haystack, size_t haystacklen, 266 gconstpointer needle, size_t needlelen) 267 { 268 const gchar *haystack_ = (const gchar *)haystack; 269 const gchar *needle_ = (const gchar *)needle; 270 const gchar *haystack_cur = (const gchar *)haystack; 271 size_t haystack_left = haystacklen; 272 273 if (needlelen == 1) 274 return memchr(haystack_, *needle_, haystacklen); 275 276 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left)) 277 != NULL) { 278 if (haystacklen - (haystack_cur - haystack_) < needlelen) 279 break; 280 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0) 281 return (gpointer)haystack_cur; 282 else{ 283 haystack_cur++; 284 haystack_left = haystacklen - (haystack_cur - haystack_); 285 } 286 } 287 288 return NULL; 289 } 290 291 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */ 292 gchar *strncpy2(gchar *dest, const gchar *src, size_t n) 293 { 294 register const gchar *s = src; 295 register gchar *d = dest; 296 297 while (--n && *s) 298 *d++ = *s++; 299 *d = '\0'; 300 301 return dest; 302 } 303 304 305 /* Examine if next block is non-ASCII string */ 306 gboolean is_next_nonascii(const gchar *s) 307 { 308 const gchar *p; 309 310 /* skip head space */ 311 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++) 312 ; 313 for (; *p != '\0' && !g_ascii_isspace(*p); p++) { 314 if (*(guchar *)p > 127 || *(guchar *)p < 32) 315 return TRUE; 316 } 317 318 return FALSE; 319 } 320 321 gint get_next_word_len(const gchar *s) 322 { 323 gint len = 0; 324 325 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++) 326 ; 327 328 return len; 329 } 330 331 static void trim_subject_for_compare(gchar *str) 332 { 333 gchar *srcp; 334 335 eliminate_parenthesis(str, '[', ']'); 336 eliminate_parenthesis(str, '(', ')'); 337 g_strstrip(str); 338 339 srcp = str + subject_get_prefix_length(str); 340 if (srcp != str) 341 memmove(str, srcp, strlen(srcp) + 1); 342 } 343 344 static void trim_subject_for_sort(gchar *str) 345 { 346 gchar *srcp; 347 348 g_strstrip(str); 349 350 srcp = str + subject_get_prefix_length(str); 351 if (srcp != str) 352 memmove(str, srcp, strlen(srcp) + 1); 353 } 354 355 /* compare subjects */ 356 gint subject_compare(const gchar *s1, const gchar *s2) 357 { 358 gchar *str1, *str2; 359 360 if (!s1 || !s2) return -1; 361 if (!*s1 || !*s2) return -1; 362 363 Xstrdup_a(str1, s1, return -1); 364 Xstrdup_a(str2, s2, return -1); 365 366 trim_subject_for_compare(str1); 367 trim_subject_for_compare(str2); 368 369 if (!*str1 || !*str2) return -1; 370 371 return strcmp(str1, str2); 372 } 373 374 gint subject_compare_for_sort(const gchar *s1, const gchar *s2) 375 { 376 gchar *str1, *str2; 377 378 if (!s1 || !s2) return -1; 379 380 Xstrdup_a(str1, s1, return -1); 381 Xstrdup_a(str2, s2, return -1); 382 383 trim_subject_for_sort(str1); 384 trim_subject_for_sort(str2); 385 386 if (!g_utf8_validate(str1, -1, NULL)) { 387 g_warning("message subject \"%s\" failed UTF-8 validation", str1); 388 return 0; 389 } else if (!g_utf8_validate(str2, -1, NULL)) { 390 g_warning("message subject \"%s\" failed UTF-8 validation", str2); 391 return 0; 392 } 393 394 return g_utf8_collate(str1, str2); 395 } 396 397 void trim_subject(gchar *str) 398 { 399 register gchar *srcp; 400 gchar op, cl; 401 gint in_brace; 402 403 g_strstrip(str); 404 405 srcp = str + subject_get_prefix_length(str); 406 407 if (*srcp == '[') { 408 op = '['; 409 cl = ']'; 410 } else if (*srcp == '(') { 411 op = '('; 412 cl = ')'; 413 } else 414 op = 0; 415 416 if (op) { 417 ++srcp; 418 in_brace = 1; 419 while (*srcp) { 420 if (*srcp == op) 421 in_brace++; 422 else if (*srcp == cl) 423 in_brace--; 424 srcp++; 425 if (in_brace == 0) 426 break; 427 } 428 } 429 while (g_ascii_isspace(*srcp)) srcp++; 430 memmove(str, srcp, strlen(srcp) + 1); 431 } 432 433 void eliminate_parenthesis(gchar *str, gchar op, gchar cl) 434 { 435 register gchar *srcp, *destp; 436 gint in_brace; 437 438 destp = str; 439 440 while ((destp = strchr(destp, op))) { 441 in_brace = 1; 442 srcp = destp + 1; 443 while (*srcp) { 444 if (*srcp == op) 445 in_brace++; 446 else if (*srcp == cl) 447 in_brace--; 448 srcp++; 449 if (in_brace == 0) 450 break; 451 } 452 while (g_ascii_isspace(*srcp)) srcp++; 453 memmove(destp, srcp, strlen(srcp) + 1); 454 } 455 } 456 457 void extract_parenthesis(gchar *str, gchar op, gchar cl) 458 { 459 register gchar *srcp, *destp; 460 gint in_brace; 461 462 destp = str; 463 464 while ((srcp = strchr(destp, op))) { 465 if (destp > str) 466 *destp++ = ' '; 467 memmove(destp, srcp + 1, strlen(srcp)); 468 in_brace = 1; 469 while(*destp) { 470 if (*destp == op) 471 in_brace++; 472 else if (*destp == cl) 473 in_brace--; 474 475 if (in_brace == 0) 476 break; 477 478 destp++; 479 } 480 } 481 *destp = '\0'; 482 } 483 484 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr, 485 gchar op, gchar cl) 486 { 487 register gchar *srcp, *destp; 488 gint in_brace; 489 gboolean in_quote = FALSE; 490 491 destp = str; 492 493 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) { 494 if (destp > str) 495 *destp++ = ' '; 496 memmove(destp, srcp + 1, strlen(srcp)); 497 in_brace = 1; 498 while(*destp) { 499 if (*destp == op && !in_quote) 500 in_brace++; 501 else if (*destp == cl && !in_quote) 502 in_brace--; 503 else if (*destp == quote_chr) 504 in_quote ^= TRUE; 505 506 if (in_brace == 0) 507 break; 508 509 destp++; 510 } 511 } 512 *destp = '\0'; 513 } 514 515 void extract_quote(gchar *str, gchar quote_chr) 516 { 517 register gchar *p; 518 519 if ((str = strchr(str, quote_chr))) { 520 p = str; 521 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) { 522 memmove(p - 1, p, strlen(p) + 1); 523 p--; 524 } 525 if(p) { 526 *p = '\0'; 527 memmove(str, str + 1, p - str); 528 } 529 } 530 } 531 532 /* Returns a newly allocated string with all quote_chr not at the beginning 533 or the end of str escaped with '\' or the given str if not required. */ 534 gchar *escape_internal_quotes(gchar *str, gchar quote_chr) 535 { 536 register gchar *p, *q; 537 gchar *qstr; 538 int k = 0, l = 0; 539 540 if (str == NULL || *str == '\0') 541 return str; 542 543 g_strstrip(str); 544 if (*str == '\0') 545 return str; 546 /* search for unescaped quote_chr */ 547 p = str; 548 if (*p == quote_chr) 549 ++p, ++l; 550 while (*p) { 551 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0') 552 ++k; 553 ++p, ++l; 554 } 555 if (!k) /* nothing to escape */ 556 return str; 557 558 /* unescaped quote_chr found */ 559 qstr = g_malloc(l + k + 1); 560 p = str; 561 q = qstr; 562 if (*p == quote_chr) { 563 *q = quote_chr; 564 ++p, ++q; 565 } 566 while (*p) { 567 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0') 568 *q++ = '\\'; 569 *q++ = *p++; 570 } 571 *q = '\0'; 572 573 return qstr; 574 } 575 576 void eliminate_address_comment(gchar *str) 577 { 578 register gchar *srcp, *destp; 579 gint in_brace; 580 581 destp = str; 582 583 while ((destp = strchr(destp, '"'))) { 584 if ((srcp = strchr(destp + 1, '"'))) { 585 srcp++; 586 if (*srcp == '@') { 587 destp = srcp + 1; 588 } else { 589 while (g_ascii_isspace(*srcp)) srcp++; 590 memmove(destp, srcp, strlen(srcp) + 1); 591 } 592 } else { 593 *destp = '\0'; 594 break; 595 } 596 } 597 598 destp = str; 599 600 while ((destp = strchr_with_skip_quote(destp, '"', '('))) { 601 in_brace = 1; 602 srcp = destp + 1; 603 while (*srcp) { 604 if (*srcp == '(') 605 in_brace++; 606 else if (*srcp == ')') 607 in_brace--; 608 srcp++; 609 if (in_brace == 0) 610 break; 611 } 612 while (g_ascii_isspace(*srcp)) srcp++; 613 memmove(destp, srcp, strlen(srcp) + 1); 614 } 615 } 616 617 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c) 618 { 619 gboolean in_quote = FALSE; 620 621 while (*str) { 622 if (*str == c && !in_quote) 623 return (gchar *)str; 624 if (*str == quote_chr) 625 in_quote ^= TRUE; 626 str++; 627 } 628 629 return NULL; 630 } 631 632 void extract_address(gchar *str) 633 { 634 cm_return_if_fail(str != NULL); 635 eliminate_address_comment(str); 636 if (strchr_with_skip_quote(str, '"', '<')) 637 extract_parenthesis_with_skip_quote(str, '"', '<', '>'); 638 g_strstrip(str); 639 } 640 641 void extract_list_id_str(gchar *str) 642 { 643 if (strchr_with_skip_quote(str, '"', '<')) 644 extract_parenthesis_with_skip_quote(str, '"', '<', '>'); 645 g_strstrip(str); 646 } 647 648 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments) 649 { 650 gchar *work; 651 gchar *workp; 652 653 if (!str) return addr_list; 654 655 Xstrdup_a(work, str, return addr_list); 656 657 if (removecomments) 658 eliminate_address_comment(work); 659 workp = work; 660 661 while (workp && *workp) { 662 gchar *p, *next; 663 664 if ((p = strchr_with_skip_quote(workp, '"', ','))) { 665 *p = '\0'; 666 next = p + 1; 667 } else 668 next = NULL; 669 670 if (removecomments && strchr_with_skip_quote(workp, '"', '<')) 671 extract_parenthesis_with_skip_quote 672 (workp, '"', '<', '>'); 673 674 g_strstrip(workp); 675 if (*workp) 676 addr_list = g_slist_append(addr_list, g_strdup(workp)); 677 678 workp = next; 679 } 680 681 return addr_list; 682 } 683 684 GSList *address_list_append(GSList *addr_list, const gchar *str) 685 { 686 return address_list_append_real(addr_list, str, TRUE); 687 } 688 689 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str) 690 { 691 return address_list_append_real(addr_list, str, FALSE); 692 } 693 694 GSList *references_list_prepend(GSList *msgid_list, const gchar *str) 695 { 696 const gchar *strp; 697 698 if (!str) return msgid_list; 699 strp = str; 700 701 while (strp && *strp) { 702 const gchar *start, *end; 703 gchar *msgid; 704 705 if ((start = strchr(strp, '<')) != NULL) { 706 end = strchr(start + 1, '>'); 707 if (!end) break; 708 } else 709 break; 710 711 msgid = g_strndup(start + 1, end - start - 1); 712 g_strstrip(msgid); 713 if (*msgid) 714 msgid_list = g_slist_prepend(msgid_list, msgid); 715 else 716 g_free(msgid); 717 718 strp = end + 1; 719 } 720 721 return msgid_list; 722 } 723 724 GSList *references_list_append(GSList *msgid_list, const gchar *str) 725 { 726 GSList *list; 727 728 list = references_list_prepend(NULL, str); 729 list = g_slist_reverse(list); 730 return g_slist_concat(msgid_list, list); 731 } 732 733 GList *add_history(GList *list, const gchar *str) 734 { 735 GList *old; 736 gchar *oldstr; 737 738 cm_return_val_if_fail(str != NULL, list); 739 740 uint maxhist = 32; 741 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)g_strcmp0); 742 if (old) { 743 oldstr = old->data; 744 list = g_list_remove(list, old->data); 745 g_free(oldstr); 746 } else if (g_list_length(list) >= maxhist) { 747 GList *last; 748 749 last = g_list_last(list); 750 if (last) { 751 oldstr = last->data; 752 list = g_list_remove(list, last->data); 753 g_free(oldstr); 754 } 755 } 756 757 list = g_list_prepend(list, g_strdup(str)); 758 759 return list; 760 } 761 762 void remove_return(gchar *str) 763 { 764 register gchar *p = str; 765 766 while (*p) { 767 if (*p == '\n' || *p == '\r') 768 memmove(p, p + 1, strlen(p)); 769 else 770 p++; 771 } 772 } 773 774 void remove_space(gchar *str) 775 { 776 register gchar *p = str; 777 register gint spc; 778 779 while (*p) { 780 spc = 0; 781 while (g_ascii_isspace(*(p + spc))) 782 spc++; 783 if (spc) 784 memmove(p, p + spc, strlen(p + spc) + 1); 785 else 786 p++; 787 } 788 } 789 790 void unfold_line(gchar *str) 791 { 792 register gchar *ch; 793 register gunichar c; 794 register gint len; 795 796 ch = str; /* iterator for source string */ 797 798 while (*ch != 0) { 799 c = g_utf8_get_char_validated(ch, -1); 800 801 if (c == (gunichar)-1 || c == (gunichar)-2) { 802 /* non-unicode byte, move past it */ 803 ch++; 804 continue; 805 } 806 807 len = g_unichar_to_utf8(c, NULL); 808 809 if ((!g_unichar_isdefined(c) || !g_unichar_isprint(c) || 810 g_unichar_isspace(c)) && c != 173) { 811 /* replace anything bad or whitespacey with a single space */ 812 *ch = ' '; 813 ch++; 814 if (len > 1) { 815 /* move rest of the string forwards, since we just replaced 816 * a multi-byte sequence with one byte */ 817 memmove(ch, ch + len-1, strlen(ch + len-1) + 1); 818 } 819 } else { 820 /* A valid unicode character, copy it. */ 821 ch += len; 822 } 823 } 824 } 825 826 void subst_char(gchar *str, gchar orig, gchar subst) 827 { 828 register gchar *p = str; 829 830 while (*p) { 831 if (*p == orig) 832 *p = subst; 833 p++; 834 } 835 } 836 837 void subst_chars(gchar *str, gchar *orig, gchar subst) 838 { 839 register gchar *p = str; 840 841 while (*p) { 842 if (strchr(orig, *p) != NULL) 843 *p = subst; 844 p++; 845 } 846 } 847 848 void subst_for_filename(gchar *str) 849 { 850 if (!str) 851 return; 852 subst_chars(str, "\t\r\n\\/*", '_'); 853 } 854 855 void subst_for_shellsafe_filename(gchar *str) 856 { 857 if (!str) 858 return; 859 subst_for_filename(str); 860 subst_chars(str, " \"'|&;()<>'!{}[]",'_'); 861 } 862 863 gboolean is_ascii_str(const gchar *str) 864 { 865 const guchar *p = (const guchar *)str; 866 867 while (*p != '\0') { 868 if (*p != '\t' && *p != ' ' && 869 *p != '\r' && *p != '\n' && 870 (*p < 32 || *p >= 127)) 871 return FALSE; 872 p++; 873 } 874 875 return TRUE; 876 } 877 878 gint check_line_length(const gchar *str, gint max_chars, gint *line) 879 { 880 const gchar *p = str, *q; 881 gint cur_line = 0, len; 882 883 while ((q = strchr(p, '\n')) != NULL) { 884 len = q - p + 1; 885 if (len > max_chars) { 886 if (line) 887 *line = cur_line; 888 return -1; 889 } 890 p = q + 1; 891 ++cur_line; 892 } 893 894 len = strlen(p); 895 if (len > max_chars) { 896 if (line) 897 *line = cur_line; 898 return -1; 899 } 900 901 return 0; 902 } 903 904 const gchar * line_has_quote_char(const gchar * str) 905 { 906 return strchr(str, '>'); 907 } 908 909 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle) 910 { 911 register guint haystack_len, needle_len; 912 gboolean in_squote = FALSE, in_dquote = FALSE; 913 914 haystack_len = strlen(haystack); 915 needle_len = strlen(needle); 916 917 if (haystack_len < needle_len || needle_len == 0) 918 return NULL; 919 920 while (haystack_len >= needle_len) { 921 if (!in_squote && !in_dquote && 922 !strncmp(haystack, needle, needle_len)) 923 return (gchar *)haystack; 924 925 /* 'foo"bar"' -> foo"bar" 926 "foo'bar'" -> foo'bar' */ 927 if (*haystack == '\'') { 928 if (in_squote) 929 in_squote = FALSE; 930 else if (!in_dquote) 931 in_squote = TRUE; 932 } else if (*haystack == '\"') { 933 if (in_dquote) 934 in_dquote = FALSE; 935 else if (!in_squote) 936 in_dquote = TRUE; 937 } else if (*haystack == '\\') { 938 haystack++; 939 haystack_len--; 940 } 941 942 haystack++; 943 haystack_len--; 944 } 945 946 return NULL; 947 } 948 949 gchar **strsplit_with_quote(const gchar *str, const gchar *delim, 950 gint max_tokens) 951 { 952 GSList *string_list = NULL, *slist; 953 gchar **str_array, *s, *new_str; 954 guint i, n = 1, len; 955 956 cm_return_val_if_fail(str != NULL, NULL); 957 cm_return_val_if_fail(delim != NULL, NULL); 958 959 if (max_tokens < 1) 960 max_tokens = G_MAXINT; 961 962 s = strstr_with_skip_quote(str, delim); 963 if (s) { 964 guint delimiter_len = strlen(delim); 965 966 do { 967 len = s - str; 968 new_str = g_strndup(str, len); 969 970 if (new_str[0] == '\'' || new_str[0] == '\"') { 971 if (new_str[len - 1] == new_str[0]) { 972 new_str[len - 1] = '\0'; 973 memmove(new_str, new_str + 1, len - 1); 974 } 975 } 976 string_list = g_slist_prepend(string_list, new_str); 977 n++; 978 str = s + delimiter_len; 979 s = strstr_with_skip_quote(str, delim); 980 } while (--max_tokens && s); 981 } 982 983 if (*str) { 984 new_str = g_strdup(str); 985 if (new_str[0] == '\'' || new_str[0] == '\"') { 986 len = strlen(str); 987 if (new_str[len - 1] == new_str[0]) { 988 new_str[len - 1] = '\0'; 989 memmove(new_str, new_str + 1, len - 1); 990 } 991 } 992 string_list = g_slist_prepend(string_list, new_str); 993 n++; 994 } 995 996 str_array = g_new(gchar*, n); 997 998 i = n - 1; 999 1000 str_array[i--] = NULL; 1001 for (slist = string_list; slist; slist = slist->next) 1002 str_array[i--] = slist->data; 1003 1004 g_slist_free(string_list); 1005 1006 return str_array; 1007 } 1008 1009 gchar *trim_string(const gchar *str, gint len) 1010 { 1011 const gchar *p = str; 1012 gint mb_len; 1013 gchar *new_str; 1014 gint new_len = 0; 1015 1016 if (!str) return NULL; 1017 if (strlen(str) <= len) 1018 return g_strdup(str); 1019 if (g_utf8_validate(str, -1, NULL) == FALSE) 1020 return g_strdup(str); 1021 1022 while (*p != '\0') { 1023 mb_len = g_utf8_skip[*(guchar *)p]; 1024 if (mb_len == 0) 1025 break; 1026 else if (new_len + mb_len > len) 1027 break; 1028 1029 new_len += mb_len; 1030 p += mb_len; 1031 } 1032 1033 Xstrndup_a(new_str, str, new_len, return g_strdup(str)); 1034 return g_strconcat(new_str, "...", NULL); 1035 } 1036 1037 GList *uri_list_extract_filenames(const gchar *uri_list) 1038 { 1039 GList *result = NULL; 1040 const gchar *p, *q; 1041 gchar *escaped_utf8uri; 1042 1043 p = uri_list; 1044 1045 while (p) { 1046 if (*p != '#') { 1047 while (g_ascii_isspace(*p)) p++; 1048 if (!strncmp(p, "file:", 5)) { 1049 q = p; 1050 q += 5; 1051 while (*q && *q != '\n' && *q != '\r') q++; 1052 1053 if (q > p) { 1054 gchar *file, *locale_file = NULL; 1055 q--; 1056 while (q > p && g_ascii_isspace(*q)) 1057 q--; 1058 Xalloca(escaped_utf8uri, q - p + 2, 1059 return result); 1060 Xalloca(file, q - p + 2, 1061 return result); 1062 *file = '\0'; 1063 strncpy(escaped_utf8uri, p, q - p + 1); 1064 escaped_utf8uri[q - p + 1] = '\0'; 1065 decode_uri_with_plus(file, escaped_utf8uri, FALSE); 1066 /* 1067 * g_filename_from_uri() rejects escaped/locale encoded uri 1068 * string which come from Nautilus. 1069 */ 1070 if (g_utf8_validate(file, -1, NULL)) 1071 locale_file 1072 = conv_codeset_strdup( 1073 file + 5, 1074 CS_UTF_8, 1075 conv_get_locale_charset_str()); 1076 if (!locale_file) 1077 locale_file = g_strdup(file + 5); 1078 result = g_list_append(result, locale_file); 1079 } 1080 } 1081 } 1082 p = strchr(p, '\n'); 1083 if (p) p++; 1084 } 1085 1086 return result; 1087 } 1088 1089 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped 1090 * characters 1091 */ 1092 static gint axtoi(const gchar *hexstr) 1093 { 1094 gint hi, lo, result; 1095 1096 hi = hexstr[0]; 1097 if ('0' <= hi && hi <= '9') { 1098 hi -= '0'; 1099 } else 1100 if ('a' <= hi && hi <= 'f') { 1101 hi -= ('a' - 10); 1102 } else 1103 if ('A' <= hi && hi <= 'F') { 1104 hi -= ('A' - 10); 1105 } 1106 1107 lo = hexstr[1]; 1108 if ('0' <= lo && lo <= '9') { 1109 lo -= '0'; 1110 } else 1111 if ('a' <= lo && lo <= 'f') { 1112 lo -= ('a'-10); 1113 } else 1114 if ('A' <= lo && lo <= 'F') { 1115 lo -= ('A' - 10); 1116 } 1117 result = lo + (16 * hi); 1118 return result; 1119 } 1120 1121 gboolean is_uri_string(const gchar *str) 1122 { 1123 while (str && *str && g_ascii_isspace(*str)) 1124 str++; 1125 return (g_ascii_strncasecmp(str, "http://", 7) == 0 || 1126 g_ascii_strncasecmp(str, "https://", 8) == 0 || 1127 g_ascii_strncasecmp(str, "ftp://", 6) == 0 || 1128 g_ascii_strncasecmp(str, "ftps://", 7) == 0 || 1129 g_ascii_strncasecmp(str, "sftp://", 7) == 0 || 1130 g_ascii_strncasecmp(str, "ftp.", 4) == 0 || 1131 g_ascii_strncasecmp(str, "webcal://", 9) == 0 || 1132 g_ascii_strncasecmp(str, "webcals://", 10) == 0 || 1133 g_ascii_strncasecmp(str, "www.", 4) == 0); 1134 } 1135 1136 gchar *get_uri_path(const gchar *uri) 1137 { 1138 while (uri && *uri && g_ascii_isspace(*uri)) 1139 uri++; 1140 if (g_ascii_strncasecmp(uri, "http://", 7) == 0) 1141 return (gchar *)(uri + 7); 1142 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0) 1143 return (gchar *)(uri + 8); 1144 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0) 1145 return (gchar *)(uri + 6); 1146 else if (g_ascii_strncasecmp(uri, "ftps://", 7) == 0) 1147 return (gchar *)(uri + 7); 1148 else if (g_ascii_strncasecmp(uri, "sftp://", 7) == 0) 1149 return (gchar *)(uri + 7); 1150 else if (g_ascii_strncasecmp(uri, "webcal://", 9) == 0) 1151 return (gchar *)(uri + 7); 1152 else if (g_ascii_strncasecmp(uri, "webcals://", 10) == 0) 1153 return (gchar *)(uri + 7); 1154 else 1155 return (gchar *)uri; 1156 } 1157 1158 gint get_uri_len(const gchar *str) 1159 { 1160 const gchar *p; 1161 1162 if (is_uri_string(str)) { 1163 for (p = str; *p != '\0'; p++) { 1164 if (g_ascii_isspace(*p) || strchr("<>\"", *p)) 1165 break; 1166 } 1167 return p - str; 1168 } 1169 1170 return 0; 1171 } 1172 1173 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by 1174 * plusses, and escape characters are used) 1175 */ 1176 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus) 1177 { 1178 gchar *dec = decoded_uri; 1179 const gchar *enc = encoded_uri; 1180 1181 while (*enc) { 1182 if (*enc == '%') { 1183 enc++; 1184 if (isxdigit((guchar)enc[0]) && 1185 isxdigit((guchar)enc[1])) { 1186 *dec = axtoi(enc); 1187 dec++; 1188 enc += 2; 1189 } 1190 } else { 1191 if (with_plus && *enc == '+') 1192 *dec = ' '; 1193 else 1194 *dec = *enc; 1195 dec++; 1196 enc++; 1197 } 1198 } 1199 1200 *dec = '\0'; 1201 } 1202 1203 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri) 1204 { 1205 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE); 1206 } 1207 1208 static gchar *decode_uri_gdup(const gchar *encoded_uri) 1209 { 1210 gchar *buffer = g_malloc(strlen(encoded_uri)+1); 1211 decode_uri_with_plus(buffer, encoded_uri, FALSE); 1212 return buffer; 1213 } 1214 1215 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc, 1216 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto) 1217 { 1218 gchar *tmp_mailto; 1219 gchar *p; 1220 const gchar *forbidden_uris[] = { ".gnupg/", 1221 "/etc/passwd", 1222 "/etc/shadow", 1223 ".ssh/", 1224 "../", 1225 NULL }; 1226 gint num_attach = 0; 1227 1228 cm_return_val_if_fail(mailto != NULL, -1); 1229 1230 Xstrdup_a(tmp_mailto, mailto, return -1); 1231 1232 if (!strncmp(tmp_mailto, "mailto:", 7)) 1233 tmp_mailto += 7; 1234 1235 p = strchr(tmp_mailto, '?'); 1236 if (p) { 1237 *p = '\0'; 1238 p++; 1239 } 1240 1241 if (to && !*to) 1242 *to = decode_uri_gdup(tmp_mailto); 1243 1244 while (p) { 1245 gchar *field, *value; 1246 1247 field = p; 1248 1249 p = strchr(p, '='); 1250 if (!p) break; 1251 *p = '\0'; 1252 p++; 1253 1254 value = p; 1255 1256 p = strchr(p, '&'); 1257 if (p) { 1258 *p = '\0'; 1259 p++; 1260 } 1261 1262 if (*value == '\0') continue; 1263 1264 if (from && !g_ascii_strcasecmp(field, "from")) { 1265 if (!*from) { 1266 *from = decode_uri_gdup(value); 1267 } else { 1268 gchar *tmp = decode_uri_gdup(value); 1269 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp); 1270 g_free(tmp); 1271 g_free(*from); 1272 *from = new_from; 1273 } 1274 } else if (cc && !g_ascii_strcasecmp(field, "cc")) { 1275 if (!*cc) { 1276 *cc = decode_uri_gdup(value); 1277 } else { 1278 gchar *tmp = decode_uri_gdup(value); 1279 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp); 1280 g_free(tmp); 1281 g_free(*cc); 1282 *cc = new_cc; 1283 } 1284 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) { 1285 if (!*bcc) { 1286 *bcc = decode_uri_gdup(value); 1287 } else { 1288 gchar *tmp = decode_uri_gdup(value); 1289 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp); 1290 g_free(tmp); 1291 g_free(*bcc); 1292 *bcc = new_bcc; 1293 } 1294 } else if (subject && !*subject && 1295 !g_ascii_strcasecmp(field, "subject")) { 1296 *subject = decode_uri_gdup(value); 1297 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) { 1298 *body = decode_uri_gdup(value); 1299 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) { 1300 int i = 0; 1301 gchar *tmp = decode_uri_gdup(value); 1302 1303 for (; forbidden_uris[i]; i++) { 1304 if (strstr(tmp, forbidden_uris[i])) { 1305 g_print("Refusing to insert '%s', potential private data leak\n", 1306 tmp); 1307 g_free(tmp); 1308 tmp = NULL; 1309 break; 1310 } 1311 } 1312 1313 if (tmp) { 1314 if (!is_file_entry_regular(tmp)) { 1315 g_warning("refusing to insert '%s', not a regular file", tmp); 1316 } else if (!g_file_get_contents(tmp, body, NULL, NULL)) { 1317 g_warning("couldn't set insert file '%s' in body", value); 1318 } 1319 1320 g_free(tmp); 1321 } 1322 } else if (attach && !g_ascii_strcasecmp(field, "attach")) { 1323 int i = 0; 1324 gchar *tmp = decode_uri_gdup(value); 1325 gchar **my_att = g_malloc(sizeof(char *)); 1326 1327 my_att[0] = NULL; 1328 1329 for (; forbidden_uris[i]; i++) { 1330 if (strstr(tmp, forbidden_uris[i])) { 1331 g_print("Refusing to attach '%s', potential private data leak\n", 1332 tmp); 1333 g_free(tmp); 1334 tmp = NULL; 1335 break; 1336 } 1337 } 1338 if (tmp) { 1339 /* attach is correct */ 1340 num_attach++; 1341 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1)); 1342 my_att[num_attach-1] = tmp; 1343 my_att[num_attach] = NULL; 1344 *attach = my_att; 1345 } 1346 else 1347 g_free(my_att); 1348 } else if (inreplyto && !*inreplyto && 1349 !g_ascii_strcasecmp(field, "in-reply-to")) { 1350 *inreplyto = decode_uri_gdup(value); 1351 } 1352 } 1353 1354 return 0; 1355 } 1356 1357 /* Return a static string with the locale dir. */ 1358 const gchar *get_locale_dir(void) 1359 { 1360 static gchar *loc_dir; 1361 if (!loc_dir) 1362 loc_dir = "/dev/null"; 1363 // loc_dir = LOCALEDIR; 1364 return loc_dir; 1365 } 1366 1367 1368 const gchar *get_home_dir(void) 1369 { 1370 static const gchar *homeenv = NULL; 1371 1372 if (homeenv) 1373 return homeenv; 1374 1375 if (!homeenv && g_getenv("HOME") != NULL) 1376 homeenv = g_strdup(g_getenv("HOME")); 1377 if (!homeenv) 1378 homeenv = g_get_home_dir(); 1379 1380 return homeenv; 1381 } 1382 1383 static gchar *claws_rc_dir = NULL; 1384 static gboolean rc_dir_alt = FALSE; 1385 const gchar *get_rc_dir(void) 1386 { 1387 1388 if (!claws_rc_dir) { 1389 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, 1390 RC_DIR, NULL); 1391 debug_print("using default rc_dir %s\n", claws_rc_dir); 1392 } 1393 return claws_rc_dir; 1394 } 1395 1396 void set_rc_dir(const gchar *dir) 1397 { 1398 gchar *canonical_dir; 1399 if (claws_rc_dir != NULL) { 1400 g_print("Error: rc_dir already set\n"); 1401 } else { 1402 int err = cm_canonicalize_filename(dir, &canonical_dir); 1403 int len; 1404 1405 if (err) { 1406 g_print("Error looking for %s: %d(%s)\n", 1407 dir, -err, g_strerror(-err)); 1408 exit(0); 1409 } 1410 rc_dir_alt = TRUE; 1411 1412 claws_rc_dir = canonical_dir; 1413 1414 len = strlen(claws_rc_dir); 1415 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR) 1416 claws_rc_dir[len - 1] = '\0'; 1417 1418 debug_print("set rc_dir to %s\n", claws_rc_dir); 1419 if (!is_dir_exist(claws_rc_dir)) { 1420 if (make_dir_hier(claws_rc_dir) != 0) { 1421 g_print("Error: can't create %s\n", 1422 claws_rc_dir); 1423 exit(0); 1424 } 1425 } 1426 } 1427 } 1428 1429 gboolean rc_dir_is_alt(void) { 1430 return rc_dir_alt; 1431 } 1432 1433 const gchar *get_mail_base_dir(void) 1434 { 1435 return get_home_dir(); 1436 } 1437 1438 const gchar *get_mime_tmp_dir(void) 1439 { 1440 static gchar *mime_tmp_dir = NULL; 1441 1442 if (!mime_tmp_dir) 1443 mime_tmp_dir = g_strconcat(get_rc_dir(), "/mimetmp", NULL); 1444 1445 return mime_tmp_dir; 1446 } 1447 1448 const gchar *get_template_dir(void) 1449 { 1450 static gchar *template_dir = NULL; 1451 1452 if (!template_dir) 1453 template_dir = g_strconcat(get_rc_dir(), "/templates", NULL); 1454 return template_dir; 1455 } 1456 1457 /* Return the filepath of the claws-mail.desktop file */ 1458 const gchar *get_desktop_file(void) 1459 { 1460 #ifdef DESKTOPFILEPATH 1461 return DESKTOPFILEPATH; 1462 #else 1463 return NULL; 1464 #endif 1465 } 1466 1467 const gchar *get_tmp_dir(void) 1468 { 1469 static gchar *tmp_dir = NULL; 1470 1471 if (!tmp_dir) 1472 tmp_dir = g_strconcat(get_rc_dir(), "/tmp", NULL); 1473 return tmp_dir; 1474 } 1475 1476 gchar *get_tmp_file(void) 1477 { 1478 gchar *tmp_file; 1479 static guint32 id = 0; 1480 1481 tmp_file = g_strdup_printf("%s%ctmpfile.%08x", 1482 get_tmp_dir(), G_DIR_SEPARATOR, id++); 1483 1484 return tmp_file; 1485 } 1486 1487 const gchar *get_domain_name(void) 1488 { 1489 #ifdef G_OS_UNIX 1490 static gchar *domain_name = NULL; 1491 struct addrinfo hints, *res; 1492 char hostname[256]; 1493 int s; 1494 1495 if (!domain_name) { 1496 if (gethostname(hostname, sizeof(hostname)) != 0) { 1497 perror("gethostname"); 1498 domain_name = "localhost"; 1499 } else { 1500 memset(&hints, 0, sizeof(struct addrinfo)); 1501 hints.ai_family = AF_UNSPEC; 1502 hints.ai_socktype = 0; 1503 hints.ai_flags = AI_CANONNAME; 1504 hints.ai_protocol = 0; 1505 1506 s = getaddrinfo(hostname, NULL, &hints, &res); 1507 if (s != 0) { 1508 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); 1509 domain_name = g_strdup(hostname); 1510 } else { 1511 domain_name = g_strdup(res->ai_canonname); 1512 freeaddrinfo(res); 1513 } 1514 } 1515 debug_print("domain name = %s\n", domain_name); 1516 } 1517 1518 return domain_name; 1519 #else 1520 return "localhost"; 1521 #endif 1522 } 1523 1524 /* Tells whether the given host address string is a valid representation of a 1525 * numerical IP address. 1526 */ 1527 gboolean is_numeric_host_address(const gchar *hostaddress) 1528 { 1529 struct addrinfo hints, *res; 1530 int err; 1531 1532 /* See what getaddrinfo makes of the string when told that it is a 1533 * numeric IP address representation. */ 1534 memset(&hints, 0, sizeof(struct addrinfo)); 1535 hints.ai_family = AF_UNSPEC; 1536 hints.ai_socktype = 0; 1537 hints.ai_flags = AI_NUMERICHOST; 1538 hints.ai_protocol = 0; 1539 1540 err = getaddrinfo(hostaddress, NULL, &hints, &res); 1541 if (err == 0) 1542 freeaddrinfo(res); 1543 1544 return (err == 0); 1545 } 1546 1547 off_t get_file_size(const gchar *file) 1548 { 1549 GStatBuf s; 1550 if (g_stat(file, &s) < 0) { 1551 FILE_OP_ERROR(file, "stat"); 1552 return -1; 1553 } 1554 return s.st_size; 1555 } 1556 1557 time_t get_file_mtime(const gchar *file) 1558 { 1559 GStatBuf s; 1560 1561 if (g_stat(file, &s) < 0) { 1562 FILE_OP_ERROR(file, "stat"); 1563 return -1; 1564 } 1565 1566 return s.st_mtime; 1567 } 1568 1569 gboolean file_exist(const gchar *file, gboolean allow_fifo) 1570 { 1571 GStatBuf s; 1572 1573 if (file == NULL) 1574 return FALSE; 1575 1576 if (g_stat(file, &s) < 0) { 1577 if (ENOENT != errno) FILE_OP_ERROR(file, "stat"); 1578 return FALSE; 1579 } 1580 1581 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode))) 1582 return TRUE; 1583 1584 return FALSE; 1585 } 1586 1587 1588 /* Test on whether FILE is a relative file name. */ 1589 gboolean is_relative_filename(const gchar *file) 1590 { 1591 if (!file) 1592 return TRUE; 1593 return !(*file == G_DIR_SEPARATOR); 1594 } 1595 1596 1597 gboolean is_dir_exist(const gchar *dir) 1598 { 1599 if (dir == NULL) 1600 return FALSE; 1601 1602 return g_file_test(dir, G_FILE_TEST_IS_DIR); 1603 } 1604 1605 gboolean is_file_entry_exist(const gchar *file) 1606 { 1607 if (file == NULL) 1608 return FALSE; 1609 1610 return g_file_test(file, G_FILE_TEST_EXISTS); 1611 } 1612 1613 gboolean is_file_entry_regular(const gchar *file) 1614 { 1615 if (file == NULL) 1616 return FALSE; 1617 1618 return g_file_test(file, G_FILE_TEST_IS_REGULAR); 1619 } 1620 1621 gint change_dir(const gchar *dir) 1622 { 1623 gchar *prevdir = NULL; 1624 1625 if (debug_mode) 1626 prevdir = g_get_current_dir(); 1627 1628 if (g_chdir(dir) < 0) { 1629 FILE_OP_ERROR(dir, "chdir"); 1630 if (debug_mode) g_free(prevdir); 1631 return -1; 1632 } else if (debug_mode) { 1633 gchar *cwd; 1634 1635 cwd = g_get_current_dir(); 1636 if (strcmp(prevdir, cwd) != 0) 1637 g_print("current dir: %s\n", cwd); 1638 g_free(cwd); 1639 g_free(prevdir); 1640 } 1641 1642 return 0; 1643 } 1644 1645 gint make_dir_hier(const gchar *dir) 1646 { 1647 gchar *parent_dir; 1648 const gchar *p; 1649 1650 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) { 1651 parent_dir = g_strndup(dir, p - dir); 1652 if (*parent_dir != '\0') { 1653 if (!is_dir_exist(parent_dir)) { 1654 if (mkdir(parent_dir, 0700) < 0) { 1655 g_free(parent_dir); 1656 return -1; 1657 } 1658 } 1659 } 1660 g_free(parent_dir); 1661 } 1662 1663 if (mkdir(dir, 0700) < 0 && errno != EEXIST) 1664 return -1; 1665 return 0; 1666 } 1667 1668 gint remove_all_files(const gchar *dir) 1669 { 1670 GDir *dp; 1671 const gchar *file_name; 1672 gchar *tmp; 1673 1674 if ((dp = g_dir_open(dir, 0, NULL)) == NULL) { 1675 g_warning("failed to open directory: %s", dir); 1676 return -1; 1677 } 1678 1679 while ((file_name = g_dir_read_name(dp)) != NULL) { 1680 tmp = g_strconcat(dir, G_DIR_SEPARATOR_S, file_name, NULL); 1681 if (unlink(tmp) < 0) 1682 FILE_OP_ERROR(tmp, "unlink"); 1683 g_free(tmp); 1684 } 1685 1686 g_dir_close(dp); 1687 1688 return 0; 1689 } 1690 1691 gint remove_numbered_files(const gchar *dir, guint first, guint last) 1692 { 1693 GDir *dp; 1694 const gchar *dir_name; 1695 gchar *prev_dir; 1696 gint file_no; 1697 1698 if (first == last) { 1699 /* Skip all the dir reading part. */ 1700 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first); 1701 if (is_dir_exist(filename)) { 1702 /* a numbered directory with this name exists, 1703 * remove the dot-file instead */ 1704 g_free(filename); 1705 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first); 1706 } 1707 if (unlink(filename) < 0) { 1708 FILE_OP_ERROR(filename, "unlink"); 1709 g_free(filename); 1710 return -1; 1711 } 1712 g_free(filename); 1713 return 0; 1714 } 1715 1716 prev_dir = g_get_current_dir(); 1717 1718 if (g_chdir(dir) < 0) { 1719 FILE_OP_ERROR(dir, "chdir"); 1720 g_free(prev_dir); 1721 return -1; 1722 } 1723 1724 if ((dp = g_dir_open(".", 0, NULL)) == NULL) { 1725 g_warning("failed to open directory: %s", dir); 1726 g_free(prev_dir); 1727 return -1; 1728 } 1729 1730 while ((dir_name = g_dir_read_name(dp)) != NULL) { 1731 file_no = atoi(dir_name); 1732 if (file_no > 0 && first <= file_no && file_no <= last) { 1733 if (is_dir_exist(dir_name)) { 1734 gchar *dot_file = g_strdup_printf(".%s", dir_name); 1735 if (is_file_exist(dot_file) && unlink(dot_file) < 0) { 1736 FILE_OP_ERROR(dot_file, "unlink"); 1737 } 1738 g_free(dot_file); 1739 continue; 1740 } 1741 if (unlink(dir_name) < 0) 1742 FILE_OP_ERROR(dir_name, "unlink"); 1743 } 1744 } 1745 1746 g_dir_close(dp); 1747 1748 if (g_chdir(prev_dir) < 0) { 1749 FILE_OP_ERROR(prev_dir, "chdir"); 1750 g_free(prev_dir); 1751 return -1; 1752 } 1753 1754 g_free(prev_dir); 1755 1756 return 0; 1757 } 1758 1759 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist) 1760 { 1761 GDir *dp; 1762 const gchar *dir_name; 1763 gchar *prev_dir; 1764 gint file_no; 1765 GHashTable *wanted_files; 1766 GSList *cur; 1767 GError *error = NULL; 1768 1769 if (numberlist == NULL) 1770 return 0; 1771 1772 prev_dir = g_get_current_dir(); 1773 1774 if (g_chdir(dir) < 0) { 1775 FILE_OP_ERROR(dir, "chdir"); 1776 g_free(prev_dir); 1777 return -1; 1778 } 1779 1780 if ((dp = g_dir_open(".", 0, &error)) == NULL) { 1781 g_message("Couldn't open current directory: %s (%d).\n", 1782 error->message, error->code); 1783 g_error_free(error); 1784 g_free(prev_dir); 1785 return -1; 1786 } 1787 1788 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal); 1789 for (cur = numberlist; cur != NULL; cur = cur->next) { 1790 /* numberlist->data is expected to be GINT_TO_POINTER */ 1791 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1)); 1792 } 1793 1794 while ((dir_name = g_dir_read_name(dp)) != NULL) { 1795 file_no = atoi(dir_name); 1796 if (is_dir_exist(dir_name)) 1797 continue; 1798 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) { 1799 debug_print("removing unwanted file %d from %s\n", file_no, dir); 1800 if (is_dir_exist(dir_name)) { 1801 gchar *dot_file = g_strdup_printf(".%s", dir_name); 1802 if (is_file_exist(dot_file) && unlink(dot_file) < 0) { 1803 FILE_OP_ERROR(dot_file, "unlink"); 1804 } 1805 g_free(dot_file); 1806 continue; 1807 } 1808 if (unlink(dir_name) < 0) 1809 FILE_OP_ERROR(dir_name, "unlink"); 1810 } 1811 } 1812 1813 g_dir_close(dp); 1814 g_hash_table_destroy(wanted_files); 1815 1816 if (g_chdir(prev_dir) < 0) { 1817 FILE_OP_ERROR(prev_dir, "chdir"); 1818 g_free(prev_dir); 1819 return -1; 1820 } 1821 1822 g_free(prev_dir); 1823 1824 return 0; 1825 } 1826 1827 gint remove_all_numbered_files(const gchar *dir) 1828 { 1829 return remove_numbered_files(dir, 0, UINT_MAX); 1830 } 1831 1832 gint remove_dir_recursive(const gchar *dir) 1833 { 1834 GStatBuf s; 1835 GDir *dp; 1836 const gchar *dir_name; 1837 gchar *prev_dir; 1838 1839 if (g_stat(dir, &s) < 0) { 1840 FILE_OP_ERROR(dir, "stat"); 1841 if (ENOENT == errno) return 0; 1842 return -(errno); 1843 } 1844 1845 if (!S_ISDIR(s.st_mode)) { 1846 if (unlink(dir) < 0) { 1847 FILE_OP_ERROR(dir, "unlink"); 1848 return -(errno); 1849 } 1850 1851 return 0; 1852 } 1853 1854 prev_dir = g_get_current_dir(); 1855 /* g_print("prev_dir = %s\n", prev_dir); */ 1856 1857 if (!path_cmp(prev_dir, dir)) { 1858 g_free(prev_dir); 1859 if (g_chdir("..") < 0) { 1860 FILE_OP_ERROR(dir, "chdir"); 1861 return -(errno); 1862 } 1863 prev_dir = g_get_current_dir(); 1864 } 1865 1866 if (g_chdir(dir) < 0) { 1867 FILE_OP_ERROR(dir, "chdir"); 1868 g_free(prev_dir); 1869 return -(errno); 1870 } 1871 1872 if ((dp = g_dir_open(".", 0, NULL)) == NULL) { 1873 g_warning("failed to open directory: %s", dir); 1874 g_chdir(prev_dir); 1875 g_free(prev_dir); 1876 return -(errno); 1877 } 1878 1879 /* remove all files in the directory */ 1880 while ((dir_name = g_dir_read_name(dp)) != NULL) { 1881 /* g_print("removing %s\n", dir_name); */ 1882 1883 if (is_dir_exist(dir_name)) { 1884 gint ret; 1885 1886 if ((ret = remove_dir_recursive(dir_name)) < 0) { 1887 g_warning("can't remove directory: %s", dir_name); 1888 g_dir_close(dp); 1889 return ret; 1890 } 1891 } else { 1892 if (unlink(dir_name) < 0) 1893 FILE_OP_ERROR(dir_name, "unlink"); 1894 } 1895 } 1896 1897 g_dir_close(dp); 1898 1899 if (g_chdir(prev_dir) < 0) { 1900 FILE_OP_ERROR(prev_dir, "chdir"); 1901 g_free(prev_dir); 1902 return -(errno); 1903 } 1904 1905 g_free(prev_dir); 1906 1907 if (g_rmdir(dir) < 0) { 1908 FILE_OP_ERROR(dir, "rmdir"); 1909 return -(errno); 1910 } 1911 1912 return 0; 1913 } 1914 1915 /* convert line endings into CRLF. If the last line doesn't end with 1916 * linebreak, add it. 1917 */ 1918 gchar *canonicalize_str(const gchar *str) 1919 { 1920 const gchar *p; 1921 guint new_len = 0; 1922 gchar *out, *outp; 1923 1924 for (p = str; *p != '\0'; ++p) { 1925 if (*p != '\r') { 1926 ++new_len; 1927 if (*p == '\n') 1928 ++new_len; 1929 } 1930 } 1931 if (p == str || *(p - 1) != '\n') 1932 new_len += 2; 1933 1934 out = outp = g_malloc(new_len + 1); 1935 for (p = str; *p != '\0'; ++p) { 1936 if (*p != '\r') { 1937 if (*p == '\n') 1938 *outp++ = '\r'; 1939 *outp++ = *p; 1940 } 1941 } 1942 if (p == str || *(p - 1) != '\n') { 1943 *outp++ = '\r'; 1944 *outp++ = '\n'; 1945 } 1946 *outp = '\0'; 1947 1948 return out; 1949 } 1950 1951 gchar *normalize_newlines(const gchar *str) 1952 { 1953 const gchar *p; 1954 gchar *out, *outp; 1955 1956 out = outp = g_malloc(strlen(str) + 1); 1957 for (p = str; *p != '\0'; ++p) { 1958 if (*p == '\r') { 1959 if (*(p + 1) != '\n') 1960 *outp++ = '\n'; 1961 } else 1962 *outp++ = *p; 1963 } 1964 1965 *outp = '\0'; 1966 1967 return out; 1968 } 1969 1970 gchar *get_outgoing_rfc2822_str(FILE *fp) 1971 { 1972 gchar buf[BUFFSIZE]; 1973 GString *str; 1974 1975 str = g_string_new(NULL); 1976 1977 /* output header part */ 1978 while (fgets(buf, sizeof(buf), fp) != NULL) { 1979 strretchomp(buf); 1980 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) { 1981 gint next; 1982 1983 for (;;) { 1984 next = fgetc(fp); 1985 if (next == EOF) 1986 break; 1987 else if (next != ' ' && next != '\t') { 1988 ungetc(next, fp); 1989 break; 1990 } 1991 if (fgets(buf, sizeof(buf), fp) == NULL) 1992 break; 1993 } 1994 } else { 1995 g_string_append(str, buf); 1996 g_string_append(str, "\r\n"); 1997 if (buf[0] == '\0') 1998 break; 1999 } 2000 } 2001 2002 /* output body part */ 2003 while (fgets(buf, sizeof(buf), fp) != NULL) { 2004 strretchomp(buf); 2005 if (buf[0] == '.') 2006 g_string_append_c(str, '.'); 2007 g_string_append(str, buf); 2008 g_string_append(str, "\r\n"); 2009 } 2010 2011 return g_string_free(str, FALSE); 2012 } 2013 2014 /* 2015 * Create a new boundary in a way that it is very unlikely that this 2016 * will occur in the following text. It would be easy to ensure 2017 * uniqueness if everything is either quoted-printable or base64 2018 * encoded (note that conversion is allowed), but because MIME bodies 2019 * may be nested, it may happen that the same boundary has already 2020 * been used. 2021 * 2022 * boundary := 0*69<bchars> bcharsnospace 2023 * bchars := bcharsnospace / " " 2024 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / 2025 * "+" / "_" / "," / "-" / "." / 2026 * "/" / ":" / "=" / "?" 2027 * 2028 * some special characters removed because of buggy MTAs 2029 */ 2030 2031 gchar *generate_mime_boundary(const gchar *prefix) 2032 { 2033 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 2034 "abcdefghijklmnopqrstuvwxyz" 2035 "1234567890+_./="; 2036 gchar buf_uniq[24]; 2037 gint i; 2038 2039 for (i = 0; i < sizeof(buf_uniq) - 1; i++) 2040 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)]; 2041 buf_uniq[i] = '\0'; 2042 2043 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP", 2044 buf_uniq); 2045 } 2046 2047 char *fgets_crlf(char *buf, int size, FILE *stream) 2048 { 2049 gboolean is_cr = FALSE; 2050 gboolean last_was_cr = FALSE; 2051 int c = 0; 2052 char *cs; 2053 2054 cs = buf; 2055 while (--size > 0 && (c = getc(stream)) != EOF) 2056 { 2057 *cs++ = c; 2058 is_cr = (c == '\r'); 2059 if (c == '\n') { 2060 break; 2061 } 2062 if (last_was_cr) { 2063 *(--cs) = '\n'; 2064 cs++; 2065 ungetc(c, stream); 2066 break; 2067 } 2068 last_was_cr = is_cr; 2069 } 2070 if (c == EOF && cs == buf) 2071 return NULL; 2072 2073 *cs = '\0'; 2074 2075 return buf; 2076 } 2077 2078 static gint execute_async(gchar *const argv[], const gchar *working_directory) 2079 { 2080 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); 2081 2082 if (g_spawn_async(working_directory, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH, 2083 NULL, NULL, NULL, FALSE) == FALSE) { 2084 g_warning("couldn't execute command: %s", argv[0]); 2085 return -1; 2086 } 2087 2088 return 0; 2089 } 2090 2091 static gint execute_sync(gchar *const argv[], const gchar *working_directory) 2092 { 2093 gint status; 2094 2095 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1); 2096 2097 #ifdef G_OS_UNIX 2098 if (g_spawn_sync(working_directory, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH, 2099 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) { 2100 g_warning("couldn't execute command: %s", argv[0]); 2101 return -1; 2102 } 2103 2104 if (WIFEXITED(status)) 2105 return WEXITSTATUS(status); 2106 else 2107 return -1; 2108 #else 2109 if (g_spawn_sync(working_directory, (gchar **)argv, NULL, 2110 G_SPAWN_SEARCH_PATH| 2111 G_SPAWN_CHILD_INHERITS_STDIN| 2112 G_SPAWN_LEAVE_DESCRIPTORS_OPEN, 2113 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) { 2114 g_warning("couldn't execute command: %s", argv[0]); 2115 return -1; 2116 } 2117 2118 return status; 2119 #endif 2120 } 2121 2122 gint execute_command_line(const gchar *cmdline, gboolean async, 2123 const gchar *working_directory) 2124 { 2125 gchar **argv; 2126 gint ret; 2127 2128 cm_return_val_if_fail(cmdline != NULL, -1); 2129 2130 debug_print("execute_command_line(): executing: %s\n", cmdline); 2131 2132 argv = strsplit_with_quote(cmdline, " ", 0); 2133 2134 if (async) 2135 ret = execute_async(argv, working_directory); 2136 else 2137 ret = execute_sync(argv, working_directory); 2138 2139 g_strfreev(argv); 2140 2141 return ret; 2142 } 2143 2144 gchar *get_command_output(const gchar *cmdline) 2145 { 2146 gchar *child_stdout; 2147 gint status; 2148 2149 cm_return_val_if_fail(cmdline != NULL, NULL); 2150 2151 debug_print("get_command_output(): executing: %s\n", cmdline); 2152 2153 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status, 2154 NULL) == FALSE) { 2155 g_warning("couldn't execute command: %s", cmdline); 2156 return NULL; 2157 } 2158 2159 return child_stdout; 2160 } 2161 2162 FILE *get_command_output_stream(const char* cmdline) 2163 { 2164 GPid pid; 2165 GError *err = NULL; 2166 gchar **argv = NULL; 2167 int fd; 2168 2169 cm_return_val_if_fail(cmdline != NULL, NULL); 2170 2171 debug_print("get_command_output_stream(): executing: %s\n", cmdline); 2172 2173 /* turn the command-line string into an array */ 2174 if (!g_shell_parse_argv(cmdline, NULL, &argv, &err)) { 2175 g_warning("could not parse command line from '%s': %s", cmdline, err->message); 2176 g_error_free(err); 2177 return NULL; 2178 } 2179 2180 if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, 2181 NULL, NULL, &pid, NULL, &fd, NULL, &err) 2182 && err) 2183 { 2184 g_warning("could not spawn '%s': %s", cmdline, err->message); 2185 g_error_free(err); 2186 g_strfreev(argv); 2187 return NULL; 2188 } 2189 2190 g_strfreev(argv); 2191 return fdopen(fd, "r"); 2192 } 2193 2194 static gint is_unchanged_uri_char(char c) 2195 { 2196 switch (c) { 2197 case '(': 2198 case ')': 2199 return 0; 2200 default: 2201 return 1; 2202 } 2203 } 2204 2205 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri) 2206 { 2207 int i; 2208 int k; 2209 2210 k = 0; 2211 for(i = 0; i < strlen(uri) ; i++) { 2212 if (is_unchanged_uri_char(uri[i])) { 2213 if (k + 2 >= bufsize) 2214 break; 2215 encoded_uri[k++] = uri[i]; 2216 } 2217 else { 2218 char * hexa = "0123456789ABCDEF"; 2219 2220 if (k + 4 >= bufsize) 2221 break; 2222 encoded_uri[k++] = '%'; 2223 encoded_uri[k++] = hexa[uri[i] / 16]; 2224 encoded_uri[k++] = hexa[uri[i] % 16]; 2225 } 2226 } 2227 encoded_uri[k] = 0; 2228 } 2229 2230 gint open_uri(const gchar *uri, const gchar *cmdline) 2231 { 2232 2233 gchar buf[BUFFSIZE]; 2234 gchar *p; 2235 gchar encoded_uri[BUFFSIZE]; 2236 cm_return_val_if_fail(uri != NULL, -1); 2237 2238 /* an option to choose whether to use encode_uri or not ? */ 2239 encode_uri(encoded_uri, BUFFSIZE, uri); 2240 2241 if (cmdline && 2242 (p = strchr(cmdline, '%')) && *(p + 1) == 's' && 2243 !strchr(p + 2, '%')) 2244 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri); 2245 else { 2246 if (cmdline) 2247 g_warning("Open URI command-line is invalid " 2248 "(there must be only one '%%s'): %s", 2249 cmdline); 2250 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri); 2251 } 2252 2253 execute_command_line(buf, TRUE, NULL); 2254 return 0; 2255 } 2256 2257 gint open_txt_editor(const gchar *filepath, const gchar *cmdline) 2258 { 2259 gchar buf[BUFFSIZE]; 2260 gchar *p; 2261 2262 cm_return_val_if_fail(filepath != NULL, -1); 2263 2264 if (cmdline && 2265 (p = strchr(cmdline, '%')) && *(p + 1) == 's' && 2266 !strchr(p + 2, '%')) 2267 g_snprintf(buf, sizeof(buf), cmdline, filepath); 2268 else { 2269 if (cmdline) 2270 g_warning("Open Text Editor command-line is invalid " 2271 "(there must be only one '%%s'): %s", 2272 cmdline); 2273 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath); 2274 } 2275 2276 execute_command_line(buf, TRUE, NULL); 2277 2278 return 0; 2279 } 2280 2281 time_t remote_tzoffset_sec(const gchar *zone) 2282 { 2283 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT"; 2284 gchar zone3[4]; 2285 gchar *p; 2286 gchar c; 2287 gint iustz; 2288 gint offset; 2289 time_t remoteoffset; 2290 2291 strncpy(zone3, zone, 3); 2292 zone3[3] = '\0'; 2293 remoteoffset = 0; 2294 2295 if (sscanf(zone, "%c%d", &c, &offset) == 2 && 2296 (c == '+' || c == '-')) { 2297 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60; 2298 if (c == '-') 2299 remoteoffset = -remoteoffset; 2300 } else if (!strncmp(zone, "UT" , 2) || 2301 !strncmp(zone, "GMT", 3)) { 2302 remoteoffset = 0; 2303 } else if (strlen(zone3) == 3) { 2304 for (p = ustzstr; *p != '\0'; p += 3) { 2305 if (!g_ascii_strncasecmp(p, zone3, 3)) { 2306 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8; 2307 remoteoffset = iustz * 3600; 2308 break; 2309 } 2310 } 2311 if (*p == '\0') 2312 return -1; 2313 } else if (strlen(zone3) == 1) { 2314 switch (zone[0]) { 2315 case 'Z': remoteoffset = 0; break; 2316 case 'A': remoteoffset = -1; break; 2317 case 'B': remoteoffset = -2; break; 2318 case 'C': remoteoffset = -3; break; 2319 case 'D': remoteoffset = -4; break; 2320 case 'E': remoteoffset = -5; break; 2321 case 'F': remoteoffset = -6; break; 2322 case 'G': remoteoffset = -7; break; 2323 case 'H': remoteoffset = -8; break; 2324 case 'I': remoteoffset = -9; break; 2325 case 'K': remoteoffset = -10; break; /* J is not used */ 2326 case 'L': remoteoffset = -11; break; 2327 case 'M': remoteoffset = -12; break; 2328 case 'N': remoteoffset = 1; break; 2329 case 'O': remoteoffset = 2; break; 2330 case 'P': remoteoffset = 3; break; 2331 case 'Q': remoteoffset = 4; break; 2332 case 'R': remoteoffset = 5; break; 2333 case 'S': remoteoffset = 6; break; 2334 case 'T': remoteoffset = 7; break; 2335 case 'U': remoteoffset = 8; break; 2336 case 'V': remoteoffset = 9; break; 2337 case 'W': remoteoffset = 10; break; 2338 case 'X': remoteoffset = 11; break; 2339 case 'Y': remoteoffset = 12; break; 2340 default: remoteoffset = 0; break; 2341 } 2342 remoteoffset = remoteoffset * 3600; 2343 } else 2344 return -1; 2345 2346 return remoteoffset; 2347 } 2348 2349 time_t tzoffset_sec(time_t *now) 2350 { 2351 struct tm gmt, *lt; 2352 gint off; 2353 struct tm buf1, buf2; 2354 2355 gmt = *gmtime_r(now, &buf1); 2356 lt = localtime_r(now, &buf2); 2357 2358 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; 2359 2360 if (lt->tm_year < gmt.tm_year) 2361 off -= 24 * 60; 2362 else if (lt->tm_year > gmt.tm_year) 2363 off += 24 * 60; 2364 else if (lt->tm_yday < gmt.tm_yday) 2365 off -= 24 * 60; 2366 else if (lt->tm_yday > gmt.tm_yday) 2367 off += 24 * 60; 2368 2369 if (off >= 24 * 60) /* should be impossible */ 2370 off = 23 * 60 + 59; /* if not, insert silly value */ 2371 if (off <= -24 * 60) 2372 off = -(23 * 60 + 59); 2373 2374 return off * 60; 2375 } 2376 2377 /* calculate timezone offset */ 2378 gchar *tzoffset(time_t *now) 2379 { 2380 static gchar offset_string[6]; 2381 struct tm gmt, *lt; 2382 gint off; 2383 gchar sign = '+'; 2384 struct tm buf1, buf2; 2385 2386 gmt = *gmtime_r(now, &buf1); 2387 lt = localtime_r(now, &buf2); 2388 2389 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min; 2390 2391 if (lt->tm_year < gmt.tm_year) 2392 off -= 24 * 60; 2393 else if (lt->tm_year > gmt.tm_year) 2394 off += 24 * 60; 2395 else if (lt->tm_yday < gmt.tm_yday) 2396 off -= 24 * 60; 2397 else if (lt->tm_yday > gmt.tm_yday) 2398 off += 24 * 60; 2399 2400 if (off < 0) { 2401 sign = '-'; 2402 off = -off; 2403 } 2404 2405 if (off >= 24 * 60) /* should be impossible */ 2406 off = 23 * 60 + 59; /* if not, insert silly value */ 2407 2408 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60); 2409 2410 return offset_string; 2411 } 2412 2413 static void _get_rfc822_date(gchar *buf, gint len, gboolean hidetz) 2414 { 2415 struct tm *lt; 2416 time_t t; 2417 gchar day[4], mon[4]; 2418 gint dd, hh, mm, ss, yyyy; 2419 struct tm buf1; 2420 gchar buf2[RFC822_DATE_BUFFSIZE]; 2421 2422 t = time(NULL); 2423 if (hidetz) 2424 lt = gmtime_r(&t, &buf1); 2425 else 2426 lt = localtime_r(&t, &buf1); 2427 2428 if (sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n", 2429 day, mon, &dd, &hh, &mm, &ss, &yyyy) != 7) 2430 g_warning("failed reading date/time"); 2431 2432 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s", 2433 day, dd, mon, yyyy, hh, mm, ss, (hidetz? "-0000": tzoffset(&t))); 2434 } 2435 2436 void get_rfc822_date(gchar *buf, gint len) 2437 { 2438 _get_rfc822_date(buf, len, FALSE); 2439 } 2440 2441 void get_rfc822_date_hide_tz(gchar *buf, gint len) 2442 { 2443 _get_rfc822_date(buf, len, TRUE); 2444 } 2445 2446 void debug_set_mode(gboolean mode) 2447 { 2448 debug_mode = mode; 2449 } 2450 2451 gboolean debug_get_mode(void) 2452 { 2453 return debug_mode; 2454 } 2455 2456 void debug_print_real(const gchar *format, ...) { 2457 if (!debug_mode) 2458 return; 2459 2460 va_list args; 2461 gchar buf[BUFFSIZE]; 2462 2463 va_start(args, format); 2464 g_vsnprintf(buf, sizeof(buf), format, args); 2465 va_end(args); 2466 fprintf(stderr, "%s\n", buf); 2467 } 2468 2469 const char * debug_srcname(const char *file) 2470 { 2471 const char *s = strrchr (file, '/'); 2472 return s? s+1:file; 2473 } 2474 2475 2476 void * subject_table_lookup(GHashTable *subject_table, gchar * subject) 2477 { 2478 if (subject == NULL) 2479 subject = ""; 2480 else 2481 subject += subject_get_prefix_length(subject); 2482 2483 return g_hash_table_lookup(subject_table, subject); 2484 } 2485 2486 void subject_table_insert(GHashTable *subject_table, gchar * subject, 2487 void * data) 2488 { 2489 if (subject == NULL || *subject == 0) 2490 return; 2491 subject += subject_get_prefix_length(subject); 2492 g_hash_table_insert(subject_table, subject, data); 2493 } 2494 2495 void subject_table_remove(GHashTable *subject_table, gchar * subject) 2496 { 2497 if (subject == NULL) 2498 return; 2499 2500 subject += subject_get_prefix_length(subject); 2501 g_hash_table_remove(subject_table, subject); 2502 } 2503 2504 static regex_t u_regex; 2505 static gboolean u_init_; 2506 2507 void utils_free_regex(void) 2508 { 2509 if (u_init_) { 2510 regfree(&u_regex); 2511 u_init_ = FALSE; 2512 } 2513 } 2514 2515 /*! 2516 *\brief Check if a string is prefixed with known (combinations) 2517 * of prefixes. The function assumes that each prefix 2518 * is terminated by zero or exactly _one_ space. 2519 * 2520 *\param str String to check for a prefixes 2521 * 2522 *\return int Number of chars in the prefix that should be skipped 2523 * for a "clean" subject line. If no prefix was found, 0 2524 * is returned. 2525 */ 2526 int subject_get_prefix_length(const gchar *subject) 2527 { 2528 /*!< Array with allowable reply prefixes regexps. */ 2529 static const gchar * const prefixes[] = { 2530 "Re\\:", /* "Re:" */ 2531 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */ 2532 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */ 2533 "Aw\\:", /* "Aw:" (German) */ 2534 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */ 2535 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */ 2536 "Fw\\:", /* "Fw:" Forward */ 2537 "Fwd\\:", /* "Fwd:" Forward */ 2538 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */ 2539 "Odp\\:", /* "Odp:" Re (Polish Outlook) */ 2540 "Rif\\:", /* "Rif:" (Italian Outlook) */ 2541 "Sv\\:", /* "Sv" (Norwegian) */ 2542 "Vs\\:", /* "Vs" (Norwegian) */ 2543 "Ad\\:", /* "Ad" (Norwegian) */ 2544 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */ 2545 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */ 2546 "Re \\:", /* "Re :" (French Yahoo Mail) */ 2547 /* add more */ 2548 }; 2549 const int PREFIXES = sizeof prefixes / sizeof prefixes[0]; 2550 int n; 2551 regmatch_t pos; 2552 2553 if (!subject) return 0; 2554 if (!*subject) return 0; 2555 2556 if (!u_init_) { 2557 GString *s = g_string_new(""); 2558 2559 for (n = 0; n < PREFIXES; n++) 2560 /* Terminate each prefix regexpression by a 2561 * "\ ?" (zero or ONE space), and OR them */ 2562 g_string_append_printf(s, "(%s\\ ?)%s", 2563 prefixes[n], 2564 n < PREFIXES - 1 ? 2565 "|" : ""); 2566 2567 g_string_prepend(s, "("); 2568 g_string_append(s, ")+"); /* match at least once */ 2569 g_string_prepend(s, "^\\ *"); /* from beginning of line */ 2570 2571 2572 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+" 2573 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */ 2574 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) { 2575 debug_print("Error compiling regexp %s\n", s->str); 2576 g_string_free(s, TRUE); 2577 return 0; 2578 } else { 2579 u_init_ = TRUE; 2580 g_string_free(s, TRUE); 2581 } 2582 } 2583 2584 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1) 2585 return pos.rm_eo; 2586 else 2587 return 0; 2588 } 2589 2590 gint g_int_compare(gconstpointer a, gconstpointer b) 2591 { 2592 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b); 2593 } 2594 2595 /* 2596 quote_cmd_argument() 2597 2598 return a quoted string safely usable in argument of a command. 2599 2600 code is extracted and adapted from etPan! project -- DINH V. Ho�. 2601 */ 2602 2603 gint quote_cmd_argument(gchar * result, guint size, 2604 const gchar * path) 2605 { 2606 const gchar * p; 2607 gchar * result_p; 2608 guint remaining; 2609 2610 result_p = result; 2611 remaining = size; 2612 2613 for(p = path ; * p != '\0' ; p ++) { 2614 2615 if (isalnum((guchar)*p) || (* p == '/')) { 2616 if (remaining > 0) { 2617 * result_p = * p; 2618 result_p ++; 2619 remaining --; 2620 } 2621 else { 2622 result[size - 1] = '\0'; 2623 return -1; 2624 } 2625 } 2626 else { 2627 if (remaining >= 2) { 2628 * result_p = '\\'; 2629 result_p ++; 2630 * result_p = * p; 2631 result_p ++; 2632 remaining -= 2; 2633 } 2634 else { 2635 result[size - 1] = '\0'; 2636 return -1; 2637 } 2638 } 2639 } 2640 if (remaining > 0) { 2641 * result_p = '\0'; 2642 } 2643 else { 2644 result[size - 1] = '\0'; 2645 return -1; 2646 } 2647 2648 return 0; 2649 } 2650 2651 typedef struct 2652 { 2653 GNode *parent; 2654 GNodeMapFunc func; 2655 gpointer data; 2656 } GNodeMapData; 2657 2658 static void g_node_map_recursive(GNode *node, gpointer data) 2659 { 2660 GNodeMapData *mapdata = (GNodeMapData *) data; 2661 GNode *newnode; 2662 GNodeMapData newmapdata; 2663 gpointer newdata; 2664 2665 newdata = mapdata->func(node->data, mapdata->data); 2666 if (newdata != NULL) { 2667 newnode = g_node_new(newdata); 2668 g_node_append(mapdata->parent, newnode); 2669 2670 newmapdata.parent = newnode; 2671 newmapdata.func = mapdata->func; 2672 newmapdata.data = mapdata->data; 2673 2674 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata); 2675 } 2676 } 2677 2678 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data) 2679 { 2680 GNode *root; 2681 GNodeMapData mapdata; 2682 2683 cm_return_val_if_fail(node != NULL, NULL); 2684 cm_return_val_if_fail(func != NULL, NULL); 2685 2686 root = g_node_new(func(node->data, data)); 2687 2688 mapdata.parent = root; 2689 mapdata.func = func; 2690 mapdata.data = data; 2691 2692 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata); 2693 2694 return root; 2695 } 2696 2697 #define HEX_TO_INT(val, hex) \ 2698 { \ 2699 gchar c = hex; \ 2700 \ 2701 if ('0' <= c && c <= '9') { \ 2702 val = c - '0'; \ 2703 } else if ('a' <= c && c <= 'f') { \ 2704 val = c - 'a' + 10; \ 2705 } else if ('A' <= c && c <= 'F') { \ 2706 val = c - 'A' + 10; \ 2707 } else { \ 2708 val = -1; \ 2709 } \ 2710 } 2711 2712 gboolean get_hex_value(guchar *out, gchar c1, gchar c2) 2713 { 2714 gint hi, lo; 2715 2716 HEX_TO_INT(hi, c1); 2717 HEX_TO_INT(lo, c2); 2718 2719 if (hi == -1 || lo == -1) 2720 return FALSE; 2721 2722 *out = (hi << 4) + lo; 2723 return TRUE; 2724 } 2725 2726 #define INT_TO_HEX(hex, val) \ 2727 { \ 2728 if ((val) < 10) \ 2729 hex = '0' + (val); \ 2730 else \ 2731 hex = 'A' + (val) - 10; \ 2732 } 2733 2734 void get_hex_str(gchar *out, guchar ch) 2735 { 2736 gchar hex; 2737 2738 INT_TO_HEX(hex, ch >> 4); 2739 *out++ = hex; 2740 INT_TO_HEX(hex, ch & 0x0f); 2741 *out = hex; 2742 } 2743 2744 #undef REF_DEBUG 2745 #ifndef REF_DEBUG 2746 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void) 2747 #else 2748 #define G_PRINT_REF g_print 2749 #endif 2750 2751 /*! 2752 *\brief Register ref counted pointer. It is based on GBoxed, so should 2753 * work with anything that uses the GType system. The semantics 2754 * are similar to a C++ auto pointer, with the exception that 2755 * C doesn't have automatic closure (calling destructors) when 2756 * exiting a block scope. 2757 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this 2758 * function directly. 2759 * 2760 *\return GType A GType type. 2761 */ 2762 GType g_auto_pointer_register(void) 2763 { 2764 static GType auto_pointer_type; 2765 if (!auto_pointer_type) 2766 auto_pointer_type = 2767 g_boxed_type_register_static 2768 ("G_TYPE_AUTO_POINTER", 2769 (GBoxedCopyFunc) g_auto_pointer_copy, 2770 (GBoxedFreeFunc) g_auto_pointer_free); 2771 return auto_pointer_type; 2772 } 2773 2774 /*! 2775 *\brief Structure with g_new() allocated pointer guarded by the 2776 * auto pointer 2777 */ 2778 typedef struct AutoPointerRef { 2779 void (*free) (gpointer); 2780 gpointer pointer; 2781 glong cnt; 2782 } AutoPointerRef; 2783 2784 /*! 2785 *\brief The auto pointer opaque structure that references the 2786 * pointer guard block. 2787 */ 2788 typedef struct AutoPointer { 2789 AutoPointerRef *ref; 2790 gpointer ptr; /*!< access to protected pointer */ 2791 } AutoPointer; 2792 2793 /*! 2794 *\brief Creates an auto pointer for a g_new()ed pointer. Example: 2795 * 2796 *\code 2797 * 2798 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER 2799 * ... when assigning, copying and freeing storage elements 2800 * 2801 * gtk_list_store_new(N_S_COLUMNS, 2802 * G_TYPE_AUTO_POINTER, 2803 * -1); 2804 * 2805 * 2806 * Template *precious_data = g_new0(Template, 1); 2807 * g_pointer protect = g_auto_pointer_new(precious_data); 2808 * 2809 * gtk_list_store_set(container, &iter, 2810 * S_DATA, protect, 2811 * -1); 2812 * 2813 * ... the gtk_list_store has copied the pointer and 2814 * ... incremented its reference count, we should free 2815 * ... the auto pointer (in C++ a destructor would do 2816 * ... this for us when leaving block scope) 2817 * 2818 * g_auto_pointer_free(protect); 2819 * 2820 * ... gtk_list_store_set() now manages the data. When 2821 * ... *explicitly* requesting a pointer from the list 2822 * ... store, don't forget you get a copy that should be 2823 * ... freed with g_auto_pointer_free() eventually. 2824 * 2825 *\endcode 2826 * 2827 *\param pointer Pointer to be guarded. 2828 * 2829 *\return GAuto * Pointer that should be used in containers with 2830 * GType support. 2831 */ 2832 GAuto *g_auto_pointer_new(gpointer p) 2833 { 2834 AutoPointerRef *ref; 2835 AutoPointer *ptr; 2836 2837 if (p == NULL) 2838 return NULL; 2839 2840 ref = g_new0(AutoPointerRef, 1); 2841 ptr = g_new0(AutoPointer, 1); 2842 2843 ref->pointer = p; 2844 ref->free = g_free; 2845 ref->cnt = 1; 2846 2847 ptr->ref = ref; 2848 ptr->ptr = p; 2849 2850 #ifdef REF_DEBUG 2851 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p); 2852 #endif 2853 return ptr; 2854 } 2855 2856 /*! 2857 *\brief Allocate an autopointer using the passed \a free function to 2858 * free the guarded pointer 2859 */ 2860 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_) 2861 { 2862 AutoPointer *aptr; 2863 2864 if (p == NULL) 2865 return NULL; 2866 2867 aptr = g_auto_pointer_new(p); 2868 aptr->ref->free = free_; 2869 return aptr; 2870 } 2871 2872 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr) 2873 { 2874 if (auto_ptr == NULL) 2875 return NULL; 2876 return ((AutoPointer *) auto_ptr)->ptr; 2877 } 2878 2879 /*! 2880 *\brief Copies an auto pointer by. It's mostly not necessary 2881 * to call this function directly, unless you copy/assign 2882 * the guarded pointer. 2883 * 2884 *\param auto_ptr Auto pointer returned by previous call to 2885 * g_auto_pointer_new_XXX() 2886 * 2887 *\return gpointer An auto pointer 2888 */ 2889 GAuto *g_auto_pointer_copy(GAuto *auto_ptr) 2890 { 2891 AutoPointer *ptr; 2892 AutoPointerRef *ref; 2893 AutoPointer *newp; 2894 2895 if (auto_ptr == NULL) 2896 return NULL; 2897 2898 ptr = auto_ptr; 2899 ref = ptr->ref; 2900 newp = g_new0(AutoPointer, 1); 2901 2902 newp->ref = ref; 2903 newp->ptr = ref->pointer; 2904 ++(ref->cnt); 2905 2906 #ifdef REF_DEBUG 2907 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt); 2908 #endif 2909 return newp; 2910 } 2911 2912 /*! 2913 *\brief Free an auto pointer 2914 */ 2915 void g_auto_pointer_free(GAuto *auto_ptr) 2916 { 2917 AutoPointer *ptr; 2918 AutoPointerRef *ref; 2919 2920 if (auto_ptr == NULL) 2921 return; 2922 2923 ptr = auto_ptr; 2924 ref = ptr->ref; 2925 2926 if (--(ref->cnt) == 0) { 2927 #ifdef REF_DEBUG 2928 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt); 2929 #endif 2930 ref->free(ref->pointer); 2931 g_free(ref); 2932 } 2933 #ifdef REF_DEBUG 2934 else 2935 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt); 2936 #endif 2937 g_free(ptr); 2938 } 2939 2940 /* get_uri_part() - retrieves a URI starting from scanpos. 2941 Returns TRUE if successful */ 2942 gboolean get_uri_part(const gchar *start, const gchar *scanpos, 2943 const gchar **bp, const gchar **ep, gboolean hdr) 2944 { 2945 const gchar *ep_; 2946 gint parenthese_cnt = 0; 2947 2948 cm_return_val_if_fail(start != NULL, FALSE); 2949 cm_return_val_if_fail(scanpos != NULL, FALSE); 2950 cm_return_val_if_fail(bp != NULL, FALSE); 2951 cm_return_val_if_fail(ep != NULL, FALSE); 2952 2953 *bp = scanpos; 2954 2955 /* find end point of URI */ 2956 for (ep_ = scanpos; *ep_ != '\0'; ep_ = g_utf8_next_char(ep_)) { 2957 gunichar u = g_utf8_get_char_validated(ep_, -1); 2958 if (!g_unichar_isgraph(u) || 2959 u == (gunichar)-1 || 2960 strchr("[]{}<>\"", *ep_)) { 2961 break; 2962 } else if (strchr("(", *ep_)) { 2963 parenthese_cnt++; 2964 } else if (strchr(")", *ep_)) { 2965 if (parenthese_cnt > 0) 2966 parenthese_cnt--; 2967 else 2968 break; 2969 } 2970 } 2971 2972 /* no punctuation at end of string */ 2973 2974 /* FIXME: this stripping of trailing punctuations may bite with other URIs. 2975 * should pass some URI type to this function and decide on that whether 2976 * to perform punctuation stripping */ 2977 2978 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("$/?=-_~)", ch)) 2979 2980 for (; ep_ - 1 > scanpos + 1 && 2981 IS_REAL_PUNCT(*(ep_ - 1)); 2982 ep_--) 2983 ; 2984 2985 #undef IS_REAL_PUNCT 2986 2987 *ep = ep_; 2988 2989 return TRUE; 2990 } 2991 2992 gchar *make_uri_string(const gchar *bp, const gchar *ep) 2993 { 2994 while (bp && *bp && g_ascii_isspace(*bp)) 2995 bp++; 2996 return g_strndup(bp, ep - bp); 2997 } 2998 2999 /* valid mail address characters */ 3000 #define IS_RFC822_CHAR(ch) \ 3001 (IS_ASCII(ch) && \ 3002 (ch) > 32 && \ 3003 (ch) != 127 && \ 3004 !g_ascii_isspace(ch) && \ 3005 !strchr("(),;<>\"", (ch))) 3006 3007 /* alphabet and number within 7bit ASCII */ 3008 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch)) 3009 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"') 3010 3011 static gboolean is_toplvl_domain(const gchar *first, const gchar *last) 3012 { 3013 gchar buf[BUFFSIZE + 1]; 3014 const gchar *m = buf + BUFFSIZE + 1; 3015 register gchar *p; 3016 3017 if (last - first > BUFFSIZE || first > last) 3018 return FALSE; 3019 3020 for (p = buf; p < m && first < last; *p++ = *first++) 3021 ; 3022 *p = 0; 3023 3024 return fence_is_top_level_domain(buf); 3025 } 3026 3027 /* get_email_part() - retrieves an email address. Returns TRUE if successful */ 3028 gboolean get_email_part(const gchar *start, const gchar *scanpos, 3029 const gchar **bp, const gchar **ep, gboolean hdr) 3030 { 3031 /* more complex than the uri part because we need to scan back and forward starting from 3032 * the scan position. */ 3033 gboolean result = FALSE; 3034 const gchar *bp_ = NULL; 3035 const gchar *ep_ = NULL; 3036 const gchar *last_dot = NULL; 3037 const gchar *prelast_dot = NULL; 3038 const gchar *last_tld_char = NULL; 3039 3040 /* the informative part of the email address (describing the name 3041 * of the email address owner) may contain quoted parts. the 3042 * closure stack stores the last encountered quotes. */ 3043 gchar closure_stack[128]; 3044 gchar *ptr = closure_stack; 3045 3046 cm_return_val_if_fail(start != NULL, FALSE); 3047 cm_return_val_if_fail(scanpos != NULL, FALSE); 3048 cm_return_val_if_fail(bp != NULL, FALSE); 3049 cm_return_val_if_fail(ep != NULL, FALSE); 3050 3051 if (hdr) { 3052 const gchar *start_quote = NULL; 3053 const gchar *end_quote = NULL; 3054 search_again: 3055 /* go to the real start */ 3056 if (start[0] == ',') 3057 start++; 3058 if (start[0] == ';') 3059 start++; 3060 while (start[0] == '\n' || start[0] == '\r') 3061 start++; 3062 while (start[0] == ' ' || start[0] == '\t') 3063 start++; 3064 3065 *bp = start; 3066 3067 /* check if there are quotes (to skip , in them) */ 3068 if (*start == '"') { 3069 start_quote = start; 3070 start++; 3071 end_quote = strstr(start, "\""); 3072 } else { 3073 start_quote = NULL; 3074 end_quote = NULL; 3075 } 3076 3077 /* skip anything between quotes */ 3078 if (start_quote && end_quote) { 3079 start = end_quote; 3080 3081 } 3082 3083 /* find end (either , or ; or end of line) */ 3084 if (strstr(start, ",") && strstr(start, ";")) 3085 *ep = strstr(start,",") < strstr(start, ";") 3086 ? strstr(start, ",") : strstr(start, ";"); 3087 else if (strstr(start, ",")) 3088 *ep = strstr(start, ","); 3089 else if (strstr(start, ";")) 3090 *ep = strstr(start, ";"); 3091 else 3092 *ep = start+strlen(start); 3093 3094 /* go back to real start */ 3095 if (start_quote && end_quote) { 3096 start = start_quote; 3097 } 3098 3099 /* check there's still an @ in that, or search 3100 * further if possible */ 3101 if (strstr(start, "@") && strstr(start, "@") < *ep) 3102 return TRUE; 3103 else if (*ep < start+strlen(start)) { 3104 start = *ep; 3105 goto search_again; 3106 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) { 3107 *bp = start_quote; 3108 return TRUE; 3109 } else 3110 return FALSE; 3111 } 3112 3113 /* scan start of address */ 3114 for (bp_ = scanpos - 1; 3115 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--) 3116 ; 3117 3118 /* TODO: should start with an alnum? */ 3119 bp_++; 3120 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++) 3121 ; 3122 3123 if (bp_ != scanpos) { 3124 /* scan end of address */ 3125 for (ep_ = scanpos + 1; 3126 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++) 3127 if (*ep_ == '.') { 3128 prelast_dot = last_dot; 3129 last_dot = ep_; 3130 if (*(last_dot + 1) == '.') { 3131 if (prelast_dot == NULL) 3132 return FALSE; 3133 last_dot = prelast_dot; 3134 break; 3135 } 3136 } 3137 3138 /* TODO: really should terminate with an alnum? */ 3139 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_); 3140 --ep_) 3141 ; 3142 ep_++; 3143 3144 if (last_dot == NULL) 3145 return FALSE; 3146 if (last_dot >= ep_) 3147 last_dot = prelast_dot; 3148 if (last_dot == NULL || (scanpos + 1 >= last_dot)) 3149 return FALSE; 3150 last_dot++; 3151 3152 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++) 3153 if (*last_tld_char == '?') 3154 break; 3155 3156 result = is_toplvl_domain(last_dot, last_tld_char); 3157 3158 *ep = ep_; 3159 *bp = bp_; 3160 } 3161 3162 if (!result) return FALSE; 3163 3164 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"' 3165 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<' 3166 && IS_RFC822_CHAR(*(ep_ + 3))) { 3167 /* this informative part with an @ in it is 3168 * followed by the email address */ 3169 ep_ += 3; 3170 3171 /* go to matching '>' (or next non-rfc822 char, like \n) */ 3172 for (; *ep_ != '>' && *ep_ != '\0' && IS_RFC822_CHAR(*ep_); ep_++) 3173 ; 3174 3175 /* include the bracket */ 3176 if (*ep_ == '>') ep_++; 3177 3178 /* include the leading quote */ 3179 bp_--; 3180 3181 *ep = ep_; 3182 *bp = bp_; 3183 return TRUE; 3184 } 3185 3186 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */ 3187 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_)) 3188 return FALSE; 3189 3190 /* see if this is <bracketed>; in this case we also scan for the informative part. */ 3191 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>') 3192 return TRUE; 3193 3194 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack) 3195 #define IN_STACK() (ptr > closure_stack) 3196 /* has underrun check */ 3197 #define POP_STACK() if(IN_STACK()) --ptr 3198 /* has overrun check */ 3199 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE 3200 /* has underrun check */ 3201 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0) 3202 3203 ep_++; 3204 3205 /* scan for the informative part. */ 3206 for (bp_ -= 2; bp_ >= start; bp_--) { 3207 /* if closure on the stack keep scanning */ 3208 if (PEEK_STACK() == *bp_) { 3209 POP_STACK(); 3210 continue; 3211 } 3212 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) { 3213 PUSH_STACK(*bp_); 3214 continue; 3215 } 3216 3217 /* if nothing in the closure stack, do the special conditions 3218 * the following if..else expression simply checks whether 3219 * a token is acceptable. if not acceptable, the clause 3220 * should terminate the loop with a 'break' */ 3221 if (!PEEK_STACK()) { 3222 if (*bp_ == '-' 3223 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1))) 3224 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) { 3225 /* hyphens are allowed, but only in 3226 between alnums */ 3227 } else if (strchr(" \"'", *bp_)) { 3228 /* but anything not being a punctiation 3229 is ok */ 3230 } else { 3231 break; /* anything else is rejected */ 3232 } 3233 } 3234 } 3235 3236 bp_++; 3237 3238 /* scan forward (should start with an alnum) */ 3239 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++) 3240 ; 3241 #undef PEEK_STACK 3242 #undef PUSH_STACK 3243 #undef POP_STACK 3244 #undef IN_STACK 3245 #undef FULL_STACK 3246 3247 3248 *bp = bp_; 3249 *ep = ep_; 3250 3251 return result; 3252 } 3253 3254 #undef IS_QUOTE 3255 #undef IS_ASCII_ALNUM 3256 #undef IS_RFC822_CHAR 3257 3258 gchar *make_email_string(const gchar *bp, const gchar *ep) 3259 { 3260 /* returns a mailto: URI; mailto: is also used to detect the 3261 * uri type later on in the button_pressed signal handler */ 3262 gchar *tmp; 3263 gchar *result; 3264 gchar *colon, *at; 3265 3266 tmp = g_strndup(bp, ep - bp); 3267 3268 /* If there is a colon in the username part of the address, 3269 * we're dealing with an URI for some other protocol - do 3270 * not prefix with mailto: in such case. */ 3271 colon = strchr(tmp, ':'); 3272 at = strchr(tmp, '@'); 3273 if (colon != NULL && at != NULL && colon < at) { 3274 result = tmp; 3275 } else { 3276 result = g_strconcat("mailto:", tmp, NULL); 3277 g_free(tmp); 3278 } 3279 3280 return result; 3281 } 3282 3283 gchar *make_http_string(const gchar *bp, const gchar *ep) 3284 { 3285 /* returns an http: URI; */ 3286 gchar *tmp; 3287 gchar *result; 3288 3289 while (bp && *bp && g_ascii_isspace(*bp)) 3290 bp++; 3291 tmp = g_strndup(bp, ep - bp); 3292 result = g_strconcat("http://", tmp, NULL); 3293 g_free(tmp); 3294 3295 return result; 3296 } 3297 3298 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open) 3299 { 3300 FILE *fp = g_fopen(path, "rb"); 3301 gchar buf[BUFFSIZE]; 3302 gchar *result = NULL; 3303 if (!fp) 3304 return NULL; 3305 while (fgets(buf, sizeof (buf), fp) != NULL) { 3306 gchar **parts = g_strsplit(buf, ";", 3); 3307 gchar *trimmed = parts[0]; 3308 while (trimmed[0] == ' ' || trimmed[0] == '\t') 3309 trimmed++; 3310 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t') 3311 trimmed[strlen(trimmed)-1] = '\0'; 3312 3313 if (!strcmp(trimmed, type)) { 3314 gboolean needsterminal = FALSE; 3315 if (parts[2] && strstr(parts[2], "needsterminal")) { 3316 needsterminal = TRUE; 3317 } 3318 if (parts[2] && strstr(parts[2], "test=")) { 3319 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5); 3320 gchar *testcmd = orig_testcmd; 3321 if (strstr(testcmd,";")) 3322 *(strstr(testcmd,";")) = '\0'; 3323 while (testcmd[0] == ' ' || testcmd[0] == '\t') 3324 testcmd++; 3325 while (testcmd[strlen(testcmd)-1] == '\n') 3326 testcmd[strlen(testcmd)-1] = '\0'; 3327 while (testcmd[strlen(testcmd)-1] == '\r') 3328 testcmd[strlen(testcmd)-1] = '\0'; 3329 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t') 3330 testcmd[strlen(testcmd)-1] = '\0'; 3331 3332 if (strstr(testcmd, "%s")) { 3333 gchar *tmp = g_strdup_printf(testcmd, file_to_open); 3334 gint res = system(tmp); 3335 g_free(tmp); 3336 g_free(orig_testcmd); 3337 3338 if (res != 0) { 3339 g_strfreev(parts); 3340 continue; 3341 } 3342 } else { 3343 gint res = system(testcmd); 3344 g_free(orig_testcmd); 3345 3346 if (res != 0) { 3347 g_strfreev(parts); 3348 continue; 3349 } 3350 } 3351 } 3352 3353 trimmed = parts[1]; 3354 while (trimmed[0] == ' ' || trimmed[0] == '\t') 3355 trimmed++; 3356 while (trimmed[strlen(trimmed)-1] == '\n') 3357 trimmed[strlen(trimmed)-1] = '\0'; 3358 while (trimmed[strlen(trimmed)-1] == '\r') 3359 trimmed[strlen(trimmed)-1] = '\0'; 3360 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t') 3361 trimmed[strlen(trimmed)-1] = '\0'; 3362 result = g_strdup(trimmed); 3363 g_strfreev(parts); 3364 fclose(fp); 3365 if (needsterminal) { 3366 gchar *tmp = g_strdup_printf("xterm -e %s", result); 3367 g_free(result); 3368 result = tmp; 3369 } 3370 return result; 3371 } 3372 g_strfreev(parts); 3373 } 3374 fclose(fp); 3375 return NULL; 3376 } 3377 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open) 3378 { 3379 gchar *result = NULL; 3380 gchar *path = NULL; 3381 if (type == NULL) 3382 return NULL; 3383 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL); 3384 result = mailcap_get_command_in_file(path, type, file_to_open); 3385 g_free(path); 3386 if (result) 3387 return result; 3388 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open); 3389 return result; 3390 } 3391 3392 void mailcap_update_default(const gchar *type, const gchar *command) 3393 { 3394 gchar *path = NULL, *outpath = NULL; 3395 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL); 3396 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL); 3397 FILE *fp = g_fopen(path, "rb"); 3398 FILE *outfp = NULL; 3399 gchar buf[BUFFSIZE]; 3400 gboolean err = FALSE; 3401 3402 if (!fp) { 3403 fp = g_fopen(path, "a"); 3404 if (!fp) { 3405 g_warning("failed to create file %s", path); 3406 g_free(path); 3407 g_free(outpath); 3408 return; 3409 } 3410 fp = g_freopen(path, "rb", fp); 3411 if (!fp) { 3412 g_warning("failed to reopen file %s", path); 3413 g_free(path); 3414 g_free(outpath); 3415 return; 3416 } 3417 } 3418 3419 outfp = g_fopen(outpath, "wb"); 3420 if (!outfp) { 3421 g_warning("failed to create file %s", outpath); 3422 g_free(path); 3423 g_free(outpath); 3424 fclose(fp); 3425 return; 3426 } 3427 while (fp && fgets(buf, sizeof (buf), fp) != NULL) { 3428 gchar **parts = g_strsplit(buf, ";", 3); 3429 gchar *trimmed = parts[0]; 3430 while (trimmed[0] == ' ') 3431 trimmed++; 3432 while (trimmed[strlen(trimmed)-1] == ' ') 3433 trimmed[strlen(trimmed)-1] = '\0'; 3434 3435 if (!strcmp(trimmed, type)) { 3436 g_strfreev(parts); 3437 continue; 3438 } 3439 else { 3440 if(fputs(buf, outfp) == EOF) { 3441 err = TRUE; 3442 g_strfreev(parts); 3443 break; 3444 } 3445 } 3446 g_strfreev(parts); 3447 } 3448 if (fprintf(outfp, "%s; %s\n", type, command) < 0) 3449 err = TRUE; 3450 3451 if (fp) 3452 fclose(fp); 3453 3454 if (fclose(outfp) == EOF) 3455 err = TRUE; 3456 3457 if (!err) 3458 g_rename(outpath, path); 3459 3460 g_free(path); 3461 g_free(outpath); 3462 } 3463 3464 /* crude test to see if a file is an email. */ 3465 gboolean file_is_email (const gchar *filename) 3466 { 3467 FILE *fp = NULL; 3468 gchar buffer[2048]; 3469 gint score = 0; 3470 if (filename == NULL) 3471 return FALSE; 3472 if ((fp = g_fopen(filename, "rb")) == NULL) 3473 return FALSE; 3474 while (score < 3 3475 && fgets(buffer, sizeof (buffer), fp) != NULL) { 3476 if (!strncasecmp(buffer, "From:", strlen("From:"))) 3477 score++; 3478 else if (!strncasecmp(buffer, "Date:", strlen("Date:"))) 3479 score++; 3480 else if (!strncasecmp(buffer, "Message-ID:", strlen("Message-ID:"))) 3481 score++; 3482 else if (!strncasecmp(buffer, "Subject:", strlen("Subject:"))) 3483 score++; 3484 else if (!strcmp(buffer, "\r\n")) { 3485 debug_print("End of headers\n"); 3486 break; 3487 } 3488 } 3489 fclose(fp); 3490 return (score >= 3); 3491 } 3492 3493 gboolean sc_g_list_bigger(GList *list, gint max) 3494 { 3495 GList *cur = list; 3496 int i = 0; 3497 while (cur && i <= max+1) { 3498 i++; 3499 cur = cur->next; 3500 } 3501 return (i > max); 3502 } 3503 3504 gboolean sc_g_slist_bigger(GSList *list, gint max) 3505 { 3506 GSList *cur = list; 3507 int i = 0; 3508 while (cur && i <= max+1) { 3509 i++; 3510 cur = cur->next; 3511 } 3512 return (i > max); 3513 } 3514 3515 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL}; 3516 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 3517 NULL, NULL, NULL, NULL, NULL, NULL}; 3518 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL}; 3519 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 3520 NULL, NULL, NULL, NULL, NULL, NULL}; 3521 3522 gint daynames_len[] = {0,0,0,0,0,0,0}; 3523 gint monthnames_len[] = {0,0,0,0,0,0, 3524 0,0,0,0,0,0}; 3525 gint s_daynames_len[] = {0,0,0,0,0,0,0}; 3526 gint s_monthnames_len[] = {0,0,0,0,0,0, 3527 0,0,0,0,0,0}; 3528 const gchar *s_am_up = NULL; 3529 const gchar *s_pm_up = NULL; 3530 const gchar *s_am_low = NULL; 3531 const gchar *s_pm_low = NULL; 3532 3533 gint s_am_up_len = 0; 3534 gint s_pm_up_len = 0; 3535 gint s_am_low_len = 0; 3536 gint s_pm_low_len = 0; 3537 3538 static gboolean time_names_init_done = FALSE; 3539 3540 static void init_time_names(void) 3541 { 3542 int i = 0; 3543 3544 daynames[0] = C_("Complete day name for use by strftime", "Sunday"); 3545 daynames[1] = C_("Complete day name for use by strftime", "Monday"); 3546 daynames[2] = C_("Complete day name for use by strftime", "Tuesday"); 3547 daynames[3] = C_("Complete day name for use by strftime", "Wednesday"); 3548 daynames[4] = C_("Complete day name for use by strftime", "Thursday"); 3549 daynames[5] = C_("Complete day name for use by strftime", "Friday"); 3550 daynames[6] = C_("Complete day name for use by strftime", "Saturday"); 3551 3552 monthnames[0] = C_("Complete month name for use by strftime", "January"); 3553 monthnames[1] = C_("Complete month name for use by strftime", "February"); 3554 monthnames[2] = C_("Complete month name for use by strftime", "March"); 3555 monthnames[3] = C_("Complete month name for use by strftime", "April"); 3556 monthnames[4] = C_("Complete month name for use by strftime", "May"); 3557 monthnames[5] = C_("Complete month name for use by strftime", "June"); 3558 monthnames[6] = C_("Complete month name for use by strftime", "July"); 3559 monthnames[7] = C_("Complete month name for use by strftime", "August"); 3560 monthnames[8] = C_("Complete month name for use by strftime", "September"); 3561 monthnames[9] = C_("Complete month name for use by strftime", "October"); 3562 monthnames[10] = C_("Complete month name for use by strftime", "November"); 3563 monthnames[11] = C_("Complete month name for use by strftime", "December"); 3564 3565 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun"); 3566 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon"); 3567 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue"); 3568 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed"); 3569 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu"); 3570 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri"); 3571 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat"); 3572 3573 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan"); 3574 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb"); 3575 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar"); 3576 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr"); 3577 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May"); 3578 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun"); 3579 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul"); 3580 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug"); 3581 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep"); 3582 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct"); 3583 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov"); 3584 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec"); 3585 3586 for (i = 0; i < 7; i++) { 3587 daynames_len[i] = strlen(daynames[i]); 3588 s_daynames_len[i] = strlen(s_daynames[i]); 3589 } 3590 for (i = 0; i < 12; i++) { 3591 monthnames_len[i] = strlen(monthnames[i]); 3592 s_monthnames_len[i] = strlen(s_monthnames[i]); 3593 } 3594 3595 s_am_up = C_("For use by strftime (morning)", "AM"); 3596 s_pm_up = C_("For use by strftime (afternoon)", "PM"); 3597 s_am_low = C_("For use by strftime (morning, lowercase)", "am"); 3598 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm"); 3599 3600 s_am_up_len = strlen(s_am_up); 3601 s_pm_up_len = strlen(s_pm_up); 3602 s_am_low_len = strlen(s_am_low); 3603 s_pm_low_len = strlen(s_pm_low); 3604 3605 time_names_init_done = TRUE; 3606 } 3607 3608 #define CHECK_SIZE() { \ 3609 total_done += len; \ 3610 if (total_done >= buflen) { \ 3611 buf[buflen-1] = '\0'; \ 3612 return 0; \ 3613 } \ 3614 } 3615 3616 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt) 3617 { 3618 gchar *curpos = buf; 3619 gint total_done = 0; 3620 gchar subbuf[64], subfmt[64]; 3621 static time_t last_tzset = (time_t)0; 3622 3623 if (!time_names_init_done) 3624 init_time_names(); 3625 3626 if (format == NULL || lt == NULL) 3627 return 0; 3628 3629 if (last_tzset != time(NULL)) { 3630 tzset(); 3631 last_tzset = time(NULL); 3632 } 3633 while(*format) { 3634 if (*format == '%') { 3635 gint len = 0, tmp = 0; 3636 format++; 3637 switch(*format) { 3638 case '%': 3639 len = 1; CHECK_SIZE(); 3640 *curpos = '%'; 3641 break; 3642 case 'a': 3643 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE(); 3644 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done); 3645 break; 3646 case 'A': 3647 len = daynames_len[lt->tm_wday]; CHECK_SIZE(); 3648 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done); 3649 break; 3650 case 'b': 3651 case 'h': 3652 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE(); 3653 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done); 3654 break; 3655 case 'B': 3656 len = monthnames_len[lt->tm_mon]; CHECK_SIZE(); 3657 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done); 3658 break; 3659 case 'c': 3660 strftime(subbuf, 64, "%c", lt); 3661 len = strlen(subbuf); CHECK_SIZE(); 3662 strncpy2(curpos, subbuf, buflen - total_done); 3663 break; 3664 case 'C': 3665 total_done += 2; CHECK_SIZE(); 3666 tmp = (lt->tm_year + 1900)/100; 3667 *curpos++ = '0'+(tmp / 10); 3668 *curpos++ = '0'+(tmp % 10); 3669 break; 3670 case 'd': 3671 total_done += 2; CHECK_SIZE(); 3672 *curpos++ = '0'+(lt->tm_mday / 10); 3673 *curpos++ = '0'+(lt->tm_mday % 10); 3674 break; 3675 case 'D': 3676 total_done += 8; CHECK_SIZE(); 3677 *curpos++ = '0'+((lt->tm_mon+1) / 10); 3678 *curpos++ = '0'+((lt->tm_mon+1) % 10); 3679 *curpos++ = '/'; 3680 *curpos++ = '0'+(lt->tm_mday / 10); 3681 *curpos++ = '0'+(lt->tm_mday % 10); 3682 *curpos++ = '/'; 3683 tmp = lt->tm_year%100; 3684 *curpos++ = '0'+(tmp / 10); 3685 *curpos++ = '0'+(tmp % 10); 3686 break; 3687 case 'e': 3688 len = 2; CHECK_SIZE(); 3689 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday); 3690 break; 3691 case 'F': 3692 len = 10; CHECK_SIZE(); 3693 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 3694 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday); 3695 break; 3696 case 'H': 3697 total_done += 2; CHECK_SIZE(); 3698 *curpos++ = '0'+(lt->tm_hour / 10); 3699 *curpos++ = '0'+(lt->tm_hour % 10); 3700 break; 3701 case 'I': 3702 total_done += 2; CHECK_SIZE(); 3703 tmp = lt->tm_hour; 3704 if (tmp > 12) 3705 tmp -= 12; 3706 else if (tmp == 0) 3707 tmp = 12; 3708 *curpos++ = '0'+(tmp / 10); 3709 *curpos++ = '0'+(tmp % 10); 3710 break; 3711 case 'j': 3712 len = 3; CHECK_SIZE(); 3713 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1); 3714 break; 3715 case 'k': 3716 len = 2; CHECK_SIZE(); 3717 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour); 3718 break; 3719 case 'l': 3720 len = 2; CHECK_SIZE(); 3721 tmp = lt->tm_hour; 3722 if (tmp > 12) 3723 tmp -= 12; 3724 else if (tmp == 0) 3725 tmp = 12; 3726 snprintf(curpos, buflen - total_done, "%2d", tmp); 3727 break; 3728 case 'm': 3729 total_done += 2; CHECK_SIZE(); 3730 tmp = lt->tm_mon + 1; 3731 *curpos++ = '0'+(tmp / 10); 3732 *curpos++ = '0'+(tmp % 10); 3733 break; 3734 case 'M': 3735 total_done += 2; CHECK_SIZE(); 3736 *curpos++ = '0'+(lt->tm_min / 10); 3737 *curpos++ = '0'+(lt->tm_min % 10); 3738 break; 3739 case 'n': 3740 len = 1; CHECK_SIZE(); 3741 *curpos = '\n'; 3742 break; 3743 case 'p': 3744 if (lt->tm_hour >= 12) { 3745 len = s_pm_up_len; CHECK_SIZE(); 3746 snprintf(curpos, buflen-total_done, "%s", s_pm_up); 3747 } else { 3748 len = s_am_up_len; CHECK_SIZE(); 3749 snprintf(curpos, buflen-total_done, "%s", s_am_up); 3750 } 3751 break; 3752 case 'P': 3753 if (lt->tm_hour >= 12) { 3754 len = s_pm_low_len; CHECK_SIZE(); 3755 snprintf(curpos, buflen-total_done, "%s", s_pm_low); 3756 } else { 3757 len = s_am_low_len; CHECK_SIZE(); 3758 snprintf(curpos, buflen-total_done, "%s", s_am_low); 3759 } 3760 break; 3761 case 'r': 3762 strftime(subbuf, 64, "%r", lt); 3763 len = strlen(subbuf); CHECK_SIZE(); 3764 strncpy2(curpos, subbuf, buflen - total_done); 3765 break; 3766 case 'R': 3767 total_done += 5; CHECK_SIZE(); 3768 *curpos++ = '0'+(lt->tm_hour / 10); 3769 *curpos++ = '0'+(lt->tm_hour % 10); 3770 *curpos++ = ':'; 3771 *curpos++ = '0'+(lt->tm_min / 10); 3772 *curpos++ = '0'+(lt->tm_min % 10); 3773 break; 3774 case 's': 3775 snprintf(subbuf, 64, "%lld", mktime(lt)); 3776 len = strlen(subbuf); CHECK_SIZE(); 3777 strncpy2(curpos, subbuf, buflen - total_done); 3778 break; 3779 case 'S': 3780 total_done += 2; CHECK_SIZE(); 3781 *curpos++ = '0'+(lt->tm_sec / 10); 3782 *curpos++ = '0'+(lt->tm_sec % 10); 3783 break; 3784 case 't': 3785 len = 1; CHECK_SIZE(); 3786 *curpos = '\t'; 3787 break; 3788 case 'T': 3789 total_done += 8; CHECK_SIZE(); 3790 *curpos++ = '0'+(lt->tm_hour / 10); 3791 *curpos++ = '0'+(lt->tm_hour % 10); 3792 *curpos++ = ':'; 3793 *curpos++ = '0'+(lt->tm_min / 10); 3794 *curpos++ = '0'+(lt->tm_min % 10); 3795 *curpos++ = ':'; 3796 *curpos++ = '0'+(lt->tm_sec / 10); 3797 *curpos++ = '0'+(lt->tm_sec % 10); 3798 break; 3799 case 'u': 3800 len = 1; CHECK_SIZE(); 3801 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday); 3802 break; 3803 case 'w': 3804 len = 1; CHECK_SIZE(); 3805 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday); 3806 break; 3807 case 'x': 3808 strftime(subbuf, 64, "%x", lt); 3809 len = strlen(subbuf); CHECK_SIZE(); 3810 strncpy2(curpos, subbuf, buflen - total_done); 3811 break; 3812 case 'X': 3813 strftime(subbuf, 64, "%X", lt); 3814 len = strlen(subbuf); CHECK_SIZE(); 3815 strncpy2(curpos, subbuf, buflen - total_done); 3816 break; 3817 case 'y': 3818 total_done += 2; CHECK_SIZE(); 3819 tmp = lt->tm_year%100; 3820 *curpos++ = '0'+(tmp / 10); 3821 *curpos++ = '0'+(tmp % 10); 3822 break; 3823 case 'Y': 3824 len = 4; CHECK_SIZE(); 3825 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900); 3826 break; 3827 case 'G': 3828 case 'g': 3829 case 'U': 3830 case 'V': 3831 case 'W': 3832 case 'z': 3833 case 'Z': 3834 case '+': 3835 /* let these complicated ones be done with the libc */ 3836 snprintf(subfmt, 64, "%%%c", *format); 3837 strftime(subbuf, 64, subfmt, lt); 3838 len = strlen(subbuf); CHECK_SIZE(); 3839 strncpy2(curpos, subbuf, buflen - total_done); 3840 break; 3841 case 'E': 3842 case 'O': 3843 /* let these complicated modifiers be done with the libc */ 3844 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1)); 3845 strftime(subbuf, 64, subfmt, lt); 3846 len = strlen(subbuf); CHECK_SIZE(); 3847 strncpy2(curpos, subbuf, buflen - total_done); 3848 format++; 3849 break; 3850 default: 3851 g_warning("format error (%c)", *format); 3852 *curpos = '\0'; 3853 return total_done; 3854 } 3855 curpos += len; 3856 format++; 3857 } else { 3858 int len = 1; CHECK_SIZE(); 3859 *curpos++ = *format++; 3860 } 3861 } 3862 *curpos = '\0'; 3863 return total_done; 3864 } 3865 3866 static gchar *canonical_list_to_file(GSList *list) 3867 { 3868 GString *result = g_string_new(NULL); 3869 GSList *pathlist = g_slist_reverse(g_slist_copy(list)); 3870 GSList *cur; 3871 3872 result = g_string_append(result, G_DIR_SEPARATOR_S); 3873 for (cur = pathlist; cur; cur = cur->next) { 3874 result = g_string_append(result, (gchar *)cur->data); 3875 if (cur->next) 3876 result = g_string_append(result, G_DIR_SEPARATOR_S); 3877 } 3878 g_slist_free(pathlist); 3879 3880 return g_string_free(result, FALSE); 3881 } 3882 3883 static GSList *cm_split_path(const gchar *filename, int depth) 3884 { 3885 gchar **path_parts; 3886 GSList *canonical_parts = NULL; 3887 GStatBuf st; 3888 int i; 3889 gboolean follow_symlinks = TRUE; 3890 3891 if (depth > 32) { 3892 errno = EINVAL; /* can't happen, no symlink handling */ 3893 return NULL; 3894 } 3895 3896 if (!g_path_is_absolute(filename)) { 3897 errno =EINVAL; 3898 return NULL; 3899 } 3900 3901 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1); 3902 3903 for (i = 0; path_parts[i] != NULL; i++) { 3904 if (!strcmp(path_parts[i], "")) 3905 continue; 3906 if (!strcmp(path_parts[i], ".")) 3907 continue; 3908 else if (!strcmp(path_parts[i], "..")) { 3909 if (i == 0) { 3910 errno =ENOTDIR; 3911 g_strfreev(path_parts); 3912 return NULL; 3913 } 3914 else /* Remove the last inserted element */ 3915 canonical_parts = 3916 g_slist_delete_link(canonical_parts, 3917 canonical_parts); 3918 } else { 3919 gchar *tmp_path; 3920 3921 canonical_parts = g_slist_prepend(canonical_parts, 3922 g_strdup(path_parts[i])); 3923 3924 tmp_path = canonical_list_to_file(canonical_parts); 3925 3926 if(g_stat(tmp_path, &st) < 0) { 3927 if (errno == ENOENT) { 3928 errno = 0; 3929 follow_symlinks = FALSE; 3930 } 3931 if (errno != 0) { 3932 g_free(tmp_path); 3933 slist_free_strings_full(canonical_parts); 3934 g_strfreev(path_parts); 3935 3936 return NULL; 3937 } 3938 } 3939 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) { 3940 GError *error = NULL; 3941 gchar *target = g_file_read_link(tmp_path, &error); 3942 3943 if (!g_path_is_absolute(target)) { 3944 /* remove the last inserted element */ 3945 canonical_parts = 3946 g_slist_delete_link(canonical_parts, 3947 canonical_parts); 3948 /* add the target */ 3949 canonical_parts = g_slist_prepend(canonical_parts, 3950 g_strdup(target)); 3951 g_free(target); 3952 3953 /* and get the new target */ 3954 target = canonical_list_to_file(canonical_parts); 3955 } 3956 3957 /* restart from absolute target */ 3958 slist_free_strings_full(canonical_parts); 3959 canonical_parts = NULL; 3960 if (!error) 3961 canonical_parts = cm_split_path(target, depth + 1); 3962 else 3963 g_error_free(error); 3964 if (canonical_parts == NULL) { 3965 g_free(tmp_path); 3966 g_strfreev(path_parts); 3967 return NULL; 3968 } 3969 g_free(target); 3970 } 3971 g_free(tmp_path); 3972 } 3973 } 3974 g_strfreev(path_parts); 3975 return canonical_parts; 3976 } 3977 3978 /* 3979 * Canonicalize a filename, resolving symlinks along the way. 3980 * Returns a negative errno in case of error. 3981 */ 3982 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) { 3983 GSList *canonical_parts; 3984 gboolean is_absolute; 3985 3986 if (filename == NULL) 3987 return -EINVAL; 3988 if (canonical_name == NULL) 3989 return -EINVAL; 3990 *canonical_name = NULL; 3991 3992 is_absolute = g_path_is_absolute(filename); 3993 if (!is_absolute) { 3994 /* Always work on absolute filenames. */ 3995 gchar *cur = g_get_current_dir(); 3996 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S, 3997 filename, NULL); 3998 3999 canonical_parts = cm_split_path(absolute_filename, 0); 4000 g_free(absolute_filename); 4001 g_free(cur); 4002 } else 4003 canonical_parts = cm_split_path(filename, 0); 4004 4005 if (canonical_parts == NULL) 4006 return -errno; 4007 4008 *canonical_name = canonical_list_to_file(canonical_parts); 4009 slist_free_strings_full(canonical_parts); 4010 return 0; 4011 } 4012 4013 /* Returns a decoded base64 string, guaranteed to be null-terminated. */ 4014 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len) 4015 { 4016 gchar *tmp = g_base64_decode(text, out_len); 4017 gchar *out = g_strndup(tmp, *out_len); 4018 4019 g_free(tmp); 4020 4021 if (strlen(out) != *out_len) { 4022 g_warning("strlen(out) %"G_GSIZE_FORMAT" != *out_len %"G_GSIZE_FORMAT, strlen(out), *out_len); 4023 } 4024 4025 return out; 4026 } 4027 4028 /* Attempts to read count bytes from a PRNG into memory area starting at buf. 4029 * It is up to the caller to make sure there is at least count bytes 4030 * available at buf. */ 4031 gboolean 4032 get_random_bytes(void *buf, size_t count) 4033 { 4034 int rnd; 4035 ssize_t ret; 4036 4037 rnd = open("/dev/urandom", O_RDONLY); 4038 if (rnd == -1) { 4039 FILE_OP_ERROR("/dev/urandom", "open"); 4040 debug_print("Could not open /dev/urandom.\n"); 4041 return FALSE; 4042 } 4043 4044 /* Read data from the source into buf. */ 4045 ret = read(rnd, buf, count); 4046 if (ret != count) { 4047 FILE_OP_ERROR("/dev/urandom", "read"); 4048 debug_print("Could not read enough data from /dev/urandom, read only %ld of %lu bytes.\n", ret, count); 4049 close(rnd); 4050 return FALSE; 4051 } 4052 close(rnd); 4053 return TRUE; 4054 } 4055 4056 /* returns FALSE if parsing failed, otherwise returns TRUE and sets *server, *port 4057 and eventually *fp from filename (if not NULL, they must be free'd by caller after 4058 user. 4059 filenames we expect: 'host.name.port.cert' or 'host.name.port.f:i:n:g:e:r:p:r:i:n:t.cert' */ 4060 gboolean get_serverportfp_from_filename(const gchar *str, gchar **server, gchar **port, gchar **fp) 4061 { 4062 const gchar *pos, *dotport_pos = NULL, *dotcert_pos = NULL, *dotfp_pos = NULL; 4063 4064 g_return_val_if_fail(str != NULL, FALSE); 4065 4066 pos = str + strlen(str) - 1; 4067 while ((pos > str) && !dotport_pos) { 4068 if (*pos == '.') { 4069 if (!dotcert_pos) { 4070 /* match the .cert suffix */ 4071 if (strcmp(pos, ".cert") == 0) { 4072 dotcert_pos = pos; 4073 } 4074 } else { 4075 if (!dotfp_pos) { 4076 /* match an eventual fingerprint */ 4077 /* or the port number */ 4078 if (strncmp(pos + 3, ":", 1) == 0) { 4079 dotfp_pos = pos; 4080 } else { 4081 dotport_pos = pos; 4082 } 4083 } else { 4084 /* match the port number */ 4085 dotport_pos = pos; 4086 } 4087 } 4088 } 4089 pos--; 4090 } 4091 if (!dotport_pos || !dotcert_pos) { 4092 g_warning("could not parse filename %s", str); 4093 return FALSE; 4094 } 4095 4096 if (server != NULL) 4097 *server = g_strndup(str, dotport_pos - str); 4098 if (dotfp_pos) { 4099 if (port != NULL) 4100 *port = g_strndup(dotport_pos + 1, dotfp_pos - dotport_pos - 1); 4101 if (fp != NULL) 4102 *fp = g_strndup(dotfp_pos + 1, dotcert_pos - dotfp_pos - 1); 4103 } else { 4104 if (port != NULL) 4105 *port = g_strndup(dotport_pos + 1, dotcert_pos - dotport_pos - 1); 4106 if (fp != NULL) 4107 *fp = NULL; 4108 } 4109 4110 debug_print("filename='%s' => server='%s' port='%s' fp='%s'\n", 4111 str, 4112 (server ? *server : "(n/a)"), 4113 (port ? *port : "(n/a)"), 4114 (fp ? *fp : "(n/a)")); 4115 4116 if (!(server && *server) || !(port && *port)) 4117 return FALSE; 4118 else 4119 return TRUE; 4120 }