CEF4Delphi certificate selection window - delphi

I'm using CEF4Delphi to browse a site, this site asks for a certificate installed on windows, the documentation says I need to select this certificate in the "SelectClientCertificate" event in a callback function passing the index, my question is how to show this certificate window to select one of them
procedure TFPrin.WebBCSelectClientCertificate(Sender: TObject;
const browser: ICefBrowser; isProxy: Boolean; const host: ustring;
port: Integer; certificatesCount: NativeUInt;
const certificates: TCefX509CertificateArray;
const callback: ICefSelectClientCertificateCallback; var aResult: Boolean);
begin
aresult:=true;
//show certificate window here?
callback.Select(certificates[Certindex]);
end;
would it be the same window when accessing by firefox or chrome?
I appreciate any help, thanks!

I don't understand where the big deal is: you just display either a Form that you've designed just like any other Form, or you temporarily create one on the fly, just to destroy it again. How you plan to display each certificate (level of detail, fancyness, colors...) is up to you and (of course) works better with a Form already designed.
This is an example with a Form created on the fly:
procedure TFPrin.Chromium1SelectClientCertificate
( Sender: TObject
; const browser: ICefBrowser
; isProxy: Boolean
; const host: uCEFTypes.ustring
; port: Integer
; certificatesCount: Cardinal
; const certificates: TCefX509CertificateArray
; const callback: ICefSelectClientCertificateCallback
; var aResult: Boolean
);
var
iCert: Integer; // Which certificate we're just analyzing
sLine: String; // Information about the current certificate
frm: TForm; // Displayed (temporary) modal window
lbx: TListBox; // All certificates to choose from
pan: TPanel; // For the buttons
// Converting a certificate time
function _TimeToStr( vTime: TCefTime ): String;
begin
result:= IntToStr( vTime.year )+ '-'
+ IntToStr( vTime.month )+ '-'
+ IntToStr( vTime.day_of_month );
end;
begin
// Create temporary form...
frm:= TForm.Create( Application );
with frm do begin
try
BorderStyle:= bsSizeable;
// ...along with its temporary controls:
// Bottom panel, which will contain both buttons
pan:= TPanel.Create( frm );
with pan do begin
Parent:= frm;
Align:= alBottom;
Height:= 30;
end;
// Buttons that automatically set the form's modal result
with TButton.Create( frm ) do begin
Parent:= pan;
Caption:= '&Ok';
ModalResult:= ID_OK;
Default:= True; // We can press ENTER anywhere to trigger this button
Top:= 3;
Left:= 10;
end;
with TButton.Create( frm ) do begin
Parent:= pan;
Caption:= '&Cancel';
ModalResult:= ID_CANCEL;
Cancel:= True; // We can press ESC anywhere to trigger this button
Top:= 3;
Left:= 100;
end;
// A list displaying one certificate per line to choose from
lbx:= TListBox.Create( frm );
with lbx do begin
Parent:= frm;
Align:= alClient;
end;
// Now going thru all certificate details and adding each resulting text line to the listbox
for iCert:= Low( certificates ) to High( certificates ) do begin
sLine:= 'Subject: '+ certificates[iCert].GetSubject().GetDisplayName()+ '. '
+ 'Issuer: '+ certificates[iCert].GetIssuer().GetDisplayName()+ '. '
+ 'Valid from '+ _TimeToStr( certificates[iCert].GetValidStart() )+ ' to '
+ _TimeToStr( certificates[iCert].GetValidExpiry() )+ '.';
lbx.Items.Add( sLine );
end;
if lbx.Count> 0 then lbx.ItemIndex:= 0; // Pre-select first certificate
// Display the form and check if the "Ok" button has been pressed and a line is selected.
// If yes, actually choose a certificate.
aResult:= (ShowModal()= ID_OK) and (lbx.ItemIndex<> -1);
if aResult then callback.Select( certificates[lbx.ItemIndex] );
finally
// Free temporary form and all its controls
frm.Free;
end;
end;
end;
And this is an example for calling one of your existing Forms:
uses
frmOther;
procedure TFPrin.Chromium1SelectClientCertificate
...
var
iCert: Integer; // Which certificate we're just analyzing
sLine: String; // Information about the current certificate
// Converting a certificate time
function _TimeToStr( vTime: TCefTime ): String;
begin
result:= IntToStr( vTime.year )+ '-'
+ IntToStr( vTime.month )+ '-'
+ IntToStr( vTime.day_of_month );
end;
begin
// Remove any existing entries in TFOther
FOther.lbxCert.Clear();
// Now going thru all certificate details and adding each resulting text line to the listbox
for iCert:= Low( certificates ) to High( certificates ) do begin
sLine:= 'Subject: '+ certificates[iCert].GetSubject().GetDisplayName()+ '. '
+ 'Issuer: '+ certificates[iCert].GetIssuer().GetDisplayName()+ '. '
+ 'Valid from '+ _TimeToStr( certificates[iCert].GetValidStart() )+ ' to '
+ _TimeToStr( certificates[iCert].GetValidExpiry() )+ '.';
FOther.lbxCert.Items.Add( sLine );
end;
if FOther.lbxCert.Count> 0 then FOther.lbxCert.ItemIndex:= 0; // Pre-select first certificate
// Display the form and check if the "Ok" button has been pressed and a line is selected.
// If yes, actually choose a certificate.
aResult:= (FOther.ShowModal()= ID_OK) and (FOther.lbxCert.ItemIndex<> -1);
if aResult then callback.Select( certificates[FOther.lbxCert.ItemIndex] );
end;
Using the types/interfaces can't be more straight - just look at their definitions:
TCefX509CertificateArray is defined in uCEFInterfaces.pas,
along with everything that also comes with that: ICefX509Certificate and ICefX509CertPrincipal.
TCefTime is defined in uCEFTypes.pas.

Related

Get search path by handle when using FindNextFile?

I use FindFirstFile() and FindNextFile() to list files of a directory. When I call FindFirstFile(), I have to give a search path to it. It returns a handle that can be used by FindNextFile(). Is there a WinAPI function that can get the previously given path by the handle?
Just store that information like you store the search handle already: in a variable. Then create your own wrapper functions for both FindFirstFileA() and FindNextFileA():
type
// What you want to give back per file system object
TMyFindInfo= record // Whatever you want to do here on your own
wfd: Windows.WIN32_FIND_DATAA; // Just provide this as-is because it already everything
end;
// Not only storing the handle, but also other details
TMyFindHandle= record
h: THandle; // Search resource
sFilter: String; // Original query
iMatches, // How often did the search yield a file system object?
iError: Cardinal; // Which error has occured? 0=ERROR_SUCCESS.
end;
function MyFindFile1st
( const sFilter: String
; out vInfo: TMyFindInfo
): TMyFindHandle;
begin
result.sFilter:= sFilter;
result.h:= Windows.FindFirstFileA( PChar(sFilter), vInfo.wfd );
if result.h= INVALID_HANDLE_VALUE then begin
result.iError:= Windows.GetLastError();
case result.iError of
ERROR_FILE_NOT_FOUND: ; // The only error we don't need to display
else // Most likely ERROR_PATH_NOT_FOUND
Windows.MessageBoxA
( Form1.Handle
, PChar('Error initializing search "'+ result.sFilter
+ '": 0x'+ IntToHex( result.iError, 8 )) // Get text message elsewhere
, PChar('Error')
, MB_ICONSTOP
);
end;
result.iMatches:= 0;
ZeroMemory( #vInfo, SizeOf( vInfo ) ); // Nothing to see here
end else begin
result.iError:= ERROR_SUCCESS;
result.iMatches:= 1;
end;
end;
function MyFindFile2nd
( var vHandle: TMyFindHandle
; out vInfo: TMyFindInfo
): Boolean;
begin
result:= Windows.FindNextFileA( vHandle.h, vInfo.wfd );
if not result then begin
vHandle.iError:= Windows.GetLastError();
case vHandle.iError of
ERROR_SUCCESS, // The only errors we don't need to display
ERROR_NO_MORE_FILES: ;
else
Windows.MessageBoxA
( Form1.Handle
, PChar('Error during search "'+ vHandle.sFilter // Original filter from 1st call
+ '" after '+ IntToStr( vHandle.iMatches )+ ' elements occured: 0x'
+ IntToHex( vHandle.iError, 8 ))
, PChar('Error')
, MB_ICONSTOP
);
end;
Windows.ZeroMemory( #vInfo, SizeOf( vInfo ) ); // Nothing to see here
if not Windows.FindClose( vHandle.h ) then begin // Release resource
vHandle.iError:= Windows.GetLastError();
case vHandle.iError of
ERROR_SUCCESS: ;
else // Yes, this can fail, too
Windows.MessageBoxA
( Form1.Handle
, PChar('Error finalizing search "'+ vHandle.sFilter // Original filter from 1st call
+ '" after '+ IntToStr( vHandle.iMatches )+ ' elements occured: 0x'
+ IntToHex( vHandle.iError, 8 ))
, PChar('Error')
, MB_ICONSTOP
);
end;
end;
end else Inc( vHandle.iMatches ); // One more match
end;
// Now the example on how to use it
procedure TForm1.Button1Click(Sender: TObject);
var
vHandle: TMyFindHandle;
vInfo: TMyFindInfo;
begin
vHandle:= MyFindFile1st( 'C:\Windows\*.exe', vInfo );
while vHandle.iError= ERROR_SUCCESS do begin
Memo1.Lines.Add( vInfo.wfd.cFileName );
MyFindFile2nd( vHandle, vInfo ); // Don't even need the Boolean result here
end;
Memo1.Lines.Add( '= '+ IntToStr( vHandle.iMatches )+ ' FS objects' ); // Not only files
end;
At no time there is a need to re-request a detail by handle, because you can keep that detail right with the handle that you need to take care of anyway. Just put both together into a record and pass that to your own functions.
My code is for demonstration purposes (although I think it's a rather trivial overall case). I discourage from displaying dialog windows right in those functions, but instead react upon what vHandle.iError contains where I called those functions.

Open and read a file in firemonkey

What is wrong in this code ? I don't understend, if I remove the "Try" my app dont open, and if don't remove always appear "need login" ...
procedure TF_login.FormActivate(Sender: TObject);
var
Result: Integer;
TextFile: TStringList;
VarArquivo: string;
text: string;
dataI, dataF : string;
begin
TextFile := TStringList.Create;
VarArquivo := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'Limit.txt';
try
TextFile.LoadFromFile(VarArquivo);
text := TextFile.Text;
// ShowMessage(TextFile.Text); // there is the text
// ShowMessage(text); // there is the text
dataI := FormatDateTime('dd/mm/yyyy', Now);
dataF := FormatDateTime('dd/mm/yyyy', StrToDate(text));
Result := CompareDate(StrToDate(dataI), StrToDate(dataF));
ShowMessage(dataF +' data f');
ShowMessage(dataI +' data I');
if ( Result = LessThanValue ) then
begin
ShowMessage('data F low');
end
else
begin
ShowMessage('data F high');
F_inicio.Show;
end;
FreeAndNil(TextFile);
except on E:
Exception do ShowMessage('An error happened!' + sLineBreak + '[' +
E.ClassName + '] ' + E.Message);
end;
end;
The error : [EConvertError] '09/11/2019' is not a valid date
to create the file, i do:
procedure TF_login.btn_entrarClick(Sender: TObject);
var
data : tdatetime;
Resposta, data_s: string;
begin
PathFile := System.IOUtils.TPath.GetDocumentsPath;
NameFile := 'Limit.txt';
data := Now; //data actual
data := IncMonth(data, 2);
data_s := FormatDateTime('dd/mm/yyyy', data);
TFile.WriteAllText(TPath.Combine(PathFile, NameFile), data_s );
F_inicio.Show;
end;
The file exists, because the first (and second) ShowMessage (what is commented) show me the "09/11/19" but the third and fourth not appear to me...
OBS: Delphi 10.3 (RIO), Plataform: Android
There are a couple of things that you should change in your code:
procedure TF_login.FormActivate(Sender: TObject);
var
TextFile: TStringList;
VarArquivo: string;
text: string;
dataI, dataF : string;
begin
// If an exception (unlikely, but on principle) happens in your VarArquivo
// assignment, then the original version will leak the allocated TStringList.
// Always place the TRY right after allocation of a memory block. That way
// you ensure that the FINALLY block will always release the allocated
// memory. Also, always include a FINALLY block to release the memory. Don't
// count on your code to reach the FreeAndNIL code (it doesn't in this
// instance, as you can see) to make sure that you actually release the
// memory.
VarArquivo := System.IOUtils.TPath.GetDocumentsPath + PathDelim + 'Limit.txt';
TextFile := TStringList.Create;
try // - except
try // - finally
TextFile.LoadFromFile(VarArquivo);
text := TextFile.Text;
// ShowMessage(TextFile.Text); // there is the text
// ShowMessage(text); // there is the text
dataI := FormatDateTime('yyyy/mm/dd', Date);
dataF := FormatDateTime('yyyy/mm/dd', StrToDate(text));
ShowMessage(dataF +' data f');
ShowMessage(dataI +' data I');
if ( dataF < dataI ) then
begin
ShowMessage('data F low');
end
else
begin
ShowMessage('data F high');
F_inicio.Show;
end;
finally
FreeAndNil(TextFile);
end
except
// NEVER just "eat" an exception. Especially not while developing the
// application.
// Always either log the exception or show it to the user.
on E:Exception do ShowMessage('Exception '+E.ClassName+': '+E.Message+#13#10+
'need login');
end;
end;
Now - if you do this, what exception and error message is shown. This is needed in order to properly diagnose the error. Perhaps you can even figure it out for yourself when you see what exactly goes wrong...

Adding instead of replacing items to a text file Delphi

I made a simple program that adds ones information (Name, surname, ID ect) to a .txt file. When ever I make new details in the program, and click on a button to save the information, it rewrites it in the .txt file.
Here's my code:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
InfoFile : TStringList;
Name, Surname, ExtraInfo : String;
PhoneNumber,ID : Integer;
Date : TDateTime;
begin
InfoFile := TStringList.Create;
Name := edtName.text;
Surname := edtSurname.Text;
ID := StrToInt64(edtID.Text);
PhoneNumber := StrToInt64(edtPhone.Text);
Date := StrToDate(edtJoinDate.Text);
try
InfoFile.Add('NAME: '+Name);
InfoFile.Add('SURNAME: '+Surname);
InfoFile.Add('ID NUMBER: '+IntToStr(ID));
InfoFile.Add('PHONE NUMBER: '+IntToStr(PhoneNumber));
InfoFile.Add('DATE JOINED :'+DateToStr(Date));
InfoFile.Add(''); // Spacers to separate next set of details
InfoFile.Add('');
InfoFile.SaveToFile('C:\Users\GrassMan\Desktop\InfoSaver\imfofile.txt');
finally
InfoFile.Free;
end;
So instead of ADDING new details to the .txt file, its rewriting it. I know im doing something, if someone wouldn't mind giving me a hand.
Thanks
Either load the file at the beginning (via LoadFromFile), before adding to it and writing it back; or else forget about TStringList, and just use WriteLn, after opening the file with Append.
Should look like this:
begin
InfoFile := TStringList.Create;
Name := edtName.text;
Surname := edtSurname.Text;
ID := (edtID.Text);
PhoneNumber :=(edtPhone.Text);
try
InfoFile.LoadFromFile('C:\Users\grassman\Desktop\infofile.txt');
InfoFile.Add('NAME: '+Name);
InfoFile.Add('SURNAME: '+Surname);
InfoFile.Add('ID NUMBER: '+ ID);
InfoFile.Add('PHONE NUMBER: '+(PhoneNumber));
InfoFile.Add('Time of registration: ' + TimeToStr(time));
InfoFile.Add('Date of registration: ' + DateToStr(date));
InfoFile.Add(''); // Spacers to separate next set of details
InfoFile.Add('');
InfoFile.SaveToFile('C:\Users\grassman\Desktop\infofile.txt');
finally
InfoFile.Free;
procedure TForm1.BitBtn1Click(Sender: TObject);
var
InfoFile : TStringList;
Name, Surname, ExtraInfo : String;
PhoneNumber,ID : Integer;
Date : TDateTime;
FS : TFileStream;
begin
Name := edtName.text;
Surname := edtSurname.Text;
ID := StrToInt64(edtID.Text);
PhoneNumber := StrToInt64(edtPhone.Text);
Date := StrToDate(edtJoinDate.Text);
InfoFile := TStringList.Create;
try
InfoFile.Add('NAME: '+Name);
InfoFile.Add('SURNAME: '+Surname);
InfoFile.Add('ID NUMBER: '+IntToStr(ID));
InfoFile.Add('PHONE NUMBER: '+IntToStr(PhoneNumber));
InfoFile.Add('DATE JOINED :'+DateToStr(Date));
InfoFile.Add(''); // Spacers to separate next set of details
InfoFile.Add('');
FS := TFileStream.Create('C:\Users\GrassMan\Desktop\InfoSaver\imfofile.txt', fmOpenWrite);
try
FS.Seek(0, soEnd);
InfoFile.SaveToStream(FS);
finally
FS.Free;
end;
finally
InfoFile.Free;
end;
end;
You should use TFileStream:
var
recordStr: string;
fs: TFileStream;
fsFlags: Word;
filePath: string;
begin
filePath := 'C:\Users\GrassMan\Desktop\InfoSaver\imfofile.txt';
recordStr := 'NAME: '+ Name + #13#10 +
'SURNAME: '+ Surname + #13#10 +
'ID NUMBER: '+ IntToStr(ID) + #13#10 +
'PHONE NUMBER: '+ IntToStr(PhoneNumber) + #13#10 +
'DATE JOINED :' + DateToStr(Date) + #13#10 +
#13#10#13#10; // Spaces to separate next set of details
// open if exists, create if not
fsFlags := fmOpenWrite;
if (not FileExists(filePath)) then
fsFlags := fsFlags or fmCreate;
try
fs := TFileStream.Create(filePath);
try
fs.Seek(0, soEnd); // go to the end of the file
fs.Write(recordStr[1], Length(recordStr));
finally
fs.Free;
end;
except on ex: Exception do
begin
ShowMessage('Error while writing to the file: ' + ex.Message);
end;
end;
end;

Tabs and colored lines in Listbox

I am using a Tabbed Listbox component that was written by Fredric Rylander back in 1999 and it has been serving me well since then. :) Can't seem to find him anymore.
I now have an application that needs both Tabbed Data and alternating colored lines in the Listbox.
I can include the Component here for perusal if desired.
I tried coloring the lines from here
http://delphi.about.com/cs/adptips2002/a/bltip0602_4.htm
But then it eats the Tabs, but I do get the alternating colored lines.
Can someone please show me how to incorporate the two.
Thanks
Here's the Component
unit myListBoxTabbed;
{
Copyright © 1999 Fredric Rylander
You can easily add a header control to this list box: drop a header
control onto the form (it's default align property is set to alTop, if
it's not--set it); then set the myTabbedListBox's aligned property
to alClient; now, add the following two events and their code.
1) HeaderControl's OnSectionResize event:
var
i, last: integer;
begin
last := 0;
for i:=0 to HeaderControl1.Sections.Count-1 do begin
last := last + HeaderControl1.Sections[i].Width;
myTabbedListBox1.TabStops[i] := last;
end;
end;
2) Main form's OnCreate event:
var
i, last: integer;
begin
last := 0;
for i:=0 to HeaderControl1.Sections.Count-1 do begin
last := last + HeaderControl1.Sections[i].Width;
myTabbedListBox1.TabStops[i] := last;
end;
for i:=HeaderControl1.Sections.Count to MaxNumSections do
myTabbedListBox1.TabStops[i] := 2000;
end;
To get tab characters into the list box items either use the
string list property editor in the Delphi GUI and press
Ctrl + Tab or add tab characters (#9) in strings as so:
myTabbedListBox1.Items.Add( Edit1.Text + #9 + Edit2.Text );
I hope you find this tutorial helpful! :^)
(!) This is not a retail product, it's a tutorial and don't claim to
meet a potential user's demands.
If you find anything that seems odd (or incorrect even) don't hesitate to
write me a line. You can communicate with me at fredric#rylander.nu.
The source is available for you to use, abuse, modify and/or improve.
Happy trails!
/ Fredric
___________________________________F_r_e_d_r_i_c__R_y_l_a_n_d_e_r__
fredric#rylander.nu : www.rylander.nu : 6429296#pager.mirabilis.com
"power to the source sharing community"
}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TTabsArray = array[0..9] of integer;
type
TmyTabbedListBox = class( TListBox )
private
{ Private declarations }
fTabStops: TTabsArray;
function GetTabStops( iIndex: integer ): integer;
procedure SetTabStops( iIndex, iValue: integer);
function GetTabsString: string;
procedure SetTabsString( const sValue: string );
protected
{ Protected declarations }
procedure UpdateTabStops;
public
{ Public declarations }
procedure CreateParams( var cParams: TCreateParams ); override;
procedure CreateWnd; override;
property TabStops[ iIndex: integer ]: integer
read GetTabStops write SetTabStops;
published
{ Published declarations }
property TabsString: string
read GetTabsString write SetTabsString;
end;
procedure Register;
resourcestring
STR_ALPHA_UPPERLOWER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
CHAR_SEMICOLON = ';';
implementation
procedure Register;
begin
RegisterComponents('Additional', [TmyTabbedListBox]);
end;
{ myTabbedListBox }
procedure TmyTabbedListBox.CreateParams(var cParams: TCreateParams);
begin
inherited CreateParams( cParams );
// add the window style LBS_USETABSTOPS to accept tabs
cParams.Style := cParams.Style or LBS_USETABSTOPS;
end;
procedure TmyTabbedListBox.CreateWnd;
var
i: integer;
begin
inherited CreateWnd;
// set all the tabs into the box
for i := Low( fTabStops ) to High( fTabStops ) do
fTabStops[i] := i * 100;
// show the real tab positions
UpdateTabStops;
end;
function TmyTabbedListBox.GetTabsString: string;
var
sBuffer: string;
i: integer;
begin
// init var
sBuffer := SysUtils.EmptyStr;
// set all tabstops to the string (separated by ';'-char)
for i := Low( fTabStops ) to High( fTabStops ) do
sBuffer := sBuffer + IntToStr( fTabStops[i] ) + CHAR_SEMICOLON;
// and here we have the results
Result := sBuffer;
end;
function TmyTabbedListBox.GetTabStops( iIndex: integer ): integer;
begin
// nothing funny here
Result := fTabStops[iIndex];
end;
procedure TmyTabbedListBox.SetTabsString( const sValue: string );
var
sBuffer: string;
i, len: integer;
begin
// copy value into buffer
sBuffer := sValue;
// set the tabstops as specified
for i := Low( fTabStops ) to High( fTabStops ) do begin
len := Pos( sBuffer, CHAR_SEMICOLON );
fTabStops[i] := StrToIntDef( Copy( sBuffer, 1, len ), 0 );
Delete( sBuffer, 1, len );
end;
// show/redraw the results
UpdateTabStops;
Invalidate;
end;
procedure TmyTabbedListBox.SetTabStops( iIndex, iValue: integer );
begin
// do we really need to update?
if fTabStops[iIndex] <> iValue then begin
// oki, let's then
fTabStops[iIndex] := iValue;
// show/redraw the results
UpdateTabStops;
Invalidate;
end;
end;
procedure TmyTabbedListBox.UpdateTabStops;
var
i, iHUnits: integer;
arrConvertedTabs: TTabsArray;
begin
// convert dialog box units to pixels.
// dialog box unit = average character width/height div 4/8
// determine the horizontal dialog box units used by the
// list box (which depend on its current font)
Canvas.Font := Font;
iHUnits := Canvas.TextWidth( STR_ALPHA_UPPERLOWER ) div 52;
// convert the array of tab values
for i := Low( arrConvertedTabs ) to High( arrConvertedTabs ) do
arrConvertedTabs[i] := ( fTabStops[i] * 4 ) div iHUnits;
// activate the tabs stops in the list box,
// sending a Windows list box message
SendMessage( Handle, LB_SETTABSTOPS,
1 + High( arrConvertedTabs ) - Low( arrConvertedTabs ),
LongInt( #arrConvertedTabs ) );
end;
end.
Here's an example using a standard TListBox and it's OnDrawItem event, based on the code from the link you provided and tested in Delphi 2007. Note you need to set the ListBox.Style to lbOwnerDrawFixed. You can perhaps use this as a base for modifying the component (or just abandon it altogether).
procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer;
Rect: TRect; State: TOwnerDrawState);
var
LB: TListBox;
NewColor: TColor;
NewBrush: TBrush;
R: TRect;
Fmt: Cardinal;
ItemText: string;
begin
NewBrush := TBrush.Create;
LB := (Control as TListBox);
if (odSelected in State) then
begin
NewColor := LB.Canvas.Brush.Color;
end
else
begin
if not Odd(Index) then
NewColor := clSilver
else
NewColor := clYellow;
end;
NewBrush.Style := bsSolid;
NewBrush.Color := NewColor;
// This is the ListBox.Canvas brush itself, not to be
// confused with the NewBrush we've created above
LB.Canvas.Brush.Style := bsClear;
R := Rect;
ItemText := LB.Items[Index];
Fmt := DT_EXPANDTABS or DT_CALCRECT or DT_NOCLIP;
DrawText(LB.Canvas.Handle, PChar(ItemText), Length(ItemText),
R, Fmt);
// Note we need to FillRect on the original Rect and not
// the one we're using in the call to DrawText
Windows.FillRect(LB.Canvas.Handle, Rect, NewBrush.Handle) ;
DrawText(LB.Canvas.Handle, PChar(ItemText), Length(ItemText),
R, DT_EXPANDTABS);
NewBrush.Free;
end;
Here's the output of the above code:

How to download a message, store it and recreate it in a different folder? [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I'm using Delphi 2006, Indy 10 (ver. 4957), IMAP4.
I would like to download an e-mail message, store it and some weeks later I would like to recreate it in a different folder. (It is sort of archiving and restoring it, so simple moving between folders does not work as I will delete the original message.) I download the message, store it, then make a copy of it with AppendMsg.
It works until that point when I check the target Temp2 folder, where most of the messages contain
This is a multi-part message in MIME format
unit Mail_Test;
interface
uses
Windows,
Messages,
SysUtils,
Variants,
Classes,
Graphics,
Controls,
Forms,
Dialogs,
StdCtrls;
type
TForm1 = class( TForm )
memLog: TMemo;
btn1: TButton;
procedure btn1Click( Sender: TObject );
private
procedure Log( LogMsg: string );
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
uses
IdIMAP4,
IdMessage,
IdExplicitTLSClientServerBase,
IdSSLOpenSSL;
{$R *.dfm}
procedure TForm1.btn1Click( Sender: TObject );
var
IMAPClient: TIdIMAP4;
UsersFolders: TStringList;
OpenSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
res: Boolean;
i: integer;
inbox, currUID: string;
cntMsg: integer;
msg, msg2: TIdMessage;
BodyTexts: TStringList;
flags: TIdMessageFlagsSet;
fileName_MailSource, TmpFolder: string;
begin
IMAPClient := TIdIMAP4.Create( nil );
try
OpenSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create( nil );
try
IMAPClient.Host := 'imap.gmail.com';
IMAPClient.Port := 993;
IMAPClient.Username := '....#gmail.com';
IMAPClient.Password := '....';
if Pos( 'gmail.com', IMAPClient.Host ) > 0 then begin
OpenSSLHandler.SSLOptions.Method := sslvSSLv3;
IMAPClient.IOHandler := OpenSSLHandler;
IMAPClient.UseTLS := utUseImplicitTLS;
end;
try
res := IMAPClient.Connect;
if not res then begin
Log( ' Unsuccessful connection.' );
exit;
end;
except
on e: Exception do begin
Log( ' Unsuccessful connection.' );
Log( ' (' + Trim( e.Message ) + ')' );
exit;
end;
end;
try
UsersFolders := TStringList.Create;
try
res := IMAPClient.ListMailBoxes( UsersFolders );
if not res then begin
Log( ' ListMailBoxes error.' );
exit
end;
except
on e: Exception do begin
Log( ' ListMailBoxes error.' );
Log( ' (' + Trim( e.Message ) + ')' );
exit;
end;
end;
Log( 'User folders: ' + IntToStr( UsersFolders.Count ) );
for i := 0 to UsersFolders.Count - 1 do begin
Log( ' [' + inttostr( i + 1 ) + '/' + inttostr( UsersFolders.Count ) + '] Folder: "' + UsersFolders[ i ] + '"' );
end;
IMAPClient.RetrieveOnSelect := rsDisabled;
inbox := 'INBOX';
Log( 'Opening folder "' + inbox + '"...' );
res := IMAPClient.SelectMailBox( inbox );
cntMsg := IMAPClient.MailBox.TotalMsgs;
Log( 'E-mails to read: ' + IntToStr( cntMsg ) );
// res := IMAPClient.RetrieveAllEnvelopes( AMsgList );
msg := TIdMessage.Create( nil );
msg2 := TIdMessage.Create( nil );
BodyTexts := TStringList.Create;
TmpFolder := 'c:\';
res := IMAPClient.CreateMailBox( 'Temp2' )
try
for I := 0 to cntMsg - 1 do begin
Log( ' [' + inttostr( i + 1 ) + '/' + inttostr( cntMsg ) + '] E-mail...' );
IMAPClient.GetUID( i + 1, currUID );
Log( '(Downloading message...)' );
IMAPClient.UIDRetrieve( currUID, msg );
fileName_MailSource := TmpFolder + 'Log_Mail_' + currUID + '.eml';
msg.SaveToFile( fileName_MailSource, false );
// In the final version I will delete the original message
// so I have to recreate it from the archived file
msg2.LoadFromFile( fileName_MailSource );
res := IMAPClient.AppendMsg( 'Temp2', msg2, msg2.Headers, [] );
end;
finally
FreeAndNil( msg );
FreeAndNil( msg2 );
FreeAndNil( BodyTexts )
end;
finally
IMAPClient.Disconnect;
end;
finally
OpenSSLHandler.Free;
end;
finally
IMAPClient.Free;
end;
end;
procedure TForm1.Log( LogMsg: string );
begin
memLog.Lines.Add( LogMsg );
Application.ProcessMessages;
end;
end.
You are calling the version of AppendMsg() that lets you specify alternative email headers. In just about every situation I can think of, you will never want to do that (I don't even know why TIdIMAP4 exposes that functionality).
The reason is because AppendMsg() saves the TIdMessage to an internal TStream and then sends the email body from that TStream to the server. If you specify alternative headers, they will be sent as-is and not match the header data that was used to create the email body. Most importantly, the MIME boundary used to separate MIME parts within the email body will not match the boundary specified in the headers that are actually sent to the server, which would account for the symptoms you are seeing. That boundary value is randomly generated by TIdMessage whenever it is encoded, so it is not available in the TIdMessage.Headers property prior to calling AppendMsg().
So, with that said, I strongly suggest you change your code to set the AAlternativeHeaders parameter of AppendMsg() to nil (or use the overloaded version of AppendMsg() that does not have an AAlternativeHeaders parameter at all) so that AppendMsg() will send the actual headers that TIdMessage itself generates when it is encoded prior to upload:
res := IMAPClient.AppendMsg( 'Temp2', msg2, nil, [] );
Or:
res := IMAPClient.AppendMsg( 'Temp2', msg2, [] );

Resources