2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2002-2022 the Claws Mail Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include "claws-features.h"
32 #include <glib/gi18n.h>
44 #include "file-utils.h"
47 #define PLUGINS_BLOCK_PREFIX "PluginsWin32_"
49 #define PLUGINS_BLOCK_PREFIX "Plugins_"
56 const gchar
*(*name
) (void);
57 const gchar
*(*desc
) (void);
58 const gchar
*(*version
) (void);
59 const gchar
*(*type
) (void);
60 const gchar
*(*licence
) (void);
61 struct PluginFeature
*(*provides
) (void);
65 gboolean unloaded_hidden
;
66 gboolean in_prefix_dir
;
69 const gchar
*plugin_feature_names
[] =
75 N_("a privacy interface"),
81 /* The plugin must be at least under one of these licences and have
82 the corresponding token returned by the plugin_licence function.
84 const gchar
*plugin_licence_tokens
[] = {
85 "LGPL2.1+", "LGPLv2.1+", "LGPL2.1", "LGPLv2.1",
86 "LGPL3+", "LGPLv3+", "LGPL3", "LGPLv3",
87 "GPL3+", "GPLv3+", "GPL3", "GPLv3",
89 "Apache2.0", "Apache 2.0", "Apache v2.0",
90 "2-clause BSD", "Simplified BSD", "FreeBSD",
91 "3-clause BSD", "New BSD", "Modified BSD",
95 /* Dual (or more) licences are allowed, must be separated by one of these.
97 #define IS_LICENCE_SEP(a) ((a) == ',' || (a) == ';' || (a) == '|' || (a) == '/' || (a) == '\0')
100 * List of all loaded plugins
102 GSList
*plugins
= NULL
;
103 GSList
*plugin_types
= NULL
;
106 * List of plugins unloaded for some fixable reason
108 static GSList
*unloaded_plugins
= NULL
;
110 static gint
list_find_by_string(gconstpointer data
, gconstpointer str
)
112 return strcmp((gchar
*)data
, (gchar
*)str
) ? TRUE
: FALSE
;
115 static gint
list_find_by_plugin_filename(const Plugin
*plugin
, const gchar
*filename
)
117 /* FIXME: There is a problem in case of symlinks or when a
118 user tries to load a plugin with the same name from a
119 different directory. I think it would be better to compare
120 only the basename of the filename here (case-insensitive on
122 cm_return_val_if_fail(plugin
, 1);
123 cm_return_val_if_fail(plugin
->filename
, 1);
124 cm_return_val_if_fail(filename
, 1);
125 return strcmp(filename
, plugin
->filename
);
128 static gboolean
plugin_filename_is_standard_dir(const gchar
*filename
) {
129 return strncmp(filename
, get_plugin_dir(), strlen(get_plugin_dir())) == 0;
132 static gchar
* plugin_canonical_name(const Plugin
*plugin
)
134 if (plugin
->in_prefix_dir
== TRUE
) {
135 if (plugin_filename_is_standard_dir(plugin
->filename
) == TRUE
) {
136 gchar
*plugin_name
= g_path_get_basename(plugin
->filename
);
140 return g_strdup(plugin
->filename
);
143 void plugin_save_list(void)
145 gchar
*rcpath
, *block
;
147 GSList
*type_cur
, *plugin_cur
;
150 for (type_cur
= plugin_types
; type_cur
!= NULL
; type_cur
= g_slist_next(type_cur
)) {
151 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, COMMON_RC
, NULL
);
152 block
= g_strconcat(PLUGINS_BLOCK_PREFIX
, type_cur
->data
, NULL
);
153 if ((pfile
= prefs_write_open(rcpath
)) == NULL
||
154 (prefs_set_block_label(pfile
, block
) < 0)) {
155 g_warning("failed to write plugin list");
162 for (plugin_cur
= plugins
; plugin_cur
!= NULL
; plugin_cur
= g_slist_next(plugin_cur
)) {
163 plugin
= (Plugin
*) plugin_cur
->data
;
165 if (plugin
->unloaded_hidden
)
168 if (!strcmp(plugin
->type(), type_cur
->data
)) {
169 gchar
* name
= plugin_canonical_name(plugin
);
170 int err
= fprintf(pfile
->fp
, "%s\n", name
);
176 for (plugin_cur
= unloaded_plugins
; plugin_cur
!= NULL
; plugin_cur
= g_slist_next(plugin_cur
)) {
177 plugin
= (Plugin
*) plugin_cur
->data
;
179 if (plugin
->unloaded_hidden
)
182 if (!strcmp(plugin
->type(), type_cur
->data
)) {
183 gchar
* name
= plugin_canonical_name(plugin
);
184 int err
= fprintf(pfile
->fp
, "%s\n", name
);
190 if (fprintf(pfile
->fp
, "\n") < 0)
193 if (prefs_file_close(pfile
) < 0)
194 g_warning("failed to write plugin list");
201 g_warning("failed to write plugin list");
202 if (prefs_file_close_revert(pfile
) < 0)
203 g_warning("failed to revert plugin list");
209 static gboolean
plugin_is_loaded(const gchar
*filename
)
211 return (g_slist_find_custom(plugins
, filename
,
212 (GCompareFunc
)list_find_by_plugin_filename
) != NULL
);
215 static Plugin
*plugin_get_by_filename(const gchar
*filename
)
217 GSList
*cur
= plugins
;
218 for(; cur
; cur
= cur
->next
) {
219 Plugin
*p
= (Plugin
*)cur
->data
;
220 if (!strcmp(p
->filename
, filename
)) {
228 * Loads a plugin dependancies
230 * Plugin dependancies are, optionnaly, listed in a file in
231 * get_plugin_dir()/$pluginname.deps.
232 * \param filename The filename of the plugin for which we have to load deps
233 * \param error The location where an error string can be stored
234 * \return 0 on success, -1 otherwise
236 static gint
plugin_load_deps(const gchar
*filename
, gchar
**error
)
239 gchar
*deps_file
= NULL
;
244 tmp
= g_strdup(filename
);
245 if( (p
= strrchr(tmp
, '.')) )
247 deps_file
= g_strconcat(tmp
, ".deps", NULL
);
250 fp
= claws_fopen(deps_file
, "rb");
256 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
257 Plugin
*dep_plugin
= NULL
;
259 buf
[strlen(buf
)-1]='\0'; /* chop off \n */
260 path
= g_strconcat(get_plugin_dir(), buf
,
261 ".", G_MODULE_SUFFIX
, NULL
);
262 if ((dep_plugin
= plugin_get_by_filename(path
)) == NULL
) {
263 debug_print("trying to load %s\n", path
);
264 dep_plugin
= plugin_load(path
, error
);
265 if (dep_plugin
== NULL
) {
270 dep_plugin
->in_prefix_dir
= TRUE
;
273 if (!g_slist_find_custom(dep_plugin
->rdeps
,
274 (gpointer
) filename
, list_find_by_string
)) {
275 debug_print("adding %s to %s rdeps\n",
277 dep_plugin
->filename
);
279 g_slist_append(dep_plugin
->rdeps
,
287 static void plugin_unload_rdeps(Plugin
*plugin
)
289 GSList
*cur
= plugin
->rdeps
;
290 debug_print("removing %s rdeps\n", plugin
->filename
);
292 gchar
*file
= (gchar
*)cur
->data
;
293 Plugin
*rdep_plugin
= file
?plugin_get_by_filename(file
):NULL
;
294 debug_print(" rdep %s: %p\n", file
, rdep_plugin
);
296 plugin_unload(rdep_plugin
);
301 g_slist_free(plugin
->rdeps
);
302 plugin
->rdeps
= NULL
;
305 static void plugin_remove_from_unloaded_list (const gchar
*filename
)
307 GSList
*item
= g_slist_find_custom(unloaded_plugins
,
308 (gpointer
) filename
, (GCompareFunc
)list_find_by_plugin_filename
);
309 Plugin
*unloaded_plugin
= item
? ((Plugin
*)item
->data
):NULL
;
310 if (unloaded_plugin
!= NULL
) {
311 debug_print("removing %s from unloaded list\n", unloaded_plugin
->filename
);
312 unloaded_plugins
= g_slist_remove(unloaded_plugins
, unloaded_plugin
);
313 g_module_close(unloaded_plugin
->module
);
314 g_free(unloaded_plugin
->filename
);
315 g_free(unloaded_plugin
->error
);
316 g_free(unloaded_plugin
);
320 static gchar
*plugin_check_features(struct PluginFeature
*features
) {
322 GSList
*cur
= plugins
;
324 if (features
== NULL
)
326 for(; cur
; cur
= cur
->next
) {
327 Plugin
*p
= (Plugin
*)cur
->data
;
328 struct PluginFeature
*cur_features
= p
->provides();
329 if (p
->unloaded_hidden
)
331 for (j
= 0; cur_features
[j
].type
!= PLUGIN_NOTHING
; j
++) {
332 for (i
= 0; features
[i
].type
!= PLUGIN_NOTHING
; i
++) {
333 if (cur_features
[j
].type
== features
[i
].type
&&
334 !strcmp(cur_features
[j
].subtype
, features
[i
].subtype
)) {
335 return g_strdup_printf(_(
336 "This plugin provides %s (%s), which is "
337 "already provided by the %s plugin."),
338 _(plugin_feature_names
[features
[i
].type
]),
339 _(features
[i
].subtype
),
349 static gboolean
plugin_licence_check(const gchar
*licence
) {
353 if (licence
!= NULL
) {
354 len
= strlen(licence
);
357 g_warning("plugin licence check failed: empty licence");
360 while (plugin_licence_tokens
[i
] != NULL
) {
361 gchar
*found
= g_strstr_len(licence
, len
, plugin_licence_tokens
[i
]);
363 gint tlen
= strlen(plugin_licence_tokens
[i
]);
364 if (len
!= tlen
) { /* not a single license */
365 if (((found
== licence
) && (!IS_LICENCE_SEP(licence
[tlen
])))
366 || (!IS_LICENCE_SEP(*(found
- 1)))
367 || (!IS_LICENCE_SEP(*(found
+ tlen
)))) {
368 debug_print("plugin licence check failed: invalid separator\n");
372 debug_print("plugin licence check passed: %s found\n", plugin_licence_tokens
[i
]);
377 debug_print("plugin licence check failed: %s is not a valid licence\n", licence
);
381 static Plugin
*plugin_load_in_default_dir(const gchar
*filename
, gchar
**error
)
383 Plugin
*plugin
= NULL
;
384 gchar
*filename_default_dir
= NULL
;
385 gchar
*default_error
= NULL
;
386 gchar
*plugin_name
= g_path_get_basename(filename
);
388 filename_default_dir
= g_strconcat(get_plugin_dir(), plugin_name
, NULL
);
390 debug_print("trying to load %s in default plugin directory %s\n",
391 plugin_name
, get_plugin_dir());
395 plugin
= plugin_load(filename_default_dir
, &default_error
);
397 g_free(filename_default_dir
);
402 plugin
->in_prefix_dir
= TRUE
;
405 g_free(default_error
);
414 * \param filename The filename of the plugin to load
415 * \param error The location where an error string can be stored
416 * \return the plugin on success, NULL otherwise
418 Plugin
*plugin_load(const gchar
*filename
, gchar
**error
)
421 gint (*plugin_init
) (gchar
**error
);
422 gpointer plugin_name
, plugin_desc
, plugin_version
;
423 const gchar
*(*plugin_type
)(void);
424 const gchar
*(*plugin_licence
)(void);
425 struct PluginFeature
*(*plugin_provides
)(void);
428 START_TIMING((filename
?filename
:"NULL plugin"));
429 cm_return_val_if_fail(filename
!= NULL
, NULL
);
430 cm_return_val_if_fail(error
!= NULL
, NULL
);
432 /* check duplicate plugin path name */
433 if (plugin_is_loaded(filename
)) {
434 plugin
= plugin_get_by_filename(filename
);
435 if (plugin
->unloaded_hidden
) {
439 *error
= g_strdup(_("Plugin already loaded"));
444 plugin_remove_from_unloaded_list(filename
);
446 if (plugin_load_deps(filename
, error
) < 0)
448 plugin
= g_new0(Plugin
, 1);
449 if (plugin
== NULL
) {
450 *error
= g_strdup(_("Failed to allocate memory for Plugin"));
454 debug_print("trying to load `%s'\n", filename
);
455 plugin
->module
= g_module_open(filename
, 0);
456 if (plugin
->module
== NULL
) {
457 *error
= g_strdup(g_module_error());
459 if (!plugin_filename_is_standard_dir(filename
))
460 return plugin_load_in_default_dir(filename
, error
);
464 plugin
->in_prefix_dir
= FALSE
;
468 if (!g_module_symbol(plugin
->module
, "plugin_name", &plugin_name
) ||
469 !g_module_symbol(plugin
->module
, "plugin_desc", &plugin_desc
) ||
470 !g_module_symbol(plugin
->module
, "plugin_version", &plugin_version
) ||
471 !g_module_symbol(plugin
->module
, "plugin_type", (gpointer
)&plugin_type
) ||
472 !g_module_symbol(plugin
->module
, "plugin_licence", (gpointer
)&plugin_licence
) ||
473 !g_module_symbol(plugin
->module
, "plugin_provides", (gpointer
)&plugin_provides
) ||
474 !g_module_symbol(plugin
->module
, "plugin_init", (gpointer
)&plugin_init
)) {
475 *error
= g_strdup(g_module_error());
476 if (plugin
->unloaded_hidden
)
478 g_module_close(plugin
->module
);
483 if (plugin_licence_check(plugin_licence()) != TRUE
) {
484 *error
= g_strdup(_("This module is not licensed under a GPL v3 or later compatible license."));
485 if (plugin
->unloaded_hidden
)
487 g_module_close(plugin
->module
);
492 if (!strcmp(plugin_type(), "GTK")) {
493 *error
= g_strdup(_("This module is for Claws Mail GTK1."));
494 if (plugin
->unloaded_hidden
)
496 g_module_close(plugin
->module
);
501 if ((*error
= plugin_check_features(plugin_provides())) != NULL
) {
502 if (plugin
->unloaded_hidden
)
504 g_module_close(plugin
->module
);
508 plugin
->name
= plugin_name
;
509 plugin
->desc
= plugin_desc
;
510 plugin
->version
= plugin_version
;
511 plugin
->type
= plugin_type
;
512 plugin
->licence
= plugin_licence
;
513 plugin
->provides
= plugin_provides
;
514 plugin
->filename
= g_strdup(filename
);
515 plugin
->error
= NULL
;
517 if ((ok
= plugin_init(error
)) < 0) {
519 plugin
->error
= g_strdup(*error
);
520 unloaded_plugins
= g_slist_append(unloaded_plugins
, plugin
);
524 if (!plugin
->unloaded_hidden
)
525 plugins
= g_slist_append(plugins
, plugin
);
526 plugin
->unloaded_hidden
= FALSE
;
528 debug_print("Plugin %s (from file %s) loaded\n", plugin
->name(), filename
);
533 void plugin_unload(Plugin
*plugin
)
535 gboolean (*plugin_done
) (void);
536 gboolean can_unload
= TRUE
;
538 plugin_unload_rdeps(plugin
);
540 if (plugin
->unloaded_hidden
)
544 plugin_remove_from_unloaded_list(plugin
->filename
);
547 if (g_module_symbol(plugin
->module
, "plugin_done", (gpointer
) &plugin_done
)) {
548 can_unload
= plugin_done();
553 if (!RUNNING_ON_VALGRIND
) {
554 g_module_close(plugin
->module
);
557 g_module_close(plugin
->module
);
559 plugins
= g_slist_remove(plugins
, plugin
);
560 g_free(plugin
->filename
);
563 plugin
->unloaded_hidden
= TRUE
;
568 static void replace_old_plugin_name(gchar
*plugin_name
)
570 gchar
*old_name_end
= g_strconcat("_plugin.", G_MODULE_SUFFIX
, NULL
);
571 gchar
*matches
= strstr(plugin_name
, old_name_end
);
574 g_free(old_name_end
);
576 } else if (plugin_name
+ strlen(plugin_name
) != matches
+ strlen(matches
)) {
577 g_free(old_name_end
);
580 gchar
*new_name_end
= g_strconcat(".", G_MODULE_SUFFIX
, NULL
);
581 int offset
= strlen(plugin_name
) - strlen(old_name_end
);
583 debug_print("Replacing old plugin name %s\n", plugin_name
);
585 strncpy(plugin_name
+ offset
, new_name_end
, strlen(old_name_end
) - 1);
586 debug_print(" to %s\n", plugin_name
);
587 g_free(new_name_end
);
589 g_free(old_name_end
);
592 void plugin_load_all(const gchar
*type
)
597 gchar
*error
= NULL
, *block
;
599 plugin_types
= g_slist_append(plugin_types
, g_strdup(type
));
601 rcpath
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
, COMMON_RC
, NULL
);
602 block
= g_strconcat(PLUGINS_BLOCK_PREFIX
, type
, NULL
);
603 if ((pfile
= prefs_read_open(rcpath
)) == NULL
||
604 (prefs_set_block_label(pfile
, block
) < 0)) {
608 prefs_file_close(pfile
);
613 while (claws_fgets(buf
, sizeof(buf
), pfile
->fp
) != NULL
) {
618 replace_old_plugin_name(buf
);
620 if ((buf
[0] != '\0') && (plugin_load(buf
, &error
) == NULL
)) {
621 g_warning("plugin loading error: %s", error
);
625 prefs_file_close(pfile
);
630 void plugin_unload_all(const gchar
*type
)
634 list
= g_slist_copy(plugins
);
635 list
= g_slist_reverse(list
);
637 for(cur
= list
; cur
!= NULL
; cur
= g_slist_next(cur
)) {
638 Plugin
*plugin
= (Plugin
*) cur
->data
;
640 if (!strcmp(type
, plugin
->type()))
641 plugin_unload(plugin
);
645 cur
= g_slist_find_custom(plugin_types
, (gpointer
) type
, list_find_by_string
);
648 plugin_types
= g_slist_remove(plugin_types
, cur
);
653 /* Load those plugins we always want to use. No error output; just
655 void plugin_load_standard_plugins (void)
657 static const char *names
[] = {
668 gchar
*error
, *filename
;
670 for (i
=0; names
[i
]; i
++) {
671 /* Simple hack to check whether the plugin has already
672 * been loaded but checking only for the basename. */
673 GSList
*cur
= plugins
;
674 for(; cur
; cur
= cur
->next
) {
675 Plugin
*p
= (Plugin
*)cur
->data
;
676 if (strstr(p
->filename
, names
[i
]))
679 if (!cur
) { /* Not yet loaded. */
680 /* FIXME: get_plugin_dir () returns with a trailing
681 * (back)slash; this should be fixed so that we can use
682 * g_module_build_path here. */
684 filename
= g_strconcat (get_plugin_dir(),
687 filename
= g_strconcat (get_plugin_dir(),
688 names
[i
], ".", G_MODULE_SUFFIX
, NULL
);
691 plugin_load(filename
, &error
);
698 GSList
*plugin_get_list(void)
701 GSList
*cur
= plugins
;
702 for (; cur
; cur
= cur
->next
) {
703 Plugin
*p
= (Plugin
*)cur
->data
;
704 if (!p
->unloaded_hidden
)
705 new = g_slist_prepend(new, p
);
707 new = g_slist_reverse(new);
711 Plugin
*plugin_get_loaded_by_name(const gchar
*name
)
713 Plugin
*plugin
= NULL
;
715 new = plugin_get_list();
716 for (cur
= new; cur
; cur
= g_slist_next(cur
)) {
717 plugin
= (Plugin
*)cur
->data
;
718 if (!g_ascii_strcasecmp(plugin
->name(), name
))
727 GSList
*plugin_get_unloaded_list(void)
729 return g_slist_copy(unloaded_plugins
);
732 const gchar
*plugin_get_name(Plugin
*plugin
)
734 return plugin
->name();
737 const gchar
*plugin_get_desc(Plugin
*plugin
)
739 return plugin
->desc();
742 const gchar
*plugin_get_version(Plugin
*plugin
)
744 return plugin
->version();
747 const gchar
*plugin_get_error(Plugin
*plugin
)
749 return plugin
->error
;
752 /* Generally called in plugin_init() function of each plugin. It check the
753 * minimal and compiled version of claws binary required by the plugin.
754 * If (@minimum_claws_version == 0 || @compiled_claws_version == 0), don't
755 * check the corresponding version.
757 * If an error occurs {
758 * If @error == NULL { don't allocate error string. }
759 * If @error != NULL { error string is allocated and must be freed after
762 * Returns: FALSE if an error occurs, TRUE if all is OK.
764 gint
check_plugin_version(guint32 minimum_claws_version
,
765 guint32 compiled_claws_version
,
766 const gchar
*plugin_name
,
769 guint32 claws_version
= claws_get_version();
771 if (compiled_claws_version
!= 0 && claws_version
> compiled_claws_version
) {
773 *error
= (plugin_name
&& *plugin_name
)
774 ? g_strdup_printf(_("Your version of Claws Mail is newer than the "
775 "version the '%s' plugin was built with."),
777 : g_strdup(_("Your version of Claws Mail is newer than the "
778 "version the plugin was built with."));
783 if (minimum_claws_version
!= 0 && claws_version
< minimum_claws_version
) {
785 *error
= (plugin_name
&& *plugin_name
)
786 ? g_strdup_printf(_("Your version of Claws Mail is too old for "
787 "the '%s' plugin."), plugin_name
)
788 : g_strdup(_("Your version of Claws Mail is too old for the plugin."));