Since version 3.x of μTorrent some people like me got away from it, since it became bloatware. That all started when it was purchased by BitTorrent, Inc., and the client has gone down hill from there. It’s still the most popular torrent client on the planet though.
So I moved to qBittorrent which is a wonderful client, but it lacks a feature that I really use and need: the ability to specify cookies when downloading a torrent from a URL using the WebUI.
I also wanted another little feature that I really need: the forms have to support the autocomplete feature of the internet browsers. So when I fill a field in a form, the browser saves it for future use. For this to work the forms need to be actual forms, and you’ll see qBittorrent doesn’t use them.
Since qBittorrent is an Open Source project, it’s pretty easy to implement new features on.
Note: All this post is be based in qBittorrent beta version 3.3.0 + gitd60f2fc
Let’s take a look at the download page located at src/webui/www/public/download.html:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>QBT_TR(Add Torrent Link)QBT_TR</title> <link rel="stylesheet" href="css/style.css" type="text/css" /> <script type="text/javascript" src="scripts/mootools-1.2-core-yc.js" charset="utf-8"> </script> <script type="text/javascript" src="scripts/download.js" charset="utf-8"> </script> </head> <body> <center> <h2 class="vcenter"> QBT_TR(Download Torrents from their URLs or Magnet links)QBT_TR </h2> <textarea id="urls" rows="10"></textarea> QBT_TR(Only one link per line)QBT_TR <input type="button" value="QBT_TR(Download)QBT_TR" id="downButton"/> </center> </body> </html>
As you can see it’s not a real HTML form. It’s just a single field with a button. Then there’s some JavaScript to make a REST request to download the URL that the user types in the field. This is src/webui/www/public/scripts/download.js:
window.addEvent('domready', function() { $('urls').focus(); $('downButton').addEvent('click', function(e) { new Event(e).stop(); new Request({ url: 'command/download', method: 'post', data: { urls: $('urls').value }, onComplete: function() { window.parent.document.getElementById('downloadPage'). parentNode.removeChild( window.parent.document.getElementById('downloadPage') ); } }).send(); }); });
Since it is not a real form, this won’t allow my browser to save the fields so I can reuse them. I don’t want to copy/paste the same cookie over and over, so I had to modify this into a real form. Here’s what I want:
And this is what I came up with:
QBT_TR(Add Torrent Link)QBT_TR
So basically I’m doing three things here:
- Including a field for the download location and retrieving the default location via REST from the server. That’s because I want my downloads organized in several folders.
- Including a cookie field where I can type all the cookies if I’m downloading from a URL of a private site that needs an authentication first.
- Also I added a label field to set the label and keep things organized.
From the server side I had to include the cookies in the AddTorrentParams, that’s a structure that contains several torrents parameters. I included:
QList cookies;
And modified a little bit the command/download request from the server side, to include the parsing of the cookies
void WebApplication::action_command_download() { CHECK_URI(0); CHECK_PARAMETERS("urls" << "savepath" << "label" << "cookie"); QString urls = request().posts["urls"]; QStringList list = urls.split('\n'); QString savepath = request().posts["savepath"]; QString label = request().posts["label"]; QString cookie = request().posts["cookie"]; QList<QNetworkCookie> cookies; if (!cookie.isEmpty()) { QStringList cookiesStr = cookie.split("; "); foreach (QString cookieStr, cookiesStr) { cookieStr = cookieStr.trimmed(); QStringList pair = cookieStr.split("="); if (pair.size() == 2) { QNetworkCookie c(pair[0].toLatin1(),pair[1].toLatin1()); cookies << c; } } } savepath = savepath.trimmed(); label = label.trimmed(); foreach (QString url, list) { url = url.trimmed(); if (!url.isEmpty()) { if (url.startsWith("bc://bt/", Qt::CaseInsensitive)) { qDebug("Converting bc link to magnet link"); url = Utils::Misc::bcLinkToMagnet(url); } if ((url.size() == 40 && !url.contains(QRegExp("[^0-9A-Fa-f]"))) || (url.size() == 32 && !url.contains(QRegExp("[^2-7A-Za-z]")))) url = "magnet:?xt=urn:btih:" + url; BitTorrent::AddTorrentParams params; params.savePath = savepath; params.label = label; params.cookies = cookies; BitTorrent::Session::instance()->addTorrent(url, params); } } print(QString("<script type=\"text/javascript\">" "window.parent.closeWindows();</script>")); }
I was losing the trick of closing the window automatically when the download is complete, since I’m not using click event of the downButton. The only solution I could came up with was to send an HTML back that closed the window from the server. That’s the last line you see there in the code with a window.parent.closeWindows().
Now all I need that the DownloadManager actually send the cookies header in the download request, so I changed the downloadUrl() function a little bit, adding a new argument and setting the cookies:
DownloadHandler *DownloadManager::downloadUrl(const QString &url, bool saveToFile, qint64 limit, bool handleRedirectToMagnet, QList *cookies) { // Update proxy settings applyProxySettings(); // Process download request qDebug("url is %s", qPrintable(url)); const QUrl qurl = QUrl::fromEncoded(url.toUtf8()); QNetworkRequest request(qurl); // Spoof Firefox 38 user agent to avoid web server banning request.setRawHeader("User-Agent", "Mozilla/5.0 (X11; Linux i686; rv:38.0) Gecko/20100101 Firefox/38.0"); // Spoof HTTP Referer to allow adding torrent link from Torcache/KickAssTorrents request.setRawHeader("Referer", request.url().toEncoded().data()); qDebug("Downloading %s...", request.url().toEncoded().data()); // accept gzip request.setRawHeader("Accept-Encoding", "gzip"); if (cookies) setCookiesFromUrl(*cookies, qurl); return new DownloadHandler(m_networkManager.get(request), this, saveToFile, limit, handleRedirectToMagnet); }
And that’s it and it works really great. Now see it in action:
Yay! The browser saved the last cookie I used!
If you’re interested in the whole patch you can see the code here.