/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * pikainterpreterdb.c * (C) 2005 Manish Singh * * 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 . */ /* * 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 #include #include #include "libpikabase/pikabase.h" #include "plug-in-types.h" #include "pikainterpreterdb.h" #include "pika-intl.h" #define BUFSIZE 4096 typedef struct _PikaInterpreterMagic PikaInterpreterMagic; struct _PikaInterpreterMagic { gulong offset; gchar *magic; gchar *mask; guint size; gchar *program; }; static void pika_interpreter_db_finalize (GObject *object); static void pika_interpreter_db_load_interp_file (PikaInterpreterDB *db, GFile *file); static void pika_interpreter_db_add_program (PikaInterpreterDB *db, GFile *file, gchar *buffer); static void pika_interpreter_db_add_binfmt_misc (PikaInterpreterDB *db, GFile *file, gchar *buffer); static gboolean pika_interpreter_db_add_extension (GFile *file, PikaInterpreterDB *db, gchar **tokens); static gboolean pika_interpreter_db_add_magic (PikaInterpreterDB *db, gchar **tokens); static void pika_interpreter_db_clear_magics (PikaInterpreterDB *db); static void pika_interpreter_db_resolve_programs (PikaInterpreterDB *db); G_DEFINE_TYPE (PikaInterpreterDB, pika_interpreter_db, G_TYPE_OBJECT) #define parent_class pika_interpreter_db_parent_class static void pika_interpreter_db_class_init (PikaInterpreterDBClass *class) { GObjectClass *object_class = G_OBJECT_CLASS (class); object_class->finalize = pika_interpreter_db_finalize; } static void pika_interpreter_db_init (PikaInterpreterDB *db) { } static void pika_interpreter_db_finalize (GObject *object) { PikaInterpreterDB *db = PIKA_INTERPRETER_DB (object); pika_interpreter_db_clear (db); G_OBJECT_CLASS (parent_class)->finalize (object); } PikaInterpreterDB * pika_interpreter_db_new (gboolean verbose) { PikaInterpreterDB *db = g_object_new (PIKA_TYPE_INTERPRETER_DB, NULL); db->verbose = verbose; return db; } void pika_interpreter_db_load (PikaInterpreterDB *db, GList *path) { GList *list; g_return_if_fail (PIKA_IS_INTERPRETER_DB (db)); pika_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_attribute_boolean (info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) && g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_REGULAR) { GFile *file = g_file_enumerator_get_child (enumerator, info); pika_interpreter_db_load_interp_file (db, file); g_object_unref (file); } g_object_unref (info); } g_object_unref (enumerator); } } pika_interpreter_db_resolve_programs (db); } void pika_interpreter_db_clear (PikaInterpreterDB *db) { g_return_if_fail (PIKA_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; } pika_interpreter_db_clear_magics (db); } static void pika_interpreter_db_load_interp_file (PikaInterpreterDB *db, GFile *file) { GInputStream *input; GDataInputStream *data_input; gchar *buffer; gsize buffer_len; GError *error = NULL; if (db->verbose) g_print ("Parsing '%s'\n", pika_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"), pika_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] == '/')) { pika_interpreter_db_add_program (db, file, buffer); } else if (! g_ascii_isspace (buffer[0]) && (buffer[0] != '\0')) { pika_interpreter_db_add_binfmt_misc (db, file, buffer); } g_free (buffer); } if (error) { g_message (_("Error reading '%s': %s"), pika_file_get_utf8_name (file), error->message); g_clear_error (&error); } g_object_unref (data_input); } static void pika_interpreter_db_add_program (PikaInterpreterDB *db, GFile *file, gchar *buffer) { gchar *name; gchar *program; gchar *p; p = strchr (buffer, '='); if (! p) return; *p = '\0'; name = buffer; program = p + 1; g_strchomp (program); 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"), pika_file_get_utf8_name (file), pika_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 pika_interpreter_db_add_binfmt_misc (PikaInterpreterDB *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 (! pika_interpreter_db_add_extension (file, db, tokens)) goto bail; break; case 'M': if (! pika_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"), pika_file_get_utf8_name (file)); out: g_strfreev (tokens); } static gboolean pika_interpreter_db_add_extension (GFile *file, PikaInterpreterDB *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"), pika_file_get_utf8_name (file), pika_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 pika_interpreter_db_add_magic (PikaInterpreterDB *db, gchar **tokens) { PikaInterpreterMagic *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 (PikaInterpreterMagic); interp_magic->offset = offset; interp_magic->magic = g_memdup2 (magic, size); interp_magic->mask = g_memdup2 (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 pika_interpreter_db_clear_magics (PikaInterpreterDB *db) { PikaInterpreterMagic *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 (PikaInterpreterMagic, 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) { PikaInterpreterDB *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 pika_interpreter_db_resolve_programs (PikaInterpreterDB *db) { GSList *list; GHashTable *extensions; for (list = db->magics; list; list = list->next) { PikaInterpreterMagic *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) { PikaInterpreterMagic *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 (PikaInterpreterDB *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 (PikaInterpreterDB *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 (PikaInterpreterDB *db, const gchar *program_path, gchar *buffer) { GSList *list; PikaInterpreterMagic *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 * pika_interpreter_db_resolve (PikaInterpreterDB *db, const gchar *program_path, gchar **interp_arg) { GFile *file; GInputStream *input; gchar *program = NULL; g_return_val_if_fail (PIKA_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); } /** * pika_interpreter_db_get_extensions: * @db: * * Returns: a newly allocated string with all registered file * extensions separated by %G_SEARCHPATH_SEPARATOR; * or %NULL if no extensions are registered **/ gchar * pika_interpreter_db_get_extensions (PikaInterpreterDB *db) { GString *str; g_return_val_if_fail (PIKA_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); }