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