
2007-03-17 Michael Natterer <mitch@gimp.org> * app/file/file-utils.c (file_check_single_magic): alow negative offsets in magics and interpret them as relative to the end of the file. (file_utils_find_proc) (file_check_magic_list): some cleanup. * plug-ins/common/tga.c (query): register the magic string "TRUEVISION-XFILE.\0" 18 bytes before the end of the file. This way, "new style" TGA files are detected regardless of their filename. "old style" TGA files still need the right extension to be treated as TGA. Added "vda", "icb" and "vst" to the list of extensions. Fixes bug #133798. svn path=/trunk/; revision=22138
1005 lines
26 KiB
C
1005 lines
26 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995, 1996, 1997 Spencer Kimball and Peter Mattis
|
|
* Copyright (C) 1997 Josh MacDonald
|
|
*
|
|
* file-utils.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 2 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
#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 <gdk-pixbuf/gdk-pixbuf.h>
|
|
|
|
#include "libgimpbase/gimpbase.h"
|
|
#include "libgimpmath/gimpmath.h"
|
|
#include "libgimpthumb/gimpthumb.h"
|
|
|
|
#include "core/core-types.h"
|
|
|
|
#include "core/gimp.h"
|
|
#include "core/gimpimage.h"
|
|
#include "core/gimpimagefile.h"
|
|
|
|
#include "plug-in/gimppluginmanager.h"
|
|
#include "plug-in/gimppluginprocedure.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 gchar * file_utils_unescape_uri (const gchar *escaped,
|
|
gint len,
|
|
const gchar *illegal_escaped_characters,
|
|
gboolean ascii_must_not_be_escaped);
|
|
|
|
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 */
|
|
|
|
gchar *
|
|
file_utils_filename_to_uri (Gimp *gimp,
|
|
const gchar *filename,
|
|
GError **error)
|
|
{
|
|
gchar *absolute;
|
|
gchar *uri;
|
|
|
|
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
|
|
g_return_val_if_fail (filename != NULL, NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* check for prefixes like http or ftp */
|
|
if (file_proc_find_by_prefix (gimp->plug_in_manager->load_procs,
|
|
filename, FALSE))
|
|
{
|
|
if (g_utf8_validate (filename, -1, NULL))
|
|
{
|
|
return g_strdup (filename);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
|
|
_("Invalid character sequence in URI"));
|
|
return NULL;
|
|
}
|
|
}
|
|
else if (strstr (filename, "://"))
|
|
{
|
|
gchar *scheme;
|
|
gchar *canon;
|
|
|
|
scheme = g_strndup (filename, (strstr (filename, "://") - filename));
|
|
canon = g_strdup (scheme);
|
|
|
|
g_strcanon (canon, G_CSET_A_2_Z G_CSET_a_2_z G_CSET_DIGITS "+-.", '-');
|
|
|
|
if (! strcmp (scheme, canon) && g_ascii_isgraph (canon[0]))
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, 0,
|
|
_("URI scheme '%s:' is not supported"), scheme);
|
|
|
|
g_free (scheme);
|
|
g_free (canon);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
g_free (scheme);
|
|
g_free (canon);
|
|
}
|
|
|
|
if (! g_path_is_absolute (filename))
|
|
{
|
|
gchar *current;
|
|
|
|
current = g_get_current_dir ();
|
|
absolute = g_build_filename (current, filename, NULL);
|
|
g_free (current);
|
|
}
|
|
else
|
|
{
|
|
absolute = g_strdup (filename);
|
|
}
|
|
|
|
uri = g_filename_to_uri (absolute, NULL, error);
|
|
|
|
g_free (absolute);
|
|
|
|
return uri;
|
|
}
|
|
|
|
gchar *
|
|
file_utils_any_to_uri (Gimp *gimp,
|
|
const gchar *filename_or_uri,
|
|
GError **error)
|
|
{
|
|
gchar *uri;
|
|
|
|
g_return_val_if_fail (GIMP_IS_GIMP (gimp), NULL);
|
|
g_return_val_if_fail (filename_or_uri != NULL, NULL);
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
|
|
/* first try if we got a file uri */
|
|
uri = g_filename_from_uri (filename_or_uri, NULL, NULL);
|
|
|
|
if (uri)
|
|
{
|
|
g_free (uri);
|
|
uri = g_strdup (filename_or_uri);
|
|
}
|
|
else
|
|
{
|
|
uri = file_utils_filename_to_uri (gimp, filename_or_uri, error);
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
|
|
/**
|
|
* file_utils_filename_from_uri:
|
|
* @uri: a URI
|
|
*
|
|
* A utility function to be used as a replacement for
|
|
* g_filename_from_uri(). It deals with file: URIs with hostname in a
|
|
* platform-specific way. On Win32, a UNC path is created and
|
|
* returned, on other platforms the URI is detected as non-local and
|
|
* NULL is returned.
|
|
*
|
|
* Returns: newly allocated filename or %NULL if @uri is a remote file
|
|
**/
|
|
gchar *
|
|
file_utils_filename_from_uri (const gchar *uri)
|
|
{
|
|
gchar *filename;
|
|
gchar *hostname;
|
|
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
filename = g_filename_from_uri (uri, &hostname, NULL);
|
|
|
|
if (!filename)
|
|
return NULL;
|
|
|
|
if (hostname)
|
|
{
|
|
/* we have a file: URI with a hostname */
|
|
#ifdef G_OS_WIN32
|
|
/* on Win32, create a valid UNC path and use it as the filename */
|
|
|
|
gchar *tmp = g_build_filename ("//", hostname, filename, NULL);
|
|
|
|
g_free (filename);
|
|
filename = tmp;
|
|
#else
|
|
/* otherwise return NULL, caller should use URI then */
|
|
g_free (filename);
|
|
filename = NULL;
|
|
#endif
|
|
|
|
g_free (hostname);
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
GimpPlugInProcedure *
|
|
file_utils_find_proc (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;
|
|
gint head_size = -2;
|
|
gint size_match_count = 0;
|
|
guchar head[256];
|
|
|
|
while (procs)
|
|
{
|
|
file_proc = procs->data;
|
|
procs = procs->next;
|
|
|
|
if (file_proc->magics_list)
|
|
{
|
|
if (head_size == -2)
|
|
{
|
|
head_size = 0;
|
|
|
|
if ((ifp = g_fopen (filename, "rb")) != NULL)
|
|
{
|
|
head_size = fread ((gchar *) head, 1, sizeof (head), ifp);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error,
|
|
G_FILE_ERROR,
|
|
g_file_error_from_errno (errno),
|
|
g_strerror (errno));
|
|
}
|
|
}
|
|
|
|
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 (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 (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Unknown file type"));
|
|
}
|
|
|
|
return file_proc;
|
|
}
|
|
|
|
GimpPlugInProcedure *
|
|
file_utils_find_proc_by_extension (GSList *procs,
|
|
const gchar *uri)
|
|
{
|
|
g_return_val_if_fail (procs != NULL, NULL);
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
return file_proc_find_by_extension (procs, uri, FALSE);
|
|
}
|
|
|
|
gchar *
|
|
file_utils_uri_to_utf8_filename (const gchar *uri)
|
|
{
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
if (g_str_has_prefix (uri, "file:"))
|
|
{
|
|
gchar *filename = file_utils_filename_from_uri (uri);
|
|
|
|
if (filename)
|
|
{
|
|
GError *error = NULL;
|
|
gchar *utf8;
|
|
|
|
utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, &error);
|
|
g_free (filename);
|
|
|
|
if (utf8)
|
|
return utf8;
|
|
|
|
g_warning ("%s: cannot convert filename to UTF-8: %s",
|
|
G_STRLOC, error->message);
|
|
g_error_free (error);
|
|
}
|
|
}
|
|
|
|
return g_strdup (uri);
|
|
}
|
|
|
|
static gchar *
|
|
file_utils_uri_to_utf8_basename (const gchar *uri)
|
|
{
|
|
gchar *filename;
|
|
gchar *basename = NULL;
|
|
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
filename = file_utils_uri_to_utf8_filename (uri);
|
|
|
|
if (strstr (filename, G_DIR_SEPARATOR_S))
|
|
{
|
|
basename = g_path_get_basename (filename);
|
|
}
|
|
else if (strstr (filename, "://"))
|
|
{
|
|
basename = strrchr (uri, '/');
|
|
|
|
if (basename)
|
|
basename = g_strdup (basename + 1);
|
|
}
|
|
|
|
if (basename)
|
|
{
|
|
g_free (filename);
|
|
return basename;
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
gchar *
|
|
file_utils_uri_display_basename (const gchar *uri)
|
|
{
|
|
gchar *basename = NULL;
|
|
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
if (g_str_has_prefix (uri, "file:"))
|
|
{
|
|
gchar *filename = file_utils_filename_from_uri (uri);
|
|
|
|
if (filename)
|
|
{
|
|
basename = g_filename_display_basename (filename);
|
|
g_free (filename);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
gchar *name = file_utils_uri_display_name (uri);
|
|
|
|
basename = strrchr (name, '/');
|
|
if (basename)
|
|
basename = g_strdup (basename + 1);
|
|
|
|
g_free (name);
|
|
}
|
|
|
|
return basename ? basename : file_utils_uri_to_utf8_basename (uri);
|
|
}
|
|
|
|
gchar *
|
|
file_utils_uri_display_name (const gchar *uri)
|
|
{
|
|
gchar *name = NULL;
|
|
|
|
g_return_val_if_fail (uri != NULL, NULL);
|
|
|
|
if (g_str_has_prefix (uri, "file:"))
|
|
{
|
|
gchar *filename = file_utils_filename_from_uri (uri);
|
|
|
|
if (filename)
|
|
{
|
|
name = g_filename_display_name (filename);
|
|
g_free (filename);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
name = file_utils_unescape_uri (uri, -1, "/", FALSE);
|
|
}
|
|
|
|
return name ? name : g_strdup (uri);
|
|
}
|
|
|
|
GdkPixbuf *
|
|
file_utils_load_thumbnail (const gchar *filename)
|
|
{
|
|
GimpThumbnail *thumbnail = NULL;
|
|
GdkPixbuf *pixbuf = NULL;
|
|
gchar *uri;
|
|
|
|
g_return_val_if_fail (filename != NULL, NULL);
|
|
|
|
uri = g_filename_to_uri (filename, NULL, NULL);
|
|
|
|
if (uri)
|
|
{
|
|
thumbnail = gimp_thumbnail_new ();
|
|
gimp_thumbnail_set_uri (thumbnail, uri);
|
|
|
|
pixbuf = gimp_thumbnail_load_thumb (thumbnail,
|
|
GIMP_THUMBNAIL_SIZE_NORMAL,
|
|
NULL);
|
|
}
|
|
|
|
g_free (uri);
|
|
|
|
if (pixbuf)
|
|
{
|
|
gint width = gdk_pixbuf_get_width (pixbuf);
|
|
gint height = gdk_pixbuf_get_height (pixbuf);
|
|
|
|
if (gdk_pixbuf_get_n_channels (pixbuf) != 3)
|
|
{
|
|
GdkPixbuf *tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8,
|
|
width, height);
|
|
|
|
gdk_pixbuf_composite_color (pixbuf, tmp,
|
|
0, 0, width, height, 0, 0, 1.0, 1.0,
|
|
GDK_INTERP_NEAREST, 255,
|
|
0, 0, GIMP_CHECK_SIZE_SM,
|
|
0x66666666, 0x99999999);
|
|
|
|
g_object_unref (pixbuf);
|
|
pixbuf = tmp;
|
|
}
|
|
}
|
|
|
|
return pixbuf;
|
|
}
|
|
|
|
gboolean
|
|
file_utils_save_thumbnail (GimpImage *image,
|
|
const gchar *filename)
|
|
{
|
|
const gchar *image_uri;
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
image_uri = gimp_object_get_name (GIMP_OBJECT (image));
|
|
|
|
if (image_uri)
|
|
{
|
|
gchar *uri = g_filename_to_uri (filename, NULL, NULL);
|
|
|
|
if (uri)
|
|
{
|
|
if ( ! strcmp (uri, image_uri))
|
|
{
|
|
GimpImagefile *imagefile;
|
|
|
|
imagefile = gimp_imagefile_new (image->gimp, uri);
|
|
success = gimp_imagefile_save_thumbnail (imagefile, NULL, image);
|
|
g_object_unref (imagefile);
|
|
}
|
|
|
|
g_free (uri);
|
|
}
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/* 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;
|
|
}
|
|
|
|
|
|
/* the following two functions are copied from glib/gconvert.c */
|
|
|
|
static gint
|
|
unescape_character (const gchar *scanner)
|
|
{
|
|
gint first_digit;
|
|
gint second_digit;
|
|
|
|
first_digit = g_ascii_xdigit_value (scanner[0]);
|
|
if (first_digit < 0)
|
|
return -1;
|
|
|
|
second_digit = g_ascii_xdigit_value (scanner[1]);
|
|
if (second_digit < 0)
|
|
return -1;
|
|
|
|
return (first_digit << 4) | second_digit;
|
|
}
|
|
|
|
static gchar *
|
|
file_utils_unescape_uri (const gchar *escaped,
|
|
gint len,
|
|
const gchar *illegal_escaped_characters,
|
|
gboolean ascii_must_not_be_escaped)
|
|
{
|
|
const gchar *in, *in_end;
|
|
gchar *out, *result;
|
|
gint c;
|
|
|
|
if (escaped == NULL)
|
|
return NULL;
|
|
|
|
if (len < 0)
|
|
len = strlen (escaped);
|
|
|
|
result = g_malloc (len + 1);
|
|
|
|
out = result;
|
|
for (in = escaped, in_end = escaped + len; in < in_end; in++)
|
|
{
|
|
c = *in;
|
|
|
|
if (c == '%')
|
|
{
|
|
/* catch partial escape sequences past the end of the substring */
|
|
if (in + 3 > in_end)
|
|
break;
|
|
|
|
c = unescape_character (in + 1);
|
|
|
|
/* catch bad escape sequences and NUL characters */
|
|
if (c <= 0)
|
|
break;
|
|
|
|
/* catch escaped ASCII */
|
|
if (ascii_must_not_be_escaped && c <= 0x7F)
|
|
break;
|
|
|
|
/* catch other illegal escaped characters */
|
|
if (strchr (illegal_escaped_characters, c) != NULL)
|
|
break;
|
|
|
|
in += 2;
|
|
}
|
|
|
|
*out++ = c;
|
|
}
|
|
|
|
g_assert (out - result <= len);
|
|
*out = '\0';
|
|
|
|
if (in != in_end)
|
|
{
|
|
g_free (result);
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
}
|