talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

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 }