olefs

command line tools to extract data from OLE documents like doc, ppt, xls, msg
git clone https://logand.com/git/olefs.git/
Log | Files | Refs

commit e7eeca1ca087c7f38e07e8da8e55fe10890d31a9
Author: Tomas Hlavaty <tom@logand.com>
Date:   Sun, 29 May 2011 16:24:33 +0200

Initial commit

Diffstat:
AMakefile | 20++++++++++++++++++++
Acfbfs.c | 681+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aodrawfs.c | 205+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 906 insertions(+), 0 deletions(-)

diff --git a/Makefile b/Makefile @@ -0,0 +1,20 @@ +ALL=cfbfs odrawfs +#-std=c99 +CFLAGS=-g -Wall +#CFLAGS=-Wall -O2 +LDFLAGS= +CFLAGSFUSE=-DFUSE_USE_VERSION=25 $(shell pkg-config fuse --cflags) +LDFLAGSFUSE=$(shell pkg-config fuse --libs) + +all: $(ALL) + +cfbfs: cfbfs.c + $(CC) $(CFLAGS) $(CFLAGSFUSE) -o $@ $< $(LDFLAGS) $(LDFLAGSFUSE) +# strip $@ + +odrawfs: odrawfs.c + $(CC) $(CFLAGS) $(CFLAGSFUSE) -o $@ $< $(LDFLAGS) $(LDFLAGSFUSE) +# strip $@ + +clean: + rm -f $(ALL) diff --git a/cfbfs.c b/cfbfs.c @@ -0,0 +1,681 @@ +// TODO version 4 with bigger sector size + +#include <fuse.h> +#include <stdlib.h> +#include <assert.h> // TODO dont use assert! +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +//#include <iconv.h> // TODO wchar -> utf8 properly + +// MS-CFB Compound File Binary File Format + +#define UNUSED_SECTOR 0 +#define MAXREGSECT 0xfffffffa +#define DIFSECT 0xfffffffc +#define FATSECT 0xfffffffd +#define ENDOFCHAIN 0xfffffffe +#define FREESECT 0xffffffff + +#define MAXREGSIG 0xfffffffa +#define NOSTREAM 0xffffffff + +#define ENTRY_UNKNOWN 0 +#define ENTRY_STORAGE 1 +#define ENTRY_STREAM 2 +#define ENTRY_ROOT 5 + +typedef uint8_t byte; +typedef uint16_t ushort; +typedef uint16_t wchar; +typedef uint32_t dword; +typedef uint64_t filetime; +typedef uint64_t ulonglong; + +struct entry { + wchar name[32]; + ushort name_length; + byte object_type; + byte color_flag; + dword left_sibling_id; + dword right_sibling_id; + dword child_id; + byte clsid[16]; + dword state_bits; + filetime creation_time; + filetime modified_time; + dword starting_sector_location; + ulonglong stream_size; +}; + +struct chain { + dword location; + struct chain *next; +}; + +#define PATH_MAX 1024 + +static char *filename; +static char cwd[PATH_MAX]; // fuse changes cwd:-{ +//static iconv_t conv; +static dword difat_length; +static dword *difat; +static dword fat_length; +static dword *fat; +static struct chain *directory_chain; +static int directories_length; +static struct entry *directories; +static struct chain *mfat_chain; +static int mfat_length; +static dword *mfat; + +// header +static byte signature[8]; +static byte clsid[16]; +static ushort minor_version; +static ushort major_version; +static ushort byte_order; +static ushort sector_shift; +static ushort mini_sector_shift; +static byte reserved[6]; +static dword number_of_directory_sectors; +static dword number_of_fat_sectors; +static dword first_directory_sector_location; +static dword transaction_signature_number; +static dword mini_stream_cutoff_size; +static dword first_mini_fat_sector_location; +static dword number_of_mini_fat_sectors; +static dword first_difat_sector_location; +static dword number_of_difat_sectors; + +static dword location_position(dword location) { + return (1 + location) * 512; +} + +static void seek_sector(FILE *stream, dword location) { + dword position = location_position(location); + fseek(stream, position, SEEK_SET); +} + +static void read_byte(FILE *stream, byte *place, int count) { + fread(place, sizeof(byte), count, stream); +} + +static void read_wchar(FILE *stream, wchar *place, int count) { + fread(place, sizeof(wchar), count, stream); +} + +static void read_ushort(FILE *stream, ushort *place, int count) { + fread(place, sizeof(ushort), count, stream); +} + +static void read_dword(FILE *stream, dword *place, int count) { + fread(place, sizeof(dword), count, stream); +} + +static void read_filetime(FILE *stream, filetime *place, int count) { + fread(place, sizeof(filetime), count, stream); +} + +static void read_ulonglong(FILE *stream, ulonglong *place, int count) { + fread(place, sizeof(ulonglong), count, stream); +} + +static void read_header(FILE *stream) { + read_byte(stream, signature, 8); + read_byte(stream, clsid, 16); + read_ushort(stream, &minor_version, 1); + read_ushort(stream, &major_version, 1); + read_ushort(stream, &byte_order, 1); + read_ushort(stream, &sector_shift, 1); + read_ushort(stream, &mini_sector_shift, 1); + read_byte(stream, reserved, 6); + read_dword(stream, &number_of_directory_sectors, 1); + read_dword(stream, &number_of_fat_sectors, 1); + read_dword(stream, &first_directory_sector_location, 1); + read_dword(stream, &transaction_signature_number, 1); + read_dword(stream, &mini_stream_cutoff_size, 1); + read_dword(stream, &first_mini_fat_sector_location, 1); + read_dword(stream, &number_of_mini_fat_sectors, 1); + read_dword(stream, &first_difat_sector_location, 1); + read_dword(stream, &number_of_difat_sectors, 1); +} + +static const byte expected_signature[8] = + {0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1}; + +static const byte expected_reserved[6] = {0, 0, 0, 0, 0, 0}; + +static void check_header() { + assert(!memcmp(signature, expected_signature, 8)); +/* ;;(assert (equalp clsid_null (ole_header.clsid x))) */ + assert(0xfffe == byte_order); + assert(!memcmp(reserved, expected_reserved, 6)); + assert(3 == major_version); + assert(512 == (1 << sector_shift)); + assert(64 == (1 << mini_sector_shift)); + assert(0 == number_of_directory_sectors); + /* ;;(assert (eql 0xfffffffe (first_directory_sector_location x))) */ + assert(0 == transaction_signature_number); + assert(4096 == mini_stream_cutoff_size); + /* ;;(assert (eql 0xfffffffe (first_mini_fat_sector_location x))) */ + if(number_of_difat_sectors <= 0) + assert(0xfffffffe == first_difat_sector_location); +} + +static void read_entry(FILE *stream, struct entry *entry) { + read_wchar(stream, &entry->name[0], 32); + read_ushort(stream, &entry->name_length, 1); + read_byte(stream, &entry->object_type, 1); + read_byte(stream, &entry->color_flag, 1); + read_dword(stream, &entry->left_sibling_id, 1); + read_dword(stream, &entry->right_sibling_id, 1); + read_dword(stream, &entry->child_id, 1); + read_byte(stream, &entry->clsid[0], 16); + read_dword(stream, &entry->state_bits, 1); + read_filetime(stream, &entry->creation_time, 1); + read_filetime(stream, &entry->modified_time, 1); + read_dword(stream, &entry->starting_sector_location, 1); + read_ulonglong(stream, &entry->stream_size, 1); +}; + +static void print_bytes(FILE *stream, byte *place, int count) { + int i; + for(i = 0; i < count; i++) { + fprintf(stream, "%s%02x", (0 < i ? ":" : ""), place[i]); + } +} + +static void print_ushort(FILE *stream, ushort x) { + fprintf(stream, "%u 0x%04x", x, x); +} + +static void print_dword(FILE *stream, dword x) { + fprintf(stream, "%u 0x%08x", x, x); +} + +static void print_header(FILE *stream) { + fprintf(stream, "signature "); + print_bytes(stream, signature, 8); + fprintf(stream, "\nclsid "); + print_bytes(stream, clsid, 16); + fprintf(stream, "\nminor_version "); + print_ushort(stream, minor_version); + fprintf(stream, "\nmajor_version "); + print_ushort(stream, major_version); + fprintf(stream, "\nbyte_order "); + print_ushort(stream, byte_order); + fprintf(stream, "\nsector_shift "); + print_ushort(stream, sector_shift); + fprintf(stream, "\nmini_sector_shift "); + print_ushort(stream, mini_sector_shift); + fprintf(stream, "\nreserved "); + print_bytes(stream, reserved, 6); + fprintf(stream, "\nnumber_of_directory_sectors "); + print_dword(stream, number_of_directory_sectors); + fprintf(stream, "\nnumber_of_fat_sectors "); + print_dword(stream, number_of_fat_sectors); + fprintf(stream, "\nfirst_directory_sector_location "); + print_dword(stream, first_directory_sector_location); + fprintf(stream, "\ntransaction_signature_number "); + print_dword(stream, transaction_signature_number); + fprintf(stream, "\nmini_stream_cutoff_size "); + print_dword(stream, mini_stream_cutoff_size); + fprintf(stream, "\nfirst_mini_fat_sector_location "); + print_dword(stream, first_mini_fat_sector_location); + fprintf(stream, "\nnumber_of_mini_fat_sectors "); + print_dword(stream, number_of_mini_fat_sectors); + fprintf(stream, "\nfirst_difat_sector_location "); + print_dword(stream, first_difat_sector_location); + fprintf(stream, "\nnumber_of_difat_sectors "); + print_dword(stream, number_of_difat_sectors); + //fprintf(stream, "\n"); + fprintf(stream, "\nfilename %s", filename); + fprintf(stream, "\ndirectory %s", cwd); + fprintf(stream, "\ndifat_length "); + print_dword(stream, difat_length); + fprintf(stream, "\nfat_length "); + print_dword(stream, fat_length); + fprintf(stream, "\ndirectories_length %d", directories_length); + fprintf(stream, "\nmfat_length %d", mfat_length); + fprintf(stream, "\n"); +} + +static void read_difat (FILE *stream) { + difat_length = 109 + ((512 - 4) / 4) * number_of_difat_sectors; + difat = calloc(difat_length, sizeof(dword)); + read_dword(stream, difat, 109); + dword n = first_difat_sector_location, i = 109, m = 512 / 4 - 1; + for(; n != ENDOFCHAIN; read_dword(stream, &n, 1), i += m) { + seek_sector(stream, n); + read_dword(stream, &difat[i], m); + } +} + +static void read_fat (FILE *stream) { + dword m = 512 / 4; + fat_length = m * difat_length; + fat = calloc(fat_length, sizeof(dword)); + int i; + for(i = 0; i < difat_length; i++) { + dword s = difat[i]; + if(s != FREESECT) { + seek_sector(stream, s); + read_dword(stream, &fat[i * m], m); + } + } +} + +static struct chain *make_chain(dword location, struct chain *next) { + struct chain *x = malloc(sizeof(struct chain)); + x->location = location; + x->next = next; + return x; +} + +#define FOREACH_CHAIN(chain) for(; chain; chain = chain->next) + +static void free_chain(struct chain *x) { + struct chain *next; + for(; x; x = next) { + next = x->next; + free(x); + } +} + +static int chain_length(struct chain *chain) { + int i = 0; + FOREACH_CHAIN(chain) i++; + return i; +} + +static struct chain *nth_chain(struct chain *chain, int n) { + if(0 <= n) { + FOREACH_CHAIN(chain) { + if(n == 0) + return chain; + n--; + } + } + return NULL; +} + +static struct chain *sector_chain(dword *fat, dword location) { + struct chain *x = NULL; + switch(location) { + case DIFSECT: + case FATSECT: + case ENDOFCHAIN: + case FREESECT: + break; + default: + assert(0 <= location && location <= MAXREGSECT); + x = make_chain(location, sector_chain(fat, fat[location])); + } + return x; +} + +static void read_directories (FILE *stream) { + dword m = 512 / 128; + directories_length = m * chain_length(directory_chain); + directories = calloc(directories_length, sizeof(struct entry)); + int i = 0; + struct chain *x = directory_chain; + FOREACH_CHAIN(x) { + seek_sector(stream, x->location); + int j; + for(j = 0; j < m; j++) + read_entry(stream, &directories[i++]); + } +} + +static void read_mfat (FILE *stream) { + dword m = 512 / 4; + mfat_length = m * chain_length(mfat_chain); + mfat = calloc(mfat_length, sizeof(dword)); + int i = 0; + struct chain *x = mfat_chain; + FOREACH_CHAIN(x) { + seek_sector(stream, x->location); + read_dword(stream, &mfat[i++ * m], m); + } +} + +static const char *header_path = "/header"; + +static void print_header_to_string(char **str, size_t *size) { + FILE *stream = open_memstream(str, size); + print_header(stream); + fflush(stream); + fclose(stream); +} + +static size_t xconv(wchar *iname, char *oname, size_t length) { + /* size_t ileft = length, oleft; */ + /* return iconv(conv, (char **) &iname, &ileft, &oname, &oleft); */ + int i; + for(i = 0; i < length / sizeof(wchar); i++) + oname[i] = iname[i]; + return 0; +} + +typedef int (*walk_directory_cb)(void *env, struct entry *entry, dword id, + char *path, char *name, char *parent_path); + +static void walk_directory(void *env, walk_directory_cb cb, dword id, char *path) { + struct entry *x = &directories[id]; + if(x->object_type == ENTRY_STORAGE + || x->object_type == ENTRY_STREAM + || x->object_type == ENTRY_ROOT) { + char name[32 * sizeof(wchar)]; + xconv(x->name, name, x->name_length); + size_t xlen = strlen(path) + 1 + strlen(name) + 1; + char xpath[xlen]; + snprintf(xpath, xlen, "%s/%s", path, name); + int descend = cb(env, x, id, xpath, name, path); + dword n; + n = x->left_sibling_id; + if(n <= MAXREGSIG) + walk_directory(env, cb, n, path); + if(descend) { + n = x->child_id; + if(n <= MAXREGSIG) + walk_directory(env, cb, n, xpath); + } + n = x->right_sibling_id; + if(n <= MAXREGSIG) + walk_directory(env, cb, n, path); + } +} + +struct getattr_walk { + const char *path; + struct stat *stbuf; + int result; +}; + +static int getattr_walk_cb(void *env, struct entry *entry, dword id, + char *path, char *name, char *parent_path) { + struct getattr_walk *e = env; + if(!strcmp(path, e->path)) { + switch(entry->object_type) { + case ENTRY_STORAGE: + e->stbuf->st_mode = S_IFDIR | 0755; + e->stbuf->st_nlink = 1; + e->stbuf->st_size = 0; + e->result = 0; + return 0; + case ENTRY_STREAM: + e->stbuf->st_mode = S_IFREG | 0444; + e->stbuf->st_nlink = 1; + e->stbuf->st_size = entry->stream_size; + e->result = 0; + return 0; + case ENTRY_ROOT: + e->stbuf->st_mode = S_IFDIR | 0755; + e->stbuf->st_nlink = 1; + e->stbuf->st_size = 0; + e->result = 0; + return 0; + } + } + return 1; +} + +static int olefs_getattr(const char *path, struct stat *stbuf) { + int res = 0; + memset(stbuf, 0, sizeof(struct stat)); + if(strcmp(path, "/") == 0) { + stbuf->st_mode = S_IFDIR | 0755; + stbuf->st_nlink = 2; + } + else if(strcmp(path, header_path) == 0) { + stbuf->st_mode = S_IFREG | 0444; + stbuf->st_nlink = 1; + size_t len; + char *header_str; + print_header_to_string(&header_str, &len); + free(header_str); + stbuf->st_size = len; //0; //strlen(header_str); + } + else { + struct getattr_walk e = {path, stbuf, -ENOENT}; + walk_directory(&e, getattr_walk_cb, 0, ""); + return e.result; + } + return res; +} + +struct readdir_walk { + const char *path; + void *buf; + fuse_fill_dir_t filler; + int result; +}; + +static int readdir_walk_cb(void *env, struct entry *entry, dword id, + char *path, char *name, char *parent_path) { + struct readdir_walk *e = env; + if(!strcmp(parent_path, e->path)) { + e->filler(e->buf, name, NULL, 0); + e->result = 0; + return 0; + } + return 1; +} + +static int olefs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, + off_t offset, struct fuse_file_info *fi) { + (void) offset; + (void) fi; + struct entry *root = &directories[0]; + char name[32 * sizeof(wchar)]; + xconv(root->name, name, root->name_length); + if(strcmp(path, "/") == 0) { + filler(buf, ".", NULL, 0); + filler(buf, "..", NULL, 0); + filler(buf, header_path + 1, NULL, 0); + filler(buf, name, NULL, 0); + } + else { + struct readdir_walk e = {path, buf, filler, -ENOENT}; + walk_directory(&e, readdir_walk_cb, 0, ""); + return e.result; + } + return 0; +} + +struct open_walk { + const char *path; + struct fuse_file_info *fi; + int result; +}; + +static int open_walk_cb(void *env, struct entry *entry, dword id, + char *path, char *name, char *parent_path) { + struct open_walk *e = env; + if(!strcmp(path, e->path)) { + if(entry->object_type == ENTRY_STREAM) + e->result = (e->fi->flags & 3) != O_RDONLY ? -EACCES : 0; + return 0; + } + return 1; +} + +static int olefs_open(const char *path, struct fuse_file_info *fi) { + if(!strcmp(path, header_path)) { + if((fi->flags & 3) != O_RDONLY) + return -EACCES; + return 0; + } + struct open_walk e = {path, fi, -ENOENT}; + walk_directory(&e, open_walk_cb, 0, ""); + return e.result; +} + +struct entry_stream { + FILE *file; + dword offset; + struct chain *chain; + struct chain *mchain; + dword sector; + char buffer[512]; + dword size; +}; + +static void open_entry_stream(struct entry_stream *s, struct entry *e, FILE *f) { + int mini = e->stream_size < mini_stream_cutoff_size; + s->file = f; + s->offset = 0; + s->chain = sector_chain(fat, (mini ? directories : e)->starting_sector_location); + s->mchain = mini ? sector_chain(mfat, e->starting_sector_location) : NULL; + s->sector = -1; + s->size = e->stream_size; +} + +static void close_entry_stream(struct entry_stream *s) { + //fclose(s->file); + free_chain(s->chain); + free_chain(s->mchain); +} + +static void seek_entry_stream(struct entry_stream *stream, off_t off) { + stream->offset = off; + stream->sector = -1; +} + +static void stream_read_byte_pick(struct entry_stream *x, dword q, dword r, + char *byte) { + if(x->sector != q) { + seek_sector(x->file, nth_chain(x->chain, q)->location); + int n = fread(x->buffer, sizeof(char), 512, x->file); + assert(512 == n); + x->sector = q; + } + *byte = x->buffer[r]; + x->offset++; +} + +static int stream_read_byte(struct entry_stream *x, char *byte) { + if(x->offset < x->size) { + if(x->mchain) { + dword mq = x->offset / 64, mr = x->offset % 64; + dword s = nth_chain(x->mchain, mq)->location; + dword q = s / (512 / 64), r = s % (512 / 64); + stream_read_byte_pick(x, q, (64 * r) + mr, byte); + } + else { + dword q = x->offset / 512, r = x->offset % 512; + stream_read_byte_pick(x, q, r, byte); + } + return 1; + } + return 0; +} + +static int copy_entry_data_from(char *path, struct entry *entry, + char *buf, size_t size, off_t off) { + int n = 0; + FILE *f = fopen(path, "r"); // TODO move to open_entry_stream + if(f) { + struct entry_stream s; + open_entry_stream(&s, entry, f); + seek_entry_stream(&s, off); + for(; 0 < size; size--, n++) + if(!stream_read_byte(&s, &buf[n])) // TODO read block instead of bytes + break; + close_entry_stream(&s); + fclose(f); // TODO move to close_entry_stream + } + return n; +} + +static int copy_entry_data(struct entry *entry, char *buf, size_t size, off_t off) { + int n = 0; + if(0 < size && 0 <= off && off < entry->stream_size) { + if('/' == filename[0]) + n = copy_entry_data_from(filename, entry, buf, size, off); + else { + char x[PATH_MAX]; + snprintf(x, PATH_MAX, "%s/%s", cwd, filename); + n = copy_entry_data_from(x, entry, buf, size, off); + } + } + return n; +} + +struct read_walk { + const char *path; + char *buf; + size_t size; + off_t offset; + struct fuse_file_info *fi; + int result; +}; + +static int read_walk_cb(void *env, struct entry *entry, dword id, + char *path, char *name, char *parent_path) { + struct read_walk *e = env; + if(!strcmp(path, e->path)) { + e->result = copy_entry_data(entry, e->buf, e->size, e->offset); + return 0; + } + return 1; +} + +static int olefs_read(const char *path, char *buf, size_t size, off_t offset, + struct fuse_file_info *fi) { + if(!strcmp(path, header_path)) { + size_t len; + char *header_str; + print_header_to_string(&header_str, &len); + /* len = strlen(header_str); */ + if (offset < len) { + if (offset + size > len) + size = len - offset; + memcpy(buf, header_str + offset, size); + } else + size = 0; + free(header_str); + return size; + } + struct read_walk e = {path, buf, size, offset, fi, -ENOENT}; + walk_directory(&e, read_walk_cb, 0, ""); + return e.result; +} + +static struct fuse_operations olefs_operations = { + .readdir = olefs_readdir, + .getattr = olefs_getattr, + .open = olefs_open, + .read = olefs_read, +}; + +int main(int argc, char *argv[]) { + if(argc < 3) { + fprintf(stderr, "Usage: %s filename directory\n", argv[0]); + exit(-1); + } + filename = argv[1]; + argv++; + argc--; + getcwd(cwd, PATH_MAX); + FILE *stream = fopen(filename, "r"); + if(!stream) { + fprintf(stderr, "Unable to open '%s'.\n", filename); + exit(-1); + } + //conv = iconv_open("UTF-8", "UTF-16LE"); //"UCS-2"); //"UCS2-LE"); + read_header(stream); + check_header(); + read_difat(stream); + read_fat(stream); + directory_chain = sector_chain(fat, first_directory_sector_location); + read_directories(stream); + mfat_chain = sector_chain(fat, first_mini_fat_sector_location); + read_mfat(stream); + fclose(stream); + return fuse_main(argc, argv, &olefs_operations); +} diff --git a/odrawfs.c b/odrawfs.c @@ -0,0 +1,205 @@ +// TODO proper little endian read_ + +#include <fuse.h> +#include <stdlib.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> + +typedef uint8_t byte; +typedef uint16_t ushort; +typedef uint16_t wchar; +typedef uint32_t dword; +typedef uint64_t filetime; +typedef uint64_t ulonglong; + +char *filename; +char *dir; +FILE *stream; + +struct shorter_stream { + FILE *wrap; + size_t size; + off_t offset; +}; + +static int shorter_stream_eof(struct shorter_stream *s) { + return !(s->offset < s->size); +} + +static int shorter_stream_read(struct shorter_stream *s, void *buf, size_t size) { + int left = s->size - s->offset; + if(0 < left) { + int n = left < size ? left : size; + int m = fread(buf, sizeof(byte), n, s->wrap); + if(0 < m) + s->offset += m; + return m; + } + return 0; +} + +static int read_guid(struct shorter_stream *s, byte guid[]) { + int n = shorter_stream_read(s, guid, 16); + if(0 < n) + assert(n == 16); + return n; +} + +// MS-PPT PowerPoint (.ppt) Binary File Format + +struct RecordHeader { + ushort recVer: 4; //(logand #x0f %dummy1)) + ushort recInstance: 12; //(logior (ash %dummy2 4) (ash %dummy1 -4))) + ushort recType; + dword recLen; +} __attribute__((__packed__)); + +static int read_RecordHeader(struct RecordHeader *x) { + return fread(x, sizeof(struct RecordHeader), 1, stream); +} + +/* static void print_RecordHeader(struct RecordHeader *x) { */ +/* printf("RecordHeader 0x%x 0x%x 0x%x %d 0x%lx\n", x->recVer, x->recInstance, */ +/* x->recType, x->recLen, ftell(stream)); */ +/* } */ + +// MS-ODRAW Office Drawing Binary File Format + +struct POINT { + dword x; + dword y; +} __attribute__((__packed__)); + +struct RECT { + dword left; + dword top; + dword right; + dword bottom; +} __attribute__((__packed__)); + +struct OfficeArtMetafileHeader { + dword cbSize; + struct RECT rcBounds; + struct POINT ptSize; + dword cbSave; + byte compression; // :member '(#x00 #xfe)) + byte filter; //:always #xfe)) +} __attribute__((__packed__)); + +static int read_OfficeArtMetafileHeader(struct shorter_stream *s, + struct OfficeArtMetafileHeader *x) { + return shorter_stream_read(s, x, sizeof(struct OfficeArtMetafileHeader)); +} + +static const struct OfficeArtBlip_config { + ushort recType; + ushort recInstance[4]; + char *ext; + ushort guid2[2]; + int metafileHeader; +} OfficeArtBlip_config[] = { + {0xF01A, {0x3d4, 0x3d5}, "emf", {0x3d5}, 1}, + {0xF01B, {0x216, 0x217}, "wmf", {0x217}, 1}, + {0xF01C, {0x542, 0x543}, "pict", {0x543}, 1}, + {0xF01D, {0x46A, 0x46B, 0x6E2, 0x6E3}, "jpeg", {0x46B, 0x6E3}, 0}, + {0xF01E, {0x6e0, 0x6e1}, "png", {0x6e1}, 0}, + {0xF01F, {0x7a8, 0x7a9}, "dib", {0x7a9}, 0}, + {0xF029, {0x6e4, 0x6e5}, "tiff", {0x6e5}, 0}, + {0xF02A, {0x46A, 0x46B, 0x6E2, 0x6E3}, "jpeg", {0x46B, 0x6E3}, 0}, +}; + +static int member(ushort x, const ushort a[]) { // , size_t n + int i; + for(i = 0; i < sizeof(a); i++) + if(a[i] == x) + return 1; + return 0; +} + +static void blip(int n, ushort recType, ushort recInstance, dword recLen) { + const struct OfficeArtBlip_config *config; + int i; + for(i = 0; i < sizeof(OfficeArtBlip_config); i++) { + config = &OfficeArtBlip_config[i]; + if(OfficeArtBlip_config[i].recType == recType) break; + } + assert(member(recInstance, config->recInstance)); + struct shorter_stream s = {stream, recLen, 0}; + byte guid[16]; + read_guid(&s, guid); + if(member(recInstance, config->guid2)) + read_guid(&s, guid); + if(config->metafileHeader) { + struct OfficeArtMetafileHeader h; + read_OfficeArtMetafileHeader(&s, &h); + } + else { + byte b; + shorter_stream_read(&s, &b, 1); + } + char ofile[1024]; + snprintf(ofile, 1024, "%s/%d.%s", dir, n, config->ext); + FILE *o = fopen(ofile, "w"); + assert(o); + while(!shorter_stream_eof(&s)) { + byte buf[1024]; + int n = shorter_stream_read(&s, buf, 1024); + if(n <= 0) break; + fwrite(buf, sizeof(char), n, o); + } + fclose(o); + //printf("<p><img src=\"%d.%s\">\n", n, config->ext); +} + +int main(int argc, char *argv[]) { + if(argc < 3) { + fprintf(stderr, "Usage: %s filename directory\n", argv[0]); + exit(-1); + } + filename = argv[1]; + dir = argv[2]; + stream = fopen(filename, "r"); + if(!stream) { + fprintf(stderr, "Unable to open '%s'.\n", filename); + exit(-1); + } + int i; + for(i = 0;; i++) { + struct RecordHeader h; + /* struct CurrentUserAtom u; */ + if(read_RecordHeader(&h) <= 0) break; + //print_RecordHeader(&h); + switch(h.recType) { + /* case RT_CurrentUserAtom: */ + /* assert(!x->recVer); */ + /* assert(!x->recInstance); */ + /* read_CurrentUserAtom(&u); */ + /* /\* #+nil ;; why recLen too small? *\/ */ + /* /\* (with-shorter-stream (in stream (RecordHeader.recLen x)) *\/ */ + /* /\* (list x (read-CurrentUserAtom in)))) *\/ */ + /* break; */ + case 0xF01A: + case 0xF01B: + case 0xF01C: + case 0xF01D: + case 0xF01E: + case 0xF01F: + case 0xF029: + case 0xF02A: + assert(!h.recVer); + blip(i, h.recType, h.recInstance, h.recLen); + break; + default: + fprintf(stderr, "Unknown recType 0x%x\n", h.recType); + exit(-1); + } + } + fclose(stream); + return 0; +} + +// n[0..] -> fpos