1 /*
2  * Copyright (c) 2017-2021 Free Software Foundation, Inc.
3  *
4  * This file is part of Wget.
5  *
6  * Wget 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 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Wget 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 Wget.  If not, see <https://www.gnu.org/licenses/>.
18  *
19  *
20  * Plugin support implementation
21  *
22  */
23 
24 #include <config.h>
25 
26 #include <string.h>
27 
28 #include <wget.h>
29 
30 #include "wget_main.h"
31 #include "wget_dl.h"
32 #include "wget_plugin.h"
33 
34 // Strings
35 static const char *plugin_list_envvar = "WGET2_PLUGINS";
36 
37 // Splits string using the given separator and appends the array to vector.
split_string(const char * str,char separator,wget_vector * v)38 static void split_string(const char *str, char separator, wget_vector *v)
39 {
40 	const char *ptr, *pmark;
41 
42 	for (pmark = ptr = str; *ptr; pmark = ptr + 1) {
43 		if ((ptr = strchrnul(pmark, separator)) > pmark)
44 			wget_vector_add(v, wget_strmemdup(pmark, ptr - pmark));
45 	}
46 }
47 
48 // Private members of the plugin
49 typedef struct {
50 	plugin_t parent;
51 	// Finalizer function, to be called when wget2 exits
52 	wget_plugin_finalizer_fn *finalizer;
53 	// The plugin's option processor
54 	wget_plugin_option_callback *argp;
55 	// The plugin's URL filter
56 	wget_plugin_url_filter_callback *url_filter;
57 	// The plugin's post processor
58 	wget_plugin_post_processor *post_processor;
59 	// Buffer to store plugin name
60 	char name_buf[];
61 } plugin_priv_t;
62 
63 static int initialized = 0;
64 // Plugin search paths
65 static wget_vector *search_paths;
66 // List of loaded plugins
67 static wget_vector *plugin_list;
68 // Index of plugins by plugin name
69 static wget_stringmap *plugin_name_index;
70 // Whether any of the previous options forwarded was 'help'
71 static int plugin_help_forwarded;
72 
73 // Sets a list of directories to search for plugins, separated by
74 // _separator_.
plugin_db_add_search_paths(const char * paths,char separator)75 void plugin_db_add_search_paths(const char *paths, char separator)
76 {
77 	split_string(paths, separator, search_paths);
78 }
79 
80 // Clears list of directories to search for plugins
plugin_db_clear_search_paths(void)81 void plugin_db_clear_search_paths(void)
82 {
83 	wget_vector_clear(search_paths);
84 }
85 
86 // Basic plugin API
impl_register_finalizer(wget_plugin * p_plugin,wget_plugin_finalizer_fn * fn)87 static void impl_register_finalizer
88 	(wget_plugin *p_plugin, wget_plugin_finalizer_fn *fn)
89 {
90 	plugin_priv_t *priv = (plugin_priv_t *) p_plugin;
91 
92 	priv->finalizer = fn;
93 }
94 
impl_get_name(wget_plugin * p_plugin)95 static const char *impl_get_name(wget_plugin *p_plugin)
96 {
97 	plugin_t *plugin = (plugin_t *) p_plugin;
98 
99 	return plugin->name;
100 }
101 
impl_register_argp(wget_plugin * p_plugin,wget_plugin_option_callback * fn)102 static void impl_register_argp
103 	(wget_plugin *p_plugin, wget_plugin_option_callback *fn)
104 {
105 	plugin_priv_t *priv = (plugin_priv_t *) p_plugin;
106 
107 	priv->argp = fn;
108 }
109 
110 // API for URL interception
111 typedef struct {
112 	wget_intercept_action parent;
113 
114 	struct plugin_db_forward_url_verdict verdict;
115 } intercept_action;
116 
impl_action_reject(wget_intercept_action * p_action)117 static void impl_action_reject(wget_intercept_action *p_action)
118 {
119 	intercept_action *action = (intercept_action *) p_action;
120 
121 	action->verdict.reject = 1;
122 }
123 
impl_action_accept(wget_intercept_action * p_action)124 static void impl_action_accept(wget_intercept_action *p_action)
125 {
126 	intercept_action *action = (intercept_action *) p_action;
127 
128 	action->verdict.accept = 1;
129 }
130 
impl_action_set_alt_url(wget_intercept_action * p_action,const wget_iri * iri)131 static void impl_action_set_alt_url(wget_intercept_action *p_action, const wget_iri *iri)
132 {
133 	intercept_action *action = (intercept_action *) p_action;
134 
135 	if (action->verdict.alt_iri)
136 		wget_iri_free(&action->verdict.alt_iri);
137 	action->verdict.alt_iri = wget_iri_clone(iri);
138 }
139 
impl_action_set_local_filename(wget_intercept_action * p_action,const char * local_filename)140 static void impl_action_set_local_filename(wget_intercept_action *p_action, const char *local_filename)
141 {
142 	intercept_action *action = (intercept_action *) p_action;
143 
144 	if (action->verdict.alt_local_filename)
145 		wget_free(action->verdict.alt_local_filename);
146 	action->verdict.alt_local_filename = wget_strdup(local_filename);
147 }
148 
impl_register_url_filter(wget_plugin * p_plugin,wget_plugin_url_filter_callback * fn)149 static void impl_register_url_filter(wget_plugin *p_plugin, wget_plugin_url_filter_callback *fn)
150 {
151 	plugin_priv_t *priv = (plugin_priv_t *) p_plugin;
152 
153 	priv->url_filter = fn;
154 }
155 
156 // API Exposed for plugins for intercepting downloaded files:
157 typedef struct {
158 	wget_downloaded_file parent;
159 
160 	const wget_iri *iri;
161 	const char *filename;
162 	uint64_t size;
163 	const void *data;
164 	void *data_buf;
165 	wget_vector *recurse_iris;
166 } downloaded_file;
167 
impl_file_get_source_url(wget_downloaded_file * p_file)168 static const wget_iri *impl_file_get_source_url(wget_downloaded_file *p_file)
169 {
170 	downloaded_file *file = (downloaded_file *) p_file;
171 
172 	return file->iri;
173 }
174 
impl_file_get_local_filename(wget_downloaded_file * p_file)175 static const char *impl_file_get_local_filename(wget_downloaded_file *p_file)
176 {
177 	downloaded_file *file = (downloaded_file *) p_file;
178 
179 	return file->filename;
180 }
181 
impl_file_get_size(wget_downloaded_file * p_file)182 static uint64_t impl_file_get_size(wget_downloaded_file *p_file)
183 {
184 	downloaded_file *file = (downloaded_file *) p_file;
185 
186 	return file->size;
187 }
188 
impl_file_get_contents(wget_downloaded_file * p_file,const void ** data,size_t * size)189 static int impl_file_get_contents(wget_downloaded_file *p_file, const void **data, size_t *size)
190 {
191 	downloaded_file *file = (downloaded_file *) p_file;
192 
193 	if ((! file->data) && file->filename) {
194 		size_t dummy;
195 		file->data_buf = wget_read_file(file->filename, &dummy);
196 		if (! file->data_buf)
197 			return -1;
198 		file->data = file->data_buf;
199 	}
200 
201 	*data = file->data;
202 	*size = file->size;
203 
204 	return 0;
205 }
206 
impl_file_open_stream(wget_downloaded_file * p_file)207 static FILE *impl_file_open_stream(wget_downloaded_file *p_file)
208 {
209 	downloaded_file *file = (downloaded_file *) p_file;
210 
211 #ifdef HAVE_FMEMOPEN
212 	if (file->data)
213 		return fmemopen((void *) file->data, file->size, "rb");
214 #endif
215 	if (file->filename)
216 		return fopen(file->filename, "rb");
217 	return NULL;
218 }
219 
impl_file_get_recurse(wget_downloaded_file * p_file)220 static bool impl_file_get_recurse(wget_downloaded_file *p_file)
221 {
222 	downloaded_file *file = (downloaded_file *) p_file;
223 
224 	return file->recurse_iris ? true : false;
225 }
226 
impl_file_add_recurse_url(wget_downloaded_file * p_file,const wget_iri * iri)227 static void impl_file_add_recurse_url(wget_downloaded_file *p_file, const wget_iri *iri)
228 {
229 	downloaded_file *file = (downloaded_file *) p_file;
230 
231 	if (file->recurse_iris)
232 		wget_vector_add(file->recurse_iris, wget_iri_clone(iri));
233 }
234 
impl_register_post_processor(wget_plugin * p_plugin,wget_plugin_post_processor * fn)235 static void impl_register_post_processor(wget_plugin *p_plugin, wget_plugin_post_processor *fn)
236 {
237 	plugin_priv_t *priv = (plugin_priv_t *) p_plugin;
238 
239 	priv->post_processor = fn;
240 }
241 
242 // vtable
243 static struct wget_plugin_vtable vtable = {
244 	.get_name = impl_get_name,
245 	.register_finalizer = impl_register_finalizer,
246 	.register_argp = impl_register_argp,
247 
248 	.action_reject = impl_action_reject,
249 	.action_accept = impl_action_accept,
250 	.action_set_alt_url = impl_action_set_alt_url,
251 	.action_set_local_filename = impl_action_set_local_filename,
252 	.register_url_filter = impl_register_url_filter,
253 
254 	.file_get_source_url = impl_file_get_source_url,
255 	.file_get_local_filename = impl_file_get_local_filename,
256 	.file_get_size = impl_file_get_size,
257 	.file_get_contents = impl_file_get_contents,
258 	.file_open_stream = impl_file_open_stream,
259 	.file_get_recurse = impl_file_get_recurse,
260 	.file_add_recurse_url = impl_file_add_recurse_url,
261 	.register_post_processor = impl_register_post_processor,
262 };
263 
264 // Free resources of the plugin and plugin itself
plugin_free(plugin_t * plugin)265 static void plugin_free(plugin_t *plugin)
266 {
267 	dl_file_close(plugin->dm);
268 	wget_free(plugin);
269 }
270 
271 // Loads a plugin located at given path and assign it a name
load_plugin(const char * name,const char * path,dl_error_t * e)272 static plugin_t *load_plugin(const char *name, const char *path, dl_error_t *e)
273 {
274 	size_t name_len;
275 	dl_file_t *dm;
276 	plugin_t *plugin;
277 	plugin_priv_t *priv;
278 	wget_plugin_initializer_fn *init_fn;
279 
280 	name_len = strlen(name);
281 
282 	// Open object file
283 	dm = dl_file_open(path, e);
284 	if (! dm)
285 		return NULL;
286 
287 	// Create plugin object
288 	plugin = wget_malloc(sizeof(plugin_priv_t) + name_len + 1);
289 
290 	// Initialize private members
291 	priv = (plugin_priv_t *) plugin;
292 	priv->finalizer = NULL;
293 	priv->argp = NULL;
294 	priv->url_filter = NULL;
295 	priv->post_processor = NULL;
296 	wget_strscpy(priv->name_buf, name, name_len + 1);
297 
298 	// Initialize public members
299 	plugin->parent.plugin_data = NULL;
300 	plugin->parent.vtable = &vtable;
301 	plugin->name = priv->name_buf;
302 	plugin->dm = dm;
303 
304 	// Call initializer
305 	*((void **)(&init_fn)) = dl_file_lookup(dm, "wget_plugin_initializer", e);
306 	if (! init_fn) {
307 		plugin_free(plugin);
308 		return NULL;
309 	}
310 	if (init_fn((wget_plugin *) plugin) != 0) {
311 		dl_error_set(e, "Plugin failed to initialize");
312 		plugin_free(plugin);
313 		return NULL;
314 	}
315 
316 	// Add to plugin list
317 	wget_vector_add(plugin_list, plugin);
318 
319 	// Add to map
320 	wget_stringmap_put(plugin_name_index, plugin->name, plugin);
321 
322 	return plugin;
323 }
324 
325 // Loads a plugin using its path. On failure it sets error and
326 // returns NULL.
plugin_db_load_from_path(const char * path,dl_error_t * e)327 plugin_t *plugin_db_load_from_path(const char *path, dl_error_t *e)
328 {
329 	char *name = dl_get_name_from_path(path, 0);
330 	plugin_t *plugin = load_plugin(name, path, e);
331 	wget_free(name);
332 	return plugin;
333 }
334 
335 // Loads a plugin using its name. On failure it sets error and
336 // returns NULL.
plugin_db_load_from_name(const char * name,dl_error_t * e)337 plugin_t *plugin_db_load_from_name(const char *name, dl_error_t *e)
338 {
339 	// Search where the plugin is
340 	plugin_t *plugin;
341 
342 	char *filename = dl_search(name, search_paths);
343 	if (! filename) {
344 		dl_error_set_printf(e, "Plugin '%s' not found in any of the plugin search paths.",
345 				name);
346 		return NULL;
347 	}
348 
349 	// Delegate
350 	plugin = load_plugin(name, filename, e);
351 	wget_free(filename);
352 	return plugin;
353 }
354 
355 // Loads all plugins from environment variables. On any errors it
356 // logs them using wget_error_printf().
plugin_db_load_from_envvar(void)357 int plugin_db_load_from_envvar(void)
358 {
359 	dl_error_t e[1];
360 	wget_vector *v;
361 	const char *str;
362 	int ret = 0;
363 
364 	// Fetch from environment variable
365 	str = getenv(plugin_list_envvar);
366 
367 	if (str) {
368 #ifdef _WIN32
369 		char sep = ';';
370 #else
371 		char sep = ':';
372 #endif
373 		dl_error_init(e);
374 
375 		// Split the value
376 		v = wget_vector_create(16, NULL);
377 		split_string(str, sep, v);
378 
379 		// Load each plugin
380 		for (int i = 0; i < wget_vector_size(v) && ret == 0; i++) {
381 			plugin_t *plugin;
382 			int local = 0;
383 
384 			str = (const char *) wget_vector_get(v, i);
385 			if (strchr(str, '/'))
386 				local = 1;
387 #ifdef _WIN32
388 			if (strchr(str, '\\'))
389 				local = 1;
390 #endif
391 			if (local)
392 				plugin = plugin_db_load_from_path(str, e);
393 			else
394 				plugin = plugin_db_load_from_name(str, e);
395 
396 			if (! plugin) {
397 				wget_error_printf(_("Plugin '%s' failed to load: %s"), str, dl_error_get_msg(e));
398 				dl_error_set(e, NULL);
399 				ret = -1;
400 			}
401 		}
402 
403 		wget_vector_free(&v);
404 	}
405 
406 	return ret;
407 }
408 
409 // Creates a list of all plugins found in plugin search paths.
plugin_db_list(wget_vector * names_out)410 void plugin_db_list(wget_vector *names_out)
411 {
412 	dl_list(search_paths, names_out);
413 }
414 
415 // Forwards a command line option to appropriate plugin.
plugin_db_forward_option(const char * plugin_option,dl_error_t * e)416 int plugin_db_forward_option(const char *plugin_option, dl_error_t *e)
417 {
418 	char *plugin_option_copy;
419 	char *plugin_name, *option, *value;
420 	char *ptr;
421 	plugin_t *plugin;
422 	plugin_priv_t *priv;
423 	int op_res;
424 
425 	// Create writable copy of the input
426 	plugin_option_copy = wget_strdup(plugin_option);
427 
428 	// Get plugin name
429 	ptr = strchr(plugin_option_copy, '.');
430 	if (! ptr) {
431 		dl_error_set_printf(e, "'%s': '.' is missing (separates plugin name and option)", plugin_option);
432 		wget_free(plugin_option_copy);
433 		return -1;
434 	}
435 	if (ptr == plugin_option_copy) {
436 		dl_error_set_printf(e, "'%s': Plugin name is missing.", plugin_option);
437 		wget_free(plugin_option_copy);
438 		return -1;
439 	}
440 	*ptr = 0;
441 	plugin_name = plugin_option_copy;
442 
443 	// Split plugin option and value
444 	option = ptr + 1;
445 	ptr = strchr(option, '=');
446 	if (ptr) {
447 		*ptr = 0;
448 		value = ptr + 1;
449 	} else {
450 		value = NULL;
451 	}
452 	if (*option == 0) {
453 		dl_error_set_printf(e, "'%s': An option is required (after '.', and before '=' if present)",
454 				plugin_option);
455 		wget_free(plugin_option_copy);
456 		return -1;
457 	}
458 
459 	// Handle '--help'
460 	if (strcmp(option, "help") == 0) {
461 		if (value) {
462 			dl_error_set_printf(e, "'help' option does not accept arguments\n");
463 			wget_free(plugin_option_copy);
464 			return -1;
465 		}
466 		plugin_help_forwarded = 1;
467 	}
468 
469 	// Search for plugin
470 	if (!wget_stringmap_get(plugin_name_index, plugin_name, &plugin)) {
471 		dl_error_set_printf(e, "Plugin '%s' is not loaded.", plugin_name);
472 		wget_free(plugin_option_copy);
473 		return -1;
474 	}
475 	priv = (plugin_priv_t *) plugin;
476 	if (! priv->argp) {
477 		dl_error_set_printf(e, "Plugin '%s' does not accept options.", plugin->name);
478 		wget_free(plugin_option_copy);
479 		return -1;
480 	}
481 
482 	op_res = priv->argp((wget_plugin *) plugin, option, value);
483 
484 	if (op_res < 0)
485 	{
486 		dl_error_set_printf(e, "Plugin '%s' did not accept option '%s'",
487 				plugin->name, strchrnul(plugin_option, '.'));
488 		wget_free(plugin_option_copy);
489 		return -1;
490 	}
491 
492 	wget_free(plugin_option_copy);
493 	return 0;
494 }
495 
496 // Shows help from all loaded plugins
plugin_db_show_help(void)497 void plugin_db_show_help(void)
498 {
499 	int n_plugins = wget_vector_size(plugin_list);
500 
501 	for (int i = 0; i < n_plugins; i++) {
502 		plugin_t *plugin = (plugin_t *) wget_vector_get(plugin_list, i);
503 		plugin_priv_t *priv = (plugin_priv_t *) plugin;
504 		if (priv->argp) {
505 			printf(_("Options for %s:\n"), plugin->name);
506 			priv->argp((wget_plugin *) plugin, "help", NULL);
507 			printf("\n");
508 		}
509 	}
510 	plugin_help_forwarded = 1;
511 }
512 
513 // Returns 1 if any of the previous options forwarded was 'help'.
plugin_db_help_forwarded(void)514 int plugin_db_help_forwarded(void)
515 {
516 	return plugin_help_forwarded;
517 }
518 
519 // Forwards a URL about to be enqueued to interested plugins
plugin_db_forward_url(const wget_iri * iri,struct plugin_db_forward_url_verdict * verdict)520 void plugin_db_forward_url(const wget_iri *iri, struct plugin_db_forward_url_verdict *verdict)
521 {
522 	// Initialize action structure
523 	intercept_action action = { .parent.vtable = &vtable };
524 	int n_plugins = wget_vector_size(plugin_list);
525 
526 	for (int i = 0; i < n_plugins; i++) {
527 		plugin_t *plugin = (plugin_t *) wget_vector_get(plugin_list, i);
528 		plugin_priv_t *priv = (plugin_priv_t *) plugin;
529 
530 		if (priv->url_filter) {
531 			const wget_iri *cur_iri = action.verdict.alt_iri;
532 			if (! cur_iri)
533 				cur_iri = iri;
534 
535 			priv->url_filter((wget_plugin *) plugin, cur_iri, (wget_intercept_action *) &action);
536 			if (action.verdict.reject || action.verdict.accept)
537 				break;
538 		}
539 	}
540 
541 	*verdict = action.verdict;
542 }
543 
544 // Free's all contents of plugin_db_forward_url_verdict
plugin_db_forward_url_verdict_free(struct plugin_db_forward_url_verdict * verdict)545 void plugin_db_forward_url_verdict_free(struct plugin_db_forward_url_verdict *verdict)
546 {
547 	if (verdict->alt_iri)
548 		wget_iri_free(&verdict->alt_iri);
549 	if (verdict->alt_local_filename)
550 		wget_free(verdict->alt_local_filename);
551 }
552 
553 // Forwards downloaded file to interested plugins
plugin_db_forward_downloaded_file(const wget_iri * iri,int64_t size,const char * filename,const void * data,wget_vector * recurse_iris)554 int plugin_db_forward_downloaded_file(const wget_iri *iri, int64_t size, const char *filename, const void *data,
555 		wget_vector *recurse_iris)
556 {
557 	int ret = 1;
558 
559 	// Initialize the structure
560 	downloaded_file file = {
561 		.parent.vtable = &vtable,
562 		.iri = iri,
563 		.filename = filename,
564 		.size = size,
565 		.data = data,
566 		.data_buf = NULL,
567 		.recurse_iris = recurse_iris
568 	};
569 
570 	// Forward to each plugin
571 	for (int i = 0; i < wget_vector_size(plugin_list); i++) {
572 		plugin_t *plugin = (plugin_t *) wget_vector_get(plugin_list, i);
573 		plugin_priv_t *priv = (plugin_priv_t *) plugin;
574 
575 		if (priv->post_processor) {
576 			if (priv->post_processor((wget_plugin *) plugin, (wget_downloaded_file *) &file) == 0) {
577 				ret = 0;
578 				break;
579 			}
580 		}
581 	}
582 
583 	// Cleanup
584 	if (file.data_buf)
585 		wget_free(file.data_buf);
586 
587 	return ret;
588 }
589 
590 // Initializes the plugin framework
plugin_db_init(void)591 void plugin_db_init(void)
592 {
593 	if (! initialized) {
594 		search_paths = wget_vector_create(16, NULL);
595 		plugin_list = wget_vector_create(16, NULL);
596 		wget_vector_set_destructor(plugin_list, (wget_vector_destructor *) plugin_free);
597 		plugin_name_index = wget_stringmap_create(16);
598 		wget_stringmap_set_key_destructor(plugin_name_index, NULL);
599 		wget_stringmap_set_value_destructor(plugin_name_index, NULL);
600 		plugin_help_forwarded = 0;
601 
602 		initialized = 1;
603 	}
604 }
605 
606 // Sends 'finalize' signal to all plugins and unloads all plugins
plugin_db_finalize(int exitcode)607 void plugin_db_finalize(int exitcode)
608 {
609 	if (! initialized)
610 		return;
611 
612 	int n_plugins = wget_vector_size(plugin_list);
613 
614 	for (int i = 0; i < n_plugins; i++) {
615 		plugin_t *plugin = (plugin_t *) wget_vector_get(plugin_list, i);
616 		plugin_priv_t *priv = (plugin_priv_t *) plugin;
617 		if (priv->finalizer)
618 			priv->finalizer((wget_plugin *) plugin, exitcode);
619 	}
620 	wget_vector_free(&plugin_list);
621 	wget_stringmap_free(&plugin_name_index);
622 	wget_vector_free(&search_paths);
623 
624 	initialized = 0;
625 }
626