1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /* Balsa E-Mail Client
3  *
4  * Copyright (C) 1997-2013 Stuart Parmenter and others,
5  *                         See the file AUTHORS for a list.
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 /* The routines that go here should depend only on common libraries - so that
22    this file can be linked against the address book program balsa-ab
23    without introducing extra dependencies. External library
24    dependencies should go to libbalsa.c */
25 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
26 # include "config.h"
27 #endif                          /* HAVE_CONFIG_H */
28 #include "misc.h"
29 
30 #define _SVID_SOURCE           1
31 #include <ctype.h>
32 #include <dirent.h>
33 #include <errno.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/file.h>
38 #include <sys/stat.h>
39 #include <sys/types.h>
40 #include <sys/utsname.h>
41 
42 #include "libbalsa.h"
43 #include "libbalsa_private.h"
44 #include "html.h"
45 #include <glib/gi18n.h>
46 
47 static const gchar *libbalsa_get_codeset_name(const gchar *txt,
48 					      LibBalsaCodeset Codeset);
49 static int getdnsdomainname(char *s, size_t l);
50 
51 gchar *
libbalsa_get_hostname(void)52 libbalsa_get_hostname(void)
53 {
54     struct utsname utsname;
55     gchar *p;
56     uname(&utsname);
57 
58     /* Some systems return Fqdn rather than just the hostname */
59     if ((p = strchr (utsname.nodename, '.')))
60 	*p = 0;
61 
62     return g_strdup (utsname.nodename);
63 }
64 
65 static int
getdnsdomainname(char * s,size_t l)66 getdnsdomainname (char *s, size_t l)
67 {
68   FILE *f;
69   char tmp[1024];
70   char *p = NULL;
71   char *q;
72 
73   if ((f = fopen ("/etc/resolv.conf", "r")) == NULL) return (-1);
74 
75   tmp[sizeof (tmp) - 1] = 0;
76 
77   l--; /* save room for the terminal \0 */
78 
79   while (fgets (tmp, sizeof (tmp) - 1, f) != NULL)
80   {
81     p = tmp;
82     while ( g_ascii_isspace (*p)) p++;
83     if (strncmp ("domain", p, 6) == 0 || strncmp ("search", p, 6) == 0)
84     {
85       p += 6;
86 
87       for (q = strtok (p, " \t\n"); q; q = strtok (NULL, " \t\n"))
88 	if (strcmp (q, "."))
89 	  break;
90 
91       if (q)
92       {
93 	  char *a = q;
94 
95 	  for (; *q; q++)
96 	      a = q;
97 
98 	  if (*a == '.')
99 	      *a = '\0';
100 
101 	  g_stpcpy (s, q);
102 	  fclose (f);
103 	  return 0;
104       }
105 
106     }
107   }
108 
109   fclose (f);
110   return (-1);
111 }
112 
113 gchar *
libbalsa_get_domainname(void)114 libbalsa_get_domainname(void)
115 {
116     char domainname[256]; /* arbitrary length */
117     struct utsname utsname;
118     gchar *d;
119 
120     uname(&utsname);
121     d = strchr( utsname.nodename, '.' );
122     if(d) {
123         return g_strdup( d+1 );
124     }
125 
126     if ( getdnsdomainname(domainname, sizeof(domainname)) == 0 ) {
127 	return g_strdup(domainname);
128     }
129     return NULL;
130 }
131 
132 /* libbalsa_urlencode:
133  * Taken from PHP's urlencode()
134  */
135 gchar*
libbalsa_urlencode(const gchar * str)136 libbalsa_urlencode(const gchar* str)
137 {
138     static const unsigned char hexchars[] = "0123456789ABCDEF";
139     gchar *retval = NULL;
140     gchar *x = NULL;
141 
142     g_return_val_if_fail(str != NULL, NULL);
143 
144     retval = malloc(strlen(str) * 3 + 1);
145 
146     for (x = retval; *str != '\0'; str++, x++) {
147        *x = *str;
148        if (*x == ' ') {
149            *x = '+';
150        } else if (!isalnum(*x) && strchr("_-.", *x) == NULL) {
151            /* Allow only alnum chars and '_', '-', '.'; escape the rest */
152            *x++ = '%';
153            *x++ = hexchars[(*str >> 4) & 0x0F];
154            *x = hexchars[(*str) & 0x0F];
155        }
156     }
157 
158     *x = '\0';
159     return retval;
160 }
161 
162 gchar *
libbalsa_urldecode(const gchar * str)163 libbalsa_urldecode(const gchar * str)
164 {
165     gchar *retval;
166     gchar *x;
167 
168     retval = g_new(char, strlen(str)+1);
169 
170     for (x = retval; *str != '\0'; str++, x++) {
171 	*x = *str;
172 	if (*x == '+')
173 	    *x = ' ';
174 	else if (*x == '%') {
175 	    if (!*++str || !g_ascii_isxdigit(*str))
176 		break;
177 	    *x = g_ascii_xdigit_value(*str);
178 	    if (!*++str || !g_ascii_isxdigit(*str))
179 		break;
180 	    *x = *x << 4 | g_ascii_xdigit_value(*str);
181 	}
182     }
183 
184     *x = '\0';
185     return retval;
186 }
187 
188 /* readfile allocates enough space for the ending '\0' characeter as well.
189    returns the number of read characters.
190 */
libbalsa_readfile(FILE * fp,char ** buf)191 size_t libbalsa_readfile(FILE * fp, char **buf)
192 {
193     size_t size;
194     off_t offset;
195     int r;
196     int fd;
197     struct stat statbuf;
198 
199     *buf = NULL;
200     if (!fp)
201 	return 0;
202 
203     fd = fileno(fp);
204     if (fstat(fd, &statbuf) == -1)
205 	return -1;
206 
207     size = statbuf.st_size;
208 
209     if (!size) {
210 	*buf = NULL;
211 	return size;
212     }
213 
214     lseek(fd, 0, SEEK_SET);
215 
216     *buf = (char *) g_malloc(size + 1);
217     if (*buf == NULL)
218 	return -1;
219 
220     offset = 0;
221     while ((size_t)offset < size) {
222 	r = read(fd, *buf + offset, size - offset);
223 	if (r == 0) { /* proper EOF */
224             (*buf)[offset] = '\0';
225 	    return offset;
226         }
227 	if (r > 0) {
228 	    offset += r;
229 	} else if ((errno != EAGAIN) && (errno != EINTR)) {
230 	    perror("Error reading file:");
231             (*buf)[offset] = '\0';
232 	    return -1;
233 	}
234     }
235     (*buf)[size] = '\0';
236 
237     return size;
238 }
239 
240 /* readfile_nostat is identical to readfile except it reads to EOF.
241    This enables the use of pipes to programs for such things as
242    the signature file.
243 */
libbalsa_readfile_nostat(FILE * fp,char ** buf)244 size_t libbalsa_readfile_nostat(FILE * fp, char **buf)
245 {
246     size_t size;
247     GString *gstr;
248     char rbuf[512];
249     size_t rlen = 512;
250     int r;
251     int fd;
252 
253     *buf = NULL;
254     fd = fileno(fp);
255     if (!fp)
256 	return 0;
257 
258     r = read(fd, rbuf, rlen-1);
259     if (r <= 0)
260 	return 0;
261 
262     rbuf[r] = '\0';
263     gstr = g_string_new(rbuf);
264 
265     do {
266 	if ((r = read(fd, rbuf, rlen-1)) != 0) {
267 	    /* chbm: if your sig is larger than 512 you deserve this */
268 	    if ((r < 0) && (errno != EAGAIN) && (errno != EINTR)) {
269 		perror("Error reading file:");
270 		g_string_free(gstr, TRUE);
271 		return -1;
272 	    };
273 	    if (r > 0) {
274 		rbuf[r] = '\0';
275 		g_string_append(gstr,rbuf);
276 	    }
277 	}
278     } while( r != 0 );
279 
280     size = gstr->len;
281     *buf = g_string_free(gstr, FALSE);
282 
283     return size;
284 }
285 
286 /* libbalsa_find_word:
287    searches given word delimited by blanks or string boundaries in given
288    string. IS NOT case-sensitive.
289    Returns TRUE if the word is found.
290 */
libbalsa_find_word(const gchar * word,const gchar * str)291 gboolean libbalsa_find_word(const gchar * word, const gchar * str)
292 {
293     const gchar *ptr = str;
294     int len = strlen(word);
295 
296     while (*ptr) {
297 	if (g_ascii_strncasecmp(word, ptr, len) == 0)
298 	    return TRUE;
299 	/* skip one word */
300 	while (*ptr && !isspace((int)*ptr))
301 	    ptr++;
302 	while (*ptr && isspace((int) *ptr))
303 	    ptr++;
304     }
305     return FALSE;
306 }
307 
308 /* libbalsa_wrap_string
309    wraps given string replacing spaces with '\n'.  do changes in place.
310    lnbeg - line beginning position, sppos - space position,
311    te - tab's extra space.
312 */
313 void
libbalsa_wrap_string(gchar * str,int width)314 libbalsa_wrap_string(gchar * str, int width)
315 {
316     const int minl = width / 2;
317 
318     gchar *space_pos, *ptr;
319     gint te = 0;
320 
321     gint ptr_offset, line_begin_offset, space_pos_offset;
322 
323     g_return_if_fail(str != NULL);
324 
325 
326     line_begin_offset = ptr_offset = space_pos_offset = 0;
327     space_pos = ptr = str;
328 
329     while (*ptr) {
330 	switch (*ptr) {
331 	case '\t':
332 	    te += 7;
333 	    break;
334 	case '\n':
335 	    line_begin_offset = ptr_offset + 1;
336 	    te = 0;
337 	    break;
338 	case ' ':
339 	    space_pos = ptr;
340 	    space_pos_offset = ptr_offset;
341 	    break;
342 	}
343 	if (ptr_offset - line_begin_offset >= width - te
344 	     && space_pos_offset >= line_begin_offset + minl) {
345 	    *space_pos = '\n';
346 	    line_begin_offset = space_pos_offset + 1;
347 	    te = 0;
348 	}
349 	ptr=g_utf8_next_char(ptr);
350 	ptr_offset++;
351     }
352 }
353 
354 
355 /* Delete the contents of a directory (not the directory itself).
356    Return TRUE if everything was OK.
357    If FALSE is returned then errno will be set to some useful value.
358 */
359 gboolean
libbalsa_delete_directory_contents(const gchar * path)360 libbalsa_delete_directory_contents(const gchar *path)
361 {
362     struct stat sb;
363     DIR *d;
364     struct dirent *de;
365     gchar *new_path;
366 
367     d = opendir(path);
368     g_return_val_if_fail(d, FALSE);
369 
370     for (de = readdir(d); de; de = readdir(d)) {
371 	if (strcmp(de->d_name, ".") == 0 ||
372 	    strcmp(de->d_name, "..") == 0)
373 	    continue;
374 	new_path = g_strdup_printf("%s/%s", path, de->d_name);
375 
376 	stat(new_path, &sb);
377 	if (S_ISDIR(sb.st_mode)) {
378 	    if (!libbalsa_delete_directory_contents(new_path) ||
379 		rmdir(new_path) == -1) {
380 		g_free(new_path);
381 		closedir(d);
382 		return FALSE;
383 	    }
384 	} else {
385 	    if (unlink( new_path ) == -1) {
386 		g_free(new_path);
387 		closedir(d);
388 		return FALSE;
389 	    }
390 	}
391 	g_free(new_path);
392 	new_path = 0;
393     }
394 
395     closedir(d);
396     return TRUE;
397 }
398 
399 /* libbalsa_truncate_string
400  *
401  * Arguments:
402  *   str    the string to be truncated;
403  *   length truncation length;
404  *   dots   the number of trailing dots to be used to indicate
405  *          truncation.
406  *
407  * Value:
408  *   pointer to a newly allocated string; free with `g_free' when it's
409  *   no longer needed.
410  */
411 gchar *
libbalsa_truncate_string(const gchar * str,gint length,gint dots)412 libbalsa_truncate_string(const gchar *str, gint length, gint dots)
413 {
414     gchar *res;
415     gchar *p;
416 
417     if (str == NULL)
418         return NULL;
419 
420     if (length <= 0 || strlen(str) <= (guint) length)
421         return g_strdup(str);
422 
423     res = g_strndup(str, length);
424 
425     p = res + length - dots;
426     while (--dots >= 0)
427         *p++ = '.';
428 
429     return res;
430 }
431 
432 /* libbalsa_expand_path:
433    We handle only references to ~/.
434 */
435 gchar*
libbalsa_expand_path(const gchar * path)436 libbalsa_expand_path(const gchar * path)
437 {
438     const gchar *home = g_get_home_dir();
439 
440     if(path[0] == '~') {
441         if(path[1] == '/')
442             return g_strconcat(home, path+1, NULL);
443         else if(path[1] == '\0')
444             return g_strdup(home);
445         /* else: unrecognized combination */
446     }
447     return g_strdup(path);
448 }
449 
450 
451 
452 /* create a uniq directory, resulting name should be freed with g_free */
453 gboolean
libbalsa_mktempdir(char ** s)454 libbalsa_mktempdir (char **s)
455 {
456     gchar *name;
457     int fd;
458 
459     g_return_val_if_fail(s != NULL, FALSE);
460 
461     do {
462 	GError *error = NULL;
463 	fd = g_file_open_tmp("balsa-tmpdir-XXXXXX", &name, &error);
464 	close(fd);
465 	unlink(name);
466 	/* Here is a short time that the name could be reused */
467 	fd = mkdir(name, 0700);
468 	if (fd == -1) {
469 	    g_free(name);
470 	    if (!g_error_matches(error, G_FILE_ERROR, G_FILE_ERROR_EXIST))
471 		return FALSE;
472 	}
473 	if (error)
474 	    g_error_free(error);
475     } while (fd == -1);
476     *s = name;
477     /* FIXME: rmdir(name) at sometime */
478     return TRUE;
479 }
480 
481 /* libbalsa_set_fallback_codeset: sets the codeset for incorrectly
482  * encoded characters. */
483 static LibBalsaCodeset sanitize_fallback_codeset = WEST_EUROPE;
484 LibBalsaCodeset
libbalsa_set_fallback_codeset(LibBalsaCodeset codeset)485 libbalsa_set_fallback_codeset(LibBalsaCodeset codeset)
486 {
487     LibBalsaCodeset ret = sanitize_fallback_codeset;
488     const gchar *charsets[] = {
489         "UTF-8",
490         libbalsa_get_codeset_name(NULL, codeset),
491         NULL
492     };
493 
494     g_mime_set_user_charsets(charsets);
495 
496     sanitize_fallback_codeset = codeset;
497     return ret;
498 }
499 
500 /* libbalsa_utf8_sanitize
501  *
502  * Validate utf-8 text, and if validation fails, replace each offending
503  * byte with either '?' or assume a reasonable codeset for conversion.
504  *
505  * Arguments:
506  *   text       The text to be sanitized; NULL is OK.
507  *   fallback   if TRUE and *text is not clean, convert using codeset
508  *   codeset    the codeset to use for fallback conversion
509  *   target     if not NULL filled with the name of the used codeset or NULL
510  *              or error/"?" conversion
511  *
512  * Return value:
513  *   TRUE if *text was clean and FALSE otherwise
514  *
515  * NOTE:    The text is either modified in place or replaced and freed.
516  */
517 gboolean
libbalsa_utf8_sanitize(gchar ** text,gboolean fallback,gchar const ** target)518 libbalsa_utf8_sanitize(gchar **text, gboolean fallback,
519 		       gchar const **target)
520 {
521     gchar *p;
522 
523     if (target)
524 	*target = NULL;
525     if (!*text || g_utf8_validate(*text, -1, NULL))
526 	return TRUE;
527 
528     if (fallback) {
529 	gsize b_written;
530 	GError *conv_error = NULL;
531 	const gchar *use_enc =
532             libbalsa_get_codeset_name(*text, sanitize_fallback_codeset);
533 	p = g_convert(*text, strlen(*text), "utf-8", use_enc, NULL,
534                       &b_written, &conv_error);
535 
536 	if (p) {
537 	    g_free(*text);
538 	    *text = p;
539 	    if (target)
540 		*target = use_enc;
541 	    return FALSE;
542 	}
543 	g_message("conversion %s -> utf8 failed: %s", use_enc,
544                   conv_error->message);
545 	g_error_free(conv_error);
546     }
547     p = *text;
548     while (!g_utf8_validate(p, -1, (const gchar **) &p))
549 	*p++ = '?';
550 
551     return FALSE;
552 }
553 
554 /* libbalsa_utf8_strstr() returns TRUE if s2 is a substring of s1.
555  * libbalsa_utf8_strstr is case insensitive
556  * this functions understands utf8 strings (as you might have guessed ;-)
557  */
558 gboolean
libbalsa_utf8_strstr(const gchar * s1,const gchar * s2)559 libbalsa_utf8_strstr(const gchar *s1, const gchar *s2)
560 {
561     const gchar * p,* q;
562 
563     /* convention : NULL string is contained in anything */
564     if (!s2) return TRUE;
565     /* s2 is non-NULL, so if s1==NULL we return FALSE :)*/
566     if (!s1) return FALSE;
567     /* OK both are non-NULL now*/
568     /* If s2 is the empty string return TRUE */
569     if (!*s2) return TRUE;
570     while (*s1) {
571 	/* We look for the first char of s2*/
572 	for (;*s1 &&
573 		 g_unichar_toupper(g_utf8_get_char(s2))!=g_unichar_toupper(g_utf8_get_char(s1));
574 	     s1 = g_utf8_next_char(s1));
575 	if (*s1) {
576 	    /* We found the first char let see if this potential match is an actual one */
577 	    s1 = g_utf8_next_char(s1);
578 	    q = s1;
579 	    p = g_utf8_next_char(s2);
580 	    while (*q && *p &&
581 		   g_unichar_toupper(g_utf8_get_char(p))
582 		   ==g_unichar_toupper(g_utf8_get_char(q))) {
583 		p = g_utf8_next_char(p);
584 		q = g_utf8_next_char(q);
585 	    }
586 	    /* We have a match if p has reached the end of s2, ie *p==0 */
587 	    if (!*p) return TRUE;
588 	}
589     }
590     return FALSE;
591 }
592 
593 /* The LibBalsaCodeset enum is not used for anything currently, but this
594  * list must be the same length, and should probably be kept consistent: */
595 LibBalsaCodesetInfo libbalsa_codeset_info[LIBBALSA_NUM_CODESETS] = {
596     {N_("west european"),       /* WEST_EUROPE          */
597      "iso-8859-1", "windows-1252"} ,
598     {N_("east european"),       /* EAST_EUROPE          */
599      "iso-8859-2", "windows-1250"} ,
600     {N_("south european"),      /* SOUTH_EUROPE         */
601      "iso-8859-3"} ,
602     {N_("north european"),      /* NORTH_EUROPE         */
603      "iso-8859-4"} ,
604     {N_("cyrillic"),            /* CYRILLIC             */
605      "iso-8859-5", "windows-1251"} ,
606     {N_("arabic"),              /* ARABIC               */
607      "iso-8859-6", "windows-1256"} ,
608     {N_("greek"),               /* GREEK                */
609      "iso-8859-7", "windows-1253"} ,
610     {N_("hebrew"),              /* HEBREW               */
611      "iso-8859-8", "windows-1255"} ,
612     {N_("turkish"),             /* TURKISH              */
613      "iso-8859-9", "windows-1254"} ,
614     {N_("nordic"),              /* NORDIC               */
615      "iso-8859-10"} ,
616     {N_("thai"),                /* THAI                 */
617      "iso-8859-11"} ,
618     {N_("baltic"),              /* BALTIC               */
619      "iso-8859-13", "windows-1257"} ,
620     {N_("celtic"),              /* CELTIC               */
621      "iso-8859-14"} ,
622     {N_("west europe (euro)"),  /* WEST_EUROPE_EURO     */
623      "iso-8859-15"} ,
624     {N_("russian"),             /* RUSSIAN              */
625      "koi-8r"} ,
626     {N_("ukrainian"),           /* UKRAINE              */
627      "koi-8u"} ,
628     {N_("japanese"),            /* JAPAN                */
629      "iso-2022-jp"} ,
630     {N_("korean"),              /* KOREA                */
631      "euc-kr"} ,
632     {N_("east european"),       /* EAST_EUROPE_WIN      */
633      "windows-1250"} ,
634     {N_("cyrillic"),            /* CYRILLIC_WIN         */
635      "windows-1251"} ,
636     {N_("greek"),               /* GREEK_WIN            */
637      "windows-1253"} ,
638     {N_("hebrew"),              /* HEBREW_WIN           */
639      "windows-1255"} ,
640     {N_("arabic"),              /* ARABIC_WIN           */
641      "windows-1256"} ,
642     {N_("baltic"),              /* BALTIC_WIN           */
643      "windows-1257"} ,
644 };
645 
646 /*
647  * Return the name of a codeset according to Codeset. If txt is not NULL, is
648  * is scanned for chars between 0x80 and 0x9f. If such a char is found, this
649  * usually means that txt contains windows (not iso) characters.
650  */
651 static const gchar *
libbalsa_get_codeset_name(const gchar * txt,LibBalsaCodeset Codeset)652 libbalsa_get_codeset_name(const gchar * txt, LibBalsaCodeset Codeset)
653 {
654     LibBalsaCodesetInfo *info = &libbalsa_codeset_info[Codeset];
655 
656     if (txt && info->win) {
657         LibBalsaTextAttribute attr = libbalsa_text_attr_string(txt);
658         if (attr & LIBBALSA_TEXT_HI_CTRL)
659             return info->win;
660     }
661     return info->std;
662 }
663 
664 /* Create a GtkComboBox with the national charsets as options;
665  * called when some text is found to be neither US-ASCII nor UTF-8, so
666  * the list includes neither of these. */
667 GtkWidget *
libbalsa_charset_button_new(void)668 libbalsa_charset_button_new(void)
669 {
670     GtkWidget *combo_box;
671     LibBalsaCodeset n, active = WEST_EUROPE;
672     const gchar *locale_charset;
673 
674     combo_box = gtk_combo_box_text_new();
675     locale_charset = g_mime_locale_charset();
676 
677     for (n = 0; n < LIBBALSA_NUM_CODESETS; n++) {
678         LibBalsaCodesetInfo *info = &libbalsa_codeset_info[n];
679         gchar *tmp = g_strdup_printf("%s (%s)", _(info->label), info->std);
680         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo_box), tmp);
681         g_free(tmp);
682 
683 	if (!g_ascii_strcasecmp(info->std, locale_charset))
684 	    active = n;
685     }
686 
687     /* locale_charset may be UTF-8, in which case it was not found,
688      * and the initial choice will be WEST_EUROPE (= 0). */
689     gtk_combo_box_set_active(GTK_COMBO_BOX(combo_box), active);
690 
691     return combo_box;
692 }
693 
694 /* Helper */
695 static void
lb_text_attr(const gchar * text,gssize len,gboolean * has_esc,gboolean * has_hi_bit,gboolean * has_hi_ctrl)696 lb_text_attr(const gchar * text, gssize len, gboolean * has_esc,
697              gboolean * has_hi_bit, gboolean * has_hi_ctrl)
698 {
699     if (len < 0)
700         len = strlen(text);
701 
702     for (; --len >= 0; text++) {
703 	guchar c = *text;
704         if (c == 0x1b)
705             *has_esc = TRUE;
706         if (c >= 0x80) {
707             *has_hi_bit = TRUE;
708             if (c <= 0x9f)
709                 *has_hi_ctrl = TRUE;
710         }
711     }
712 }
713 
714 /* Return text attributes of a string. */
715 LibBalsaTextAttribute
libbalsa_text_attr_string(const gchar * string)716 libbalsa_text_attr_string(const gchar * string)
717 {
718     LibBalsaTextAttribute attr;
719     gboolean has_esc = FALSE;
720     gboolean has_hi_bit = FALSE;
721     gboolean has_hi_ctrl = FALSE;
722     gboolean is_utf8 = TRUE;
723 
724     lb_text_attr(string, -1, &has_esc, &has_hi_bit, &has_hi_ctrl);
725     is_utf8 = g_utf8_validate(string, -1, NULL);
726 
727     attr = 0;
728     if (has_esc)
729         attr |= LIBBALSA_TEXT_ESC;
730     if (has_hi_bit)
731         attr |= LIBBALSA_TEXT_HI_BIT;
732     if (has_hi_ctrl)
733         attr |= LIBBALSA_TEXT_HI_CTRL;
734     if (is_utf8 && has_hi_bit)
735         attr |= LIBBALSA_TEXT_HI_UTF8;
736 
737     return attr;
738 }
739 
740 /* Return text attributes of the contents of a file;
741  * filename is in URI form;
742  * returns -1 on error. */
743 LibBalsaTextAttribute
libbalsa_text_attr_file(const gchar * filename)744 libbalsa_text_attr_file(const gchar * filename)
745 {
746     GFile *file;
747     GFileInputStream *stream;
748     gssize bytes;
749     LibBalsaTextAttribute attr;
750     gchar buf[80];
751     gchar *new_chars = buf;
752     gboolean has_esc = FALSE;
753     gboolean has_hi_bit = FALSE;
754     gboolean has_hi_ctrl = FALSE;
755     gboolean is_utf8 = TRUE;
756 
757     file = g_file_new_for_uri(filename);
758     stream = g_file_read(file, NULL, NULL);
759     g_object_unref(file);
760     if (!stream)
761         return -1;
762 
763     while ((bytes = g_input_stream_read(G_INPUT_STREAM(stream), new_chars,
764                                (sizeof buf) - (new_chars - buf), NULL,
765                                NULL)) > 0) {
766 	gboolean test_bits;
767 
768 	test_bits = !has_esc || !has_hi_bit || !has_hi_ctrl;
769 	if (!test_bits && !is_utf8)
770 	    break;
771 
772         if (test_bits)
773             lb_text_attr(new_chars, bytes, &has_esc, &has_hi_bit,
774                          &has_hi_ctrl);
775 
776         if (is_utf8) {
777             const gchar *end;
778 
779             bytes += new_chars - buf;
780             new_chars = buf;
781             if (!g_utf8_validate(buf, bytes, &end)) {
782                 bytes -= (end - buf);
783                 if (g_utf8_get_char_validated(end, bytes) ==
784                     (gunichar) (-1)) {
785                     is_utf8 = FALSE;
786                 } else {
787                     /* copy remaining bytes to start of buffer */
788                     memmove(buf, end, bytes);
789                     new_chars += bytes;
790                 }
791             }
792         }
793     }
794 
795     g_object_unref(stream);
796 
797     if (bytes < 0)
798         return -1;
799 
800     attr = 0;
801     if (has_esc)
802         attr |= LIBBALSA_TEXT_ESC;
803     if (has_hi_bit)
804         attr |= LIBBALSA_TEXT_HI_BIT;
805     if (has_hi_ctrl)
806         attr |= LIBBALSA_TEXT_HI_CTRL;
807     if (is_utf8 && has_hi_bit)
808         attr |= LIBBALSA_TEXT_HI_UTF8;
809 
810     return attr;
811 }
812 
813 #define compare_stat(osb, nsb)  ( (osb.st_dev != nsb.st_dev || osb.st_ino != nsb.st_ino || osb.st_rdev != nsb.st_rdev) ? -1 : 0 )
814 
815 int
libbalsa_safe_open(const char * path,int flags,mode_t mode,GError ** err)816 libbalsa_safe_open (const char *path, int flags, mode_t mode, GError **err)
817 {
818   struct stat osb, nsb;
819   int fd;
820 
821   if ((fd = open (path, flags, mode)) < 0) {
822       g_set_error(err, LIBBALSA_ERROR_QUARK, errno,
823                   _("Cannot open %s: %s"), path, g_strerror(errno));
824       return fd;
825   }
826 
827   /* make sure the file is not symlink */
828   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
829       compare_stat(osb, nsb) == -1) {
830       close (fd);
831       g_set_error(err, LIBBALSA_ERROR_QUARK, errno,
832                   _("Cannot open %s: is a symbolic link"), path);
833       return (-1);
834   }
835 
836   return (fd);
837 }
838 
839 /*
840  * This function is supposed to do nfs-safe renaming of files.
841  *
842  * Warning: We don't check whether src and target are equal.
843  */
844 
845 int
libbalsa_safe_rename(const char * src,const char * target)846 libbalsa_safe_rename (const char *src, const char *target)
847 {
848   struct stat ssb, tsb;
849 
850   if (!src || !target)
851     return -1;
852 
853   if (link (src, target) != 0)
854   {
855 
856     /*
857      * Coda does not allow cross-directory links, but tells
858      * us it's a cross-filesystem linking attempt.
859      *
860      * However, the Coda rename call is allegedly safe to use.
861      *
862      * With other file systems, rename should just fail when
863      * the files reside on different file systems, so it's safe
864      * to try it here.
865      *
866      */
867 
868     if (errno == EXDEV)
869       return rename (src, target);
870 
871     return -1;
872   }
873 
874   /*
875    * Stat both links and check if they are equal.
876    */
877 
878   if (stat (src, &ssb) == -1)
879   {
880     return -1;
881   }
882 
883   if (stat (target, &tsb) == -1)
884   {
885     return -1;
886   }
887 
888   /*
889    * pretend that the link failed because the target file
890    * did already exist.
891    */
892 
893   if (compare_stat (ssb, tsb) == -1)
894   {
895     errno = EEXIST;
896     return -1;
897   }
898 
899   /*
900    * Unlink the original link.  Should we really ignore the return
901    * value here? XXX
902    */
903 
904   unlink (src);
905 
906   return 0;
907 }
908 
909 
910 #define MAXLOCKATTEMPT 5
911 
912 /* Args:
913  *      excl            if excl != 0, request an exclusive lock
914  *      dot             if dot != 0, try to dotlock the file
915  *      timeout         should retry locking?
916  */
917 int
libbalsa_lock_file(const char * path,int fd,int excl,int dot,int timeout)918 libbalsa_lock_file (const char *path, int fd, int excl, int dot, int timeout)
919 {
920 #if defined (USE_FCNTL) || defined (USE_FLOCK)
921     int count;
922     int attempt;
923     struct stat prev_sb = { 0 };
924 #endif
925     int r = 0;
926 
927 #ifdef USE_FCNTL
928     struct flock lck;
929 
930     memset (&lck, 0, sizeof (struct flock));
931     lck.l_type = excl ? F_WRLCK : F_RDLCK;
932     lck.l_whence = SEEK_SET;
933 
934     count = 0;
935     attempt = 0;
936     while (fcntl (fd, F_SETLK, &lck) == -1)
937 	{
938 	    struct stat sb;
939 	    g_print("%s(): fcntl errno %d.\n", __FUNCTION__, errno);
940     if (errno != EAGAIN && errno != EACCES)
941 	{
942 	    libbalsa_information
943 		(LIBBALSA_INFORMATION_DEBUG, "fcntl failed, errno=%d.", errno);
944 	    return -1;
945 	}
946 
947     if (fstat (fd, &sb) != 0)
948 	sb.st_size = 0;
949 
950     if (count == 0)
951 	prev_sb = sb;
952 
953     /* only unlock file if it is unchanged */
954     if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
955 	{
956 	    if (timeout)
957 		libbalsa_information
958 		    (LIBBALSA_INFORMATION_WARNING,
959 		     _("Timeout exceeded while attempting fcntl lock!"));
960 	    return (-1);
961 	}
962 
963     prev_sb = sb;
964 
965     libbalsa_information(LIBBALSA_INFORMATION_MESSAGE,
966 			 _("Waiting for fcntl lock... %d"), ++attempt);
967     sleep (1);
968 }
969 #endif /* USE_FCNTL */
970 
971 #ifdef USE_FLOCK
972 count = 0;
973 attempt = 0;
974 while (flock (fd, (excl ? LOCK_EX : LOCK_SH) | LOCK_NB) == -1)
975 {
976     struct stat sb;
977     if (errno != EWOULDBLOCK)
978 	{
979 	    libbalsa_message ("flock: %s", strerror(errno));
980 	    r = -1;
981 	    break;
982 	}
983 
984     if (fstat(fd,&sb) != 0 )
985 	sb.st_size=0;
986 
987     if (count == 0)
988 	prev_sb=sb;
989 
990     /* only unlock file if it is unchanged */
991     if (prev_sb.st_size == sb.st_size && ++count >= (timeout?MAXLOCKATTEMPT:0))
992 	{
993 	    if (timeout)
994 		libbalsa_message (_("Timeout exceeded while attempting flock lock!"));
995 	    r = -1;
996 	    break;
997 	}
998 
999     prev_sb = sb;
1000 
1001     libbalsa_message (_("Waiting for flock attempt... %d"), ++attempt);
1002     sleep (1);
1003 }
1004 #endif /* USE_FLOCK */
1005 
1006 #ifdef USE_DOTLOCK
1007 if (r == 0 && dot)
1008      r = dotlock_file (path, fd, timeout);
1009 #endif /* USE_DOTLOCK */
1010 
1011      if (r == -1)
1012 {
1013     /* release any other locks obtained in this routine */
1014 
1015 #ifdef USE_FCNTL
1016     lck.l_type = F_UNLCK;
1017     fcntl (fd, F_SETLK, &lck);
1018 #endif /* USE_FCNTL */
1019 
1020 #ifdef USE_FLOCK
1021     flock (fd, LOCK_UN);
1022 #endif /* USE_FLOCK */
1023 
1024     return (-1);
1025 }
1026 
1027 return 0;
1028 }
1029 
1030 int
libbalsa_unlock_file(const char * path,int fd,int dot)1031 libbalsa_unlock_file (const char *path, int fd, int dot)
1032 {
1033 #ifdef USE_FCNTL
1034     struct flock unlockit = { F_UNLCK, 0, 0, 0 };
1035 
1036     memset (&unlockit, 0, sizeof (struct flock));
1037     unlockit.l_type = F_UNLCK;
1038     unlockit.l_whence = SEEK_SET;
1039     fcntl (fd, F_SETLK, &unlockit);
1040 #endif
1041 
1042 #ifdef USE_FLOCK
1043     flock (fd, LOCK_UN);
1044 #endif
1045 
1046 #ifdef USE_DOTLOCK
1047     if (dot)
1048 	undotlock_file (path, fd);
1049 #endif
1050 
1051     return 0;
1052 }
1053 
1054 
1055 /* libbalsa_ia_rfc2821_equal
1056    compares two addresses according to rfc2821: local-part@domain is equal,
1057    if the local-parts are case sensitive equal, but the domain case-insensitive
1058 */
1059 gboolean
libbalsa_ia_rfc2821_equal(const InternetAddress * a,const InternetAddress * b)1060 libbalsa_ia_rfc2821_equal(const InternetAddress * a,
1061 			  const InternetAddress * b)
1062 {
1063     const gchar *a_atptr, *b_atptr;
1064     const gchar *a_addr, *b_addr;
1065     gint a_atpos, b_atpos;
1066 
1067     if (!INTERNET_ADDRESS_IS_MAILBOX(a) || !INTERNET_ADDRESS_IS_MAILBOX(b))
1068         return FALSE;
1069 
1070     /* first find the "@" in the two addresses */
1071     a_addr = INTERNET_ADDRESS_MAILBOX(a)->addr;
1072     b_addr = INTERNET_ADDRESS_MAILBOX(b)->addr;
1073     a_atptr = strchr(a_addr, '@');
1074     b_atptr = strchr(b_addr, '@');
1075     if (!a_atptr || !b_atptr)
1076         return FALSE;
1077     a_atpos = a_atptr - a_addr;
1078     b_atpos = b_atptr - b_addr;
1079 
1080     /* now compare the strings */
1081     if (!a_atpos || !b_atpos || a_atpos != b_atpos ||
1082         strncmp(a_addr, b_addr, a_atpos) ||
1083         g_ascii_strcasecmp(a_atptr, b_atptr))
1084         return FALSE;
1085     else
1086         return TRUE;
1087 }
1088 
1089 /*
1090  * Utilities for making consistent dialogs.
1091  */
1092 
1093 #define LB_PADDING 12           /* per HIG */
1094 
1095 GtkWidget *
libbalsa_create_grid(void)1096 libbalsa_create_grid(void)
1097 {
1098     GtkWidget *grid;
1099 
1100     grid = gtk_grid_new();
1101 
1102     gtk_grid_set_row_spacing(GTK_GRID(grid), LB_PADDING);
1103     gtk_grid_set_column_spacing(GTK_GRID(grid), LB_PADDING);
1104 
1105     return grid;
1106 }
1107 
1108 /* create_label:
1109    Create a label and add it to a table in the first column of given row,
1110    setting the keyval to found accelerator value, that can be later used
1111    in create_entry.
1112 */
1113 GtkWidget *
libbalsa_create_grid_label(const gchar * text,GtkWidget * grid,gint row)1114 libbalsa_create_grid_label(const gchar * text, GtkWidget * grid, gint row)
1115 {
1116     GtkWidget *label;
1117 
1118     label = gtk_label_new_with_mnemonic(text);
1119     gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
1120     gtk_widget_set_halign(label, GTK_ALIGN_START);
1121 
1122     gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
1123 
1124     return label;
1125 }
1126 
1127 /* create_check:
1128    creates a checkbox with a given label and places them in given array.
1129 */
1130 GtkWidget *
libbalsa_create_grid_check(const gchar * text,GtkWidget * grid,gint row,gboolean initval)1131 libbalsa_create_grid_check(const gchar * text, GtkWidget * grid, gint row,
1132                            gboolean initval)
1133 {
1134     GtkWidget *check_button;
1135 
1136     check_button = gtk_check_button_new_with_mnemonic(text);
1137 
1138     gtk_grid_attach(GTK_GRID(grid), check_button, 0, row, 2, 1);
1139 
1140     if (initval)
1141         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check_button),
1142                                      TRUE);
1143 
1144     return check_button;
1145 }
1146 
1147 /* Create a text entry and add it to the table */
1148 GtkWidget *
libbalsa_create_grid_entry(GtkWidget * grid,GCallback changed_func,gpointer data,gint row,const gchar * initval,GtkWidget * hotlabel)1149 libbalsa_create_grid_entry(GtkWidget * grid, GCallback changed_func,
1150                            gpointer data, gint row, const gchar * initval,
1151                            GtkWidget * hotlabel)
1152 {
1153     GtkWidget *entry;
1154 
1155     entry = gtk_entry_new();
1156 
1157     gtk_grid_attach(GTK_GRID(grid), entry, 1, row, 1, 1);
1158 
1159     if (initval)
1160         gtk_entry_set_text(GTK_ENTRY(entry), initval);
1161 
1162     gtk_label_set_mnemonic_widget(GTK_LABEL(hotlabel), entry);
1163 
1164     /* Watch for changes... */
1165     if (changed_func)
1166         g_signal_connect(entry, "changed", changed_func, data);
1167 
1168     return entry;
1169 }
1170 
1171 /* Create a GtkSizeGroup and add to it any GtkLabel packed in a GtkGrid
1172  * inside the chooser widget; size_group will be unreffed when the
1173  * chooser widget is finalized. */
1174 static void
lb_create_size_group_func(GtkWidget * widget,gpointer data)1175 lb_create_size_group_func(GtkWidget * widget, gpointer data)
1176 {
1177     if (GTK_IS_LABEL(widget) &&
1178         GTK_IS_GRID(gtk_widget_get_parent(widget)))
1179         gtk_size_group_add_widget(GTK_SIZE_GROUP(data), widget);
1180     else if (GTK_IS_CONTAINER(widget))
1181         gtk_container_foreach(GTK_CONTAINER(widget),
1182                               lb_create_size_group_func, data);
1183 }
1184 
1185 GtkSizeGroup *
libbalsa_create_size_group(GtkWidget * chooser)1186 libbalsa_create_size_group(GtkWidget * chooser)
1187 {
1188     GtkSizeGroup *size_group;
1189 
1190     size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
1191     g_object_weak_ref(G_OBJECT(chooser), (GWeakNotify) g_object_unref,
1192                       size_group);
1193     lb_create_size_group_func(chooser, size_group);
1194 
1195     return size_group;
1196 }
1197 
1198 void
libbalsa_assure_balsa_dir(void)1199 libbalsa_assure_balsa_dir(void)
1200 {
1201     gchar* dir = g_strconcat(g_get_home_dir(), "/.balsa", NULL);
1202     mkdir(dir, S_IRUSR|S_IWUSR|S_IXUSR);
1203     g_free(dir);
1204 }
1205 
1206 /* Some more "guess" functions symmetric to libbalsa_guess_mail_spool()... */
1207 
1208 #define POP_SERVER "pop"
1209 #define IMAP_SERVER "mx"
1210 #define LDAP_SERVER "ldap"
1211 
1212 static gchar*
qualified_hostname(const char * name)1213 qualified_hostname(const char *name)
1214 {
1215     gchar *domain=libbalsa_get_domainname();
1216 
1217     if(domain) {
1218 	gchar *host=g_strdup_printf("%s.%s", name, domain);
1219 
1220 	g_free(domain);
1221 
1222 	return host;
1223     } else
1224 	return g_strdup(name);
1225 }
1226 
1227 
libbalsa_guess_pop_server()1228 gchar *libbalsa_guess_pop_server()
1229 {
1230     return qualified_hostname(POP_SERVER);
1231 }
1232 
libbalsa_guess_imap_server()1233 gchar *libbalsa_guess_imap_server()
1234 {
1235     return qualified_hostname(IMAP_SERVER);
1236 }
1237 
libbalsa_guess_ldap_server()1238 gchar *libbalsa_guess_ldap_server()
1239 {
1240     return qualified_hostname(LDAP_SERVER);
1241 }
1242 
libbalsa_guess_imap_inbox()1243 gchar *libbalsa_guess_imap_inbox()
1244 {
1245     gchar *server = libbalsa_guess_imap_server();
1246 
1247     if(server) {
1248 	gchar *url = g_strdup_printf("imap://%s/INBOX", server);
1249 
1250 	g_free(server);
1251 
1252 	return url;
1253     }
1254 
1255     return NULL;
1256 }
1257 
libbalsa_guess_ldap_base()1258 gchar *libbalsa_guess_ldap_base()
1259 {
1260     gchar *server = libbalsa_guess_ldap_server();
1261 
1262     /* Note: Assumes base dn is "o=<domain name>". Somewhat speculative... */
1263     if(server) {
1264 	gchar *base=NULL, *domain;
1265 
1266 	if((domain=strchr(server, '.')))
1267 	   base = g_strdup_printf("o=%s", domain+1);
1268 
1269 	g_free(server);
1270 
1271 	return base;
1272     }
1273     return NULL;
1274 }
1275 
libbalsa_guess_ldap_name()1276 gchar *libbalsa_guess_ldap_name()
1277 {
1278     gchar *base = libbalsa_guess_ldap_base();
1279 
1280     if(base) {
1281 	gchar *name = strchr(base, '=');
1282 	gchar *dir_name = g_strdup_printf(_("LDAP Directory for %s"),
1283 					  (name?name+1:base));
1284 	g_free(base);
1285 
1286 	return dir_name;
1287     }
1288 
1289     return NULL;
1290 }
1291 
libbalsa_guess_ldif_file()1292 gchar *libbalsa_guess_ldif_file()
1293 {
1294     int i;
1295     gchar *ldif;
1296 
1297     static const gchar *guesses[] = {
1298 	"address.ldif",
1299 	".address.ldif",
1300 	"address-book.ldif",
1301 	".address-book.ldif",
1302 	".addressbook.ldif",
1303 	NULL
1304     };
1305 
1306     for (i = 0; guesses[i] != NULL; i++) {
1307 	ldif =  g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S,
1308 			    guesses[i], NULL);
1309 	if (g_file_test(ldif, G_FILE_TEST_EXISTS))
1310 	     return ldif;
1311 
1312 	g_free(ldif);
1313     }
1314     return g_strconcat(g_get_home_dir(), G_DIR_SEPARATOR_S,
1315 			guesses[i], NULL); /* *** Or NULL */
1316 
1317 }
1318 
1319 gboolean
libbalsa_path_is_below_dir(const gchar * path,const gchar * dir)1320 libbalsa_path_is_below_dir(const gchar * path, const gchar * dir)
1321 {
1322     gsize len;
1323 
1324     if (!path || !dir || !g_str_has_prefix(path, dir))
1325         return FALSE;
1326 
1327     len = strlen(dir);
1328 
1329     return dir[len - 1] == G_DIR_SEPARATOR || path[len] == G_DIR_SEPARATOR;
1330 }
1331 
1332 #define LIBBALSA_RADIX 1024.0
1333 #define MAX_WITHOUT_SUFFIX 9999
1334 
1335 gchar *
libbalsa_size_to_gchar(guint64 size)1336 libbalsa_size_to_gchar(guint64 size)
1337 {
1338     if (size > MAX_WITHOUT_SUFFIX) {
1339         gdouble displayed_size = (gdouble) size;
1340         gchar *s, suffix[] = "KMGT";
1341 
1342         for (s = suffix; /* *s != '\0' */; s++) {
1343             displayed_size /= LIBBALSA_RADIX;
1344             if (displayed_size < 9.995)
1345                 return g_strdup_printf("%.2f%c", displayed_size, *s);
1346             if (displayed_size < 99.95)
1347                 return g_strdup_printf("%.1f%c", displayed_size, *s);
1348             if (displayed_size < LIBBALSA_RADIX - 0.5 || !s[1])
1349                 return g_strdup_printf("%" G_GUINT64_FORMAT "%c",
1350                                        ((guint64) (displayed_size + 0.5)),
1351                                        *s);
1352         }
1353     }
1354 
1355     return g_strdup_printf("%" G_GUINT64_FORMAT, size);
1356 }
1357