libclammspack/mspack/oabd.c
d3699d57
 /* This file is part of libmspack.
  * © 2013 Intel Corporation
  *
  * libmspack is free software; you can redistribute it and/or modify it under
  * the terms of the GNU Lesser General Public License (LGPL) version 2.1
  *
  * For further details, see the file COPYING.LIB distributed with libmspack
  */
 
 /* The Exchange Online Addressbook (OAB or sometimes OAL) is distributed
  * as a .LZX file in one of two forms. Either a "full download" containing
  * the entire address list, or an incremental binary patch which should be
  * applied to a previous version of the full decompressed data.
  *
  * The contents and format of the decompressed OAB are not handled here.
  *
  * For a complete description of the format, see the MSDN site:
  *
  * http://msdn.microsoft.com/en-us/library/cc463914 - [MS-OXOAB].pdf
  * http://msdn.microsoft.com/en-us/library/cc483133 - [MS-PATCH].pdf
  */
 
 /* OAB decompression implementation */
 
 #include <system.h>
 #include <oab.h>
 #include <lzx.h>
 #include <crc32.h>
 
 /* prototypes */
 static int oabd_decompress(struct msoab_decompressor *self, const char *input,
a7ac0e8d
                            const char *output);
d3699d57
 static int oabd_decompress_incremental(struct msoab_decompressor *self,
a7ac0e8d
                                        const char *input, const char *base,
                                        const char *output);
 static int oabd_param(struct msoab_decompressor *base, int param, int value);
 static int copy_fh(struct mspack_system *sys, struct mspack_file *infh,
                    struct mspack_file *outfh, size_t bytes_to_copy,
                    unsigned char *buf, int buf_size);
 
d3699d57
 
 struct msoab_decompressor *
   mspack_create_oab_decompressor(struct mspack_system *sys)
 {
   struct msoab_decompressor_p *self = NULL;
 
   if (!sys) sys = mspack_default_system;
   if (!mspack_valid_system(sys)) return NULL;
 
   if ((self = (struct msoab_decompressor_p *) sys->alloc(sys, sizeof(struct msoab_decompressor_p)))) {
     self->base.decompress             = &oabd_decompress;
     self->base.decompress_incremental = &oabd_decompress_incremental;
a7ac0e8d
     self->base.set_param              = &oabd_param;
d3699d57
     self->system                      = sys;
a7ac0e8d
     self->buf_size                    = 4096;
d3699d57
   }
   return (struct msoab_decompressor *) self;
 }
 
 void mspack_destroy_oab_decompressor(struct msoab_decompressor *base) {
   struct msoab_decompressor_p *self = (struct msoab_decompressor_p *)base;
   if (self) {
     struct mspack_system *sys = self->system;
     sys->free(self);
   }
 }
 
 struct oabd_file {
   struct mspack_system *orig_sys;
   struct mspack_file *orig_file;
   unsigned int crc;
   size_t available;
 };
 
 
 static int oabd_sys_read (struct mspack_file *base_file, void *buf, int size)
 {
   struct oabd_file *file = (struct oabd_file *)base_file;
   int bytes_read;
 
   if ((size_t)size > file->available)
     size = file->available;
 
   bytes_read = file->orig_sys->read(file->orig_file, buf, size);
   if (bytes_read < 0)
     return bytes_read;
 
   file->available -= bytes_read;
   return bytes_read;
 }
 
 static int oabd_sys_write (struct mspack_file *base_file, void *buf, int size)
 {
   struct oabd_file *file = (struct oabd_file *)base_file;
   int bytes_written = file->orig_sys->write(file->orig_file, buf, size);
 
   if (bytes_written > 0)
     file->crc = crc32(file->crc, buf, bytes_written);
 
   return bytes_written;
 }
 
 static int oabd_decompress(struct msoab_decompressor *_self, const char *input,
a7ac0e8d
                            const char *output)
d3699d57
 {
   struct msoab_decompressor_p *self = (struct msoab_decompressor_p *) _self;
   struct mspack_system *sys;
   struct mspack_file *infh = NULL;
   struct mspack_file *outfh = NULL;
   unsigned char *buf = NULL;
   unsigned char hdrbuf[oabhead_SIZEOF];
   unsigned int block_max, target_size;
   struct lzxd_stream *lzx = NULL;
   struct mspack_system oabd_sys;
   struct oabd_file in_ofh, out_ofh;
   unsigned int window_bits;
   int ret = MSPACK_ERR_OK;
 
   if (!self) return MSPACK_ERR_ARGS;
   sys = self->system;
 
   infh = sys->open(sys, input, MSPACK_SYS_OPEN_READ);
   if (!infh) {
     ret = MSPACK_ERR_OPEN;
     goto out;
   }
 
   if (sys->read(infh, hdrbuf, oabhead_SIZEOF) != oabhead_SIZEOF) {
     ret = MSPACK_ERR_READ;
     goto out;
   }
 
   if (EndGetI32(&hdrbuf[oabhead_VersionHi]) != 3 ||
       EndGetI32(&hdrbuf[oabhead_VersionLo]) != 1) {
     ret = MSPACK_ERR_SIGNATURE;
     goto out;
   }
 
   block_max   = EndGetI32(&hdrbuf[oabhead_BlockMax]);
   target_size = EndGetI32(&hdrbuf[oabhead_TargetSize]);
 
   outfh = sys->open(sys, output, MSPACK_SYS_OPEN_WRITE);
   if (!outfh) {
     ret = MSPACK_ERR_OPEN;
     goto out;
   }
 
a7ac0e8d
   buf = sys->alloc(sys, self->buf_size);
d3699d57
   if (!buf) {
     ret = MSPACK_ERR_NOMEMORY;
     goto out;
   }
 
   oabd_sys = *sys;
   oabd_sys.read = oabd_sys_read;
   oabd_sys.write = oabd_sys_write;
 
   in_ofh.orig_sys = sys;
   in_ofh.orig_file = infh;
 
   out_ofh.orig_sys = sys;
   out_ofh.orig_file = outfh;
 
   while (target_size) {
     unsigned int blk_csize, blk_dsize, blk_crc, blk_flags;
 
     if (sys->read(infh, buf, oabblk_SIZEOF) != oabblk_SIZEOF) {
       ret = MSPACK_ERR_READ;
       goto out;
     }
     blk_flags = EndGetI32(&buf[oabblk_Flags]);
     blk_csize = EndGetI32(&buf[oabblk_CompSize]);
     blk_dsize = EndGetI32(&buf[oabblk_UncompSize]);
     blk_crc   = EndGetI32(&buf[oabblk_CRC]);
 
     if (blk_dsize > block_max || blk_dsize > target_size || blk_flags > 1) {
       ret = MSPACK_ERR_DATAFORMAT;
       goto out;
     }
 
     if (!blk_flags) {
       /* Uncompressed block */
       if (blk_dsize != blk_csize) {
a7ac0e8d
         ret = MSPACK_ERR_DATAFORMAT;
         goto out;
d3699d57
       }
a7ac0e8d
       ret = copy_fh(sys, infh, outfh, blk_dsize, buf, self->buf_size);
       if (ret) goto out;
d3699d57
     } else {
       /* LZX compressed block */
       window_bits = 17;
 
       while (window_bits < 25 && (1U << window_bits) < blk_dsize)
a7ac0e8d
         window_bits++;
d3699d57
 
       in_ofh.available = blk_csize;
       out_ofh.crc = 0xffffffff;
 
       lzx = lzxd_init(&oabd_sys, (void *)&in_ofh, (void *)&out_ofh, window_bits,
a7ac0e8d
                       0, self->buf_size, blk_dsize, 1);
d3699d57
       if (!lzx) {
a7ac0e8d
         ret = MSPACK_ERR_NOMEMORY;
         goto out;
d3699d57
       }
 
       ret = lzxd_decompress(lzx, blk_dsize);
       if (ret != MSPACK_ERR_OK)
a7ac0e8d
         goto out;
d3699d57
 
       lzxd_free(lzx);
       lzx = NULL;
 
       /* Consume any trailing padding bytes before the next block */
a7ac0e8d
       ret = copy_fh(sys, infh, NULL, in_ofh.available, buf, self->buf_size);
       if (ret) goto out;
d3699d57
 
       if (out_ofh.crc != blk_crc) {
a7ac0e8d
         ret = MSPACK_ERR_CHECKSUM;
         goto out;
d3699d57
       }
     }
     target_size -= blk_dsize;
   }
 
  out:
32a2e7ce
   if (lzx) lzxd_free(lzx);
   if (outfh) sys->close(outfh);
   if (infh) sys->close(infh);
   sys->free(buf);
d3699d57
 
   return ret;
 }
 
 static int oabd_decompress_incremental(struct msoab_decompressor *_self,
a7ac0e8d
                                        const char *input, const char *base,
                                        const char *output)
d3699d57
 {
   struct msoab_decompressor_p *self = (struct msoab_decompressor_p *) _self;
   struct mspack_system *sys;
   struct mspack_file *infh = NULL;
   struct mspack_file *basefh = NULL;
   struct mspack_file *outfh = NULL;
   unsigned char *buf = NULL;
   unsigned char hdrbuf[patchhead_SIZEOF];
   unsigned int block_max, target_size;
   struct lzxd_stream *lzx = NULL;
   struct mspack_system oabd_sys;
   struct oabd_file in_ofh, out_ofh;
   unsigned int window_bits, window_size;
   int ret = MSPACK_ERR_OK;
 
   if (!self) return MSPACK_ERR_ARGS;
   sys = self->system;
 
   infh = sys->open(sys, input, MSPACK_SYS_OPEN_READ);
   if (!infh) {
     ret = MSPACK_ERR_OPEN;
     goto out;
   }
 
   if (sys->read(infh, hdrbuf, patchhead_SIZEOF) != patchhead_SIZEOF) {
     ret = MSPACK_ERR_READ;
     goto out;
   }
 
   if (EndGetI32(&hdrbuf[patchhead_VersionHi]) != 3 ||
       EndGetI32(&hdrbuf[patchhead_VersionLo]) != 2) {
     ret = MSPACK_ERR_SIGNATURE;
     goto out;
   }
 
   block_max = EndGetI32(&hdrbuf[patchhead_BlockMax]);
   target_size = EndGetI32(&hdrbuf[patchhead_TargetSize]);
 
   /* We use it for reading block headers too */
   if (block_max < patchblk_SIZEOF)
     block_max = patchblk_SIZEOF;
 
   basefh = sys->open(sys, base, MSPACK_SYS_OPEN_READ);
   if (!basefh) {
     ret = MSPACK_ERR_OPEN;
     goto out;
   }
 
   outfh = sys->open(sys, output, MSPACK_SYS_OPEN_WRITE);
   if (!outfh) {
     ret = MSPACK_ERR_OPEN;
     goto out;
   }
 
a7ac0e8d
   buf = sys->alloc(sys, self->buf_size);
d3699d57
   if (!buf) {
     ret = MSPACK_ERR_NOMEMORY;
     goto out;
   }
 
   oabd_sys = *sys;
   oabd_sys.read = oabd_sys_read;
   oabd_sys.write = oabd_sys_write;
 
   in_ofh.orig_sys = sys;
   in_ofh.orig_file = infh;
 
   out_ofh.orig_sys = sys;
   out_ofh.orig_file = outfh;
 
   while (target_size) {
     unsigned int blk_csize, blk_dsize, blk_ssize, blk_crc;
 
     if (sys->read(infh, buf, patchblk_SIZEOF) != patchblk_SIZEOF) {
       ret = MSPACK_ERR_READ;
       goto out;
     }
     blk_csize = EndGetI32(&buf[patchblk_PatchSize]);
     blk_dsize = EndGetI32(&buf[patchblk_TargetSize]);
     blk_ssize = EndGetI32(&buf[patchblk_SourceSize]);
     blk_crc   = EndGetI32(&buf[patchblk_CRC]);
 
     if (blk_dsize > block_max || blk_dsize > target_size ||
a7ac0e8d
         blk_ssize > block_max) {
d3699d57
       ret = MSPACK_ERR_DATAFORMAT;
       goto out;
     }
 
 
     window_size = (blk_ssize + 32767) & ~32767;
     window_size += blk_dsize;
     window_bits = 17;
 
     while (window_bits < 25 && (1U << window_bits) < window_size)
       window_bits++;
 
     in_ofh.available = blk_csize;
     out_ofh.crc = 0xffffffff;
 
     lzx = lzxd_init(&oabd_sys, (void *)&in_ofh, (void *)&out_ofh, window_bits,
a7ac0e8d
                     0, 4096, blk_dsize, 1);
d3699d57
     if (!lzx) {
       ret = MSPACK_ERR_NOMEMORY;
       goto out;
     }
     ret = lzxd_set_reference_data(lzx, sys, basefh, blk_ssize);
     if (ret != MSPACK_ERR_OK)
       goto out;
 
     ret = lzxd_decompress(lzx, blk_dsize);
     if (ret != MSPACK_ERR_OK)
       goto out;
 
     lzxd_free(lzx);
     lzx = NULL;
 
     /* Consume any trailing padding bytes before the next block */
a7ac0e8d
     ret = copy_fh(sys, infh, NULL, in_ofh.available, buf, self->buf_size);
     if (ret) goto out;
d3699d57
 
     if (out_ofh.crc != blk_crc) {
       ret = MSPACK_ERR_CHECKSUM;
       goto out;
     }
 
     target_size -= blk_dsize;
   }
 
  out:
32a2e7ce
   if (lzx) lzxd_free(lzx);
   if (outfh) sys->close(outfh);
   if (basefh) sys->close(basefh);
   if (infh) sys->close(infh);
   sys->free(buf);
d3699d57
 
   return ret;
 }
a7ac0e8d
 
 static int copy_fh(struct mspack_system *sys, struct mspack_file *infh,
                    struct mspack_file *outfh, size_t bytes_to_copy,
                    unsigned char *buf, int buf_size)
 {
     while (bytes_to_copy) {
         int run = buf_size;
         if ((size_t) run > bytes_to_copy) {
             run = (int) bytes_to_copy;
         }
         if (sys->read(infh, buf, run) != run) {
             return MSPACK_ERR_READ;
         }
         if (outfh && sys->write(outfh, buf, run) != run) {
             return MSPACK_ERR_WRITE;
         }
         bytes_to_copy -= run;
     }
     return MSPACK_ERR_OK;
 }
 
 static int oabd_param(struct msoab_decompressor *base, int param, int value) {
     struct msoab_decompressor_p *self = (struct msoab_decompressor_p *) base;
     if (self && param == MSOABD_PARAM_DECOMPBUF && value >= 16) {
         /* must be at least 16 bytes (patchblk_SIZEOF, oabblk_SIZEOF) */
         self->buf_size = value;
         return MSPACK_ERR_OK;
     }
     return MSPACK_ERR_ARGS;
 }