gnucash/lib/libgsf-1.12.3/gsf/gsf-outfile-msole.c
Joshua Sled 94e9fe5e6f Fold branches/goffice-update/ back into trunk/.
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@12096 57a11ea4-9604-0410-9ed3-97b8803252fd
2005-12-04 21:27:17 +00:00

796 lines
23 KiB
C

/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* gsf-outfile-msole.c:
*
* Copyright (C) 2002-2004 Jody Goldberg (jody@gnome.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2.1 of the GNU Lesser General Public
* License as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Outc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
* USA
*/
#include <gsf-config.h>
#include <gsf/gsf-outfile-impl.h>
#include <gsf/gsf-outfile-msole.h>
#include <gsf/gsf-impl-utils.h>
#include <gsf/gsf-msole-impl.h>
#include <gsf/gsf-utils.h>
#include <string.h>
#include <stdio.h>
static GObjectClass *parent_class;
static GsfOutputClass *gsf_output_class;
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "libgsf:msole"
typedef enum { MSOLE_DIR, MSOLE_SMALL_BLOCK, MSOLE_BIG_BLOCK } MSOleOutfileType;
/* The most common values */
#define OLE_DEFAULT_THRESHOLD 0x1000
#define OLE_DEFAULT_BB_SIZE (1 << OLE_DEFAULT_BB_SHIFT)
#define OLE_DEFAULT_SB_SIZE (1 << OLE_DEFAULT_SB_SHIFT)
struct _GsfOutfileMSOle {
GsfOutfile parent;
GsfOutput *sink;
GsfOutfileMSOle *root;
MSOleOutfileType type;
unsigned first_block;
unsigned blocks;
unsigned child_index;
struct {
unsigned shift;
unsigned size;
} bb, sb;
union {
struct {
GSList *children;
GPtrArray *root_order; /* only valid for the root */
} dir;
struct {
guint8 *buf;
} small_block;
struct {
size_t start_offset; /* in bytes */
} big_block;
} content;
unsigned char clsid[16]; /* 16 byte GUID used by some apps */
};
typedef GsfOutfileClass GsfOutfileMSOleClass;
static void
gsf_outfile_msole_finalize (GObject *obj)
{
GsfOutfileMSOle *ole = GSF_OUTFILE_MSOLE (obj);
GsfOutput *output = GSF_OUTPUT (obj);
if (!gsf_output_is_closed (output))
gsf_output_close (output);
if (ole->sink != NULL) {
g_object_unref (G_OBJECT (ole->sink));
ole->sink = NULL;
}
switch (ole->type) {
case MSOLE_DIR:
if (ole->content.dir.children != NULL) {
g_slist_free (ole->content.dir.children);
ole->content.dir.children = NULL;
}
if (ole->content.dir.root_order != NULL)
g_warning ("Finalizing a MSOle Outfile without closing it.");
break;
case MSOLE_SMALL_BLOCK:
if (ole->content.small_block.buf != NULL) {
g_free (ole->content.small_block.buf);
ole->content.small_block.buf = NULL;
}
break;
case MSOLE_BIG_BLOCK:
break;
default :
g_warning ("Unknown file type");
}
parent_class->finalize (obj);
}
static gboolean
gsf_outfile_msole_seek (GsfOutput *output, gsf_off_t offset,
GSeekType whence)
{
GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
switch (whence) {
case G_SEEK_SET : break;
case G_SEEK_CUR : offset += output->cur_offset; break;
case G_SEEK_END : offset += output->cur_size; break;
default :
break; /*checked in GsfOutput wrapper */
}
switch (ole->type) {
case MSOLE_DIR:
if (offset != 0) {
g_warning ("Attempt to seek a directory");
return FALSE;
}
return TRUE;
case MSOLE_SMALL_BLOCK:
/* it is ok to seek past the big block threshold
* we don't convert until they _write_ something
*/
return TRUE;
case MSOLE_BIG_BLOCK:
return gsf_output_seek (ole->sink,
(gsf_off_t)(ole->content.big_block.start_offset + offset),
G_SEEK_SET);
default :
return FALSE;
}
return FALSE;
}
/* Globals to support variable OLE sector size. */
/* 512 and 4096 bytes are the only known values for sector size on */
/* Win2k/XP platforms. Attempts to create OLE files on Win2k/XP with */
/* other values using StgCreateStorageEx() fail with invalid parameter. */
/* This code has been tested with 128,256,512,4096,8192 sizes for */
/* libgsf read/write. Interoperability with MS OLE32.DLL has been */
/* tested with 512 and 4096 block size for filesizes up to 2GB. */
#define ZERO_PAD_BUF_SIZE 4096
/* static objects are zero-initialized as per C/C++ standards */
static guint8 const zero_buf [ZERO_PAD_BUF_SIZE];
/* Calculate the block of the current offset in the file. A useful idiom is to
* pad_zero to move to the start of the next block, then get the block number.
* This avoids fence post type problems with partial blocks. */
static inline guint32
ole_cur_block (GsfOutfileMSOle const *ole)
{
return (gsf_output_tell (ole->sink) - OLE_HEADER_SIZE) >> ole->bb.shift;
}
static inline unsigned
ole_bytes_left_in_block (GsfOutfileMSOle *ole)
{
/* blocks are multiples of bb.size (the header is padded out to bb.size) */
unsigned r = gsf_output_tell (ole->sink) % ole->bb.size;
return (r != 0) ? (ole->bb.size - r) : 0;
}
static void
ole_pad_zero (GsfOutfileMSOle *ole)
{
/* no need to bounds check. len will always be less than bb.size, and
* we already check that zero_buf is big enough at creation */
unsigned len = ole_bytes_left_in_block (ole);
if (len > 0)
gsf_output_write (ole->sink, len, zero_buf);
}
/* Utility routine to generate a BAT for a file known to be sequential and
* continuous. */
static void
ole_write_bat (GsfOutput *sink, guint32 block, unsigned blocks)
{
guint8 buf [BAT_INDEX_SIZE];
/* FIXME FIXME FIXME optimize this to dump a buffer in 1 step */
while (blocks-- > 1) {
block++;
GSF_LE_SET_GUINT32 (buf, block);
gsf_output_write (sink, BAT_INDEX_SIZE, buf);
}
GSF_LE_SET_GUINT32 (buf, BAT_MAGIC_END_OF_CHAIN);
gsf_output_write (sink, BAT_INDEX_SIZE, buf);
}
static void
ole_write_const (GsfOutput *sink, guint32 value, unsigned n)
{
guint8 buf [BAT_INDEX_SIZE];
GSF_LE_SET_GUINT32 (buf, value);
while (n-- > 0)
gsf_output_write (sink, BAT_INDEX_SIZE, buf);
}
static void
ole_pad_bat_unused (GsfOutfileMSOle *ole, unsigned residual)
{
ole_write_const (ole->sink, BAT_MAGIC_UNUSED,
(ole_bytes_left_in_block (ole) / BAT_INDEX_SIZE) - residual);
}
/* write the metadata (dirents, small block, xbats) and close the sink */
static gboolean
gsf_outfile_msole_close_root (GsfOutfileMSOle *ole)
{
GsfOutfile *tmp;
guint8 buf [OLE_HEADER_SIZE];
guint32 sbat_start, num_sbat, sb_data_start, sb_data_size, sb_data_blocks;
guint32 bat_start, num_bat, dirent_start, num_dirent_blocks, next, child_index;
unsigned i, j, blocks, num_xbat, xbat_pos;
gsf_off_t data_size;
unsigned metabat_size = ole->bb.size / BAT_INDEX_SIZE - 1;
GPtrArray *elem = ole->root->content.dir.root_order;
/* write small block data */
blocks = 0;
sb_data_start = ole_cur_block (ole);
data_size = gsf_output_tell (ole->sink);
for (i = 0 ; i < elem->len ; i++) {
GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
if (child->type == MSOLE_SMALL_BLOCK) {
gsf_off_t size = gsf_output_size (GSF_OUTPUT (child));
if (size > 0) {
child->blocks = ((size - 1) >> ole->sb.shift) + 1;
gsf_output_write (ole->sink,
child->blocks << ole->sb.shift,
child->content.small_block.buf);
child->first_block = blocks;
blocks += child->blocks;
} else {
child->blocks = 0;
child->first_block = BAT_MAGIC_END_OF_CHAIN;
}
}
}
data_size = gsf_output_tell (ole->sink) - data_size;
sb_data_size = data_size;
if ((gsf_off_t) sb_data_size != data_size) {
/* Check for overflow */
g_warning ("File too big");
return FALSE;
}
ole_pad_zero (ole);
sb_data_blocks = ole_cur_block (ole) - sb_data_start;
/* write small block BAT (the meta bat is in a file) */
sbat_start = ole_cur_block (ole);
for (i = 0 ; i < elem->len ; i++) {
GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
if (child->type == MSOLE_SMALL_BLOCK && child->blocks > 0)
ole_write_bat (ole->sink, child->first_block, child->blocks);
}
ole_pad_bat_unused (ole, 0);
num_sbat = ole_cur_block (ole) - sbat_start;
/* write dirents */
dirent_start = ole_cur_block (ole);
for (i = 0 ; i < elem->len ; i++) {
GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
glong j, name_len = 0;
memset (buf, 0, DIRENT_SIZE);
/* Hard code 'Root Entry' for the root */
if (i == 0 || gsf_output_name (GSF_OUTPUT (child)) != NULL) {
char const *name = (i == 0)
? "Root Entry" : gsf_output_name (GSF_OUTPUT (child));
gunichar2 *name_utf16 = g_utf8_to_utf16 (name,
-1, NULL, &name_len, NULL);
if (name_len >= DIRENT_MAX_NAME_SIZE)
name_len = DIRENT_MAX_NAME_SIZE-1;
/* be wary about endianness */
for (j = 0 ; j < name_len ; j++)
GSF_LE_SET_GUINT16 (buf + j*2, name_utf16 [j]);
g_free (name_utf16);
name_len++;
}
GSF_LE_SET_GUINT16 (buf + DIRENT_NAME_LEN, name_len*2);
if (child->root == child) {
GSF_LE_SET_GUINT8 (buf + DIRENT_TYPE, DIRENT_TYPE_ROOTDIR);
GSF_LE_SET_GUINT32 (buf + DIRENT_FIRSTBLOCK,
(sb_data_size > 0) ? sb_data_start : BAT_MAGIC_END_OF_CHAIN);
GSF_LE_SET_GUINT32 (buf + DIRENT_FILE_SIZE, sb_data_size);
memcpy (buf + DIRENT_CLSID, child->clsid, sizeof (child->clsid));
} else if (child->type == MSOLE_DIR) {
GSF_LE_SET_GUINT8 (buf + DIRENT_TYPE, DIRENT_TYPE_DIR);
GSF_LE_SET_GUINT32 (buf + DIRENT_FIRSTBLOCK, BAT_MAGIC_END_OF_CHAIN);
GSF_LE_SET_GUINT32 (buf + DIRENT_FILE_SIZE, 0);
/* write the class id */
memcpy (buf + DIRENT_CLSID, child->clsid, sizeof (child->clsid));
} else {
guint32 size = child->parent.parent.cur_size;
if ((gsf_off_t) size != child->parent.parent.cur_size)
g_warning ("File too big");
GSF_LE_SET_GUINT8 (buf + DIRENT_TYPE, DIRENT_TYPE_FILE);
GSF_LE_SET_GUINT32 (buf + DIRENT_FIRSTBLOCK, child->first_block);
GSF_LE_SET_GUINT32 (buf + DIRENT_FILE_SIZE, size);
}
/* make everything black (red == 0) */
GSF_LE_SET_GUINT8 (buf + DIRENT_COLOUR, 1);
tmp = gsf_output_container (GSF_OUTPUT (child));
next = DIRENT_MAGIC_END;
if (child->root != child && tmp != NULL) {
GSList *ptr = GSF_OUTFILE_MSOLE (tmp)->content.dir.children;
for (; ptr != NULL ; ptr = ptr->next)
if (ptr->data == child) {
if (ptr->next != NULL) {
GsfOutfileMSOle *sibling = ptr->next->data;
next = sibling->child_index;
}
break;
}
}
/* make linked list rather than tree, only use next */
GSF_LE_SET_GUINT32 (buf + DIRENT_PREV, DIRENT_MAGIC_END);
GSF_LE_SET_GUINT32 (buf + DIRENT_NEXT, next);
child_index = DIRENT_MAGIC_END;
if (child->type == MSOLE_DIR && child->content.dir.children != NULL) {
GsfOutfileMSOle *first = child->content.dir.children->data;
child_index = first->child_index;
}
GSF_LE_SET_GUINT32 (buf + DIRENT_CHILD, child_index);
gsf_output_write (ole->sink, DIRENT_SIZE, buf);
}
ole_pad_zero (ole);
num_dirent_blocks = ole_cur_block (ole) - dirent_start;
/* write BAT */
bat_start = ole_cur_block (ole);
for (i = 0 ; i < elem->len ; i++) {
GsfOutfileMSOle *child = g_ptr_array_index (elem, i);
if (child->type == MSOLE_BIG_BLOCK)
ole_write_bat (ole->sink, child->first_block, child->blocks);
}
if (sb_data_blocks > 0)
ole_write_bat (ole->sink, sb_data_start, sb_data_blocks);
if (num_sbat > 0)
ole_write_bat (ole->sink, sbat_start, num_sbat);
ole_write_bat (ole->sink, dirent_start, num_dirent_blocks);
/* List the BAT and meta-BAT blocks in the BAT. Doing this may
* increase the size of the bat and hence the metabat, so be
* prepared to iterate.
*/
num_bat = 0;
num_xbat = 0;
recalc_bat_bat :
i = ((ole->sink->cur_size
+ BAT_INDEX_SIZE * (num_bat + num_xbat)
- OLE_HEADER_SIZE - 1) >> ole->bb.shift) + 1;
i -= bat_start;
if (num_bat != i) {
num_bat = i;
goto recalc_bat_bat;
}
i = 0;
if (num_bat > OLE_HEADER_METABAT_SIZE)
i = 1 + ((num_bat - OLE_HEADER_METABAT_SIZE - 1)
/ metabat_size);
if (num_xbat != i) {
num_xbat = i;
goto recalc_bat_bat;
}
ole_write_const (ole->sink, BAT_MAGIC_BAT, num_bat);
ole_write_const (ole->sink, BAT_MAGIC_METABAT, num_xbat);
ole_pad_bat_unused (ole, 0);
if (num_xbat > 0) {
xbat_pos = ole_cur_block (ole);
blocks = OLE_HEADER_METABAT_SIZE;
} else {
xbat_pos = BAT_MAGIC_END_OF_CHAIN;
blocks = num_bat;
}
/* fix up the header */
if (ole->bb.size == 4096) {
/* set _cSectDir for 4k sector files */
GSF_LE_SET_GUINT32 (buf, num_dirent_blocks);
gsf_output_seek (ole->sink,
(gsf_off_t) OLE_HEADER_CSECTDIR, G_SEEK_SET);
gsf_output_write (ole->sink, 4, buf);
}
GSF_LE_SET_GUINT32 (buf, num_bat);
GSF_LE_SET_GUINT32 (buf+4, dirent_start);
gsf_output_seek (ole->sink,
(gsf_off_t) OLE_HEADER_NUM_BAT, G_SEEK_SET);
gsf_output_write (ole->sink, 8, buf);
GSF_LE_SET_GUINT32 (buf+0x0,
(num_sbat > 0) ? sbat_start : BAT_MAGIC_END_OF_CHAIN);
GSF_LE_SET_GUINT32 (buf+0x4, num_sbat);
GSF_LE_SET_GUINT32 (buf+0x8, xbat_pos);
GSF_LE_SET_GUINT32 (buf+0xc, num_xbat);
gsf_output_seek (ole->sink, (gsf_off_t) OLE_HEADER_SBAT_START,
G_SEEK_SET);
gsf_output_write (ole->sink, 0x10, buf);
/* write initial Meta-BAT */
for (i = 0 ; i < blocks ; i++) {
GSF_LE_SET_GUINT32 (buf, bat_start + i);
gsf_output_write (ole->sink, BAT_INDEX_SIZE, buf);
}
/* write extended Meta-BAT */
if (num_xbat > 0) {
gsf_output_seek (ole->sink, 0, G_SEEK_END);
for (i = 0 ; i++ < num_xbat ; ) {
bat_start += blocks;
num_bat -= blocks;
blocks = (num_bat > metabat_size) ? metabat_size : num_bat;
for (j = 0 ; j < blocks ; j++) {
GSF_LE_SET_GUINT32 (buf, bat_start + j);
gsf_output_write (ole->sink, BAT_INDEX_SIZE, buf);
}
if (i == num_xbat) {
ole_pad_bat_unused (ole, 1);
xbat_pos = BAT_MAGIC_END_OF_CHAIN;
} else
xbat_pos++;
GSF_LE_SET_GUINT32 (buf, xbat_pos);
gsf_output_write (ole->sink, BAT_INDEX_SIZE, buf);
}
}
/* free the children */
for (i = 0 ; i < elem->len ; i++)
g_object_unref (G_OBJECT (g_ptr_array_index (elem, i)));
g_ptr_array_free (elem, TRUE);
ole->content.dir.root_order = NULL;
return gsf_output_close (ole->sink);
}
static gboolean
gsf_outfile_msole_close (GsfOutput *output)
{
GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
if (gsf_output_container (output) == NULL) /* The root dir */
return gsf_outfile_msole_close_root (ole);
if (ole->type == MSOLE_BIG_BLOCK) {
gsf_outfile_msole_seek (output, 0, G_SEEK_END);
ole_pad_zero (ole);
ole->blocks = ole_cur_block (ole) - ole->first_block;
return gsf_output_unwrap (G_OBJECT (output), ole->sink);
}
return TRUE;
}
static gboolean
gsf_outfile_msole_write (GsfOutput *output,
size_t num_bytes, guint8 const *data)
{
GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
size_t wsize;
g_return_val_if_fail (ole->type != MSOLE_DIR, FALSE);
if (ole->type == MSOLE_SMALL_BLOCK) {
gboolean ok;
guint8 *buf;
gsf_off_t start_offset;
if ((output->cur_offset + num_bytes) < OLE_DEFAULT_THRESHOLD) {
memcpy (ole->content.small_block.buf + output->cur_offset,
data, num_bytes);
return TRUE;
}
ok = gsf_output_wrap (G_OBJECT (output), ole->sink);
if (!ok)
return FALSE;
buf = ole->content.small_block.buf;
ole->content.small_block.buf = NULL;
start_offset = gsf_output_tell (ole->sink);
ole->content.big_block.start_offset = start_offset;
if ((gsf_off_t) ole->content.big_block.start_offset
!= start_offset) {
/* Check for overflow */
g_warning ("File too big");
return FALSE;
}
ole->first_block = ole_cur_block (ole);
ole->type = MSOLE_BIG_BLOCK;
wsize = output->cur_size;
if ((gsf_off_t) wsize != output->cur_size) {
/* Check for overflow */
g_warning ("File too big");
return FALSE;
}
gsf_output_write (ole->sink, wsize, buf);
g_free (buf);
}
g_return_val_if_fail (ole->type == MSOLE_BIG_BLOCK, FALSE);
gsf_output_write (ole->sink, num_bytes, data);
return TRUE;
}
static gsf_off_t gsf_outfile_msole_vprintf (GsfOutput *output,
char const *format, va_list args) G_GNUC_PRINTF (2, 0);
static gsf_off_t
gsf_outfile_msole_vprintf (GsfOutput *output, char const *format, va_list args)
{
GsfOutfileMSOle *ole = (GsfOutfileMSOle *)output;
/* An optimization. */
if (ole->type == MSOLE_BIG_BLOCK)
return gsf_output_vprintf (ole->sink, format, args);
/* In other cases, use the gsf_output_real_vprintf fallback method.
* (This eventually calls gsf_outfile_msole_write, which will also
* check that ole->type != MSOLE_DIR.)
*/
return gsf_output_class->Vprintf (output, format, args);
}
static void
ole_register_child (GsfOutfileMSOle *root, GsfOutfileMSOle *child)
{
child->root = root;
g_object_ref (G_OBJECT (child));
child->child_index = root->content.dir.root_order->len;
g_ptr_array_add (root->content.dir.root_order, child);
}
static gint
ole_name_cmp (GsfOutfileMSOle const *a, GsfOutfileMSOle const *b)
{
/* According to the docs length is more important than lexical order */
char const *a_name = gsf_output_name ((GsfOutput const *)a);
char const *b_name = gsf_output_name ((GsfOutput const *)b);
/* be anal */
if (a_name == NULL)
return (b_name == NULL) ? 0 : -1;
else if (b_name == NULL)
return 1;
else {
unsigned a_len = g_utf8_strlen (a_name, -1);
unsigned b_len = g_utf8_strlen (b_name, -1);
if (a_len != b_len)
return a_len - b_len;
return g_utf8_collate (a_name, b_name);
}
}
static void
gsf_outfile_msole_set_block_shift (GsfOutfileMSOle *ole,
unsigned bb_shift, unsigned sb_shift)
{
ole->bb.shift = bb_shift;
ole->bb.size = (1 << ole->bb.shift);
ole->sb.shift = sb_shift;
ole->sb.size = (1 << ole->sb.shift);
}
static GsfOutput *
gsf_outfile_msole_new_child (GsfOutfile *parent,
char const *name, gboolean is_dir,
char const *first_property_name, va_list args)
{
GsfOutfileMSOle *ole_parent = (GsfOutfileMSOle *)parent;
GsfOutfileMSOle *child;
g_return_val_if_fail (ole_parent != NULL, NULL);
g_return_val_if_fail (ole_parent->type == MSOLE_DIR, NULL);
child = (GsfOutfileMSOle *)g_object_new_valist (
GSF_OUTFILE_MSOLE_TYPE, first_property_name, args);
if (is_dir) {
child->type = MSOLE_DIR;
child->content.dir.children = NULL;
} else {
/* start as small block */
child->type = MSOLE_SMALL_BLOCK;
child->content.small_block.buf = g_new0 (guint8, OLE_DEFAULT_THRESHOLD);
}
g_object_ref (G_OBJECT (ole_parent->sink));
child->sink = ole_parent->sink;
child->root = ole_parent->root;
gsf_outfile_msole_set_block_shift (child,
ole_parent->bb.shift, ole_parent->sb.shift);
gsf_output_set_name (GSF_OUTPUT (child), name);
gsf_output_set_container (GSF_OUTPUT (child), parent);
ole_parent->content.dir.children = g_slist_insert_sorted (
ole_parent->content.dir.children, child,
(GCompareFunc)ole_name_cmp);
ole_register_child (ole_parent->root, child);
return GSF_OUTPUT (child);
}
static void
gsf_outfile_msole_init (GObject *obj)
{
GsfOutfileMSOle *ole = GSF_OUTFILE_MSOLE (obj);
ole->sink = NULL;
ole->root = NULL;
ole->type = MSOLE_DIR;
gsf_outfile_msole_set_block_shift (ole,
OLE_DEFAULT_BB_SHIFT, OLE_DEFAULT_SB_SHIFT);
ole->content.dir.children = NULL;
ole->content.dir.root_order = NULL;
memset (ole->clsid, 0, sizeof (ole->clsid));
}
static void
gsf_outfile_msole_class_init (GObjectClass *gobject_class)
{
GsfOutputClass *output_class = GSF_OUTPUT_CLASS (gobject_class);
GsfOutfileClass *outfile_class = GSF_OUTFILE_CLASS (gobject_class);
gobject_class->finalize = gsf_outfile_msole_finalize;
output_class->Close = gsf_outfile_msole_close;
output_class->Seek = gsf_outfile_msole_seek;
output_class->Write = gsf_outfile_msole_write;
output_class->Vprintf = gsf_outfile_msole_vprintf;
outfile_class->new_child = gsf_outfile_msole_new_child;
parent_class = g_type_class_peek_parent (gobject_class);
gsf_output_class = g_type_class_peek (GSF_OUTPUT_TYPE);
}
GSF_CLASS (GsfOutfileMSOle, gsf_outfile_msole,
gsf_outfile_msole_class_init, gsf_outfile_msole_init,
GSF_OUTFILE_TYPE)
/* returns the number of times 1 must be shifted left to reach value */
static unsigned
compute_shift (unsigned value)
{
unsigned i = 0;
while ((value >> i) > 1)
i++;
return i;
}
/**
* gsf_outfile_msole_new_full :
* @sink : a #GsfOutput to hold the OLE2 file.
* @bb_size : size of large blocks.
* @sb_size : size of small blocks.
*
* Creates the root directory of an MS OLE file and manages the addition of
* children.
*
* NOTE : adds a reference to @sink
*
* Returns the new ole file handler
**/
GsfOutfile *
gsf_outfile_msole_new_full (GsfOutput *sink, guint bb_size, guint sb_size)
{
static guint8 const default_header [] = {
/* 0x00 */ 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1,
/* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x18 */ 0x3e, 0x00, 0x03, 0x00, 0xfe, 0xff, 0x09, 0x00,
/* 0x20 */ 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0x28 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* 0x30 */ 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
/* 0x38 */ 0x00, 0x10, 0x00, 0x00 /* 0x3c-0x4b: filled on close */
};
guint8 *buf;
GsfOutfileMSOle *ole;
g_return_val_if_fail (GSF_IS_OUTPUT (sink), NULL);
ole = g_object_new (GSF_OUTFILE_MSOLE_TYPE, NULL);
g_object_ref (G_OBJECT (sink));
ole->sink = sink;
ole->type = MSOLE_DIR;
ole->content.dir.root_order = g_ptr_array_new ();
ole_register_child (ole, ole);
gsf_outfile_msole_set_block_shift (ole,
compute_shift (bb_size), compute_shift (sb_size));
if (ole->bb.size != bb_size ||
ole->sb.size != sb_size ||
bb_size <= sb_size ||
bb_size < DIRENT_SIZE ||
sb_size < 8 ||
ZERO_PAD_BUF_SIZE < ole->bb.size) {
if (ZERO_PAD_BUF_SIZE < ole->bb.size)
g_warning ("Block size is too big, failing back to defaults.");
else
g_warning ("Incorrect block sizes, failing back to defaults.");
gsf_outfile_msole_set_block_shift (ole,
OLE_DEFAULT_BB_SHIFT, OLE_DEFAULT_SB_SHIFT);
}
/* The names are the same */
gsf_output_set_name (GSF_OUTPUT (ole), gsf_output_name (sink));
gsf_output_set_container (GSF_OUTPUT (ole), NULL);
/* build the header */
buf = g_new (guint8, OLE_HEADER_SIZE);
memcpy (buf, default_header, sizeof (default_header));
memset (buf + sizeof (default_header), 0xff,
OLE_HEADER_SIZE - sizeof (default_header));
GSF_LE_SET_GUINT16 (buf + OLE_HEADER_BB_SHIFT, ole->bb.shift);
GSF_LE_SET_GUINT16 (buf + OLE_HEADER_SB_SHIFT, ole->sb.shift);
/* 4k sector OLE files seen in the wild have version 4 */
if (ole->bb.size == 4096)
GSF_LE_SET_GUINT16 (buf + OLE_HEADER_MAJOR_VER, 4);
gsf_output_write (sink, OLE_HEADER_SIZE, buf);
g_free (buf);
/* header must be padded out to bb.size with zeros */
ole_pad_zero(ole);
return GSF_OUTFILE (ole);
}
/**
* gsf_outfile_msole_new :
* @sink : a #GsfOutput to hold the OLE2 file
*
* Creates the root directory of an MS OLE file and manages the addition of
* children.
*
* NOTE : adds a reference to @sink
*
* Returns the new ole file handler
**/
GsfOutfile *
gsf_outfile_msole_new (GsfOutput *sink)
{
return gsf_outfile_msole_new_full (sink,
OLE_DEFAULT_BB_SIZE, OLE_DEFAULT_SB_SIZE);
}
/**
* gsf_outfile_msole_set_class_id :
* @ole: a #GsfOutfileMSOle
* @clsid: 16 byte identifier (often a GUID in MS Windows apps)
*
* Write @clsid to the directory associated with @ole.
*
* Returns TRUE on success.
**/
gboolean
gsf_outfile_msole_set_class_id (GsfOutfileMSOle *ole, guint8 const *clsid)
{
g_return_val_if_fail (ole != NULL && ole->type == MSOLE_DIR, FALSE);
memcpy (ole->clsid, clsid, sizeof (ole->clsid));
return TRUE;
}