
The code in file_procedure_find() tries to lazy-open the image file, so it's not opened if unnecessary. To keep track of whether the image was opened or not, it overrides head_size to keep track of this. head_size is also assigned the return value of fread() in the loop. The patch separates this by introducing another variable. I think it's best to move the file opening code outside the while loop, as it's most likely bound to be opened anyway, and the penalty is not too bad.
606 lines
17 KiB
C
606 lines
17 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995, 1996, 1997 Spencer Kimball and Peter Mattis
|
|
* Copyright (C) 1997 Josh MacDonald
|
|
*
|
|
* file-procedure.c
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef HAVE_SYS_PARAM_H
|
|
#include <sys/param.h>
|
|
#endif
|
|
|
|
#include <sys/types.h>
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <glib-object.h>
|
|
#include <glib/gstdio.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
|
|
#include "core/core-types.h"
|
|
|
|
#include "plug-in/gimppluginprocedure.h"
|
|
|
|
#include "file-procedure.h"
|
|
#include "file-utils.h"
|
|
|
|
#include "gimp-intl.h"
|
|
|
|
|
|
typedef enum
|
|
{
|
|
FILE_MATCH_NONE,
|
|
FILE_MATCH_MAGIC,
|
|
FILE_MATCH_SIZE
|
|
} FileMatchType;
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static GimpPlugInProcedure * file_proc_find_by_prefix (GSList *procs,
|
|
const gchar *uri,
|
|
gboolean skip_magic);
|
|
static GimpPlugInProcedure * file_proc_find_by_extension (GSList *procs,
|
|
const gchar *uri,
|
|
gboolean skip_magic);
|
|
static GimpPlugInProcedure * file_proc_find_by_name (GSList *procs,
|
|
const gchar *uri,
|
|
gboolean skip_magic);
|
|
|
|
static void file_convert_string (const gchar *instr,
|
|
gchar *outmem,
|
|
gint maxmem,
|
|
gint *nmem);
|
|
static FileMatchType file_check_single_magic (const gchar *offset,
|
|
const gchar *type,
|
|
const gchar *value,
|
|
const guchar *file_head,
|
|
gint headsize,
|
|
FILE *ifp);
|
|
static FileMatchType file_check_magic_list (GSList *magics_list,
|
|
const guchar *head,
|
|
gint headsize,
|
|
FILE *ifp);
|
|
|
|
|
|
/* public functions */
|
|
|
|
GimpPlugInProcedure *
|
|
file_procedure_find (GSList *procs,
|
|
const gchar *uri,
|
|
GError **error)
|
|
{
|
|
GimpPlugInProcedure *file_proc;
|
|
GSList *all_procs = procs;
|
|
gchar *filename;
|
|
|
|
g_return_val_if_fail (procs != NULL, NULL);
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* First, check magicless prefixes/suffixes */
|
|
file_proc = file_proc_find_by_name (all_procs, uri, TRUE);
|
|
|
|
if (file_proc)
|
|
return file_proc;
|
|
|
|
filename = file_utils_filename_from_uri (uri);
|
|
|
|
/* Then look for magics */
|
|
if (filename)
|
|
{
|
|
GimpPlugInProcedure *size_matched_proc = NULL;
|
|
FILE *ifp = NULL;
|
|
gboolean opened = FALSE;
|
|
gint head_size = 0;
|
|
gint size_match_count = 0;
|
|
guchar head[256];
|
|
|
|
while (procs)
|
|
{
|
|
file_proc = procs->data;
|
|
procs = procs->next;
|
|
|
|
if (file_proc->magics_list)
|
|
{
|
|
if (G_UNLIKELY (!opened))
|
|
{
|
|
ifp = g_fopen (filename, "rb");
|
|
if (ifp != NULL)
|
|
head_size = fread ((gchar *) head, 1, sizeof (head), ifp);
|
|
else
|
|
g_set_error_literal (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (errno),
|
|
g_strerror (errno));
|
|
opened = TRUE;
|
|
}
|
|
|
|
if (head_size >= 4)
|
|
{
|
|
FileMatchType match_val;
|
|
|
|
match_val = file_check_magic_list (file_proc->magics_list,
|
|
head, head_size,
|
|
ifp);
|
|
|
|
if (match_val == FILE_MATCH_SIZE)
|
|
{
|
|
/* Use it only if no other magic matches */
|
|
size_match_count++;
|
|
size_matched_proc = file_proc;
|
|
}
|
|
else if (match_val != FILE_MATCH_NONE)
|
|
{
|
|
fclose (ifp);
|
|
g_free (filename);
|
|
|
|
return file_proc;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ifp)
|
|
{
|
|
if (ferror (ifp))
|
|
g_set_error_literal (error, G_FILE_ERROR,
|
|
g_file_error_from_errno (errno),
|
|
g_strerror (errno));
|
|
|
|
fclose (ifp);
|
|
}
|
|
|
|
g_free (filename);
|
|
|
|
if (size_match_count == 1)
|
|
return size_matched_proc;
|
|
}
|
|
|
|
/* As a last resort, try matching by name */
|
|
file_proc = file_proc_find_by_name (all_procs, uri, FALSE);
|
|
|
|
if (file_proc)
|
|
{
|
|
/* we found a procedure, clear error that might have been set */
|
|
g_clear_error (error);
|
|
}
|
|
else
|
|
{
|
|
/* set an error message unless one was already set */
|
|
if (error && *error == NULL)
|
|
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Unknown file type"));
|
|
}
|
|
|
|
return file_proc;
|
|
}
|
|
|
|
GimpPlugInProcedure *
|
|
file_procedure_find_by_prefix (GSList *procs,
|
|
const gchar *uri)
|
|
{
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
return file_proc_find_by_prefix (procs, uri, FALSE);
|
|
}
|
|
|
|
GimpPlugInProcedure *
|
|
file_procedure_find_by_extension (GSList *procs,
|
|
const gchar *uri)
|
|
{
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
return file_proc_find_by_extension (procs, uri, FALSE);
|
|
}
|
|
|
|
gboolean
|
|
file_procedure_in_group (GimpPlugInProcedure *file_proc,
|
|
FileProcedureGroup group)
|
|
{
|
|
const gchar *name = gimp_object_get_name (file_proc);
|
|
gboolean is_xcf_save = FALSE;
|
|
gboolean is_filter = FALSE;
|
|
|
|
is_xcf_save = (strcmp (name, "gimp-xcf-save") == 0);
|
|
|
|
is_filter = (strcmp (name, "file-gz-save") == 0 ||
|
|
strcmp (name, "file-bz2-save") == 0);
|
|
|
|
switch (group)
|
|
{
|
|
case FILE_PROCEDURE_GROUP_SAVE:
|
|
/* Only .xcf shall pass */
|
|
/* FIXME: Handle .gz and .bz2 properly */
|
|
return is_xcf_save || is_filter;
|
|
|
|
case FILE_PROCEDURE_GROUP_EXPORT:
|
|
/* Anything but .xcf shall pass */
|
|
/* FIXME: Handle .gz and .bz2 properly */
|
|
return ! is_xcf_save || is_filter;
|
|
|
|
case FILE_PROCEDURE_GROUP_OPEN:
|
|
/* No filter applied for Open */
|
|
return TRUE;
|
|
|
|
default:
|
|
case FILE_PROCEDURE_GROUP_ANY:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/* private functions */
|
|
|
|
static GimpPlugInProcedure *
|
|
file_proc_find_by_prefix (GSList *procs,
|
|
const gchar *uri,
|
|
gboolean skip_magic)
|
|
{
|
|
GSList *p;
|
|
|
|
for (p = procs; p; p = g_slist_next (p))
|
|
{
|
|
GimpPlugInProcedure *proc = p->data;
|
|
GSList *prefixes;
|
|
|
|
if (skip_magic && proc->magics_list)
|
|
continue;
|
|
|
|
for (prefixes = proc->prefixes_list;
|
|
prefixes;
|
|
prefixes = g_slist_next (prefixes))
|
|
{
|
|
if (g_str_has_prefix (uri, prefixes->data))
|
|
return proc;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GimpPlugInProcedure *
|
|
file_proc_find_by_extension (GSList *procs,
|
|
const gchar *uri,
|
|
gboolean skip_magic)
|
|
{
|
|
GSList *p;
|
|
const gchar *ext;
|
|
|
|
ext = strrchr (uri, '.');
|
|
|
|
if (ext)
|
|
ext++;
|
|
|
|
for (p = procs; p; p = g_slist_next (p))
|
|
{
|
|
GimpPlugInProcedure *proc = p->data;
|
|
GSList *extensions;
|
|
|
|
for (extensions = proc->extensions_list;
|
|
ext && extensions;
|
|
extensions = g_slist_next (extensions))
|
|
{
|
|
const gchar *p1 = ext;
|
|
const gchar *p2 = extensions->data;
|
|
|
|
if (skip_magic && proc->magics_list)
|
|
continue;
|
|
|
|
while (*p1 && *p2)
|
|
{
|
|
if (g_ascii_tolower (*p1) != g_ascii_tolower (*p2))
|
|
break;
|
|
|
|
p1++;
|
|
p2++;
|
|
}
|
|
|
|
if (!(*p1) && !(*p2))
|
|
return proc;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static GimpPlugInProcedure *
|
|
file_proc_find_by_name (GSList *procs,
|
|
const gchar *uri,
|
|
gboolean skip_magic)
|
|
{
|
|
GimpPlugInProcedure *proc;
|
|
|
|
proc = file_proc_find_by_prefix (procs, uri, skip_magic);
|
|
|
|
if (! proc)
|
|
proc = file_proc_find_by_extension (procs, uri, skip_magic);
|
|
|
|
return proc;
|
|
}
|
|
|
|
|
|
static void
|
|
file_convert_string (const gchar *instr,
|
|
gchar *outmem,
|
|
gint maxmem,
|
|
gint *nmem)
|
|
{
|
|
/* Convert a string in C-notation to array of char */
|
|
const guchar *uin = (const guchar *) instr;
|
|
guchar *uout = (guchar *) outmem;
|
|
guchar tmp[5], *tmpptr;
|
|
guint k;
|
|
|
|
while ((*uin != '\0') && ((((gchar *) uout) - outmem) < maxmem))
|
|
{
|
|
if (*uin != '\\') /* Not an escaped character ? */
|
|
{
|
|
*(uout++) = *(uin++);
|
|
continue;
|
|
}
|
|
|
|
if (*(++uin) == '\0')
|
|
{
|
|
*(uout++) = '\\';
|
|
break;
|
|
}
|
|
|
|
switch (*uin)
|
|
{
|
|
case '0': case '1': case '2': case '3': /* octal */
|
|
for (tmpptr = tmp; (tmpptr - tmp) <= 3;)
|
|
{
|
|
*(tmpptr++) = *(uin++);
|
|
if ( (*uin == '\0') || (!g_ascii_isdigit (*uin))
|
|
|| (*uin == '8') || (*uin == '9'))
|
|
break;
|
|
}
|
|
|
|
*tmpptr = '\0';
|
|
sscanf ((gchar *) tmp, "%o", &k);
|
|
*(uout++) = k;
|
|
break;
|
|
|
|
case 'a': *(uout++) = '\a'; uin++; break;
|
|
case 'b': *(uout++) = '\b'; uin++; break;
|
|
case 't': *(uout++) = '\t'; uin++; break;
|
|
case 'n': *(uout++) = '\n'; uin++; break;
|
|
case 'v': *(uout++) = '\v'; uin++; break;
|
|
case 'f': *(uout++) = '\f'; uin++; break;
|
|
case 'r': *(uout++) = '\r'; uin++; break;
|
|
|
|
default : *(uout++) = *(uin++); break;
|
|
}
|
|
}
|
|
|
|
*nmem = ((gchar *) uout) - outmem;
|
|
}
|
|
|
|
static FileMatchType
|
|
file_check_single_magic (const gchar *offset,
|
|
const gchar *type,
|
|
const gchar *value,
|
|
const guchar *file_head,
|
|
gint headsize,
|
|
FILE *ifp)
|
|
|
|
{
|
|
FileMatchType found = FILE_MATCH_NONE;
|
|
glong offs;
|
|
gulong num_testval;
|
|
gulong num_operatorval;
|
|
gint numbytes, k;
|
|
const gchar *num_operator_ptr;
|
|
gchar num_operator;
|
|
|
|
/* Check offset */
|
|
if (sscanf (offset, "%ld", &offs) != 1)
|
|
return FILE_MATCH_NONE;
|
|
|
|
/* Check type of test */
|
|
num_operator_ptr = NULL;
|
|
num_operator = '\0';
|
|
|
|
if (g_str_has_prefix (type, "byte"))
|
|
{
|
|
numbytes = 1;
|
|
num_operator_ptr = type + strlen ("byte");
|
|
}
|
|
else if (g_str_has_prefix (type, "short"))
|
|
{
|
|
numbytes = 2;
|
|
num_operator_ptr = type + strlen ("short");
|
|
}
|
|
else if (g_str_has_prefix (type, "long"))
|
|
{
|
|
numbytes = 4;
|
|
num_operator_ptr = type + strlen ("long");
|
|
}
|
|
else if (g_str_has_prefix (type, "size"))
|
|
{
|
|
numbytes = 5;
|
|
}
|
|
else if (strcmp (type, "string") == 0)
|
|
{
|
|
numbytes = 0;
|
|
}
|
|
else
|
|
{
|
|
return FILE_MATCH_NONE;
|
|
}
|
|
|
|
/* Check numerical operator value if present */
|
|
if (num_operator_ptr && (*num_operator_ptr == '&'))
|
|
{
|
|
if (g_ascii_isdigit (num_operator_ptr[1]))
|
|
{
|
|
if (num_operator_ptr[1] != '0') /* decimal */
|
|
sscanf (num_operator_ptr+1, "%lu", &num_operatorval);
|
|
else if (num_operator_ptr[2] == 'x') /* hexadecimal */
|
|
sscanf (num_operator_ptr+3, "%lx", &num_operatorval);
|
|
else /* octal */
|
|
sscanf (num_operator_ptr+2, "%lo", &num_operatorval);
|
|
|
|
num_operator = *num_operator_ptr;
|
|
}
|
|
}
|
|
|
|
if (numbytes > 0) /* Numerical test ? */
|
|
{
|
|
gchar num_test = '=';
|
|
gulong fileval = 0;
|
|
|
|
/* Check test value */
|
|
if ((value[0] == '>') || (value[0] == '<'))
|
|
{
|
|
num_test = value[0];
|
|
value++;
|
|
}
|
|
|
|
errno = 0;
|
|
num_testval = strtol (value, NULL, 0);
|
|
|
|
if (errno != 0)
|
|
return FILE_MATCH_NONE;
|
|
|
|
if (numbytes == 5) /* Check for file size ? */
|
|
{
|
|
struct stat buf;
|
|
|
|
if (fstat (fileno (ifp), &buf) < 0)
|
|
return FILE_MATCH_NONE;
|
|
|
|
fileval = buf.st_size;
|
|
}
|
|
else if (offs >= 0 &&
|
|
(offs + numbytes <= headsize)) /* We have it in memory ? */
|
|
{
|
|
for (k = 0; k < numbytes; k++)
|
|
fileval = (fileval << 8) | (glong) file_head[offs + k];
|
|
}
|
|
else /* Read it from file */
|
|
{
|
|
gint c = 0;
|
|
|
|
if (fseek (ifp, offs, (offs >= 0) ? SEEK_SET : SEEK_END) < 0)
|
|
return FILE_MATCH_NONE;
|
|
|
|
for (k = 0; k < numbytes; k++)
|
|
fileval = (fileval << 8) | (c = getc (ifp));
|
|
|
|
if (c == EOF)
|
|
return FILE_MATCH_NONE;
|
|
}
|
|
|
|
if (num_operator == '&')
|
|
fileval &= num_operatorval;
|
|
|
|
if (num_test == '<')
|
|
found = (fileval < num_testval);
|
|
else if (num_test == '>')
|
|
found = (fileval > num_testval);
|
|
else
|
|
found = (fileval == num_testval);
|
|
|
|
if (found && (numbytes == 5))
|
|
found = FILE_MATCH_SIZE;
|
|
}
|
|
else if (numbytes == 0) /* String test */
|
|
{
|
|
gchar mem_testval[256];
|
|
|
|
file_convert_string (value,
|
|
mem_testval, sizeof (mem_testval),
|
|
&numbytes);
|
|
|
|
if (numbytes <= 0)
|
|
return FILE_MATCH_NONE;
|
|
|
|
if (offs >= 0 &&
|
|
(offs + numbytes <= headsize)) /* We have it in memory ? */
|
|
{
|
|
found = (memcmp (mem_testval, file_head + offs, numbytes) == 0);
|
|
}
|
|
else /* Read it from file */
|
|
{
|
|
if (fseek (ifp, offs, (offs >= 0) ? SEEK_SET : SEEK_END) < 0)
|
|
return FILE_MATCH_NONE;
|
|
|
|
found = FILE_MATCH_MAGIC;
|
|
|
|
for (k = 0; found && (k < numbytes); k++)
|
|
{
|
|
gint c = getc (ifp);
|
|
|
|
found = (c != EOF) && (c == (gint) mem_testval[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static FileMatchType
|
|
file_check_magic_list (GSList *magics_list,
|
|
const guchar *head,
|
|
gint headsize,
|
|
FILE *ifp)
|
|
|
|
{
|
|
const gchar *offset;
|
|
const gchar *type;
|
|
const gchar *value;
|
|
gboolean and = FALSE;
|
|
gboolean found = FALSE;
|
|
FileMatchType match_val;
|
|
|
|
while (magics_list)
|
|
{
|
|
if ((offset = magics_list->data) == NULL) break;
|
|
if ((magics_list = magics_list->next) == NULL) break;
|
|
if ((type = magics_list->data) == NULL) break;
|
|
if ((magics_list = magics_list->next) == NULL) break;
|
|
if ((value = magics_list->data) == NULL) break;
|
|
|
|
magics_list = magics_list->next;
|
|
|
|
match_val = file_check_single_magic (offset, type, value,
|
|
head, headsize,
|
|
ifp);
|
|
if (and)
|
|
found = found && (match_val != FILE_MATCH_NONE);
|
|
else
|
|
found = (match_val != FILE_MATCH_NONE);
|
|
|
|
and = (strchr (offset, '&') != NULL);
|
|
|
|
if (! and && found)
|
|
return match_val;
|
|
}
|
|
|
|
return FILE_MATCH_NONE;
|
|
}
|