diff options
Diffstat (limited to '')
-rw-r--r-- | app/plug-in/gimpinterpreterdb.c | 874 |
1 files changed, 874 insertions, 0 deletions
diff --git a/app/plug-in/gimpinterpreterdb.c b/app/plug-in/gimpinterpreterdb.c new file mode 100644 index 0000000..14b1f79 --- /dev/null +++ b/app/plug-in/gimpinterpreterdb.c @@ -0,0 +1,874 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpinterpreterdb.c + * (C) 2005 Manish Singh <yosh@gimp.org> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +/* + * The binfmt_misc bits are derived from linux/fs/binfmt_misc.c + * Copyright (C) 1997 Richard Günther + */ + +/* + * The sh-bang code is derived from linux/fs/binfmt_script.c + * Copyright (C) 1996 Martin von Löwis + * original #!-checking implemented by tytso. + */ + +#include "config.h" + +#include <stdlib.h> +#include <string.h> + +#include <gio/gio.h> + +#include "libgimpbase/gimpbase.h" + +#include "plug-in-types.h" + +#include "gimpinterpreterdb.h" + +#include "gimp-intl.h" + + +#define BUFSIZE 4096 + + +typedef struct _GimpInterpreterMagic GimpInterpreterMagic; + +struct _GimpInterpreterMagic +{ + gulong offset; + gchar *magic; + gchar *mask; + guint size; + gchar *program; +}; + + +static void gimp_interpreter_db_finalize (GObject *object); + +static void gimp_interpreter_db_load_interp_file (GimpInterpreterDB *db, + GFile *file); + +static void gimp_interpreter_db_add_program (GimpInterpreterDB *db, + GFile *file, + gchar *buffer); +static void gimp_interpreter_db_add_binfmt_misc (GimpInterpreterDB *db, + GFile *file, + gchar *buffer); + +static gboolean gimp_interpreter_db_add_extension (GFile *file, + GimpInterpreterDB *db, + gchar **tokens); +static gboolean gimp_interpreter_db_add_magic (GimpInterpreterDB *db, + gchar **tokens); + +static void gimp_interpreter_db_clear_magics (GimpInterpreterDB *db); + +static void gimp_interpreter_db_resolve_programs (GimpInterpreterDB *db); + + +G_DEFINE_TYPE (GimpInterpreterDB, gimp_interpreter_db, G_TYPE_OBJECT) + +#define parent_class gimp_interpreter_db_parent_class + + +static void +gimp_interpreter_db_class_init (GimpInterpreterDBClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = gimp_interpreter_db_finalize; +} + +static void +gimp_interpreter_db_init (GimpInterpreterDB *db) +{ +} + +static void +gimp_interpreter_db_finalize (GObject *object) +{ + GimpInterpreterDB *db = GIMP_INTERPRETER_DB (object); + + gimp_interpreter_db_clear (db); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +GimpInterpreterDB * +gimp_interpreter_db_new (gboolean verbose) +{ + GimpInterpreterDB *db = g_object_new (GIMP_TYPE_INTERPRETER_DB, NULL); + + db->verbose = verbose; + + return db; +} + +void +gimp_interpreter_db_load (GimpInterpreterDB *db, + GList *path) +{ + GList *list; + + g_return_if_fail (GIMP_IS_INTERPRETER_DB (db)); + + gimp_interpreter_db_clear (db); + + db->programs = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + db->extensions = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + db->magic_names = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + db->extension_names = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + for (list = path; list; list = g_list_next (list)) + { + GFile *dir = list->data; + GFileEnumerator *enumerator; + + enumerator = + g_file_enumerate_children (dir, + G_FILE_ATTRIBUTE_STANDARD_NAME "," + G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," + G_FILE_ATTRIBUTE_STANDARD_TYPE, + G_FILE_QUERY_INFO_NONE, + NULL, NULL); + + if (enumerator) + { + GFileInfo *info; + + while ((info = g_file_enumerator_next_file (enumerator, + NULL, NULL))) + { + if (! g_file_info_get_is_hidden (info) && + g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR) + { + GFile *file = g_file_enumerator_get_child (enumerator, info); + + gimp_interpreter_db_load_interp_file (db, file); + + g_object_unref (file); + } + + g_object_unref (info); + } + + g_object_unref (enumerator); + } + } + + gimp_interpreter_db_resolve_programs (db); +} + +void +gimp_interpreter_db_clear (GimpInterpreterDB *db) +{ + g_return_if_fail (GIMP_IS_INTERPRETER_DB (db)); + + if (db->magic_names) + { + g_hash_table_destroy (db->magic_names); + db->magic_names = NULL; + } + + if (db->extension_names) + { + g_hash_table_destroy (db->extension_names); + db->extension_names = NULL; + } + + if (db->programs) + { + g_hash_table_destroy (db->programs); + db->programs = NULL; + } + + if (db->extensions) + { + g_hash_table_destroy (db->extensions); + db->extensions = NULL; + } + + gimp_interpreter_db_clear_magics (db); +} + +static void +gimp_interpreter_db_load_interp_file (GimpInterpreterDB *db, + GFile *file) +{ + GInputStream *input; + GDataInputStream *data_input; + gchar *buffer; + gsize buffer_len; + GError *error = NULL; + + if (db->verbose) + g_print ("Parsing '%s'\n", gimp_file_get_utf8_name (file)); + + input = G_INPUT_STREAM (g_file_read (file, NULL, &error)); + if (! input) + { + g_message (_("Could not open '%s' for reading: %s"), + gimp_file_get_utf8_name (file), + error->message); + g_clear_error (&error); + return; + } + + data_input = g_data_input_stream_new (input); + g_object_unref (input); + + while ((buffer = g_data_input_stream_read_line (data_input, &buffer_len, + NULL, &error))) + { + /* Skip comments */ + if (buffer[0] == '#') + { + g_free (buffer); + continue; + } + + if (g_ascii_isalnum (buffer[0]) || (buffer[0] == '/')) + { + gimp_interpreter_db_add_program (db, file, buffer); + } + else if (! g_ascii_isspace (buffer[0]) && (buffer[0] != '\0')) + { + gimp_interpreter_db_add_binfmt_misc (db, file, buffer); + } + + g_free (buffer); + } + + if (error) + { + g_message (_("Error reading '%s': %s"), + gimp_file_get_utf8_name (file), + error->message); + g_clear_error (&error); + } + + g_object_unref (data_input); +} + +static void +gimp_interpreter_db_add_program (GimpInterpreterDB *db, + GFile *file, + gchar *buffer) +{ + gchar *name; + gchar *program; + gchar *p; + + p = strchr (buffer, '='); + if (! p) + return; + + *p = '\0'; + + name = buffer; + program = p + 1; + + if (! g_file_test (program, G_FILE_TEST_IS_EXECUTABLE)) + { + gchar *prog; + + prog = g_find_program_in_path (program); + if (! prog || ! g_file_test (prog, G_FILE_TEST_IS_EXECUTABLE)) + { + g_message (_("Bad interpreter referenced in interpreter file %s: %s"), + gimp_file_get_utf8_name (file), + gimp_filename_to_utf8 (program)); + if (prog) + g_free (prog); + return; + } + program = prog; + } + else + program = g_strdup (program); + + if (! g_hash_table_lookup (db->programs, name)) + g_hash_table_insert (db->programs, g_strdup (name), program); +} + +static void +gimp_interpreter_db_add_binfmt_misc (GimpInterpreterDB *db, + GFile *file, + gchar *buffer) +{ + gchar **tokens = NULL; + gchar *name, *type, *program; + gsize count; + gchar del[2]; + + count = strlen (buffer); + + if ((count < 10) || (count > 255)) + goto bail; + + buffer = g_strndup (buffer, count + 9); + + del[0] = *buffer; + del[1] = '\0'; + + memset (buffer + count, del[0], 8); + + tokens = g_strsplit (buffer + 1, del, -1); + + g_free (buffer); + + name = tokens[0]; + type = tokens[1]; + program = tokens[5]; + + if ((name[0] == '\0') || (program[0] == '\0') || + (type[0] == '\0') || (type[1] != '\0')) + goto bail; + + switch (type[0]) + { + case 'E': + if (! gimp_interpreter_db_add_extension (file, db, tokens)) + goto bail; + break; + + case 'M': + if (! gimp_interpreter_db_add_magic (db, tokens)) + goto bail; + break; + + default: + goto bail; + } + + goto out; + +bail: + g_message (_("Bad binary format string in interpreter file %s"), + gimp_file_get_utf8_name (file)); + +out: + g_strfreev (tokens); +} + +static gboolean +gimp_interpreter_db_add_extension (GFile *file, + GimpInterpreterDB *db, + gchar **tokens) +{ + const gchar *name = tokens[0]; + const gchar *extension = tokens[3]; + const gchar *program = tokens[5]; + + if (! g_hash_table_lookup (db->extension_names, name)) + { + gchar *prog; + + if (extension[0] == '\0' || extension[0] == '/') + return FALSE; + + if (! g_file_test (program, G_FILE_TEST_IS_EXECUTABLE)) + { + prog = g_find_program_in_path (program); + if (! prog || ! g_file_test (prog, G_FILE_TEST_IS_EXECUTABLE)) + { + g_message (_("Bad interpreter referenced in interpreter file %s: %s"), + gimp_file_get_utf8_name (file), + gimp_filename_to_utf8 (program)); + if (prog) + g_free (prog); + return FALSE; + } + } + else + prog = g_strdup (program); + + g_hash_table_insert (db->extensions, g_strdup (extension), prog); + g_hash_table_insert (db->extension_names, g_strdup (name), prog); + } + + return TRUE; +} + +static gboolean +scanarg (const gchar *s) +{ + gchar c; + + while ((c = *s++) != '\0') + { + if (c == '\\' && *s == 'x') + { + s++; + + if (! g_ascii_isxdigit (*s++)) + return FALSE; + + if (! g_ascii_isxdigit (*s++)) + return FALSE; + } + } + + return TRUE; +} + +static guint +unquote (gchar *from) +{ + gchar *s = from; + gchar *p = from; + gchar c; + + while ((c = *s++) != '\0') + { + if (c == '\\' && *s == 'x') + { + s++; + *p = g_ascii_xdigit_value (*s++) << 4; + *p++ |= g_ascii_xdigit_value (*s++); + continue; + } + + *p++ = c; + } + + return p - from; +} + +static gboolean +gimp_interpreter_db_add_magic (GimpInterpreterDB *db, + gchar **tokens) +{ + GimpInterpreterMagic *interp_magic; + gchar *name, *num, *magic, *mask, *program; + gulong offset; + guint size; + + name = tokens[0]; + num = tokens[2]; + magic = tokens[3]; + mask = tokens[4]; + program = tokens[5]; + + if (! g_hash_table_lookup (db->magic_names, name)) + { + if (num[0] != '\0') + { + offset = strtoul (num, &num, 10); + + if (num[0] != '\0') + return FALSE; + + if (offset > (BUFSIZE / 4)) + return FALSE; + } + else + { + offset = 0; + } + + if (! scanarg (magic)) + return FALSE; + + if (! scanarg (mask)) + return FALSE; + + size = unquote (magic); + + if ((size + offset) > (BUFSIZE / 2)) + return FALSE; + + if (mask[0] == '\0') + mask = NULL; + else if (unquote (mask) != size) + return FALSE; + + interp_magic = g_slice_new (GimpInterpreterMagic); + + interp_magic->offset = offset; + interp_magic->magic = g_memdup (magic, size); + interp_magic->mask = g_memdup (mask, size); + interp_magic->size = size; + interp_magic->program = g_strdup (program); + + db->magics = g_slist_append (db->magics, interp_magic); + + g_hash_table_insert (db->magic_names, g_strdup (name), interp_magic); + } + + return TRUE; +} + +static void +gimp_interpreter_db_clear_magics (GimpInterpreterDB *db) +{ + GimpInterpreterMagic *magic; + GSList *list, *last; + + list = db->magics; + db->magics = NULL; + + while (list) + { + magic = list->data; + + g_free (magic->magic); + g_free (magic->mask); + g_free (magic->program); + + g_slice_free (GimpInterpreterMagic, magic); + + last = list; + list = list->next; + + g_slist_free_1 (last); + } +} + +#ifdef INTERP_DEBUG +static void +print_kv (gpointer key, + gpointer value, + gpointer user_data) +{ + g_print ("%s: %s\n", (gchar *) key, (gchar *) value); +} + +static gchar * +quote (gchar *s, + guint size) +{ + GString *d; + guint i; + + if (s == NULL) + return "(null)"; + + d = g_string_sized_new (size * 4); + + for (i = 0; i < size; i++) + g_string_append_printf (d, "\\x%02x", ((guint) s[i]) & 0xff); + + return g_string_free (d, FALSE); +} +#endif + +static gboolean +resolve_program (gpointer key, + gpointer value, + gpointer user_data) +{ + GimpInterpreterDB *db = user_data; + gchar *program; + + program = g_hash_table_lookup (db->programs, value); + + if (program != NULL) + { + g_free (value); + value = g_strdup (program); + } + + g_hash_table_insert (db->extensions, key, value); + + return TRUE; +} + +static void +gimp_interpreter_db_resolve_programs (GimpInterpreterDB *db) +{ + GSList *list; + GHashTable *extensions; + + for (list = db->magics; list; list = list->next) + { + GimpInterpreterMagic *magic = list->data; + const gchar *program; + + program = g_hash_table_lookup (db->programs, magic->program); + + if (program != NULL) + { + g_free (magic->program); + magic->program = g_strdup (program); + } + } + + extensions = db->extensions; + db->extensions = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, g_free); + + g_hash_table_foreach_steal (extensions, resolve_program, db); + + g_hash_table_destroy (extensions); + +#ifdef INTERP_DEBUG + g_print ("Programs:\n"); + g_hash_table_foreach (db->programs, print_kv, NULL); + + g_print ("\nExtensions:\n"); + g_hash_table_foreach (db->extensions, print_kv, NULL); + + g_print ("\nMagics:\n"); + + list = db->magics; + + while (list) + { + GimpInterpreterMagic *magic; + + magic = list->data; + g_print ("program: %s, offset: %lu, magic: %s, mask: %s\n", + magic->program, magic->offset, + quote (magic->magic, magic->size), + quote (magic->mask, magic->size)); + + list = list->next; + } + + g_print ("\n"); +#endif +} + +static gchar * +resolve_extension (GimpInterpreterDB *db, + const gchar *program_path) +{ + gchar *filename; + gchar *p; + const gchar *program; + + filename = g_path_get_basename (program_path); + + p = strrchr (filename, '.'); + if (! p) + { + g_free (filename); + return NULL; + } + + program = g_hash_table_lookup (db->extensions, p + 1); + + g_free (filename); + + return g_strdup (program); +} + +static gchar * +resolve_sh_bang (GimpInterpreterDB *db, + const gchar *program_path, + gchar *buffer, + gssize len, + gchar **interp_arg) +{ + gchar *cp; + gchar *name; + gchar *program; + + cp = strchr (buffer, '\n'); + if (! cp) + cp = buffer + len - 1; + + *cp = '\0'; + + while (cp > buffer) + { + cp--; + if ((*cp == ' ') || (*cp == '\t') || (*cp == '\r')) + *cp = '\0'; + else + break; + } + + for (cp = buffer + 2; (*cp == ' ') || (*cp == '\t'); cp++); + + if (*cp == '\0') + return NULL; + + name = cp; + + for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) + /* nothing */ ; + + while ((*cp == ' ') || (*cp == '\t')) + *cp++ = '\0'; + + if (*cp) + { + if (strcmp ("/usr/bin/env", name) == 0) + { + program = g_hash_table_lookup (db->programs, cp); + + if (program) + { + /* Shift program name and arguments to the right, if and + * only if we recorded a specific interpreter for such + * script. Otherwise let `env` tool do its job. + */ + name = cp; + + for ( ; *cp && (*cp != ' ') && (*cp != '\t'); cp++) + ; + + while ((*cp == ' ') || (*cp == '\t')) + *cp++ = '\0'; + } + } + + if (*cp) + *interp_arg = g_strdup (cp); + } + + program = g_hash_table_lookup (db->programs, name); + if (! program) + program = name; + + return g_strdup (program); +} + +static gchar * +resolve_magic (GimpInterpreterDB *db, + const gchar *program_path, + gchar *buffer) +{ + GSList *list; + GimpInterpreterMagic *magic; + gchar *s; + guint i; + + list = db->magics; + + while (list) + { + magic = list->data; + + s = buffer + magic->offset; + + if (magic->mask) + { + for (i = 0; i < magic->size; i++) + if ((*s++ ^ magic->magic[i]) & magic->mask[i]) + break; + } + else + { + for (i = 0; i < magic->size; i++) + if ((*s++ ^ magic->magic[i])) + break; + } + + if (i == magic->size) + return g_strdup (magic->program); + + list = list->next; + } + + return NULL; +} + +gchar * +gimp_interpreter_db_resolve (GimpInterpreterDB *db, + const gchar *program_path, + gchar **interp_arg) +{ + GFile *file; + GInputStream *input; + gchar *program = NULL; + + g_return_val_if_fail (GIMP_IS_INTERPRETER_DB (db), NULL); + g_return_val_if_fail (program_path != NULL, NULL); + g_return_val_if_fail (interp_arg != NULL, NULL); + + *interp_arg = NULL; + + file = g_file_new_for_path (program_path); + input = G_INPUT_STREAM (g_file_read (file, NULL, NULL)); + g_object_unref (file); + + if (input) + { + gsize bytes_read; + gchar buffer[BUFSIZE]; + + memset (buffer, 0, sizeof (buffer)); + g_input_stream_read_all (input, buffer, + sizeof (buffer) - 1, /* leave one nul at the end */ + &bytes_read, NULL, NULL); + g_object_unref (input); + + if (bytes_read) + { + if (bytes_read > 3 && buffer[0] == '#' && buffer[1] == '!') + program = resolve_sh_bang (db, program_path, buffer, bytes_read, interp_arg); + + if (! program) + program = resolve_magic (db, program_path, buffer); + } + } + + if (! program) + program = resolve_extension (db, program_path); + + return program; +} + +static void +collect_extensions (const gchar *ext, + const gchar *program G_GNUC_UNUSED, + GString *str) +{ + if (str->len) + g_string_append_c (str, G_SEARCHPATH_SEPARATOR); + + g_string_append_c (str, '.'); + g_string_append (str, ext); +} + +/** + * gimp_interpreter_db_get_extensions: + * @db: + * + * Return value: a newly allocated string with all registered file + * extensions separated by %G_SEARCHPATH_SEPARATOR; + * or %NULL if no extensions are registered + **/ +gchar * +gimp_interpreter_db_get_extensions (GimpInterpreterDB *db) +{ + GString *str; + + g_return_val_if_fail (GIMP_IS_INTERPRETER_DB (db), NULL); + + if (g_hash_table_size (db->extensions) == 0) + return NULL; + + str = g_string_new (NULL); + + g_hash_table_foreach (db->extensions, (GHFunc) collect_extensions, str); + + return g_string_free (str, FALSE); +} |