凤之焚的博客

静者,无澜也.净者,无贪也.无贪无澜者,海纳百川也!
posts - 2, comments - 5, trackbacks - 0, articles - 0
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

网页源码过滤

Posted on 2006-08-29 16:43 凤之焚 阅读(3890) 评论(2)  编辑 收藏 引用 所属分类: IE相关

本例通过Mime filter技术对网页源码进行过滤,本文部分摘自《HTML代码过滤技术》

       要实现HTML代码过滤必需注册一个或多个MIME过滤器(Pluggable MIME Filter)。MIME过滤器是一个COM对象,必需实现IInternetProtocolSink和IInternetProtocol接口。
       在实现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预先保存数据:
                     CString Ts("");
       char p[1024];
       HRESULT hr;
       ULONG Readtotal;
       do
       {
              memset(p,0,sizeof(p));
              hr = UrlMonProtocol->Read(p, sizeof(p)-1, &Readtotal);
              CString pTemp(p);
              Ts=Ts+pTemp;
}while((hr != S_FALSE) && (hr != INET_E_DOWNLOAD_FAILURE) && (hr != INET_E_DATA_NOT_AVAILABLE));
 
Read成功取得数据一般只返回S_OK或S_FALSE ,返回S_OK表示还有数据,而S_FALSE
表示数据已读取完毕,因此循环的条件设为 hr==S_OK。那A处的条件判断为什么不是
if( hr == S_OK || hr == S_FALSE ) 呢, 因为我发现某些情况下,Read可能返回其
它值,但仍然有成功读取一部分数据出来,数据的大小就是Readtotal指定的值。如果将
那部分数据遗落,网页将无法正常解析。
       下列代码建立临时文件:
if (CacheFileName == "")
       {
                     TCHAR FName[512];
CreateUrlCacheEntry(OLE2T(Url), Ts.GetLength(), _T("htm"), FName, 0);
                     CFile hFile;
                     hFile.Open(FName, CFile::modeCreate|CFile::modeWrite);
                     hFile.Write(Ts,Ts.GetLength()); 
                     ReportProgress(BINDSTATUS_CACHEFILENAMEAVAILABLE, T2W(FName));
       }
       修改网页代码:
Ts.Replace(_T("百度"),_T("千度"));
       为浏览器准备好数据:
TotalSize= Ts.GetLength() ;
              CreateStreamOnHGlobal(0, true, &DataStream);
              const char * pTs = Ts.GetBuffer(Ts.GetLength());
              ULONG cbWritten;
              DataStream->Write(pTs,Ts.GetLength(),&cbWritten);
              Ts.ReleaseBuffer();
              pTs = NULL;
 
              ULARGE_INTEGER Dummy;
              _LARGE_INTEGER zero;
              zero.QuadPart =0;
              DataStream->Seek ( zero, STREAM_SEEK_SET, &Dummy);
 
4、IInternetProtocol::Read方法
    该方法由WEB处理器调用来取得浏览器要解析的数据。在上一方法ReportData中
我们已经将所有数据缓存到流中,因此,这里只需将流中的数据返回给WEB处理器。
下面的代码示例了Read中的简单处理:
                  DataStream->Read(pv, cb, pcbRead);
       Written+=*pcbRead;
       if (Written == TotalSize)
       {
              return S_FALSE;
       }
       else
       {
              return S_OK;
       }
    千万注意,在数据已读取完毕时要返回S_FALSE , 不然可能导致Read被无穷循环调用。处理完这几个方法后,基本是大功造成,其它一些方法处理十分简单,可以参考上面提到的例子。 

源代码下载

Feedback

# re: 网页源码过滤  回复  更多评论   

2006-11-22 17:20 by lael
请问怎么知道是iframe还是主框架呢?

# re: 网页源码过滤  回复  更多评论   

2014-03-03 15:27 by mrdrag
正好看到这个,不错的文章

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理