Ide Indy Delphi

Title: Hotmail---Delphi Style!
Question: This is the second of a three-part series showing how to access Hotmail through the HTTPMail "protocol" that OE uses using Delphi.
This part builds an application that allows the user to get and send messages via Hotmail.
Answer:
Previously published in Hardcore Delphi
(Pinnacle Publishing-Feb/Mar 2004)
In last month's issue, we took a look at how Outlook Express
communicates with Hotmail using HTTP and WebDAV. In this
article, we are going to build a sample project to do this in
Delphi. As stated in the first part of this series, Microsoft
continues to say that its WebDAV functionality is a beta
product and it has never been publicly documented nor added
this functionality to other products such as Outlook.
Overview
In my searches for information on Microsoft's implementation of
WebDAV for Hotmail, I came upon several Hotmail WebDAV projects
but most of them were written in java or C++. I did, however,
find one that was written in Visual Basic and the application
in this article is based upon that project. This sample
application is a port of the Visual Basic project provided by
Julian Benjamin that was included in the HTTPMail Sourceforge
project by Dave Dunkin. However, I am adding much functionality
that does not exist in the VB project.
The sample project uses Microsoft's XML and XMLHTTP controls to
accomplish communicating to Hotmail via HTTP and WebDAV. An
effort to port this functionality to a Delphi custom component
is underway. Ian Boyd has already done a conversion of the VB
Control to Delphi but his is a straight port and does not
include much of the functionality that I am adding in this
article.
Beginnings
The first thing we do is import the XML Type Library. This
TypeLib includes both the XML and XMLHTTP interfaces that we
will be using. In the Delphi IDE, we select Project-Import
Type Library from the menu and select Microsoft XML in the
list, select the package we want to install into and click
Install. Note that you may not see version 3.0 of MSXML in your
list unless you have IE5 or later installed. Version 3 of the
MSXML DLL was used in the making of this sample application and
other versions may be incompatible. The IXMLHTTPRequest and
IXMLDOMDocument objects are created in the FormCreate and
destroyed in the FormDestroy, as they are used throughout the
life of the app. See listing 1. We are also creating the
FolderList and MailList TList objects to hold Folder and Mail
Message information.
Listing 1. FormCreate
procedure TfmHotmailForm.FormCreate
( Sender: TObject
);
begin
// Create the XML and XMLHTTP Objects we are going to use
oXMLDoc := CreateOleObject('MSXML2.DOMDocument')
as IXMLDOMDocument;
oXMLHTTP := CreateOleObject('MSXML2.XMLHTTP.3.0') as IXMLHTTPRequest;
FFolderList := TList.Create;
FMailList := TList.Create;
// Initialize the FolderURLs to blank
InitProperties;
end;
Once we have the Type Library installed, we create a new
project. Drop a TTreeview, TListView, TToolbar, TActionList,
TMainMenu and TWebBrowser on your form (See Figure 1). The
Treeview will hold the Folder List, the Listview, the messages
list and the TWebBrowser is to show the message contents.
We are adding menu items and Actions to Mark messages Read and
Unread, Move and Delete messages, Empty the Trash Folder and to
Create and Delete Folders. We are also adding a menu item to
add the capability of composing and sending messages via
Hotmail, "New Message....
Figure 1.
Connecting
The actConnect Action calls the Connect function and if
successful, calls the RetrieveFolderURLs, ParseFolderInfo,
DisplayMail and ParseFolders procedures. The Connect function
in Listing 2 connects to Hotmail's servers and the
IXMLHTTPRequest object does the authentication internally.
Because of security patches that Microsoft has issued, the
services.msn.com domain needs to be added to the list of
Trusted sites for IE. If this is not done, XMLHTTP hangs on the
send and then will timeout with an OLE error 0x800C0008. There
is a function to check to see if the site is, in fact, in the
users IE Trusted Zone. If it is not, the user is prompted to
add it to run the demo properly. Even with this in mind, we
still have to call oXMLHTTP.Send twice to get it to work
correctly.
Listing 2.
function TfmHotmailForm.Connect:
Boolean;
var
UserPassDlg: TfmUserPassDlg;
begin
Result := False;
Result := CheckTrustedDomain;
if not Result then
begin
FHotmailError :=
'The site ' + HMTrustedHost +
' is not in the IE Trusted Zone'+ #13 +
'Please correct this for '+
'this demo to work properly';
Result := False;
Exit;
end;
if FUserName = '' then
begin
UserPassDlg := TfmUserPassDlg.Create(Self);
try
if UserPassDlg.Showmodal = mrOK then
begin
FUsername := Trim(UserPassDlg.UserName);
FPassword := Trim(UserPassDlg.Password);
end
else
begin
Result := False;
Exit;
end;
finally
UserPassDlg.Free;
end;
if FUserName = '' then
Exit;
end; Screen.Cursor := crHourglass;
try
oXMLHTTP.open('PROPFIND', HotmailURL, False,
FUserName, FPassword);
oXMLHTTP.setRequestHeader('PROPFIND',
QUERY_FOLDERS);
oXMLHTTP.setRequestHeader('Content-Type',
'text/xml');
oXMLHTTP.setRequestHeader('Depth', '0');
oXMLHTTP.setRequestHeader('User-Agent',
USER_AGENT);
oXMLHTTP.send(EmptyParam);
//
// This nees to be done twice to get the
// required result.
//
oXMLHTTP.open('PROPFIND', HotmailURL, False,
FUserName, FPassword);
oXMLHTTP.setRequestHeader('PROPFIND',
QUERY_FOLDERS);
oXMLHTTP.setRequestHeader('Content-Type',
'text/xml');
oXMLHTTP.setRequestHeader('Depth', '0');
oXMLHTTP.setRequestHeader('User-Agent',
USER_AGENT);
oXMLHTTP.send(EmptyParam);
// The ResponseText will be parsed for Folder
// Info, etc.
FResponseText := Trim(oXMLHTTP.ResponseText);
FResponseHdrs := oXMLHTTP.getAllResponseHeaders;
FHotmailError :=
oXMLHTTP.getResponseHeader('X-Dav-Error');
// We actually got something back from the server.
Result := FResponseText '';
if Result then
fmHotmailForm.Caption := fmHotmailForm.Caption +
' {connected as: '+ FUserName + '}';
finally
Screen.Cursor := crDefault;
end;
end;
Retrieving Folder and Message Info
The RetrieveFolderURLs procedure takes the ResponseText that is
returned from the IXMLHTTPRequest object and loads it into the
XMLDocument then parses it to obtain the FolderURL variables.
In ParseFolderInfo, we are loading the XML returned by
GetAllFolderInfo, which takes the RootFolderURL, does a
PROPFIND to get the sub-folder URLs, into the XMLDocument. We
then iterate through the nodes of the XMLDocument and create a
TFolderInfo object for each folder and load it into the
FolderList object. The Inbox is then selected in the Treeview.
Listing 3.
function TfmHotmailForm.RetrieveFolderURLs:
Boolean;
var
oResponseNode : IXMLDOMNode;
oHREFNode : IXMLDOMNode;
oPropStatNode : IXMLDOMNode;
oPropNode : IXMLDOMNode;
oElement : IXMLDOMElement;
oNodeList : IXMLDOMNodeList;
begin
oXMLDoc.LoadXML(FResponseText);
Result := oXMLDoc.childNodes.length 0;
if Result then
begin
oElement := oXMLDoc.documentElement;
oNodeList := oElement.childNodes;
oResponseNode := oNodeList.Item[0];
oHREFNode := oResponseNode.childNodes.Item[0];
FBaseRefURL := oHREFNode.text;
oPropStatNode := oResponseNode.childNodes.Item[1];
oPropNode := oPropStatNode.childNodes.Item[0];
FContactsURL := oPropNode.childNodes.Item[0].text;
FInboxURL := oPropNode.childNodes.Item[1].text;
FOutboxURL := oPropNode.childNodes.Item[2].text;
FSentItemsURL := oPropNode.childNodes.Item[3].text;
FTrashURL := oPropNode.childNodes.Item[4].text;
FRootFolderURL := oPropNode.childNodes.Item[5].text;
end
else
begin
FHotmailError := 'Invalid response from server';
ShowMessage(FHotmailError);
end;
end;
procedure TfmHotmailForm.ParseFolderInfo;
var
fInfo : TFolderInfo;
oNodelist : IXMLDOMNodeList;
i : Integer;
oFolderNode : IXMLDOMNode;
oNode : IXMLDOMNode;
begin
FFolderList.Clear;
oXMLDoc.loadXML(GetAllFolderInfo);
oNodelist := oXMLDoc.selectNodes('//D:response');
for i := 0 to oNodelist.length -1 do
begin
fInfo := TFolderInfo.Create;
oFolderNode := oNodelist.Item[i];
oNode := oFolderNode.childNodes[0];
fInfo.Url := oNode.text;
oNode := oFolderNode.childNodes[1].childNodes[0];
fInfo.Name := oNode.childNodes[1].text;
// Because the nodes for these folders are
// different, we need to get the msgcount and
// unreadcount from different child nodes.
if (fInfo.Name = 'msnpromo') or
(fInfo.Name = 'inbox') or
(fInfo.Name = 'sentitems') or
(fInfo.Name = 'deleteditems') then
begin
fInfo.UnreadCount :=
StrToInt(oNode.childNodes[4].text);
fInfo.MsgCount :=
StrToInt(oNode.childNodes[5].text);
end
else
begin
fInfo.UnreadCount :=
StrToInt(oNode.childNodes[5].text);
fInfo.MsgCount :=
StrToInt(oNode.childNodes[6].text);
end;
// These folders do not have a displayname element
// so we modify them as they are added.
// In the newer version of Hotmail, this folder
// no longer exists.
{
if Pos('msnpromo', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'msnpromo',
'MSN Announcements',
[rfReplaceAll]);
}
if Pos('inbox', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'inbox',
'Inbox',
[rfReplaceAll]);
if Pos('sentitems', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'sentitems',
'Sent Items',
[rfReplaceAll]);
if Pos('deleteditems', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'deleteditems',
'Deleted Items',
[rfReplaceAll]);
// if you want to leave it as Junk Mail .vs Bulk
// Mail, remove these two lines
if Pos('Junk E-Mail', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'Junk E-Mail',
'Bulk Mail',
[rfReplaceAll]);
if (Pos('Junk E-Mail', FInfo.Name) 0) then
FJunkURL := fInfo.Url;
FFolderList.Add(fInfo);
end;
end;
procedure TfmHotmailForm.DisplayMail;
var
i, x : Integer;
lItem : TListItem;
sentitems: Boolean;
MailInfoItem: TMailInfo;
begin
MessageView.Items.Clear;
if FMailList.Count 0 then
begin
if FolderView.Selected nil then
begin
i := FolderView.Selected.AbsoluteIndex;
Mailview.Navigate('about:blank');
lblSubject.Caption := 'Subject: ';
lblFrom.Caption := 'From: ';
if Lowercase(FolderView.Items[i].Text) =
'sentitems' then
begin
MessageView.Columns[1].Caption := 'To';
sentitems := True;
end
else
begin
MessageView.Columns[1].Caption := 'From';
sentitems := False;
end;
for x := 0 to FMailList.Count-1 do
begin
MailInfoItem := TMailInfo(FMailList.Items[x]);
lItem := MessageView.Items.Add;
lItem.Checked := False;
lItem.Data := MailInfoItem
lItem.Caption := MailInfoItem.Subject;
if sentitems then
lItem.SubItems.Add(MailInfoItem.Recipient)
else
lItem.SubItems.Add(MailInfoItem.From);
lItem.SubItems.Add(MailInfoItem.Date);
lItem.SubItems.Add(MailInfoItem.Length);
end;
MessageView.RowSelect := True;
end;
end;
end;
When a Folder node is clicked in the treeview, the Folder URL
is retrieved from the TFolderInfo object and ParseMailboxInfo
gets the Messages List from the selected folder, creates a
TMailInfo Object for each message, adds it to the MailList,
DisplayMail iterates through the MailList and fills the
Messages Listview with the info.
When an item in the Messages Listview is clicked, we obtain the
URL for the message from the MailInfo object and Navigate to it
in the TWebBrowser message viewer.
Listing 4.
function TfmHotmailForm.RetrieveFolderURLs:
Boolean;
var
oResponseNode : IXMLDOMNode;
oHREFNode : IXMLDOMNode;
oPropStatNode : IXMLDOMNode;
oPropNode : IXMLDOMNode;
oElement : IXMLDOMElement;
oNodeList : IXMLDOMNodeList;
begin
oXMLDoc.LoadXML(FResponseText);
Result := oXMLDoc.childNodes.length 0;
if Result then
begin
oElement := oXMLDoc.documentElement;
oNodeList := oElement.childNodes;
oResponseNode := oNodeList.Item[0];
oHREFNode := oResponseNode.childNodes.Item[0];
FBaseRefURL := oHREFNode.text;
oPropStatNode := oResponseNode.childNodes.Item[1];
oPropNode := oPropStatNode.childNodes.Item[0];
FContactsURL := oPropNode.childNodes.Item[0].text;
FInboxURL := oPropNode.childNodes.Item[1].text;
FOutboxURL := oPropNode.childNodes.Item[2].text;
FSentItemsURL := oPropNode.childNodes.Item[3].text;
FTrashURL := oPropNode.childNodes.Item[4].text;
FRootFolderURL := oPropNode.childNodes.Item[5].text;
end
else
begin
FHotmailError := 'Invalid response from server';
ShowMessage(FHotmailError);
end;
end;
procedure TfmHotmailForm.ParseFolderInfo;
var
fInfo : TFolderInfo;
oNodelist : IXMLDOMNodeList;
i : Integer;
oFolderNode : IXMLDOMNode;
oNode : IXMLDOMNode;
begin
FFolderList.Clear;
oXMLDoc.loadXML(GetAllFolderInfo);
oNodelist := oXMLDoc.selectNodes('//D:response');
for i := 0 to oNodelist.length -1 do
begin
fInfo := TFolderInfo.Create;
oFolderNode := oNodelist.Item[i];
oNode := oFolderNode.childNodes[0];
fInfo.Url := oNode.text;
oNode := oFolderNode.childNodes[1].childNodes[0];
fInfo.Name := oNode.childNodes[1].text;
// Because the nodes for these folders are
// different, we need to get the msgcount and
// unreadcount from different child nodes.
if (fInfo.Name = 'msnpromo') or
(fInfo.Name = 'inbox') or
(fInfo.Name = 'sentitems') or
(fInfo.Name = 'deleteditems') then
begin
fInfo.UnreadCount :=
StrToInt(oNode.childNodes[4].text);
fInfo.MsgCount :=
StrToInt(oNode.childNodes[5].text);
end
else
begin
fInfo.UnreadCount :=
StrToInt(oNode.childNodes[5].text);
fInfo.MsgCount :=
StrToInt(oNode.childNodes[6].text);
end;
// These folders do not have a displayname element
// so we modify them as they are added.
// In the newer version of Hotmail, this folder
// no longer exists.
{
if Pos('msnpromo', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'msnpromo',
'MSN Announcements',
[rfReplaceAll]);
}
if Pos('inbox', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'inbox',
'Inbox',
[rfReplaceAll]);
if Pos('sentitems', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'sentitems',
'Sent Items',
[rfReplaceAll]);
if Pos('deleteditems', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'deleteditems',
'Deleted Items',
[rfReplaceAll]);
// if you want to leave it as Junk Mail .vs Bulk
// Mail, remove these two lines
if Pos('Junk E-Mail', fInfo.Name) 0 then
fInfo.Name := StringReplace(fInfo.Name,
'Junk E-Mail',
'Bulk Mail',
[rfReplaceAll]);
if (Pos('Junk E-Mail', FInfo.Name) 0) then
FJunkURL := fInfo.Url;
FFolderList.Add(fInfo);
end;
end;
procedure TfmHotmailForm.DisplayMail;
var
i, x : Integer;
lItem : TListItem;
sentitems: Boolean;
MailInfoItem: TMailInfo;
begin
MessageView.Items.Clear;
if FMailList.Count 0 then
begin
if FolderView.Selected nil then
begin
i := FolderView.Selected.AbsoluteIndex;
Mailview.Navigate('about:blank');
lblSubject.Caption := 'Subject: ';
lblFrom.Caption := 'From: ';
if Lowercase(FolderView.Items[i].Text) =
'sentitems' then
begin
MessageView.Columns[1].Caption := 'To';
sentitems := True;
end
else
begin
MessageView.Columns[1].Caption := 'From';
sentitems := False;
end;
for x := 0 to FMailList.Count-1 do
begin
MailInfoItem := TMailInfo(FMailList.Items[x]);
lItem := MessageView.Items.Add;
lItem.Checked := False;
lItem.Data := MailInfoItem
lItem.Caption := MailInfoItem.Subject;
if sentitems then
lItem.SubItems.Add(MailInfoItem.Recipient)
else
lItem.SubItems.Add(MailInfoItem.From);
lItem.SubItems.Add(MailInfoItem.Date);
lItem.SubItems.Add(MailInfoItem.Length);
end;
MessageView.RowSelect := True;
end;
end;
end;
Deleting/Moving Messages and Emptying the Trash Folder
The process for deleting and moving message(s) is essentially
the same, there is just a different destination URL for the
selected message(s). {Listing 5} When a message is moved, a
folder must be selected to move it to and a folder selection
dialog has been provided in the project. When deleting
messages, the Trash folder is the destination URL. In moving
deleting or moving multiple messages, the program builds a list
of messages to delete/move and the list of messages are sent in
the XML in a BMOVE (bulk move) XMLHTTP call.
The original VB project did not have the capability to empty
the Trash Folder. This functionality was added based upon study
of the HTTPMail log file and the code that was passed between
Outlook Express and the Hotmail servers. When emptying the
Trash folder, a list of messages in the Trash Folder is built
and passed in a BDELETE (bulk delete) XMLHTTP call. Note that
BDELETE cannot be used on any other folder except the Trash
folder.
Listing 5.
procedure TfmHotmailForm.BuildMailList
( var AMailList: TStringList
);
var
i, iLastSlash : Integer;
sKey : String;
begin
// Adds messageids to the stringlist only
for i := 0 to MessageView.Items.Count-1 do
begin
if (MessageView.Items[i].Checked) or
(MessageView.Items[i].Selected)then
begin
sKey := TMailInfo(MessageView.Items[i].Data).URL;
iLastSlash := RatChar(sKey, '/');
sKey := Copy(sKey, iLastSlash + 1, Length(sKey));
AMailList.Add(sKey);
end;
end;
end;
procedure fmHotmailForm.actDeleteMessagesExecute
( Sender: TObject
);
var
i : Integer;
sCurrentFolder : String;
slMailList : TStringList;
FolderListItem: TFolderInfo;
begin
// Delete the checked messages
// Get the currently selected folder
i := FolderView.Selected.AbsoluteIndex-1;
FolderListItem := TFolderInfo(FFolderList.Items[i]);
sCurrentFolder := FolderListItem.URL;
slMailList := TStringList.Create;
try
// Adds all checked messageids (URLS) to the
// slMailList stringlist
BuildMailList(slMailList);
// move checked messages to the Trash Folder.
MoveMail(slMailList, sCurrentFolder, FTrashURL);
finally
slMailList.Free;
end;
end;
function TfmHotmailForm.MoveMail
( uMail: TStringList
; mSrcFolder
, mDestFolder: String
): String;
var
oElement: IXMLDOMElement;
oRoot : IXMLDOMElement;
sMail : String;
i : Integer;
begin
// Moves mail from mSrcFolder to mDestFolder
oXMLDoc.loadXML(MOVE_MAIL);
oRoot := oXMLDoc.documentElement;
// Add each checked message URl to the XML Document
for i := 0 to uMail.Count -1 do
begin
oElement := oXMLDoc.createElement('D:href');
oElement.Text := uMail.Strings[i];
oRoot.childNodes[0].appendChild(oElement);
end;
sMail := oXMLDoc.xml;
oXMLHTTP.open('BMOVE',
mSrcFolder,
False,
FUsername,
FPassword);
oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT);
oXMLHTTP.setRequestHeader('Destination', mDestFolder);
oXMLHTTP.setRequestHeader('Content-Type',
CONTENT_TYPE);
oXMLHTTP.send(sMail);
Result := oXMLHTTP.getAllResponseHeaders;
FResponseText := oXMLHTTP.ResponseText;
FHotmailError :=
oXMLHTTP.getResponseHeader('X-Dav-Error');
ParseFolderInfo;
ParseMailboxInfo(mSrcFolder);
DisplayMail;
end;
procedure TfmHotmailForm.actMoveMessagesExecute
( Sender : TObject
);
var
sCurrentFolder: String;
sDestFolder : String;
slMailList : TStringList;
FolderSelect : TfmFolderSelect;
i: Integer;
begin
// Move checked messages to the selected folder
FolderSelect := TfmFolderSelect.Create(Self);
try
FolderSelect.sDestFolder := sDestFolder;
for i := 0 to FFolderList.Count-1 do
begin
FolderSelect.ListBox1.Items.Add(TFolderInfo(FFolderList.Items[i]).Name);
end;
FolderSelect.ShowModal;
finally
sDestFolder := FolderSelect.sDestFolder;
FolderSelect.Free;
if sDestFolder '' then
begin
for i := 0 to FFolderList.Count-1 do
begin
if Pos(sDestFolder, TFolderInfo(FFolderList.Items[i]).Name) 0 then
begin
sDestFolder := TFolderInfo(FFolderList.Items[i]).URL;
Break;
end;
end;
i := FolderView.Selected.AbsoluteIndex-1;
sCurrentFolder := TFolderInfo(FFolderList.Items[i]).URL;
slMailList := TStringList.Create;
try
BuildMailList(slMailList);
MoveMail(slMailList, sCurrentFolder, sDestFolder);
finally
slMailList.Free;
end;
end;
end;
end;
Marking Message Read/Unread
When marking messages read or unread, a PROPPATCH method is
called to modify the Read property of the message in question.
If more than one message is checked in the listview, a list is
made of the checked messages and if there are more than one
message in the list, the BPROPPATCH method is used else the
PROPPATCH method is called for the lone message in the list as
shown in Listing 6.
Listing 6.
procedure TfmHotmailForm.FlagMailRead
( AMessages: TStrings
; currentfolder: String
);
var
oElement : IXMLDOMElement;
oRoot : IXMLDOMElement;
sMessages : String;
i : Integer;
begin
if AMessages.Count 1 then
begin
// Bulk Marks messages read
oXMLDoc.loadXML(FLAG_MAIL_READ);
oRoot := oXMLDoc.documentElement;
for i := 0 to AMessages.Count -1 do
begin
oElement := oXMLDoc.createElement('D:href');
oElement.Text := AMessages[i];
oRoot.childNodes[0].appendChild(oElement);
end;
sMessages := oXMLDoc.xml;
oXMLHTTP.open('BPROPPATCH', currentfolder, False,
FUsername, FPassword);
oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT);
oXMLHTTP.send(sMessages);
end
else
begin
oXMLHTTP.open('PROPPATCH', AMessages[0], False,
FUsername, FPassword);
oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT);
oXMLHTTP.send(FLAG_MAIL_READ);
end;
FResponseHdrs := oXMLHTTP.getAllResponseHeaders;
FResponseText := oXMLHTTP.ResponseText;
FHotmailError :=
oXMLHTTP.getResponseHeader('X-Dav-Error');
// Now refresh the current folder
ParseMailboxInfo(currentfolder);
DisplayMail;
end;
procedure TfmHotmailForm.FlagMailUnread
( AMessages: TStrings
; currentfolder: String
);
var
oElement : IXMLDOMElement;
oRoot : IXMLDOMElement;
sMessages : String;
i : Integer;
begin
if AMessages.Count 1 then
begin
// Bulk Marks messages unread
oXMLDoc.loadXML(FLAG_MAIL_UNREAD);
oRoot := oXMLDoc.documentElement;
for i := 0 to AMessages.Count -1 do
begin
oElement := oXMLDoc.createElement('D:href');
oElement.Text := AMessages[i];
oRoot.childNodes[0].appendChild(oElement);
end;
sMessages := oXMLDoc.xml;
oXMLHTTP.open('BPROPPATCH', currentfolder, False,
FUsername, FPassword);
oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT);
oXMLHTTP.send(sMessages);
end
else
begin
oXMLHTTP.open('PROPPATCH', AMessages[0], False,
FUsername, FPassword);
oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT);
oXMLHTTP.send(FLAG_MAIL_UNREAD);
end;
FResponseHdrs := oXMLHTTP.getAllResponseHeaders;
FResponseText := oXMLHTTP.ResponseText;
FHotmailError :=
oXMLHTTP.getResponseHeader('X-Dav-Error');
ParseFolderInfo;
// Now refresh the current folder
ParseMailboxInfo(currentfolder);
DisplayMail;
end;
procedure TfmHotmailForm.actMarkReadExecute
( Sender : TObject
);
var
i : Integer;
sMsgCount : String;
sUnReadCount : String;
sFolderInfo : String;
sCurrentFolder: String;
slMailList : TStringList;
FolderListItem: TFolderInfo;
begin
// Mark checked items Read
MessageView.Items.BeginUpdate;
i := FolderView.Selected.AbsoluteIndex-1;
FolderListItem := TFolderInfo(FFolderList.Items[i]);
sCurrentFolder := FolderListItem.URL;
slMailList := TStringList.Create;
try
BuildMailURLList(slMailList);
sFolderInfo := FolderListItem.fName;
sMsgCount := IntToStr(FolderListItem.MsgCount);
sUnReadCount :=
IntToStr(FolderListItem.UnreadCount);
sFolderInfo := sFolderInfo +
'(' + sMsgCount + ':' +
sUnreadCount + ')';
FolderView.Selected.Text := sFolderInfo;
for i := 0 to slMailList.Count-1 do
begin
FlagMailRead(slMailList.Strings[i],
sCurrentFolder);
end;
finally
slMailList.Free;
end;
ParseFolderInfo;
// Now refresh the current folder
ParseMailboxInfo(sCurrentFolder);
Mailview.Navigate('about:blank');
lblSubject.Caption := 'Subject: ';
lblFrom.Caption := 'From: ';
DisplayMail;
MessageView.Items.EndUpdate;
end;
procedure TfmHotmailForm.actMarkUnreadExecute
( Sender : TObject
);
var
i : Integer;
sMsgCount : String;
sUnReadCount : String;
sFolderInfo : String;
sCurrentFolder: String;
slMailList : TStringList;
FolderListItem: TFolderInfo;
begin
// Mark checked items Unread
MessageView.Items.BeginUpdate;
i := FolderView.Selected.AbsoluteIndex-1;
FolderListItem := TFolderInfo(FFolderList.Items[i]);
sCurrentFolder := FolderListItem.URL;
slMailList := TStringList.Create;
try
BuildMailURLList(slMailList);
sFolderInfo := FolderListItem.fName;
sMsgCount := IntToStr(FolderListItem.MsgCount);
sUnReadCount :=
IntToStr(FolderListItem.UnreadCount);
sFolderInfo := sFolderInfo +
'(' + sMsgCount + ':' +
sUnreadCount + ')';
FolderView.Selected.Text := sFolderInfo;
for i := 0 to slMailList.Count-1 do
begin
FlagMailUnRead(slMailList.Strings[i],
sCurrentFolder);
end;
finally
slMailList.Free;
end;
ParseFolderInfo;
// Now refresh the current folder
ParseMailboxInfo(sCurrentFolder);
Mailview.Navigate('about:blank');
lblSubject.Caption := 'Subject: ';
lblFrom.Caption := 'From: ';
DisplayMail;
MessageView.Items.EndUpdate;
end;
Creating/Deleting Folders
This functionality was also not present in the VB project. In
WebDAV, folders are considered collections, hence the MKCOL
method to create a folder. And to remove a folder, we use the
standard DELETE method.
Listing 7.
function TfmHotmailForm.CreateNewFolder
( mNewFolderName: String
): String;
var
sKey: String;
begin
sKey := FRootFolderURL + mNewFolderName;
oXMLHTTP.open('MKCOL', sKey, False,
FUserName, FPassword);
oXMLHTTP.setRequestHeader('User-Agent', USER_AGENT);
oXMLHTTP.setRequestHeader('Content-Type',
CONTENT_TYPE);
oXMLHTTP.send(CREATE_FOLDER);
oXMLHTTP.open('PROPFIND', HotmailURL, False, '', '');
GetFolderInfo(sKey);
ParseFolderInfo;
ParseMailboxInfo(sKey);
Mailview.Navigate('about:blank');
lblSubject.Caption := 'Subject: ';
lblFrom.Caption := 'From: ';
DisplayMail;
end;
function TfmHotmailForm.DeleteFolder
( mDestFolder: String
): String;
begin
// Delete the folder using the DELETE method
oXMLHTTP.open('DELETE', mDestFolder, False,
FUserName, FPassword);
ParseFolderInfo;
ParseFolders;
oXMLHTTP.open('PROPFIND', HotmailURL, False, '', '');
GetFolderInfo(FInBoxURL);
ParseFolderInfo;
ParseMailboxInfo(FInBoxURL);
Mailview.Navigate('about:blank');
lblSubject.Caption := 'Subject: ';
lblFrom.Caption := 'From: ';
DisplayMail;
end;
procedure TfmHotmailForm.actNewFolderExecute
( Sender : TObject
);
begin
CreateNewFolder(InputBox('New Folder',
'Enter Name for New Folder',
'New Folder'));
end;
procedure TfmHotmailForm.actDeleteFolderExecute
( Sender : TObject
);
var
i : Integer;
sDestFolder : String;
FolderSelect: TfmFolderSelect;
begin
sDestFolder := '';
FolderSelect := TfmFolderSelect.Create(Self);
try
FolderSelect.sDestFolder := sDestFolder;
for i := 0 to FFolderList.Count-1 do
begin
FolderSelect.ListBox1.Items.Add(TFolderInfo(FFolderList.Items[i]).Name);
end;
FolderSelect.Caption := 'Select Folder to Delete';
FolderSelect.ShowModal;
finally
sDestFolder := FolderSelect.sDestFolder;
FolderSelect.Free;
if sDestFolder '' then
begin
for i := 0 to FFolderList.Count-1 do
begin
if Pos(sDestFolder, TFolderInfo(FFolderList.Items[i]).Name) 0 then
begin
sDestFolder := TFolderInfo(FFolderList.Items[i]).URL;
Break;
end;
end;
DeleteFolder(sDestFolder);
end;
end;
end;
Extras
There are a lot of little extras in the program that help to
make this a presentable application. In ParseFolderInfo, I
modify the folder names for the deleted items, msnpromo,
sentitems, inbox, and Junk Mail to the Outlook Express
default folder titles. When the folder Sent Items is selected,
the From: column header is changed to To: and unread
messages are in bold text in the DisplayMail procedure.
Sending Messages via Hotmail WebDAV
Messages can also be sent via Hotmails servers without using
Outlook Express or any other email client using the WebDAV POST
method. The SendMail procedure only allows plaintext messages
but this could be enhanced to include HTML messages as well as
attachments. Be sure to read RFC821 for more info on the format
of multipart messages. You can also save a copy of the message
in your Hotmail sent folder by setting the request header value
SAVEINSENT: to t.
Listing 8
procedure TfmHotmailForm.actNewMessageExecute
( Sender : TObject
);
var
frmNewMessage: TfrmNewMessage;
begin
frmNewMessage := TfrmNewMessage.Create(Self);
try
if frmNewMessage.ShowModal = mrOK then
SendMail(FuserName + '@hotmail.com',
FUserName,
frmNewMessage.edtToAddress.Text,
frmNewMessage.edtSubject.Text,
frmNewMessage.chkSaveCopy.Checked,
frmNewMessage.memMsgBody.Lines);
finally
frmNewMessage.Free;
end;
end;
procedure TfmHotmailForm.SendMail
( AFromAddress
, AFromName
, AToAddress
, ASubject: String
; ASaveCopy: Boolean
; ABody: TStrings
);
var
sTimeStamp, sHeaders, sSaveCopy: String;
slPostBody: TStringList;
i: Integer;
begin
slPostBody := TStringList.Create;
try
// Generate the time stamp.
sTimeStamp := FormatDateTime('ddd, dd MMM yyyy '+
'hh:mm:ss', Now);
// Dump mail headers.
slPostBody.Add('MAIL FROM:+ AFromAddress + '');
slPostBody.Add('RCPT TO:+ AToAddress + '');
slPostBody.Add('');
slPostBody.Add('From: "' + AFromName + '"
' + AFromAddress + '');
slPostBody.Add('To: + AToAddress + '');
slPostBody.Add('Subject: ' + ASubject +'');
//
// Change the GMT setting based upon your timezone,
// please.
//
slPostBody.Add('Date: ' + sTimeStamp + ' -6000');
slPostBody.Add('');
// Dump mail body.
for i := 0 to ABody.Count-1 do
slPostBody.Add(ABody.Strings[i]);
// Open the connection.
oXMLHTTP.open('POST', FOutBoxURL, False,
FUsername, FPassword);
// Send the request.
if ASaveCopy then
sSaveCopy := 't'
else
sSaveCopy := 'f';
oXMLHTTP.setRequestHeader('SAVEINSENT:', sSaveCopy);
oXMLHTTP.setRequestHeader('User-Agent:',
USER_AGENT);
oXMLHTTP.setRequestHeader('Content-Type',
'message/rfc821');
oXMLHTTP.send(slPostBody.Text);
sHeaders := oXMLHTTP.getAllResponseHeaders;
FResponseText := oXMLHTTP.ResponseText;
FHotmailError :=
oXMLHTTP.getResponseHeader('X-Dav-Error');
// Show the error if it doesn't go through.
if Pos('200', FHotmailError) = 0 then
ShowMessage(FHotmailError);
finally
slPostBody.Free;
end;
end;
Conclusion
In this issue, we built an application that can communicate
directly with Hotmail. You can now make this functionality
available in your own programs by following the example.
Hopefully, this will satisfy the many users that have asked for
such a feature in their applications. I am also working on a
conversion to Delphi for .Net, look for it in a future issue.