Detect Chrome as browser associated with html files in Windows - delphi

We provide Flash tutorial videos that install on the local (Windows) hard disk with our application. Our app uses ShellExecute to open the html file (in whatever browser is associated with html files) in which they are embedded.
Apparently there's a bug in Chrome's more recent Flash players that fails to play local files (but files over the web are fine.)
(Frankly, I'm astonished that this bug hasn't been fixed by Google. Seems like a big one to me... but maybe not many people play Flash from locations other than the web?)
There's a work-around on the about:plugins screen in Chrome, but we can't ask our users to do that. Here's a discussion of the work-around: http://techsmith.custhelp.com/app/answers/detail/a_id/3518
I want to provide my users with an option to open our html files IE. If Chrome is their default browser, then I'd show a checkbox that says something embarrassing like "If our tutorial videos fail to play, check this box to try them in IE."
Is this XE2 code (from two years ago on SO: link) still reasonable?
if pos('CHROME', UpperCase(GetAssociation('C:\Path\File.html')) > 0 then
// Chrome is the default browser
function GetAssociation(const DocFileName: string): string;
var
FileClass: string;
Reg: TRegistry;
begin
Result := '';
Reg := TRegistry.Create(KEY_EXECUTE);
Reg.RootKey := HKEY_CLASSES_ROOT;
FileClass := '';
if Reg.OpenKeyReadOnly(ExtractFileExt(DocFileName)) then
begin
FileClass := Reg.ReadString('');
Reg.CloseKey;
end;
if FileClass <> '' then begin
if Reg.OpenKeyReadOnly(FileClass + '\Shell\Open\Command') then
begin
Result := Reg.ReadString('');
Reg.CloseKey;
end;
end;
Reg.Free;
end;

If you have an actual full path to an existing file on disk, you can use FindExecutable instead. It's easier, and doesn't require access to the registry, but it does require that an actual file exists.
Here's a console app for XE2 that demonstrates use:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, ShellAPI, Windows;
var
Buffer: array[0..MAX_PATH] of Char;
Res: Integer;
begin
FillChar(Buffer, SizeOf(Buffer), #0);
Res := FindExecutable(PChar('C:\Path\File.html'), nil, Buffer);
if Res > 32 then
Writeln('Executable is ' + Buffer)
else
WriteLn(SysErrorMessage(Res));
Readln;
end.
The method you show will work, but FindExecutable is easier (less code) and works on XP and above.

Related

Different file hash of shortcut file when shortcut created from 32-bit or 64-bit program

I create a ShellLink Shortcut from a 64-bit program:
program ShellLinkShortcutHashTest;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Winapi.Windows,
JclShell,
Winapi.ActiveX,
IdHashMessageDigest,
System.Classes, System.SysUtils;
const
ShortcutFile = 'R:\myshortcut.lnk';
ShortcutTarget = 'C:\Windows\System32\notepad.exe';
function GetHashFromFile(const AFileToHash: string): string;
var
IdMD5: TIdHashMessageDigest5;
FS: TFileStream;
begin
IdMD5 := TIdHashMessageDigest5.Create;
FS := TFileStream.Create(AFileToHash, fmOpenRead or fmShareDenyWrite);
try
Result := IdMD5.HashStreamAsHex(FS);
finally
FS.Free;
IdMD5.Free;
end;
end;
function SaveShortcutShellLink(const AFile: string): string;
var
SL: JclShell.TShellLink;
HR: Integer;
begin
Result := 'error';
SL.Target := ShortcutTarget;
SL.Description := 'My description';
HR := JclShell.ShellLinkCreate(SL, AFile);
if HR = Winapi.Windows.S_OK then
Result := 'OK - this is the shortcut file hash: ' + GetHashFromFile(AFile)
else
Result := 'Error: ' + IntToStr(HR);
end;
begin
try
Winapi.ActiveX.OleInitialize(nil);
try
Writeln(SaveShortcutShellLink(ShortcutFile));
finally
Winapi.ActiveX.OleUninitialize;
end;
Readln;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Readln;
end;
end;
end.
The MD5 file hash from the shortcut file is: 4113F96CD9D6D94EB1B93D03B9604FFA.
I then build a 32-bit version of the SAME program. But the hash of the shortcut file created with the 32 bit program is different: 6512AB03F39307D9F7E3FC129140117A.
I have tested the MD5 hash of the shortcut file also with other external tools not related to Delphi. They also confirm the 64/32-bit difference.
Does this mean that shortcuts are binary-different if they have been created from a 64-bit program or from a 32-bit program? What is the difference? Could this be a security problem?
You're falling victim to the WOW64 filesystem redirector.
When your 64-bit application attempts to access :
C:\Windows\System32\notepad.exe
everything is normal you get a shortcut to the 64-bit notepad application in System32. When you attempt to access the same path from a 32-bit application, however, the redirector silently substitutes the WOW64 path in its place, to :
C:\Windows\SysWOW64\notepad.exe
and your application instead creates a shortcut to the 32-bit notepad application in SysWOW64. So these hash differently because they are shortcuts to two different programs.
The filesystem redirector is well documented and understood. While that doesn't preclude it having some security vulnerabilities, the redirector itself, and its documented behaviours, should not generally be considered a security risk.

Fetch the content of a web page with DELPHI

I am trying to retrieve the <table><tbody> section of this page:
http://www.mfinante.ro/infocodfiscal.html?captcha=null&cod=18505138
I am using Delphi XE7.
I tried using IXMLHttpRequest, WinInet (InternetOpenURL(), InternetReadFile()), TRestClient/TRestRequest/TRestResponse, TIdHTTP.Get(), but all they retrieve is some gibberish, like this:
<html><head><meta http-equiv="Pragma" content="no-cache"/>'#$D#$A'<meta http-equiv="Expires" content="-1"/>'#$D#$A'<meta http-equiv="CacheControl" content="no-cache"/>'#$D#$A'<script>'#$D#$A'(function(){p={g:"0119a4477bb90c7a81666ed6496cf13b5aad18374e35ca73f205151217be1217a93610c5877ece5575231e088ff52583c46a8e8807483e7185307ed65e",v:"87696d3d40d846a7c63fa2d10957202e",u:"1",e:"1",d:"1",a:"challenge etc.
Look at this code for example:
program htttpget;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, HTTPApp, IdHTTP, ActiveX;
var
CoResult: Integer;
HTTP: TIdHTTP;
Query: String;
Buffer: String;
begin
try
CoResult := CoInitializeEx(nil, COINIT_MULTITHREADED);
if not((CoResult = S_OK) or (CoResult = S_FALSE)) then
begin
Writeln('Failed to initialize COM library.');
Exit;
end;
HTTP := TIdHTTP.Create;
Query := 'http://www.mfinante.ro/infocodfiscal.html?captcha=null' +
'&cod=18505138';
Buffer := HTTP.Get(Query);
writeln(Buffer);
HTTP.Destroy;
except
end;
end.
What is wrong with this page? I haven not done very many "get" functions in my life, but other websites return normal responses. Can someone at least clarify to me why this isn't working?
Are there other ways to get the content of this web page? Are there other programming languages (Java, scripting, etc) that can do this without third party software (like using Firefox source code to emulate a browser, fetch the page, without showing the window, and then copy the content).
You can use TWebBrowser for this.
See this post: How can I get HTML source code from TWebBrowser
The answer by RRUZ, which you can find in many places on the internet, is not what you are looking for. This gives you are original html source, as would IdHttp.Get().
However, the answer by Mehmet Fide will give you the HTML source of the DOM, which is what you are looking for.
I offer a variation here. (It includes some hacks that were required at the time to get full DOCTYPE. Not sure if they are still needed...)
function EndStr(const S: String; const Count: Integer): String;
var
I: Integer;
Index: Integer;
begin
Result := '';
for I := 1 to Count do
begin
Index := Length(S)-I+1;
if Index > 0 then
Result := S[Index] + Result;
end;
end;
function GetHTMLDocumentSource(WebBrowser: TWebBrowser; var Charset: String):
String;
var
Element: IHTMLElement;
Node: IHTMLDomNode;
Document: IHTMLDocument2;
I: Integer;
S: String;
begin
Result := '';
Document := WebBrowser.Document as IHTMLDocument2;
For I := 0 to Document.all.length -1 do
begin
Element := Document.all.item(I, 0) as IHTMLElement;
If Element.tagName = '!' Then
begin
Node := Element as IHTMLDomNode;
If (Node <> nil) and (Pos('CTYPE', UpperCase(Node.nodeValue)) > 0) Then
begin
S := VarToStr(Node.nodeValue); { don't change case of result }
if Copy(Uppercase(S), 1, 5) = 'CTYPE' then
S := 'DO' + S;
if Copy(Uppercase(S), 1, 7) = 'DOCTYPE' then
S := '<!' + S;
if Uppercase(S) = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 TRANSITIONAL//E' then
S := S +'N">';
if EndStr(Lowercase(S), 3) = '.dt' then
S := S + 'd"';
if EndStr(Lowercase(S), 5) = '.dtd"' then
S := S + '>';
Result := Result + S;
end;
end
Else
Result := Result + Element.outerHTML;
If Element.tagName = 'HTML' Then
Break;
end;
Charset := Document.charset;
end;
So call WebBrowser.Navigate(URL), then in OnDocumentComplete event retrieve the Html Source.
However, with your URL you will see the OnDocumentComplete event fires twice :(, so you need to get the Html from the last fire.
You can refer to this post How do I avoid the OnDocumentComplete event for embedded iframe elements? for info on how to get the final OnDocumentComplete event. However, I tried it and it was not working for me. You may need to use some other strategy to get the last event.
Not sure of your needs, but you may also optimize this process by disabling WebBrowser from downloading images. I believe that is possible.
This is normal, you have indeed retrieved the content correctly. What happens in your browser is that the script is executed and the page gets built client side. If you wish to replicate that in your code, then you will need to do the same. Execute the script exactly as the browser would.
What you are really looking for here is what is known as a headless browser. Integrate one of those into your program. Then get the headless browser to process the request, including executing scripts. When it has done executing scripts, read the modified content of the page.

what causes this error 'Unable to write to application file.ini'

My application is build in delphi and it runs perfect on other platforms except Windows 7 64bit machine. Each and everytime try to close the application is giving me this error
'Unable to write to application file.ini'
here is my code for closing
procedure TfrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
frmMain.close;
end;
This error is usually caused by trying to write to your app's own folder under Program Files, which is not allowed for a non-Administrator under Vista and higher (and XP, if you're not running as an Administrator or Power User).
Here's some code for getting the proper folder for your .INI file:
uses
Windows,
ShlObj; // For SHGetSpecialFolderPath
function GetFolderLocation(Handle: HWnd; Folder: Integer): string;
begin
Result := '';
SetLength(Result, MAX_PATH);
if not SHGetSpecialFolderPath(Handle, PChar(Result), Folder, False) then
RaiseLastOSError;
end;
I use these in my application to retrieve the non-roaming profile folder, and use a sub-folder created beneath that for my app's data. It's set up during the creation of a TDataModule:
procedure TAppData.Create(Sender.TObject);
begin
// DataPath is a property of the datamodule, declared as a string
// CSIDL_LOCAL_APPDATA is the local non-roaming profile folder.
// CSIDL_APPDATA is for the local roaming profile folder, and is more typically used
DataPath := GetFolderLocation(Application.Handle, CSIDL_LOCAL_APPDATA);
DataPath := IncludeTrailingPathDelimiter(DataPath) + 'MyApp\';
end;
See MSDN's documentation page on the meaning of the various CSIDL_ or FOLDERID_ values. The FOLDERID_ values are similar, but are available only on Vista and above and used with SHGetKnownFolderIDList.
For those of you not willing to disregard MS's warnings about SHGetSpecialFolderPath not being supported, here's an alternate version of GetFolderLocation using SHGetFolderPath, which is preferred:
uses
ShlObj, SHFolder, ActiveX, Windows;
function GetFolderLocation(Handle: HWnd; Folder: Integer): string;
begin
Result := '';
SetLength(Result, MAX_PATH);
if not Succeeded(SHGetFolderPath(Handle, Folder, 0, 0, PChar(Result))) then
RaiseLastOSError();
end;
And finally, for those working with only Vista and higher, here's an example using SHGetKnownFolderPath - note this isn't available in pre-XE versions of Delphi (AFAIK-may be in 2009 or 2010), and you'll need to use KNOWNFOLDERID values instead of CSIDL_, like FOLDERID_LocalAppData:
uses
ShlObj, ActiveX, KnownFolders;
// Tested on XE2, VCL forms application, Win32 target, on Win7 64-bit Pro
function GetFolderLocation(const Folder: TGuid): string;
var
Buf: PWideChar;
begin
Result := '';
if Succeeded(SHGetKnownFolderPath(Folder, 0, 0, Buf)) then
begin
Result := Buf;
CoTaskMemFree(Buf);
end
else
RaiseLastOSError();
end;
You should not write ini files to the program directory. Although it worked in the past, it has never been a good practice.
You should be using %APPDATA% for user specific application data.
You might want to read Best practices storing application data

Getting Page Number of PDF document from Adobe Reader's ActiveX control

I'm successfully using Delph 7 and the ActiveX control of Adobe Reader version 7 to extract the page number from an open PDF document housed in the ActiveX component (TAcroPDF). I am interested in upgrading to the latest Adobe reader but something changed in Adobe Reader 8 (and 9) that prevented me from upgrading (I have not tested Adobe 10/X). With Adobe 7, I use the Windows SDK function EnumChildWindows to gather the child windows of my form containing the TAcroPDF component and find a control with the name AVPageNumView, then FindWindowEx to get its handle. Then I call SendMessage to get the text of that control which has the page number information. With Adobe 8 and 9, window/control AVPageNumView is no longer there it seems. Thus I am stuck in Adobe 7 and still looking for a way to get the page number, preferably Adobe 9 or 10/X. The goal would be to not have to do a complete rewrite with another technology, but I am open to that if its the only solution.
Thanks,
Michael
You're using a wndclass name (AVPageNumView). Obviously, the class name has changed in the new version. You can use something like WinDowse to investigate the windows in the newer version of Reader to find out the new class names. Update your code to first check for the old wndclass; if it's not found, try and find the new one.
function EnumWindowProc(pHwnd: THandle; Edit: Integer): LongBool; stdcall;
function GetWindowTxt(gwtHwnd: THandle): string;
var dWTextBuf: PChar;
TextLen: Integer;
begin
TextLen := SendMessage(gwtHwnd, WM_GetTextLength, 0, 0);;
dWTextBuf := StrAlloc(TextLen + 1);
SendMessage(gwtHwnd, WM_GetText, TextLen + 1, Integer(dWTextBuf));
Result := dWTextBuf;
StrDispose(dWTextBuf);
end;
function GetClassNameTxt(gcnHwnd: THandle): string;
var dWClassBuf: PChar;
begin
dWClassBuf := StrAlloc(1024);
GetClassName(gcnHwnd, dWClassBuf, 1024);
Result := dWClassBuf;
StrDispose(dWClassBuf);
end;
begin
Result := LongBool(True);
if (GetClassNameTxt(pHwnd) = 'AVL_AVView') and (GetWindowTxt(pHwnd) = 'AVPageView') then
begin
TEdit(Edit).Text := GetWindowTxt(FindWindowEx(pHwnd, 0, 'RICHEDIT50W', nil));
Result := LongBool(False);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
EnumChildWindows(AcroPDF1.Handle, #EnumWindowProc, LongInt(Edit1));
end;

How to get Firefox bookmarks from a Delphi application?

I know how to get the favourites from IE, but how can I access Firefox's bookmarks?
Here's the code I have for retrieving the IE favourites:
uses
ShlObj, ActiveX;
function GetIEFavourites(const favpath: string): TStrings;
var
searchrec: TSearchRec;
str: TStrings;
path, dir, FileName: string;
Buffer: array[0..2047] of Char;
found: Integer;
begin
str := TStringList.Create;
// Get all file names in the favourites path
path := FavPath + '\*.url';
dir := ExtractFilepath(path);
found := FindFirst(path, faAnyFile, searchrec);
while found = 0 do
begin
// Get now URLs from files in variable files
Setstring(FileName, Buffer, GetPrivateProfilestring('InternetShortcut',
PChar('URL'), nil, Buffer, SizeOf(Buffer), PChar(dir + searchrec.Name)));
str.Add(FileName);
found := FindNext(searchrec);
end;
// find Subfolders
found := FindFirst(dir + '\*.*', faAnyFile, searchrec);
while found = 0 do
begin
if ((searchrec.Attr and faDirectory) > 0) and (searchrec.Name[1] <> '.') then
str.Addstrings(GetIEFavourites(dir + '\' + searchrec.Name));
found := FindNext(searchrec);
end;
FindClose(searchrec);
Result := str;
end;
procedure FreePidl(pidl: PItemIDList);
var
allocator: IMalloc;
begin
if Succeeded(SHGetMalloc(allocator)) then
begin
allocator.Free(pidl);
{$IFDEF VER100}
allocator.Release;
{$ENDIF}
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
pidl: PItemIDList;
FavPath: array[0..MAX_PATH] of Char;
begin
if Succeeded(ShGetSpecialFolderLocation(Handle, CSIDL_FAVORITES, pidl)) then
begin
if ShGetPathfromIDList(pidl, FavPath) then
ListBox1.Items := GetIEFavourites(StrPas(FavPath));
// The calling application is responsible for freeing the PItemIDList-pointer
// with the Shell's IMalloc interface
FreePIDL(pidl);
end;
end;
Thanks.
The favorites are saved in 'places.sqlite' in the profile folder. They are in table moz_bookmarks. They refer to entries in table moz_places with their field fk. Get sqlite as dll and a delphi binding like this one.
Open the database with SQLite3_Open and use SQLite3_Exec to send ordinary sql statements to access the data, like
SELECT * FROM moz_bookmarks;
Unfortunately firefox locks places.sqlite, which means you have to copy it first (normal file copy). After you have worked on the copy you can delete it.
TBookmarks component from MetaProduct... it's $75.00 though:
http://www.metaproducts.com/mp/TBookmarks_component.htm
quote from their site:
For Borland Delphi 2, 3, 4, 5, 6, 7, 2005, 2006, 2007, 2009.
New! FireFox 3, Safari and Google Chrome Bookmarks are supported!
MetaProducts TBookmarks is a Delphi 2 - 7, 2005-2009 component that helps you to display MS Internet Explorer Favorites (4.0 - 8.0,) MSN Explorer, Opera Hotlists (3.0 - 9.0) and Netscape, Safari, Chrome, FireFox and Mozilla Bookmarks (2.0 - 8.0) in a menu.
Simply drop the TBookmarks component on the form and assign its Menu property and OnURL event. Set Enabled to True to collect all bookmark information in the specified TMenuItem.
You can also use TTreeView component to make TBookmarks populate the entries there automatically.
Well, Firefox bookmarks are a HTML file stored in
<WindowsUserPath>\Application Data\Mozilla\Firefox\Profiles\
<aRamdonProfileName>\bookmark.htm as Sinan Ünür said.
So you need to get the mozilla profiles dir and retrie the folder
name in there.
After that you need to parse the html file.....
So AFAIK, no, there's no API to directly get the FF bookmarks.

Resources