/*

  Zip File Writing Prototype Code

  haleyjd 08/28/09

*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef unsigned char byte;
typedef enum { false, true } boolean;

//
// zipfile - a single file to be added to the zip
//
typedef struct zipfile_s
{
   struct zipfile_s *next; // next file

   const char *name;       // file name
   const byte *data;       // file data
   unsigned int len;       // length of data
   long offset;            // offset in physical file after written
   unsigned int crc;       // cached CRC value
   unsigned int   extattr; // external attributes
   unsigned short intattr; // internal attributes
} zipfile_t;

//
// ziparchive - represents the entire zip file
//
typedef struct ziparchive_s
{
   zipfile_t *files;      // list of files
   zipfile_t *last;       // last file
   const char *filename;  // physical file name
   long diroffset;        // offset of central directory
   unsigned short fcount; // count of files
   unsigned int   dirlen; // length of central directory
} ziparchive_t;

//
// File output buffer
//
typedef struct outbuffer_s
{
   FILE *f;          // destination file
   byte *buffer;     // buffer
   unsigned int len; // total buffer length
   unsigned int idx; // current index
} outbuffer_t;

//=============================================================================
//
// Buffer Functions
//

void Buffer_CreateFile(outbuffer_t *ob, const char *filename, unsigned int len)
{
   ob->f = fopen(filename, "wb");

   ob->buffer = calloc(len, sizeof(byte));

   ob->len = len;
   ob->idx = 0;
}

void Buffer_Flush(outbuffer_t *ob)
{
   if(ob->idx)
   {
      fwrite(ob->buffer, ob->idx, sizeof(byte), ob->f);
      ob->idx = 0;
   }
}

void Buffer_Close(outbuffer_t *ob)
{
   Buffer_Flush(ob);
   fclose(ob->f);
   free(ob->buffer);
}

long Buffer_Tell(outbuffer_t *ob)
{
   return ftell(ob->f);
}

void Buffer_Write(outbuffer_t *ob, const void *data, unsigned int size)
{
   const byte *src = data;
   unsigned int writeAmt;
   unsigned int bytesToWrite = size;

   while(bytesToWrite)
   {
      writeAmt = ob->len - ob->idx;
      
      if(!writeAmt)
      {
         Buffer_Flush(ob);
         writeAmt = ob->len;
      }

      if(bytesToWrite < writeAmt)
         writeAmt = bytesToWrite;

      memcpy(&(ob->buffer[ob->idx]), src, writeAmt);

      ob->idx += writeAmt;
      src += writeAmt;
      bytesToWrite -= writeAmt;
   }
}

void Buffer_WriteUint32(outbuffer_t *ob, unsigned int num)
{
   Buffer_Write(ob, &num, sizeof(unsigned int));
}

void Buffer_WriteUint16(outbuffer_t *ob, unsigned short num)
{
   Buffer_Write(ob, &num, sizeof(unsigned short));
}

//=============================================================================
//
// Zip Functions
//

//
// Zip_Create
//
void Zip_Create(ziparchive_t *zip, const char *filename)
{
   memset(zip, 0, sizeof(ziparchive_t));
   zip->files = zip->last = NULL;
   zip->filename = filename;
}

//
// Zip_AddFile
//
zipfile_t *Zip_AddFile(ziparchive_t *zip, const char *name, const byte *data, 
                       unsigned int len)
{
   zipfile_t *file = calloc(1, sizeof(zipfile_t));

   file->name = name;
   file->data = data;
   file->len  = len;

   if(zip->last)
   {
      zip->last->next = file;
      zip->last = file;
   }
   else
      zip->files = zip->last = file;

   zip->fcount++;

   return file;
}

//=============================================================================
//
// CRC routine
//

#define CRC32_IEEE_POLY 0xEDB88320

static unsigned int crc32_table[256];

//
// M_CRC32BuildTable
//
// Builds the polynomial table for CRC32.
//
static void CRC32BuildTable(void)
{
   unsigned int i, j;
   
   for(i = 0; i < 256; ++i)
   {
      unsigned int val = i;
      
      for(j = 0; j < 8; ++j)
      {
         if(val & 1)
            val = (val >> 1) ^ CRC32_IEEE_POLY;
         else
            val >>= 1;
      }
      
      crc32_table[i] = val;
   }
}

//
// M_CRC32Initialize
//
// Builds the table if it hasn't been built yet. Doesn't do anything
// else, since CRC32 starting value has already been set properly by
// the main hash init routine.
//
static void CRC32Initialize(void)
{
   static boolean tablebuilt = false;
   
   // build the CRC32 table if it hasn't been built yet
   if(!tablebuilt)
   {
      CRC32BuildTable();
      tablebuilt = true;
   }
   
   // zero is the appropriate starting value for CRC32, so we need do nothing
   // special here
}

//
// M_CRC32HashData
//
// Calculates a running CRC32 for the provided block of data.
//
static unsigned int CRC32HashData(byte *data, unsigned int len)
{
   unsigned int crc = 0xFFFFFFFF;
   
   while(len)
   {
      byte idx = (byte)(((int)crc ^ *data++) & 0xff);
      
      crc = crc32_table[idx] ^ (crc >> 8);
      
      --len;
   }
   
   return crc ^ 0xFFFFFFFF;
}

//
// End CRC routine
//
//=============================================================================

//
// Zip_WriteFile
//
void Zip_WriteFile(zipfile_t *file, outbuffer_t *ob)
{
   unsigned short date, time;
   unsigned short namelen;

   Buffer_Flush(ob);

   file->offset = Buffer_Tell(ob);

   Buffer_WriteUint32(ob, 0x04034b50); // local file header signature
   Buffer_WriteUint16(ob, 0x0A);       // version needed to extract (1.0)
   Buffer_WriteUint16(ob, 0);          // general purpose bit flag
   Buffer_WriteUint16(ob, 0);          // compression method == store
   
   // make up a date/time
   time = (35 << 5) | (3 << 11);
   date = 3 | (5 << 5) | (113 << 9);
   
   Buffer_WriteUint16(ob, time);       // file time (3:35)
   Buffer_WriteUint16(ob, date);       // file date (3/5/2093)

   if(file->len)
   {
      CRC32Initialize();
      file->crc = CRC32HashData(file->data, file->len);
   }
   else
      file->crc = 0;

   Buffer_WriteUint32(ob, file->crc);  // CRC-32
   Buffer_WriteUint32(ob, file->len);  // compressed size
   Buffer_WriteUint32(ob, file->len);  // uncompressed size

   namelen = (unsigned short)strlen(file->name);
   Buffer_WriteUint16(ob, namelen);    // filename length
   Buffer_WriteUint16(ob, 0);          // extra field length

   // write file name
   Buffer_Write(ob, file->name, namelen);

   // write file contents
   if(file->len)
      Buffer_Write(ob, file->data, file->len);
}

//
// Zip_WriteDirEntry
//
void Zip_WriteDirEntry(ziparchive_t *zip, zipfile_t *file, outbuffer_t *ob)
{
   unsigned short time, date;
   unsigned short namelen;

   Buffer_WriteUint32(ob, 0x02014b50); // central file header signature
   Buffer_WriteUint16(ob, 0x0B14);     // version made by (Info-Zip NTFS)
   Buffer_WriteUint16(ob, 0x0A);       // version needed to extract (1.0)
   Buffer_WriteUint16(ob, 0);          // general purpose bit flag
   Buffer_WriteUint16(ob, 0);          // compression method == store

   // make up a date/time
   time = (35 << 5) | (3 << 11);
   date = 3 | (5 << 5) | (113 << 9);
   
   Buffer_WriteUint16(ob, time);       // file time (3:35)
   Buffer_WriteUint16(ob, date);       // file date (3/5/2093)
   Buffer_WriteUint32(ob, file->crc);  // CRC-32 (already calculated)
   Buffer_WriteUint32(ob, file->len);  // compressed size
   Buffer_WriteUint32(ob, file->len);  // uncompressed size

   namelen = (unsigned short)strlen(file->name);
   Buffer_WriteUint16(ob, namelen);     // filename length
   Buffer_WriteUint16(ob, 0);           // extra field length
   Buffer_WriteUint16(ob, 0);           // file comment length
   Buffer_WriteUint16(ob, 0);           // disk number start
   
   Buffer_WriteUint16(ob, file->intattr); // internal attributes
   Buffer_WriteUint32(ob, file->extattr); // external attributes
   Buffer_WriteUint32(ob, file->offset);  // local header offset

   // write the file name
   Buffer_Write(ob, file->name, namelen);

   zip->dirlen += (46 + namelen);
}

//
// Zip_WriteEndOfDir
//
void Zip_WriteEndOfDir(ziparchive_t *zip, outbuffer_t *ob)
{
   Buffer_WriteUint32(ob, 0x06054b50);     // end of central dir signature
   Buffer_WriteUint16(ob, 0);              // number of disk
   Buffer_WriteUint16(ob, 0);              // no. of disk with central dir
   Buffer_WriteUint16(ob, zip->fcount);    // no. of central dir entries on disk
   Buffer_WriteUint16(ob, zip->fcount);    // no. of central dir entries total
   Buffer_WriteUint32(ob, zip->dirlen);    // size of central directory
   Buffer_WriteUint32(ob, zip->diroffset); // offset of central dir
   Buffer_WriteUint16(ob, 0);              // length of zip comment
}

//
// Zip_Write
//
void Zip_Write(ziparchive_t *zip)
{
   outbuffer_t ob;
   zipfile_t *curfile;

   Buffer_CreateFile(&ob, zip->filename, 16384);

   // write files
   curfile = zip->files;

   while(curfile)
   {
      Zip_WriteFile(curfile, &ob);
      curfile = curfile->next;
   }

   Buffer_Flush(&ob);

   zip->diroffset = Buffer_Tell(&ob);

   // write central directory
   curfile = zip->files;

   while(curfile)
   {
      Zip_WriteDirEntry(zip, curfile, &ob);
      curfile = curfile->next;
   }

   // write end of central directory
   Zip_WriteEndOfDir(zip, &ob);

   // close the file
   Buffer_Close(&ob);
}

//
// main
//
int main(void)
{
   ziparchive_t zip;
   zipfile_t *foo;
   zipfile_t *bar_folder;
   zipfile_t *baz;

   const char   *foodata = "Hello Zip World!\n";
   unsigned int  bazdata = 0xDEADBEEF;

   Zip_Create(&zip, "test.zip");

   // add some files
   foo        = Zip_AddFile(&zip, "foo.txt", foodata,          strlen(foodata));
   bar_folder = Zip_AddFile(&zip, "bar/",    NULL,             0);
   baz        = Zip_AddFile(&zip, "bar/baz", (byte *)&bazdata, sizeof(unsigned int));

   // set attributes
   foo->intattr        = 0x01; // is a text file
   foo->extattr        = 0x20; // set archive flag
   bar_folder->extattr = 0x10; // set directory flag
   baz->extattr        = 0x20; // set archive flag

   Zip_Write(&zip);

   return 0;
}

// EOF