Related
When I run the prog below, the result value of the stgOpenStorage is STG_E_SHAREVIOLATION. How should I close the IStorage to get it unlocked?
procedure TForm1.btnSaveClick(Sender: TObject);
var
fileName : string;
streamName : string;
procedure storeTextIntoStorageStream( text_ : string );
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
if ( fileExists( fileName ) ) then
deleteFile( fileName );
stgCreateDocfile( #fileName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage );
try
documentStorage.CreateStream( #streamName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream );
try
i := length( text_ );
levelIStream.write( #i, sizeOf( integer ), #j );
levelIStream.write( #text_[1], i*sizeOf( char ), #j );
finally
levelIStream.Commit( 0 );
levelIStream := NIL;
end;
finally
documentStorage.Commit( 0 );
documentStorage := NIL;
end;
end;
function readTextFromStorageStream : string;
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
i := stgOpenStorage( #fileName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, NIL, 0, documentStorage );
try
documentStorage.OpenStream( #streamName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, levelIStream );
try
levelIStream.read( #i, sizeOf( integer ), #j );
setLength( result, i );
levelIStream.read( #result[1], i*sizeOf( char ), #j );
finally
levelIStream := NIL;
end;
finally
documentStorage := NIL;
end;
end;
begin
fileName := 'c:\temp\test.stg';
streamName := 'Stream-0';
storeTextIntoStorageStream( memo1.Lines.DelimitedText );
memo1.Lines.DelimitedText := readTextFromStorageStream;
end;
And how could I set the IStorage/IStream default size / size step? Because my test 1.6K byte content stored in 16K.
There are two IStorage implementations in the Delphi source libraries.
WinApi.OLE2 and WinApi.ActiveX. Which one do you use? In the WinApi.OLE2 unit, the IStorage and IStream are CLASSES, they are not INTERFACES. If you use this unit, the interface garbage collection and so the automatic closing does not work on the variables. If you use the WinApi.ActiveX unit, the example will work just fine.
The code looks fine 1, so I have a feeling that the problem is due to your use of STGM_SHARE_EXCLUSIVE. The file is in use at the time you are trying to open it, so I'm betting your OS/AV is the one keeping the file open (ie, to scan its content), not the interfaces in storeTextIntoStorageStream(), which are long gone by the time readTextFromStorageStream() is entered.
1: well, aside from the lack of adequate error handling. And the redundant nil'ing of interface variables. And, consider replacing your string indexes with PChar() casts instead.
In readTextFromStorageStream(), try replacing STGM_SHARE_EXCLUSIVE (which makes sense for a writer, but not a reader) with STGM_SHARE_DENY_WRITE instead and see if the error goes away:
procedure TForm1.btnSaveClick(Sender: TObject);
var
fileName : string;
streamName : string;
procedure storeTextIntoStorageStream( const text_ : string );
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
if ( FileExists( fileName ) ) then
DeleteFile( fileName );
OleCheck( StgCreateDocFile( PChar(fileName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage ));
try
OleCheck( documentStorage.CreateStream( PChar(streamName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream ) );
try
i := Length( text_ );
OleCheck( levelIStream.Write( #i, SizeOf( i ), #j ) );
if ( i > 0 ) then
OleCheck( levelIStream.Write( PChar(text_), i * SizeOf( Char ), #j ) );
finally
levelIStream.Commit( 0 );
end;
finally
documentStorage.Commit( 0 );
end;
end;
function readTextFromStorageStream : string;
var
documentStorage : IStorage;
levelIStream : IStream;
i, j : integer;
begin
Result := '';
OleCheck( StgOpenStorage( PChar(fileName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, NIL, 0, documentStorage ) );
OleCheck( documentStorage.OpenStream( PChar(streamName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, 0, levelIStream ) );
OleCheck( levelIStream.Read( #i, SizeOf( i ), #j ) );
if ( i > 0 ) then
begin
SetLength( Result, i );
OleCheck( levelIStream.Read( PChar(Result), i * SizeOf( Char ), #j ) );
end;
end;
begin
fileName := 'c:\temp\test.stg';
streamName := 'Stream-0';
storeTextIntoStorageStream( Memo1.Lines.DelimitedText );
Memo1.Lines.DelimitedText := readTextFromStorageStream;
end;
In Delphi, using WinAPI, I am try to get list of groups that have permission to file or folder, example group list:
What function or records I need to use to get these information?
I am try to using GetNamedSecurityInfoA but it fails - function return false and variables sidGrp with dacl are still nil, but variable sc (Security Descriptor) is initialized.
procedure TForm1.Button2Click(Sender: TObject);
var
sciezka: array [0 .. 256] of ansiChar;
sidOwn: PSID;
sidGrp: PSID;
dacl: PACL;
sacl: PACL;
sc: PSECURITY_DESCRIPTOR;
success: DWORD;
access: EXPLICIT_ACCESS_A;
sid_id_auth: _SID_IDENTIFIER_AUTHORITY;
hToken: THandle;
TokenUserPoint: pTokenUser;
bufferSize: DWORD;
BufferSize2: DWORD;
ptgGroups: PTokenGroups;
psidAdmin: PSID;
x: Integer;
const
SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));
begin
Memo1.Lines.Clear();
ZeroMemory(#sciezka, Length(sciezka));
GetMem(ptgGroups, 1024);
bufferSize := 0;
System.AnsiStrings.StrLCopy(#sciezka, PAnsiChar(AnsiString(Edit1.Text)), Length(Edit1.Text));
// success := CheckFileAccess(string(sciezka), FILE_READ_DATA);
success := Cardinal(OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY OR TOKEN_READ, hToken));
success := Cardinal(GetTokenInformation(hToken, TokenUser, ptgGroups, 1024, bufferSize));
GetMem(TokenUserPoint, BYTE(bufferSize));
//FillChar(TokenUserPoint, bufferSize, 0);
//success := Cardinal(GetTokenInformation(hToken, TokenUser, TokenUserPoint, bufferSize, BufferSize2));
sidOwn := nil;
sidGrp := nil;
dacl := nil;
sacl := nil;
sc := nil;
sid_id_auth.Value[0] := 2;
sid_id_auth.Value[1] := 3;
sid_id_auth.Value[2] := 5;
sid_id_auth.Value[3] := 0;
sid_id_auth.Value[4] := 0;
sid_id_auth.Value[5] := 0;
success := Cardinal(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2, SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0, psidAdmin));
{$R-}
{ for x := 0 to ptgGroups.GroupCount - 1 do
if EqualSid(psidAdmin, ptgGroups.Groups[x].Sid) then
begin
Memo1.Lines.Add('Jest administrator');
Break;
end; }
{$R+}
// success := GetFileSecurityA(sciezka, GROUP_SECURITY_INFORMATION, sc, );
success := GetNamedSecurityInfoA(sciezka,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
nil,
sidGrp,
dacl,
nil,
sc
);
Memo1.Lines.Add('Funkcja zwróciła wartoć = ' + success.ToHexString());
if IsValidSid(sidOwn) then Memo1.Lines.Add('sidOwn - poprawne')
else Memo1.Lines.Add('sidOwn - niepoprawne');
if IsValidSid(sidGrp) then Memo1.Lines.Add('sidGrp - poprawne')
else Memo1.Lines.Add('sidGrp - niepoprawne');
if(sidOwn = nil) then
Memo1.Lines.Add('sidOwn is null');
if(sidGrp = nil) then
Memo1.Lines.Add('sidGrp is null');
end;
As #FredS said, the parameters use pointer of pointer, and you declare here:
sidGrp: PSID;
Dacl: PACL;
Sacl: PACL;
sidGrp := nil;
dacl := nil;
sacl := nil;
sc: PSECURITY_DESCRIPTOR;
success := GetNamedSecurityInfoA(sciezka,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
nil,
sidGrp,
dacl,
nil,
sc
);
which is equal to:
success := GetNamedSecurityInfoA(sciezka,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
nil,
nil,
nil,
nil,
nil
);
And you will get the return error: 87(ERROR_INVALID_PARAMETER)
According to the function document:
success := GetNamedSecurityInfoA(sciezka,
SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION or GROUP_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
nil,
#sidGrp,
#dacl,
nil,
sc
);
I use the WinINet library to connect to a website.
Using the Internet Explorer (Win10) it works and shows me the message to select the certificate to use.
This is the delphi code I call:
FUNCTION TRAD.lastOrganization(): Integer;
VAR
js:TlkJSONobject;
ws: TlkJSONstring;
url, resp: String;
count,statusCodeLen, bodyCodeLen: Cardinal;
header,tmp: String;
buffer, body: String;
statusCode: ARRAY [0 .. 1024] OF Char;
bodyCode: ARRAY [0 .. 1024] OF Char;
UrlHandle: HINTERNET;
BEGIN
buffer := '00000000000000000000';
url := contextUrl + '/rest/organization/count';
UrlHandle := InternetOpenUrl(NetHandle, PChar(url), nil, 0, INTERNET_FLAG_RELOAD, 0);
IF NOT ASSIGNED(UrlHandle) THEN
SHOWMESSAGE('Unable to read the amount of Organization using the URL ' + url + ': ' + SysErrorMessage(GetLastError));
statusCodeLen := Length(statusCode);
bodyCodeLen := Length(bodyCode);
count := 0;
IF HttpQueryInfo(UrlHandle, HTTP_QUERY_STATUS_CODE, #statusCode[0], statusCodeLen, count) THEN
BEGIN
buffer := statusCode;
IF buffer <> '200' THEN
BEGIN
ShowMessage('While read amount of Organization I got a status code ' + buffer + ' but 200 was expected.');
EXIT;
END;
END;
count := 0;
body := '';
REPEAT
FillChar(bodyCode, bodyCodeLen, 0);
IF NOT InternetReadFile(UrlHandle, #bodyCode[0], bodyCodeLen, count) THEN
BEGIN
ShowMessage('Problem on reading from response stream while read the amount of Organization using the URL ' + url + '.');
EXIT;
END;
IF count > 0 THEN
BEGIN
tmp := bodyCode;
body := body + LeftStr(tmp, count);
END;
UNTIL count = 0;
InternetCloseHandle(UrlHandle);
Result := strtoint(body);
END;
If I call the method, I get this message:
Buuut, using the Edge-Browser I have to specify a certificate, and it works just great.
Question
How to specify the certificate?
Edit (new informations):
If I change the code to
FUNCTION TRAD.lastOrganization(): Integer;
VAR
js:TlkJSONobject;
ws: TlkJSONstring;
url, resp: String;
count,statusCodeLen, bodyCodeLen: Cardinal;
header,tmp: String;
buffer, body: String;
statusCode: ARRAY [0 .. 1024] OF Char;
bodyCode: ARRAY [0 .. 1024] OF Char;
UrlHandle: HINTERNET;
BEGIN
buffer := '00000000000000000000';
url := contextUrl + '/rest/organization/count';
UrlHandle := InternetOpenUrl(NetHandle, PChar(url), nil, 0, INTERNET_FLAG_RELOAD, 0);
IF NOT ASSIGNED(UrlHandle) THEN
raiseLastOSError();
It shows:
Consider the use of InternetErrorDlg
Code example:
function WebSiteConnect(const UserAgent: string; const Server: string; const Resource: string;): string;
var
hInet: HINTERNET;
hConn: HINTERNET;
hReq: HINTERNET;
dwLastError:DWORD;
nilptr:Pointer;
dwRetVal:DWORD;
bLoop: boolean;
port:Integer;
begin
hInet := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if hInet = nil then exit;
hConn := InternetConnect(hInet, PChar(Server), INTERNET_DEFAULT_HTTPS_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
if hConn = nil then
begin
InternetCloseHandle(hInet);
exit;
end;
hReq := HttpOpenRequest(hConn, 'GET', PChar(Resource), 'HTTP/1.0', nil, nil, INTERNET_FLAG_SECURE, 0);
if hReq = nil then
Begin
InternetCloseHandle(hConn);
InternetCloseHandle(hInet);
exit;
end;
bLoop := true;
while bLoop do
begin
if HttpSendRequest(hReq, nil, 0, nil, 0) then
dwLastError := ERROR_SUCCESS
else
dwLastError:= GetLastError();
if dwLastError = ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED then
begin
dwRetVal:= InternetErrorDlg(application.handle, hReq, dwLastError,
FLAGS_ERROR_UI_FILTER_FOR_ERRORS or
FLAGS_ERROR_UI_FLAGS_GENERATE_DATA or
FLAGS_ERROR_UI_FLAGS_CHANGE_OPTIONS,
nilptr );
if dwRetVal = ERROR_INTERNET_FORCE_RETRY then
continue
else // CANCEL button
begin
InternetCloseHandle(hReq);
InternetCloseHandle(hConn);
InternetCloseHandle(hInet);
exit;
end;
end
else
bLoop := false;
end;
Result:= ...
end;
Using WinHTTP (You can do the same with WinInetHTTP) you can set the certificate like this via ActiveX :
// Instantiate a WinHttpRequest object.
var HttpReq = new ActiveXObject("WinHttp.WinHttpRequest.5.1");
// Open an HTTP connection.
HttpReq.Open("GET", "https://www.fabrikam.com/", false);
// Select a client certificate.
HttpReq.SetClientCertificate(
"LOCAL_MACHINE\\Personal\\My Middle-Tier Certificate");
// Send the HTTP Request.
HttpReq.Send();
So that easy with ActiveX but it's not really what you want (i gave you the example as illustration). So with the windows API, WinHTTP enables you to select and send a certificate from a local certificate store. The following code example shows how to open a certificate store and locate a certificate based on subject name after the ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED error has been returned.
if( !WinHttpReceiveResponse( hRequest, NULL ) )
{
if( GetLastError( ) == ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED )
{
//MY is the store the certificate is in.
hMyStore = CertOpenSystemStore( 0, TEXT("MY") );
if( hMyStore )
{
pCertContext = CertFindCertificateInStore( hMyStore,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
0,
CERT_FIND_SUBJECT_STR,
(LPVOID) szCertName, //Subject string in the certificate.
NULL );
if( pCertContext )
{
WinHttpSetOption( hRequest,
WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
(LPVOID) pCertContext,
sizeof(CERT_CONTEXT) );
CertFreeCertificateContext( pCertContext );
}
CertCloseStore( hMyStore, 0 );
// NOTE: Application should now resend the request.
}
}
}
Is there a method to determine if an exe file has been compressed with UPX?
The function to determine if an exe file has been compressed is excellent except I found a problem with the code. If the function IsUPXCompressed is called then you try to run upx, upx can not save the file it modifies. There is something not sharing rights correctly in the function. I have tested this for several hours. If I do not call the method then UPX can write the files with no problem. You you call it then try to run UPX it will not save the file. UPX reports an IOException Permission denied error when trying to write the file.
Can anyone spot something wrong in the code that would cause this problem?
Thank-you
The function to determine if an exe file has been compressed is excellent except I found a problem with the code. If the function IsUPXCompressed is called then you try to run upx, upx can not save the file it modifies. There is something not sharing rights correctly in the function. I have tested this for several hours. If I do not call the method then UPX can write the files with no problem. You you call it then try to run UPX it will not save the file. UPX reports an IOException Permission denied error when trying to write the file.
Can anyone spot something wrong in the code that would cause this problem?
Thank-you
Another Method, when a exe is packed with the UPX tool, the section of the PE header contains sections called UPX0,UPX1, etc. so if read these sections and compare the name with the string UPX you can determine if the exe was compressed using the UPX packer.
check this function
uses
Windows;
function IsUPXCompressed(const Filename:TFileName): Boolean;
var
i : integer;
pBaseAddress : PByte;
pDosHeader : PImageDosHeader;
pNtHeaders : PImageNtHeaders;
hFile : Cardinal;
hFileMap : Cardinal;
pSectionHeader: PImageSectionHeader;
dwOffset : Cardinal;
SectName : AnsiString;
begin
Result:=False;
hFile := CreateFile(PChar(Filename), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile = INVALID_HANDLE_VALUE) then Exit;
hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY or SEC_IMAGE, 0, 0, nil);
if (hFileMap = 0) then
begin
CloseHandle(hFile);
Exit;
end;
pBaseAddress := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (pBaseAddress = nil) then
begin
CloseHandle(hFileMap);
CloseHandle(hFile);
Exit;
end;
try
dwOffset := Cardinal(pBaseAddress);
pDosHeader := PImageDosHeader(pBaseAddress);
pNtHeaders := PImageNtHeaders(dwOffset + Cardinal(pDosHeader._lfanew));
pSectionHeader := pImageSectionHeader(Cardinal(pNtHeaders) + SizeOf(TImageNtHeaders));
for i := 0 to pNtHeaders.FileHeader.NumberOfSections-1 do
begin
SetString(SectName, PAnsiChar(#pSectionHeader.Name), SizeOf(pSectionHeader.Name));
Result:=Pos('UPX',SectName)>0;
If Result then break;
Inc(pSectionHeader);
end;
finally
UnmapViewOfFile(pBaseAddress);
CloseHandle(hFileMap);
CloseHandle(hFile);
end;
end;
UPX itself does it like this:
if (memcmp(isection[0].name,"UPX",3) == 0)
throwAlreadyPackedByUPX();
This is the implementation for 32-bit PEs; 64-bit PEs need different offsets,
and other executable formats have to be handled separately.
#include <stdio.h>
typedef unsigned int uint;
uint peek_d( FILE* f, uint offs ) {
fseek( f, offs, SEEK_SET );
uint a = 0;
fread( &a, 1,sizeof(a), f );
return a;
}
int main( int argc, char** argv ) {
FILE* f = fopen( argv[1], "rb" ); if( f==0 ) return 1;
uint p,n,x,y;
p = peek_d( f, 0x3C ); // PE header offset
n = peek_d( f, p+0x74 ); // pointer table size
x = p + 0x78 + n*8;
y = peek_d( f, x+0*0x28+0 ); // 1st section name
if( (y&0xFFFFFF) == ('U'+('P'<<8)+('X'<<16)) ) {
printf( "UPX detected!\n" );
} else {
printf( "No UPX!\n" );
}
return 0;
}
try to uncompress it with upx?
// Returns IsUPXCompressed - Modified for Delphi 2010
function IsUPXCompressed( const Filename: TFileName ): Boolean;
var
i: integer;
pBaseAddress: PByte;
pDosHeader: PImageDosHeader;
pNtHeaders: PImageNtHeaders;
hFile: Cardinal;
hFileMap: Cardinal;
pSectionHeader: PImageSectionHeader;
dwOffset: Cardinal;
SectName: AnsiString;
begin
Result := False;
hFile := CreateFile( PChar( Filename ), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0 );
if ( hFile = INVALID_HANDLE_VALUE ) then
Exit;
hFileMap := CreateFileMapping( hFile, nil, PAGE_READONLY or SEC_IMAGE, 0, 0, nil );
if ( hFileMap = 0 ) then
begin
CloseHandle( hFile );
Exit;
end;
pBaseAddress := MapViewOfFile( hFileMap, FILE_MAP_READ, 0, 0, 0 );
if ( pBaseAddress = nil ) then
begin
CloseHandle( hFileMap );
CloseHandle( hFile );
Exit;
end;
dwOffset := Cardinal( pBaseAddress );
pDosHeader := PImageDosHeader( pBaseAddress );
pNtHeaders := PImageNtHeaders( dwOffset + Cardinal( pDosHeader._lfanew ) );
pSectionHeader := pImageSectionHeader( Cardinal( pNtHeaders ) + SizeOf( TImageNtHeaders ) );
for i := 0 to pNtHeaders.FileHeader.NumberOfSections - 1 do
begin
SetString( SectName, PAnsiChar( #pSectionHeader.name ), SizeOf( pSectionHeader.name ) );
if Pos( 'UPX', SectName ) > 0 then
begin
Result := True;
exit;
end;
Inc( pSectionHeader );
end;
end;
Thanks Rob for the pointers.
The section names are not included the UPX word always. It mabebe contain another name changed by user. For certain. Ypu must search for UPX copmpressor signature in the whole file.
Do you know any free component, compatible with Delphi 2010 or XE to manage ZIP archives (actually, only reading archive contents and extracting files required)?
Please no betas.
I thought about ZipForge from ComponentAce, but it's free only for personal use. No software distribution allowed.
You can get the TurboPower Abbrevia for 2010 from:
http://tpabbrevia.sourceforge.net/
you can take a look at this if you like 7zip
If you only need decoding (developed for Delphi 2007, not yet tested under Delphi 2010/XE):
unit UnitZip;
interface
uses
SysUtils, Classes;
type
EZipException = class( Exception );
TZipFileInfo = record
LastModified: TDateTime;
Crc32: Longword;
CompressedSize: Longword;
UncompressedSize: Longword;
end;
TZipFileReader = class
private
// Information about the memory mapped file
FFileHandle: THandle;
FFileMapping: THandle;
FMappedAddress: Pointer;
// Location of the ZIPfile in memory. Currently we only support memory mapped ZIPfiles without disk spanning.
FStart: Pointer;
FSize: Longword;
// ZIP file contents
FFilenames: TStrings;
function GetZipFileInfo ( const FileName: AnsiString ): TZipFileInfo;
public
constructor Create ( const FileName: string; ZipStartOffset: Int64 = 0; Size: Longword = 0 ); overload;
constructor Create ( const ResourceName, ResourceType: string; Instance: HMODULE = 0 ); overload;
constructor Create ( Buffer: Pointer; Size: Longword ); overload;
destructor Destroy; override;
function GetFile ( const FileName: string ): TBytes; overload;
function GetFile ( FileID: Integer ): TBytes; overload;
property FileNames: TStrings read FFileNames;
property FileInfo [ const FileName: AnsiString ]: TZipFileInfo read GetZipFileInfo;
end;
implementation
uses
ZLib, Windows;
const
cResourceNotFound = 'Resource not found: %s.%s.';
cResourceNotLoaded = 'Resource not loaded: %s.%s.';
cCannotOpenFile = 'Cannot open file %s: OS error: %d.';
cCannotGetFileSize = 'Cannot get file size of file %s: OS error: %d.';
cCannotMapFile = 'Cannot create file mapping of file %s: OS error: %d.';
cZipFileTooSmall = 'ZIP file is too small.';
cZipFileFormatError = 'ZIP file is invalid.';
cZipBufferInvalid = 'ZIP memory buffer is invalid.';
cUnsupportedMethod = 'ZIP unsupported compression method: %d.';
cFileNotFoundInZip = 'File not found in ZIP content: %s';
// ZIP file format records.
// The generic zip file format is ( TLocalFileHeader; Name; Extra; compressed data )* ( TFileHeader; Name; Extra; Remark )* TLastHeader
type
TFileInfo = packed record
NeededVersion: Word; // 20
Flags: Word; // 1=Text,4=extra present
ZipMethod: Word; // 0=stored 8=deflate
LastModified: Longword; // time in dos format or Unix Timestamp
Crc32: Longword;
CompressedSize: Longword;
UncompressedSize: Longword;
NameSize: Word;
ExtraSize: Word;
end;
TFileHeader = packed record
Signature: Longword; // $02014b50 PK#1#2
MadeBy: Word; // Version number, 20
FileInfo: TFileInfo;
CommentSize: Word; // 0
FirstDiskNumber: Word; // 0
IntFileAttr: Word; // 0 = binary; 1 = text
ExtFileAttr: Longword; // DOS file attributes (Archived=32)
LocalFileHeaderHeadOff: Longword; // #TLocalFileHeader
end;
PFileHeader = ^TFileHeader;
TLocalFileHeader = packed record
Signature: Longword; // $02014b50 PK#3#4
FileInfo: TFileInfo;
end;
PLocalFileHeader = ^TLocalFileHeader;
TLastHeader = packed record
Signature: Longword; // $02014b50 PK#5#6
ThisDiskNumber: Word;
CentralDirDisk: Word;
ThisDiskFileCount: Word;
TotalFileCount: Word;
FileHeaderSize: Longword;
FileHeaderOffset: Longword;
CommentSize: Word;
end;
PLastHeader = ^TLastHeader;
const
MagicLastHeader = $06054b50;
MagicLocalHeader = $04034b50;
MagicFileHeader = $02014b50;
type
IntPtr = Longword; // NativeInt on Delphi2007 is an Int64 ??
{$if CompilerVersion < 19}
procedure SetAnsiString( var S: AnsiString; P: PAnsiChar; L: Integer ); inline;
begin
SetString( S, P, L );
end;
{$ifend}
{ TZipFileReader }
constructor TZipFileReader.Create( const FileName: string; ZipStartOffset: Int64; Size: Longword );
begin
// Open the file in question.
FFileHandle := CreateFile( PChar( FileName ), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0 );
if FFileHandle = INVALID_HANDLE_VALUE then raise EZipException.CreateFmt( cCannotOpenFile, [ Filename, GetLastError() ] );
if Size = 0 then Size := GetFileSize( FFileHandle, nil );
if Size = INVALID_FILE_SIZE then raise EZipException.CreateFmt( cCannotGetFileSize, [ Filename, GetLastError() ] );
try
// Create a file mapping of the file in question
FFileMapping := CreateFileMapping( FFileHandle, nil, PAGE_READONLY, 0, 0, nil);
if FFileMapping = 0 then raise EZipException.CreateFmt( cCannotMapFile, [ Filename, GetLastError() ] );
try
// Get the file mapped in memory (NOTE: The offset needs to be on the memory allocation granularity of the system)
// Hence we assign it it's own pointer -> todo rounding etc.
FMappedAddress := MapViewOfFile( FFileMapping, FILE_MAP_READ, Int64Rec( ZipStartOffset ).Hi, Int64Rec( ZipStartOffset ).Lo, Size );
if not Assigned( FMappedAddress ) then EZipException.CreateFmt( cCannotMapFile, [ Filename, GetLastError() ] );
Create( FMappedAddress, Size );
except
CloseHandle( FFileMapping );
FFileMapping := 0;
raise;
end;
except
CloseHandle( FFileHandle );
FFileHandle := 0;
raise;
end;
end;
constructor TZipFileReader.Create( const ResourceName, ResourceType: string; Instance: HMODULE );
var
Resource: HRSRC;
Global: HGLOBAL;
begin
Resource := FindResource( Instance, PChar( ResourceName ), PChar( ResourceType ) );
if Resource = 0 then raise EZipException.CreateFmt( cResourceNotFound, [ ResourceName, ResourceType ] );
Global := LoadResource( Instance, Resource );
if Global = 0 then raise EZipException.CreateFmt( cResourceNotLoaded, [ ResourceName, ResourceType ] );
Create( LockResource( Global ), SizeofResource( HInstance, Resource ) );
// Note: kb57808: SizeofResource() returns the resource size rounded up to the alignment size.
end;
constructor TZipFileReader.Create( Buffer: Pointer; Size: Longword );
var
LastHeader: PLastHeader;
FileHeader: PFileHeader;
i, Off: Longword;
Name: AnsiString;
begin
// Note the location.
FStart := Buffer;
FSize := Size;
// Some sanity checks.
if FSize < sizeof( TLocalFileHeader ) + sizeof( TFileHeader ) + sizeof( TLastHeader ) then raise EZipException.Create( cZipFileTooSmall );
if IsBadReadPtr( Buffer, Size ) then raise EZipException.Create( cZipBufferInvalid );
if PLongword( Buffer )^ <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
// Find the last header. Due to the alignment of SizeofResource, we need o search for it.
LastHeader := Pointer( IntPtr( Buffer ) + Size - sizeof( TLastHeader ) );
for i := 0 to 31 do begin
if LastHeader^.Signature = MagicLastHeader then Break;
Dec( IntPtr( LastHeader ) );
end;
if LastHeader^.Signature <> MagicLastHeader then raise EZipException.Create( cZipFileFormatError );
FFilenames := TStringList.Create();
Off := LastHeader^.FileHeaderOffset;
for i := 0 to LastHeader^.TotalFileCount - 1 do begin
// Get header
if Off + sizeof( TFileHeader ) >= Size then raise EZipException.Create( cZipFileFormatError );
FileHeader := Pointer( IntPtr( Buffer ) + Off );
Inc( Off, sizeof( TFileHeader ) );
if FileHeader^.Signature <> MagicFileHeader then raise EZipException.Create( cZipFileFormatError );
// Get filename
if Off + FileHeader^.FileInfo.NameSize + FileHeader^.FileInfo.ExtraSize >= Size then raise EZipException.Create( cZipFileFormatError );
SetAnsiString( Name, Pointer( IntPtr( Buffer ) + Off ), FileHeader^.FileInfo.NameSize );
Inc( Off, FileHeader^.FileInfo.NameSize + FileHeader^.FileInfo.ExtraSize );
// Save filename and offset into ZIPfile where it can be found.
FFileNames.AddObject( Name, Pointer( FileHeader^.LocalFileHeaderHeadOff ) );
end;
// For quick access.
TStringList( FFilenames ).Sorted := True;
end;
destructor TZipFileReader.Destroy;
begin
if Assigned( FMappedAddress ) then UnmapViewOfFile( FMappedAddress );
if FFileMapping <> 0 then CloseHandle( FFileMapping );
if FFileHandle <> 0 then CloseHandle( FFileHandle );
inherited Destroy;
end;
function TZipFileReader.GetFile( const FileName: string ): TBytes;
var
ID: Integer;
begin
// Convert filename in FileID and access by ID.
ID := FFilenames.IndexOf( FileName );
if ID < 0 then raise EZipException.CreateFmt( cFileNotFoundInZip, [ FileName ] );
Result := GetFile( ID );
end;
function TZipFileReader.GetFile( FileID: Integer ): TBytes;
var
Off: Longword;
Local: PLocalFileHeader;
ZRec: TZStreamRec;
const
ZLibHeader: array [ 0..1 ] of Byte = ( $78, $01 ); // Deflate 32KB window size no preset dictionary.
begin
// Sanity check
if ( FileID < 0 ) or ( FileID >= FFilenames.Count ) then raise EZipException.CreateFmt( 'Invalid File ID: %d', [ FileID ] );
// Get the file header and perform sanity check
Off := Longword( FFilenames.Objects[ FileID ] );
if Off + sizeof( TLocalFileHeader ) >= FSize then raise EZipException.Create( cZipFileFormatError );
Local := Pointer( IntPtr( FStart ) + Off );
if Local^.Signature <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
Inc( Off, sizeof( TLocalFileHeader ) + Local^.FileInfo.NameSize + Local^.FileInfo.ExtraSize );
if Off + Local^.FileInfo.CompressedSize >= FSize then raise EZipException.Create( cZipFileFormatError );
// note: should we check the name again?
SetLength( Result, Local^.FileInfo.UncompressedSize );
if Length( Result ) > 0 then case Local^.FileInfo.ZipMethod of
0: begin // STORED
if Local^.FileInfo.CompressedSize <> Local^.FileInfo.UncompressedSize then raise EZipException.Create( cZipFileFormatError );
Move( Pointer( IntPtr( FStart ) + Off )^, Result[ 0 ], Local^.FileInfo.UncompressedSize );
end;
8: begin // DEFLATE
ZeroMemory( #ZRec, sizeof( ZRec ) );
ZRec.next_in := #ZLibHeader;
ZRec.avail_in := sizeof( ZLibHeader );
ZRec.total_in := sizeof( ZLibHeader ) + Local^.FileInfo.CompressedSize;
ZRec.next_out := #Result[ 0 ];
ZRec.avail_out := Local^.FileInfo.UncompressedSize;
ZRec.total_out := Local^.FileInfo.UncompressedSize;
ZRec.zalloc := zlibAllocMem;
ZRec.zfree := zlibFreeMem;
if inflateInit_( ZRec, zlib_Version, sizeof( ZRec ) ) <> 0 then raise EZipException.Create( cZipFileFormatError );
try
if not( inflate( ZRec, Z_FULL_FLUSH ) in [ Z_OK, Z_STREAM_END ] ) then raise EZipException.Create( cZipFileFormatError );
ZRec.next_in := Pointer( IntPtr( FStart ) + Off );
ZRec.avail_in := Local^.FileInfo.CompressedSize;
if not( inflate( ZRec, Z_FINISH ) in [ Z_OK, Z_STREAM_END ] ) then raise EZipException.Create( cZipFileFormatError );
finally
inflateEnd( ZRec );
end;
end;
else raise EZipException.CreateFmt( cUnsupportedMethod, [ Local^.FileInfo.ZipMethod ] );
end;
// todo: CRC32 sanity check if requested.
end;
function TZipFileReader.GetZipFileInfo( const FileName: AnsiString ): TZipFileInfo;
var
FileID: Integer;
Off: Longword;
Local: PLocalFileHeader;
begin
// Get the correct file ID
FileID := FFilenames.IndexOf( FileName );
if FileID < 0 then raise EZipException.CreateFmt( cFileNotFoundInZip, [ FileName ] );
// Get the file header and perform sanity check
Off := Longword( FFilenames.Objects[ FileID ] );
if Off + sizeof( TLocalFileHeader ) >= FSize then raise EZipException.Create( cZipFileFormatError );
Local := Pointer( IntPtr( FStart ) + Off );
if Local^.Signature <> MagicLocalHeader then raise EZipException.Create( cZipFileFormatError );
Inc( Off, sizeof( TLocalFileHeader ) + Local^.FileInfo.NameSize + Local^.FileInfo.ExtraSize );
if Off + Local^.FileInfo.CompressedSize >= FSize then raise EZipException.Create( cZipFileFormatError );
// Return requested data.
Result.LastModified := Local^.FileInfo.LastModified;
Result.Crc32 := Local^.FileInfo.Crc32;
Result.CompressedSize := Local^.FileInfo.CompressedSize;
Result.UncompressedSize := Local^.FileInfo.UncompressedSize;
end;
end.
Take a look at this OpenSource SynZip unit. It's even faster for decompression than the default unit shipped with Delphi, and it will generate a smaller exe (crc tables are created at startup).
No external dll is needed.
I just made some changes to handle Unicode file names inside Zip content, not only Win-Ansi charset but any Unicode chars. Feedback is welcome.
I like the WinZip compatible TZipMaster for Delphi, available here: http://www.delphizip.org/
TZipMaster is a non-visual VCL wrapper
created by ChrisVleghert and
EricW.Engler for their freeware Zip
and Unzip DLLs.
Those DLLs are based on the InfoZip
Official Freeware Zip/Unzip source
code, but are NOT equivalent to
InfoZip's DLLs. The InfoZip source
code has been modified to enhance
their ease-of-use, power, and
flexibility for use with Delphi and
C++ Builder.
Also, this question has been covered before on Stack Overflow, which may yield some other solutions for you.
If distributing an ActiveX DLL with your project is not a problem for you, then Chilkat Zip (http://www.chilkatsoft.com/zip-activex.asp) seems to do the trick. Delphi examples are here: http://www.example-code.com/delphi/zip.asp
DotNetZip is a managed code (.NET) library, that exposes a COM interface.
Free.
Open source
MS-PL licensed.