Introduction
My favorite browser is Google Chrome. It's fast and easy to use.
When I hit a web site which I can not access because of a web filter, one of my options is to use the original Tor Browser which is a custom version of Firefox and I can simply say that "I don't like it".
When I read the excellent article "Tor.NET - A managed Tor network library" by Chris Copeland, I started to play with the sample application in the project which uses the Internet Explorer component (WebBrowser). I've been using CefSharp Chromium browser component in my projects for some time and I changed the WebBrowser in the project with CefSharp and this is how this application came to life.
Ingredients
This application uses CefSharp as the web browser and the Tor.NET library to connect to the Tor network via an HTTP proxy. Please see the references section for detailed information about these two components.
Application
This is how the application looks when you start it.
It is a standard tabbed web browser which mimics Google Chrome.
When the application starts, it connects to the Tor network and starts the HTTP proxy. When Tor is ready to use, the address bar goes pale green.
"Downloads" button opens the downloads page.
"DuckDuckGo" button opens the alternative DuckDuckGo search engine with a ".onion" Tor address.
You can check if you are really connected to the Tor network by clicking the "Check if Tor is active" button.
Interesting Parts of the Code
The application is a WinForms application with only one form. However there are some interesting parts in the code which may be helpful for curious coders.
The following is the CefSharp initialization code.
CefSettings settings = new CefSettings();
settings.CefCommandLineArgs.Add("proxy-server", "127.0.0.1:8182");
settings.RegisterScheme(new CefCustomScheme
{
SchemeName = SchemeHandlerFactory.SchemeName,
SchemeHandlerFactory = new SchemeHandlerFactory()
});
We make the CefSharp component to use our Tor proxy by adding the "proxy-server" command line argument.
If you want to use Flash in your browser, you can copy the PepperFlash folder from your Chrome installation to the application directory and uncomment the PepperFlash related line. Chrome is generally located in a folder like the following one:
C:\Program Files (x86)\Google\Chrome\Application\47.0.2526.111
But keep in mind that, if you are really concerned about your privacy, you are not recommended to use Flash when using Tor because Flash plugins might reach the Internet bypassing your proxy.
Tor is initialized by calling the InitializeTor function which is an exact copy from the Tor.NET sample application.
Handlers are used to make CefSharp to "behave". Here is a list of the CefSharp handlers and what they do:
DownloadHandler: Tells the application when a new download starts and about the progress of the ongoing downloads.
KeyboardHandler: When the focus is on the CefSharp browser, it "eats" all the keys pressed by the user. The application uses Ctrl-F4 to close the active browser tab so this handler helps to inform the application that Ctrl-F4 was pressed in the browser.
LifeSpanHandler: CefSharp calls the OnBeforePopup function of this handler before it opens a pop-up window and it tells CefSharp to open it in a new tab and not in a seperate window.
MenuHandler: I added the items from the Chrome Browser which I use most to the browser context menu. This handler does that stuff. An interesting extra feature of this context menu is the "Save as Pdf" option which does not exist in Google Chrome.
SchemeHandler: This handler helps to load the web pages that start with "chrome://".
"Downloads" Page
Google Chrome uses HTML interface for Settings, Downloads, Extensions and other pages so I did the same for the "Downloads" page.
The "Downloads" page of the application is a "clone" from the Chrome Browser which is an HTML page stored in the "storage" folder. To be able to load this page in the application, I defined a SchemeHandler and named it "chrome:". So when you type "chrome://storage/downloads.htm" in the address bar, CefSharp asks the SchemeHandler how to handle it and the handler returns the requested file from the storage folder.
Here is the JavaScript code from the downloads.htm page.
<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);
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>
When the page loads, it sets a timer to update the downloads list every 500 miliseconds.
timer = setInterval(UpdateList, 500);
The update function requests the list of download items from the main application and populates the list using this information.
function UpdateList() {
host.getDownloads().then(function (res) {
var list = JSON.parse(res);
$.each(list, function (key, item) {
UpdateItem(item);
});
});
}
With CefSharp, you can call .NET functions of your application from JavaScript.
The following is the line of code which binds a .NET class to the browser object:
if (url.StartsWith("chrome:"))
{
browser.RegisterAsyncJsObject("host", host, true);
}
For security reasons, only pages loaded from inside the application are given a "host" object.
Functions on your "host" object can return only primitive types like String, Integer, Float etc. so I used a JSON Serializer (see References) to return a JSON string for complex objects.
public string getDownloads()
{
lock(myForm.downloads)
{
string x = JsonSerializer.SerializeToString(myForm.downloads);
return x;
}
}
The calling function in JavaScript uses JSON.parse to access this object:
function UpdateList() {
host.getDownloads().then(function (res) {
var list = JSON.parse(res);
$.each(list, function (key, item) {
UpdateItem(item);
});
});
}
Most Challenging Part
Google Chrome displays the icons of the downloaded files in the Downloads page.
To able to do the same in my application, I added an extra case in the SchemeHandler:
if (uri.Host == "fileicon")
{
Task.Factory.StartNew(() =>
{
using (callback)
{
stream = GetFileIcon(fileName, IconReader.IconSize.Large);
mimeType = ResourceHandler.GetMimeType(".png");
callback.Continue();
}
});
return true;
}
If you type "chrome://fileicon/somefile.zip" to the address bar, this code gets executed and it asks the operating system to return the icon for the given file type which it returns to the CefSharp browser.
Tab Component
I googled a lot but could not find a "free" tab component which looks like Google Chrome's on the Internet. If you know one, please provide a link in the comments.
References
History
- Version 1.0.0 - Initial version of the application and the article