Commit c30453f4 authored by Raphael Quinet's avatar Raphael Quinet Committed by Raphaël Quinet
Browse files

Fixed crash occuring after a double free of some structured XMP

2005-04-22  Raphael Quinet  <raphael@gimp.org>

	* plug-ins/metadata/xmp-model.[ch]: Fixed crash occuring after a
	double free of some structured XMP properties.  Added new type
	XMP_TYPE_GENERIC_STRUCTURE for unknown structured properties.  Be
	more tolerant in parsing incorrectly written schema URIs and try
	to extract a valid URI from them.  Converted to use g_print().
	Moved the definitions of standard XMP schemas from here...

	* plug-ins/metadata/xmp-schemas.[ch]: ...to these new files.

	* plug-ins/metadata/xmp-encode.[ch]: Rewritten using GString
	instead of fixed buffers.

	* plug-ins/metadata/metadata.c
	* plug-ins/metadata/interface.c: Adapted to the new function
	xmp_generate_block() using GString.

	* plug-ins/metadata/base64.c (base64_encode): Fixed incorrect
	encoding of bytes with the sign bit set.

	* plug-ins/metadata/testbase64.c
	* plug-ins/metadata/Makefile.am: Added xmp-schema.[ch] and test
	program testbase64.c (testing base64 encoding and decoding).

	* plug-ins/metadata/xmpdump.c: Converted to use g_print().

	* plug-ins/metadata/xmp-parse.c: Added some #ifdef's around
	debugging code, added more comments.
parent ee1cac14
2005-04-22 Raphaël Quinet <raphael@gimp.org>
* plug-ins/metadata/xmp-model.[ch]: Fixed crash occuring after a
double free of some structured XMP properties. Added new type
XMP_TYPE_GENERIC_STRUCTURE for unknown structured properties. Be
more tolerant in parsing incorrectly written schema URIs and try
to extract a valid URI from them. Converted to use g_print().
Moved the definitions of standard XMP schemas from here...
* plug-ins/metadata/xmp-schemas.[ch]: ...to these new files.
* plug-ins/metadata/xmp-encode.[ch]: Rewritten using GString
instead of fixed buffers.
* plug-ins/metadata/metadata.c
* plug-ins/metadata/interface.c: Adapted to the new function
xmp_generate_block() using GString.
* plug-ins/metadata/base64.c (base64_encode): Fixed incorrect
encoding of bytes with the sign bit set.
* plug-ins/metadata/testbase64.c
* plug-ins/metadata/Makefile.am: Added xmp-schema.[ch] and test
program testbase64.c (testing base64 encoding and decoding).
* plug-ins/metadata/xmpdump.c: Converted to use g_print().
* plug-ins/metadata/xmp-parse.c: Added some #ifdef's around
debugging code, added more comments.
2005-04-22 Sven Neumann <sven@gimp.org>
* libgimpbase/gimpbaseenums.h (GimpTransformDirection): removed
......
......@@ -20,14 +20,16 @@ metadata_SOURCES = \
metadata.c \
interface.h \
interface.c \
base64.h \
base64.c \
xmp-model.h \
xmp-model.c \
xmp-parse.h \
xmp-parse.c \
xmp-encode.h \
xmp-encode.c \
base64.h \
base64.c
xmp-schemas.h \
xmp-schemas.c
# exif-decode.h \
# exif-decode.c \
# exif-encode.h \
......@@ -38,10 +40,10 @@ metadata_SOURCES = \
noinst_PROGRAMS = xmpdump
xmpdump_SOURCES = \
xmpdump.c \
xmp-parse.h \
xmp-parse.c \
base64.h \
base64.c
base64.c \
xmp-parse.h \
xmp-parse.c
INCLUDES = \
-I$(top_srcdir) \
......@@ -58,3 +60,13 @@ LDADD = \
$(GTK_LIBS) \
$(RT_LIBS) \
$(INTLLIBS)
# test program, not built by default
TESTS = testbase64$(EXEEXT)
EXTRA_PROGRAMS = testbase64
testbase64_SOURCES = \
base64.h \
base64.c \
testbase64.c
/* base64.h - encode and decode base64 encoding according to RFC 1521
/* base64.h - encode and decode base64 encoding according to RFC 2045
*
* Copyright (C) 2005, Raphaël Quinet <raphael@gimp.org>
*
......@@ -24,7 +24,7 @@
* ignore whitespace (especially those written for HTTP usage) and the
* rest were not compatible with the LGPL (some were GPL, not LGPL).
* Or at least I haven't been able to find LGPL implementations.
* Writing this according to RFC 1521 did not take long anyway.
* Writing this according to RFC 2045 did not take long anyway.
*/
#ifndef WITHOUT_GIMP
......@@ -86,7 +86,7 @@ static const gint base64_6bits[256] =
* specified correctly. The decoder will stop at the first nul byte
* or at the first '=' (padding byte) so you should ensure that one of
* these is present if you supply -1 for @src_size. For more details
* about the base64 encoding, see RFC 1521.
* about the base64 encoding, see RFC 2045, chapter 6.8.
*
* Returns: the number of bytes stored in @dest, or -1 if invalid data was found.
*/
......@@ -160,14 +160,11 @@ base64_decode (const gchar *src_b64,
* Since the base64 encoding uses 4 bytes for every 3 bytes of input,
* @dest_size should be at least 4/3 of @src_size, plus optional line
* breaks if @columns > 0 and up to two padding bytes at the end. For
* more details about the base64 encoding, see RFC 1521.
* more details about the base64 encoding, see RFC 2045, chapter 6.8.
* Note that RFC 2045 recommends setting @columns to 76.
*
* Returns: the number of bytes stored in @dest.
*/
/*
* FIXME: docs!
* if columns <= 0, no line breaks
*/
gssize
base64_encode (const gchar *src,
gsize src_size,
......@@ -175,10 +172,10 @@ base64_encode (const gchar *src,
gsize dest_size,
gint columns)
{
gint32 bits;
gssize i;
gint n;
gint c;
guint32 bits;
gssize i;
gint n;
gint c;
g_return_val_if_fail (src != NULL, -1);
g_return_val_if_fail (dest_b64 != NULL, -1);
......@@ -188,7 +185,7 @@ base64_encode (const gchar *src,
c = 0;
for (i = 0; (src_size != 0) && (i + 4 <= dest_size); src++, src_size--)
{
bits += *src;
bits += *(guchar *)src;
if (++n == 3)
{
dest_b64[i++] = base64_code[(bits >> 18) & 0x3f];
......
......@@ -50,7 +50,7 @@
#include "libgimp/stdplugins-intl.h"
#include "interface.h"
#include "xmp-model.h"
#include "xmp-schemas.h"
#include "xmp-encode.h"
......@@ -532,25 +532,21 @@ export_dialog_response (GtkWidget *dlg,
if (response_id == GTK_RESPONSE_OK)
{
gchar *filename;
gchar *buffer;
gssize buffer_length;
int fd;
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg));
GString *buffer;
gchar *filename;
int fd;
/* FIXME: improve this code and rewrite the error handling */
buffer_length = xmp_estimate_size (mgui->xmp_model);
buffer = g_new (gchar, buffer_length);
xmp_generate_block (mgui->xmp_model, buffer, buffer_length);
buffer = g_string_new (NULL);
xmp_generate_packet (mgui->xmp_model, buffer);
filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dlg));
fd = g_open (filename, O_CREAT | O_TRUNC | O_WRONLY | _O_BINARY, 0666);
if (fd < 0)
{
metadata_message_dialog (GTK_MESSAGE_ERROR, GTK_WINDOW (dlg),
_("Open failed"),
_("Cannot create file"));
g_free (buffer);
g_string_free (buffer, TRUE);
g_free (filename);
return;
}
......@@ -560,12 +556,12 @@ export_dialog_response (GtkWidget *dlg,
strlen (buffer), filename);
*/
if (write (fd, buffer, strlen (buffer)) < 0)
if (write (fd, buffer->str, buffer->len) < 0)
{
metadata_message_dialog (GTK_MESSAGE_ERROR, GTK_WINDOW (dlg),
_("Save failed"),
_("Some error occurred while saving"));
g_free (buffer);
g_string_free (buffer, TRUE);
g_free (filename);
return;
}
......@@ -575,12 +571,12 @@ export_dialog_response (GtkWidget *dlg,
metadata_message_dialog (GTK_MESSAGE_ERROR, GTK_WINDOW (dlg),
_("Save failed"),
_("Could not close the file"));
g_free (buffer);
g_string_free (buffer, TRUE);
g_free (filename);
return;
}
g_free (buffer);
g_string_free (buffer, TRUE);
g_free (filename);
}
......
......@@ -526,29 +526,24 @@ run (const gchar *name,
if (status == GIMP_PDB_SUCCESS)
{
gchar *buffer;
gssize buffer_size;
gssize used_size;
GString *buffer;
/* Generate the updated parasite and attach it to the image */
buffer_size = xmp_estimate_size (xmp_model);
buffer = g_new (gchar, buffer_size + METADATA_MARKER_LEN);
strcpy (buffer, METADATA_MARKER);
used_size = xmp_generate_block (xmp_model,
buffer + METADATA_MARKER_LEN,
buffer_size);
buffer = g_string_new (METADATA_MARKER);
xmp_generate_packet (xmp_model, buffer);
parasite = gimp_parasite_new (METADATA_PARASITE,
GIMP_PARASITE_PERSISTENT,
used_size + METADATA_MARKER_LEN,
(gpointer) buffer);
buffer->len,
(gpointer) buffer->str);
gimp_image_parasite_attach (image_ID, parasite);
if (! strcmp (name, "plug_in_metadata_encode_xmp"))
{
*nreturn_vals = 2;
values[1].type = GIMP_PDB_STRING;
values[1].data.d_string = g_strdup (buffer + METADATA_MARKER_LEN);
values[1].data.d_string = g_strdup (buffer->str
+ METADATA_MARKER_LEN);
}
g_free (buffer);
g_string_free (buffer, TRUE);
xmp_model_free (xmp_model);
}
......
/* Small test program to test the base64 encoding and decoding */
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "base64.h"
static int
string_encode_decode (char *s)
{
int n;
char encoded[300];
char decoded[400];
n = base64_encode (s, strlen (s), encoded, sizeof (encoded) - 1, 0);
if (n < 0)
{
g_print ("base64 encoding failed for '%s'", s);
return 1;
}
g_print ("'%s' -> '%s' (%d) ", s, encoded, n);
n = base64_decode (encoded, strlen (encoded), decoded, sizeof (decoded) - 1);
if (n < 0)
{
g_print ("\nbase64 decoding failed for '%s'", s);
return 1;
}
if (! strcmp (s, decoded))
g_print ("-> '%s' (%d)\n", decoded, n);
else
{
g_print ("-> '%s' (%d) MISMATCH!\n", decoded, n);
g_print ("decoded buffer does not match original!\n");
return 1;
}
return 0;
}
static int
buffer_encode_decode (char *buf,
gint buf_len,
gint columns)
{
int n;
char encoded[3000];
char decoded[4000];
n = base64_encode (buf, buf_len, encoded, sizeof (encoded) - 1, columns);
if (n < 0)
{
g_print ("base64 encoding failed");
return 1;
}
g_print ("buffer length %d -> encoded %d (columns: %d) ", buf_len, n,
columns);
n = base64_decode (encoded, strlen (encoded), decoded, sizeof (decoded) - 1);
if (n < 0)
{
g_print ("\nbase64 decoding failed");
return 1;
}
if ((n == buf_len) && ! memcmp (buf, decoded, buf_len))
g_print ("-> decoded %d match OK\n", n);
else
{
g_print ("-> decoded %d MISMATCH!\n", n);
g_print ("decoded buffer does not match original!\n");
return 1;
}
return 0;
}
int
main (int argc,
char *argv[])
{
int failed = 0;
int i;
char buf[1000];
g_print ("Testing base64 encoding ...\n");
failed += string_encode_decode ("");
failed += string_encode_decode ("A");
failed += string_encode_decode ("AB");
failed += string_encode_decode ("ABC");
failed += string_encode_decode ("ABCD");
failed += string_encode_decode ("ABCDE");
failed += string_encode_decode ("ABCDEF");
failed += string_encode_decode ("ABCDEFG");
failed += string_encode_decode ("ABCDEFGH");
failed += string_encode_decode ("ABCDEFGHI");
failed += string_encode_decode ("abcdefghik");
failed += string_encode_decode ("1234567890abcdefghijklmnopqrstuvwxyz");
failed += string_encode_decode ("«© Raphaël»");
for (i = 0; i < sizeof (buf); i++)
buf[i] = (char) (i % 0xff);
failed += buffer_encode_decode (buf, sizeof (buf), 0);
failed += buffer_encode_decode (buf, sizeof (buf), 76);
failed += buffer_encode_decode (buf, sizeof (buf), 4);
failed += buffer_encode_decode (buf, sizeof (buf), 1);
for (i = 0; i < sizeof (buf); i++)
buf[i] = (char) (0xff - (i % 0xff));
failed += buffer_encode_decode (buf, 600, 0);
failed += buffer_encode_decode (buf, 500, 0);
failed += buffer_encode_decode (buf, 400, 0);
if (failed > 0)
{
g_print ("%d test(s) failed!\n", failed);
return EXIT_FAILURE;
}
g_print ("No problems detected.\n");
return EXIT_SUCCESS;
}
/* xmp-gen.c - generate XMP metadata from the tree model
/* xmp-encode.c - generate XMP metadata from the tree model
*
* Copyright (C) 2005, Raphaël Quinet <raphael@gimp.org>
*
......@@ -30,175 +30,31 @@
#include "libgimp/stdplugins-intl.h"
#include "xmp-encode.h"
#include "xmp-model.h"
#include "xmp-schemas.h"
static gssize
size_schema (GtkTreeModel *model,
GtkTreeIter *iter,
const XMPSchema **schema_r)
static void
gen_schema_start (GString *buffer,
const XMPSchema *schema)
{
gtk_tree_model_get (model, iter,
COL_XMP_TYPE_XREF, schema_r,
-1);
return (sizeof (" <rdf:Description xmlns:%s='%s'>\n") - 5
+ strlen ((*schema_r)->prefix)
+ strlen ((*schema_r)->uri)
+ sizeof (" </rdf:Description>\n\n") - 1);
g_string_append_printf (buffer, " <rdf:Description xmlns:%s='%s'>\n",
schema->prefix, schema->uri);
}
static gssize
size_property (GtkTreeModel *model,
GtkTreeIter *iter,
const XMPSchema *schema)
static void
gen_schema_end (GString *buffer)
{
const XMPProperty *property;
const gchar **value_array;
gssize length;
gint i;
gtk_tree_model_get (model, iter,
COL_XMP_TYPE_XREF, &property,
COL_XMP_VALUE_RAW, &value_array,
-1);
switch (property->type)
{
case XMP_TYPE_BOOLEAN:
case XMP_TYPE_DATE:
case XMP_TYPE_INTEGER:
case XMP_TYPE_REAL:
case XMP_TYPE_MIME_TYPE:
case XMP_TYPE_TEXT:
case XMP_TYPE_RATIONAL:
return (sizeof (" <%s:%s>%s</%s:%s>\n") - 11
+ 2 * strlen (schema->prefix)
+ 2 * strlen (property->name)
+ strlen (value_array[0]));
case XMP_TYPE_LOCALE_BAG:
case XMP_TYPE_TEXT_BAG:
case XMP_TYPE_XPATH_BAG:
case XMP_TYPE_JOB_BAG:
case XMP_TYPE_INTEGER_SEQ:
case XMP_TYPE_TEXT_SEQ:
case XMP_TYPE_RESOURCE_EVENT_SEQ:
case XMP_TYPE_RATIONAL_SEQ:
length = (sizeof (" <%s:%s>\n <rdf:Bag>\n") - 5
+ sizeof (" </rdf:Bag>\n </%s:%s>\n") - 5
+ 2 * strlen (schema->prefix)
+ 2 * strlen (property->name));
for (i = 0; value_array[i] != NULL; i++)
length += (sizeof (" <rdf:li>%s</rdf:li>\n") - 3
+ strlen (value_array[i]));
return length;
case XMP_TYPE_LANG_ALT:
length = (sizeof (" <%s:%s>\n <rdf:Alt>\n") - 5
+ sizeof (" </rdf:Alt>\n </%s:%s>\n") - 5
+ 2 * strlen (schema->prefix)
+ 2 * strlen (property->name));
for (i = 0; value_array[i] != NULL; i += 2)
length += (sizeof (" <rdf:li xml:lang='%s'>%s</rdf:li>\n") - 5
+ strlen (value_array[i])
+ strlen (value_array[i + 1]));
return length;
case XMP_TYPE_URI:
return (sizeof (" <%s:%s rdf:resource='%s' />\n") - 7
+ strlen (schema->prefix)
+ strlen (property->name)
+ strlen (value_array[0]));
case XMP_TYPE_RESOURCE_REF:
case XMP_TYPE_DIMENSIONS:
case XMP_TYPE_THUMBNAIL_ALT:
case XMP_TYPE_GPS_COORDINATE:
case XMP_TYPE_FLASH:
case XMP_TYPE_OECF_SFR:
case XMP_TYPE_CFA_PATTERN:
case XMP_TYPE_DEVICE_SETTINGS:
return 100; /* FIXME */
case XMP_TYPE_UNKNOWN:
return 0;
}
return 0;
g_string_append (buffer, " </rdf:Description>\n\n");
}
/**
* xmp_estimate_size:
* @xmp_model: An #XMPModel
*
* Return value: estimated size (upper bound) of the XMP (RDF) encoding of
* the given model.
**/
gssize
xmp_estimate_size (XMPModel *xmp_model)
static void
gen_property (GString *buffer,
const XMPSchema *schema,
const XMPProperty *property,
const gchar **value_array)
{
GtkTreeModel *model;
GtkTreeIter iter;
GtkTreeIter child;
gssize buffer_size;
const XMPSchema *schema;
model = xmp_model_get_tree_model (xmp_model);
buffer_size = 158 + 44 + 1;
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter))
{
do
{
buffer_size += size_schema (model, &iter, &schema);
if (gtk_tree_model_iter_children (model, &child, &iter))
{
do
{
buffer_size += size_property (model, &child, schema);
}
while (gtk_tree_model_iter_next (model, &child));
}
}
while (gtk_tree_model_iter_next (model, &iter));
}
return buffer_size;
}
static gint
gen_schema_start (GtkTreeModel *model,
GtkTreeIter *iter,
gchar *buffer,
const XMPSchema **schema_r)
{
gtk_tree_model_get (model, iter,
COL_XMP_TYPE_XREF, schema_r,
-1);
return sprintf (buffer, " <rdf:Description xmlns:%s='%s'>\n",
(*schema_r)->prefix, (*schema_r)->uri);
}
static gint
gen_schema_end (GtkTreeModel *model,
GtkTreeIter *iter,
gchar *buffer)
{
return sprintf (buffer, " </rdf:Description>\n\n");
}
static gint
gen_property (GtkTreeModel *model,
GtkTreeIter *iter,
gchar *buffer,
const XMPSchema *schema)
{
const XMPProperty *property;
const gchar **value_array;
gssize length;
gint i;
gint i;
const gchar *ns_prefix;
gtk_tree_model_get (model, iter,
COL_XMP_TYPE_XREF, &property,
COL_XMP_VALUE_RAW, &value_array,
-1);
g_return_val_if_fail (property->name != NULL, 0);
switch (property->type)
{
case XMP_TYPE_BOOLEAN:
......@@ -208,124 +64,162 @@ gen_property (GtkTreeModel *model,
case XMP_TYPE_MIME_TYPE:
case XMP_TYPE_TEXT:
case XMP_TYPE_RATIONAL:
return sprintf (buffer, " <%s:%s>%s</%s:%s>\n",
schema->prefix, property->name,
value_array[0],
schema->prefix, property->name);
g_string_append_printf (buffer, " <%s:%s>%s</%s:%s>\n",
schema->prefix, property->name,
value_array[0],
schema->prefix, property->name);
break;
case XMP_TYPE_LOCALE_BAG:
case XMP_TYPE_TEXT_BAG:
case XMP_TYPE_XPATH_BAG:
case XMP_TYPE_JOB_BAG:
length = sprintf (buffer, " <%s:%s>\n <rdf:Bag>\n",
schema->prefix, property->name);
g_string_append_printf (buffer, " <%s:%s>\n <rdf:Bag>\n",
schema->prefix, property->name);
for (i = 0; value_array[i] != NULL; i++)
length += sprintf (buffer + length, " <rdf:li>%s</rdf:li>\n",
value_array[i]);
length += sprintf (buffer + length, " </rdf:Bag>\n </%s:%s>\n",
schema->prefix, property->name);
return length;
g_string_append_printf (buffer, " <rdf:li>%s</rdf:li>\n",
value_array[i]);
g_string_append_printf (buffer, " </rdf:Bag>\n </%s:%s>\n",
schema->prefix, property->name);
break;
case XMP_TYPE_INTEGER_SEQ:
case XMP_TYPE_TEXT_SEQ:
case XMP_TYPE_RESOURCE_EVENT_SEQ:
case XMP_TYPE_RATIONAL_SEQ:
length = sprintf (buffer, " <%s:%s>\n <rdf:Seq>\n",
schema->prefix, property->name);
g_string_append_printf (buffer, " <%s:%s>\n <rdf:Seq>\n",
schema->prefix, property->name);
for (i = 0; value_array[i] != NULL; i++)
length += sprintf (buffer + length, " <rdf:li>%s</rdf:li>\n",
value_array[i]);
length += sprintf (buffer + length, " </rdf:Seq>\n </%s:%s>\n",