65.9K
CodeProject 正在变化。 阅读更多。
Home

Chromium (CefSharp) Tor 浏览器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (11投票s)

2016年1月28日

CPOL

5分钟阅读

viewsIcon

106502

downloadIcon

6861

使用 C# 和 CefSharp 以及 Tor.NET 构建的替代 Tor 浏览器。

引言

我最喜欢的浏览器是 Google Chrome。它速度快且易于使用。

当我遇到一个因网络过滤器而无法访问的网站时,我的选择之一是使用原始的 Tor 浏览器,它是 Firefox 的定制版本,我可以直接说“我不喜欢它”。

当我阅读了 Chris Copeland 的优秀文章“Tor.NET - 一个托管的 Tor 网络库”时,我开始尝试项目中使用的 Internet Explorer 组件 (WebBrowser) 的示例应用程序。我在我的项目中使用 CefSharp Chromium 浏览器组件已有段时间了,我用 CefSharp 替换了项目中的 WebBrowser,于是这个应用程序就诞生了。

所需材料

此应用程序使用 CefSharp 作为 Web 浏览器,并使用 Tor.NET 库通过 HTTP 代理连接到 Tor 网络。有关这两个组件的详细信息,请参阅参考部分。

Application

应用程序启动时就是这样的。

Main screen of the application

这是一个模仿 Google Chrome 的标准标签页式 Web 浏览器。

应用程序启动时,它会连接到 Tor 网络并启动 HTTP 代理。当 Tor 准备好使用时,地址栏会变成淡绿色。

“下载”按钮会打开下载页面。

“DuckDuckGo”按钮会打开替代的 DuckDuckGo 搜索引擎,其地址为“.onion”Tor 地址。

您可以通过单击“检查 Tor 是否处于活动状态”按钮来检查您是否确实已连接到 Tor 网络。

代码中的有趣部分

该应用程序是一个只有单个窗体的 WinForms 应用程序。但是,代码中有一些有趣的部分,可能对好奇的程序员有所帮助。

以下是 CefSharp 的初始化代码。

            CefSettings settings = new CefSettings();

            //Set proxy for Tor
            settings.CefCommandLineArgs.Add("proxy-server", "127.0.0.1:8182");

            //Load pepper flash player 
            //settings.CefCommandLineArgs.Add("ppapi-flash-path", appPath + @"PepperFlash\pepflashplayer.dll");

            settings.RegisterScheme(new CefCustomScheme
            {
                SchemeName = SchemeHandlerFactory.SchemeName,
                SchemeHandlerFactory = new SchemeHandlerFactory()
            });

我们通过添加“proxy-server”命令行参数,使 CefSharp 组件使用我们的 Tor 代理。

如果您想在浏览器中使用 Flash,可以将您的 Chrome 安装中的 PepperFlash 文件夹复制到应用程序目录,然后取消注释与 PepperFlash 相关的行。Chrome 通常位于类似以下的文件夹中

C:\Program Files (x86)\Google\Chrome\Application\47.0.2526.111

但请记住,如果您真的关心您的隐私,不建议在使用 Tor 时使用 Flash,因为 Flash 插件可能会绕过您的代理访问 Internet。

Tor 通过调用 Tor.NET 示例应用程序中的 InitializeTor 函数来初始化,该函数是完全复制的。

Handler 用于让 CefSharp “表现正常”。以下是 CefSharp Handler 及其功能的列表:

DownloadHandler: 告知应用程序何时开始新的下载以及正在进行的下载的进度。

KeyboardHandler: 当焦点在 CefSharp 浏览器上时,它会“吞掉”用户按下的所有按键。应用程序使用 Ctrl-F4 关闭活动浏览器标签页,因此此 Handler 有助于告知应用程序 Ctrl-F4 是在浏览器中按下的。

LifeSpanHandler: 在 CefSharp 打开弹出窗口之前,会调用此 Handler 的 OnBeforePopup 函数,它会告知 CefSharp 在新标签页中打开它,而不是在单独的窗口中打开。

MenuHandler: 我将我最常用的 Chrome 浏览器中的项目添加到了浏览器上下文菜单中。此 Handler 负责这些操作。此上下文菜单的一个有趣的附加功能是“另存为 PDF”选项,该选项在 Google Chrome 中不存在。

SchemeHandler: 此 Handler 有助于加载以“chrome://”开头的网页。

“下载”页面

The "Downloads" Page

Google Chrome 对设置、下载、扩展程序和其他页面使用 HTML 界面,因此我也为“下载”页面做了同样的事情。

应用程序的“下载”页面是 Chrome 浏览器的一个“克隆”,它是一个存储在“storage”文件夹中的 HTML 页面。为了能够在应用程序中加载此页面,我定义了一个 SchemeHandler 并将其命名为“chrome:”。因此,当您在地址栏中键入“chrome://storage/downloads.htm”时,CefSharp 会询问 SchemeHandler 如何处理它,然后 Handler 会从 storage 文件夹返回请求的文件。

这是 downloads.htm 页面中的 JavaScript 代码。

  <script type="text/javascript">

      var $container;
      var $template;
      var timer;

      $(document).ready(function () {
          $container = $("#downloads-display");
          $template = $("#template");
          UpdateList();
          timer = setInterval(UpdateList, 500);
      });

      //...

      function UpdateItem(item) {
          var $item;
          var id = "d" + item.Id;
          $item = $("#" + id);
          //Add item if it does not exist
          if ($item.length == 0) {
              $item = $($template[0].outerHTML);
              $container.prepend($item);
              $item.removeAttr("hidden");
              $item.attr("id", id);
              $item.find("a.cancel").click(function () {
                  host.cancelDownload(item.Id);
              });
              $item.find("img.icon").attr("src", "chrome://fileicon/" + item.SuggestedFileName);
              var startTime = getDate(item.StartTime);
              $item.find("div.since").text(startTime.format("dd.MM.yyyy"));
              $item.find("div.date").text(startTime.format("hh:mm:ss"));
              if (item.SuggestedFileName != "") $item.find("span.name").text(item.SuggestedFileName);
              $item.find("a.src-url").attr("href", item.Url).text(item.Url);
              $item.find("a.cancel").removeAttr("hidden");
          }
          var progress = "";
          if (item.IsInProgress) {
              progress = formatBytes(item.CurrentSpeed) + "/s - " + formatBytes(item.ReceivedBytes, 2);
              if (item.TotalBytes > 0) progress += " of " + formatBytes(item.TotalBytes, 2);
              if (item.PercentComplete > 0) progress += " (" + item.PercentComplete + "%)";
          } else {
              if (item.IsComplete) progress = "Complete";
              else if (item.IsCancelled) progress = "Cancelled";
              $item.find("a.cancel").attr("hidden","");
          }
          $item.find("span.status").text(progress);
      }

      function UpdateList() {
          host.getDownloads().then(function (res) {
              var list = JSON.parse(res);
              $.each(list, function (key, item) {
                  UpdateItem(item);
              });
          });
      }

  </script>

页面加载后,它会设置一个计时器,每 500 毫秒更新一次下载列表。

          timer = setInterval(UpdateList, 500);

update 函数从主应用程序请求下载项列表,并使用此信息填充列表。

      function UpdateList() {
          host.getDownloads().then(function (res) {
              var list = JSON.parse(res);
              $.each(list, function (key, item) {
                  UpdateItem(item);
              });
          });
      }

使用 CefSharp,您可以从 JavaScript 调用应用程序的 .NET 函数。

以下是将 .NET 类绑定到浏览器对象的代码行:

            if (url.StartsWith("chrome:"))
            {
                browser.RegisterAsyncJsObject("host", host, true);
            }

出于安全原因,只有从应用程序内部加载的页面才会被赋予“host”对象。

您的“host”对象上的函数只能返回原始类型,如 String、Integer、Float 等,因此我使用了 JSON 序列化器(参见参考)来为复杂对象返回 JSON 字符串。

            public string getDownloads()
            {
                lock(myForm.downloads)
                {
                    string x = JsonSerializer.SerializeToString(myForm.downloads);
                    return x;
                }
            }

JavaScript 中的调用函数使用 JSON.parse 来访问此对象。

      function UpdateList() {
          host.getDownloads().then(function (res) {
              var list = JSON.parse(res);
              $.each(list, function (key, item) {
                  UpdateItem(item);
              });
          });
      }

最具挑战性的部分

Google Chrome 在下载页面上显示下载文件的图标。

Google Chrome downloads page

为了在我的应用程序中实现相同的功能,我在 SchemeHandler 中添加了一个额外的 case:

            if (uri.Host == "fileicon")
            {
                Task.Factory.StartNew(() =>
                {
                    using (callback)
                    {
                        stream = GetFileIcon(fileName, IconReader.IconSize.Large);
                        mimeType = ResourceHandler.GetMimeType(".png");
                        callback.Continue();
                    }
                });
                return true;
            }

如果您在地址栏中键入“chrome://fileicon/somefile.zip”,则会执行此代码,它会请求操作系统返回给定文件类型的图标,然后将其返回给 CefSharp 浏览器。

选项卡组件

我搜索了很多,但没有在互联网上找到一个看起来像 Google Chrome 的“免费”选项卡组件。如果您知道一个,请在评论中提供链接。

参考文献

历史

  • 版本 1.0.0 - 应用程序和文章的初始版本
© . All rights reserved.