參考資料:MSDN的《Pluggable Protocols Overview》
參考樣本:MSDN提供的
http://support.microsoft.com/default.aspx?scid=kb;EN-US;q260840#appliesto
另一樣本是用Delphi寫的:http://www.guicode.com/scr/mimefilter.zip
要實現HTML代碼過濾必需註冊一個或多個MIME過濾器(Pluggable MIME Filter)。
MIME過濾器是一個COM對象,必需實現IInternetProtocolSink和IInternetProtocol介面。MIME過濾器可以註冊成臨時或者永久的,如果同時註冊多個臨時的MIME過濾器,那麼後註冊的對象先被調用!
要如何註冊一個MIME過濾器?要註冊一個永久的MIME過濾器,你必需在註冊表
的HKEY_CLASSES_ROOT"PROTOCOLS"Filter鍵下添加一個子鍵,子鍵的名稱是你要
註冊的MIME類型
,在添加的子鍵下必需有一個名為CLSID的字串值,值的內容就是你
提供的COM對象的CLSID。該鍵的預設值可以是關於你的對象的簡單描述。如果你用ATL
了開發,你可以在對象的RGS檔案中添加如下內容:
HKCR
{
NoRemove PROTOCOLS
{
NoRemove Filter
{
ForceRemove 'text/html' = s 'XMLMimeFilter MIME Filter Sample'
{
val CLSID = s '{53B95211-7D77-11D2-9F80-00104B107C96}'
}
}
}
}
上面的代碼來自文章開頭提到的樣本。’XMLMimeFilter MIME Filter Sample’和
{53B95211-7D77-11D2-9F80-00104B107C96}'都要換成你自已的!
如果要註冊臨時的MIME過濾器,就要通過IInternetSession介面(取消註冊也
用該介面),下面提供的註冊一個臨時過濾器的代碼:
CComPtr<IInternetSession> m_spSession ;
CComPtr<IClassFactory> m_spClassFactoryMime ;
hr = ::CoGetClassObject( CLSID_MimeFilter, CLSCTX_SERVER,
NULL, IID_IClassFactory,
(void**)&m_spClassFactoryMime );
if( hr == S_OK )
{
if( ::CoInternetGetSession( 0, &m_spSession, 0) ==S_OK )
{
m_spSession->RegisterMimeFilter(m_spClassFactoryMime,
CLSID_MimeFilter, L"text/html" );
}
}
這裡的CLSID_MimeFilter就是你的對象的CLSID。
MIME的類型有許多種,要瞭解這些資訊可以查看MSDN的附錄
《MIME Type Detection in Internet Explorer 4.0》,但實際的類型要比這裡列的多得多。
要瞭解你的電腦註冊的MIME類型,可以查看註冊表的[HKEY_CLASSES_ROOT"MIME"
Database"Content Type]鍵。也可以通過調用FindMimeFromData函數來得到檔案的對應MIME
類型,下面的程式碼範例了得到JS檔案的MIME類型:
LPWSTR pwzMimeOut ;
FindMimeFromData( NULL , L"time.js" , 0 , 0 , 0 , 0 , &pwzMimeOut , 0 );
得到的MIME類型是:application/x-javascript。
一般情況下,要過濾HMTL頁面,可以註冊text/html類型。你也可以根據實際情況
通過調用RegisterMimeFilter註冊多個不同的MIME過濾器。
註冊一個臨時或永久的MIME過濾器後,接下來的工作就是實現MIME過濾器對象。
在實現之前,先看一下《Pluggable Protocols Overview》一文中關於MIME過濾器與WEB
處理器(transaction handler,即urlmon.dll)之間介面的調用的描述(註:urlmon.dll內部實現了IInternetProtocol和IInternetProtocolSink介面):
1、 WEB處理器調用MIME過濾器的IInternetProtocolRoot::Start方法(IInternetProtocol
從IInternetProtocolRoot派生);
2、 WEB處理器先後調用MIME過濾器的IInternetProtocolSink::ReportProgress 和
IInternetProtocolSink::ReportData方法;
3、 MIME過濾器調用WEB處理器的IInternetProtocol::Read方法;
4、 MIME過濾器調用WEB處理器的IInternetProtocolSink::ReportData方法;
5、 WEB處理器調用MIME過濾器的IInternetProtoco::Read方法;
因此,要實現MIME過濾器,有幾個重要的方法:
1、IInternetProtocolRoot::Start方法:
HRESULT Start(
[in] LPCWSTR szUrl,
[in] IInternetProtocolSink *pOIProtSink,
[in] IInternetBindInfo *pOIBindInfo,
[in] DWORD grfPI,
[in] DWORD dwReserved
);
作為MIME過濾對象,szUrl傳入的是MIME的類型(如果是name space handlers對象,
則該參數為一個即將下載或解析的URL)。若是你想得到URL,可以通過pOIBindInfo 接
口得到,下面是樣本:
LPOLESTR pwzUrl ;
ULONG uElFetched ;
pIBindInfo->GetBindString( BINDSTRING_URL , &pwzUrl , 1 , &uElFetched ) ;
pOIProtSink是由urlmon.dll提供的IInternetProtocolSink介面,因為在後面的處理過程中,需要調用到該介面,所以要將它儲存;
grfPI是一個枚舉變數,必需包含PI_FILTER_MODE標誌,表示該對象運行在filter模式中。
dwReserved是一個指向PROTOCOLFILTERDATA結構的指標,該結構的pProtocol成員是由urlmon.dll提供的IInternetProtocol介面,因為在後面的處理過程中需要調用到該介面,所以要將它儲存。實際上該介面也可以通過pOIProtSink參數調用QueryInterface得到,同樣PROTOCOLFILTERDATA結構的pProtocolSink與pOIProtSink都是指向同一個介面。
在Start方法中,我們必需做的實際上只是儲存urlmon.dll提供的IInternetProtocolSink
和IInternetProtocol介面。
2、IInternetProtocolSink::ReportProgress方法:
HRESULT ReportProgress(
[in] ULONG ulStatusCode,
[in] LPCWSTR szStatusText )
作為MIME過濾器,ulStatusCode一般都是BINDSTATUS_CACHEFILENAMEAVAILABLE , 當
ulStatusCode為BINDSTATUS_CACHEFILENAMEAVAILABLE時,szStatusText為臨時快取檔案
的路徑名稱,但有一些網頁並不寫到緩衝裡,所以szStatusText可能為空白字串。
3、IInternetProtocolSink::ReportData方法:
HRESULT ReportData(
[in] DWORD grfBSCF,
[in] ULONG ulProgress,
[in] ULONG ulProgressMax
);
IE下載檔案過程中或下載完畢時會調用MIME過濾器的ReportData方法,ulProgressMax
為檔案總是資料量,ulProgress為下載進度,理論上當檔案全部下載完後,ulProgress應
等於ulProgressMax(實際上,當網頁檔案不是很大時,即使ulProgress不等於ulProgressMax時,檔案也可能全部下載下來),還有一個反應檔案下載情況的參數是grfBSCF。有時,ReportData方法會被Web處理器調用多次。
ReportData是過濾網頁內容或修改網頁內容比較合適的地方。在此地,可以將網頁內容通過調用Read儲存到自已的緩衝或流中並做適當的處理(注意檢查字元的編碼)。
最後,別忘了調用Web處理器的IInternetProtocolSink::ReportData方法,向它彙報資料下載的情況。Web處理器得到此通知後,就會調用MIME過濾器的IInternetProtocol::Read,此時,你就可以將修改後的資料交給WEB處理器。
下面的程式碼範例了如何在ReportData中調用Web處理器的Read預先儲存資料:
// m_spIncomingProt是在Start中儲存的Web處理器的IInternetProtocol介面
// m_spStm 是IStream 指標,用來快取資料
BYTE buffer[ SIZE_BUFFER ] ;
DWORD cbRead ;
do
{
cbRead = 0 ;
hr = m_spIncomingProt->Read( buffer , SIZE_BUFFER , &cbRead ) ;
if( cbRead > 0 ) **A 處
{
if( m_spStm->Write( buffer , cbRead , NULL ) == S_OK )
{
m_cbTotal += cbRead ;
}
}
}while ( hr == S_OK );
Read成功取得資料一般只返回S_OK或S_FALSE ,返回S_OK表示還有資料,而S_FALSE
表示資料已讀取完畢,因此迴圈的條件設為 hr==S_OK。那A處的條件判斷為什麼不是
if( hr == S_OK || hr == S_FALSE ) 呢, 因為我發現某些情況下,Read可能返回其
它值,但仍然有成功讀取一部分資料出來,資料的大小就是cbRead指定的值。如果將
那部分資料遺落,網頁將無法正常解析!那這樣會不會導致因讀取失敗而將一些無用的
資料儲存到緩衝中?至少目前還沒碰到過。
4、IInternetProtocol::Read方法
該方法由WEB處理器調用來取得瀏覽器要解析的資料。在上一方法ReportData中
我們已經將所有資料緩衝到流中,因此,這裡只需將流中的資料返回給WEB處理器。
下面的程式碼範例了Read中的簡單處理:
if( m_spStm->Read( pv , cb , pcbRead ) == S_OK )
{
if( *pcbRead == cb )
{
return S_OK ;
}
else
return S_FALSE ;
}
千萬注意,在資料已讀取完畢時要返回S_FALSE , 不然可能導致Read被無窮迴圈調用。
處理完這幾個方法後,基本是大功造成,其它一些方法處理十分簡單,可以參考上面
提到的例子。