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