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