/*
 * COPYRIGHT:   See COPYING in the top level directory
 * PROJECT:     ReactOS cabinet manager
 * FILE:        tools/cabman/cabinet.h
 * PURPOSE:     Cabinet definitions
 */

#pragma once

#if defined(_WIN32)
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
#else
    #include <typedefs.h>
    #include <unistd.h>
#endif

#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <time.h>

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

#ifndef PATH_MAX
#define PATH_MAX MAX_PATH
#endif

#if defined(_WIN32)
#define DIR_SEPARATOR_CHAR '\\'
#define DIR_SEPARATOR_STRING "\\"

#define strcasecmp _stricmp
#define strdup _strdup
#else
#define DIR_SEPARATOR_CHAR '/'
#define DIR_SEPARATOR_STRING "/"
#endif // _WIN32

inline LONG GetSizeOfFile(FILE* handle)
{
    LONG size;
    LONG currentPos = ftell(handle);

    if (fseek(handle, 0, SEEK_END) != 0)
        return (LONG)-1;

    size = ftell(handle);
    fseek(handle, 0, SEEK_SET);
    return size;
}

/* Debugging */

#define NORMAL_MASK    0x000000FF
#define SPECIAL_MASK   0xFFFFFF00
#define MIN_TRACE      0x00000001
#define MID_TRACE      0x00000002
#define MAX_TRACE      0x00000003

#define DEBUG_MEMORY   0x00000100

#if DBG

extern ULONG DebugTraceLevel;

#undef DPRINT
#define DPRINT(_t_, _x_) \
    if ((_t_ > NORMAL_MASK) \
        ? (DebugTraceLevel & _t_) > NORMAL_MASK \
        : (DebugTraceLevel & NORMAL_MASK) >= _t_) { \
        printf("(%s:%d)(%s) ", __FILE__, __LINE__, __FUNCTION__); \
        printf _x_ ; \
    }

#undef ASSERT
#define ASSERT(_b_) { \
    if (!(_b_)) { \
        printf("(%s:%d)(%s) ASSERTION: ", __FILE__, __LINE__, __FUNCTION__); \
        printf(#_b_); \
        exit(0); \
    } \
}

#else /* DBG */

#undef DPRINT
#define DPRINT(_t_, _x_) do { } while(0)

#undef ASSERT
#define ASSERT(_x_)

#endif /* DBG */


/* Cabinet constants */

#define CAB_SIGNATURE        0x4643534D // "MSCF"
#define CAB_VERSION          0x0103
#define CAB_BLOCKSIZE        32768

#define CAB_COMP_MASK        0x00FF
#define CAB_COMP_NONE        0x0000
#define CAB_COMP_MSZIP       0x0001
#define CAB_COMP_QUANTUM     0x0002
#define CAB_COMP_LZX         0x0003

#define CAB_FLAG_HASPREV     0x0001
#define CAB_FLAG_HASNEXT     0x0002
#define CAB_FLAG_RESERVE     0x0004

#define CAB_ATTRIB_READONLY  0x0001
#define CAB_ATTRIB_HIDDEN    0x0002
#define CAB_ATTRIB_SYSTEM    0x0004
#define CAB_ATTRIB_VOLUME    0x0008
#define CAB_ATTRIB_DIRECTORY 0x0010
#define CAB_ATTRIB_ARCHIVE   0x0020
#define CAB_ATTRIB_EXECUTE   0x0040
#define CAB_ATTRIB_UTF_NAME  0x0080

#define CAB_FILE_MAX_FOLDER  0xFFFC
#define CAB_FILE_CONTINUED   0xFFFD
#define CAB_FILE_SPLIT       0xFFFE
#define CAB_FILE_PREV_NEXT   0xFFFF


/* Cabinet structures */

typedef struct _CFHEADER
{
    ULONG Signature;        // File signature 'MSCF' (CAB_SIGNATURE)
    ULONG Reserved1;        // Reserved field
    ULONG CabinetSize;      // Cabinet file size
    ULONG Reserved2;        // Reserved field
    ULONG FileTableOffset;  // Offset of first CFFILE
    ULONG Reserved3;        // Reserved field
    USHORT Version;          // Cabinet version (CAB_VERSION)
    USHORT FolderCount;      // Number of folders
    USHORT FileCount;        // Number of files
    USHORT Flags;            // Cabinet flags (CAB_FLAG_*)
    USHORT SetID;            // Cabinet set id
    USHORT CabinetNumber;    // Zero-based cabinet number
/* Optional fields (depends on Flags)
    USHORT CabinetResSize    // Per-cabinet reserved area size
    char     FolderResSize     // Per-folder reserved area size
    char     FileResSize       // Per-file reserved area size
    char     CabinetReserved[] // Per-cabinet reserved area
    char     CabinetPrev[]     // Name of previous cabinet file
    char     DiskPrev[]        // Name of previous disk
    char     CabinetNext[]     // Name of next cabinet file
    char     DiskNext[]        // Name of next disk
 */
} CFHEADER, *PCFHEADER;


typedef struct _CFFOLDER
{
    ULONG DataOffset;       // Absolute offset of first CFDATA block in this folder
    USHORT DataBlockCount;   // Number of CFDATA blocks in this folder in this cabinet
    USHORT CompressionType;  // Type of compression used for all CFDATA blocks in this folder
/* Optional fields (depends on Flags)
    char     FolderReserved[]  // Per-folder reserved area
 */
} CFFOLDER, *PCFFOLDER;


typedef struct _CFFILE
{
    ULONG FileSize;         // Uncompressed file size in bytes
    ULONG FileOffset;       // Uncompressed offset of file in the folder
    USHORT FileControlID;    // File control ID (CAB_FILE_*)
    USHORT FileDate;         // File date stamp, as used by DOS
    USHORT FileTime;         // File time stamp, as used by DOS
    USHORT Attributes;       // File attributes (CAB_ATTRIB_*)
    /* After this is the NULL terminated filename */
} CFFILE, *PCFFILE;


typedef struct _CFDATA
{
    ULONG Checksum;         // Checksum of CFDATA entry
    USHORT CompSize;         // Number of compressed bytes in this block
    USHORT UncompSize;       // Number of uncompressed bytes in this block
/* Optional fields (depends on Flags)
    char  DataReserved[]    // Per-datablock reserved area
 */
} CFDATA, *PCFDATA;

typedef struct _CFDATA_NODE
{
    struct _CFDATA_NODE *Next;
    struct _CFDATA_NODE *Prev;
    ULONG       ScratchFilePosition;    // Absolute offset in scratch file
    ULONG       AbsoluteOffset;         // Absolute offset in cabinet
    ULONG       UncompOffset;           // Uncompressed offset in folder
    CFDATA         Data;
} CFDATA_NODE, *PCFDATA_NODE;

typedef struct _CFFOLDER_NODE
{
    struct _CFFOLDER_NODE *Next;
    struct _CFFOLDER_NODE *Prev;
    ULONG         UncompOffset;     // File size accumulator
    ULONG         AbsoluteOffset;
    ULONG         TotalFolderSize;  // Total size of folder in current disk
    PCFDATA_NODE     DataListHead;
    PCFDATA_NODE     DataListTail;
    ULONG         Index;
    bool             Commit;           // true if the folder should be committed
    bool             Delete;           // true if marked for deletion
    CFFOLDER         Folder;
} CFFOLDER_NODE, *PCFFOLDER_NODE;

typedef struct _CFFILE_NODE
{
    struct _CFFILE_NODE *Next;
    struct _CFFILE_NODE *Prev;
    CFFILE              File;
    char*               FileName;
    PCFDATA_NODE        DataBlock;      // First data block of file. NULL if not known
    bool                Commit;         // true if the file data should be committed
    bool                Delete;         // true if marked for deletion
    PCFFOLDER_NODE      FolderNode;     // Folder this file belong to
} CFFILE_NODE, *PCFFILE_NODE;

typedef struct _SEARCH_CRITERIA
{
    struct _SEARCH_CRITERIA  *Next;   // Pointer to next search criteria
    struct _SEARCH_CRITERIA  *Prev;   // Pointer to previous search criteria
    char*                    Search;  // The actual search criteria
} SEARCH_CRITERIA, *PSEARCH_CRITERIA;

typedef struct _CAB_SEARCH
{
    PCFFILE_NODE      Next;      // Pointer to next node
    PCFFILE           File;      // Pointer to current CFFILE
    char*             FileName;  // Current filename
} CAB_SEARCH, *PCAB_SEARCH;


/* Constants */

/* Status codes */
#define CAB_STATUS_SUCCESS       0x00000000
#define CAB_STATUS_FAILURE       0x00000001
#define CAB_STATUS_NOMEMORY      0x00000002
#define CAB_STATUS_CANNOT_OPEN   0x00000003
#define CAB_STATUS_CANNOT_CREATE 0x00000004
#define CAB_STATUS_CANNOT_READ   0x00000005
#define CAB_STATUS_CANNOT_WRITE  0x00000006
#define CAB_STATUS_FILE_EXISTS   0x00000007
#define CAB_STATUS_INVALID_CAB   0x00000008
#define CAB_STATUS_NOFILE        0x00000009
#define CAB_STATUS_UNSUPPCOMP    0x0000000A



/* Codecs */

class CCABCodec
{
public:
    /* Default constructor */
    CCABCodec() {};
    /* Default destructor */
    virtual ~CCABCodec() {};
    /* Compresses a data block */
    virtual ULONG Compress(void* OutputBuffer,
                           void* InputBuffer,
                           ULONG InputLength,
                           PULONG OutputLength) = 0;
    /* Uncompresses a data block */
    virtual ULONG Uncompress(void* OutputBuffer,
                             void* InputBuffer,
                             ULONG InputLength,
                             PULONG OutputLength) = 0;
};


/* Codec status codes */
#define CS_SUCCESS      0x0000  /* All data consumed */
#define CS_NOMEMORY     0x0001  /* Not enough free memory */
#define CS_BADSTREAM    0x0002  /* Bad data stream */


/* Codec indentifiers */
#define CAB_CODEC_RAW   0x00
#define CAB_CODEC_LZX   0x01
#define CAB_CODEC_MSZIP 0x02



/* Classes */

#ifndef CAB_READ_ONLY

class CCFDATAStorage
{
public:
    /* Default constructor */
    CCFDATAStorage();
    /* Default destructor */
    virtual ~CCFDATAStorage();
    ULONG Create();
    ULONG Destroy();
    ULONG Truncate();
    ULONG Position();
    ULONG Seek(LONG Position);
    ULONG ReadBlock(PCFDATA Data, void* Buffer, PULONG BytesRead);
    ULONG WriteBlock(PCFDATA Data, void* Buffer, PULONG BytesWritten);
private:
    char FullName[PATH_MAX];
    FILE* FileHandle;
};

#endif /* CAB_READ_ONLY */

class CCabinet
{
public:
    /* Default constructor */
    CCabinet();
    /* Default destructor */
    virtual ~CCabinet();
    /* Determines if a character is a separator */
    bool IsSeparator(char Char);
    /* Replaces \ or / with the one used be the host environment */
    char* ConvertPath(char* Path, bool Allocate);
    /* Returns a pointer to the filename part of a fully qualified filename */
    char* GetFileName(char* Path);
    /* Removes a filename from a fully qualified filename */
    void RemoveFileName(char* Path);
    /* Normalizes a path */
    bool NormalizePath(char* Path, ULONG Length);
    /* Returns name of cabinet file */
    char* GetCabinetName();
    /* Sets the name of the cabinet file */
    void SetCabinetName(char* FileName);
    /* Sets destination path for extracted files */
    void SetDestinationPath(char* DestinationPath);
    /* Sets cabinet reserved file */
    bool SetCabinetReservedFile(char* FileName);
    /* Returns cabinet reserved file */
    char* GetCabinetReservedFile();
    /* Returns destination path */
    char* GetDestinationPath();
    /* Returns zero-based current disk number */
    ULONG GetCurrentDiskNumber();
    /* Opens the current cabinet file */
    ULONG Open();
    /* Closes the current open cabinet file */
    void Close();
    /* Locates the first file in the current cabinet file that matches a search criteria */
    ULONG FindFirst(PCAB_SEARCH Search);
    /* Locates the next file in the current cabinet file */
    ULONG FindNext(PCAB_SEARCH Search);
    /* Extracts a file from the current cabinet file */
    ULONG ExtractFile(char* FileName);
    /* Select codec engine to use */
    void SelectCodec(LONG Id);
    /* Returns whether a codec engine is selected */
    bool IsCodecSelected();
    /* Adds a search criteria for adding files to a simple cabinet, displaying files in a cabinet or extracting them */
    ULONG AddSearchCriteria(char* SearchCriteria);
    /* Destroys the search criteria list */
    void DestroySearchCriteria();
    /* Returns whether we have search criteria */
    bool HasSearchCriteria();

#ifndef CAB_READ_ONLY
    /* Creates a simple cabinet based on the search criteria data */
    bool CreateSimpleCabinet();
    /* Sets the codec to use for compression (based on a string value) */
    bool SetCompressionCodec(char* CodecName);
    /* Creates a new cabinet file */
    ULONG NewCabinet();
    /* Forces a new disk to be created */
    ULONG NewDisk();
    /* Forces a new folder to be created */
    ULONG NewFolder();
    /* Writes a file to scratch storage */
    ULONG WriteFileToScratchStorage(PCFFILE_NODE FileNode);
    /* Forces the current disk to be written */
    ULONG WriteDisk(ULONG MoreDisks);
    /* Commits the current disk */
    ULONG CommitDisk(ULONG MoreDisks);
    /* Closes the current disk */
    ULONG CloseDisk();
    /* Closes the current cabinet */
    ULONG CloseCabinet();
    /* Adds a file to the current disk */
    ULONG AddFile(char* FileName);
    /* Sets the maximum size of the current disk */
    void SetMaxDiskSize(ULONG Size);
#endif /* CAB_READ_ONLY */

    /* Default event handlers */

    /* Handler called when a file is about to be overridden */
    virtual bool OnOverwrite(PCFFILE Entry, char* FileName);
    /* Handler called when a file is about to be extracted */
    virtual void OnExtract(PCFFILE Entry, char* FileName);
    /* Handler called when a new disk is to be processed */
    virtual void OnDiskChange(char* CabinetName, char* DiskLabel);
#ifndef CAB_READ_ONLY
    /* Handler called when a file is about to be added */
    virtual void OnAdd(PCFFILE Entry, char* FileName);
    /* Handler called when a cabinet need a name */
    virtual bool OnCabinetName(ULONG Number, char* Name);
    /* Handler called when a disk needs a label */
    virtual bool OnDiskLabel(ULONG Number, char* Label);
#endif /* CAB_READ_ONLY */
private:
    PCFFOLDER_NODE LocateFolderNode(ULONG Index);
    ULONG GetAbsoluteOffset(PCFFILE_NODE File);
    ULONG LocateFile(char* FileName, PCFFILE_NODE *File);
    ULONG ReadString(char* String, LONG MaxLength);
    ULONG ReadFileTable();
    ULONG ReadDataBlocks(PCFFOLDER_NODE FolderNode);
    PCFFOLDER_NODE NewFolderNode();
    PCFFILE_NODE NewFileNode();
    PCFDATA_NODE NewDataNode(PCFFOLDER_NODE FolderNode);
    void DestroyDataNodes(PCFFOLDER_NODE FolderNode);
    void DestroyFileNodes();
    void DestroyDeletedFileNodes();
    void DestroyFolderNodes();
    void DestroyDeletedFolderNodes();
    ULONG ComputeChecksum(void* Buffer, ULONG Size, ULONG Seed);
    ULONG ReadBlock(void* Buffer, ULONG Size, PULONG BytesRead);
    bool MatchFileNamePattern(char* FileName, char* Pattern);
#ifndef CAB_READ_ONLY
    ULONG InitCabinetHeader();
    ULONG WriteCabinetHeader(bool MoreDisks);
    ULONG WriteFolderEntries();
    ULONG WriteFileEntries();
    ULONG CommitDataBlocks(PCFFOLDER_NODE FolderNode);
    ULONG WriteDataBlock();
    ULONG GetAttributesOnFile(PCFFILE_NODE File);
    ULONG SetAttributesOnFile(char* FileName, USHORT FileAttributes);
    ULONG GetFileTimes(FILE* FileHandle, PCFFILE_NODE File);
#if !defined(_WIN32)
    void ConvertDateAndTime(time_t* Time, PUSHORT DosDate, PUSHORT DosTime);
#endif
#endif /* CAB_READ_ONLY */
    ULONG CurrentDiskNumber;    // Zero based disk number
    char CabinetName[256];     // Filename of current cabinet
    char CabinetPrev[256];     // Filename of previous cabinet
    char DiskPrev[256];        // Label of cabinet in file CabinetPrev
    char CabinetNext[256];     // Filename of next cabinet
    char DiskNext[256];        // Label of cabinet in file CabinetNext
    ULONG TotalHeaderSize;      // Size of header and optional fields
    ULONG NextFieldsSize;       // Size of next cabinet name and next disk label
    ULONG TotalFolderSize;      // Size of all folder entries
    ULONG TotalFileSize;        // Size of all file entries
    ULONG FolderUncompSize;     // Uncompressed size of folder
    ULONG BytesLeftInBlock;     // Number of bytes left in current block
    bool ReuseBlock;
    char DestPath[PATH_MAX];
    char CabinetReservedFile[PATH_MAX];
    void* CabinetReservedFileBuffer;
    ULONG CabinetReservedFileSize;
    FILE* FileHandle;
    bool FileOpen;
    CFHEADER CABHeader;
    ULONG CabinetReserved;
    ULONG FolderReserved;
    ULONG DataReserved;
    PCFFOLDER_NODE FolderListHead;
    PCFFOLDER_NODE FolderListTail;
    PCFFOLDER_NODE CurrentFolderNode;
    PCFDATA_NODE CurrentDataNode;
    PCFFILE_NODE FileListHead;
    PCFFILE_NODE FileListTail;
    PSEARCH_CRITERIA CriteriaListHead;
    PSEARCH_CRITERIA CriteriaListTail;
    CCABCodec *Codec;
    LONG CodecId;
    bool CodecSelected;
    void* InputBuffer;
    void* CurrentIBuffer;               // Current offset in input buffer
    ULONG CurrentIBufferSize;   // Bytes left in input buffer
    void* OutputBuffer;
    ULONG TotalCompSize;        // Total size of current CFDATA block
    void* CurrentOBuffer;               // Current offset in output buffer
    ULONG CurrentOBufferSize;   // Bytes left in output buffer
    ULONG BytesLeftInCabinet;
    bool RestartSearch;
    ULONG LastFileOffset;       // Uncompressed offset of last extracted file
#ifndef CAB_READ_ONLY
    ULONG LastBlockStart;       // Uncompressed offset of last block in folder
    ULONG MaxDiskSize;
    ULONG DiskSize;
    ULONG PrevCabinetNumber;    // Previous cabinet number (where split file starts)
    bool CreateNewDisk;
    bool CreateNewFolder;

    CCFDATAStorage *ScratchFile;
    FILE* SourceFile;
    bool ContinueFile;
    ULONG TotalBytesLeft;
    bool BlockIsSplit;                  // true if current data block is split
    ULONG NextFolderNumber;     // Zero based folder number
#endif /* CAB_READ_ONLY */
};

/* EOF */