Delphi: open a zip archive from a stream -> extract to a stream - delphi

Are there any zip components with such features? I need to download a zip archive from the Internet to a stream, then to open the archive from the stream and then to extract files to another stream.
E.g. ZipForge can open an archive from a stream ZipForge.OpenArchive(MyStream, false);
but how to extract to another one...?
procedure ExtractToStream(FileName: WideString; Stream: TStream);
Description
Use ExtractToStream to decompress data stored in the file inside the
archive to a TStream descendant object like TFileStream, TMemoryStream
or TBlobStream.
The FileName parameter specifies file name being extracted.
And what use of the OpenArchive(MyStream, false) method if extraction isn't supported...

The zip file component that is built into XE2 will do this.
There is an overloaded Open method that receives a TStream as its input parameters.
To extract individual files you can call an overloaded Read method passing the name of the file that you wish to extract. The extracted file is returned as a new instance of TStream. You can that use CopyFrom on that instance to transfer the extracted file to your stream.
var
ZipFile: TZipFile;
DownloadedStream, DecompressionStream, MyStream: TStream;
LocalHeader: TZipHeader;
...
ZipFile := TZipFile.Create;
try
ZipFile.Open(DownloadedStream, zmRead);
ZipFile.Read('myzippedfile', DecompressionStream, LocalHeader);
try
MyStream.CopyFrom(DecompressionStream, DecompressionStream.Size);
finally
DecompressionStream.Free;
end;
finally
ZipFile.Free;
end;
Note that I've not tested this code, I've just written it based on the source code for TZipFile and the documentation contained in that source code. There may be a few wrinkles in this but if the code behaves as advertised it meets your needs perfectly.
OK, now I tested it because I was curious. Here's the program that shows that this all works as advertised:
program ZipTest;
{$APPTYPE CONSOLE}
uses
System.SysUtils,
System.Classes,
System.Zip;
procedure ExtractToFile(
const ZipFileName: string;
const ZippedFileIndex: Integer;
const ExtractedFileName: string
);
var
ZipFile: TZipFile;
DownloadedStream, DecompressionStream, OutputStream: TStream;
LocalHeader: TZipHeader;
begin
DownloadedStream := TFileStream.Create(ZipFileName, fmOpenRead);
try
ZipFile := TZipFile.Create;
try
ZipFile.Open(DownloadedStream, zmRead);
ZipFile.Read(ZippedFileIndex, DecompressionStream, LocalHeader);
try
OutputStream := TFileStream.Create(ExtractedFileName, fmCreate);
try
OutputStream.CopyFrom(DecompressionStream, DecompressionStream.Size);
finally
OutputStream.Free;
end;
finally
DecompressionStream.Free;
end;
finally
ZipFile.Free;
end;
finally
DownloadedStream.Free;
end;
end;
begin
try
ExtractToFile('C:\desktop\test.zip', 0, 'C:\desktop\out.txt');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
Note that I extracted by index rather than file name since that was more convenient for me. And I used file streams rather than memory streams which I imagine you would use. However, since the TZipFile methods work with TStream I'm sure that the code will work with streams of any form.
This is the latest in a series of questions about ZIP files. I know that you are using XE2 and I wonder why you seem reluctant to use the built in ZIP class that XE2 provides. I've not seen anything to indicate that it will not fulfil your requirements. In fact, it is precisely this ability to work directly with streams that makes me feel it has sufficient generality for any application.

Related

Delphi Clipboard: Read file properties of a file copied

I would like to retrieve the file size of a file copied into the clipboard.
I read the documentation of TClipboard but I did not find a solution.
I see that TClipboard.GetAsHandle could be of some help but I was not able to complete the task.
Just from inspecting the clipboard I could see at least 2 useful formats:
FileName (Ansi) and FileNameW (Unicode) which hold the file name copied to the clipboard.
So basically you could register one of then (or both) with RegisterClipboardFormat and then retrieve the information you need. e.g.
uses Clipbrd;
var
CF_FILE: UINT;
procedure TForm1.FormCreate(Sender: TObject);
begin
CF_FILE := RegisterClipboardFormat('FileName');
end;
function ClipboardGetAsFile: string;
var
Data: THandle;
begin
Clipboard.Open;
Data := GetClipboardData(CF_FILE);
try
if Data <> 0 then
Result := PChar(GlobalLock(Data)) else
Result := '';
finally
if Data <> 0 then GlobalUnlock(Data);
Clipboard.Close;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Clipboard.HasFormat(CF_FILE) then
ShowMessage(ClipboardGetAsFile);
end;
Once you have the file name, just get it's size or other properties you want.
Note: The above was tested in Delphi 7. for Unicode versions of Delphi use the FileNameW format.
An alternative and more practical way (also useful for multiple files copied) is to register and handle the CF_HDROP format.
Here is an example in Delphi: How to paste files from Windows Explorer into your application

Delphi Write TResourceStream into TStream

I have embedded an exe file in Resorce file.
when I just Use Stream.SaveToFile('test.exe'); everything works fine,produced exe file works with no error. but when i try to Stream.SaveToStream(Stin); , I get error " Stream write error " . what's wrong with my code ?
var
list: TStringList;
Stream: TResourceStream;
Stin, Stout: TStream;
MemStream: TMemoryStream;
begin
Stream := TResourceStream.Create(HInstance, 'phprogram' , RT_RCDATA);
try
Stin := TStream.Create;
Stout := TStream.Create;
Stream.Position := 0;
Stream.SaveToStream(Stin);
EnDecryptStream(Stin, Stout, 2913);
MemStream.LoadFromStream(Stout);
MemStream.SaveToFile('test.exe');
//Stream.SaveToFile('test.exe');
finally
Stream.Free;
end;
end;
Edited :
Thanks to David ... I changed my code and it worked fine :
var
Stream: TResourceStream;
MemStream: TMemoryStream;
begin
Stream := TResourceStream.Create(HInstance, 'testres' , RT_RCDATA);
MemStream := TMemoryStream.Create;
try
EnDecryptStream(Stream, MemStream, 2913);
MemStream.SaveToFile('test.exe');
finally
MemStream.Free;
Stream.Free;
end;
end;
TStream is an abstract class. You must not instantiate instances of TStream. You must always instantiate a concrete class derived from TStream, such as TFileStream, TMemoryStream, TStringStream, etc.
Furthermore, you use MemStream without initialising it.
It looks like you need to do something like this:
Create the resource stream.
Create a memory stream.
Call EnDecryptStream providing the resource stream as input and the memory stream as output.
Call SaveToFile on the memory stream to save it.
Or even simpler:
Create the resource stream.
Create a file stream.
Call EnDecryptStream providing the resource stream as input and the file stream as output.
One of the most common anti-patterns that we see on Stack Overflow is the excessive use of the memory stream. You appear to want to write to a file, so why not cut out the memory stream, and go straight to the file.
I don't particularly want to write any code here because we can only see a small part of the picture here, and any code that I would write would likely be wrong.
I suspect that you have not enabled compiler warnings and hints, or are perhaps ignoring them. Don't do that. Enable warnings and hints, and heed them.

How to Convert Ansi to UTF 8 with TXMLDocument in Delphi

It's possible to convert the XML to UTF-8 encoding in Delphi 6?
Currently that's what I am doing:
Fill TXMLDocument with AnsiString
At the end convert the Data to UTF-8 by using WideStringVariable = AnsiToUtf8(Doc.XML.Text);
Save the value of WideStringVariable to file using TFileStream and Adding BOM for UTF8 at the file beggining.
CODE:
Procedure SaveAsUTF8( const Name:String; Data: TStrings );
const
cUTF8 = $BFBBEF;
var
W_TXT: WideString;
fs: TFileStream;
wBOM: Integer;
begin
if TRIM(Data.Text) <> '' then begin
W_TXT:= AnsiToUTF8(Data.Text);
fs:= Tfilestream.create( Name, fmCreate );
try
wBOM := cUTF8;
fs.WriteBUffer( wBOM, sizeof(wBOM)-1);
fs.WriteBuffer( W_TXT[1], Length(W_TXT)*Sizeof( W_TXT[1] ));
finally
fs.free
end;
end;
end;
If I open the file in Notepad++ or another editor that detects encoding, it shows me UTF-8 with BOM. However, it seems like the text it's not properly encoded.
What is wrong and how can I fix it?
UPDATE: XML Properties:
XMLDoc.Version := '1.0';
XMLDoc.Encoding := 'UTF-8';
XMLDoc.StandAlone := 'yes';
You can save the file using standard SaveToFile method over the TXMLDocument variable: http://docs.embarcadero.com/products/rad_studio/delphiAndcpp2009/HelpUpdate2/EN/html/delphivclwin32/XMLDoc_TXMLDocument_SaveToFile.html
Whether the file would be or not UTF8 you have to check using local tools like aforementioned Notepad++ or Hex Editor or anything else.
If you insist of using intermediate string and file stream, you should use the proper variable. AnsiToUTF8 returns UTF8String type and that is what to be used.
Compiling `WideStringVar := AnsiStringSource' would issue compiler warning and
It is a proper warning. Googling for "Delphi WideString" - or reading Delphi manuals on topic - shows that WideString aka Microsoft OLE BSTR keeps data in UTF-16 format. http://delphi.about.com/od/beginners/l/aa071800a.htm
Thus assignment UTF16 string <= 8-bit source would necessarily convert data and thus dumping WideString data can not be dumping UTF-8 text by the definition of WideString
Procedure SaveAsUTF8( const Name:String; Data: TStrings );
const
cUTF8: array [1..3] of byte = ($EF,$BB,$BF)
var
W_TXT: UTF8String;
fs: TFileStream;
Trimmed: AnsiString;
begin
Trimmed := TRIM(Data.Text);
if Trimmed <> '' then begin
W_TXT:= AnsiToUTF8(Trimmed);
fs:= TFileStream.Create( Name, fmCreate );
try
fs.WriteBuffer( cUTF8[1], sizeof(cUTF8) );
fs.WriteBuffer( W_TXT[1], Length(W_TXT)*Sizeof( W_TXT[1] ));
finally
fs.free
end;
end;
end;
BTW, this code of yours would not create even empty file if the source data was empty. It looks rather suspicious, though it is you to decide whether that is an error or not wrt the rest of your program.
The proper "uploading" of received file or stream to web is yet another issue (to be put as a separate question on Q&A site like SO), related to testing conformance with HTTP. As a foreword, you can readsome hints at WWW server reports error after POST Request by Internet Direct components in Delphi
In order to have the correct encoding inside the document, you should set it by using the Encoding property in your XML Document, like this:
myXMLDocument.Encoding := 'UTF-8';
I hope this helps.
You simply need to call the SaveToFile method of the document:
XMLDoc.SaveToFile(FileName);
Since you specified the encoding already, the component will use that encoding.
This won't include a BOM, but that's generally what you want for an XML file. The content of the file will specify the encoding.
As regards your SaveAsUTF8 method, it is not needed, but it is easy to fix. And that may be instructive to you.
The problem is that you are converting to UTF-16 when you assign to a WideString variable. You should instead put the UTF-8 text into an AnsiString variable. Changing the type of the variable that you named W_TXT to AnsiString is enough.
The function might look like this:
Procedure SaveAsUTF8(const Name: string; Data: TStrings);
const
UTF8BOM: array [0..2] of AnsiChar = #$EF#$BB#$BF;
var
utf8: AnsiString;
fs: TFileStream;
begin
utf8 := AnsiToUTF8(Data.Text);
fs:= Tfilestream.create(Name, fmCreate);
try
fs.WriteBuffer(UTF8BOM, SizeOf(UTF8BOM));
fs.WriteBuffer(Pointer(utf8)^, Length(utf8));
finally
fs.free;
end;
end;
Another solution:
procedure SaveAsUTF8(const Name: string; Data: TStrings);
var
fs: TFileStream;
vStreamWriter: TStreamWriter;
begin
fs := TFileStream.Create(Name, fmCreate);
try
vStreamWriter := TStreamWriter.Create(fs, TEncoding.UTF8);
try
vStreamWriter.Write(Data.Text);
finally
vStreamWriter.Free;
end;
finally
fs.free;
end;
end;

Extracting images from zip into memory delphi

I'm wanting to extract a zip file loaded with images into memory in some way. I don't really care what type of stream they go into, as long as I can load them afterwards. I do not have that great of an understanding with streams, and explanations on the subject don't seem to go into much detail.
Essentially, what I am doing now is extracting the files to (getcurrentdir + '\temp\'). This works, but isn't quite what I am wanting to do. I would be more happy to have the jpg's end up in memory and then be able to read from memory into a TImage.bitmap.
I am currently using jclcompresion to handle zips and rars, but was considering moving back to system.zip because I really only need to be able to handle zip files. If it would be easier to stay with jclcompression though that would work for me.
The read method of the TZipFile class can be used with a stream
procedure Read(FileName: string; out Stream: TStream; out LocalHeader: TZipHeader); overload;
procedure Read(Index: Integer; out Stream: TStream; out LocalHeader: TZipHeader); overload;
from here you can access the compressed file using the index or the filename.
Check this sample which uses a TMemoryStream to hold the uncompressed data.
uses
Vcl.AxCtrls,
System.Zip;
procedure TForm41.Button1Click(Sender: TObject);
var
LStream : TStream;
LZipFile : TZipFile;
LOleGraphic: TOleGraphic;
LocalHeader: TZipHeader;
begin
LZipFile := TZipFile.Create;
try
//open the compressed file
LZipFile.Open('C:\Users\Dexter\Desktop\registry.zip', zmRead);
//create the memory stream
LStream := TMemoryStream.Create;
try
//LZipFile.Read(0, LStream, LocalHeader); you can use the index of the file
LZipFile.Read('SAM_0408.JPG', LStream, LocalHeader); //or use the filename
//do something with the memory stream
//now using the TOleGraphic to detect the image type from the stream
LOleGraphic := TOleGraphic.Create;
try
LStream.Position:=0;
//load the image from the memory stream
LOleGraphic.LoadFromStream(LStream);
//load the image into the TImage component
Image1.Picture.Assign(LOleGraphic);
finally
LOleGraphic.Free;
end;
finally
LStream.Free;
end;
finally
LZipFile.Free;
end;
end;

ZipForge and invalid archives

I have ZipForge for Delphi XE2 & Delphi XE2.
I try to test any invalid zip archives (e.g. not fully downloaded) like in their demo:
procedure TfmMain.bnStartClick(Sender: TObject);
begin
with Archiver do
begin
FileName := 'c:\2.zip';
OpenArchive;
try
TestFiles('*.*');
except
MessageDlg('Errors occurred in the archive file', mtError, [mbOk], 0);
end;
CloseArchive;
end;
end;
But my exception doesn't fire; ZipForge's dialog fires instead of mine.
I tried Abbrevia Component but it even can't recognize if an archive is invalid...
Please help me to make my exception working (not ZipForge's one) or suggest me a better component for zip files with a test feature. Thanks!
Be aware that you can modify ZIP files, e.g. by truncating them somewhat, the ZIP file will still be valid. With my test file, I removed the final 5000 bytes and it was reported as valid. I extracted it successfully using my ZIP program. Of course the extracted contents were incorrect and not the original contents. Perhaps this is what was happening for you. Maybe your attempts to corrupt your ZIP file were not in fact making it into an invalid ZIP file.
Delphi XE2 comes with a built in ZIP component that worked well in my simple test and successfully detected an invalid file, once I had truncated the file enough to make it truly corrupt.
I used the IsValid method to check validity. Here is my very simple test program.
program ZipTest;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.Zip;
procedure Main;
const
ZipArchive = 'C:\desktop\test.zip';
var
ZipFile: TZipFile;
FileName: string;
begin
ZipFile := TZipFile.Create;
try
if ZipFile.IsValid(ZipArchive) then begin
ZipFile.Open(ZipArchive, zmRead);
for FileName in ZipFile.FileNames do begin
Writeln(FileName);
end;
end else begin
Writeln(ZipArchive + ' not valid');
end;
finally
ZipFile.Free;
end;
end;
begin
try
Main;
Readln;
except
on E: Exception do begin
Writeln(E.ClassName, ': ', E.Message);
end;
end;
end.
If you have an invalid ZIP file, it is most likely that the call to OpenArchive will fail. As long as your execption handling doesn't cover that case, you will get the result you describe.
Update: The suggested way to catch exceptions during TestFiles or any other method is to connect an OnProcessFileFailure event handler.

Resources