How to get all cookie details with TEmbeddedWB? - delphi

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:

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().

Delphi: SetFileDate creates wrong LastWriteTime (Summer/Wintertime)

i am downloading a file from my server (i only get the bytes and a DateTime for the lastwritetime attribute) and after downloading the data i create a new file on my local machine and want to set the lastwritetime attribute.
For this i am using the following method:
procedure SetFileDate(const FileName: string; NewDate: TDateTime);
var
FileDate, FileHandle: Integer;
begin
try
FileDate := DateTimeToFileDate(NewDate);
FileHandle := FileOpen(FileName, fmOpenReadWrite or fmShareDenyWrite);
if FileHandle > 0 then
begin
FileSetDate(FileHandle, FileDate);
FileClose(FileHandle);
end;
except
begin
// ERROR Log
err.Msg('FileReqThrd.SetFileDate');
end;
end;
end;
For the 'NewDate' parameter i use the DateTime which i get from my server.
I tried to convert the DateTime from the server like this to get the valid lastwritetime (i am requesting the data from a WCF this is why i am converting it to UTCDateTime, the untouched data from the WCF service is TXSDateTime):
TDateTime cloudFileDateTime := StrToDateTime(DateTimeToStr(cloudDownloadResult.FileCloudData.Lastwritetime.AsUTCDateTime));
But in the end my lastwritetime attribute from files which have a lastwritetime in the wintertime period are wrong with -1h.
I hope you understand my problem and can give me an idea how to solve it.
Best regards
The easiest way to do this is to call TFile.SetLastWriteTimeUtc from the System.IOUtils unit.
TFile.SetLastWriteTimeUtc(FileName,
DateTimeUtc);
If this function is not available use the Win32 API function SetFileTime.
You'll also need DateTimeToSystemTime and then SystemTimeToFileTime in that scenario.
The answer provided by David (to use TFile.SetLastWriteTimeUtc) is correct. However, there was some discussion in the comments about bugs. As I am unable to comment (due to lack of rep), I'll add this here for anyone who comes across this problem in future.
While TFile.SetLastWriteTimeUtc works correctly, TFile.GetLastWriteTimeUtc does indeed have a bug relating to daylight saving time. There is a bug report filed with Embarcadero, and it looks like they've now fixed it in Delphi 10.3 Rio (though I haven't tried it yet).
If you are working with an older version of Delphi, you will have to work around the problem via use of the Windows API. e.g. GetFileAttributesEx:
function GetFileModTimeUtc(filePath: string): TDateTime;
var data: TWin32FindData;
var sysTime: TSystemTime;
begin
if GetFileAttributesEx(PChar(filePath), GetFileExInfoStandard, #data) and
FileTimeToSystemTime(data.ftLastWriteTime, sysTime) then begin
Result := SystemTimeToDateTime(sysTime);
end else begin
raise Exception.Create('Unable to get last file write time for ' + filePath);
end;
end;

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

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;

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;

Universal approach to send virtual key codes with Delphi

I am trying to write international program and need to send some text to "other text edit programs" like word or notepad or a browser. On the other hand I am not sure that I can find an international way(because of the different keyboard layouts)
it would be nice to use a code like below
SendMessage(FindActiveWindowsHWND,WM_SETTEXT,0,Integer(PChar('My String')));
and I dont have function like FindActiveWindowsHWND
Edit: The code I am tried but not satisfied so far;
procedure FindActiveWindowsHWND();
var
ThreadInfo: TGUIThreadInfo;
activewindowsHwnd: HWND;
begin
GetGUIThreadInfo(0,ThreadInfo);
activewindowsHwnd:= ThreadInfo.hwndActive; (or ThreadInfo.hwndFocus);
end;
also I used Sendinput function like this
procedure SendKey(vKey: SmallInt; booDown: boolean);
var
GInput: array[0..0] of tagINPUT; //GENERALINPUT;
// doesn't have to be array :)
begin
GInput[0].Itype := INPUT_KEYBOARD;
GInput[0].ki.wVk := vKey;
GInput[0].ki.wScan := 0;
GInput[0].ki.time := 0;
GInput[0].ki.dwExtraInfo := 0;
if not booDown then
GInput[0].ki.dwFlags := KEYEVENTF_KEYUP
else
GInput[0].ki.dwFlags := 0;
SendInput(1, GInput[0], SizeOf(GInput));
end;
then
SendKey(65,true); //to send an "A" for example
but instead it sent an "a" and when I try to send an "a" using SendKey(97,true) it sent "1".
it is really interesting that I have to send shift key down to write uppercase letters
You can use GetGUIThreadInfo() to get the HWND of the currently focused window in another process. Not all window types accept WM_SETTEXT, though. You could use SendInput() to put Unicode characters into the keyboard queue, though. Or use the Automation API, like David said, though not all window types implement that.

Resources