VFP implementation of GetZipComment and GetZipFilesList for ZIP acrchives

topic: 

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