1 /* SPDX-License-Identifier: Zlib */
2 
3 #include "plugin.h"
4 
5 #include <stdlib.h>
6 #include <glib/gi18n.h>
7 
8 #include <girara/datastructures.h>
9 #include <girara/utils.h>
10 #include <girara/statusbar.h>
11 #include <girara/session.h>
12 #include <girara/settings.h>
13 
14 /**
15  * Document plugin structure
16  */
17 struct zathura_plugin_s {
18   girara_list_t* content_types; /**< List of supported content types */
19   zathura_plugin_functions_t functions; /**< Document functions */
20   GModule* handle; /**< DLL handle */
21   char* path; /**< Path to the plugin */
22   const zathura_plugin_definition_t* definition;
23 };
24 
25 /**
26  * Plugin mapping
27  */
28 typedef struct zathura_type_plugin_mapping_s {
29   const gchar* type; /**< Plugin type */
30   zathura_plugin_t* plugin; /**< Mapped plugin */
31 } zathura_type_plugin_mapping_t;
32 
33 /**
34  * Plugin manager
35  */
36 struct zathura_plugin_manager_s {
37   girara_list_t* plugins; /**< List of plugins */
38   girara_list_t* path; /**< List of plugin paths */
39   girara_list_t* type_plugin_mapping; /**< List of type -> plugin mappings */
40   girara_list_t* content_types; /**< List of all registered content types */
41 };
42 
43 static void plugin_add_mimetype(zathura_plugin_t* plugin, const char* mime_type);
44 static bool register_plugin(zathura_plugin_manager_t* plugin_manager, zathura_plugin_t* plugin);
45 static bool plugin_mapping_new(zathura_plugin_manager_t* plugin_manager, const gchar* type, zathura_plugin_t* plugin);
46 static void zathura_plugin_free(zathura_plugin_t* plugin);
47 static void zathura_type_plugin_mapping_free(zathura_type_plugin_mapping_t* mapping);
48 
49 zathura_plugin_manager_t*
zathura_plugin_manager_new(void)50 zathura_plugin_manager_new(void)
51 {
52   zathura_plugin_manager_t* plugin_manager = g_try_malloc0(sizeof(zathura_plugin_manager_t));
53   if (plugin_manager == NULL) {
54     return NULL;
55   }
56 
57   plugin_manager->plugins = girara_list_new2((girara_free_function_t) zathura_plugin_free);
58   plugin_manager->path    = girara_list_new2(g_free);
59   plugin_manager->type_plugin_mapping = girara_list_new2((girara_free_function_t)zathura_type_plugin_mapping_free);
60   plugin_manager->content_types = girara_list_new2(g_free);
61 
62   if (plugin_manager->plugins == NULL
63       || plugin_manager->path == NULL
64       || plugin_manager->type_plugin_mapping == NULL
65       || plugin_manager->content_types == NULL) {
66     zathura_plugin_manager_free(plugin_manager);
67     return NULL;
68   }
69 
70   return plugin_manager;
71 }
72 
73 void
zathura_plugin_manager_add_dir(zathura_plugin_manager_t * plugin_manager,const char * dir)74 zathura_plugin_manager_add_dir(zathura_plugin_manager_t* plugin_manager, const char* dir)
75 {
76   if (plugin_manager == NULL || plugin_manager->path == NULL) {
77     return;
78   }
79 
80   girara_list_append(plugin_manager->path, g_strdup(dir));
81 }
82 
83 static bool
check_suffix(const char * path)84 check_suffix(const char* path)
85 {
86 #ifdef __APPLE__
87   if (g_str_has_suffix(path, ".dylib") == TRUE) {
88     return true;
89   }
90 #else
91   if (g_str_has_suffix(path, ".so") == TRUE) {
92     return true;
93   }
94 #endif
95 
96   return false;
97 }
98 
99 static void
load_plugin(zathura_plugin_manager_t * plugin_manager,const char * plugindir,const char * name)100 load_plugin(zathura_plugin_manager_t* plugin_manager, const char* plugindir, const char* name)
101 {
102   char* path = g_build_filename(plugindir, name, NULL);
103   if (g_file_test(path, G_FILE_TEST_IS_REGULAR) == 0) {
104     girara_debug("'%s' is not a regular file. Skipping.", path);
105     g_free(path);
106     return;
107   }
108 
109   if (check_suffix(path) == false) {
110     girara_debug("'%s' is not a plugin file. Skipping.", path);
111     g_free(path);
112     return;
113   }
114 
115   /* load plugin */
116   GModule* handle = g_module_open(path, G_MODULE_BIND_LOCAL);
117   if (handle == NULL) {
118     girara_error("Could not load plugin '%s' (%s).", path, g_module_error());
119     g_free(path);
120     return;
121   }
122 
123   /* resolve symbols and check API and ABI version*/
124   const zathura_plugin_definition_t* plugin_definition = NULL;
125   if (g_module_symbol(handle, G_STRINGIFY(ZATHURA_PLUGIN_DEFINITION_SYMBOL), (void**) &plugin_definition) == FALSE ||
126       plugin_definition == NULL) {
127     girara_error("Could not find '%s' in plugin %s - is not a plugin or needs to be rebuilt.", G_STRINGIFY(ZATHURA_PLUGIN_DEFINITION_SYMBOL), path);
128     g_free(path);
129     g_module_close(handle);
130     return;
131   }
132 
133   /* check name */
134   if (plugin_definition->name == NULL) {
135     girara_error("Plugin has no name.");
136     g_free(path);
137     g_module_close(handle);
138     return;
139   }
140 
141   /* check mime type */
142   if (plugin_definition->mime_types == NULL || plugin_definition->mime_types_size == 0) {
143     girara_error("Plugin does not handly any mime types.");
144     g_free(path);
145     g_module_close(handle);
146     return;
147   }
148 
149   zathura_plugin_t* plugin = g_try_malloc0(sizeof(zathura_plugin_t));
150   if (plugin == NULL) {
151     girara_error("Failed to allocate memory for plugin.");
152     g_free(path);
153     g_module_close(handle);
154     return;
155   }
156 
157   plugin->definition = plugin_definition;
158   plugin->functions = plugin_definition->functions;
159   plugin->content_types = girara_list_new2(g_free);
160   plugin->handle = handle;
161   plugin->path = path;
162 
163   // register mime types
164   for (size_t s = 0; s != plugin_definition->mime_types_size; ++s) {
165     plugin_add_mimetype(plugin, plugin_definition->mime_types[s]);
166   }
167 
168   bool ret = register_plugin(plugin_manager, plugin);
169   if (ret == false) {
170     girara_error("Could not register plugin '%s'.", path);
171     zathura_plugin_free(plugin);
172   } else {
173     girara_debug("Successfully loaded plugin from '%s'.", path);
174     girara_debug("plugin %s: version %u.%u.%u", plugin_definition->name,
175                  plugin_definition->version.major, plugin_definition->version.minor,
176                  plugin_definition->version.rev);
177   }
178 }
179 
180 static void
load_dir(void * data,void * userdata)181 load_dir(void* data, void* userdata)
182 {
183   const char* plugindir                    = data;
184   zathura_plugin_manager_t* plugin_manager = userdata;
185 
186   GDir* dir = g_dir_open(plugindir, 0, NULL);
187   if (dir == NULL) {
188     girara_error("could not open plugin directory: %s", plugindir);
189   } else {
190     const char* name = NULL;
191     while ((name = g_dir_read_name(dir)) != NULL) {
192       load_plugin(plugin_manager, plugindir, name);
193     }
194     g_dir_close(dir);
195   }
196 }
197 
198 void
zathura_plugin_manager_load(zathura_plugin_manager_t * plugin_manager)199 zathura_plugin_manager_load(zathura_plugin_manager_t* plugin_manager)
200 {
201   if (plugin_manager == NULL || plugin_manager->path == NULL) {
202     return;
203   }
204 
205   /* read all files in the plugin directory */
206   girara_list_foreach(plugin_manager->path, load_dir, plugin_manager);
207 }
208 
209 zathura_plugin_t*
zathura_plugin_manager_get_plugin(zathura_plugin_manager_t * plugin_manager,const char * type)210 zathura_plugin_manager_get_plugin(zathura_plugin_manager_t* plugin_manager, const char* type)
211 {
212   if (plugin_manager == NULL || plugin_manager->type_plugin_mapping == NULL || type == NULL) {
213     return NULL;
214   }
215 
216   zathura_plugin_t* plugin = NULL;
217   GIRARA_LIST_FOREACH_BODY(plugin_manager->type_plugin_mapping, zathura_type_plugin_mapping_t*, mapping,
218     if (g_content_type_equals(type, mapping->type)) {
219       plugin = mapping->plugin;
220       break;
221     }
222   );
223 
224   return plugin;
225 }
226 
227 girara_list_t*
zathura_plugin_manager_get_plugins(zathura_plugin_manager_t * plugin_manager)228 zathura_plugin_manager_get_plugins(zathura_plugin_manager_t* plugin_manager)
229 {
230   if (plugin_manager == NULL || plugin_manager->plugins == NULL) {
231     return NULL;
232   }
233 
234   return plugin_manager->plugins;
235 }
236 
237 girara_list_t*
zathura_plugin_manager_get_content_types(zathura_plugin_manager_t * plugin_manager)238 zathura_plugin_manager_get_content_types(zathura_plugin_manager_t* plugin_manager)
239 {
240   if (plugin_manager == NULL) {
241     return NULL;
242   }
243 
244   return plugin_manager->content_types;
245 }
246 
247 void
zathura_plugin_manager_free(zathura_plugin_manager_t * plugin_manager)248 zathura_plugin_manager_free(zathura_plugin_manager_t* plugin_manager)
249 {
250   if (plugin_manager == NULL) {
251     return;
252   }
253 
254   girara_list_free(plugin_manager->content_types);
255   girara_list_free(plugin_manager->type_plugin_mapping);
256   girara_list_free(plugin_manager->path);
257   girara_list_free(plugin_manager->plugins);
258 
259   g_free(plugin_manager);
260 }
261 
262 static bool
register_plugin(zathura_plugin_manager_t * plugin_manager,zathura_plugin_t * plugin)263 register_plugin(zathura_plugin_manager_t* plugin_manager, zathura_plugin_t* plugin)
264 {
265   if (plugin == NULL
266       || plugin->content_types == NULL
267       || plugin_manager == NULL
268       || plugin_manager->plugins == NULL) {
269     girara_error("plugin: could not register");
270     return false;
271   }
272 
273   bool at_least_one = false;
274   GIRARA_LIST_FOREACH_BODY(plugin->content_types, gchar*, type,
275     if (plugin_mapping_new(plugin_manager, type, plugin) == false) {
276       girara_error("plugin: filetype already registered: %s", type);
277     } else {
278       girara_debug("plugin: filetype mapping added: %s", type);
279       at_least_one = true;
280     }
281   );
282 
283   if (at_least_one == true) {
284     girara_list_append(plugin_manager->plugins, plugin);
285   }
286 
287   return at_least_one;
288 }
289 
290 static bool
plugin_mapping_new(zathura_plugin_manager_t * plugin_manager,const gchar * type,zathura_plugin_t * plugin)291 plugin_mapping_new(zathura_plugin_manager_t* plugin_manager, const gchar* type, zathura_plugin_t* plugin)
292 {
293   g_return_val_if_fail(plugin_manager != NULL, false);
294   g_return_val_if_fail(type           != NULL, false);
295   g_return_val_if_fail(plugin         != NULL, false);
296 
297   bool already_registered = false;
298   GIRARA_LIST_FOREACH_BODY(plugin_manager->type_plugin_mapping, zathura_type_plugin_mapping_t*, mapping,
299     if (g_content_type_equals(type, mapping->type)) {
300       already_registered = true;
301       break;
302     }
303   );
304 
305   if (already_registered == true) {
306     return false;
307   }
308 
309   zathura_type_plugin_mapping_t* mapping = g_try_malloc0(sizeof(zathura_type_plugin_mapping_t));
310   if (mapping == NULL) {
311     return false;
312   }
313 
314   mapping->type   = g_strdup(type);
315   mapping->plugin = plugin;
316   girara_list_append(plugin_manager->type_plugin_mapping, mapping);
317   girara_list_append(plugin_manager->content_types, g_strdup(type));
318 
319   return true;
320 }
321 
322 static void
zathura_type_plugin_mapping_free(zathura_type_plugin_mapping_t * mapping)323 zathura_type_plugin_mapping_free(zathura_type_plugin_mapping_t* mapping)
324 {
325   if (mapping == NULL) {
326     return;
327   }
328 
329   g_free((void*)mapping->type);
330   g_free(mapping);
331 }
332 
333 static void
zathura_plugin_free(zathura_plugin_t * plugin)334 zathura_plugin_free(zathura_plugin_t* plugin)
335 {
336   if (plugin == NULL) {
337     return;
338   }
339 
340   if (plugin->path != NULL) {
341     g_free(plugin->path);
342   }
343 
344   g_module_close(plugin->handle);
345   girara_list_free(plugin->content_types);
346 
347   g_free(plugin);
348 }
349 
350 static void
plugin_add_mimetype(zathura_plugin_t * plugin,const char * mime_type)351 plugin_add_mimetype(zathura_plugin_t* plugin, const char* mime_type)
352 {
353   if (plugin == NULL || mime_type == NULL) {
354     return;
355   }
356 
357   char* content_type = g_content_type_from_mime_type(mime_type);
358   if (content_type == NULL) {
359     girara_warning("plugin: unable to convert mime type: %s", mime_type);
360   } else {
361     girara_list_append(plugin->content_types, content_type);
362   }
363 }
364 
365 const zathura_plugin_functions_t*
zathura_plugin_get_functions(zathura_plugin_t * plugin)366 zathura_plugin_get_functions(zathura_plugin_t* plugin)
367 {
368   if (plugin != NULL) {
369     return &plugin->functions;
370   } else {
371     return NULL;
372   }
373 }
374 
375 const char*
zathura_plugin_get_name(zathura_plugin_t * plugin)376 zathura_plugin_get_name(zathura_plugin_t* plugin)
377 {
378   if (plugin != NULL && plugin->definition != NULL) {
379     return plugin->definition->name;
380   } else {
381     return NULL;
382   }
383 }
384 
385 char*
zathura_plugin_get_path(zathura_plugin_t * plugin)386 zathura_plugin_get_path(zathura_plugin_t* plugin)
387 {
388   if (plugin != NULL) {
389     return plugin->path;
390   } else {
391     return NULL;
392   }
393 }
394 
395 zathura_plugin_version_t
zathura_plugin_get_version(zathura_plugin_t * plugin)396 zathura_plugin_get_version(zathura_plugin_t* plugin)
397 {
398   if (plugin != NULL && plugin->definition != NULL) {
399     return plugin->definition->version;
400   }
401 
402   zathura_plugin_version_t version = { 0, 0, 0 };
403   return version;
404 }
405