/* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * gsf-infile-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, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 * USA */ /* Lots of useful information in * http://www.aafassociation.org/html/specs/aafcontainerspec-v1.0.1.pdf */ #include #include #include #include #include #include #include #include #include #undef G_LOG_DOMAIN #define G_LOG_DOMAIN "libgsf:msole" static GObjectClass *parent_class; typedef struct { guint32 *block; guint32 num_blocks; } MSOleBAT; typedef struct { char *name; char *collation_name; int index; size_t size; gboolean use_sb; guint32 first_block; gboolean is_directory; GList *children; unsigned char clsid[16]; /* 16 byte GUID used by some apps */ } MSOleDirent; typedef struct { struct { MSOleBAT bat; unsigned shift; unsigned filter; size_t size; } bb, sb; gsf_off_t max_block; guint32 threshold; /* transition between small and big blocks */ guint32 sbat_start, num_sbat; MSOleDirent *root_dir; GsfInput *sb_file; int ref_count; } MSOleInfo; struct _GsfInfileMSOle { GsfInfile parent; GsfInput *input; MSOleInfo *info; MSOleDirent *dirent; MSOleBAT bat; gsf_off_t cur_block; struct { guint8 *buf; size_t buf_size; } stream; }; typedef struct { GsfInfileClass parent_class; } GsfInfileMSOleClass; #define GSF_INFILE_MSOLE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GSF_INFILE_MSOLE_TYPE, GsfInfileMSOleClass)) #define GSF_IS_INFILE_MSOLE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), GSF_INFILE_MSOLE_TYPE)) /* utility macros */ #define OLE_BIG_BLOCK(index, ole) ((index) >> ole->info->bb.shift) static GsfInput *gsf_infile_msole_new_child (GsfInfileMSOle *parent, MSOleDirent *dirent, GError **err); static void ole_info_unref (MSOleInfo *info); /** * ole_get_block : * @ole: the infile * @block: * @buffer: optionally NULL * * Read a block of data from the underlying input. * Be really anal. * * Returns: pointer to the buffer or NULL if there is an error or 0 bytes are * requested. **/ static guint8 const * ole_get_block (GsfInfileMSOle const *ole, guint32 block, guint8 *buffer) { g_return_val_if_fail (block < ole->info->max_block, NULL); /* OLE_HEADER_SIZE is fixed at 512, but the sector containing the * header is padded out to bb.size (sector size) when bb.size > 512. */ if (gsf_input_seek (ole->input, (gsf_off_t)(MAX (OLE_HEADER_SIZE, ole->info->bb.size) + (block << ole->info->bb.shift)), G_SEEK_SET) < 0) return NULL; return gsf_input_read (ole->input, ole->info->bb.size, buffer); } /** * ole_make_bat : * @metabat: a meta bat to connect to the raw blocks (small or large) * @size_guess: An optional guess as to how many blocks are in the file * @block: The first block in the list. * @res: where to store the result. * * Walk the linked list of the supplied block allocation table and build up a * table for the list starting in @block. * * Returns: TRUE on error. */ static gboolean ole_make_bat (MSOleBAT const *metabat, size_t size_guess, guint32 block, MSOleBAT *res) { /* NOTE : Only use size as a suggestion, sometimes it is wrong */ GArray *bat = g_array_sized_new (FALSE, FALSE, sizeof (guint32), size_guess); guint8 *used = (guint8*)g_alloca (1 + metabat->num_blocks / 8); memset (used, 0, 1 + metabat->num_blocks / 8); if (block < metabat->num_blocks) do { /* Catch cycles in the bat list */ g_return_val_if_fail (0 == (used[block/8] & (1 << (block & 0x7))), TRUE); used[block/8] |= 1 << (block & 0x7); g_array_append_val (bat, block); block = metabat->block [block]; } while (block < metabat->num_blocks); res->block = NULL; res->num_blocks = bat->len; res->block = (guint32 *) (gpointer) g_array_free (bat, FALSE); if (block != BAT_MAGIC_END_OF_CHAIN) { g_warning ("This OLE2 file is invalid.\n" "The Block Allocation Table for one of the streams had %x instead of a terminator (%x).\n" "We might still be able to extract some data, but you'll want to check the file.", block, BAT_MAGIC_END_OF_CHAIN); } return FALSE; } static void ols_bat_release (MSOleBAT *bat) { if (bat->block != NULL) { bat->num_blocks = 0; g_free (bat->block); bat->block = NULL; } } /** * ole_info_read_metabat : * @ole: * @bats: * * A small utility routine to read a set of references to bat blocks * either from the OLE header, or a meta-bat block. * * Returns: a pointer to the element after the last position filled. **/ static guint32 * ole_info_read_metabat (GsfInfileMSOle *ole, guint32 *bats, guint32 max_bat, guint32 const *metabat, guint32 const *metabat_end) { guint8 const *bat, *end; for (; metabat < metabat_end; metabat++) { if (*metabat != BAT_MAGIC_UNUSED) { bat = ole_get_block (ole, *metabat, NULL); if (bat == NULL) return NULL; end = bat + ole->info->bb.size; for ( ; bat < end ; bat += BAT_INDEX_SIZE, bats++) { *bats = GSF_LE_GET_GUINT32 (bat); g_return_val_if_fail (*bats < max_bat || *bats >= BAT_MAGIC_METABAT, NULL); } } else { /* Looks like something in the wild sometimes creates * 'unused' entries in the metabat. Let's assume that * corresponds to lots of unused blocks * http://bugzilla.gnome.org/show_bug.cgi?id=336858 */ unsigned i = ole->info->bb.size / BAT_INDEX_SIZE; while (i-- > 0) *bats++ = BAT_MAGIC_UNUSED; } } return bats; } /** * gsf_ole_get_guint32s : * @dst: * @src: * @num_bytes: * * Copy some some raw data into an array of guint32. **/ static void gsf_ole_get_guint32s (guint32 *dst, guint8 const *src, int num_bytes) { for (; (num_bytes -= BAT_INDEX_SIZE) >= 0 ; src += BAT_INDEX_SIZE) *dst++ = GSF_LE_GET_GUINT32 (src); } static GsfInput * ole_info_get_sb_file (GsfInfileMSOle *parent) { MSOleBAT meta_sbat; if (parent->info->sb_file != NULL) return parent->info->sb_file; parent->info->sb_file = gsf_infile_msole_new_child (parent, parent->info->root_dir, NULL); if (!parent->info->sb_file) return NULL; /* avoid creating a circular reference */ ole_info_unref (((GsfInfileMSOle *)parent->info->sb_file)->info); g_return_val_if_fail (parent->info->sb.bat.block == NULL, NULL); if (ole_make_bat (&parent->info->bb.bat, parent->info->num_sbat, parent->info->sbat_start, &meta_sbat)) return NULL; parent->info->sb.bat.num_blocks = meta_sbat.num_blocks * (parent->info->bb.size / BAT_INDEX_SIZE); parent->info->sb.bat.block = g_new0 (guint32, parent->info->sb.bat.num_blocks); ole_info_read_metabat (parent, parent->info->sb.bat.block, parent->info->sb.bat.num_blocks, meta_sbat.block, meta_sbat.block + meta_sbat.num_blocks); ols_bat_release (&meta_sbat); return parent->info->sb_file; } static gint ole_dirent_cmp (MSOleDirent const *a, MSOleDirent const *b) { g_return_val_if_fail (a, 0); g_return_val_if_fail (b, 0); g_return_val_if_fail (a->collation_name, 0); g_return_val_if_fail (b->collation_name, 0); return strcmp (b->collation_name, a->collation_name); } /** * ole_dirent_new : * @ole: * @entry: * @parent: optional * * Parse dirent number @entry and recursively handle its siblings and children. * * Returns: The dirent **/ static MSOleDirent * ole_dirent_new (GsfInfileMSOle *ole, guint32 entry, MSOleDirent *parent) { MSOleDirent *dirent; guint32 block, next, prev, child, size; guint8 const *data; guint8 type; guint16 name_len; if (entry >= DIRENT_MAGIC_END) return NULL; block = OLE_BIG_BLOCK (entry * DIRENT_SIZE, ole); g_return_val_if_fail (block < ole->bat.num_blocks, NULL); data = ole_get_block (ole, ole->bat.block [block], NULL); if (data == NULL) return NULL; data += (DIRENT_SIZE * entry) % ole->info->bb.size; type = GSF_LE_GET_GUINT8 (data + DIRENT_TYPE); if (type != DIRENT_TYPE_DIR && type != DIRENT_TYPE_FILE && type != DIRENT_TYPE_ROOTDIR) { g_warning ("Unknown stream type 0x%x", type); return NULL; } if (!parent && type != DIRENT_TYPE_ROOTDIR) { /* See bug 346118. */ g_warning ("Root directory is not marked as such."); type = DIRENT_TYPE_ROOTDIR; } /* It looks like directory (and root directory) sizes are sometimes bogus */ size = GSF_LE_GET_GUINT32 (data + DIRENT_FILE_SIZE); g_return_val_if_fail (type == DIRENT_TYPE_DIR || type == DIRENT_TYPE_ROOTDIR || size <= (guint32)ole->input->size, NULL); dirent = g_new0 (MSOleDirent, 1); dirent->index = entry; dirent->size = size; /* Store the class id which is 16 byte identifier used by some apps */ memcpy(dirent->clsid, data + DIRENT_CLSID, sizeof(dirent->clsid)); /* root dir is always big block */ dirent->use_sb = parent && (size < ole->info->threshold); dirent->first_block = (GSF_LE_GET_GUINT32 (data + DIRENT_FIRSTBLOCK)); dirent->is_directory = (type != DIRENT_TYPE_FILE); dirent->children = NULL; prev = GSF_LE_GET_GUINT32 (data + DIRENT_PREV); next = GSF_LE_GET_GUINT32 (data + DIRENT_NEXT); child = GSF_LE_GET_GUINT32 (data + DIRENT_CHILD); name_len = GSF_LE_GET_GUINT16 (data + DIRENT_NAME_LEN); dirent->name = NULL; if (0 < name_len && name_len <= DIRENT_MAX_NAME_SIZE) { gunichar2 uni_name [DIRENT_MAX_NAME_SIZE+1]; gchar const *end; int i; /* !#%!@$#^ * Sometimes, rarely, people store the stream name as ascii * rather than utf16. Do a validation first just in case. */ if (!g_utf8_validate (data, -1, &end) || ((guint8 const *)end - data + 1) != name_len) { /* be wary about endianness */ for (i = 0 ; i < name_len ; i += 2) uni_name [i/2] = GSF_LE_GET_GUINT16 (data + i); uni_name [i/2] = 0; dirent->name = g_utf16_to_utf8 (uni_name, -1, NULL, NULL, NULL); } else dirent->name = g_strndup ((gchar *)data, (gsize)((guint8 const *)end - data + 1)); } /* be really anal in the face of screwups */ if (dirent->name == NULL) dirent->name = g_strdup (""); dirent->collation_name = g_utf8_collate_key (dirent->name, -1); #if 0 printf ("%c '%s' :\tsize = %d\tfirst_block = 0x%x\n", dirent->is_directory ? 'd' : ' ', dirent->name, dirent->size, dirent->first_block); #endif if (parent != NULL) parent->children = g_list_insert_sorted (parent->children, dirent, (GCompareFunc)ole_dirent_cmp); /* NOTE : These links are a tree, not a linked list */ if (prev == entry) { g_warning ("Invalid OLE file with a cycle in its directory tree"); } else ole_dirent_new (ole, prev, parent); if (next == entry) { g_warning ("Invalid OLE file with a cycle in its directory tree"); } else ole_dirent_new (ole, next, parent); if (dirent->is_directory) ole_dirent_new (ole, child, dirent); else if (child != DIRENT_MAGIC_END) g_warning ("A non directory stream with children ?"); return dirent; } static void ole_dirent_free (MSOleDirent *dirent) { GList *tmp; g_return_if_fail (dirent != NULL); g_free (dirent->name); g_free (dirent->collation_name); for (tmp = dirent->children; tmp; tmp = tmp->next) ole_dirent_free ((MSOleDirent *)tmp->data); g_list_free (dirent->children); g_free (dirent); } /*****************************************************************************/ static void ole_info_unref (MSOleInfo *info) { if (info->ref_count-- != 1) return; ols_bat_release (&info->bb.bat); ols_bat_release (&info->sb.bat); if (info->root_dir != NULL) { ole_dirent_free (info->root_dir); info->root_dir = NULL; } if (info->sb_file != NULL) { g_object_unref (G_OBJECT (info->sb_file)); info->sb_file = NULL; } g_free (info); } static MSOleInfo * ole_info_ref (MSOleInfo *info) { info->ref_count++; return info; } /** * ole_dup: * @src: * * Utility routine to _partially_ replicate a file. It does NOT copy the bat * blocks, or init the dirent. * * Returns: the partial duplicate. **/ static GsfInfileMSOle * ole_dup (GsfInfileMSOle const *src, GError **err) { GsfInfileMSOle *dst; GsfInput *input; g_return_val_if_fail (src != NULL, NULL); input = gsf_input_dup (src->input, err); if (input == NULL) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Failed to duplicate input stream"); return NULL; } dst = (GsfInfileMSOle *)g_object_new (GSF_INFILE_MSOLE_TYPE, NULL); if (G_UNLIKELY (NULL == dst)) return NULL; dst->input = input; dst->info = ole_info_ref (src->info); /* buf and buf_size are initialized to NULL */ return dst; } /** * ole_init_info : * @ole: * @err: optionally NULL * * Read an OLE header and do some sanity checking * along the way. * * Returns: TRUE on error setting @err if it is supplied. **/ static gboolean ole_init_info (GsfInfileMSOle *ole, GError **err) { static guint8 const signature[] = { 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1 }; guint8 const *header, *tmp; guint32 *metabat = NULL; MSOleInfo *info; guint32 bb_shift, sb_shift, num_bat, num_metabat, last, dirent_start; guint32 metabat_block, *ptr; gboolean fail; /* check the header */ if (gsf_input_seek (ole->input, 0, G_SEEK_SET) || NULL == (header = gsf_input_read (ole->input, OLE_HEADER_SIZE, NULL)) || 0 != memcmp (header, signature, sizeof (signature))) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "No OLE2 signature"); return TRUE; } bb_shift = GSF_LE_GET_GUINT16 (header + OLE_HEADER_BB_SHIFT); sb_shift = GSF_LE_GET_GUINT16 (header + OLE_HEADER_SB_SHIFT); num_bat = GSF_LE_GET_GUINT32 (header + OLE_HEADER_NUM_BAT); dirent_start = GSF_LE_GET_GUINT32 (header + OLE_HEADER_DIRENT_START); metabat_block = GSF_LE_GET_GUINT32 (header + OLE_HEADER_METABAT_BLOCK); num_metabat = GSF_LE_GET_GUINT32 (header + OLE_HEADER_NUM_METABAT); /* Some sanity checks * 1) There should always be at least 1 BAT block * 2) It makes no sense to have a block larger than 2^31 for now. * Maybe relax this later, but not much. */ if (6 > bb_shift || bb_shift >= 31 || sb_shift > bb_shift || (gsf_input_size (ole->input) >> bb_shift) < 1) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Unreasonable block sizes"); return TRUE; } info = g_new0 (MSOleInfo, 1); ole->info = info; info->ref_count = 1; info->bb.shift = bb_shift; info->bb.size = 1 << info->bb.shift; info->bb.filter = info->bb.size - 1; info->sb.shift = sb_shift; info->sb.size = 1 << info->sb.shift; info->sb.filter = info->sb.size - 1; info->threshold = GSF_LE_GET_GUINT32 (header + OLE_HEADER_THRESHOLD); info->sbat_start = GSF_LE_GET_GUINT32 (header + OLE_HEADER_SBAT_START); info->num_sbat = GSF_LE_GET_GUINT32 (header + OLE_HEADER_NUM_SBAT); info->max_block = (gsf_input_size (ole->input) - OLE_HEADER_SIZE) / info->bb.size; info->sb_file = NULL; if (info->num_sbat == 0 && info->sbat_start != BAT_MAGIC_END_OF_CHAIN && info->sbat_start != BAT_MAGIC_UNUSED) { g_warning ("There are not supposed to be any blocks in the small block allocation table, yet there is a link to some. Ignoring it."); } /* very rough heuristic, just in case */ if (num_bat < info->max_block) { info->bb.bat.num_blocks = num_bat * (info->bb.size / BAT_INDEX_SIZE); info->bb.bat.block = g_new0 (guint32, info->bb.bat.num_blocks); metabat = g_try_new (guint32, MAX (info->bb.size, OLE_HEADER_SIZE)); if (!metabat) { g_free (info); if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Insufficient memory"); return TRUE; } /* Reading the elements invalidates this memory, make copy */ gsf_ole_get_guint32s (metabat, header + OLE_HEADER_START_BAT, OLE_HEADER_SIZE - OLE_HEADER_START_BAT); last = num_bat; if (last > OLE_HEADER_METABAT_SIZE) last = OLE_HEADER_METABAT_SIZE; ptr = ole_info_read_metabat (ole, info->bb.bat.block, info->bb.bat.num_blocks, metabat, metabat + last); num_bat -= last; } else ptr = NULL; last = (info->bb.size - BAT_INDEX_SIZE) / BAT_INDEX_SIZE; while (ptr != NULL && num_metabat-- > 0) { tmp = ole_get_block (ole, metabat_block, NULL); if (tmp == NULL) { ptr = NULL; break; } /* Reading the elements invalidates this memory, make copy */ gsf_ole_get_guint32s (metabat, tmp, (int)info->bb.size); if (num_metabat == 0) { if (last < num_bat) { /* there should be less that a full metabat block * remaining */ ptr = NULL; break; } last = num_bat; } else if (num_metabat > 0) { metabat_block = metabat[last]; if (num_bat < last) { /* ::num_bat and ::num_metabat are * inconsistent. There are too many metabats * for the bat count in the header. */ ptr = NULL; break; } num_bat -= last; } ptr = ole_info_read_metabat (ole, ptr, info->bb.bat.num_blocks, metabat, metabat + last); } fail = (ptr == NULL); g_free (metabat); metabat = ptr = NULL; if (fail) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Inconsistent block allocation table"); return TRUE; } /* Read the directory's bat, we do not know the size */ if (ole_make_bat (&info->bb.bat, 0, dirent_start, &ole->bat)) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Problems making block allocation table"); return TRUE; } /* Read the directory */ ole->dirent = info->root_dir = ole_dirent_new (ole, 0, NULL); if (ole->dirent == NULL) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Problems reading directory"); return TRUE; } return FALSE; } static void gsf_infile_msole_finalize (GObject *obj) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (obj); if (ole->input != NULL) { g_object_unref (G_OBJECT (ole->input)); ole->input = NULL; } if (ole->info != NULL && ole->info->sb_file != (GsfInput *)ole) { ole_info_unref (ole->info); ole->info = NULL; } ols_bat_release (&ole->bat); g_free (ole->stream.buf); parent_class->finalize (obj); } static GsfInput * gsf_infile_msole_dup (GsfInput *src_input, GError **err) { GsfInfileMSOle const *src = GSF_INFILE_MSOLE (src_input); GsfInfileMSOle *dst = ole_dup (src, err); if (dst == NULL) return NULL; if (src->bat.block != NULL) { dst->bat.block = g_new (guint32, src->bat.num_blocks), memcpy (dst->bat.block, src->bat.block, sizeof (guint32) * src->bat.num_blocks); } dst->bat.num_blocks = src->bat.num_blocks; dst->dirent = src->dirent; return GSF_INPUT (dst); } static guint8 const * gsf_infile_msole_read (GsfInput *input, size_t num_bytes, guint8 *buffer) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (input); gsf_off_t first_block, last_block, raw_block, offset, i; guint8 const *data; guint8 *ptr; size_t count; /* small block files are preload */ if (ole->dirent != NULL && ole->dirent->use_sb) { if (buffer != NULL) { memcpy (buffer, ole->stream.buf + input->cur_offset, num_bytes); return buffer; } return ole->stream.buf + input->cur_offset; } /* GsfInput guarantees that num_bytes > 0 */ first_block = OLE_BIG_BLOCK (input->cur_offset, ole); last_block = OLE_BIG_BLOCK (input->cur_offset + num_bytes - 1, ole); offset = input->cur_offset & ole->info->bb.filter; /* optimization : are all the raw blocks contiguous */ i = first_block; raw_block = ole->bat.block [i]; while (++i <= last_block && ++raw_block == ole->bat.block [i]) ; if (i > last_block) { /* optimization don't seek if we don't need to */ if (ole->cur_block != first_block) { if (gsf_input_seek (ole->input, (gsf_off_t)(MAX (OLE_HEADER_SIZE, ole->info->bb.size) + (ole->bat.block [first_block] << ole->info->bb.shift) + offset), G_SEEK_SET) < 0) return NULL; } ole->cur_block = last_block; return gsf_input_read (ole->input, num_bytes, buffer); } /* damn, we need to copy it block by block */ if (buffer == NULL) { if (ole->stream.buf_size < num_bytes) { g_free (ole->stream.buf); ole->stream.buf_size = num_bytes; ole->stream.buf = g_new (guint8, num_bytes); } buffer = ole->stream.buf; } ptr = buffer; for (i = first_block ; i <= last_block ; i++ , ptr += count, num_bytes -= count) { count = ole->info->bb.size - offset; if (count > num_bytes) count = num_bytes; data = ole_get_block (ole, ole->bat.block [i], NULL); if (data == NULL) return NULL; /* TODO : this could be optimized to avoid the copy */ memcpy (ptr, data + offset, count); offset = 0; } ole->cur_block = BAT_MAGIC_UNUSED; return buffer; } static gboolean gsf_infile_msole_seek (GsfInput *input, gsf_off_t offset, GSeekType whence) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (input); (void) offset; (void) whence; ole->cur_block = BAT_MAGIC_UNUSED; return FALSE; } static GsfInput * gsf_infile_msole_new_child (GsfInfileMSOle *parent, MSOleDirent *dirent, GError **err) { GsfInfileMSOle *child; MSOleInfo *info; MSOleBAT const *metabat; GsfInput *sb_file = NULL; size_t size_guess; child = ole_dup (parent, err); if (!child) return NULL; child->dirent = dirent; gsf_input_set_size (GSF_INPUT (child), (gsf_off_t) dirent->size); /* The root dirent defines the small block file */ if (dirent->index != 0) { gsf_input_set_name (GSF_INPUT (child), dirent->name); gsf_input_set_container (GSF_INPUT (child), GSF_INFILE (parent)); if (dirent->is_directory) { /* be wary. It seems as if some implementations pretend that the * directories contain data */ gsf_input_set_size (GSF_INPUT (child), 0); return GSF_INPUT (child); } } info = parent->info; /* build the bat */ if (dirent->use_sb) { metabat = &info->sb.bat; size_guess = dirent->size >> info->sb.shift; sb_file = ole_info_get_sb_file (parent); if (!sb_file) { if (err != NULL) *err = g_error_new (gsf_input_error_id (), 0, "Failed to access child"); g_object_unref (G_OBJECT (child)); return NULL; } } else { metabat = &info->bb.bat; size_guess = dirent->size >> info->bb.shift; } if (ole_make_bat (metabat, size_guess + 1, dirent->first_block, &child->bat)) { g_object_unref (G_OBJECT (child)); return NULL; } if (dirent->use_sb) { unsigned i; guint8 const *data; g_return_val_if_fail (sb_file != NULL, NULL); child->stream.buf_size = info->threshold; child->stream.buf = g_new (guint8, info->threshold); for (i = 0 ; i < child->bat.num_blocks; i++) if (gsf_input_seek (GSF_INPUT (sb_file), (gsf_off_t)(child->bat.block [i] << info->sb.shift), G_SEEK_SET) < 0 || (data = gsf_input_read (GSF_INPUT (sb_file), info->sb.size, child->stream.buf + (i << info->sb.shift))) == NULL) { g_warning ("failure reading block %d", i); g_object_unref (G_OBJECT (child)); return NULL; } } return GSF_INPUT (child); } static GsfInput * gsf_infile_msole_child_by_index (GsfInfile *infile, int target, GError **err) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (infile); GList *p; for (p = ole->dirent->children; p != NULL ; p = p->next) if (target-- <= 0) return gsf_infile_msole_new_child (ole, (MSOleDirent *)p->data, err); return NULL; } static char const * gsf_infile_msole_name_by_index (GsfInfile *infile, int target) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (infile); GList *p; for (p = ole->dirent->children; p != NULL ; p = p->next) if (target-- <= 0) return ((MSOleDirent *)p->data)->name; return NULL; } static GsfInput * gsf_infile_msole_child_by_name (GsfInfile *infile, char const *name, GError **err) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (infile); GList *p; for (p = ole->dirent->children; p != NULL ; p = p->next) { MSOleDirent *dirent = p->data; if (dirent->name != NULL && !strcmp (name, dirent->name)) return gsf_infile_msole_new_child (ole, dirent, err); } return NULL; } static int gsf_infile_msole_num_children (GsfInfile *infile) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (infile); g_return_val_if_fail (ole->dirent != NULL, -1); if (!ole->dirent->is_directory) return -1; return g_list_length (ole->dirent->children); } static void gsf_infile_msole_init (GObject *obj) { GsfInfileMSOle *ole = GSF_INFILE_MSOLE (obj); ole->input = NULL; ole->info = NULL; ole->bat.block = NULL; ole->bat.num_blocks = 0; ole->cur_block = BAT_MAGIC_UNUSED; ole->stream.buf = NULL; ole->stream.buf_size = 0; } static void gsf_infile_msole_class_init (GObjectClass *gobject_class) { GsfInputClass *input_class = GSF_INPUT_CLASS (gobject_class); GsfInfileClass *infile_class = GSF_INFILE_CLASS (gobject_class); gobject_class->finalize = gsf_infile_msole_finalize; input_class->Dup = gsf_infile_msole_dup; input_class->Read = gsf_infile_msole_read; input_class->Seek = gsf_infile_msole_seek; infile_class->num_children = gsf_infile_msole_num_children; infile_class->name_by_index = gsf_infile_msole_name_by_index; infile_class->child_by_index = gsf_infile_msole_child_by_index; infile_class->child_by_name = gsf_infile_msole_child_by_name; parent_class = g_type_class_peek_parent (gobject_class); } GSF_CLASS (GsfInfileMSOle, gsf_infile_msole, gsf_infile_msole_class_init, gsf_infile_msole_init, GSF_INFILE_TYPE) /** * gsf_infile_msole_new : * @source: * @err: * * Opens the root directory of an MS OLE file. * This adds a reference to @source. * * Returns: the new ole file handler **/ GsfInfile * gsf_infile_msole_new (GsfInput *source, GError **err) { GsfInfileMSOle *ole; gsf_off_t calling_pos; g_return_val_if_fail (GSF_IS_INPUT (source), NULL); ole = (GsfInfileMSOle *)g_object_new (GSF_INFILE_MSOLE_TYPE, NULL); if (G_UNLIKELY (NULL == ole)) return NULL; ole->input = gsf_input_proxy_new (source); gsf_input_set_size (GSF_INPUT (ole), 0); calling_pos = gsf_input_tell (source); if (ole_init_info (ole, err)) { /* It's not clear to me why we do this. And if this fails, there's really nothing we can do. */ (void)gsf_input_seek (source, calling_pos, G_SEEK_SET); g_object_unref (G_OBJECT (ole)); return NULL; } return GSF_INFILE (ole); } /** * gsf_infile_msole_get_class_id : * @ole: a #GsfInfileMSOle * @res: 16 byte identifier (often a GUID in MS Windows apps) * * Retrieves the 16 byte indentifier (often a GUID in MS Windows apps) * stored within the directory associated with @ole and stores it in @res. * * Returns: TRUE on success **/ gboolean gsf_infile_msole_get_class_id (GsfInfileMSOle const *ole, guint8 *res) { g_return_val_if_fail (ole != NULL && ole->dirent != NULL, FALSE); memcpy (res, ole->dirent->clsid, sizeof(ole->dirent->clsid)); return TRUE; }