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.

This is sample code. Add error handling and adjust to your requirements as necessary.

* 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)
Your rating: None Average: 5 (1 vote)