⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ie异步可插入协议扩展(摘自哈巴狗的小窝).txt

📁 IE异步可插入协议扩展(摘自哈巴狗的小窝)
💻 TXT
📖 第 1 页 / 共 2 页
字号:
IE异步可插入协议扩展(摘自哈巴狗的小窝)
IE异步可插入协议扩展
作者 陈省
介绍
对于每天都要使用的IE浏览器的人来说,输入www.google.com 等网址进行网上冲浪就象呼吸一样自然。大多数情况时,我们可能根本想不起来要在网址前面加上http:// 来声明要访问的是一个基于http协议的Web网站。所谓网络协议,其实无非就是一组描述如何获取不同资源并进行通讯的行为规则。IE浏览器除了内置了对 http协议外,还持ftp和gopher等协议。
从IE4开始,IE允许通过插入式异步协议扩展来扩展它处理协议的功能,人们可以通过自定义的扩展来让IE支持更多的协议,比如一些不是普遍支持的流媒体协议等。此外,我们还可以通过插入式协议扩展让IE可以以HTML文件的形式显示一个数据库中的表。
异步可插入协议的原理
可插入式协议是基于异步的URL Moniker技术的。Moniker最早是从OLE2中引入的概念,当时的Moniker就是一个COM绑定和定位对象,人们可以使用Moniker来定位并加载被保存到文件中的COM组件,实现COM的可持续性,一开始Moniker是基于同步方式实现的。随着网络技术的发展,定位并从网络上获取信息的需求逐渐超过了对本地数据的存取需求,因为网络的通讯通常都是不稳定的,因此需要以异步的方式来实现。为此微软设计了URL moniker对象来提供网络信息下载过程的一个统一接口,基于URL来访问网络资源的Moniker演变成了以异步方式实现的Moniker。
    IE的URL moniker是在urlmon.dll动态连接库中实现的。当urlmon.dll处理http, ftp, Gopher等内置协议的访问时,它把访问请求转发给内部的一个COM组件来处理,该COM组件使用WinInet函数来完成实际的处理工作。对于非内置的协议,urlmon.dll则把请求转发给特定的可插入协议扩展进行处理,比如说mailto:协议。
一个典型的异步可插入协议(APP)的主要工作的就是接收一个非IE内置的UrlURL协议字符串,对字符串进行解析,分析字符串的元素,并根据协议访问相应的系统或者网络资源,并将网络资源的内容输出到浏览器。
 
一个自定义的电子书可插入协议的实现
 
我平时业余时间喜欢上网上找一些娱乐小说和技术书籍来看,其中有一些小说采用的是付费方式才能看既然是付费的小说,自然会提供一些加密的方式,避免盗版书在网上的传播。
接下来,我想写一个程序对一些Html文件进行加密,只有用户在浏览器中键入EBook://c:\abc.htm,然后输入口令后,才能看到解密后的Html页面。接下来,就看如何使用APP来实现这样一个可插入协议。
 
创建COM组件
       首先,新建一个ActiveX Library项目,保存为IEProtocol.dpr,然后新建一个名为TIEEncryptAPP的COM组件,保存为 CIEProtocol.pas文件。一个APP组件至少要实现IInternetProtocol接口(该接口定义在urlmon.pas单元中),又由于IInternetProtocol接口派生自IInternetProtocolRoot,所以我们还需要实现 IInternetProtocolRoot接口。下面是实现了IInternetProtocol接口的TIEEncryptAPP类的定义:
type  TIEEncryptAPP = class(TComObject, IInternetProtocol)  protected    //IInternetProtocolRoot接口定义    function Start(szUrl: LPCWSTR; OIProtSink: IInternetProtocolSink;      OIBindInfo: IInternetBindInfo; grfPI, dwReserved: DWORD): HResult;      stdcall;    function Continue(const ProtocolData: TProtocolData): HResult; stdcall;    function Abort(hrReason: HResult; dwOptions: DWORD): HResult; stdcall;    function Terminate(dwOptions: DWORD): HResult; stdcall;    function Suspend: HResult; stdcall;    function Resume: HResult; stdcall;    //IInternetProtocol接口定义    function Read(pv: Pointer; cb: ULONG; out cbRead: ULONG): HResult; stdcall;    function Seek(dlibMove: LARGE_INTEGER; dwOrigin: DWORD; out libNewPosition:      ULARGE_INTEGER): HResult; stdcall;    function LockRequest(dwOptions: DWORD): HResult; stdcall;    function UnlockRequest: HResult; stdcall;  end; 
其中IInternetProtocolRoot接口的方法意义如下:
Abort 停止一个正在进行的资源下载过程
Continue 允许协议扩展继续进行进行资源数据下载过程。
Resume 未来扩充需要,暂时未实现。
Start 启动同该协议相关的资源下载过程。
Suspend 未来扩充需要,暂时未实现
Terminate 结束下载过程,释放扩展分配的资源。

而IInternetProtocol协议的方法定义如下:
LockRequest锁定资源下载请求,这时IInternetProtocolRoot接口的Terminate方法将允许被调用,与此同时未下载完的数据仍然可以被读取。
Read浏览器调用这个方法从协议扩展获得相应的数据。
Seek移动读取数据的位置。
UnlockRequest释放请求锁定

 
对于电子图书这样一个简单的协议扩展来说,我们只需要实现Start方法来启动下载过程,并通过Read方法向浏览器返回解密后的电子图书的数据就可以了。其它的方法只要简单的返回请求结果,而无须做任何的操作:
 
function TIEEncryptAPP.Abort(hrReason: HResult; dwOptions: DWORD): HResult;begin  Result := Inet_E_Invalid_Request;end; function TIEEncryptAPP.Continue(  const ProtocolData: TProtocolData): HResult;begin  Result := Inet_E_Invalid_Request;end; function TIEEncryptAPP.LockRequest(dwOptions: DWORD): HResult;begin  Result := S_OK;end; function TIEEncryptAPP.Resume: HResult;begin  Result := Inet_E_Invalid_Request;end; function TIEEncryptAPP.Seek(dlibMove: LARGE_INTEGER; dwOrigin: DWORD;  out libNewPosition: ULARGE_INTEGER): HResult;begin  Result := E_Fail;end; 
function TIEEncryptAPP.Suspend: HResult;begin  Result := Inet_E_Invalid_Request;end; function TIEEncryptAPP.Terminate(dwOptions: DWORD): HResult;begin  Result := S_OK;end; function TIEEncryptAPP.UnlockRequest: HResult;begin  Result := S_OK;end; 
启动协议处理
 
首先来看如何启动协议处理,当我们在浏览器中输入EBook://c:\ebook.htm字符串想要浏览加密的页面文件时,IE会找到EBook的扩展协议,然后调用协议的Start方法来启动协议处理过程:
 
threadvar  ResultHTML: array[0..64 * 1024 - 1] of Char; { 64 kB }  CurrPos: Integer;  BytesLeft: Integer;  ProtSink: IInternetProtocolSink; 
function TIEEncryptAPP.Start(szUrl: LPCWSTR;  OIProtSink: IInternetProtocolSink; OIBindInfo: IInternetBindInfo; grfPI,  dwReserved: DWORD): HResult;Const  ErrorHTML = '<HTML><BODY BGCOLOR="#FFFFFF">'#13+                '<H2>浏览电子书%s时发生错误</H2>'#13+                '<P><I>%s</I></P>'#13+                '</BODY></HTML>';var  S: string;begin  S := WideCharToString(szURL);  { EBook:// }  Delete(S, 1, 8);  //去掉后面/符号  SetLength(S, Length(S) - 1);  S := HTTPDecode(S);  if FileExists(S) then  begin    //显示密码提示框    if InputBox('密码','请输入密码', '')<>'hubdog' then      S:=Format(ErrorHTML, [S, '无效的密码'])    else      S := Decrypt(S);  end  else    S := Format(ErrorHTML, [S, '没有找到文件']);  CurrPos := 0;  BytesLeft := Length(S);  FillChar(ResultHTML, SizeOf(ResultHTML), 0);  StrPCopy(ResultHTML, S);  ProtSink := OIProtSink;  //数据通知  OIProtSink.ReportData(bscf_LastDataNotification, 0, BytesLeft);  //数据可完全获得的通知  OIProtSink.ReportData(bscf_DataFullyAvailable, 0, BytesLeft);  Result := S_OK;end; 
Start方法中有一个szUrl的参数,对应着我们在浏览器中输入的url字符串(注意:IE会在输入的字符串末尾自动加上一个斜杠),为了获得要处理的被加了密的html文件,使用Delete函数先从字符串中删除EBook://8个字符,然后在用SetLength去掉IE添加的斜杠,同时要注意IE传过来的字符串参数是进行Http编码的,所以还要调用HttpApp单元中的HttpDecode来进行解码还原为c:\ebook.htm的文件名字符串。
 
如果输入的文件存在的话,则提示用户输入密码,如果密码匹配的话,则调用Decrypt函数对文件进行解密并,返回解密后的文本串。如果文件不存在,或者密码不匹配,则生成ErrorHtml返回一个错误描述的HTML页面。关于加密和解密过程,比较简单,我会在后面介绍。
 
获得解密后的文本后,将文本内容复制到ResultHTML字符串缓冲区中(这里的缓冲区处于简单的考虑,写死成64K)。另外要注意的是这里用的参数都使用ThreadVar来声明,这是因为协议处理过程是一个多线程异步的过程,同一时刻,可能有多个EBook的协议请求在处理中,所以变量都要声明为线程安全的,以避免资源冲突。接下来保存IE通过Start方法传过来的OIProtSink协议处理事件接口(稍后还会用到),然后调用接口的ReportData方法通知IE要获取的数据量为BytesLeft,并通过设定ReportData的grfBSCF参数为LastDataNotification 和DataFullyAvailable通知IE,数据已经完全准备好了,这样稍后IE就会调用扩展的Read方法来获得解密后的页面数据。
 
返回解密数据
 
function TIEEncryptAPP.Read(pv: Pointer; cb: ULONG;  out cbRead: ULONG): HResult;var  I: Integer;begin  if (BytesLeft > 0) then  begin    I := CB;    if (I > BytesLeft) then      I := BytesLeft;    Move(ResultHTML[CurrPos], PV^, I);    CBRead := I;    Dec(BytesLeft, I);    Inc(CurrPos, I);    Result := S_OK;    {通知IE读取更多的数据 }  end  else  begin    //数据全部下载完成    Result := S_False;    ProtSink.ReportResult(S_OK, 0, nil);  end;end; 
在Read 方法中,IE会传过来一个内部缓冲区的指针pv,同时cb参数表示缓冲区的大小,电子书的数据有可能会很大,而IE的缓冲区不会无限大,因此IE会分多次来读取电子书的数据,我们每次应该尽可能读取cb大小的数据,将其移动到IE的缓冲区内,读取完成后减少BytesLeft的值,同时增加CurrPos 的值来记录当前以发送给IE的数据位置,并返回cbRead告诉IE传送的数据到底有多少。如果一次没有返回全部的数据,则返回S_OK通知IE还有没传送完的数据,这样IE就会继续调用Read方法来完成数据下载,最后当所有的数据都处理完毕后,则返回S_False通知IE已经没有要传的数据了,同时,调用事件接口ProtSink的ReportData方法通知IE,协议处理完毕。
 
加密解密
 
还是为了简单起见,html页面的加密非常简单,我使用XOR加密,这样的好处是,处理简单。因为XOR加密和解密是一个可逆过程,加密和解密使用同一个函数就可以完成了。下面是加密和解密字符串类:
type  //加密字符串类  TEncryptStrings = class(TStringList)  public    procedure SaveToStream(Stream: TStream); override;  end;   //解密字符串类  TDecryptStrings = class(TStringList)  public    procedure LoadFromStream(Stream: TStream); override;  end; implementation //用xor算法进行加密 procedure EncodeStream(Input, Output: TStream);var  InBuf: array[0..1023] of byte;  BufPtr: PChar;  I, BytesRead: Integer;begin  Assert(Assigned(Input), '无效的流指针');  //必须重新设置流指针位置  Input.Position := 0;  Output.Position := 0;  repeat    BytesRead := Input.Read(InBuf, SizeOf(InBuf));    I := 0;    while I < BytesRead do    begin      InBuf[I] := InBuf[I] xor 8;      Inc(I);    end;    OutPut.Write(InBuf, BytesRead);  until BytesRead = 0;  Input.Position := 0;  Output.Position := 0;end; { TDecryptStrings } procedure TDecryptStrings.LoadFromStream(Stream: TStream);var  OutStream:TMemoryStream;begin  //解密  OutStream:=TMemoryStream.Create;  try    EncodeStream(Stream, OutStream);    inherited LoadFromStream(OutStream);  finally    OutStream.Free;  end;end; { TEncryptStrings } procedure TEncryptStrings.SaveToStream(Stream: TStream);var  OutStream: TMemoryStream;begin  inherited;  //加密  OutStream := TMemoryStream.Create;  try    EncodeStream(Stream, OutStream);    Stream.CopyFrom(OutStream, 0);  finally    OutStream.Free;  end;end; 
为了减少编码工作量,我直接从TStringList类派生了两个字符串列表处理类,并重载了LoadFromStream和SaveToStream方法来对流进行加解密处理。加解密处理都是调用的EncodeStream方法来对字符串流进行加密,加密使用每个字符同8进行xor运算。
 
下面我写了一个程序,可以对html文件进行处理点击Button1,则将文件进行加密处理,点击Button2可以对察看解密后文件的原有内容:

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -