What is the best way of detecting that a Delphi TWebBrowser web page has changed since I last displayed it? - delphi

I want to display a 'news' page in a form using Deplhi TWebBrowser. The news page is a simple HTML page which we upload to our website from time to time and may be output from various tools. The display is fine but I'd like to know in my app whether it has changed since I last displayed it, so ideally I'd like to get either its modified date/time or its size / checksum. Precision is not important and ideally should not rely on properties that might fail because 'simple' tools were used to edit the HTML file such as NotePad. Checking on the web there are several document modified java calls but I really dont know where to start with those. I've looked through the numerous calls in Delphi's Winapi.WinInet unit and I see I can fetch the file with HTTP to examine it but that seems like cracking a walnut with a sledgehammer. I also cannot see any file date time functionality which makes me think I'm missing something obvious. I'm using Delphi XE5. In which direction should I be looking please? Thanks for any pointers.

You could use Indy TIdHTTP to send a HEAD request and examine Last-Modified / Content-Length headers.
e.g.:
procedure TForm1.Button1Click(Sender: TObject);
var
Url: string;
Http: TIdHTTP;
LastModified: TDateTime;
ContentLength: Integer;
begin
Url := 'http://yoursite.com/newspage.html';
Http := TIdHTTP.Create(nil);
try
Http.Head(Url);
LastModified := Http.Response.LastModified;
ContentLength := Http.Response.ContentLength;
ShowMessage(Format('Last-Modified: %s ; Content-Length: %d', [DateTimeToStr(LastModified), ContentLength]));
finally
Http.Free;
end;
end;
When the TWebBrowser.DocumentComplete event is fired make a HEAD request and store LastModified and ContentLength variables.
Then periodically make HEAD requests to test for changes (via TTimer for example).
These Header parameters are dependent on the web server implementation, and may not return file system date-time on the server (dynamic pages for example). your server might not result back these parameters at all.
For example, with static HTML pages on IIS, Last-Modified returns the file system last modified date-time, which is what you want.
For dynamic content (e.g. php, asp, .NET etc..), if you control the web-server, you might as well add your own custom HTTP response header on the server side to indicate the file system date-time (e.g. X-Last-Modified) or set the response Last-Modified header to your needs and examine this header on the client side.
If you need to examine/hash the entire HTTP content, you need to issue a GET method: http.Get(URL)

Thanks to a mixture of suggestions and pointers from kobik, David and TLama, I realised that I actually did need a sledgehammer and I finally came up with this solution (and I'm probably not the first, or the last!). I had to read the file contents because this did seem a better way of detecting changes. The code below calls "CheckForWebNewsOnTimer" from a TTimer infrequently and uses Indy to read the news page, make an MD5 hash of its contents and compare that with a previous hash stored in the registry. If the contents change, or 120 days elapses, the page pops up. The code has wrinkles, for example a change to a linked image on the page might not trigger a change but hey, its only news, and text almost always changes too.
function StreamToMD5HashHex( AStream : TStream ) : string;
// Creates an MD5 hash hex of this stream
var
idmd5 : TIdHashMessageDigest5;
begin
idmd5 := TIdHashMessageDigest5.Create;
try
result := idmd5.HashStreamAsHex( AStream );
finally
idmd5.Free;
end;
end;
function HTTPToMD5HashHex( const AURL : string ) : string;
var
HTTP : TidHTTP;
ST : TMemoryStream;
begin
HTTP := TidHTTP.Create( nil );
try
ST := TMemoryStream.Create;
try
HTTP.Get( AURL, ST );
Result := StreamToMD5HashHex( ST );
finally
ST.Free;
end;
finally
HTTP.Free;
end;
end;
function ShouldShowNews( const ANewHash : string; AShowAfterDays : integer ) : boolean;
const
Section = 'NewsPrompt';
IDHash = 'LastHash';
IDLastDayNum = 'LastDayNum';
var
sLastHash : string;
iLastPromptDay : integer;
begin
// Check hash
sLastHash := ReadRegKeyUserStr( Section, IDHash, '' );
Result := not SameText( sLastHash, ANewHash );
if not Result then
begin
// Check elapsed days
iLastPromptDay := ReadRegKeyUserInt( Section, IDLastDayNum, 0 );
Result := Round( Now ) - iLastPromptDay > AShowAfterDays;
end;
if Result then
begin
// Save params for checking next time.
WriteRegKeyUserStr( Section, IDHash, ANewHash );
WriteRegKeyUserInt( Section, IDLastDayNum, Round(Now) );
end;
end;
procedure CheckForWebNewsOnTimer;
var
sHashHex, S : string;
begin
try
S := GetNewsURL; // < my news address
sHashHex := HTTPToMD5HashHex( S );
If ShouldShowNews( sHashHex, 120 {days default} ) then
begin
WebBrowserDlg( S );
end;
except
// .. ignore or save as info
end;
end;

Related

Delphi, retrieve both visible text and hidden hyperlink when pasting into a delphi application

How can I do that? I've been looking all over the internet to find some clues but failed.
You can click on a link in the browser and copy it and then paste it into a word doc document for example.
I using a tcxGrid with some fields and want to paste this link into the field. The field will show you the text but if you click on it it will open the browser with this link.
I can fix all the later part but I don't know how to extract the text and the link from the clipboard.
Does anyone know how to do it?
I've found an old article that describes how you can do it but the result is not good. I get Chinese text instead of HTML.. see below my test code:
function TForm2.clipBoardAsHTML: string;
var
CF_HTML: UINT;
CFSTR_INETURL: UINT;
URL: THandle;
HTML: THandle;
Ptr: PChar;
begin
CF_HTML := RegisterClipboardFormat('HTML Format');
CFSTR_INETURL := RegisterClipboardFormat('UniformResourceLocator');
result := '';
with Clipboard do
begin
Open;
try
HTML := GetAsHandle(CF_HTML);
if HTML <> 0 then
begin
Ptr := PChar(GlobalLock(HTML));
if Ptr <> nil then
try
Result := Ptr;
finally
GlobalUnlock(HTML);
end;
end;
finally
Close;
end;
end;
end;
Data looks like:
敖獲潩㩮⸱ര匊慴瑲呈䱍〺〰〰〰ㄲര䔊摮呈䱍〺〰〰㈰㐳ള匊慴
and much more.
So something is wrong with my code it looks.. :(
The recommended format CFSTR_INETURL does not exist in the clipboard when takes a copy from Firefox, and Excel so I couldn't get any data using that format.
==================================
Latest test - Retrieve of format names.
procedure TForm2.Button2Click(Sender: TObject);
var
i: integer;
s: string;
szFmtBuf: array[0..350] of PWideChar;
fn: string;
fmt: integer;
begin
Memo1.Clear;
for i := 0 to clipBoard.FormatCount - 1 do
begin
fmt := clipBoard.Formats[i];
getClipBoardFormatName(fmt,#szFmtBuf,sizeOf(szFmtBuf));
fn := WideCharToString(#szFmtBuf);
if fmt >= 49152 then
Memo1.Lines.Add(fmt.ToString+ ' - '+fn);
end;
end;
Finally I made this code work :) but the main question how I'll get the url from the clipboard are still unsolved. :(
If I loop through all found formats I only get garbage from them.
The formats from Firefox looks:
49161 - DataObject
49451 - text/html
49348 - HTML Format
50225 - text/_moz_htmlcontext
50223 - text/_moz_htmlinfo
50222 - text/x-moz-url-priv
49171 - Ole Private Data
It really depends on which format(s) the copier decides to place on the clipboard. It may place multiple formats on the clipboard at a time.
A hyperlink with url and optional text may be represented using either:
the Shell CFSTR_INETURL format (registered name: 'UniformResourceLocator') containing the URL of the link, and the CF_(UNICODE)TEXT format containing the text of the link, if any.
the CF_HTML format (registered name: 'HTML Format') containing whole fragments of HTML, including <a> hyperlinks and optional display text.
The VCL's TClipboard class has HasFormat() and GetAsHandle() methods for accessing the data of formats other than CF_(UNICODE)TEXT (which can be retrieved using the TClipboard.AsText property).
You need to use the Win32 RegisterClipboardFormat() function at runtime to get the format IDs for CFSTR_INETURL and CF_HTML (using the name strings mentioned above) before you can then use those IDs with HasFormat() and GetAsHandle().
You can also enumerate the formats that are currently available on the clipboard, using the TClipboard.FormatCount and TClipboard.Formats[] properties. For format IDs in the $C000..$FFFF range, use the Win32 GetClipboardFormatName() function to retrieve the names that were originally registered with RegisterClipboardFormat().

How to get all cookie details with TEmbeddedWB?

In a Delphi XE8 VCL Form Aplication, with TEmbeddedWB I get the cookies with this method:
CookieStr := EmbeddedWB1.Cookie;
CodeSite.Send('CookieStr', CookieStr);
This is the result (for example):
name1=value1; name2=value2; name3=value3
However, as you can see, this gets only the name and value of the cookies.
So how can I get the other cookie fields such as path, expiration date, etc.?
Here is the solution:
First, we need to make us familiar with the Winapi FILETIME structure.
Then get the IE cookie files from here:
C:\Users\%username%\AppData\Roaming\Microsoft\Windows\Cookies\
Now look for the cookie files with the identical name=value pairs you got from TEmbeddedWB.Cookie.
Here is an example of the content of a IE cookie file, where we take our data from:
(Similarities with people alive or other authorities are purely accidental and not intended!)
We can see the meaning of the various numbers from the red colored comments.
And here is the source code to decipher those numbers:
uses Winapi.Windows;
function ConvertWinapiFileTimeLoHiValuesToDateTimeStr(const AFTLoValue, AFTHiValue: Cardinal): string;
const
InvalidDate = '01/01/80 12:00:00 AM';
var
lCookieFileTime: TFileTime;
lDosDT: Integer;
lLocalFileTime: TFileTime;
begin
lCookieFileTime.dwLowDateTime := AFTLoValue;
lCookieFileTime.dwHighDateTime := AFTHiValue;
FileTimeToLocalFiletime(lCookieFileTime, lLocalFileTime);
if FileTimeToDosDateTime(lLocalFileTime, Longrec(lDosDT).Hi, Longrec(lDosDT).Lo) then
begin
try
Result := DateTimeToStr(FiledateToDatetime(lDosDT));
except
Result := InvalidDate;
end;
end
else
Result := InvalidDate;
end;
Now we can use this function with the numbers from the above cookie file, as an example:
CodeSite.Send('Expiration Date', ConvertWinapiFileTimeLoHiValuesToDateTimeStr(2496134912, 30471078));
CodeSite.Send('Modified Date', ConvertWinapiFileTimeLoHiValuesToDateTimeStr(2624224682, 30465043));
Which will give us this result:

How to pass multilined TStrings data from a TIdTCPServer to TIdTCPClient

I tried to pass a database record from my server-side application to my client-side application. On the client-side I need to store my data into a TStrings collection.
When I pass a multiline field, I receive two separate data items at the client-side, instead of one multiline data item! I've also tried to do that with Unicode UTF8 based commands, but unfortunately the result is same.
Server-side code:
procedure TForm1.IdCmdTCPServer1CommandHandlers0Command(ASender: TIdCommand);
var
myData: TStrings;
begin
myData := TStringList.Create;
myData.Add('12'); // ID
myData.Add('This is a multi line' + #13#10 + 'description.'); // Descriptions
myData.Add('Thom Smith'); // Name
try
ASender.Context.Connection.Socket.Write(myData, True{, TIdTextEncoding.UTF8});
finally
myData.Free;
end;
end;
myData debug-time values on server-side are:
myData[0] = '12'
myData[1] = 'This is a multi line'#$D#$A'description.'
myData[2] = 'Thom Smith'
Client-side code:
procedure TForm1.Button1Click(Sender: TObject);
var
myData: TStrings;
begin
with TIdTCPClient.Create(nil) do
begin
Port := 1717;
Host := 'localhost';
try
Connect;
//IOHandler.DefStringEncoding := TIdTextEncoding.UTF8;
myData := TStringList.Create;
try
SendCmd('greating');
Socket.ReadStrings(myData, -1{, TIdTextEncoding.UTF8});
eID.Text := myData[0]; // ID TEdit
mDes.Text := myData[1]; // Descriptions TMemo
eTelNo.Text := myData[2]; // Name TEdit
finally
myData.Free;
end;
finally
Disconnect;
Free;
end;
end;
end;
myData debug-time valuese on client-side:
myData[0] = '12'
myData1 = 'This is a multi line'
myData[2] = 'description.'
Telnet result:
Actually, myData[2] that should keep 'Thom Smith' was replaced with the second line of the Description field! and there are no items after myData[2]. myData[3] is not accessible any more.
I think this issue is related to Indy's Write or ReadStrings procedures, because it sends ItemCount as 3, but it sends two items (one correct, and next beaked to two items!).
How can I pass a Carriage Return character to the other side without having the Write procedure break myData[1] into two separate lines?
Thanks a lot.
If you want TStrings.Text be oblivious to special characters - you should escape them before sending by net, and un-escape after that. There are a lot of ways of escaping, so choose one that suits you.
function EscapeString:(String): String --- your choice
function DeEscapeString:(String): String --- your choice
procedure SendEscapedStrings(const socket: TIdSocket; const data: TStrings);
var s: string; temp: TStringList;
begin
temp := TStringList.Create;
try
temp.Capacity := data.Count;
for s in data do
temp.Add( EscapeString( s ) );
socket.Write(temp);
finally
temp.Destroy;
end;
end;
procedure ReadDeescapedStrings(const socket: TIdSocket; const data: TStrings);
var s: string; temp: TStringList;
begin
temp := TStringList.Create;
try
Socket.ReadStrings(temp, -1);
data.Clear;
data.Capacity := temp.Count;
for s in temp do
temp.Add( DeEscapeString( s ) );
finally
temp.Destroy;
end;
end;
Now the question is what would you choose for DeEscapeString and EscapeString ? The options are many.
You can choose convert string to base64 before sending and from base64 after reading
You can choose UUEEncode for escapgin and UUEDecode for de-escaping
You can choose yEnc: http://en.wikipedia.org/wiki/YEnc
Or you can choose very simplistic functions StrStringToEscaped and StrEscapedToString from JclString unit of from Jedi CodeLib ( http://jcl.sf.net ):
what kind of escaping
If you ask for suggestion i would suggest not using raw TCP Server. There is well-known and standard HTTP protocol, there are many libraries for Delphi implementing both HTTP server and HTTP client. And in the protocol (and libraries) there are already decided things like ciphering, compressing, languages support, etc. And if somethign goes wrong - you can take any HTTP sniffer and see who is in the wrong- clent or server - with your own eyes. Debugging is much simpler.
If you are just starting, i suggest you looking into HTTP+JSON Synopse mORMot library, maybe it would cover your needs. You can take sample server code from http://robertocschneiders.wordpress.com/2012/11/22/datasnap-analysis-based-on-speed-stability-tests/ for example, or from demos in the lib.
Then, if to arrange around raw TCP server, i'd send compressed data, so it would work better (networks are slower than CPU usually). See http://docwiki.embarcadero.com/CodeExamples/XE5/en/ZLibCompressDecompress_(Delphi).
Sending:
1: Send into network (int32) - TStringList.Count
2: for every string doing
2.1 creating TStringStream from the string[i]
2.2 passing it via TZCompressionStream
2.3 sending (int32) size of compressed data
2.4 sending the data itself
2.5 freeing the temporary streams
Receiving
1: Receive from net (int32) - count of packets
1.1 ResultStringList.Clear; ResultStringList.Capacity := read_count.
2: for every string doing
2.1 creating TBytesStream
2.2 read from net (int32) size of compressed data
2.3 read N bytes from the network into BytesStream
2.4 unpack it via TZDecompressionStream into TStringStream
2.5 ResultStringList.Add( StringStream -> string );
2.6 freeing the temporary streams
Now, if you really don't want ot change almost anything, then JCL escaping would hopefully be enough for you. At least it worked for me, but my task was very different and was not about networks at all. But you can just test them all and see how it works for you.
Don't use the TStrings overload as it seems to use line breaks as separator between strings which does not work if your strings contain line breaks themselves.
You can easily write your own wrapper method to send a list of strings over the wire (take that as pseudocode):
procedure WriteStrings(IOHandler : TIdIOHandler; Strings : TStrings);
var
Str : String;
begin
IOHandler.WriteBufferOpen;
try
IOHandler.Write(Strings.Count);
for Str in Strings do
IOHandler.Write(Str);
finally
IOHandler.WriteBufferClose;
end;
end;
procedure ReadStrings(IOHandler : TIdIOHandler; Strings : TStrings);
var
Count, I : Integer;
begin
Count := IOHandler.ReadInteger;
for I := 1 to Count do
Strings.Add(IOHandler.ReadString);
end;

Sending email with Indy 9 with an embedded picture

I am trying to add to a program of mine the capability of sending html email via SMTP with Indy 9. If the program only contains text (the text will be in Hebrew so I need to display it right to left, which means that I am using HTML statements), then the email is sent correctly. My problem lays with embedding pictures into the HTML stream.
The HTML stream will use a command like
<IMG SRC="cid:foo4atfoo1atbar.net" ALT="IETF logo">
Whilst the Indy 10 component TIdAttachmentFile has a ComponentID property whose value has to be set to the value that 'cid' references, I can't find where to set the ComponentID property in Indy 9.
At the moment, the code which deals with adding the picture (whose name is in laPicture.text) looks like this
if laPicture.text <> '' then
with TIdAttachment.Create (email.MessageParts, laPicture.text) do
begin
ContentDisposition:= 'inline';
ContentType:= 'image/jpeg';
DisplayName:= ExtractFileName (laPicture.text);
filename:= ExtractFileName (laPicture.text);
end;
Where do I define the ContentID?
And, although this is a stupid question, how do I know which version of Indy I have?
TIdAttachment derives from TIdMessagePart, which has a public ContentID property. If your installed version of Indy 9 does not have that property, then you are using an outdated version, so use the ExtraHeaders property instead to add a Content-ID header manually.
Have a look at the following blog article on Indy's website for more information about working with HTML emails:
HTML Messages
Update: so, if the HTML says cid:foo4atfoo1atbar.net then you need to do this in your code to match it:
with TIdAttachment.Create (email.MessageParts, laPicture.text) do
begin
...
ContentID := '<foo4atfoo1atbar.net>';
// or this, if you do not have the ContentID property available:
// ExtraHeaders.Values['Content-ID'] := '<foo4atfoo1atbar.net>';
end;
Note that in Indy 9, you have to provide the brackets manually. Indy 10 inserts them for you if they are omitted, eg:
ContentID := 'foo4atfoo1atbar.net';
I found a solution - I didn't need Indy10 not the Content-ID field.
The code which I showed in my question was fine, the problem was probably in the HTML code which displayed the picture. I thought that the "cid" variable had to 'point' to the value of Content-ID; it transpires that it can be set to the name of the file (TIDAttachment.filename), as follows
<img src="cid:' + ExtractFileName (laPicture.text) + '"><br>
The above line gets inserted into the html stream at the appropriate place.
This works for me:
function SendEmail(SMTP: TIdSMTP; CONST AdrTo, AdrFrom, Subject, Body, HtmlImage, DownloadableAttachment: string; SendAsHtml: Boolean= FALSE): Boolean;
VAR MailMessage: TIdMessage;
begin
Result:= FALSE;
Assert(SMTP <> NIL, 'SMTP in NIL!');
MailMessage:= TIdMessage.Create(NIL);
TRY
MailMessage.ConvertPreamble:= TRUE;
MailMessage.Encoding := meDefault;
MailMessage.Subject := Subject;
MailMessage.From.Address := AdrFrom;
MailMessage.Priority := mpNormal;
MailMessage.Recipients.EMailAddresses := AdrTo;
{How to send multi-part/attachment emails with Indy:
www.indyproject.org/2005/08/17/html-messages
www.indyproject.org/2008/01/16/new-html-message-builder-class }
WITH IdMessageBuilder.TIdMessageBuilderHtml.Create DO
TRY
if SendAsHtml
then Html.Text := Body
else PlainText.Text := Body;
{ This will be visible ONLY if the email contains HTML! }
if SendAsHtml AND FileExists(HtmlImage)
then HtmlFiles.Add(HtmlImage);
if FileExists(DownloadableAttachment)
then Attachments.Add(DownloadableAttachment);
FillMessage(MailMessage);
FINALLY
Free;
END;
{ Connect }
TRY
if NOT SMTP.Connected
then SMTP.Connect;
EXCEPT
on E: Exception DO
begin
AppLog.AddError('Cannot connect to the email server.');
AppLog.AddError(E.Message);
end;
END;
{ Send mail }
if SMTP.Connected then
TRY
SMTP.Send(MailMessage);
Result:= TRUE;
EXCEPT
on E:Exception DO
begin
AppLog.AddError('Connected to server but could not send email!');
AppLog.AddError(E.Message);
end;
END;
if SMTP.Connected
then SMTP.Disconnect;
FINALLY
FreeAndNil(MailMessage);
END;
end;
Note: Replace AppLog with your personal logging system or with ShowMessage.
You need of course the libeay32.dll + ssleay32.dll. I would have posted a link to them, but could not find them anymore.

Return an image from a Delphi REST server and show it in a browser

When you return an image using a file stream object in a Delphi rest server, it will not display in a browser. Here is an example method that returns an image:
function TServerClass.Image: TFileStream;
begin
Result := TFileStream.Create('pathtofile\image.png', fmOpenRead or fmShareDenyNone);
end;
The problem is that Delphi REST server always sets the content type to text/html. This confuses the browser when you send other types of content. It is a bug, since most responses are json, which means the most sensible default content type should be application/json.
Fortunately there is a way to override the content type from within the server method.
You need to add Data.DBXPlatform to the uses list of your implementation.
This unit contains the function GetInvocationMetadata, which gives access to the response that is being build. It returns a TDSInvocationMetadata object which among varius other useful properties has the ResponseContentType property.
Setting this property overrides the Content-Type header that the method returns in the http response.
The given example becomes:
function TServerClass.Image: TFileStream;
begin
Result := TFileStream.Create('pathtofile\image.png', fmOpenRead or fmShareDenyNone);
GetInvocationMetadata.ResponseContentType := 'image/png';
end;
Now the result image will be displayed properly in the browser.
I've found this problem too trying to download different file types (png, pdf, xlsx, docx, etc...) from DataSnap REST server (Delphi XE3) to a JavaScript web client.
Some browsers (es.: FireFox) will take correct action anyway, but not all. Internet Explorer doesn't recognize the proper action for the downloaded file without correct content-type.
The #Anders solution initially seems to work for me because I was working with PDF and Firefox.
But when I've tested on IE (and others) and with different extensions, the files where not recognised. Using FireBug I've seen that Content-Type was always "text/html" and not the assigned one using
GetInvocationMetadata.ResponseContentType := '...my assigned content type ...';
The workaround found working for me is:
In ServerMethodsUnit
var
ContentTypeHeaderToUse: string; // Global variable
TServerMethods1.GetFile(params: JSON):TStream;
begin
.... processing ....
ContentTypeHeaderToUse := '...' (assign correct content type).
end;
In WebModuleUnit
procedure TWebModule1.WebModuleAfterDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
if ContentTypeHeaderToUse<>'' then begin
Response.ContentType := ContentTypeHeaderToUse;
ContentTypeHeaderToUse := ''; // Reset global variable
end;
end;
I used a similar solution for assigning Content-Disposition too. This is a useful header key in order to set file name to download and attachment/inline mode.
With this one the code is:
procedure TWebModule1.WebModuleAfterDispatch(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
if ContentDispositionHeaderToUse<>'' then begin
Response.SetCustomHeader('content-disposition',ContentDispositionHeaderToUse);
ContentDispositionHeaderToUse := '';
end;
if ContentTypeHeaderToUse<>'' then begin
Response.ContentType := ContentTypeHeaderToUse;
ContentTypeHeaderToUse := '';
end;
end;
Assign ContentDispositionHeaderToUse into the server methods implementation.
EDIT
This workaround doesn't work in ISAPI DLL on IIS with data compression enabled!
With no data compressione (local debuggin IIS) the response header is:
Connection close
Content-Disposition inline; filename="Privacy-0.rtf.pdf"
Content-Length 150205
Content-Type application/pdf; charset=ISO-8859-1
Pragma dssession=28177.371935.39223,dssessionexpires=1200000
but with production enabled IIS the response comes with:
Content-Encoding gzip
Content-Length 11663
Content-Type text/html
Date Thu, 11 Sep 2014 21:56:43 GMT
Pragma dssession=682384.52215.879906,dssessionexpires=1200000
Server Microsoft-IIS/7.5
Vary Accept-Encoding
X-Powered-By ASP.NET
Content-disposition and content-type assigned in DataSnap code are not surfaced.

Resources