IE瀏覽器 自定義地址協議的實現

weixin_34321977發表於2008-03-19

瀏覽QQ空間的時候發現,只要在IE地址中輸入象一下這種形式的地址,
tencent://Message/?Uin=251464630&websiteName=qzone.qq.com&Menu=yes
就會彈出給 251464630 傳送資訊的對話方塊,也就是說QQ對IE位址列的東西做了監控。而且可以發現輸入地址確定之後他就啟動了timwp.exe這個程式。
還有新增QQ書籤時的:tencent://AddPortal/?PanelID=10020 在PPlive 也有實現類似的功能,只要你電腦上安裝了PPlive 這個程式,在IE位址列中輸入
synacast://09jN1+TK3K3nodzJoaLOmqeS1KGhoKOZoqGcltid1qeZy9ec1dbRy9ue1aKe5pzI2dSpna +VpJbayuPKrbOvvcySpRMUHl01NaScmcEIGRMUNh4vQzNmNR8IGaqemauXq7OvvcySpZiekrCWoKOfj +LU162emaiToaGgl6eToaalo66VoKCmoaaVoJbX2LPa1ODgo6WU057TmtqT3tXgo66VoKCn3trV5KqbmNuT16HQl +TK5KqkmaaVq+XQ2eqfn5/Nl92W1J7azuqfqKCcmbHZ0+Dgo6WU1J7TmtqT3tXgo66Vq+TP2eqfn5/Ol92W1J7azuqfqKCn3dnV5KqbmNyT16HQl +TK5KqkmZzZ2NXZzrPN5ePg3N7G4tWSwtvR3N/judfM1bnQpqeXpZavyurG3N/Tstqip6k=然 後確定,就會彈出播放CCTV5的視窗。很有意思的一種功能,竟然這麼多程式都用到了,我也就找了一下,找到了一下實現方法。

方法一: 也就是QQ和PPlive所採用的方法,在登錄檔裡面新增兩種型別的註冊。

QQ的:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\TENCENT]
@="TencentProtocol"
"URL Protocol"="C:\\Program Files\\Tencent\\QQ\\Timwp.exe"

[HKEY_CLASSES_ROOT\TENCENT\DefaultIcon]
@="C:\\Program Files\\Tencent\\QQ\\Timwp.exe,1"

[HKEY_CLASSES_ROOT\TENCENT\shell]

[HKEY_CLASSES_ROOT\TENCENT\shell\open]

[HKEY_CLASSES_ROOT\TENCENT\shell\open\command]
@="\"C:\\Program Files\\Tencent\\QQ\\Timwp.exe\" \"%1\""

PPlive的:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\Synacast]
@="URL:synacast Protocol"
"Version"="1.5.38"
"URL Protocol"=""

[HKEY_CLASSES_ROOT\Synacast\DefaultIcon]
@="C:\\Program Files\\PPLive\\PPLive.exe"

[HKEY_CLASSES_ROOT\Synacast\Shell]

[HKEY_CLASSES_ROOT\Synacast\Shell\Open]

[HKEY_CLASSES_ROOT\Synacast\Shell\Open\Command]
@="C:\\Program Files\\PPLive\\PPLive.exe \"%1\""


通過多方查詢終於發現是登錄檔這兩項在起作用,原來只要在登錄檔裡象新增副檔名一樣,新增兩個Synacast和TENCENT副檔名來,IE就會

自動查詢到這裡來呼叫相應的程式。IE果然和windows系統核心整合起來了!原來登錄檔副檔名項還有這種作用,自己見識太少了,這種方法實

現IE地址的自定義估計是最簡單的了。


方法二:一開始不知道方法一的時候,在網上找了很多可以實現這種功能的程式碼,採用BHO(Browser Helper Object,瀏覽器輔助物件)或者

IURLSearchHook介面 來做到。也就是通常所說的IE外掛了,我這裡統稱為方法二。如果不知道什麼叫做BHO和IURLSearchHook的就去搜尋一下

吧,最近流氓外掛很火,所以這個技術也有多人提到,藉助IURLSearchHook還可以實現中文實名上網等功能,不過那些臭名昭著的流氓軟體可

都不是這樣子坐的他更多的精力是放到防止別人解除安裝那邊去了。
     因為我沒有編寫過ATL或者COM方面的程式,所以也就藉著這個機會寫了個IURLSearchHook的實現,以後碰到IE外掛程式設計,ATL程式設計,COM編

程,Shell介面程式設計的時候也好能夠玩一玩,好像shell介面程式設計還是有很多有意思東西的。
     這個是MSDN上IURLSearchHook介面的說明:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/reference/ifaces/iurlsearchhook/iurlsearchhook.asp

    下面這個是MSDN上shell介面程式設計的說明:http://msdn2.microsoft.com/en-us/library/ms631201.aspx
    這個是ATL程式設計的 http://msdn2.microsoft.com/zh-cn/library/65t81w8a(vs.80).aspx
    有一片不錯的來之VCbase的文章說得是“ATL 實現定製的 IE 瀏覽器欄、工具欄和桌面工具欄”
   http://www.vckbase.com/document/viewdoc/?id=1457
  
     簡單的說,IURLSearchHook被瀏覽器用來轉換一個未知的URL協議地址,當瀏覽器企圖去開啟一個未知協議的URL地址時,瀏覽器首先嚐試從這 個地址得到當前的協議,如果不成功,瀏覽器將尋找系統裡所有註冊為“URL Search Hook”(資源搜尋鉤子,USH)的物件並把這個IE不能理解的地址傳送過去,如果某個USH物件“認識”這個地址,它就返回一個特定的標識告訴IE它 知道怎麼開啟這個地址,然後IE就根據約定的方法呼叫它,最終開啟這個地址。其實USH物件並不陌生,我們一些偷懶的使用者就經常為了省事而不輸入 “http://”,但是IE最終還是能認出並開啟某個地址,就是USH的功勞。當然通過BHO的 GetSite方法也可以做到同樣的事情,不過 IURLSearchHook簡單一些,只有一個Translate方法,我技術不行所以專挑簡單的^_^

     以下是實現程式碼:
     使用VC2003新建一個名字為UrlSearchHook的ATL工程
進入工程之後在類檢視中右擊工程名字-》新增類—》 新增一個 叫WidebrightBlog的 “ATL簡單物件”

還是類檢視中右擊 WidebrightBlog 類——》新增->實現介面,利用嚮導找到 shell介面中的IURLSearchHook 後新增實現。
    

     嚮導裡面列了很多,不過我是沒找到啦,所以之後手工新增介面實現了,以下全部程式碼,紅色的是自己寫的。

// WidebrightBlog.h : CWidebrightBlog 的宣告

#pragma once
#include "resource.h"        // 主符號

#include <comdef.h>

#include <shlobj.h>


// IWidebrightBlog
[
object,
uuid("1F0B2F61-221A-456C-A8E1-E0E01796E482"),
dual, helpstring("IWidebrightBlog 介面"),
pointer_default(unique)
]
__interface IWidebrightBlog : IDispatch
{
};

// CWidebrightBlog

[
coclass,
threading("apartment"),
vi_progid("UrlSearchHook.WidebrightBlog"),
progid("UrlSearchHook.WidebrightBlog.1"),
version(1.0),
uuid("44AA49F1-7E20-472E-A5A4-08D3233D9132"),
helpstring("WidebrightBlog Class")
]
class ATL_NO_VTABLE CWidebrightBlog :
   public IWidebrightBlog,
public IURLSearchHook
{
public:
CWidebrightBlog()
{
   // MessageBox(NULL,"在CWidebrightBlog()","widebright ",MB_OK);
}
     
DECLARE_PROTECT_FINAL_CONSTRUCT()

//元件介面對映部分,該部分對映主要是告訴QueryInterface能返回哪些介面給外部

   BEGIN_COM_MAP(CWidebrightBlog)
        COM_INTERFACE_ENTRY(IWidebrightBlog)
        COM_INTERFACE_ENTRY(IDispatch)
        COM_INTERFACE_ENTRY(IURLSearchHook)
   END_COM_MAP()


HRESULT FinalConstruct()
{
   return S_OK;
}

void FinalRelease()
{
}

public:
  
  STDMETHODIMP Translate(   LPWSTR lpwszSearchURL,   DWORD cchBufferSize)
   {   
   //   MessageBox(NULL,"在Translate函式中了","widebright ",MB_OK);
   
    if (wcsncmp(L"widebright",lpwszSearchURL,10 )==0)
        {
     // MessageBox(NULL,"找到了","widebright ",MB_OK);
      wcscpy(lpwszSearchURL,L"
http://hi.baidu.com/widebright");
          return S_OK ;
        }
     return E_FAIL;   //沒有修改lpwszSearchURL
     return S_FALSE; //修改了lpwszSearchURL的,但還需要繼續處理
    
   }

   int MyFunction(void)       //這個是自己利用嚮導生成Method的函式,想試一下COM介面,沒什麼用的,根本程式無關。
   {
    return 0;
   }
};

不過編譯一下,有錯,說是IURLSearchHook介面GUID沒定義,明顯是有這個介面的,在MSDN裡面沒有什麼說明,最後在CSDN上找到一張帖子
,說是VC2003裡面 和VC6的ATL不同,在VC6裡面上面程式碼是可以通過的,但在VC2003以後版本就不行了。這是引用帖子中原話“There are two

<comdef.h> header files in VC.NET, one in Vc7/include and the other in Vc7/PlatformSDK/include. The former splits off the

smart pointer typedefs into comdefsp.h, and it doesn't include IContextMenu. The latter does. You can try to #include the

PlatformSDK header directly, change your INCLUDE path order, or supply the missing typedef yourself, e.g.
struct __declspec(uuid("000214e4-0000-0000-c000-000000000046"))
IContextMenu;

_COM_SMARTPTR_TYPEDEF(IContextMenu, __uuidof(IContextMenu));”

      我檢視了VC目錄下的兩個檔案也確實如此,所以手工新增了一下Include路徑確保#include <comdef.h> 包含的是Platform中的那個comdef.h就行了。在 UrlSearchHook工程-“工程屬性”-》“C/C++” -》   “附加包含目錄” 屬性,增加一個 "C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\PlatformSDK\Include"   。  

     然後再編譯就通過了,生成了dll檔案,整個程式碼很簡單,就是實現IURLSearchHook介面的 Translate函式,BEGIN_COM_MAP和 COM_INTERFACE_ENTRY幾個ATL巨集宣告IURLSearchHook介面外部可見,就一些ok了。

      不過要讓IE知道有這個URL Search Hook擴充套件,還要修改登錄檔才行。我手工在HKEY_CURRENT_USER\Software\ Microsoft\ Internet Explorer\ UrlSearchHooks 新增了一項 REG_SZ型別名字為{44AA49F1-7E20-472E-A5A4-08D3233D9132} 的項,其中名字和你生成的dll註冊型別對應 ,在上面程式碼裡也可以看到。注意的是MSDN上說的是HKEY_LOCAL_MACHINE\..\.. 登錄檔位置,但其他文件說得是HKEY_CURRENT_USER位置,而且我在HKEY_LOCAL_MACHINE下也沒看到 UrlSearchHooks項,不知道新增在HKEY_LOCAL_MACHINE會不會有效果,不過新增在HKEY_CURRENT_USER\ Software\ Microsoft\ Internet Explorer\ UrlSearchHooks會成功就是了。

      好了,啟動IE7,輸入widebright開頭的地址,都跳到http://hi.baidu.com/widebright 來了, 在卡卡上網助手裡可見 CWidebrightBlog Object 位址列搜尋 項 。
   


方法三: 也是使用IE擴充套件介面實現,我之所以把它獨立處理列為一種方法,是我覺得是一種很使用的價值的功能。我發現電腦裡面有的電子書在我升級到IE7後看不了,可能也是這方面有問題。在登錄檔中都可見到這些Ebook電子書的註冊值,可能Ebook也就是這麼幹的吧。

更多相關內容搜尋:協議外掛(Asynchronous Pluggable Protocols),MIME Filter

我是在下面這篇文章裡面發現這種實現方法的,下面轉載:
   轉貼 來網際網路 ,不過文章作者倒是很眼熟,好像是看過他幾篇文章了,支援!

    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可以對察看解密後檔案的原有內容:

procedure TForm1.Button1Click(Sender: TObject);
var
   Strings:TEncryptStrings;
begin
   if not OpenDialog1.Execute then Exit;
   Strings:=TEncryptStrings.Create;
   try
     Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
     Strings.Text:=Memo1.Text;
     Strings.SaveToFile(OpenDialog1.FileName);
     Memo2.Lines.LoadFromFile(OpenDialog1.FileName);
   finally
     Strings.Free;
   end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
   Strings:TDecryptStrings;
begin
   if not OpenDialog1.Execute then Exit;
   Strings:=TDecryptStrings.Create;
   try
     Memo1.Lines.LoadFromFile(OpenDialog1.FileName);
     Strings.LoadFromFile(OpenDialog1.FileName);
     Memo2.Lines.Text:=Strings.Text;
   finally
     Strings.Free;
   end;
end;

介面如下:

註冊擴充套件

完成了擴充套件協議後,只剩下註冊擴充套件了,要想註冊擴充套件,需要在登錄檔的HKEY_CLASSES_ROOT\PROTOCOLS\Handler\下新增EBook關鍵字,然後在該關鍵字下新增名為CLSID的欄位,設定其值為擴充套件的Guid,下面是用於註冊的類工廠:

type
   TIEEncryptAPPFactory = class(TComObjectFactory)
   public
     procedure UpdateRegistry(Register: Boolean); override;
   end;

   { TIEEncryptAPPFactory }

procedure TIEEncryptAPPFactory.UpdateRegistry(Register: Boolean);
begin
   inherited;
   if Register then
     CreateRegKeyValue(HKEY_CLASSES_ROOT, 'PROTOCOLS\Handler\EBook', 'CLSID',
       GuidToString(ClassID))
   else
     DeleteRegKeyValue(HKEY_CLASSES_ROOT, 'PROTOCOLS\Handler\EBook', 'CLSID');
end;

initialization
   TIEEncryptAPPFactory.Create(ComServer, TIEEncryptAPP, Class_IEEncryptAPP,
     'IEEncryptAPP', '', ciMultiInstance, tmApartment);
end.

最後,將本書光碟中的ebook.htm檔案放到c:根目錄下,註冊擴充套件後,啟動IE,輸入ebook://c:\ebook.htm,然後在彈出的密碼框中輸入hubdog,IE就會顯示解密後的電子小說,介面示意如下:

臨時註冊擴充套件

上面的註冊方法可以稱為持久註冊的方法,一旦註冊就總是生效,。IE還提供臨時註冊的方法,只要編寫一個BHO擴充套件,在BHO載入時,呼叫TemporyRegister方法進行註冊,在IE退出時呼叫:

var

   Factory:IClassFactory;

procedure TemporaryRegister;

begin

   CoGetClassObject(Class_IEEncryptAPP, CLSCTX_SERVER, nil, IClassFactory, Factory);

   CoInternetGetSession(0, InternetSession, 0);

   InternetSession.RegisterNameSpace(Factory, Class_IEEncryptAPP, 'EBook', 0, nil, 0);

end;

procedure UnRegister;

begin

   InternetSession.UnregisterNameSpace(Factory, 'EBook');

end;

這樣的好處是,在程式執行時,可以隨時解除對擴充套件協議的支援,而前面的永久註冊法必須在解除註冊後,重新啟動IE才行。缺點是必須通過一個BHO來實現臨時註冊。

其它的APP

除了上面的協議擴充套件外,IE還支援NameSpace Handler以及Mime-Handler兩種APP擴充套件。其中NameSpace擴充套件是對特定名字空間進行處理的協議擴充套件,比如如果我們註冊一個對名字空間<hubdog>,則當IE處理http://hubdog.csdn.netmailto:hubdog@263.net的URL 時,一旦遇到hubdog名字空間,就會呼叫我們的NameSpace Handler進行處理,而不管URL是基於http協議的還是ftp等其它協議的都進行處理。從實現的角度來看,NameSpace的實現方法和前面的 協議擴充套件幾乎一樣,除了註冊時要填寫的登錄檔項內容不同而已。

而Mime協議擴充套件處理的主要是對一些特殊的媒體資源如圖片,聲音檔案進行處理,比如下表是IE預設支援的一些媒體形式。

text/richtext

text/html

audio/x-aiff

audio/basic

audio/wav

image/gif

image/jpeg

如果那天哪天你發明一種新的音樂形式,比如副檔名為.sy,就可以註冊一個Mime擴充套件對 .sy檔案處理,讓IE播放相應的聲音。

Mime擴充套件除了需要支援IInternetProtocol介面外,還必須實現IInternetProtocolSink介面,介面定義如下:

   IInternetProtocolSink = interface
     ['{79eac9e5-baf9-11ce-8c82-00aa004ba90b}']
     function Switch(const ProtocolData: TProtocolData): HResult; stdcall;
     function ReportProgress(ulStatusCode: ULONG; szStatusText: LPCWSTR): HResult; stdcall;
     function ReportData(grfBSCF: DWORD; ulProgress, ulProgressMax: ULONG): HResult; stdcall;
     function ReportResult(hrResult: HResult; dwError: DWORD; szResult: LPCWSTR): HResult; stdcall;
   end;

資料通訊方式上來看,Mime擴充套件同一般的協議擴充套件差別比較大,通訊的流程是這樣的:

1.        首先,IE會在遇到相應資源下載請求時,呼叫擴充套件的Start方法來啟動下載過程。

2.        然後IE會呼叫擴充套件的ReportProgress方法,告知擴充套件被下載的資料儲存的快取檔名稱。

3.        當IE下載完原始資料後,會呼叫擴充套件的ReportData方法通知擴充套件準備對原始資料進行加工處理。

4.        這時,擴充套件需要呼叫IE提供的IInternetProtocol介面的Read方法來獲得原始資料。

5.        對原始資料處理後,擴充套件要呼叫IE的IInternetProtocolSink介面的ReportData方法通知IE資料處理完畢。

6.        最後,IE呼叫擴充套件的Read方法獲得處理後的資料。

可以看出來同一般協議擴充套件的純主動向IE返回資料的方式不同,Mime的資料通訊方式即有被動的接收IE獲取的原始資料,也有將處理後的資料返回IE的主動通訊方式。

由於本質上來看,Mime同一般的APP的實現相差不多,所以這裡我將不再浪費篇幅來給出Mime擴充套件的實現例項了。

總結

     IE早已經不再是一個單純意義的Web瀏覽程式了,通過對IE支援的協議擴充,我們可以將IE變成一個網路開發平臺,可以將IE的功能無限延伸。

相關文章