VFP implementation of GetZipComment and GetZipFilesList for ZIP acrchives
VFP class below allows retrieval of a ZIP file comment or a list/count of files in the ZIP archive. It uses VFP low level file functions (LLFF) to directly read the ZIP file.
$SAMPLECODE$
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 * Examples of using GetZipComment, GetZipFilesList and GetZipFileCount lcZipName = "..." DIMENSION laList[1] loZU = NEWOBJECT("ZipUtils", "ZipUtils.fxp") * Retrieve ZIP comment llOK = loZU.GetZipComment(lcZipName) IF NOT llOK ? "Error: " + loZU.cErrorMsg ELSE ? "ZIP comment: " + loZU.cZipComment ENDIF * Get a list of files in ZIP lnZipFileCount = loZU.GetZipFilesList(lcZipName, @laList) IF lnZipFileCount < 0 ? "Error: " + loZU.cErrorMsg ELSE ? "File count in ZIP: " + TRANSFORM(lnZipFileCount ) ENDIF * Get a file count in ZIP lnZipFileCount = loZU.GetZipFileCount(lcZipName) IF lnZipFileCount < 0 ? "Error: " + loZU.cErrorMsg ELSE ? "File count in ZIP: " + TRANSFORM(lnZipFileCount ) ENDIF
* ZipUtils.prg
DEFINE CLASS ZipUtils AS custom
cZipComment = ""
nErrorNum = 0
cErrorMsg = ""
cZipFileName = ""
nBufferSize = 2^16-1
hZipFile = 0
PROCEDURE GetZipComment
* End of central directory record:
* 1 4 end of central directory signature (0x06054b50)
* 5 2 number of this disk
* 7 2 number of the disk with the start of the central directory
* 9 2 total number of entries in the central directory on this disk
* 11 2 total number of entries in the central directory
* 13 4 size of the central directory
* 17 4 offset of start of central directory with respect to the starting disk number
* 21 2 .ZIP file comment size
* 23 nnn ZIP file comment (variable size)
LPARAMETERS tcZipFileName
LOCAL lcZipSignature, lcZipEoCdrSignature, lcReadBuffer, lnPos, lcZipEoCdr, lcZipComment, lnZipCommentSize
This.cZipComment = ""
This.cZipFileName = tcZipFileName
lcZipSignature = "PK" + CHR(03) + CHR(04)
lcZipEoCdrSignature = "PK" + CHR(05) + CHR(06)
This.hZipFile = FOPEN(tcZipFileName)
IF This.hZipFile < 1
This.cErrorMsg = "Cannot open ZIP file " + This.cZipFileName
This.nErrorNum = 1
RETURN .F.
ENDIF
lcReadBuffer = FREAD(This.hZipFile, This.nBufferSize)
IF NOT( lcZipSignature $ lcReadBuffer )
FCLOSE(This.hZipFile)
This.cErrorMsg = "Cannot find ZIP signature for file " + This.cZipFileName
This.nErrorNum = 2
RETURN .F.
ENDIF
lnPos = 0
FSEEK(This.hZipFile,0)
lcReadBuffer = ""
DO WHILE NOT FEOF(This.hZipFile)
lcReadBuffer = RIGHT(lcReadBuffer,4) + FREAD(This.hZipFile, This.nBufferSize)
lnPos = AT(lcZipEoCdrSignature, lcReadBuffer)
IF lnPos > 0
lcReadBuffer = lcReadBuffer + FREAD(This.hZipFile, This.nBufferSize)
EXIT
ENDIF
ENDDO
IF lnPos = 0
FCLOSE(This.hZipFile)
This.cErrorMsg = "Cannot find the end of central index signature for file " + This.cZipFileName
This.nErrorNum = 3
RETURN .F.
ENDIF
lcZipEoCdr = SUBSTR(lcReadBuffer, lnPos)
lnZipCommentSize = ASC(SUBSTR(lcZipEoCdr,21)) + ASC(SUBSTR(lcZipEoCdr,22))*256
This.cZipComment = SUBSTR(lcZipEoCdr,23, lnZipCommentSize)
FCLOSE(This.hZipFile)
RETURN .T.
ENDPROC
*-----------------------------------------------------------------------------------------
PROCEDURE GetZipFileCount(tcZipFileName)
RETURN This.GetZipFilesList(tcZipFileName)
PROCEDURE GetZipFilesList
* 1 4 central file header signature 4 bytes (0x02014b50)
* 5 2 version made by 2 bytes
* 7 2 version needed to extract 2 bytes
* 9 2 general purpose bit flag 2 bytes
* 11 2 compression method 2 bytes
* 13 2 last mod file time 2 bytes
* 15 2 last mod file date 2 bytes
* 17 4 crc-32 4 bytes
* 21 4 compressed size 4 bytes
* 25 4 uncompressed size 4 bytes
* 29 2 file name length 2 bytes
* 31 2 extra field length 2 bytes
* 33 2 file comment length 2 bytes
* 35 2 disk number start 2 bytes
* 37 2 internal file attributes 2 bytes
* 39 4 external file attributes 4 bytes
* 43 4 relative offset of local header 4 bytes
* 47 file name (variable size)
* extra field (variable size)
* file comment (variable size)
LPARAMETERS tcZipFileName, taFileList
LOCAL lcZipSignature, lcZipEoCdrSignature, lcReadBuffer, lnPos, lcZipEoCdr, lcZipComment, lnZipCommentSize
LOCAL lcZipCfh, lnFileCount, lnFileNameLen, lnExtraFieldLen, lnFileCommentLen, lnCfhFixLen, llZipEoCdrSignature
LOCAL llCountOnly
This.cZipFileName = tcZipFileName
lcZipSignature = "PK" + CHR(03) + CHR(04)
lcZipEoCdrSignature = "PK" + CHR(05) + CHR(06)
lcZipCfhSignature = "PK" + CHR(01) + CHR(02)
llCountOnly = (PCOUNT() = 1)
lnFileCount = 0
This.hZipFile = FOPEN(tcZipFileName)
IF This.hZipFile < 1
This.cErrorMsg = "Cannot open ZIP file " + This.cZipFileName
This.nErrorNum = 1
RETURN -1
ENDIF
lcReadBuffer = FREAD(This.hZipFile, This.nBufferSize)
IF NOT( lcZipSignature $ lcReadBuffer )
FCLOSE(This.hZipFile)
This.cErrorMsg = "Cannot find ZIP signature for file " + This.cZipFileName
This.nErrorNum = 2
RETURN -1
ENDIF
lnPos = 0
FSEEK(This.hZipFile,0)
lcReadBuffer = ""
DO WHILE NOT FEOF(This.hZipFile)
lcReadBuffer = lcReadBuffer + FREAD(This.hZipFile, This.nBufferSize)
lnPos = AT(lcZipCfhSignature, lcReadBuffer)
IF lnPos > 0
lcReadBuffer = SUBSTR(lcReadBuffer, lnPos)
EXIT
ENDIF
ENDDO
IF lnPos = 0
FCLOSE(This.hZipFile)
This.cErrorMsg = "Cannot find central file header signature for ZIP file " + This.cZipFileName
This.nErrorNum = 3
RETURN -1
ENDIF
llZipEoCdrSignature = (lcZipEoCdrSignature $ lcReadBuffer)
DO WHILE NOT FEOF(This.hZipFile) AND NOT llZipEoCdrSignature
* Read one extra block in case if central file directory spins 2 blocks
* Will fail if it occupies more than 2 blocks (>128 KB)
lcReadBuffer = lcReadBuffer + FREAD(This.hZipFile, This.nBufferSize)
llZipEoCdrSignature = (lcZipEoCdrSignature $ lcReadBuffer)
ENDDO
FCLOSE(This.hZipFile)
IF NOT llZipEoCdrSignature
This.cErrorMsg = "Cannot find the end of central index signature for ZIP file " + This.cZipFileName
This.nErrorNum = 3
RETURN -1
ENDIF
lcZipCfh = lcReadBuffer
lcReadBuffer = ""
ccCfhFixLen = 47
DO WHILE lcZipCfh = lcZipCfhSignature
lnFileCount = lnFileCount + 1
lnFileNameLen = This.Short2Num(SUBSTR(lcZipCfh,29,2))
lnExtraFieldLen = This.Short2Num(SUBSTR(lcZipCfh,31,2))
lnFileCommentLen = This.Short2Num(SUBSTR(lcZipCfh,33,2))
lnCfhLen = ccCfhFixLen + lnFileNameLen + lnExtraFieldLen + lnFileCommentLen
IF NOT llCountOnly
DIMENSION taFileList[lnFileCount, 9]
* Name
taFileList[lnFileCount, 1] = SUBSTR(lcZipCfh, 47, lnFileNameLen)
* Size
taFileList[lnFileCount, 2] = This.Long2Num(SUBSTR(lcZipCfh,25,4))
* Date & Time
taFileList[lnFileCount, 3] = This.ConvertDosDate(This.Short2Num(SUBSTR(lcZipCfh,15,2)))
taFileList[lnFileCount, 4] = This.ConvertDosTime(This.Short2Num(SUBSTR(lcZipCfh,13,2)))
taFileList[lnFileCount, 5] = ""
* Compressed size
taFileList[lnFileCount, 6] = This.Long2Num(SUBSTR(lcZipCfh,21,4))
* File Comment
IF lnFileCommentLen > 0
taFileList[lnFileCount, 7] = SUBSTR(lcZipCfh, 47 + lnFileNameLen, lnFileCommentLen )
ELSE
taFileList[lnFileCount, 7] = ""
ENDIF
taFileList[lnFileCount, 8] = ""
taFileList[lnFileCount, 9] = ""
ENDIF
lcZipCfh = SUBSTR(lcZipCfh, lnCfhLen)
ENDDO
RETURN lnFileCount
ENDPROC
*-----------------------------------------------------------------------------
PROCEDURE Long2Num(tcLong)
LOCAL lnNum
lnNum = 0
=RtlCopy2Num(@lnNum, tcLong, 4)
RETURN lnNum
PROCEDURE Short2Num(tcShort)
LOCAL lnNum
lnNum = 0
=RtlCopy2Num(@lnNum, tcShort, 2)
RETURN lnNum
*-----------------------------------------------------------------------------
PROCEDURE ConvertDosDate(tnDosDate)
lnDosDate = BITAND(tnDosDate, 0xFFFF)
* Year 7 bit, Month 4 bit, Day 5 bit
RETURN DATE( 1980 + BITRSHIFT(lnDosDate, 9), ;
BITRSHIFT(BITAND(lnDosDate, 0x1E0), 5), ;
BITAND(lnDosDate, 0x1F))
ENDPROC
PROCEDURE ConvertDosTime(tnDosTime)
lnDosTime = BITAND(tnDosTime, 0xFFFF)
* Hours 5 bit, Minutes 6 bit, Bi-Seconds 5 bit
RETURN PADL(BITRSHIFT(lnDosTime, 11), 2,"0") + ":" + ;
PADL(BITRSHIFT(BITAND(lnDosTime, 0x7E0), 5), 2,"0") + ":" + ;
PADL(BITAND(lnDosTime, 0x1F) * 2, 2,"0")
ENDPROC
ENDDEFINE
*-----------------------------------------------------------------------------
PROCEDURE RtlCopy2Num(tnDest, tcFrom, tnLen)
DECLARE RtlMoveMemory IN WIN32API AS RtlCopy2Num ;
INTEGER @ DestNum, STRING pVoidSource, INTEGER nLength
RETURN RtlCopy2Num(@tnDest, tcFrom, tnLen)
Comments