1 /* GIMP - The GNU Image Manipulation Program
2 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
3 *
4 * gimpinterpreterdb.c
5 * (C) 2005 Manish Singh <yosh@gimp.org>
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21 /*
22 * The binfmt_misc bits are derived from linux/fs/binfmt_misc.c
23 * Copyright (C) 1997 Richard Günther
24 */
25
26 /*
27 * The sh-bang code is derived from linux/fs/binfmt_script.c
28 * Copyright (C) 1996 Martin von Löwis
29 * original #!-checking implemented by tytso.
30 */
31
32 #include "config.h"
33
34 #include <stdlib.h>
35 #include <string.h>
36
37 #include <gio/gio.h>
38
39 #include "libgimpbase/gimpbase.h"
40
41 #include "plug-in-types.h"
42
43 #include "gimpinterpreterdb.h"
44
45 #include "gimp-intl.h"
46
47
48 #define BUFSIZE 4096
49
50
51 typedef struct _GimpInterpreterMagic GimpInterpreterMagic;
52
53 struct _GimpInterpreterMagic
54 {
55 gulong offset;
56 gchar *magic;
57 gchar *mask;
58 guint size;
59 gchar *program;
60 };
61
62
63 static void gimp_interpreter_db_finalize (GObject *object);
64
65 static void gimp_interpreter_db_load_interp_file (GimpInterpreterDB *db,
66 GFile *file);
67
68 static void gimp_interpreter_db_add_program (GimpInterpreterDB *db,
69 GFile *file,
70 gchar *buffer);
71 static void gimp_interpreter_db_add_binfmt_misc (GimpInterpreterDB *db,
72 GFile *file,
73 gchar *buffer);
74
75 static gboolean gimp_interpreter_db_add_extension (GFile *file,
76 GimpInterpreterDB *db,
77 gchar **tokens);
78 static gboolean gimp_interpreter_db_add_magic (GimpInterpreterDB *db,
79 gchar **tokens);
80
81 static void gimp_interpreter_db_clear_magics (GimpInterpreterDB *db);
82
83 static void gimp_interpreter_db_resolve_programs (GimpInterpreterDB *db);
84
85
G_DEFINE_TYPE(GimpInterpreterDB,gimp_interpreter_db,G_TYPE_OBJECT)86 G_DEFINE_TYPE (GimpInterpreterDB, gimp_interpreter_db, G_TYPE_OBJECT)
87
88 #define parent_class gimp_interpreter_db_parent_class
89
90
91 static void
92 gimp_interpreter_db_class_init (GimpInterpreterDBClass *class)
93 {
94 GObjectClass *object_class = G_OBJECT_CLASS (class);
95
96 object_class->finalize = gimp_interpreter_db_finalize;
97 }
98
99 static void
gimp_interpreter_db_init(GimpInterpreterDB * db)100 gimp_interpreter_db_init (GimpInterpreterDB *db)
101 {
102 }
103
104 static void
gimp_interpreter_db_finalize(GObject * object)105 gimp_interpreter_db_finalize (GObject *object)
106 {
107 GimpInterpreterDB *db = GIMP_INTERPRETER_DB (object);
108
109 gimp_interpreter_db_clear (db);
110
111 G_OBJECT_CLASS (parent_class)->finalize (object);
112 }
113
114 GimpInterpreterDB *
gimp_interpreter_db_new(gboolean verbose)115 gimp_interpreter_db_new (gboolean verbose)
116 {
117 GimpInterpreterDB *db = g_object_new (GIMP_TYPE_INTERPRETER_DB, NULL);
118
119 db->verbose = verbose;
120
121 return db;
122 }
123
124 void
gimp_interpreter_db_load(GimpInterpreterDB * db,GList * path)125 gimp_interpreter_db_load (GimpInterpreterDB *db,
126 GList *path)
127 {
128 GList *list;
129
130 g_return_if_fail (GIMP_IS_INTERPRETER_DB (db));
131
132 gimp_interpreter_db_clear (db);
133
134 db->programs = g_hash_table_new_full (g_str_hash, g_str_equal,
135 g_free, g_free);
136
137 db->extensions = g_hash_table_new_full (g_str_hash, g_str_equal,
138 g_free, g_free);
139
140 db->magic_names = g_hash_table_new_full (g_str_hash, g_str_equal,
141 g_free, NULL);
142
143 db->extension_names = g_hash_table_new_full (g_str_hash, g_str_equal,
144 g_free, NULL);
145
146 for (list = path; list; list = g_list_next (list))
147 {
148 GFile *dir = list->data;
149 GFileEnumerator *enumerator;
150
151 enumerator =
152 g_file_enumerate_children (dir,
153 G_FILE_ATTRIBUTE_STANDARD_NAME ","
154 G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
155 G_FILE_ATTRIBUTE_STANDARD_TYPE,
156 G_FILE_QUERY_INFO_NONE,
157 NULL, NULL);
158
159 if (enumerator)
160 {
161 GFileInfo *info;
162
163 while ((info = g_file_enumerator_next_file (enumerator,
164 NULL, NULL)))
165 {
166 if (! g_file_info_get_is_hidden (info) &&
167 g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR)
168 {
169 GFile *file = g_file_enumerator_get_child (enumerator, info);
170
171 gimp_interpreter_db_load_interp_file (db, file);
172
173 g_object_unref (file);
174 }
175
176 g_object_unref (info);
177 }
178
179 g_object_unref (enumerator);
180 }
181 }
182
183 gimp_interpreter_db_resolve_programs (db);
184 }
185
186 void
gimp_interpreter_db_clear(GimpInterpreterDB * db)187 gimp_interpreter_db_clear (GimpInterpreterDB *db)
188 {
189 g_return_if_fail (GIMP_IS_INTERPRETER_DB (db));
190
191 if (db->magic_names)
192 {
193 g_hash_table_destroy (db->magic_names);
194 db->magic_names = NULL;
195 }
196
197 if (db->extension_names)
198 {
199 g_hash_table_destroy (db->extension_names);
200 db->extension_names = NULL;
201 }
202
203 if (db->programs)
204 {
205 g_hash_table_destroy (db->programs);
206 db->programs = NULL;
207 }
208
209 if (db->extensions)
210 {
211 g_hash_table_destroy (db->extensions);
212 db->extensions = NULL;
213 }
214
215 gimp_interpreter_db_clear_magics (db);
216 }
217
218 static void
gimp_interpreter_db_load_interp_file(GimpInterpreterDB * db,GFile * file)219 gimp_interpreter_db_load_interp_file (GimpInterpreterDB *db,
220 GFile *file)
221 {
222 GInputStream *input;
223 GDataInputStream *data_input;
224 gchar *buffer;
225 gsize buffer_len;
226 GError *error = NULL;
227
228 if (db->verbose)
229 g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file));
230
231 input = G_INPUT_STREAM (g_file_read (file, NULL, &error));
232 if (! input)
233 {
234 g_message (_("Could not open '%s' for reading: %s"),
235 gimp_file_get_utf8_name (file),
236 error->message);
237 g_clear_error (&error);
238 return;
239 }
240
241 data_input = g_data_input_stream_new (input);
242 g_object_unref (input);
243
244 while ((buffer = g_data_input_stream_read_line (data_input, &buffer_len,
245 NULL, &error)))
246 {
247 /* Skip comments */
248 if (buffer[0] == '#')
249 {
250 g_free (buffer);
251 continue;
252 }
253
254 if (g_ascii_isalnum (buffer[0]) || (buffer[0] == '/'))
255 {
256 gimp_interpreter_db_add_program (db, file, buffer);
257 }
258 else if (! g_ascii_isspace (buffer[0]) && (buffer[0] != '\0'))
259 {
260 gimp_interpreter_db_add_binfmt_misc (db, file, buffer);
261 }
262
263 g_free (buffer);
264 }
265
266 if (error)
267 {
268 g_message (_("Error reading '%s': %s"),
269 gimp_file_get_utf8_name (file),
270 error->message);
271 g_clear_error (&error);
272 }
273
274 g_object_unref (data_input);
275 }
276
277 static void
gimp_interpreter_db_add_program(GimpInterpreterDB * db,GFile * file,gchar * buffer)278 gimp_interpreter_db_add_program (GimpInterpreterDB *db,
279 GFile *file,
280 gchar *buffer)
281 {
282 gchar *name;
283 gchar *program;
284 gchar *p;
285
286 p = strchr (buffer, '=');
287 if (! p)
288 return;
289
290 *p = '\0';
291
292 name = buffer;
293 program = p + 1;
294 g_strchomp (program);
295
296 if (! g_file_test (program, G_FILE_TEST_IS_EXECUTABLE))
297 {
298 gchar *prog;
299
300 prog = g_find_program_in_path (program);
301 if (! prog || ! g_file_test (prog, G_FILE_TEST_IS_EXECUTABLE))
302 {
303 g_message (_("Bad interpreter referenced in interpreter file %s: %s"),
304 gimp_file_get_utf8_name (file),
305 gimp_filename_to_utf8 (program));
306 if (prog)
307 g_free (prog);
308 return;
309 }
310 program = prog;
311 }
312 else
313 program = g_strdup (program);
314
315 if (! g_hash_table_lookup (db->programs, name))
316 g_hash_table_insert (db->programs, g_strdup (name), program);
317 }
318
319 static void
gimp_interpreter_db_add_binfmt_misc(GimpInterpreterDB * db,GFile * file,gchar * buffer)320 gimp_interpreter_db_add_binfmt_misc (GimpInterpreterDB *db,
321 GFile *file,
322 gchar *buffer)
323 {
324 gchar **tokens = NULL;
325 gchar *name, *type, *program;
326 gsize count;
327 gchar del[2];
328
329 count = strlen (buffer);
330
331 if ((count < 10) || (count > 255))
332 goto bail;
333
334 buffer = g_strndup (buffer, count + 9);
335
336 del[0] = *buffer;
337 del[1] = '\0';
338
339 memset (buffer + count, del[0], 8);
340
341 tokens = g_strsplit (buffer + 1, del, -1);
342
343 g_free (buffer);
344
345 name = tokens[0];
346 type = tokens[1];
347 program = tokens[5];
348
349 if ((name[0] == '\0') || (program[0] == '\0') ||
350 (type[0] == '\0') || (type[1] != '\0'))
351 goto bail;
352
353 switch (type[0])
354 {
355 case 'E':
356 if (! gimp_interpreter_db_add_extension (file, db, tokens))
357 goto bail;
358 break;
359
360 case 'M':
361 if (! gimp_interpreter_db_add_magic (db, tokens))
362 goto bail;
363 break;
364
365 default:
366 goto bail;
367 }
368
369 goto out;
370
371 bail:
372 g_message (_("Bad binary format string in interpreter file %s"),
373 gimp_file_get_utf8_name (file));
374
375 out:
376 g_strfreev (tokens);
377 }
378
379 static gboolean
gimp_interpreter_db_add_extension(GFile * file,GimpInterpreterDB * db,gchar ** tokens)380 gimp_interpreter_db_add_extension (GFile *file,
381 GimpInterpreterDB *db,
382 gchar **tokens)
383 {
384 const gchar *name = tokens[0];
385 const gchar *extension = tokens[3];
386 const gchar *program = tokens[5];
387
388 if (! g_hash_table_lookup (db->extension_names, name))
389 {
390 gchar *prog;
391
392 if (extension[0] == '\0' || extension[0] == '/')
393 return FALSE;
394
395 if (! g_file_test (program, G_FILE_TEST_IS_EXECUTABLE))
396 {
397 prog = g_find_program_in_path (program);
398 if (! prog || ! g_file_test (prog, G_FILE_TEST_IS_EXECUTABLE))
399 {
400 g_message (_("Bad interpreter referenced in interpreter file %s: %s"),
401 gimp_file_get_utf8_name (file),
402 gimp_filename_to_utf8 (program));
403 if (prog)
404 g_free (prog);
405 return FALSE;
406 }
407 }
408 else
409 prog = g_strdup (program);
410
411 g_hash_table_insert (db->extensions, g_strdup (extension), prog);
412 g_hash_table_insert (db->extension_names, g_strdup (name), prog);
413 }
414
415 return TRUE;
416 }
417
418 static gboolean
scanarg(const gchar * s)419 scanarg (const gchar *s)
420 {
421 gchar c;
422
423 while ((c = *s++) != '\0')
424 {
425 if (c == '\\' && *s == 'x')
426 {
427 s++;
428
429 if (! g_ascii_isxdigit (*s++))
430 return FALSE;
431
432 if (! g_ascii_isxdigit (*s++))
433 return FALSE;
434 }
435 }
436
437 return TRUE;
438 }
439
440 static guint
unquote(gchar * from)441 unquote (gchar *from)
442 {
443 gchar *s = from;
444 gchar *p = from;
445 gchar c;
446
447 while ((c = *s++) != '\0')
448 {
449 if (c == '\\' && *s == 'x')
450 {
451 s++;
452 *p = g_ascii_xdigit_value (*s++) << 4;
453 *p++ |= g_ascii_xdigit_value (*s++);
454 continue;
455 }
456
457 *p++ = c;
458 }
459
460 return p - from;
461 }
462
463 static gboolean
gimp_interpreter_db_add_magic(GimpInterpreterDB * db,gchar ** tokens)464 gimp_interpreter_db_add_magic (GimpInterpreterDB *db,
465 gchar **tokens)
466 {
467 GimpInterpreterMagic *interp_magic;
468 gchar *name, *num, *magic, *mask, *program;
469 gulong offset;
470 guint size;
471
472 name = tokens[0];
473 num = tokens[2];
474 magic = tokens[3];
475 mask = tokens[4];
476 program = tokens[5];
477
478 if (! g_hash_table_lookup (db->magic_names, name))
479 {
480 if (num[0] != '\0')
481 {
482 offset = strtoul (num, &num, 10);
483
484 if (num[0] != '\0')
485 return FALSE;
486
487 if (offset > (BUFSIZE / 4))
488 return FALSE;
489 }
490 else
491 {
492 offset = 0;
493 }
494
495 if (! scanarg (magic))
496 return FALSE;
497
498 if (! scanarg (mask))
499 return FALSE;
500
501 size = unquote (magic);
502
503 if ((size + offset) > (BUFSIZE / 2))
504 return FALSE;
505
506 if (mask[0] == '\0')
507 mask = NULL;
508 else if (unquote (mask) != size)
509 return FALSE;
510
511 interp_magic = g_slice_new (GimpInterpreterMagic);
512
513 interp_magic->offset = offset;
514 interp_magic->magic = g_memdup (magic, size);
515 interp_magic->mask = g_memdup (mask, size);
516 interp_magic->size = size;
517 interp_magic->program = g_strdup (program);
518
519 db->magics = g_slist_append (db->magics, interp_magic);
520
521 g_hash_table_insert (db->magic_names, g_strdup (name), interp_magic);
522 }
523
524 return TRUE;
525 }
526
527 static void
gimp_interpreter_db_clear_magics(GimpInterpreterDB * db)528 gimp_interpreter_db_clear_magics (GimpInterpreterDB *db)
529 {
530 GimpInterpreterMagic *magic;
531 GSList *list, *last;
532
533 list = db->magics;
534 db->magics = NULL;
535
536 while (list)
537 {
538 magic = list->data;
539
540 g_free (magic->magic);
541 g_free (magic->mask);
542 g_free (magic->program);
543
544 g_slice_free (GimpInterpreterMagic, magic);
545
546 last = list;
547 list = list->next;
548
549 g_slist_free_1 (last);
550 }
551 }
552
553 #ifdef INTERP_DEBUG
554 static void
print_kv(gpointer key,gpointer value,gpointer user_data)555 print_kv (gpointer key,
556 gpointer value,
557 gpointer user_data)
558 {
559 g_print ("%s: %s\n", (gchar *) key, (gchar *) value);
560 }
561
562 static gchar *
quote(gchar * s,guint size)563 quote (gchar *s,
564 guint size)
565 {
566 GString *d;
567 guint i;
568
569 if (s == NULL)
570 return "(null)";
571
572 d = g_string_sized_new (size * 4);
573
574 for (i = 0; i < size; i++)
575 g_string_append_printf (d, "\\x%02x", ((guint) s[i]) & 0xff);
576
577 return g_string_free (d, FALSE);
578 }
579 #endif
580
581 static gboolean
resolve_program(gpointer key,gpointer value,gpointer user_data)582 resolve_program (gpointer key,
583 gpointer value,
584 gpointer user_data)
585 {
586 GimpInterpreterDB *db = user_data;
587 gchar *program;
588
589 program = g_hash_table_lookup (db->programs, value);
590
591 if (program != NULL)
592 {
593 g_free (value);
594 value = g_strdup (program);
595 }
596
597 g_hash_table_insert (db->extensions, key, value);
598
599 return TRUE;
600 }
601
602 static void
gimp_interpreter_db_resolve_programs(GimpInterpreterDB * db)603 gimp_interpreter_db_resolve_programs (GimpInterpreterDB *db)
604 {
605 GSList *list;
606 GHashTable *extensions;
607
608 for (list = db->magics; list; list = list->next)
609 {
610 GimpInterpreterMagic *magic = list->data;
611 const gchar *program;
612
613 program = g_hash_table_lookup (db->programs, magic->program);
614
615 if (program != NULL)
616 {
617 g_free (magic->program);
618 magic->program = g_strdup (program);
619 }
620 }
621
622 extensions = db->extensions;
623 db->extensions = g_hash_table_new_full (g_str_hash, g_str_equal,
624 g_free, g_free);
625
626 g_hash_table_foreach_steal (extensions, resolve_program, db);
627
628 g_hash_table_destroy (extensions);
629
630 #ifdef INTERP_DEBUG
631 g_print ("Programs:\n");
632 g_hash_table_foreach (db->programs, print_kv, NULL);
633
634 g_print ("\nExtensions:\n");
635 g_hash_table_foreach (db->extensions, print_kv, NULL);
636
637 g_print ("\nMagics:\n");
638
639 list = db->magics;
640
641 while (list)
642 {
643 GimpInterpreterMagic *magic;
644
645 magic = list->data;
646 g_print ("program: %s, offset: %lu, magic: %s, mask: %s\n",
647 magic->program, magic->offset,
648 quote (magic->magic, magic->size),
649 quote (magic->mask, magic->size));
650
651 list = list->next;
652 }
653
654 g_print ("\n");
655 #endif
656 }
657
658 static gchar *
resolve_extension(GimpInterpreterDB * db,const gchar * program_path)659 resolve_extension (GimpInterpreterDB *db,
660 const gchar *program_path)
661 {
662 gchar *filename;
663 gchar *p;
664 const gchar *program;
665
666 filename = g_path_get_basename (program_path);
667
668 p = strrchr (filename, '.');
669 if (! p)
670 {
671 g_free (filename);
672 return NULL;
673 }
674
675 program = g_hash_table_lookup (db->extensions, p + 1);
676
677 g_free (filename);
678
679 return g_strdup (program);
680 }
681
682 static gchar *
resolve_sh_bang(GimpInterpreterDB * db,const gchar * program_path,gchar * buffer,gssize len,gchar ** interp_arg)683 resolve_sh_bang (GimpInterpreterDB *db,
684 const gchar *program_path,
685 gchar *buffer,
686 gssize len,
687 gchar **interp_arg)
688 {
689 gchar *cp;
690 gchar *name;
691 gchar *program;
692
693 cp = strchr (buffer, '\n');
694 if (! cp)
695 cp = buffer + len - 1;
696
697 *cp = '\0';
698
699 while (cp > buffer)
700 {
701 cp--;
702 if ((*cp == ' ') || (*cp == '\t') || (*cp == '\r'))
703 *cp = '\0';
704 else
705 break;
706 }
707
708 for (cp = buffer + 2; (*cp == ' ') || (*cp == '\t'); cp++);
709
710 if (*cp == '\0')
711 return NULL;
712
713 name = cp;
714
715 for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
716 /* nothing */ ;
717
718 while ((*cp == ' ') || (*cp == '\t'))
719 *cp++ = '\0';
720
721 if (*cp)
722 {
723 if (strcmp ("/usr/bin/env", name) == 0)
724 {
725 program = g_hash_table_lookup (db->programs, cp);
726
727 if (program)
728 {
729 /* Shift program name and arguments to the right, if and
730 * only if we recorded a specific interpreter for such
731 * script. Otherwise let `env` tool do its job.
732 */
733 name = cp;
734
735 for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++)
736 ;
737
738 while ((*cp == ' ') || (*cp == '\t'))
739 *cp++ = '\0';
740 }
741 }
742
743 if (*cp)
744 *interp_arg = g_strdup (cp);
745 }
746
747 program = g_hash_table_lookup (db->programs, name);
748 if (! program)
749 program = name;
750
751 return g_strdup (program);
752 }
753
754 static gchar *
resolve_magic(GimpInterpreterDB * db,const gchar * program_path,gchar * buffer)755 resolve_magic (GimpInterpreterDB *db,
756 const gchar *program_path,
757 gchar *buffer)
758 {
759 GSList *list;
760 GimpInterpreterMagic *magic;
761 gchar *s;
762 guint i;
763
764 list = db->magics;
765
766 while (list)
767 {
768 magic = list->data;
769
770 s = buffer + magic->offset;
771
772 if (magic->mask)
773 {
774 for (i = 0; i < magic->size; i++)
775 if ((*s++ ^ magic->magic[i]) & magic->mask[i])
776 break;
777 }
778 else
779 {
780 for (i = 0; i < magic->size; i++)
781 if ((*s++ ^ magic->magic[i]))
782 break;
783 }
784
785 if (i == magic->size)
786 return g_strdup (magic->program);
787
788 list = list->next;
789 }
790
791 return NULL;
792 }
793
794 gchar *
gimp_interpreter_db_resolve(GimpInterpreterDB * db,const gchar * program_path,gchar ** interp_arg)795 gimp_interpreter_db_resolve (GimpInterpreterDB *db,
796 const gchar *program_path,
797 gchar **interp_arg)
798 {
799 GFile *file;
800 GInputStream *input;
801 gchar *program = NULL;
802
803 g_return_val_if_fail (GIMP_IS_INTERPRETER_DB (db), NULL);
804 g_return_val_if_fail (program_path != NULL, NULL);
805 g_return_val_if_fail (interp_arg != NULL, NULL);
806
807 *interp_arg = NULL;
808
809 file = g_file_new_for_path (program_path);
810 input = G_INPUT_STREAM (g_file_read (file, NULL, NULL));
811 g_object_unref (file);
812
813 if (input)
814 {
815 gsize bytes_read;
816 gchar buffer[BUFSIZE];
817
818 memset (buffer, 0, sizeof (buffer));
819 g_input_stream_read_all (input, buffer,
820 sizeof (buffer) - 1, /* leave one nul at the end */
821 &bytes_read, NULL, NULL);
822 g_object_unref (input);
823
824 if (bytes_read)
825 {
826 if (bytes_read > 3 && buffer[0] == '#' && buffer[1] == '!')
827 program = resolve_sh_bang (db, program_path, buffer, bytes_read, interp_arg);
828
829 if (! program)
830 program = resolve_magic (db, program_path, buffer);
831 }
832 }
833
834 if (! program)
835 program = resolve_extension (db, program_path);
836
837 return program;
838 }
839
840 static void
collect_extensions(const gchar * ext,const gchar * program G_GNUC_UNUSED,GString * str)841 collect_extensions (const gchar *ext,
842 const gchar *program G_GNUC_UNUSED,
843 GString *str)
844 {
845 if (str->len)
846 g_string_append_c (str, G_SEARCHPATH_SEPARATOR);
847
848 g_string_append_c (str, '.');
849 g_string_append (str, ext);
850 }
851
852 /**
853 * gimp_interpreter_db_get_extensions:
854 * @db:
855 *
856 * Return value: a newly allocated string with all registered file
857 * extensions separated by %G_SEARCHPATH_SEPARATOR;
858 * or %NULL if no extensions are registered
859 **/
860 gchar *
gimp_interpreter_db_get_extensions(GimpInterpreterDB * db)861 gimp_interpreter_db_get_extensions (GimpInterpreterDB *db)
862 {
863 GString *str;
864
865 g_return_val_if_fail (GIMP_IS_INTERPRETER_DB (db), NULL);
866
867 if (g_hash_table_size (db->extensions) == 0)
868 return NULL;
869
870 str = g_string_new (NULL);
871
872 g_hash_table_foreach (db->extensions, (GHFunc) collect_extensions, str);
873
874 return g_string_free (str, FALSE);
875 }
876