1 /* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
2 /*
3  * libbalsa vfs glue layer library
4  * Copyright (C) 2008 Albrecht Dre� <albrecht.dress@arcor.de>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2, or (at your option)
9  * any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
19  * 02111-1307, USA.
20  */
21 
22 #if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
23 # include "config.h"
24 #endif                          /* HAVE_CONFIG_H */
25 #include "libbalsa-vfs.h"
26 
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <fcntl.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <glib.h>
33 #include <glib/gi18n.h>
34 #include <glib/gstdio.h>
35 #include <gmime/gmime.h>
36 #include "libbalsa.h"
37 #include "misc.h"
38 
39 #include <gio/gio.h>
40 #include "gmime-stream-gio.h"
41 
42 
43 #define LIBBALSA_VFS_ERROR_QUARK (g_quark_from_static_string("libbalsa-vfs"))
44 
45 
46 #define LIBBALSA_VFS_MIME_ACTION "mime_action"
47 
48 #define GIO_INFO_ATTS                           \
49     G_FILE_ATTRIBUTE_STANDARD_TYPE ","          \
50     G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","  \
51     G_FILE_ATTRIBUTE_STANDARD_SIZE ","          \
52     "access::*"
53 
54 
55 struct _LibbalsaVfsPriv {
56     gchar * file_uri;
57     gchar * file_utf8;
58     gchar * folder_uri;
59     gchar * mime_type;
60     gchar * charset;
61     LibBalsaTextAttribute text_attr;
62     GFile * gio_gfile;
63     GFileInfo * info;
64 };
65 
66 
67 static GObjectClass *libbalsa_vfs_parent_class = NULL;
68 
69 
70 static void libbalsa_vfs_class_init(LibbalsaVfsClass * klass);
71 static void libbalsa_vfs_init(LibbalsaVfs * self);
72 static void libbalsa_vfs_finalize(LibbalsaVfs * self);
73 
74 
75 gboolean
libbalsa_vfs_local_only(void)76 libbalsa_vfs_local_only(void)
77 {
78     return FALSE;
79 }
80 
81 
82 GType
libbalsa_vfs_get_type(void)83 libbalsa_vfs_get_type(void)
84 {
85     static GType libbalsa_vfs_type = 0;
86 
87     if (!libbalsa_vfs_type) {
88         static const GTypeInfo libbalsa_vfs_type_info = {
89             sizeof(LibbalsaVfsClass),     /* class_size */
90             NULL,               /* base_init */
91             NULL,               /* base_finalize */
92             (GClassInitFunc) libbalsa_vfs_class_init,   /* class_init */
93             NULL,               /* class_finalize */
94             NULL,               /* class_data */
95             sizeof(LibbalsaVfs),  /* instance_size */
96             0,                  /* n_preallocs */
97             (GInstanceInitFunc) libbalsa_vfs_init,      /* instance_init */
98             /* no value_table */
99         };
100 
101         libbalsa_vfs_type =
102             g_type_register_static(G_TYPE_OBJECT, "LibbalsaVfs",
103                                    &libbalsa_vfs_type_info, 0);
104     }
105 
106     return libbalsa_vfs_type;
107 }
108 
109 
110 static void
libbalsa_vfs_class_init(LibbalsaVfsClass * klass)111 libbalsa_vfs_class_init(LibbalsaVfsClass * klass)
112 {
113     GObjectClass *gobject_klass = G_OBJECT_CLASS(klass);
114 
115     libbalsa_vfs_parent_class = g_type_class_peek(G_TYPE_OBJECT);
116     gobject_klass->finalize =
117         (GObjectFinalizeFunc) libbalsa_vfs_finalize;
118 }
119 
120 
121 static void
libbalsa_vfs_init(LibbalsaVfs * self)122 libbalsa_vfs_init(LibbalsaVfs * self)
123 {
124     self->priv = NULL;
125 }
126 
127 
128 static void
libbalsa_vfs_finalize(LibbalsaVfs * self)129 libbalsa_vfs_finalize(LibbalsaVfs * self)
130 {
131     struct _LibbalsaVfsPriv * priv;
132 
133     g_return_if_fail(self != NULL);
134     priv = self->priv;
135 
136     if (priv) {
137         g_free(priv->file_uri);
138         g_free(priv->file_utf8);
139         g_free(priv->folder_uri);
140         g_free(priv->mime_type);
141         g_free(priv->charset);
142         if (priv->gio_gfile)
143             g_object_unref(priv->gio_gfile);
144         if (priv->info)
145             g_object_unref(priv->info);
146         g_free(priv);
147     }
148 
149     libbalsa_vfs_parent_class->finalize(G_OBJECT(self));
150 }
151 
152 
153 LibbalsaVfs *
libbalsa_vfs_new(void)154 libbalsa_vfs_new(void)
155 {
156     return LIBBALSA_VFS(g_object_new(LIBBALSA_TYPE_VFS, NULL));
157 }
158 
159 
160 LibbalsaVfs *
libbalsa_vfs_new_from_uri(const gchar * uri)161 libbalsa_vfs_new_from_uri(const gchar * uri)
162 {
163     LibbalsaVfs * retval;
164 
165     g_return_val_if_fail(uri, NULL);
166     if (!(retval = libbalsa_vfs_new()))
167         return NULL;
168 
169     if (!(retval->priv = g_new0(struct _LibbalsaVfsPriv, 1))) {
170         g_object_unref(G_OBJECT(retval));
171         return NULL;
172     }
173     retval->priv->text_attr = (LibBalsaTextAttribute) -1;
174 
175     retval->priv->file_uri = g_strdup(uri);
176     retval->priv->gio_gfile = g_file_new_for_uri(uri);
177 
178     return retval;
179 }
180 
181 
182 /* create a new LibbalsaVfs object by appending text to the existing object
183  * file (note: text is in utf8, not escaped) */
184 LibbalsaVfs *
libbalsa_vfs_append(const LibbalsaVfs * file,const gchar * text)185 libbalsa_vfs_append(const LibbalsaVfs * file, const gchar * text)
186 {
187     gchar * p;
188     gchar * q;
189     LibbalsaVfs * retval;
190 
191     g_return_val_if_fail(file, NULL);
192     g_return_val_if_fail(file->priv, NULL);
193     g_return_val_if_fail(file->priv->file_uri, NULL);
194     g_return_val_if_fail(text, NULL);
195 
196     /* fake an absolute file name which we can convert to an uri */
197     p = g_strconcat("/", text, NULL);
198     q = g_filename_to_uri(p, NULL, NULL);
199     g_free(p);
200     if (!q)
201         return NULL;
202 
203     /* append to the existing uri and create the new object from it */
204     p = g_strconcat(file->priv->file_uri, q + 8, NULL);
205     g_free(q);
206     retval = libbalsa_vfs_new_from_uri(p);
207     g_free(p);
208     return retval;
209 }
210 
211 
212 /* create a new LibbalsaVfs object by appending filename to the existing
213  * object dir which describes a folder (note: filename is in utf8, not
214  * escaped) */
215 LibbalsaVfs *
libbalsa_vfs_dir_append(const LibbalsaVfs * dir,const gchar * filename)216 libbalsa_vfs_dir_append(const LibbalsaVfs * dir, const gchar * filename)
217 {
218     gchar * p;
219     gchar * q;
220     LibbalsaVfs * retval;
221 
222     g_return_val_if_fail(dir, NULL);
223     g_return_val_if_fail(dir->priv, NULL);
224     g_return_val_if_fail(dir->priv->file_uri, NULL);
225     g_return_val_if_fail(filename, NULL);
226 
227     /* fake an absolute file name which we can convert to an uri */
228     p = g_strconcat("/", filename, NULL);
229     q = g_filename_to_uri(p, NULL, NULL);
230     g_free(p);
231     if (!q)
232         return NULL;
233 
234     /* append to the existing uri and create the new object from it */
235     p = g_strconcat(dir->priv->file_uri, q + 7, NULL);
236     g_free(q);
237     retval = libbalsa_vfs_new_from_uri(p);
238     g_free(p);
239     return retval;
240 }
241 
242 
243 /* return the text uri of the passed file, removing the last component */
244 const gchar *
libbalsa_vfs_get_folder(const LibbalsaVfs * file)245 libbalsa_vfs_get_folder(const LibbalsaVfs * file)
246 {
247     struct _LibbalsaVfsPriv * priv;
248 
249     g_return_val_if_fail(file, NULL);
250     g_return_val_if_fail(file->priv, NULL);
251     priv = file->priv;
252     g_return_val_if_fail(priv->file_uri, NULL);
253 
254     if (!priv->folder_uri) {
255         gchar * p;
256 
257         if ((priv->folder_uri = g_strdup(priv->file_uri)) &&
258             (p = g_utf8_strrchr(priv->folder_uri, -1, g_utf8_get_char("/"))))
259             *p = '\0';
260     }
261 
262     return priv->folder_uri;
263 }
264 
265 
266 /* return the text uri of the passed file */
267 const gchar *
libbalsa_vfs_get_uri(const LibbalsaVfs * file)268 libbalsa_vfs_get_uri(const LibbalsaVfs * file)
269 {
270     g_return_val_if_fail(file, NULL);
271     g_return_val_if_fail(file->priv, NULL);
272     g_return_val_if_fail(file->priv->file_uri, NULL);
273 
274     return file->priv->file_uri;
275 }
276 
277 
278 /* return the text uri of the passed file as utf8 string (%xx replaced) */
279 const gchar *
libbalsa_vfs_get_uri_utf8(const LibbalsaVfs * file)280 libbalsa_vfs_get_uri_utf8(const LibbalsaVfs * file)
281 {
282     struct _LibbalsaVfsPriv * priv;
283 
284     g_return_val_if_fail(file, NULL);
285     g_return_val_if_fail(file->priv, NULL);
286     priv = file->priv;
287     g_return_val_if_fail(priv->file_uri, NULL);
288 
289     if (!priv->file_utf8) {
290         gchar * p;
291         gchar * q;
292 
293         if (!(p = priv->file_utf8 = g_malloc(strlen(priv->file_uri) + 1)))
294             return NULL;
295         q = priv->file_uri;
296         while (*q != '\0') {
297             if (*q == '%') {
298                 if (g_ascii_isxdigit(q[1]) && g_ascii_isxdigit(q[2])) {
299                     gint val = (g_ascii_xdigit_value(q[1]) << 4) +
300                         g_ascii_xdigit_value(q[2]);
301                     *p++ = (gchar) val;
302                     q += 3;
303                 } else
304                     *p++ = *q++; /* hmmm - shouldn't happen! */
305             } else
306                 *p++ = *q++;
307         }
308         *p = '\0';
309     }
310 
311     return priv->file_utf8;
312 }
313 
314 
315 const gchar *
libbalsa_vfs_get_basename_utf8(const LibbalsaVfs * file)316 libbalsa_vfs_get_basename_utf8(const LibbalsaVfs * file)
317 {
318     const gchar * uri_utf8 = libbalsa_vfs_get_uri_utf8(file);
319     const gchar * p;
320 
321     if (uri_utf8 &&
322         (p = g_utf8_strrchr(uri_utf8, -1, g_utf8_get_char("/"))))
323         return p + 1;
324     else
325         return NULL;
326 }
327 
328 
329 const gchar *
libbalsa_vfs_get_mime_type(const LibbalsaVfs * file)330 libbalsa_vfs_get_mime_type(const LibbalsaVfs * file)
331 {
332     struct _LibbalsaVfsPriv * priv;
333 
334     g_return_val_if_fail(file, NULL);
335     g_return_val_if_fail(file->priv, NULL);
336     priv = file->priv;
337     g_return_val_if_fail(priv->file_uri, NULL);
338 
339     if (!priv->mime_type) {
340         /* use GIO to determine the mime type of the file */
341         g_return_val_if_fail(priv->gio_gfile, FALSE);
342 
343         if (!priv->info)
344             priv->info =
345                 g_file_query_info(priv->gio_gfile, GIO_INFO_ATTS,
346                                   G_FILE_QUERY_INFO_NONE, NULL, NULL);
347         if (priv->info)
348             priv->mime_type =
349                 g_strdup(g_file_info_get_attribute_string(priv->info,
350                                                           G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE));
351 
352         /* always fall back to application/octet-stream */
353         if (!priv->mime_type)
354             priv->mime_type = g_strdup("application/octet-stream");
355     }
356 
357     return priv->mime_type;
358 }
359 
360 
361 const gchar *
libbalsa_vfs_get_charset(const LibbalsaVfs * file)362 libbalsa_vfs_get_charset(const LibbalsaVfs * file)
363 {
364     struct _LibbalsaVfsPriv * priv;
365 
366     g_return_val_if_fail(file, NULL);
367     g_return_val_if_fail(file->priv, NULL);
368     priv = file->priv;
369     g_return_val_if_fail(priv->file_uri, NULL);
370 
371     if (!priv->charset && priv->text_attr == (LibBalsaTextAttribute) -1) {
372         libbalsa_vfs_get_text_attr(file);
373 
374         if (!(priv->text_attr & LIBBALSA_TEXT_HI_BIT))
375             priv->charset = g_strdup("us-ascii");
376         else if (priv->text_attr & LIBBALSA_TEXT_HI_UTF8)
377             priv->charset = g_strdup("utf-8");
378     }
379 
380     return priv->charset;
381 }
382 
383 
384 LibBalsaTextAttribute
libbalsa_vfs_get_text_attr(const LibbalsaVfs * file)385 libbalsa_vfs_get_text_attr(const LibbalsaVfs * file)
386 {
387     struct _LibbalsaVfsPriv * priv;
388 
389     g_return_val_if_fail(file, 0);
390     g_return_val_if_fail(file->priv, 0);
391     priv = file->priv;
392     g_return_val_if_fail(priv->file_uri, 0);
393 
394     if (priv->text_attr == (LibBalsaTextAttribute) -1) {
395         GInputStream * stream;
396 
397         /* use GIO to determine the text attributes of the file */
398         g_return_val_if_fail(priv->gio_gfile, 0);
399         priv->text_attr = 0;
400 
401         /* read and check - see libbalsa_text_attr_file() */
402         if ((stream = G_INPUT_STREAM(g_file_read(priv->gio_gfile, NULL, NULL))) != NULL) {
403             gchar buf[1024];
404             gchar *new_chars = buf;
405             gboolean has_esc = FALSE;
406             gboolean has_hi_bit = FALSE;
407             gboolean has_hi_ctrl = FALSE;
408             gboolean is_utf8 = TRUE;
409             gssize bytes_read;
410 
411             while ((is_utf8 || (!has_esc || !has_hi_bit || !has_hi_ctrl)) &&
412                    ((bytes_read = g_input_stream_read(stream,
413                                                       new_chars,
414                                                       (sizeof buf) - (new_chars - buf) - 1,
415                                                       NULL,
416                                                       NULL)) > 0)) {
417                 new_chars[bytes_read] = '\0';
418 
419                 if (!has_esc || !has_hi_bit || !has_hi_ctrl) {
420                     guchar * p;
421 
422                     for (p = (guchar *) new_chars; *p; p++)
423                         if (*p == 0x1b)
424                             has_esc = TRUE;
425                         else if (*p >= 0x80) {
426                             has_hi_bit = TRUE;
427                             if (*p <= 0x9f)
428                                 has_hi_ctrl = TRUE;
429                         }
430                 }
431 
432                 if (is_utf8) {
433                     const gchar *end;
434 
435                     new_chars = buf;
436                     if (!g_utf8_validate(buf, -1, &end)) {
437                         if (g_utf8_get_char_validated(end, -1) == (gunichar) (-1))
438                             is_utf8 = FALSE;
439                         else
440                             /* copy any remaining bytes, including the
441                              * terminating '\0', to start of buffer */
442                             while ((*new_chars = *end++) != '\0')
443                                 new_chars++;
444                     }
445                 }
446             }
447 
448             g_object_unref(stream);
449 
450             if (has_esc)
451                 priv->text_attr |= LIBBALSA_TEXT_ESC;
452             if (has_hi_bit)
453                 priv->text_attr |= LIBBALSA_TEXT_HI_BIT;
454             if (has_hi_ctrl)
455                 priv->text_attr |= LIBBALSA_TEXT_HI_CTRL;
456             if (is_utf8 && has_hi_bit)
457                 priv->text_attr |= LIBBALSA_TEXT_HI_UTF8;
458         }
459     }
460 
461     return priv->text_attr;
462 }
463 
464 
465 guint64
libbalsa_vfs_get_size(const LibbalsaVfs * file)466 libbalsa_vfs_get_size(const LibbalsaVfs * file)
467 {
468     struct _LibbalsaVfsPriv * priv;
469 
470     g_return_val_if_fail(file, 0);
471     g_return_val_if_fail(file->priv, 0);
472     priv = file->priv;
473     g_return_val_if_fail(priv->file_uri, 0);
474 
475     /* use GIO to determine the size of the file */
476     g_return_val_if_fail(priv->gio_gfile, 0);
477 
478     if (!priv->info)
479         priv->info =
480             g_file_query_info(priv->gio_gfile, GIO_INFO_ATTS,
481                               G_FILE_QUERY_INFO_NONE, NULL, NULL);
482     if (priv->info)
483         return g_file_info_get_attribute_uint64(priv->info,
484                                                 G_FILE_ATTRIBUTE_STANDARD_SIZE);
485 
486     return 0;
487 }
488 
489 
490 /* get a GMime stream for the passed file, either read-only or in
491  * read-write mode */
492 GMimeStream *
libbalsa_vfs_create_stream(const LibbalsaVfs * file,mode_t mode,gboolean rdwr,GError ** err)493 libbalsa_vfs_create_stream(const LibbalsaVfs * file, mode_t mode,
494                            gboolean rdwr, GError ** err)
495 {
496     struct _LibbalsaVfsPriv * priv;
497 
498     g_return_val_if_fail(file, NULL);
499     g_return_val_if_fail(file->priv, NULL);
500     priv = file->priv;
501     g_return_val_if_fail(priv->file_uri, NULL);
502 
503     /* use GIO to create a GMime stream */
504     g_return_val_if_fail(priv->gio_gfile, NULL);
505 
506     return g_mime_stream_gio_new(priv->gio_gfile);
507 }
508 
509 
510 /* return TRUE if the passed file exists */
511 gboolean
libbalsa_vfs_file_exists(const LibbalsaVfs * file)512 libbalsa_vfs_file_exists(const LibbalsaVfs * file)
513 {
514     gboolean result = FALSE;
515     struct _LibbalsaVfsPriv * priv;
516 
517     g_return_val_if_fail(file, FALSE);
518     g_return_val_if_fail(file->priv, FALSE);
519     priv = file->priv;
520     g_return_val_if_fail(priv->file_uri, FALSE);
521 
522     /* use GIO to get the file's attributes - fails if the file does not exist */
523     g_return_val_if_fail(priv->gio_gfile, 0);
524 
525     if (!priv->info)
526         priv->info =
527             g_file_query_info(priv->gio_gfile, GIO_INFO_ATTS,
528                               G_FILE_QUERY_INFO_NONE, NULL, NULL);
529     result = priv->info != NULL;
530 
531     return result;
532 }
533 
534 
535 gboolean
libbalsa_vfs_is_regular_file(const LibbalsaVfs * file,GError ** err)536 libbalsa_vfs_is_regular_file(const LibbalsaVfs * file, GError **err)
537 {
538     gboolean result = FALSE;
539     struct _LibbalsaVfsPriv * priv;
540 
541     g_return_val_if_fail(file, FALSE);
542     g_return_val_if_fail(file->priv, FALSE);
543     priv = file->priv;
544     g_return_val_if_fail(priv->file_uri, FALSE);
545 
546     /* use GIO to check if the file is a regular one which can be read */
547     g_return_val_if_fail(priv->gio_gfile, 0);
548 
549     if (!priv->info)
550         priv->info =
551             g_file_query_info(priv->gio_gfile, GIO_INFO_ATTS,
552                               G_FILE_QUERY_INFO_NONE, NULL, err);
553     if (priv->info) {
554         if (g_file_info_get_file_type(priv->info) != G_FILE_TYPE_REGULAR)
555             g_set_error(err, LIBBALSA_VFS_ERROR_QUARK, -1,
556                         _("not a regular file"));
557         else if (!g_file_info_get_attribute_boolean(priv->info,
558                                                     G_FILE_ATTRIBUTE_ACCESS_CAN_READ)) {
559             /* the read flag may not be set for some remote file systems (like smb),
560              * so try to actually open the file... */
561             GFileInputStream * stream = g_file_read(priv->gio_gfile, NULL, err);
562             if (stream) {
563                 g_object_unref(stream);
564                 result = TRUE;
565             }
566         } else
567             result = TRUE;
568     }
569 
570     return result;
571 }
572 
573 
574 /* unlink the passed file, return 0 on success and -1 on error */
575 gint
libbalsa_vfs_file_unlink(const LibbalsaVfs * file,GError ** err)576 libbalsa_vfs_file_unlink(const LibbalsaVfs * file, GError **err)
577 {
578     gint result = -1;
579     struct _LibbalsaVfsPriv * priv;
580 
581     g_return_val_if_fail(file, -1);
582     g_return_val_if_fail(file->priv, -1);
583     priv = file->priv;
584     g_return_val_if_fail(priv->file_uri, -1);
585 
586     /* use GIO to delete the file */
587     g_return_val_if_fail(priv->gio_gfile, -1);
588     if (g_file_delete(priv->gio_gfile, NULL, err))
589         result = 0;
590 
591     return result;
592 }
593 
594 
595 gboolean
libbalsa_vfs_launch_app(const LibbalsaVfs * file,GObject * object,GError ** err)596 libbalsa_vfs_launch_app(const LibbalsaVfs * file, GObject * object, GError **err)
597 {
598     GAppInfo *app;
599     GList * args;
600     gboolean result;
601 
602     g_return_val_if_fail(file != NULL, FALSE);
603     g_return_val_if_fail(object != NULL, FALSE);
604 
605     app = G_APP_INFO(g_object_get_data(object, LIBBALSA_VFS_MIME_ACTION));
606     if (!app) {
607         g_set_error(err, LIBBALSA_VFS_ERROR_QUARK, -1,
608                     _("Cannot launch, missing application"));
609         return FALSE;
610     }
611     args = g_list_prepend(NULL, file->priv->gio_gfile);
612     result = g_app_info_launch(app, args, NULL, err);
613     g_list_free(args);
614     return result;
615 }
616 
617 
618 gboolean
libbalsa_vfs_launch_app_for_body(LibBalsaMessageBody * mime_body,GObject * object,GError ** err)619 libbalsa_vfs_launch_app_for_body(LibBalsaMessageBody * mime_body,
620                                  GObject * object, GError **err)
621 {
622     gchar *uri;
623     LibbalsaVfs * file;
624     gboolean result;
625 
626     g_return_val_if_fail(mime_body != NULL, FALSE);
627     g_return_val_if_fail(object != NULL, FALSE);
628 
629     if (!libbalsa_message_body_save_temporary(mime_body, err))
630         return FALSE;
631 
632     uri = g_filename_to_uri(mime_body->temp_filename, NULL, NULL);
633     file = libbalsa_vfs_new_from_uri(uri);
634     g_free(uri);
635     result = libbalsa_vfs_launch_app(file,object , err);
636     g_object_unref(file);
637 
638     return result;
639 }
640 
641 
642 gchar *
libbalsa_vfs_content_description(const gchar * mime_type)643 libbalsa_vfs_content_description(const gchar * mime_type)
644 {
645     g_return_val_if_fail(mime_type != NULL, NULL);
646 
647     return g_content_type_get_description(mime_type);
648 }
649 
650 gchar *
libbalsa_vfs_content_type_of_buffer(const guchar * buffer,gsize length)651 libbalsa_vfs_content_type_of_buffer(const guchar * buffer,
652                                     gsize length)
653 {
654     gchar * retval;
655     gboolean content_uncertain;
656 
657     g_return_val_if_fail(buffer != NULL, NULL);
658     g_return_val_if_fail(length > 0, NULL);
659 
660     retval = g_content_type_guess(NULL, buffer, length, &content_uncertain);
661     if (content_uncertain) {
662         g_free(retval);
663         retval = g_strdup("application/octet-stream");
664     }
665     return retval;
666 }
667 
668 
669 static void
gio_add_vfs_menu_item(GtkMenu * menu,GAppInfo * app,GCallback callback,gpointer data)670 gio_add_vfs_menu_item(GtkMenu * menu, GAppInfo *app, GCallback callback,
671                       gpointer data)
672 {
673     gchar *menu_label =
674         g_strdup_printf(_("Open with %s"), g_app_info_get_name(app));
675     GtkWidget *menu_item = gtk_menu_item_new_with_label (menu_label);
676 
677     g_object_ref(G_OBJECT(app));
678     g_object_set_data_full(G_OBJECT(menu_item), LIBBALSA_VFS_MIME_ACTION,
679 			   app, g_object_unref);
680     g_signal_connect(G_OBJECT (menu_item), "activate", callback, data);
681     gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item);
682     g_free(menu_label);
683 }
684 
685 
686 /* fill the passed menu with vfs items */
687 void
libbalsa_vfs_fill_menu_by_content_type(GtkMenu * menu,const gchar * content_type,GCallback callback,gpointer data)688 libbalsa_vfs_fill_menu_by_content_type(GtkMenu * menu,
689 				       const gchar * content_type,
690 				       GCallback callback, gpointer data)
691 {
692     GList* list;
693     GAppInfo *def_app;
694     GList *app_list;
695 
696     g_return_if_fail(data != NULL);
697 
698     if ((def_app = g_app_info_get_default_for_type(content_type, FALSE)))
699         gio_add_vfs_menu_item(menu, def_app, callback, data);
700 
701     app_list = g_app_info_get_all_for_type(content_type);
702     for (list = app_list; list; list = g_list_next(list)) {
703         GAppInfo *app = G_APP_INFO(list->data);
704 
705         if (app && g_app_info_should_show(app) &&
706             (!def_app || !g_app_info_equal(app, def_app)))
707             gio_add_vfs_menu_item(menu, app, callback, data);
708     }
709     if (def_app)
710         g_object_unref(def_app);
711     if (app_list) {
712         g_list_foreach(app_list, (GFunc) g_object_unref, NULL);
713         g_list_free(app_list);
714     }
715 }
716 
717 GtkWidget *
libbalsa_vfs_mime_button(LibBalsaMessageBody * mime_body,const gchar * content_type,GCallback callback,gpointer data)718 libbalsa_vfs_mime_button(LibBalsaMessageBody * mime_body,
719                          const gchar * content_type,
720                          GCallback callback, gpointer data)
721 {
722     GtkWidget *button = NULL;
723     gchar *msg;
724     GAppInfo *app = g_app_info_get_default_for_type(content_type, FALSE);
725 
726     if (app) {
727 	msg = g_strdup_printf(_("Open _part with %s"), g_app_info_get_name(app));
728 	button = gtk_button_new_with_mnemonic(msg);
729 	g_object_set_data_full(G_OBJECT(button), LIBBALSA_VFS_MIME_ACTION,
730 			       (gpointer) app, g_object_unref);
731 	g_free(msg);
732 
733 	g_signal_connect(G_OBJECT(button), "clicked",
734                          callback, data);
735     }
736 
737     return button;
738 }
739