IE 7有好几个新的API可以被扩展插件用来完成保护模式下限制的功能。这些API都在ieframe.dll中。你可以通过使用iepmapi.lib这个库来直接使用这些API,也可以在运行的时候使用LoadLibrary()/GetProcAddress()来得到这些函数的指针。如果你想让你的插件加载到Vista以前的Windows版本上,第二种方法是你必须使用的选择。
执行提升权限操作的许多功能使用到了一个代理进程(broker process),ieuser.exe。由于IE进程运行在低完整性等级下,它不能靠自己来完成更高权限的任务;ieuser.exe担任了这个角色。你会经常在本文中和微软的文档中看到提到的这个代理进程。
1.运行时检测保护模式
为了判断我们的插件是否运行在一个保护模式IE进程中,我们使用IEISProtectedModeProcess():
HRESULT IEIsProtectedModeProcess(BOOL* pbResult);
如果返回值是一个成功的HRESULR和*pbResult是True,那么保护模式就被启用了。根据在*pbResult中返回的值,你可以在你的代码中采取不同的操作:
HRESULT hr;
BOOL bProtectedMode = FALSE;
hr = IEIsProtectedModeProcess ( &bProtectedMode );
if ( SUCCEEDED(hr) && bProtectedMode )
// IE is running in protected mode
else
// IE isn't running in protected mode
示例band插件在一启动的时候调用了这个API,并显示了关于保护模式状态的一个消息。
2.写文件系统
当保护模式被启用的时候,扩展插件只能对用户配置下的几个目录进行写操作。只有在Temp、Temporary、Internet Files、Cookies和Favorites目录下的几个特定低完整性目录可以进行写操作。IE还有某些兼容性补偿功能,它虚拟化其他常用目录。对这些目录的写操作将被重定向到Temporary Internet Files的子目录中。如果一个扩展插件试图对一个敏感进行写操作的话,例如对windows目录,这个操作将会失败。
当一个扩展插件希望写这个文件系统的时候,它应该使用IEGetWriteableFolderPath()这个API,而不是GetSpecialFolderPath()、GetFolderPath()或SHGetKnownFolderPath()。IEGetWriteableFolderPath()可以发现保护模式,如果一个插件请求一个不允许写的目录,IEGetWriteableFolderPath()将返回E_ACCESSDENIED。IEGetWriteableFolderPath()的原型如下:
HRESULT IEGetWriteableFolderPath(GUID clsidFolderID, LPWSTR* lppwstrPath); 其中GUID是其中之一,在knownfolders.h: FOLDERID_InternetCache, FOLDERID_Cookies, FOLDERID_History中定义。对于Temp目录来说似乎没有一个GUID,因此当你需要写临时文件的时候,我推荐使用FOLDERID_InternetCache。
以下是在缓存中创建一个临时文件的代码片段:HRESULT hr;如果IEGetWriteableFolderPath() 成功的话,它将分配一个缓冲器,并在pwszCacheDir中返回它的地址。我们把那个目录传输给GetTempFileName(),然后使用CoTaskMemFree()释放这个缓冲器。
LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
hr = IEGetWriteableFolderPath(FOLDERID_InternetCache, &pwszCacheDir);
if ( SUCCEEDED(hr) )
{
GetTempFileName(CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile);
CoTaskMemFree(pwszCacheDir);
// szTempFile now has the full path to the temp file.
}
IEGetWriteableFolderPath() 不仅仅可以用来写临时文件。当一个插件使用保护模式版的文件保存对话框的时候,也会用到它,这一点我们将在下面的《提示用户保存文件》部分解释。在这个演示项目中,当你点击了Save Log按钮后,使用这个API。
3.写注册表
因为注册表是系统的关键部分,所以有一点很重要,运行在浏览器中的代码不允许来修改注册表的任何可能导致运行恶意软件的部分。为了实现这个目的,只有一个键值是扩展插件可以写的。对于文件系统来说,这个键值在当前用户的文件下的一个专门的低权限区域中。要想得到这个键值的句柄,可以调用IEGetWriteableHKCU():
HRESULT IEGetWriteableHKCU(HKEY* phKey);如果它成功的话,你可以在其他的注册表API中使用返回的HKEY来写任何需要的数据。本演示项目没有使用这个注册表,但是这割API是非常简单的,在使用它的时候应该不会什么麻烦。
4.提示用户保存文件
当IE运行在保护模式的时候,还有一个方式让插件来间接的写低权限区域之外的文件系统。插件可以通过调用IEShowSaveFileDialog()来显示一个常见的保存文件对话框。如果用户输入一个文件名,这个插件然后能够通过调用IESaveFile()来让IE写这个文件。注意这个操作总会让用户看到这个保存文件对话框;这样可以确保用户知道一个文件将被写到计算机上。
保存一个文件的步骤如下:
(1)调用IEShowSaveFileDialog() 来显示保存文件对话框。
(2)调用IEGetWriteableFolderPath() 来得到IE缓存目录。
(3)写数据到缓冲目录中的一个临时文件中。
(4)调用IESaveFile()来拷贝数据到用户选择的文件名中。
(5)清空temp文件。
IEShowSaveFileDialog()是对普通的文件保存对话框的封装:
HRESULT IEShowSaveFileDialog(hwnd是一个插件拥有的窗口,IE将使用它作为对话框的父窗口。lppwstrDestinationFilePath是一个指向代表用户选择的文件路径的LPWSTR的指针。它仅仅是一个数据信息,因为这个扩展插件不能直接写到这个路径。
HWND hwnd,
LPCWSTR lpwstrInitialFileName,
LPCWSTR lpwstrInitialDir,
LPCWSTR lpwstrFilter,
LPCWSTR lpwstrDefExt,
DWORD dwFilterIndex,
DWORD dwFlags,
LPWSTR* lppwstrDestinationFilePath,
HANDLE* phState
);
如果用户选择了一个文件名,IEShowSaveFileDialog()返回S_OK,如果他取消了对话框则返回S_FALSE,或者如果这个API失败的话返回一个失败的HRESULT。
以下是在保存日志到文件的演示项目中的代码。我们首先调用IEShowSaveFileDialog()来提示用户选择文件路径:
void CBandDialog::OnSaveLog(UINT uCode, int nID, HWND hwndCtrl)
{
HRESULT hr;
HANDLE hState;
LPWSTR pwszSelectedFilename = NULL;
const DWORD dwSaveFlags =
OFN_ENABLESIZING | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST |
OFN_OVERWRITEPROMPT;
// Get a filename from the user.
hr = IEShowSaveFileDialog (
m_hWnd, L"Saved log.txt", NULL,
L"Text files|*.txt|All files|*.*|",
L"txt", 1, dwSaveFlags, &pwszSelectedFilename,
&hState );
if ( S_OK != hr )
return;
接下来,我们使用IEGetWriteableFolderPath()来获得缓冲区目录的位置。
LPWSTR pwszCacheDir = NULL;
TCHAR szTempFile[MAX_PATH] = {0};
// Get the path to the IE cache dir, which is a dir that we're allowed
// to write to in protected mode.
hr = IEGetWriteableFolderPath ( FOLDERID_InternetCache, &pwszCacheDir );
if ( SUCCEEDED(hr) )
{
// Get a temp file name in that dir.
GetTempFileName ( CW2CT(pwszCacheDir), _T("bob"), 0, szTempFile );
CoTaskMemFree ( pwszCacheDir );
// Write our data to that temp file.
hr = WriteLogFile ( szTempFile );
}
如果一起都顺利的话,我们调用另一个保护模式API,IESaveFile()。IESaveFile()获得IEShowSaveFileDialog()返回的状态句柄,以及我们的临时文件的路径。注意这个HANDLE不是一个标准的句柄,不需要被关闭;在IESaveFile()调用完后,这个HANDLE会被自动释放。
由于某些原因,我们没有结束调用IESaveFile(),举个例子来说,如果当写临时文件的时候出现一个错误,我们需要清除这个HANDLE和任何IEShowSaveFileDialog()分配的任何内部数据。我们通过调用IECancelSaveFile()来实现:
if ( SUCCEEDED(hr) )
{
// If we wrote the file successfully, have IE save that data to
// the path that the user chose.
hr = IESaveFile ( hState, T2CW(szTempFile) );
// Clean up our temp file.
DeleteFile ( szTempFile );
}
else
{
// We couldn't complete the save operation, so cancel it.
IECancelSaveFile ( hState );
}