/******************************************************************************
 *
 *
 *    ZIPV  -- SHARE compatible ZIP file list utility.
 *
 *
 *    This code was written as a demonstration of how to read
 *    the ZIP file format.  It does not attempt to support
 *    ZIP files which span multiple diskettes (it is not even
 *    clear to me how that would be done).  It does support
 *    multiple ZIP file names on the command line and does
 *    support wildcards.  Under DOS 3.0 or above all files are
 *    opened in SHARE compatible mode.
 *
 *    The output format is patterned after (stolen from) Vern
 *    Buerg's ARCV format.
 *
 *
 *    It appears that there are two ways to extract the names
 *    of files contained in a ZIP file.  One copy is kept in a
 *    "local file header" which proceeds the compressed file.  The
 *    other copy is kept in the "central directory".  The notes
 *    that accompany PKZIP are not clear whether or not these
 *    will always agree.  I chose to read the information from
 *    the central directory, even though I have to read past
 *    all of the local file headers to get there.
 *
 *    This code is placed in the public domain.  You may do whatever
 *    you like with it.
 *
 *    Ken Brown
 *
 *
 *
 *
 *    Syntax:
 *
 *       ZIPV file_spec1[.ZIP] [file_spec2[.ZIP] ...]
 *
 *
 *
 ******************************************************************************
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <bios.h>
#include <dos.h>
#include <io.h>
#include <share.h>
#include <fcntl.h>


/* These structures must be byte aligned */

#pragma pack(1)

/* Structure of the local file header */

struct LOCAL_HEADER {
   long  Signature;
   int   Version;
   int   BitFlag;
   int   CompressionMethod;
   int   FileTime;
   int   FileDate;
   long  CRC32;
   long  CompressedSize;
   long  UnCompressedSize;
   int   FileNameLength;
   int   ExtraFieldLength;
   };

/* Structure of the central directory record */
struct CENTRAL_RECORD {
   long  Signature;
   int   VersionMadeBy;
   int   VersionNeeded;
   int   BitFlag;
   int   CompressionMethod;
   int   FileTime;
   int   FileDate;
   long  CRC32;
   long  CompressedSize;
   long  UnCompressedSize;
   int   FileNameLength;
   int   ExtraFieldLength;
   int   CommentFieldLength;
   int   DiskStartNumber;
   int   InternalAttributes;
   long  ExternalAttributes;
   long  LocalHeaderOffset;
   };

#pragma pack()



/*
 *    This structure will be used to build a list of files
 *    from a wildcard file_spec.
 *
*/
struct FILELIST {
   char              FileName[83];
   struct FILELIST   *NextFile;
   }




/* Global variables */

struct FILELIST *FileList,
                *FilePtr;




static int   TotalFiles;

static long  TotalBytes;
static long  TotalUnCompressedBytes;



/* Function prototypes */

extern  int main(int argc,char * *argv);
extern  int ProcessFile(char *ZipFileName);
extern  int ListZipFile(char *ZipFileName);
extern  int DisplayHeaderRecord(struct CENTRAL_RECORD CentralDirRecord,char *FileName);
extern  int DisplayTotals(void );
extern  char *DisplayCompressionType(int Compression);
extern  char *DisplayMonthName(int Month);
extern  int BuildFileList(char *FileMask);


main(int argc, char *argv[])
{
   int   Counter;
   char  ZipFileName[83],
         *StrPtr;

   if(argc < 2) {
      printf("Syntax: ZIPV file_spec1[.ZIP] [file_spec2[.ZIP] ...]\n");
      exit(1);
      }

   for(Counter = 1; Counter < argc; Counter++) {
      if(strlen(argv[Counter]) > 82) {
         printf("Syntax: ZIPV file_spec1[.ZIP] [file_spec2[.ZIP] ...]\n");
         exit(1);
         }

      strcpy(ZipFileName,argv[Counter]);
      strupr(ZipFileName);

      /* If the .ZIP extension is missing add it. */

      if(strchr(ZipFileName,'.') == NULL) {
         strcat(ZipFileName,".ZIP");
         }

      ProcessFile(ZipFileName);
      }

   exit(0);


}

/* Process each command line argument (may contain a wildcard). */

ProcessFile(char *ZipFileName)
{

   FileList = NULL;

   /* Build a list of file names matching the file_spec. */

   BuildFileList(ZipFileName);

   /* If FileList == NULL no matching files were found. */

   if(FileList == NULL) {
      if(strchr(ZipFileName,'*') == NULL && strchr(ZipFileName,'?') == NULL) {
         printf("File not found: %s\n\n",ZipFileName);
         }
      else {
         printf("No matching files found: %s\n\n",ZipFileName);
         }
      }
   else {
      FilePtr = FileList;
      while(FilePtr != NULL) {
         ListZipFile(FilePtr->FileName);
         FilePtr = FilePtr->NextFile;
         }
      }

   return 0;

}


ListZipFile(char *ZipFileName)
{
   int   ZipFileHandle,
         BytesRead;

   long  FileOffset;

   char  *FileName,
         *ReadPtr;

   struct LOCAL_HEADER LocalFileHeader;

   struct CENTRAL_RECORD CentralDirRecord;


   /* Open the ZIP file.  Use sopen() if possible. */

   if(_osmajor >= 3) {
      ZipFileHandle = sopen(ZipFileName,O_RDONLY|O_BINARY,SH_DENYWR);

      if(ZipFileHandle == -1) {
         printf("File is currently locked, cannot open: %s\n\n",ZipFileName);
         return 0;
         }
      }
   else {
      ZipFileHandle = open(ZipFileName,O_RDONLY|O_BINARY);

      if(ZipFileHandle == -1) {
         printf("Cannot open: %s\n\n",ZipFileName);
         return 0;
         }
      }



   printf("ZIP File: %s\n",ZipFileName);
   printf("\n");

   TotalFiles = 0;
   TotalBytes = 0L;
   TotalUnCompressedBytes = 0L;

   printf("Name          Length    Stowage    SF   Size now  Date       Time    CRC \n");
   printf("============  ========  ========  ====  ========  =========  ======  ========\n");


   /* Read the signature from the first local header */

   BytesRead = read(ZipFileHandle,(char *)&LocalFileHeader,sizeof(long));

   if(BytesRead != sizeof(long)) {
      printf("Not a ZIP file\n\n");
      close(ZipFileHandle);
      return 0;
      }
   if(LocalFileHeader.Signature != 0x04034b50) {
      printf("Not a ZIP file\n\n");
      close(ZipFileHandle);
      return 0;
      }

   /* Skip over all of the compressed files and get to the central
      directory */

   while(1) {

      ReadPtr = (char *)&LocalFileHeader;
      ReadPtr += 4;

      /* Read the remainder of the local file header (we already read
         the signature. */

      BytesRead = read(ZipFileHandle,ReadPtr,sizeof(struct LOCAL_HEADER)-4);

      if(BytesRead != sizeof(struct LOCAL_HEADER) - 4) {
         printf("Invalid ZIP file format\n\n");
         close(ZipFileHandle);
         return 0;
         }

      FileOffset = LocalFileHeader.FileNameLength +
                   LocalFileHeader.ExtraFieldLength +
                   LocalFileHeader.CompressedSize;

      /* Jump to the next local file header */

      if(lseek(ZipFileHandle,FileOffset,SEEK_CUR) == -1) {
         printf("Invalid ZIP file format\n\n");
         close(ZipFileHandle);
         return 0;
         }

      /* Read the next signature */

      BytesRead = read(ZipFileHandle,(char *)&LocalFileHeader,sizeof(long));
      if(BytesRead != sizeof(long)) {
         printf("Invalid ZIP file format\n\n");
         close(ZipFileHandle);
         return 0;
         }

      /* If we get a match we have found the beginning of the central
         directory. */

      if(LocalFileHeader.Signature == 0x02014b50) {
         break;
         }

      }


   CentralDirRecord.Signature = LocalFileHeader.Signature;

   /* Read the records in the central directory one at a time */
   
   while(1) {

      /* Read the remainder of the file header record (we already
         have the signature.  ReadPtr points into CentralDirRecord
         4 bytes from the beginning (right behind the signature. */

      ReadPtr = (char *)&CentralDirRecord;
      ReadPtr += 4;

      BytesRead = read(ZipFileHandle,ReadPtr,sizeof(struct CENTRAL_RECORD)-4);

      if(BytesRead != sizeof(struct CENTRAL_RECORD) - 4) {
         printf("Invalid ZIP file format\n\n");
         close(ZipFileHandle);
         return 0;
         }

      /* It is probably not possible for the FileName to have a length
         of 0 but who knows.  */

      if(CentralDirRecord.FileNameLength > 0) {

         /* The file name can be long so allocate space for the name as
            needed rather that write to a statically allocated array */

         if((FileName = malloc(CentralDirRecord.FileNameLength + 1)) == NULL) {
            printf("Out of memory\n\n");
            exit(1);
            }

         /* Read the file name. */

         BytesRead = read(ZipFileHandle,FileName,CentralDirRecord.FileNameLength);

         if(BytesRead != CentralDirRecord.FileNameLength) {
            printf("Invalid ZIP file format\n\n");
            close(ZipFileHandle);
            return 0;
            }

         /* Add the trailing \0 byte. */

         FileName[CentralDirRecord.FileNameLength] = '\0';

         /* Display the record. */

         DisplayHeaderRecord(CentralDirRecord,FileName);

         /* The file name is not needed any more. */

         free(FileName);
         }


      /* Skip over the extra field and the comment field */

      FileOffset = CentralDirRecord.ExtraFieldLength +
                   CentralDirRecord.CommentFieldLength;

      if(FileOffset > 0L) {
         if(lseek(ZipFileHandle,FileOffset,SEEK_CUR) == -1) {
            printf("Invalid ZIP file format\n\n");
            close(ZipFileHandle);
            return 0;
            }
         }


      /* Read the signature from the next record. */

      BytesRead = read(ZipFileHandle,(char *)&CentralDirRecord,sizeof(long));
      if(BytesRead != sizeof(long)) {
         printf("Invalid ZIP file format\n\n");
         close(ZipFileHandle);
         return 0;
         }

      /* If we get a match then we found the "End of central dir record"
         and we are done. */

      if(CentralDirRecord.Signature == 0x06054b50) {
         break;
         }

      }

   close(ZipFileHandle);

   DisplayTotals();

   return 0;

}


DisplayHeaderRecord(struct CENTRAL_RECORD CentralDirRecord, char *FileName)
{
   int   Month,
         Day,
         Year,
         Hour,
         Minutes,
         StowageFactor;
   char  *NamePtr;
   long  SizeReduction;

   if(CentralDirRecord.UnCompressedSize == 0) {
      StowageFactor = 0;
      }
   else {
      if(CentralDirRecord.CompressedSize <= CentralDirRecord.UnCompressedSize) {
         SizeReduction = CentralDirRecord.UnCompressedSize - CentralDirRecord.CompressedSize;
         if(SizeReduction == 0L) {
            StowageFactor = 0;
            }
         else {
            SizeReduction = SizeReduction * 100L + 50;
            StowageFactor = (int)(SizeReduction/CentralDirRecord.UnCompressedSize);
            }
         }
      else {
         SizeReduction = CentralDirRecord.CompressedSize - CentralDirRecord.UnCompressedSize;
         SizeReduction = SizeReduction * 100L + 50;
         StowageFactor = (int)(SizeReduction/CentralDirRecord.UnCompressedSize);
         StowageFactor *= -1;
         }
      }
   if(StowageFactor >= 100) {
      StowageFactor = 0;
      }


   /* Convert the DOS internal date and time format to something we can
      use for output. */

   Month = (CentralDirRecord.FileDate >> 5) & 0x0f;
   Day = CentralDirRecord.FileDate & 0x1f;
   Year = ((CentralDirRecord.FileDate >> 9) & 0x7f) + 80;

   if(CentralDirRecord.FileTime > 0) {
      Hour = (CentralDirRecord.FileTime >> 11) & 0x1f;
      Minutes = (CentralDirRecord.FileTime >> 5) & 0x3f;
      }
   else {
      Hour = 0;
      Minutes = 0;
      }

   /* The ZIP documentation says that path names are stored with '/'s
      rather than '\'s.  Look for a '/' and if so point to the file
      name rather than the whole path name. */

   if((NamePtr = strrchr(FileName,'/')) == NULL) {
      NamePtr = FileName;
      }

   printf("%-14s%8ld  %s   %2d%%  %8ld  %02d %s %2d  %02d:%02d   %08lX\n",
           NamePtr,
           CentralDirRecord.UnCompressedSize,
           DisplayCompressionType(CentralDirRecord.CompressionMethod),
           StowageFactor,
           CentralDirRecord.CompressedSize,
           Day,
           DisplayMonthName(Month),
           Year,
           Hour,
           Minutes,
           CentralDirRecord.CRC32);

   TotalFiles += 1;
   TotalBytes += CentralDirRecord.CompressedSize;
   TotalUnCompressedBytes += CentralDirRecord.UnCompressedSize;

   return 0;
}

DisplayTotals()
{
   int   StowageFactor;
   long  SizeReduction;

   printf("============  ========  ========  ====  ========  =========  ======  ========\n");

   if(TotalUnCompressedBytes == 0) {
      StowageFactor = 0;
      }
   else {
      if(TotalBytes <= TotalUnCompressedBytes) {
         SizeReduction = TotalUnCompressedBytes - TotalBytes;
         if(SizeReduction == 0L) {
            StowageFactor = 0;
            }
         else {
            SizeReduction = SizeReduction * 100L + 50;
            StowageFactor = (int)(SizeReduction/TotalUnCompressedBytes);
            }
         }
      else {
         SizeReduction = TotalBytes - TotalUnCompressedBytes;
         SizeReduction = SizeReduction * 100L + 50;
         StowageFactor = (int)(SizeReduction/TotalUnCompressedBytes);
         StowageFactor *= -1;
         }
      }
   if(StowageFactor >= 100) {
      StowageFactor = 0;
      }

   printf("*total%6d  %8ld             %2d%%  %8ld\n\n",
          TotalFiles,
          TotalUnCompressedBytes,
          StowageFactor,
          TotalBytes);

   return 0;
}


char *DisplayCompressionType(int Compression)
{
   switch (Compression) {
      case 0:
         return " Stored ";
         break;
      case 1:
         return " Shrunk ";
         break;
      case 2:
         return "Reduced1";
         break;
      case 3:
         return "Reduced2";
         break;
      case 4:
         return "Reduced3";
         break;
      case 5:
         return "Reduced4";
         break;
      default:
         return "Unknown ";
         break;
      }
}

char *DisplayMonthName(int Month)
{
   switch (Month) {
      case 1:
         return "Jan";
         break;
      case 2:
         return "Feb";
         break;
      case 3:
         return "Mar";
         break;
      case 4:
         return "Apr";
         break;
      case 5:
         return "May";
         break;
      case 6:
         return "Jun";
         break;
      case 7:
         return "Jul";
         break;
      case 8:
         return "Aug";
         break;
      case 9:
         return "Sep";
         break;
      case 10:
         return "Oct";
         break;
      case 11:
         return "Nov";
         break;
      case 12:
         return "Dec";
         break;
      }
}

/*
 * BuildFileList() uses the _dos_findfirst() and _dos_findnext() routines
 * to build a linked list of all files which match FileMask.
 *
 * _dos_findfirst() is from the Microsoft C library.  There is a Turbo C
 * equivalent but I don't know the name.
 *
 */

BuildFileList(char *FileMask)
{
   char  FileDirectory[83],
         *StrPtr;

   struct find_t FileInfo;
   struct stat StatBuffer;

   strcpy(FileDirectory,FileMask);
   if((StrPtr = strrchr(FileDirectory,'\\')) != NULL) {
      StrPtr++;
      *StrPtr = '\0';
      }
   else {
      if((StrPtr = strrchr(FileDirectory,':')) != NULL) {
         StrPtr++;
         *StrPtr = '\0';
         }
      else {
         FileDirectory[0] = '\0';
         }
      }

   if(_dos_findfirst(FileMask,_A_NORMAL|_A_RDONLY,&FileInfo) == 0) {
      do {
         if(FileList == NULL) {
            FileList = calloc(sizeof(struct FILELIST),1);

            /* This is not likely to happen but you never know. */

            if(FileList == NULL) {
               printf("Out of memory\n\n");
               exit(1);
               }
            FilePtr = FileList;
            }
         else {
            FilePtr->NextFile = calloc(sizeof(struct FILELIST),1);
            if(FilePtr->NextFile == NULL) {
               printf("Out of memory\n\n");
               exit(1);
               }
            FilePtr = FilePtr->NextFile;
            }

         /* Make a copy of the file name from the find_t structure. */

         strcpy(FilePtr->FileName,FileDirectory);
         strcat(FilePtr->FileName,FileInfo.name);


         } while (_dos_findnext(&FileInfo) == 0);
      }
   return 0;
}

