How can a message client read an attachment downloaded by indy? - delphi

I have a message client, written in delphi using Indy libraries, that receives email messages. I am having difficulties decoding an MMS text message email.
These messages come as multipart/mixed emails with one message part (an attachment) that of text/plain (that is base64 encoded) with a filename like text0.txt.
My TIdMessageClient calls ProcessMessage (using the stream-based version) to populate a TidMessage that I'm going to display on the screen. But as I go through the message parts and try to unravel them, that attached file is a thorn in my side. Currently, I have it printing out the name of the attachment into a string which works fine (see code snippet below, FBody is a string type), but can't get the text file's contents.
Here's the bit that does work:
FBody := 'Attachment: ['+TidAttachment(Msg.MessageParts.Items[0]).FileName+']';
(Edited:) Originally when I wrote this question I wasn't sure if the attachment was stored in a TidAttachmentFile or TidAttachmentMemory object. But with the right debugger commands, I've determined it's a TidAttachmentFile. I suppose it would be possible to use TidAttachmentFile.SaveToFile() to save the attachment to a file on disk and then read the file back from disk, but that seems wasteful and slow (especially for a 200 character text message). I would really prefer to do this all "in memory" without temp files if possible.
What do I need to do (a) make TidMessageClient return a TidAttachmentMemory object rather than a TidAttachmentObject (in ProcessMessage), and (b) read the attached text file into a string?
Based on the indy documentation, the start I have at how this code would look is roughly like this:
TidAttachmentMemory(Msg.MessageParts.Items[0]).PrepareTempStream();
FBody := FBody + TidAttachmentMemory(Msg.MessageParts.Items[0]).DataString;
TidAttachmentMemory(Msg.MessageParts.Items[0]).FinishTempStream;
Please feel free to point me in the right direction if this is not the right way to go or use TidAttachment(s).

I suppose it would be possible to use TidAttachmentFile.SaveToFile() to save the attachment to a file on disk and then read the file back from disk, but that seems wasteful and slow (especially for a 200 character text message).
When using TIdAttachmentFile, the file is always on disk. The TIdAttachmentFile.StoredPathName property specifies the path to the actual file. The TIdAttachmentFile.SaveToFile() method merely copies the file to the specified location.
I would really prefer to do this all "in memory" without temp files if possible.
It is possible.
What do I need to do (a) make TidMessageClient return a TidAttachmentMemory object rather than a TidAttachmentObject (in ProcessMessage)
In the TIdMessage.OnCreateAttachment event, return a TIdAttachmentMemory object, eg:
procedure TMyForm.IdMessage1CreateAttachment(const AMsg: TIdMessage; const AHeaders: TStrings; var AAttachment: TIdAttachment);
begin
AAttachment := TIdAttachmentMemory.Create(AMsg.MessageParts);
end;
If no handler is assigned to the TIdMessage.OnCreateAttachment event, or if it does not assign anything to AAttachment, then a TIdAttachmentFile is created by default.
You could optionally implement your own custom TIdAttachment-derived class instead, say one that uses TStringStream internally if you know the attachment contains textual data (which the AHeaders parameter will tell you).
and (b) read the attached text file into a string?
Based on the indy documentation, the start I have at how this code would look is roughly like this:
You are close. You need to use the TIdAttachment.OpenLoadStream() method instead of TIdAttachment.PrepareTempStream(), and you need to read the data from the TStream that TIdAttachment.OpenLoadStream() returns. In your example, you could use Indy's ReadStringFromStream() function for that, eg:
// if using Indy 10.6 or later...
var
Attachment: TIdAttachment;
Strm: TStream;
begin
...
Attachment := TIdAttachment(Msg.MessageParts.Items[0]);
Strm := Attachment.OpenLoadStream;
try
FBody := FBody + ReadStringFromStream(Strm, -1, CharsetToEncoding(Attachment.Charset));
finally
Attachment.CloseLoadStream;
end;
...
end;
Or:
// if using Indy 10.5.x or earlier...
var
Attachment: TIdAttachment;
Strm: TStream;
Enc: TIdTextEncoding;
begin
...
Attachment := TIdAttachment(Msg.MessageParts.Items[0]);
Strm := Attachment.OpenLoadStream;
try
Enc := CharsetToEncoding(Attachment.Charset);
try
FBody := FBody + ReadStringFromStream(Strm, -1, Enc);
finally
Enc.Free;
end;
finally
Attachment.CloseLoadStream;
end;
...
end;

Related

Saving a string with null characters to a file

I have a string that contains null characters.
I've tried to save it to a file with this code:
myStringList.Text := myString;
myStringList.SaveToFile('c:\myfile');
Unfortunately myStringList.Text is empty if the source string has a null character at the beginning.
I thought only C string were terminated by a null character, and Delphi was always fine with it.
How to save the content of the string to a file?
I think you mean "save a string that has #0 characters in it".
If that's the case, don't try and put it in a TStringList. In fact, don't try to save it as a string at all; just like in C, a NULL character (#0 in Delphi) causes the string to be truncated at times. Use a TFileStream and write it directly as byte content:
var
FS: TFileStream;
begin
FS := TFileStream.Create('C:\MyFile', fmCreate);
try
FS.Write(myString[1], Length(myString) * SizeOf(Char));
finally
FS.Free;
end;
end;
To read it back:
var
FS: TFileStream;
begin
FS := TFileStream.Create('C:\MyFile', fmOpenRead);
try
SetLength(MyString, FS.Size);
FS.Read(MyString[1], FS.Size);
finally
FS.Free;
end;
end;
When you set the Text property of a TStrings object, the new value is parsed as a null-terminated string. Therefore when the code reaches your null character, the parsing stops.
I'm not sure why the Delphi RTL code was designed that way, and its not documented, but that's just how setting the Text property works.
You can avoid this by using the Add method rather than the Text property.
myStringList.Clear;
myStringList.Add(myString);
myStringList.SaveToFile(FileName);
About writing strings to a file in general.. I still see people creating streams or stringlists just to write some stuff to a file, and then destroy the stream or stringlist.
Delphi7 didn't have IOUtuls.pas yet, but you're missing out on that.
There's a handy TFile record with class methods that lets you write text to a file with a single line, without requiring temporary variables:
TFile.WriteAllText('out.txt','hi');
Upgrading makes your life as a Delphi developer a lot easier. This is just a tiny example.

Converting TMemoryStream to variant

How do I convert contents of a TMemoryStream to a variant? I use Delphi 2010.
TMemoryStream stores contents of a file, it can be PDF or JPG (scanned document).
File is being kept inside MS SQL base.
When I go to editing mode in my program, I extract contents of that file from base into a TMemoryStream.
After editing document's card, I need to post document back to base.
Scanned file could be changed also (or replaced with some other file).
To post record back, I use a stored procedure with a bunch of parameters - one for every field.
I pass parameters to stored procedure as variants.
That's why I need to convert TMemoryStream to a variant.
Assuming you need the Variant to hold an array of bytes, you can use this:
var
MS: TMemoryStream;
V: Variant;
P: Pointer;
begin
...
V := VarArrayCreate([0, MS.Size-1], varByte);
if MS.Size > 0 then
begin
P := VarArrayLock(V);
Move(MS.Memory^, P^, MS.Size);
VarArrayUnlock(V);
end;
...
end;
TMemoryStream doesn't have a convenient way to get direct access to the internal data. It provides a property that gives you a pointer, but not any useful data type. However, if you use TBytesStream, which derives from TMemoryStream, you can get the data from the stream as a variable of type TBytes.
After this, assuming your parameter is a standard parameter object of type TParam, you don't need to use a variant. You can do it like this:
param.AsBlob := MyTBytesVariable;
Or, even simpler than that, you can use the stream directly:
param.AsStream := MyMemoryStream;
(If you do this, make sure that the stream's Position is set to 0 first.)

How operate on TFileStream

Hello recently I replace TextFile with TFileStream. I never use it so I have small problem with it.
How can I add someting to my file after I assign it to variable?
How can I read someting form that file?
I need defined line form that file so I was doing something like that:
var linia_klienta:array[0..30] of string;
AssignFile(tempPlik,'klienci.txt');
Reset(tempPlik);
i:=0;
While Not Eof(tempPlik) do
begin
Readln(tempPlik,linia_klient[i]);
inc(i);
end;
CloseFile(tempPlik);
Then when line two is needed I simply
edit1.text = linia_klienta[1];
If you need to read a text file and access each line, try instead using a TStringList class with this class you can load a file, read the data (accesing each line using a index) and save the data back.
something like this
FText : TStringList;
i : integer;
begin
FText := TStringList.Create;
try
FText.LoadFromFile('C:\Foo\Foo.txt');
//read the lines
for i:=0 to FText.Count-1 do
ProcessLine(FText[i]); //do something
//Add additional lines
FText.Add('Adding a new line to the end');
FText.Add('Adding a new line to the end');
//Save the data back
FText.SaveToFile('C:\Foo\Foo.txt');
finally
FText.Free;
end;
end;
end;
I newer versions of Delphi you can use TStreamReader / TStreamWriter here is an example of using TStreamReader ... this is only for manipulating text files
var
SR : TStreamReader;
line : String;
begin
SR := TStreamReader.Create('D:\test.txt');
while not (SR.EndOfStream) do
begin
line := SR.ReadLine;
ShowMessage(line);
end;
SR.Free;
end;
TStream and its immediate descendants are mostly low-level access class. They mostly deal with generic buffers. There are some more specialized classes that descend from or use a stream to perform higher level tasks.
Since Delphi 1 TReader and TWriter could be used to read and write Delphi types directly (inlcuding strings), but they were not designed to handle "line-oriented" files (unluckily they were designed too much with component properties streaming in mind, not as a general purpose framework).
Turbo Power SysTools has a nice TStAnsiTextStream class that implements line-oriented access to text files in a way similar to that of TextFile. Since Delphi 2009 new classes (see opc0de answer) implement the same kind of access without the need of third party libraries (moreover they support different encodings thanks to Delphi 2009 extend codepage support, including Unicode).
Depending with what you want to do, its the stream class you need.
Do you want to work with text (characters with break-lines and end-of-line characters) data ?
OR, do you want to work with binary data ?
I see you are using an array of char, instead, of a string.
Do you really want to use character data as if it was binary ?
Sometimes, some applications require that case.

How to load an arbitrary image from a BLOB stream into a TImage?

If I understand it correctly, TImage.LoadFromFile determines the type of picture from the file extension.
Is there any way to detect the image type automatically from a TBlobStream with a raw image in it?
My current code:
procedure LoadImageFromStream(AImage: TImage; ADataSet: TDataSet);
var
Stream: TStream;
begin
Stream := ADataSet.CreateBlobStream(Field, bmRead);
try
AImage.Picture.Graphic.LoadFromStream(Stream);
finally
Stream.Free;
end;
end
See this SO answer for file content retrieval from header.
Or you can use our TSynPicture class, which will handle all kind of pictures (bmp/gif/tiff/jpg/png) using Gdi+ library, in one single class. So your TPicture can be this unique class, for any kind of picture. With less code overhead than the Jpeg or PNG Delphi units.
var Pic: TSynPicture;
Pic := TSynPicture.Create;
Pic.LoadFromStream(aStream); // will load bmp/gif/tiff/jpeg/png content
AImage.Picture.Graphic := Pic;
....
starting from here you can easily do it: http://delphihaven.wordpress.com/2011/01/22/tip-detecting-graphic-formats/
In fact it is TPicture.LoadFromFile that detects file type, and it just uses the file extension. So you'll need to read the header of the stream to detect the file type.
On the other hand, if you know what the format is when you put the BLOB into the database you could always include that information as your own private header to the BLOB.
Seemingly magic TPicture cunning handling for arbitrary image formats is actually very simple (not to say crude). Loading from files relies on file extension. Loading from clipboard - on clipboard format indicator. See? There is always format tag which instructs TPicture which TGraphicClass to use on the data, and TGraphic base class itself provides no mechanism to identify "own" data streams besided trial-and-error approach. One might be curious how TPicture loads itself from DFM stream, but it is not an exception, here is relevant excerpt from implementation (copyrighted code provided for illustrative purpose only):
procedure TPicture.ReadData(Stream: TStream);
var
{...}
GraphicClass: TGraphicClass;
LClassName: string;
LBytes: TBytes;
LNameLen: Byte;
begin
Stream.Read(LNameLen, 1);
SetLength(LBytes, LNameLen);
Stream.Read(LBytes{$IFNDEF CLR}[0]{$ENDIF}, LNameLen);
LClassName := TEncoding.UTF8.GetString(LBytes);
GraphicClass := FileFormats.FindClassName(LClassName);

Delphi: problem with httpcli (ICS) post method

I am using HttpCli component form ICS to POST a request. I use an example that comes with the component. It says:
procedure TForm4.Button2Click(Sender: TObject);
var
Data : String;
begin
Data:='status=no';
HttpCli1.SendStream := TMemoryStream.Create;
HttpCli1.SendStream.Write(Data[1], Length(Data));
HttpCli1.SendStream.Seek(0, 0);
HttpCli1.RcvdStream := TMemoryStream.Create;
HttpCli1.URL := Trim('http://server/something');
HttpCli1.PostAsync;
end;
But it fact, it sends not
status=no
but
s.t.a.t.u
I can't understand, where is the problem. Maybe someone can show an example, how to send POST request with the help of HttpCli component?
PS I can't use Indy =)
I suppose you're using Delphi 2009 or later, where the string type holds two-byte-per-character Unicode data. The Length function gives the number of characters, not the number of bytes, so when you put your string into the memory stream, you only copy half the bytes from the string. Even if you'd copied all of them, though, you'd still have a bunch of extra data in the stream since each character has two bytes and the server probably only expects to get one.
Use a different string type, such as AnsiString or UTF8String.

Resources